summaryrefslogtreecommitdiffstats
path: root/app/display
diff options
context:
space:
mode:
Diffstat (limited to 'app/display')
-rw-r--r--app/display/Makefile.am247
-rw-r--r--app/display/Makefile.in1545
-rw-r--r--app/display/display-enums.c592
-rw-r--r--app/display/display-enums.h275
-rw-r--r--app/display/display-types.h53
-rw-r--r--app/display/gimpcanvas-style.c458
-rw-r--r--app/display/gimpcanvas-style.h81
-rw-r--r--app/display/gimpcanvas.c298
-rw-r--r--app/display/gimpcanvas.h74
-rw-r--r--app/display/gimpcanvasarc.c369
-rw-r--r--app/display/gimpcanvasarc.h69
-rw-r--r--app/display/gimpcanvasboundary.c384
-rw-r--r--app/display/gimpcanvasboundary.h60
-rw-r--r--app/display/gimpcanvasbufferpreview.c263
-rw-r--r--app/display/gimpcanvasbufferpreview.h56
-rw-r--r--app/display/gimpcanvascanvasboundary.c270
-rw-r--r--app/display/gimpcanvascanvasboundary.h58
-rw-r--r--app/display/gimpcanvascorner.c468
-rw-r--r--app/display/gimpcanvascorner.h72
-rw-r--r--app/display/gimpcanvascursor.c228
-rw-r--r--app/display/gimpcanvascursor.h59
-rw-r--r--app/display/gimpcanvasgrid.c414
-rw-r--r--app/display/gimpcanvasgrid.h56
-rw-r--r--app/display/gimpcanvasgroup.c387
-rw-r--r--app/display/gimpcanvasgroup.h68
-rw-r--r--app/display/gimpcanvasguide.c295
-rw-r--r--app/display/gimpcanvasguide.h62
-rw-r--r--app/display/gimpcanvashandle.c703
-rw-r--r--app/display/gimpcanvashandle.h92
-rw-r--r--app/display/gimpcanvasitem-utils.c474
-rw-r--r--app/display/gimpcanvasitem-utils.h81
-rw-r--r--app/display/gimpcanvasitem.c731
-rw-r--r--app/display/gimpcanvasitem.h147
-rw-r--r--app/display/gimpcanvaslayerboundary.c322
-rw-r--r--app/display/gimpcanvaslayerboundary.h58
-rw-r--r--app/display/gimpcanvaslimit.c760
-rw-r--r--app/display/gimpcanvaslimit.h84
-rw-r--r--app/display/gimpcanvasline.c281
-rw-r--r--app/display/gimpcanvasline.h65
-rw-r--r--app/display/gimpcanvaspassepartout.c235
-rw-r--r--app/display/gimpcanvaspassepartout.h59
-rw-r--r--app/display/gimpcanvaspath.c352
-rw-r--r--app/display/gimpcanvaspath.h63
-rw-r--r--app/display/gimpcanvaspen.c231
-rw-r--r--app/display/gimpcanvaspen.h60
-rw-r--r--app/display/gimpcanvaspolygon.c505
-rw-r--r--app/display/gimpcanvaspolygon.h68
-rw-r--r--app/display/gimpcanvasprogress.c459
-rw-r--r--app/display/gimpcanvasprogress.h58
-rw-r--r--app/display/gimpcanvasproxygroup.c197
-rw-r--r--app/display/gimpcanvasproxygroup.h63
-rw-r--r--app/display/gimpcanvasrectangle.c360
-rw-r--r--app/display/gimpcanvasrectangle.h66
-rw-r--r--app/display/gimpcanvasrectangleguides.c422
-rw-r--r--app/display/gimpcanvasrectangleguides.h69
-rw-r--r--app/display/gimpcanvassamplepoint.c356
-rw-r--r--app/display/gimpcanvassamplepoint.h63
-rw-r--r--app/display/gimpcanvastextcursor.c386
-rw-r--r--app/display/gimpcanvastextcursor.h58
-rw-r--r--app/display/gimpcanvastransformguides.c683
-rw-r--r--app/display/gimpcanvastransformguides.h73
-rw-r--r--app/display/gimpcanvastransformpreview.c791
-rw-r--r--app/display/gimpcanvastransformpreview.h61
-rw-r--r--app/display/gimpcursorview.c887
-rw-r--r--app/display/gimpcursorview.h68
-rw-r--r--app/display/gimpdisplay-foreach.c308
-rw-r--r--app/display/gimpdisplay-foreach.h36
-rw-r--r--app/display/gimpdisplay-handlers.c128
-rw-r--r--app/display/gimpdisplay-handlers.h26
-rw-r--r--app/display/gimpdisplay.c985
-rw-r--r--app/display/gimpdisplay.h99
-rw-r--r--app/display/gimpdisplayshell-actions.c149
-rw-r--r--app/display/gimpdisplayshell-actions.h33
-rw-r--r--app/display/gimpdisplayshell-appearance.c606
-rw-r--r--app/display/gimpdisplayshell-appearance.h92
-rw-r--r--app/display/gimpdisplayshell-autoscroll.c184
-rw-r--r--app/display/gimpdisplayshell-autoscroll.h28
-rw-r--r--app/display/gimpdisplayshell-callbacks.c647
-rw-r--r--app/display/gimpdisplayshell-callbacks.h48
-rw-r--r--app/display/gimpdisplayshell-close.c447
-rw-r--r--app/display/gimpdisplayshell-close.h26
-rw-r--r--app/display/gimpdisplayshell-cursor.c295
-rw-r--r--app/display/gimpdisplayshell-cursor.h47
-rw-r--r--app/display/gimpdisplayshell-dnd.c770
-rw-r--r--app/display/gimpdisplayshell-dnd.h25
-rw-r--r--app/display/gimpdisplayshell-draw.c256
-rw-r--r--app/display/gimpdisplayshell-draw.h41
-rw-r--r--app/display/gimpdisplayshell-expose.c76
-rw-r--r--app/display/gimpdisplayshell-expose.h32
-rw-r--r--app/display/gimpdisplayshell-filter-dialog.c151
-rw-r--r--app/display/gimpdisplayshell-filter-dialog.h25
-rw-r--r--app/display/gimpdisplayshell-filter.c116
-rw-r--r--app/display/gimpdisplayshell-filter.h28
-rw-r--r--app/display/gimpdisplayshell-grab.c124
-rw-r--r--app/display/gimpdisplayshell-grab.h36
-rw-r--r--app/display/gimpdisplayshell-handlers.c1239
-rw-r--r--app/display/gimpdisplayshell-handlers.h26
-rw-r--r--app/display/gimpdisplayshell-icon.c140
-rw-r--r--app/display/gimpdisplayshell-icon.h26
-rw-r--r--app/display/gimpdisplayshell-items.c284
-rw-r--r--app/display/gimpdisplayshell-items.h49
-rw-r--r--app/display/gimpdisplayshell-layer-select.c305
-rw-r--r--app/display/gimpdisplayshell-layer-select.h27
-rw-r--r--app/display/gimpdisplayshell-profile.c336
-rw-r--r--app/display/gimpdisplayshell-profile.h30
-rw-r--r--app/display/gimpdisplayshell-progress.c163
-rw-r--r--app/display/gimpdisplayshell-progress.h28
-rw-r--r--app/display/gimpdisplayshell-render.c351
-rw-r--r--app/display/gimpdisplayshell-render.h29
-rw-r--r--app/display/gimpdisplayshell-rotate-dialog.c293
-rw-r--r--app/display/gimpdisplayshell-rotate-dialog.h25
-rw-r--r--app/display/gimpdisplayshell-rotate.c251
-rw-r--r--app/display/gimpdisplayshell-rotate.h40
-rw-r--r--app/display/gimpdisplayshell-rulers.c181
-rw-r--r--app/display/gimpdisplayshell-rulers.h25
-rw-r--r--app/display/gimpdisplayshell-scale-dialog.c293
-rw-r--r--app/display/gimpdisplayshell-scale-dialog.h25
-rw-r--r--app/display/gimpdisplayshell-scale.c1449
-rw-r--r--app/display/gimpdisplayshell-scale.h108
-rw-r--r--app/display/gimpdisplayshell-scroll.c529
-rw-r--r--app/display/gimpdisplayshell-scroll.h59
-rw-r--r--app/display/gimpdisplayshell-scrollbars.c244
-rw-r--r--app/display/gimpdisplayshell-scrollbars.h36
-rw-r--r--app/display/gimpdisplayshell-selection.c505
-rw-r--r--app/display/gimpdisplayshell-selection.h35
-rw-r--r--app/display/gimpdisplayshell-title.c564
-rw-r--r--app/display/gimpdisplayshell-title.h25
-rw-r--r--app/display/gimpdisplayshell-tool-events.c2144
-rw-r--r--app/display/gimpdisplayshell-tool-events.h52
-rw-r--r--app/display/gimpdisplayshell-transform.c1025
-rw-r--r--app/display/gimpdisplayshell-transform.h200
-rw-r--r--app/display/gimpdisplayshell-utils.c220
-rw-r--r--app/display/gimpdisplayshell-utils.h45
-rw-r--r--app/display/gimpdisplayshell.c2140
-rw-r--r--app/display/gimpdisplayshell.h340
-rw-r--r--app/display/gimpdisplayxfer.c289
-rw-r--r--app/display/gimpdisplayxfer.h37
-rw-r--r--app/display/gimpimagewindow.c2428
-rw-r--r--app/display/gimpimagewindow.h100
-rw-r--r--app/display/gimpmotionbuffer.c594
-rw-r--r--app/display/gimpmotionbuffer.h99
-rw-r--r--app/display/gimpmultiwindowstrategy.c89
-rw-r--r--app/display/gimpmultiwindowstrategy.h54
-rw-r--r--app/display/gimpnavigationeditor.c890
-rw-r--r--app/display/gimpnavigationeditor.h79
-rw-r--r--app/display/gimpscalecombobox.c562
-rw-r--r--app/display/gimpscalecombobox.h60
-rw-r--r--app/display/gimpsinglewindowstrategy.c157
-rw-r--r--app/display/gimpsinglewindowstrategy.h54
-rw-r--r--app/display/gimpstatusbar.c1747
-rw-r--r--app/display/gimpstatusbar.h158
-rw-r--r--app/display/gimptoolcompass.c1218
-rw-r--r--app/display/gimptoolcompass.h72
-rw-r--r--app/display/gimptooldialog.c208
-rw-r--r--app/display/gimptooldialog.h58
-rw-r--r--app/display/gimptoolfocus.c1209
-rw-r--r--app/display/gimptoolfocus.h58
-rw-r--r--app/display/gimptoolgui.c1037
-rw-r--r--app/display/gimptoolgui.h115
-rw-r--r--app/display/gimptoolgyroscope.c879
-rw-r--r--app/display/gimptoolgyroscope.h58
-rw-r--r--app/display/gimptoolhandlegrid.c1186
-rw-r--r--app/display/gimptoolhandlegrid.h62
-rw-r--r--app/display/gimptoolline.c1796
-rw-r--r--app/display/gimptoolline.h99
-rw-r--r--app/display/gimptoolpath.c1904
-rw-r--r--app/display/gimptoolpath.h68
-rw-r--r--app/display/gimptoolpolygon.c1495
-rw-r--r--app/display/gimptoolpolygon.h66
-rw-r--r--app/display/gimptoolrectangle.c4256
-rw-r--r--app/display/gimptoolrectangle.h124
-rw-r--r--app/display/gimptoolrotategrid.c330
-rw-r--r--app/display/gimptoolrotategrid.h65
-rw-r--r--app/display/gimptoolsheargrid.c360
-rw-r--r--app/display/gimptoolsheargrid.h65
-rw-r--r--app/display/gimptooltransform3dgrid.c1162
-rw-r--r--app/display/gimptooltransform3dgrid.h65
-rw-r--r--app/display/gimptooltransformgrid.c2494
-rw-r--r--app/display/gimptooltransformgrid.h99
-rw-r--r--app/display/gimptoolwidget.c1117
-rw-r--r--app/display/gimptoolwidget.h318
-rw-r--r--app/display/gimptoolwidgetgroup.c731
-rw-r--r--app/display/gimptoolwidgetgroup.h65
183 files changed, 67738 insertions, 0 deletions
diff --git a/app/display/Makefile.am b/app/display/Makefile.am
new file mode 100644
index 0000000..dbee710
--- /dev/null
+++ b/app/display/Makefile.am
@@ -0,0 +1,247 @@
+## Process this file with automake to produce Makefile.in
+
+if PLATFORM_OSX
+xobjective_c = "-xobjective-c"
+xobjective_cxx = "-xobjective-c++"
+xnone = "-xnone"
+endif
+
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Display\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(GEGL_CFLAGS) \
+ $(GTK_CFLAGS) \
+ -I$(includedir)
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(xnone)
+
+noinst_LIBRARIES = libappdisplay.a
+
+libappdisplay_a_sources = \
+ display-enums.h \
+ display-types.h \
+ gimpcanvas.c \
+ gimpcanvas.h \
+ gimpcanvas-style.c \
+ gimpcanvas-style.h \
+ gimpcanvasarc.c \
+ gimpcanvasarc.h \
+ gimpcanvasboundary.c \
+ gimpcanvasboundary.h \
+ gimpcanvasbufferpreview.c \
+ gimpcanvasbufferpreview.h \
+ gimpcanvascanvasboundary.c \
+ gimpcanvascanvasboundary.h \
+ gimpcanvascorner.c \
+ gimpcanvascorner.h \
+ gimpcanvascursor.c \
+ gimpcanvascursor.h \
+ gimpcanvasgrid.c \
+ gimpcanvasgrid.h \
+ gimpcanvasgroup.c \
+ gimpcanvasgroup.h \
+ gimpcanvasguide.c \
+ gimpcanvasguide.h \
+ gimpcanvashandle.c \
+ gimpcanvashandle.h \
+ gimpcanvasitem.c \
+ gimpcanvasitem.h \
+ gimpcanvasitem-utils.c \
+ gimpcanvasitem-utils.h \
+ gimpcanvaslayerboundary.c \
+ gimpcanvaslayerboundary.h \
+ gimpcanvaslimit.c \
+ gimpcanvaslimit.h \
+ gimpcanvasline.c \
+ gimpcanvasline.h \
+ gimpcanvaspassepartout.c \
+ gimpcanvaspassepartout.h \
+ gimpcanvaspath.c \
+ gimpcanvaspath.h \
+ gimpcanvaspen.c \
+ gimpcanvaspen.h \
+ gimpcanvaspolygon.c \
+ gimpcanvaspolygon.h \
+ gimpcanvasprogress.c \
+ gimpcanvasprogress.h \
+ gimpcanvasproxygroup.c \
+ gimpcanvasproxygroup.h \
+ gimpcanvasrectangle.c \
+ gimpcanvasrectangle.h \
+ gimpcanvasrectangleguides.c \
+ gimpcanvasrectangleguides.h \
+ gimpcanvassamplepoint.c \
+ gimpcanvassamplepoint.h \
+ gimpcanvastextcursor.c \
+ gimpcanvastextcursor.h \
+ gimpcanvastransformguides.c \
+ gimpcanvastransformguides.h \
+ gimpcanvastransformpreview.c \
+ gimpcanvastransformpreview.h \
+ gimpcursorview.c \
+ gimpcursorview.h \
+ gimpdisplay.c \
+ gimpdisplay.h \
+ gimpdisplay-foreach.c \
+ gimpdisplay-foreach.h \
+ gimpdisplay-handlers.c \
+ gimpdisplay-handlers.h \
+ gimpdisplayshell.c \
+ gimpdisplayshell.h \
+ gimpdisplayshell-actions.c \
+ gimpdisplayshell-actions.h \
+ gimpdisplayshell-appearance.c \
+ gimpdisplayshell-appearance.h \
+ gimpdisplayshell-autoscroll.c \
+ gimpdisplayshell-autoscroll.h \
+ gimpdisplayshell-callbacks.c \
+ gimpdisplayshell-callbacks.h \
+ gimpdisplayshell-close.c \
+ gimpdisplayshell-close.h \
+ gimpdisplayshell-cursor.c \
+ gimpdisplayshell-cursor.h \
+ gimpdisplayshell-dnd.c \
+ gimpdisplayshell-dnd.h \
+ gimpdisplayshell-draw.c \
+ gimpdisplayshell-draw.h \
+ gimpdisplayshell-expose.c \
+ gimpdisplayshell-expose.h \
+ gimpdisplayshell-grab.c \
+ gimpdisplayshell-grab.h \
+ gimpdisplayshell-handlers.c \
+ gimpdisplayshell-handlers.h \
+ gimpdisplayshell-filter.c \
+ gimpdisplayshell-filter.h \
+ gimpdisplayshell-filter-dialog.c \
+ gimpdisplayshell-filter-dialog.h \
+ gimpdisplayshell-layer-select.c \
+ gimpdisplayshell-layer-select.h \
+ gimpdisplayshell-icon.c \
+ gimpdisplayshell-icon.h \
+ gimpdisplayshell-items.c \
+ gimpdisplayshell-items.h \
+ gimpdisplayshell-profile.c \
+ gimpdisplayshell-profile.h \
+ gimpdisplayshell-progress.c \
+ gimpdisplayshell-progress.h \
+ gimpdisplayshell-render.c \
+ gimpdisplayshell-render.h \
+ gimpdisplayshell-rotate.c \
+ gimpdisplayshell-rotate.h \
+ gimpdisplayshell-rotate-dialog.c \
+ gimpdisplayshell-rotate-dialog.h \
+ gimpdisplayshell-rulers.c \
+ gimpdisplayshell-rulers.h \
+ gimpdisplayshell-scale.c \
+ gimpdisplayshell-scale.h \
+ gimpdisplayshell-scale-dialog.c \
+ gimpdisplayshell-scale-dialog.h \
+ gimpdisplayshell-scroll.c \
+ gimpdisplayshell-scroll.h \
+ gimpdisplayshell-scrollbars.c \
+ gimpdisplayshell-scrollbars.h \
+ gimpdisplayshell-selection.c \
+ gimpdisplayshell-selection.h \
+ gimpdisplayshell-title.c \
+ gimpdisplayshell-title.h \
+ gimpdisplayshell-tool-events.c \
+ gimpdisplayshell-tool-events.h \
+ gimpdisplayshell-transform.c \
+ gimpdisplayshell-transform.h \
+ gimpdisplayshell-utils.c \
+ gimpdisplayshell-utils.h \
+ gimpdisplayxfer.c \
+ gimpdisplayxfer.h \
+ gimpimagewindow.c \
+ gimpimagewindow.h \
+ gimpmotionbuffer.c \
+ gimpmotionbuffer.h \
+ gimpmultiwindowstrategy.c \
+ gimpmultiwindowstrategy.h \
+ gimpnavigationeditor.c \
+ gimpnavigationeditor.h \
+ gimpscalecombobox.c \
+ gimpscalecombobox.h \
+ gimpsinglewindowstrategy.c \
+ gimpsinglewindowstrategy.h \
+ gimpstatusbar.c \
+ gimpstatusbar.h \
+ gimptooldialog.c \
+ gimptooldialog.h \
+ gimptoolgui.c \
+ gimptoolgui.h \
+ gimptoolcompass.c \
+ gimptoolcompass.h \
+ gimptoolfocus.h \
+ gimptoolfocus.c \
+ gimptoolgyroscope.c \
+ gimptoolgyroscope.h \
+ gimptoolhandlegrid.c \
+ gimptoolhandlegrid.h \
+ gimptoolline.c \
+ gimptoolline.h \
+ gimptoolpath.c \
+ gimptoolpath.h \
+ gimptoolpolygon.c \
+ gimptoolpolygon.h \
+ gimptoolrectangle.c \
+ gimptoolrectangle.h \
+ gimptoolrotategrid.c \
+ gimptoolrotategrid.h \
+ gimptoolsheargrid.c \
+ gimptoolsheargrid.h \
+ gimptooltransform3dgrid.c \
+ gimptooltransform3dgrid.h \
+ gimptooltransformgrid.c \
+ gimptooltransformgrid.h \
+ gimptoolwidget.c \
+ gimptoolwidget.h \
+ gimptoolwidgetgroup.c \
+ gimptoolwidgetgroup.h
+
+libappdisplay_a_built_sources = display-enums.c
+
+libappdisplay_a_SOURCES = \
+ $(libappdisplay_a_built_sources) \
+ $(libappdisplay_a_sources)
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-dec
+CLEANFILES = $(gen_sources)
+
+xgen-dec: $(srcdir)/display-enums.h $(GIMP_MKENUMS) Makefile.am
+ $(AM_V_GEN) $(GIMP_MKENUMS) \
+ --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"display-enums.h\"\n#include\"gimp-intl.h\"" \
+ --fprod "\n/* enumerations from \"@basename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n" \
+ --dhead " static const Gimp@Type@Desc descs[] =\n {" \
+ --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \
+ --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \
+ $< > $@
+
+# copy the generated enum file back to the source directory only if it's
+# changed; otherwise, only update its timestamp, so that the recipe isn't
+# executed again on the next build, however, allow this to (harmlessly) fail,
+# to support building from a read-only source tree.
+$(srcdir)/display-enums.c: xgen-dec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
diff --git a/app/display/Makefile.in b/app/display/Makefile.in
new file mode 100644
index 0000000..d1c91e8
--- /dev/null
+++ b/app/display/Makefile.in
@@ -0,0 +1,1545 @@
+# Makefile.in generated by automake 1.16.2 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = app/display
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/gtk-doc.m4 \
+ $(top_srcdir)/m4macros/intltool.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4macros/alsa.m4 \
+ $(top_srcdir)/m4macros/ax_compare_version.m4 \
+ $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \
+ $(top_srcdir)/m4macros/detectcflags.m4 \
+ $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libappdisplay_a_AR = $(AR) $(ARFLAGS)
+libappdisplay_a_LIBADD =
+am__objects_1 = display-enums.$(OBJEXT)
+am__objects_2 = gimpcanvas.$(OBJEXT) gimpcanvas-style.$(OBJEXT) \
+ gimpcanvasarc.$(OBJEXT) gimpcanvasboundary.$(OBJEXT) \
+ gimpcanvasbufferpreview.$(OBJEXT) \
+ gimpcanvascanvasboundary.$(OBJEXT) gimpcanvascorner.$(OBJEXT) \
+ gimpcanvascursor.$(OBJEXT) gimpcanvasgrid.$(OBJEXT) \
+ gimpcanvasgroup.$(OBJEXT) gimpcanvasguide.$(OBJEXT) \
+ gimpcanvashandle.$(OBJEXT) gimpcanvasitem.$(OBJEXT) \
+ gimpcanvasitem-utils.$(OBJEXT) \
+ gimpcanvaslayerboundary.$(OBJEXT) gimpcanvaslimit.$(OBJEXT) \
+ gimpcanvasline.$(OBJEXT) gimpcanvaspassepartout.$(OBJEXT) \
+ gimpcanvaspath.$(OBJEXT) gimpcanvaspen.$(OBJEXT) \
+ gimpcanvaspolygon.$(OBJEXT) gimpcanvasprogress.$(OBJEXT) \
+ gimpcanvasproxygroup.$(OBJEXT) gimpcanvasrectangle.$(OBJEXT) \
+ gimpcanvasrectangleguides.$(OBJEXT) \
+ gimpcanvassamplepoint.$(OBJEXT) gimpcanvastextcursor.$(OBJEXT) \
+ gimpcanvastransformguides.$(OBJEXT) \
+ gimpcanvastransformpreview.$(OBJEXT) gimpcursorview.$(OBJEXT) \
+ gimpdisplay.$(OBJEXT) gimpdisplay-foreach.$(OBJEXT) \
+ gimpdisplay-handlers.$(OBJEXT) gimpdisplayshell.$(OBJEXT) \
+ gimpdisplayshell-actions.$(OBJEXT) \
+ gimpdisplayshell-appearance.$(OBJEXT) \
+ gimpdisplayshell-autoscroll.$(OBJEXT) \
+ gimpdisplayshell-callbacks.$(OBJEXT) \
+ gimpdisplayshell-close.$(OBJEXT) \
+ gimpdisplayshell-cursor.$(OBJEXT) \
+ gimpdisplayshell-dnd.$(OBJEXT) gimpdisplayshell-draw.$(OBJEXT) \
+ gimpdisplayshell-expose.$(OBJEXT) \
+ gimpdisplayshell-grab.$(OBJEXT) \
+ gimpdisplayshell-handlers.$(OBJEXT) \
+ gimpdisplayshell-filter.$(OBJEXT) \
+ gimpdisplayshell-filter-dialog.$(OBJEXT) \
+ gimpdisplayshell-layer-select.$(OBJEXT) \
+ gimpdisplayshell-icon.$(OBJEXT) \
+ gimpdisplayshell-items.$(OBJEXT) \
+ gimpdisplayshell-profile.$(OBJEXT) \
+ gimpdisplayshell-progress.$(OBJEXT) \
+ gimpdisplayshell-render.$(OBJEXT) \
+ gimpdisplayshell-rotate.$(OBJEXT) \
+ gimpdisplayshell-rotate-dialog.$(OBJEXT) \
+ gimpdisplayshell-rulers.$(OBJEXT) \
+ gimpdisplayshell-scale.$(OBJEXT) \
+ gimpdisplayshell-scale-dialog.$(OBJEXT) \
+ gimpdisplayshell-scroll.$(OBJEXT) \
+ gimpdisplayshell-scrollbars.$(OBJEXT) \
+ gimpdisplayshell-selection.$(OBJEXT) \
+ gimpdisplayshell-title.$(OBJEXT) \
+ gimpdisplayshell-tool-events.$(OBJEXT) \
+ gimpdisplayshell-transform.$(OBJEXT) \
+ gimpdisplayshell-utils.$(OBJEXT) gimpdisplayxfer.$(OBJEXT) \
+ gimpimagewindow.$(OBJEXT) gimpmotionbuffer.$(OBJEXT) \
+ gimpmultiwindowstrategy.$(OBJEXT) \
+ gimpnavigationeditor.$(OBJEXT) gimpscalecombobox.$(OBJEXT) \
+ gimpsinglewindowstrategy.$(OBJEXT) gimpstatusbar.$(OBJEXT) \
+ gimptooldialog.$(OBJEXT) gimptoolgui.$(OBJEXT) \
+ gimptoolcompass.$(OBJEXT) gimptoolfocus.$(OBJEXT) \
+ gimptoolgyroscope.$(OBJEXT) gimptoolhandlegrid.$(OBJEXT) \
+ gimptoolline.$(OBJEXT) gimptoolpath.$(OBJEXT) \
+ gimptoolpolygon.$(OBJEXT) gimptoolrectangle.$(OBJEXT) \
+ gimptoolrotategrid.$(OBJEXT) gimptoolsheargrid.$(OBJEXT) \
+ gimptooltransform3dgrid.$(OBJEXT) \
+ gimptooltransformgrid.$(OBJEXT) gimptoolwidget.$(OBJEXT) \
+ gimptoolwidgetgroup.$(OBJEXT)
+am_libappdisplay_a_OBJECTS = $(am__objects_1) $(am__objects_2)
+libappdisplay_a_OBJECTS = $(am_libappdisplay_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/display-enums.Po \
+ ./$(DEPDIR)/gimpcanvas-style.Po ./$(DEPDIR)/gimpcanvas.Po \
+ ./$(DEPDIR)/gimpcanvasarc.Po ./$(DEPDIR)/gimpcanvasboundary.Po \
+ ./$(DEPDIR)/gimpcanvasbufferpreview.Po \
+ ./$(DEPDIR)/gimpcanvascanvasboundary.Po \
+ ./$(DEPDIR)/gimpcanvascorner.Po \
+ ./$(DEPDIR)/gimpcanvascursor.Po ./$(DEPDIR)/gimpcanvasgrid.Po \
+ ./$(DEPDIR)/gimpcanvasgroup.Po ./$(DEPDIR)/gimpcanvasguide.Po \
+ ./$(DEPDIR)/gimpcanvashandle.Po \
+ ./$(DEPDIR)/gimpcanvasitem-utils.Po \
+ ./$(DEPDIR)/gimpcanvasitem.Po \
+ ./$(DEPDIR)/gimpcanvaslayerboundary.Po \
+ ./$(DEPDIR)/gimpcanvaslimit.Po ./$(DEPDIR)/gimpcanvasline.Po \
+ ./$(DEPDIR)/gimpcanvaspassepartout.Po \
+ ./$(DEPDIR)/gimpcanvaspath.Po ./$(DEPDIR)/gimpcanvaspen.Po \
+ ./$(DEPDIR)/gimpcanvaspolygon.Po \
+ ./$(DEPDIR)/gimpcanvasprogress.Po \
+ ./$(DEPDIR)/gimpcanvasproxygroup.Po \
+ ./$(DEPDIR)/gimpcanvasrectangle.Po \
+ ./$(DEPDIR)/gimpcanvasrectangleguides.Po \
+ ./$(DEPDIR)/gimpcanvassamplepoint.Po \
+ ./$(DEPDIR)/gimpcanvastextcursor.Po \
+ ./$(DEPDIR)/gimpcanvastransformguides.Po \
+ ./$(DEPDIR)/gimpcanvastransformpreview.Po \
+ ./$(DEPDIR)/gimpcursorview.Po \
+ ./$(DEPDIR)/gimpdisplay-foreach.Po \
+ ./$(DEPDIR)/gimpdisplay-handlers.Po ./$(DEPDIR)/gimpdisplay.Po \
+ ./$(DEPDIR)/gimpdisplayshell-actions.Po \
+ ./$(DEPDIR)/gimpdisplayshell-appearance.Po \
+ ./$(DEPDIR)/gimpdisplayshell-autoscroll.Po \
+ ./$(DEPDIR)/gimpdisplayshell-callbacks.Po \
+ ./$(DEPDIR)/gimpdisplayshell-close.Po \
+ ./$(DEPDIR)/gimpdisplayshell-cursor.Po \
+ ./$(DEPDIR)/gimpdisplayshell-dnd.Po \
+ ./$(DEPDIR)/gimpdisplayshell-draw.Po \
+ ./$(DEPDIR)/gimpdisplayshell-expose.Po \
+ ./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po \
+ ./$(DEPDIR)/gimpdisplayshell-filter.Po \
+ ./$(DEPDIR)/gimpdisplayshell-grab.Po \
+ ./$(DEPDIR)/gimpdisplayshell-handlers.Po \
+ ./$(DEPDIR)/gimpdisplayshell-icon.Po \
+ ./$(DEPDIR)/gimpdisplayshell-items.Po \
+ ./$(DEPDIR)/gimpdisplayshell-layer-select.Po \
+ ./$(DEPDIR)/gimpdisplayshell-profile.Po \
+ ./$(DEPDIR)/gimpdisplayshell-progress.Po \
+ ./$(DEPDIR)/gimpdisplayshell-render.Po \
+ ./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po \
+ ./$(DEPDIR)/gimpdisplayshell-rotate.Po \
+ ./$(DEPDIR)/gimpdisplayshell-rulers.Po \
+ ./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po \
+ ./$(DEPDIR)/gimpdisplayshell-scale.Po \
+ ./$(DEPDIR)/gimpdisplayshell-scroll.Po \
+ ./$(DEPDIR)/gimpdisplayshell-scrollbars.Po \
+ ./$(DEPDIR)/gimpdisplayshell-selection.Po \
+ ./$(DEPDIR)/gimpdisplayshell-title.Po \
+ ./$(DEPDIR)/gimpdisplayshell-tool-events.Po \
+ ./$(DEPDIR)/gimpdisplayshell-transform.Po \
+ ./$(DEPDIR)/gimpdisplayshell-utils.Po \
+ ./$(DEPDIR)/gimpdisplayshell.Po ./$(DEPDIR)/gimpdisplayxfer.Po \
+ ./$(DEPDIR)/gimpimagewindow.Po ./$(DEPDIR)/gimpmotionbuffer.Po \
+ ./$(DEPDIR)/gimpmultiwindowstrategy.Po \
+ ./$(DEPDIR)/gimpnavigationeditor.Po \
+ ./$(DEPDIR)/gimpscalecombobox.Po \
+ ./$(DEPDIR)/gimpsinglewindowstrategy.Po \
+ ./$(DEPDIR)/gimpstatusbar.Po ./$(DEPDIR)/gimptoolcompass.Po \
+ ./$(DEPDIR)/gimptooldialog.Po ./$(DEPDIR)/gimptoolfocus.Po \
+ ./$(DEPDIR)/gimptoolgui.Po ./$(DEPDIR)/gimptoolgyroscope.Po \
+ ./$(DEPDIR)/gimptoolhandlegrid.Po ./$(DEPDIR)/gimptoolline.Po \
+ ./$(DEPDIR)/gimptoolpath.Po ./$(DEPDIR)/gimptoolpolygon.Po \
+ ./$(DEPDIR)/gimptoolrectangle.Po \
+ ./$(DEPDIR)/gimptoolrotategrid.Po \
+ ./$(DEPDIR)/gimptoolsheargrid.Po \
+ ./$(DEPDIR)/gimptooltransform3dgrid.Po \
+ ./$(DEPDIR)/gimptooltransformgrid.Po \
+ ./$(DEPDIR)/gimptoolwidget.Po \
+ ./$(DEPDIR)/gimptoolwidgetgroup.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libappdisplay_a_SOURCES)
+DIST_SOURCES = $(libappdisplay_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+AA_LIBS = @AA_LIBS@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALL_LINGUAS = @ALL_LINGUAS@
+ALSA_CFLAGS = @ALSA_CFLAGS@
+ALSA_LIBS = @ALSA_LIBS@
+ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPSTREAM_UTIL = @APPSTREAM_UTIL@
+AR = @AR@
+AS = @AS@
+ATK_CFLAGS = @ATK_CFLAGS@
+ATK_LIBS = @ATK_LIBS@
+ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BABL_CFLAGS = @BABL_CFLAGS@
+BABL_LIBS = @BABL_LIBS@
+BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@
+BUG_REPORT_URL = @BUG_REPORT_URL@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+BZIP2_LIBS = @BZIP2_LIBS@
+CAIRO_CFLAGS = @CAIRO_CFLAGS@
+CAIRO_LIBS = @CAIRO_LIBS@
+CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@
+CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@
+CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@
+CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCAS = @CCAS@
+CCASDEPMODE = @CCASDEPMODE@
+CCASFLAGS = @CCASFLAGS@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CC_VERSION = @CC_VERSION@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DESKTOP_DATADIR = @DESKTOP_DATADIR@
+DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@
+DLLTOOL = @DLLTOOL@
+DOC_SHOOTER = @DOC_SHOOTER@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILE_AA = @FILE_AA@
+FILE_EXR = @FILE_EXR@
+FILE_HEIF = @FILE_HEIF@
+FILE_JP2_LOAD = @FILE_JP2_LOAD@
+FILE_MNG = @FILE_MNG@
+FILE_PDF_SAVE = @FILE_PDF_SAVE@
+FILE_PS = @FILE_PS@
+FILE_WMF = @FILE_WMF@
+FILE_XMC = @FILE_XMC@
+FILE_XPM = @FILE_XPM@
+FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@
+FONTCONFIG_LIBS = @FONTCONFIG_LIBS@
+FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@
+FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@
+FREETYPE_CFLAGS = @FREETYPE_CFLAGS@
+FREETYPE_LIBS = @FREETYPE_LIBS@
+GDBUS_CODEGEN = @GDBUS_CODEGEN@
+GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@
+GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@
+GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@
+GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@
+GEGL = @GEGL@
+GEGL_CFLAGS = @GEGL_CFLAGS@
+GEGL_LIBS = @GEGL_LIBS@
+GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@
+GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GEXIV2_CFLAGS = @GEXIV2_CFLAGS@
+GEXIV2_LIBS = @GEXIV2_LIBS@
+GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@
+GIMP_API_VERSION = @GIMP_API_VERSION@
+GIMP_APP_VERSION = @GIMP_APP_VERSION@
+GIMP_BINARY_AGE = @GIMP_BINARY_AGE@
+GIMP_COMMAND = @GIMP_COMMAND@
+GIMP_DATA_VERSION = @GIMP_DATA_VERSION@
+GIMP_FULL_NAME = @GIMP_FULL_NAME@
+GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@
+GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@
+GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@
+GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@
+GIMP_MKENUMS = @GIMP_MKENUMS@
+GIMP_MODULES = @GIMP_MODULES@
+GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@
+GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@
+GIMP_PLUGINS = @GIMP_PLUGINS@
+GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@
+GIMP_REAL_VERSION = @GIMP_REAL_VERSION@
+GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@
+GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@
+GIMP_UNSTABLE = @GIMP_UNSTABLE@
+GIMP_USER_VERSION = @GIMP_USER_VERSION@
+GIMP_VERSION = @GIMP_VERSION@
+GIO_CFLAGS = @GIO_CFLAGS@
+GIO_LIBS = @GIO_LIBS@
+GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@
+GIO_UNIX_LIBS = @GIO_UNIX_LIBS@
+GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@
+GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@
+GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@
+GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+GS_LIBS = @GS_LIBS@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
+GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
+GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
+GTKDOC_MKPDF = @GTKDOC_MKPDF@
+GTKDOC_REBASE = @GTKDOC_REBASE@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@
+GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@
+GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@
+GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@
+GUDEV_CFLAGS = @GUDEV_CFLAGS@
+GUDEV_LIBS = @GUDEV_LIBS@
+HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@
+HARFBUZZ_LIBS = @HARFBUZZ_LIBS@
+HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@
+HAVE_CXX14 = @HAVE_CXX14@
+HAVE_FINITE = @HAVE_FINITE@
+HAVE_ISFINITE = @HAVE_ISFINITE@
+HAVE_VFORK = @HAVE_VFORK@
+HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@
+INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@
+INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@
+INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@
+ISO_CODES_LOCATION = @ISO_CODES_LOCATION@
+JPEG_LIBS = @JPEG_LIBS@
+JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@
+JSON_GLIB_LIBS = @JSON_GLIB_LIBS@
+LCMS_CFLAGS = @LCMS_CFLAGS@
+LCMS_LIBS = @LCMS_LIBS@
+LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@
+LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@
+LIBHEIF_LIBS = @LIBHEIF_LIBS@
+LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@
+LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@
+LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@
+LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@
+LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LT_VERSION_INFO = @LT_VERSION_INFO@
+LZMA_CFLAGS = @LZMA_CFLAGS@
+LZMA_LIBS = @LZMA_LIBS@
+MAIL = @MAIL@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@
+MIME_INFO_LIBS = @MIME_INFO_LIBS@
+MIME_TYPES = @MIME_TYPES@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@
+MNG_CFLAGS = @MNG_CFLAGS@
+MNG_LIBS = @MNG_LIBS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@
+MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@
+NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@
+NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENEXR_CFLAGS = @OPENEXR_CFLAGS@
+OPENEXR_LIBS = @OPENEXR_LIBS@
+OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@
+OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@
+OPENJPEG_LIBS = @OPENJPEG_LIBS@
+OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@
+PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@
+PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@
+PATHSEP = @PATHSEP@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@
+PERL_VERSION = @PERL_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PNG_CFLAGS = @PNG_CFLAGS@
+PNG_LIBS = @PNG_LIBS@
+POFILES = @POFILES@
+POPPLER_CFLAGS = @POPPLER_CFLAGS@
+POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@
+POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@
+POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@
+POPPLER_LIBS = @POPPLER_LIBS@
+POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYBIN_PATH = @PYBIN_PATH@
+PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@
+PYCAIRO_LIBS = @PYCAIRO_LIBS@
+PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@
+PYGTK_CFLAGS = @PYGTK_CFLAGS@
+PYGTK_CODEGEN = @PYGTK_CODEGEN@
+PYGTK_DEFSDIR = @PYGTK_DEFSDIR@
+PYGTK_LIBS = @PYGTK_LIBS@
+PYLINK_LIBS = @PYLINK_LIBS@
+PYTHON = @PYTHON@
+PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_INCLUDES = @PYTHON_INCLUDES@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@
+RT_LIBS = @RT_LIBS@
+SCREENSHOT_LIBS = @SCREENSHOT_LIBS@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@
+SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@
+SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@
+STRIP = @STRIP@
+SVG_CFLAGS = @SVG_CFLAGS@
+SVG_LIBS = @SVG_LIBS@
+SYMPREFIX = @SYMPREFIX@
+TIFF_LIBS = @TIFF_LIBS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEBKIT_CFLAGS = @WEBKIT_CFLAGS@
+WEBKIT_LIBS = @WEBKIT_LIBS@
+WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@
+WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@
+WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@
+WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@
+WEBPMUX_LIBS = @WEBPMUX_LIBS@
+WEBP_CFLAGS = @WEBP_CFLAGS@
+WEBP_LIBS = @WEBP_LIBS@
+WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@
+WEB_PAGE = @WEB_PAGE@
+WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@
+WINDRES = @WINDRES@
+WMF_CFLAGS = @WMF_CFLAGS@
+WMF_CONFIG = @WMF_CONFIG@
+WMF_LIBS = @WMF_LIBS@
+WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@
+XDG_EMAIL = @XDG_EMAIL@
+XFIXES_CFLAGS = @XFIXES_CFLAGS@
+XFIXES_LIBS = @XFIXES_LIBS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@
+XMC_CFLAGS = @XMC_CFLAGS@
+XMC_LIBS = @XMC_LIBS@
+XMKMF = @XMKMF@
+XMLLINT = @XMLLINT@
+XMU_LIBS = @XMU_LIBS@
+XPM_LIBS = @XPM_LIBS@
+XSLTPROC = @XSLTPROC@
+XVFB_RUN = @XVFB_RUN@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+Z_LIBS = @Z_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gimpdatadir = @gimpdatadir@
+gimpdir = @gimpdir@
+gimplocaledir = @gimplocaledir@
+gimpplugindir = @gimpplugindir@
+gimpsysconfdir = @gimpsysconfdir@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+intltool__v_merge_options_ = @intltool__v_merge_options_@
+intltool__v_merge_options_0 = @intltool__v_merge_options_0@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+manpage_gimpdir = @manpage_gimpdir@
+mkdir_p = @mkdir_p@
+ms_librarian = @ms_librarian@
+mypaint_brushes_dir = @mypaint_brushes_dir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c"
+@PLATFORM_OSX_TRUE@xobjective_cxx = "-xobjective-c++"
+@PLATFORM_OSX_TRUE@xnone = "-xnone"
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Display\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(GEGL_CFLAGS) \
+ $(GTK_CFLAGS) \
+ -I$(includedir)
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(xnone)
+
+noinst_LIBRARIES = libappdisplay.a
+libappdisplay_a_sources = \
+ display-enums.h \
+ display-types.h \
+ gimpcanvas.c \
+ gimpcanvas.h \
+ gimpcanvas-style.c \
+ gimpcanvas-style.h \
+ gimpcanvasarc.c \
+ gimpcanvasarc.h \
+ gimpcanvasboundary.c \
+ gimpcanvasboundary.h \
+ gimpcanvasbufferpreview.c \
+ gimpcanvasbufferpreview.h \
+ gimpcanvascanvasboundary.c \
+ gimpcanvascanvasboundary.h \
+ gimpcanvascorner.c \
+ gimpcanvascorner.h \
+ gimpcanvascursor.c \
+ gimpcanvascursor.h \
+ gimpcanvasgrid.c \
+ gimpcanvasgrid.h \
+ gimpcanvasgroup.c \
+ gimpcanvasgroup.h \
+ gimpcanvasguide.c \
+ gimpcanvasguide.h \
+ gimpcanvashandle.c \
+ gimpcanvashandle.h \
+ gimpcanvasitem.c \
+ gimpcanvasitem.h \
+ gimpcanvasitem-utils.c \
+ gimpcanvasitem-utils.h \
+ gimpcanvaslayerboundary.c \
+ gimpcanvaslayerboundary.h \
+ gimpcanvaslimit.c \
+ gimpcanvaslimit.h \
+ gimpcanvasline.c \
+ gimpcanvasline.h \
+ gimpcanvaspassepartout.c \
+ gimpcanvaspassepartout.h \
+ gimpcanvaspath.c \
+ gimpcanvaspath.h \
+ gimpcanvaspen.c \
+ gimpcanvaspen.h \
+ gimpcanvaspolygon.c \
+ gimpcanvaspolygon.h \
+ gimpcanvasprogress.c \
+ gimpcanvasprogress.h \
+ gimpcanvasproxygroup.c \
+ gimpcanvasproxygroup.h \
+ gimpcanvasrectangle.c \
+ gimpcanvasrectangle.h \
+ gimpcanvasrectangleguides.c \
+ gimpcanvasrectangleguides.h \
+ gimpcanvassamplepoint.c \
+ gimpcanvassamplepoint.h \
+ gimpcanvastextcursor.c \
+ gimpcanvastextcursor.h \
+ gimpcanvastransformguides.c \
+ gimpcanvastransformguides.h \
+ gimpcanvastransformpreview.c \
+ gimpcanvastransformpreview.h \
+ gimpcursorview.c \
+ gimpcursorview.h \
+ gimpdisplay.c \
+ gimpdisplay.h \
+ gimpdisplay-foreach.c \
+ gimpdisplay-foreach.h \
+ gimpdisplay-handlers.c \
+ gimpdisplay-handlers.h \
+ gimpdisplayshell.c \
+ gimpdisplayshell.h \
+ gimpdisplayshell-actions.c \
+ gimpdisplayshell-actions.h \
+ gimpdisplayshell-appearance.c \
+ gimpdisplayshell-appearance.h \
+ gimpdisplayshell-autoscroll.c \
+ gimpdisplayshell-autoscroll.h \
+ gimpdisplayshell-callbacks.c \
+ gimpdisplayshell-callbacks.h \
+ gimpdisplayshell-close.c \
+ gimpdisplayshell-close.h \
+ gimpdisplayshell-cursor.c \
+ gimpdisplayshell-cursor.h \
+ gimpdisplayshell-dnd.c \
+ gimpdisplayshell-dnd.h \
+ gimpdisplayshell-draw.c \
+ gimpdisplayshell-draw.h \
+ gimpdisplayshell-expose.c \
+ gimpdisplayshell-expose.h \
+ gimpdisplayshell-grab.c \
+ gimpdisplayshell-grab.h \
+ gimpdisplayshell-handlers.c \
+ gimpdisplayshell-handlers.h \
+ gimpdisplayshell-filter.c \
+ gimpdisplayshell-filter.h \
+ gimpdisplayshell-filter-dialog.c \
+ gimpdisplayshell-filter-dialog.h \
+ gimpdisplayshell-layer-select.c \
+ gimpdisplayshell-layer-select.h \
+ gimpdisplayshell-icon.c \
+ gimpdisplayshell-icon.h \
+ gimpdisplayshell-items.c \
+ gimpdisplayshell-items.h \
+ gimpdisplayshell-profile.c \
+ gimpdisplayshell-profile.h \
+ gimpdisplayshell-progress.c \
+ gimpdisplayshell-progress.h \
+ gimpdisplayshell-render.c \
+ gimpdisplayshell-render.h \
+ gimpdisplayshell-rotate.c \
+ gimpdisplayshell-rotate.h \
+ gimpdisplayshell-rotate-dialog.c \
+ gimpdisplayshell-rotate-dialog.h \
+ gimpdisplayshell-rulers.c \
+ gimpdisplayshell-rulers.h \
+ gimpdisplayshell-scale.c \
+ gimpdisplayshell-scale.h \
+ gimpdisplayshell-scale-dialog.c \
+ gimpdisplayshell-scale-dialog.h \
+ gimpdisplayshell-scroll.c \
+ gimpdisplayshell-scroll.h \
+ gimpdisplayshell-scrollbars.c \
+ gimpdisplayshell-scrollbars.h \
+ gimpdisplayshell-selection.c \
+ gimpdisplayshell-selection.h \
+ gimpdisplayshell-title.c \
+ gimpdisplayshell-title.h \
+ gimpdisplayshell-tool-events.c \
+ gimpdisplayshell-tool-events.h \
+ gimpdisplayshell-transform.c \
+ gimpdisplayshell-transform.h \
+ gimpdisplayshell-utils.c \
+ gimpdisplayshell-utils.h \
+ gimpdisplayxfer.c \
+ gimpdisplayxfer.h \
+ gimpimagewindow.c \
+ gimpimagewindow.h \
+ gimpmotionbuffer.c \
+ gimpmotionbuffer.h \
+ gimpmultiwindowstrategy.c \
+ gimpmultiwindowstrategy.h \
+ gimpnavigationeditor.c \
+ gimpnavigationeditor.h \
+ gimpscalecombobox.c \
+ gimpscalecombobox.h \
+ gimpsinglewindowstrategy.c \
+ gimpsinglewindowstrategy.h \
+ gimpstatusbar.c \
+ gimpstatusbar.h \
+ gimptooldialog.c \
+ gimptooldialog.h \
+ gimptoolgui.c \
+ gimptoolgui.h \
+ gimptoolcompass.c \
+ gimptoolcompass.h \
+ gimptoolfocus.h \
+ gimptoolfocus.c \
+ gimptoolgyroscope.c \
+ gimptoolgyroscope.h \
+ gimptoolhandlegrid.c \
+ gimptoolhandlegrid.h \
+ gimptoolline.c \
+ gimptoolline.h \
+ gimptoolpath.c \
+ gimptoolpath.h \
+ gimptoolpolygon.c \
+ gimptoolpolygon.h \
+ gimptoolrectangle.c \
+ gimptoolrectangle.h \
+ gimptoolrotategrid.c \
+ gimptoolrotategrid.h \
+ gimptoolsheargrid.c \
+ gimptoolsheargrid.h \
+ gimptooltransform3dgrid.c \
+ gimptooltransform3dgrid.h \
+ gimptooltransformgrid.c \
+ gimptooltransformgrid.h \
+ gimptoolwidget.c \
+ gimptoolwidget.h \
+ gimptoolwidgetgroup.c \
+ gimptoolwidgetgroup.h
+
+libappdisplay_a_built_sources = display-enums.c
+libappdisplay_a_SOURCES = \
+ $(libappdisplay_a_built_sources) \
+ $(libappdisplay_a_sources)
+
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-dec
+CLEANFILES = $(gen_sources)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu app/display/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu app/display/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libappdisplay.a: $(libappdisplay_a_OBJECTS) $(libappdisplay_a_DEPENDENCIES) $(EXTRA_libappdisplay_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libappdisplay.a
+ $(AM_V_AR)$(libappdisplay_a_AR) libappdisplay.a $(libappdisplay_a_OBJECTS) $(libappdisplay_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libappdisplay.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display-enums.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvas-style.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvas.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasarc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasboundary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasbufferpreview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvascanvasboundary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvascorner.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvascursor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasgrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasgroup.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasguide.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvashandle.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasitem-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasitem.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaslayerboundary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaslimit.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasline.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspassepartout.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspath.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspen.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspolygon.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasprogress.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasproxygroup.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasrectangle.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasrectangleguides.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvassamplepoint.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvastextcursor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvastransformguides.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvastransformpreview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcursorview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplay-foreach.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplay-handlers.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplay.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-actions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-appearance.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-autoscroll.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-callbacks.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-close.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-cursor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-dnd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-draw.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-expose.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-filter.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-grab.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-handlers.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-icon.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-items.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-layer-select.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-profile.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-progress.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-render.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-rotate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-rulers.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scale.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scroll.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scrollbars.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-selection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-title.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-tool-events.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-transform.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayxfer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimagewindow.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmotionbuffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmultiwindowstrategy.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpnavigationeditor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscalecombobox.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsinglewindowstrategy.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstatusbar.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolcompass.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooldialog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolfocus.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolgui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolgyroscope.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolhandlegrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolline.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpath.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpolygon.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolrectangle.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolrotategrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolsheargrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooltransform3dgrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooltransformgrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolwidget.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolwidgetgroup.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/display-enums.Po
+ -rm -f ./$(DEPDIR)/gimpcanvas-style.Po
+ -rm -f ./$(DEPDIR)/gimpcanvas.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasarc.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasboundary.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasbufferpreview.Po
+ -rm -f ./$(DEPDIR)/gimpcanvascanvasboundary.Po
+ -rm -f ./$(DEPDIR)/gimpcanvascorner.Po
+ -rm -f ./$(DEPDIR)/gimpcanvascursor.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasgrid.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasgroup.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasguide.Po
+ -rm -f ./$(DEPDIR)/gimpcanvashandle.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasitem-utils.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasitem.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaslayerboundary.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaslimit.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasline.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspassepartout.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspath.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspen.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspolygon.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasprogress.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasproxygroup.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasrectangle.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasrectangleguides.Po
+ -rm -f ./$(DEPDIR)/gimpcanvassamplepoint.Po
+ -rm -f ./$(DEPDIR)/gimpcanvastextcursor.Po
+ -rm -f ./$(DEPDIR)/gimpcanvastransformguides.Po
+ -rm -f ./$(DEPDIR)/gimpcanvastransformpreview.Po
+ -rm -f ./$(DEPDIR)/gimpcursorview.Po
+ -rm -f ./$(DEPDIR)/gimpdisplay-foreach.Po
+ -rm -f ./$(DEPDIR)/gimpdisplay-handlers.Po
+ -rm -f ./$(DEPDIR)/gimpdisplay.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-actions.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-appearance.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-autoscroll.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-callbacks.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-close.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-cursor.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-dnd.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-draw.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-expose.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-filter.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-grab.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-handlers.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-icon.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-items.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-layer-select.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-profile.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-progress.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-render.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-rulers.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scale.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scroll.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scrollbars.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-selection.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-title.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-tool-events.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-transform.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-utils.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayxfer.Po
+ -rm -f ./$(DEPDIR)/gimpimagewindow.Po
+ -rm -f ./$(DEPDIR)/gimpmotionbuffer.Po
+ -rm -f ./$(DEPDIR)/gimpmultiwindowstrategy.Po
+ -rm -f ./$(DEPDIR)/gimpnavigationeditor.Po
+ -rm -f ./$(DEPDIR)/gimpscalecombobox.Po
+ -rm -f ./$(DEPDIR)/gimpsinglewindowstrategy.Po
+ -rm -f ./$(DEPDIR)/gimpstatusbar.Po
+ -rm -f ./$(DEPDIR)/gimptoolcompass.Po
+ -rm -f ./$(DEPDIR)/gimptooldialog.Po
+ -rm -f ./$(DEPDIR)/gimptoolfocus.Po
+ -rm -f ./$(DEPDIR)/gimptoolgui.Po
+ -rm -f ./$(DEPDIR)/gimptoolgyroscope.Po
+ -rm -f ./$(DEPDIR)/gimptoolhandlegrid.Po
+ -rm -f ./$(DEPDIR)/gimptoolline.Po
+ -rm -f ./$(DEPDIR)/gimptoolpath.Po
+ -rm -f ./$(DEPDIR)/gimptoolpolygon.Po
+ -rm -f ./$(DEPDIR)/gimptoolrectangle.Po
+ -rm -f ./$(DEPDIR)/gimptoolrotategrid.Po
+ -rm -f ./$(DEPDIR)/gimptoolsheargrid.Po
+ -rm -f ./$(DEPDIR)/gimptooltransform3dgrid.Po
+ -rm -f ./$(DEPDIR)/gimptooltransformgrid.Po
+ -rm -f ./$(DEPDIR)/gimptoolwidget.Po
+ -rm -f ./$(DEPDIR)/gimptoolwidgetgroup.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/display-enums.Po
+ -rm -f ./$(DEPDIR)/gimpcanvas-style.Po
+ -rm -f ./$(DEPDIR)/gimpcanvas.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasarc.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasboundary.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasbufferpreview.Po
+ -rm -f ./$(DEPDIR)/gimpcanvascanvasboundary.Po
+ -rm -f ./$(DEPDIR)/gimpcanvascorner.Po
+ -rm -f ./$(DEPDIR)/gimpcanvascursor.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasgrid.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasgroup.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasguide.Po
+ -rm -f ./$(DEPDIR)/gimpcanvashandle.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasitem-utils.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasitem.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaslayerboundary.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaslimit.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasline.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspassepartout.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspath.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspen.Po
+ -rm -f ./$(DEPDIR)/gimpcanvaspolygon.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasprogress.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasproxygroup.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasrectangle.Po
+ -rm -f ./$(DEPDIR)/gimpcanvasrectangleguides.Po
+ -rm -f ./$(DEPDIR)/gimpcanvassamplepoint.Po
+ -rm -f ./$(DEPDIR)/gimpcanvastextcursor.Po
+ -rm -f ./$(DEPDIR)/gimpcanvastransformguides.Po
+ -rm -f ./$(DEPDIR)/gimpcanvastransformpreview.Po
+ -rm -f ./$(DEPDIR)/gimpcursorview.Po
+ -rm -f ./$(DEPDIR)/gimpdisplay-foreach.Po
+ -rm -f ./$(DEPDIR)/gimpdisplay-handlers.Po
+ -rm -f ./$(DEPDIR)/gimpdisplay.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-actions.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-appearance.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-autoscroll.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-callbacks.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-close.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-cursor.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-dnd.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-draw.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-expose.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-filter.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-grab.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-handlers.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-icon.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-items.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-layer-select.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-profile.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-progress.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-render.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-rulers.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scale.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scroll.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-scrollbars.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-selection.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-title.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-tool-events.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-transform.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell-utils.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayshell.Po
+ -rm -f ./$(DEPDIR)/gimpdisplayxfer.Po
+ -rm -f ./$(DEPDIR)/gimpimagewindow.Po
+ -rm -f ./$(DEPDIR)/gimpmotionbuffer.Po
+ -rm -f ./$(DEPDIR)/gimpmultiwindowstrategy.Po
+ -rm -f ./$(DEPDIR)/gimpnavigationeditor.Po
+ -rm -f ./$(DEPDIR)/gimpscalecombobox.Po
+ -rm -f ./$(DEPDIR)/gimpsinglewindowstrategy.Po
+ -rm -f ./$(DEPDIR)/gimpstatusbar.Po
+ -rm -f ./$(DEPDIR)/gimptoolcompass.Po
+ -rm -f ./$(DEPDIR)/gimptooldialog.Po
+ -rm -f ./$(DEPDIR)/gimptoolfocus.Po
+ -rm -f ./$(DEPDIR)/gimptoolgui.Po
+ -rm -f ./$(DEPDIR)/gimptoolgyroscope.Po
+ -rm -f ./$(DEPDIR)/gimptoolhandlegrid.Po
+ -rm -f ./$(DEPDIR)/gimptoolline.Po
+ -rm -f ./$(DEPDIR)/gimptoolpath.Po
+ -rm -f ./$(DEPDIR)/gimptoolpolygon.Po
+ -rm -f ./$(DEPDIR)/gimptoolrectangle.Po
+ -rm -f ./$(DEPDIR)/gimptoolrotategrid.Po
+ -rm -f ./$(DEPDIR)/gimptoolsheargrid.Po
+ -rm -f ./$(DEPDIR)/gimptooltransform3dgrid.Po
+ -rm -f ./$(DEPDIR)/gimptooltransformgrid.Po
+ -rm -f ./$(DEPDIR)/gimptoolwidget.Po
+ -rm -f ./$(DEPDIR)/gimptoolwidgetgroup.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+xgen-dec: $(srcdir)/display-enums.h $(GIMP_MKENUMS) Makefile.am
+ $(AM_V_GEN) $(GIMP_MKENUMS) \
+ --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"display-enums.h\"\n#include\"gimp-intl.h\"" \
+ --fprod "\n/* enumerations from \"@basename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n" \
+ --dhead " static const Gimp@Type@Desc descs[] =\n {" \
+ --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \
+ --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \
+ $< > $@
+
+# copy the generated enum file back to the source directory only if it's
+# changed; otherwise, only update its timestamp, so that the recipe isn't
+# executed again on the next build, however, allow this to (harmlessly) fail,
+# to support building from a read-only source tree.
+$(srcdir)/display-enums.c: xgen-dec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/app/display/display-enums.c b/app/display/display-enums.c
new file mode 100644
index 0000000..438fc3e
--- /dev/null
+++ b/app/display/display-enums.c
@@ -0,0 +1,592 @@
+
+/* Generated data (by gimp-mkenums) */
+
+#include "config.h"
+#include <gio/gio.h>
+#include "libgimpbase/gimpbase.h"
+#include "display-enums.h"
+#include"gimp-intl.h"
+
+/* enumerations from "display-enums.h" */
+GType
+gimp_button_press_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_BUTTON_PRESS_NORMAL, "GIMP_BUTTON_PRESS_NORMAL", "normal" },
+ { GIMP_BUTTON_PRESS_DOUBLE, "GIMP_BUTTON_PRESS_DOUBLE", "double" },
+ { GIMP_BUTTON_PRESS_TRIPLE, "GIMP_BUTTON_PRESS_TRIPLE", "triple" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_BUTTON_PRESS_NORMAL, "GIMP_BUTTON_PRESS_NORMAL", NULL },
+ { GIMP_BUTTON_PRESS_DOUBLE, "GIMP_BUTTON_PRESS_DOUBLE", NULL },
+ { GIMP_BUTTON_PRESS_TRIPLE, "GIMP_BUTTON_PRESS_TRIPLE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpButtonPressType", values);
+ gimp_type_set_translation_context (type, "button-press-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_button_release_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_BUTTON_RELEASE_NORMAL, "GIMP_BUTTON_RELEASE_NORMAL", "normal" },
+ { GIMP_BUTTON_RELEASE_CANCEL, "GIMP_BUTTON_RELEASE_CANCEL", "cancel" },
+ { GIMP_BUTTON_RELEASE_CLICK, "GIMP_BUTTON_RELEASE_CLICK", "click" },
+ { GIMP_BUTTON_RELEASE_NO_MOTION, "GIMP_BUTTON_RELEASE_NO_MOTION", "no-motion" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_BUTTON_RELEASE_NORMAL, "GIMP_BUTTON_RELEASE_NORMAL", NULL },
+ { GIMP_BUTTON_RELEASE_CANCEL, "GIMP_BUTTON_RELEASE_CANCEL", NULL },
+ { GIMP_BUTTON_RELEASE_CLICK, "GIMP_BUTTON_RELEASE_CLICK", NULL },
+ { GIMP_BUTTON_RELEASE_NO_MOTION, "GIMP_BUTTON_RELEASE_NO_MOTION", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpButtonReleaseType", values);
+ gimp_type_set_translation_context (type, "button-release-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_compass_orientation_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_COMPASS_ORIENTATION_AUTO, "GIMP_COMPASS_ORIENTATION_AUTO", "auto" },
+ { GIMP_COMPASS_ORIENTATION_HORIZONTAL, "GIMP_COMPASS_ORIENTATION_HORIZONTAL", "horizontal" },
+ { GIMP_COMPASS_ORIENTATION_VERTICAL, "GIMP_COMPASS_ORIENTATION_VERTICAL", "vertical" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_COMPASS_ORIENTATION_AUTO, NC_("compass-orientation", "Auto"), NULL },
+ { GIMP_COMPASS_ORIENTATION_HORIZONTAL, NC_("compass-orientation", "Horizontal"), NULL },
+ { GIMP_COMPASS_ORIENTATION_VERTICAL, NC_("compass-orientation", "Vertical"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpCompassOrientation", values);
+ gimp_type_set_translation_context (type, "compass-orientation");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_cursor_precision_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_CURSOR_PRECISION_PIXEL_CENTER, "GIMP_CURSOR_PRECISION_PIXEL_CENTER", "pixel-center" },
+ { GIMP_CURSOR_PRECISION_PIXEL_BORDER, "GIMP_CURSOR_PRECISION_PIXEL_BORDER", "pixel-border" },
+ { GIMP_CURSOR_PRECISION_SUBPIXEL, "GIMP_CURSOR_PRECISION_SUBPIXEL", "subpixel" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_CURSOR_PRECISION_PIXEL_CENTER, "GIMP_CURSOR_PRECISION_PIXEL_CENTER", NULL },
+ { GIMP_CURSOR_PRECISION_PIXEL_BORDER, "GIMP_CURSOR_PRECISION_PIXEL_BORDER", NULL },
+ { GIMP_CURSOR_PRECISION_SUBPIXEL, "GIMP_CURSOR_PRECISION_SUBPIXEL", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpCursorPrecision", values);
+ gimp_type_set_translation_context (type, "cursor-precision");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_guides_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_GUIDES_NONE, "GIMP_GUIDES_NONE", "none" },
+ { GIMP_GUIDES_CENTER_LINES, "GIMP_GUIDES_CENTER_LINES", "center-lines" },
+ { GIMP_GUIDES_THIRDS, "GIMP_GUIDES_THIRDS", "thirds" },
+ { GIMP_GUIDES_FIFTHS, "GIMP_GUIDES_FIFTHS", "fifths" },
+ { GIMP_GUIDES_GOLDEN, "GIMP_GUIDES_GOLDEN", "golden" },
+ { GIMP_GUIDES_DIAGONALS, "GIMP_GUIDES_DIAGONALS", "diagonals" },
+ { GIMP_GUIDES_N_LINES, "GIMP_GUIDES_N_LINES", "n-lines" },
+ { GIMP_GUIDES_SPACING, "GIMP_GUIDES_SPACING", "spacing" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_GUIDES_NONE, NC_("guides-type", "No guides"), NULL },
+ { GIMP_GUIDES_CENTER_LINES, NC_("guides-type", "Center lines"), NULL },
+ { GIMP_GUIDES_THIRDS, NC_("guides-type", "Rule of thirds"), NULL },
+ { GIMP_GUIDES_FIFTHS, NC_("guides-type", "Rule of fifths"), NULL },
+ { GIMP_GUIDES_GOLDEN, NC_("guides-type", "Golden sections"), NULL },
+ { GIMP_GUIDES_DIAGONALS, NC_("guides-type", "Diagonal lines"), NULL },
+ { GIMP_GUIDES_N_LINES, NC_("guides-type", "Number of lines"), NULL },
+ { GIMP_GUIDES_SPACING, NC_("guides-type", "Line spacing"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpGuidesType", values);
+ gimp_type_set_translation_context (type, "guides-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_handle_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_HANDLE_SQUARE, "GIMP_HANDLE_SQUARE", "square" },
+ { GIMP_HANDLE_DASHED_SQUARE, "GIMP_HANDLE_DASHED_SQUARE", "dashed-square" },
+ { GIMP_HANDLE_FILLED_SQUARE, "GIMP_HANDLE_FILLED_SQUARE", "filled-square" },
+ { GIMP_HANDLE_CIRCLE, "GIMP_HANDLE_CIRCLE", "circle" },
+ { GIMP_HANDLE_DASHED_CIRCLE, "GIMP_HANDLE_DASHED_CIRCLE", "dashed-circle" },
+ { GIMP_HANDLE_FILLED_CIRCLE, "GIMP_HANDLE_FILLED_CIRCLE", "filled-circle" },
+ { GIMP_HANDLE_DIAMOND, "GIMP_HANDLE_DIAMOND", "diamond" },
+ { GIMP_HANDLE_DASHED_DIAMOND, "GIMP_HANDLE_DASHED_DIAMOND", "dashed-diamond" },
+ { GIMP_HANDLE_FILLED_DIAMOND, "GIMP_HANDLE_FILLED_DIAMOND", "filled-diamond" },
+ { GIMP_HANDLE_CROSS, "GIMP_HANDLE_CROSS", "cross" },
+ { GIMP_HANDLE_CROSSHAIR, "GIMP_HANDLE_CROSSHAIR", "crosshair" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_HANDLE_SQUARE, "GIMP_HANDLE_SQUARE", NULL },
+ { GIMP_HANDLE_DASHED_SQUARE, "GIMP_HANDLE_DASHED_SQUARE", NULL },
+ { GIMP_HANDLE_FILLED_SQUARE, "GIMP_HANDLE_FILLED_SQUARE", NULL },
+ { GIMP_HANDLE_CIRCLE, "GIMP_HANDLE_CIRCLE", NULL },
+ { GIMP_HANDLE_DASHED_CIRCLE, "GIMP_HANDLE_DASHED_CIRCLE", NULL },
+ { GIMP_HANDLE_FILLED_CIRCLE, "GIMP_HANDLE_FILLED_CIRCLE", NULL },
+ { GIMP_HANDLE_DIAMOND, "GIMP_HANDLE_DIAMOND", NULL },
+ { GIMP_HANDLE_DASHED_DIAMOND, "GIMP_HANDLE_DASHED_DIAMOND", NULL },
+ { GIMP_HANDLE_FILLED_DIAMOND, "GIMP_HANDLE_FILLED_DIAMOND", NULL },
+ { GIMP_HANDLE_CROSS, "GIMP_HANDLE_CROSS", NULL },
+ { GIMP_HANDLE_CROSSHAIR, "GIMP_HANDLE_CROSSHAIR", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpHandleType", values);
+ gimp_type_set_translation_context (type, "handle-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_handle_anchor_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_HANDLE_ANCHOR_CENTER, "GIMP_HANDLE_ANCHOR_CENTER", "center" },
+ { GIMP_HANDLE_ANCHOR_NORTH, "GIMP_HANDLE_ANCHOR_NORTH", "north" },
+ { GIMP_HANDLE_ANCHOR_NORTH_WEST, "GIMP_HANDLE_ANCHOR_NORTH_WEST", "north-west" },
+ { GIMP_HANDLE_ANCHOR_NORTH_EAST, "GIMP_HANDLE_ANCHOR_NORTH_EAST", "north-east" },
+ { GIMP_HANDLE_ANCHOR_SOUTH, "GIMP_HANDLE_ANCHOR_SOUTH", "south" },
+ { GIMP_HANDLE_ANCHOR_SOUTH_WEST, "GIMP_HANDLE_ANCHOR_SOUTH_WEST", "south-west" },
+ { GIMP_HANDLE_ANCHOR_SOUTH_EAST, "GIMP_HANDLE_ANCHOR_SOUTH_EAST", "south-east" },
+ { GIMP_HANDLE_ANCHOR_WEST, "GIMP_HANDLE_ANCHOR_WEST", "west" },
+ { GIMP_HANDLE_ANCHOR_EAST, "GIMP_HANDLE_ANCHOR_EAST", "east" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_HANDLE_ANCHOR_CENTER, "GIMP_HANDLE_ANCHOR_CENTER", NULL },
+ { GIMP_HANDLE_ANCHOR_NORTH, "GIMP_HANDLE_ANCHOR_NORTH", NULL },
+ { GIMP_HANDLE_ANCHOR_NORTH_WEST, "GIMP_HANDLE_ANCHOR_NORTH_WEST", NULL },
+ { GIMP_HANDLE_ANCHOR_NORTH_EAST, "GIMP_HANDLE_ANCHOR_NORTH_EAST", NULL },
+ { GIMP_HANDLE_ANCHOR_SOUTH, "GIMP_HANDLE_ANCHOR_SOUTH", NULL },
+ { GIMP_HANDLE_ANCHOR_SOUTH_WEST, "GIMP_HANDLE_ANCHOR_SOUTH_WEST", NULL },
+ { GIMP_HANDLE_ANCHOR_SOUTH_EAST, "GIMP_HANDLE_ANCHOR_SOUTH_EAST", NULL },
+ { GIMP_HANDLE_ANCHOR_WEST, "GIMP_HANDLE_ANCHOR_WEST", NULL },
+ { GIMP_HANDLE_ANCHOR_EAST, "GIMP_HANDLE_ANCHOR_EAST", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpHandleAnchor", values);
+ gimp_type_set_translation_context (type, "handle-anchor");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_limit_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_LIMIT_CIRCLE, "GIMP_LIMIT_CIRCLE", "circle" },
+ { GIMP_LIMIT_SQUARE, "GIMP_LIMIT_SQUARE", "square" },
+ { GIMP_LIMIT_DIAMOND, "GIMP_LIMIT_DIAMOND", "diamond" },
+ { GIMP_LIMIT_HORIZONTAL, "GIMP_LIMIT_HORIZONTAL", "horizontal" },
+ { GIMP_LIMIT_VERTICAL, "GIMP_LIMIT_VERTICAL", "vertical" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_LIMIT_CIRCLE, "GIMP_LIMIT_CIRCLE", NULL },
+ { GIMP_LIMIT_SQUARE, "GIMP_LIMIT_SQUARE", NULL },
+ { GIMP_LIMIT_DIAMOND, "GIMP_LIMIT_DIAMOND", NULL },
+ { GIMP_LIMIT_HORIZONTAL, "GIMP_LIMIT_HORIZONTAL", NULL },
+ { GIMP_LIMIT_VERTICAL, "GIMP_LIMIT_VERTICAL", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpLimitType", values);
+ gimp_type_set_translation_context (type, "limit-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_path_style_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_PATH_STYLE_DEFAULT, "GIMP_PATH_STYLE_DEFAULT", "default" },
+ { GIMP_PATH_STYLE_VECTORS, "GIMP_PATH_STYLE_VECTORS", "vectors" },
+ { GIMP_PATH_STYLE_OUTLINE, "GIMP_PATH_STYLE_OUTLINE", "outline" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_PATH_STYLE_DEFAULT, "GIMP_PATH_STYLE_DEFAULT", NULL },
+ { GIMP_PATH_STYLE_VECTORS, "GIMP_PATH_STYLE_VECTORS", NULL },
+ { GIMP_PATH_STYLE_OUTLINE, "GIMP_PATH_STYLE_OUTLINE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpPathStyle", values);
+ gimp_type_set_translation_context (type, "path-style");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_rectangle_constraint_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_RECTANGLE_CONSTRAIN_NONE, "GIMP_RECTANGLE_CONSTRAIN_NONE", "none" },
+ { GIMP_RECTANGLE_CONSTRAIN_IMAGE, "GIMP_RECTANGLE_CONSTRAIN_IMAGE", "image" },
+ { GIMP_RECTANGLE_CONSTRAIN_DRAWABLE, "GIMP_RECTANGLE_CONSTRAIN_DRAWABLE", "drawable" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_RECTANGLE_CONSTRAIN_NONE, "GIMP_RECTANGLE_CONSTRAIN_NONE", NULL },
+ { GIMP_RECTANGLE_CONSTRAIN_IMAGE, "GIMP_RECTANGLE_CONSTRAIN_IMAGE", NULL },
+ { GIMP_RECTANGLE_CONSTRAIN_DRAWABLE, "GIMP_RECTANGLE_CONSTRAIN_DRAWABLE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpRectangleConstraint", values);
+ gimp_type_set_translation_context (type, "rectangle-constraint");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_rectangle_fixed_rule_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_RECTANGLE_FIXED_ASPECT, "GIMP_RECTANGLE_FIXED_ASPECT", "aspect" },
+ { GIMP_RECTANGLE_FIXED_WIDTH, "GIMP_RECTANGLE_FIXED_WIDTH", "width" },
+ { GIMP_RECTANGLE_FIXED_HEIGHT, "GIMP_RECTANGLE_FIXED_HEIGHT", "height" },
+ { GIMP_RECTANGLE_FIXED_SIZE, "GIMP_RECTANGLE_FIXED_SIZE", "size" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_RECTANGLE_FIXED_ASPECT, NC_("rectangle-fixed-rule", "Aspect ratio"), NULL },
+ { GIMP_RECTANGLE_FIXED_WIDTH, NC_("rectangle-fixed-rule", "Width"), NULL },
+ { GIMP_RECTANGLE_FIXED_HEIGHT, NC_("rectangle-fixed-rule", "Height"), NULL },
+ { GIMP_RECTANGLE_FIXED_SIZE, NC_("rectangle-fixed-rule", "Size"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpRectangleFixedRule", values);
+ gimp_type_set_translation_context (type, "rectangle-fixed-rule");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_rectangle_precision_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_RECTANGLE_PRECISION_INT, "GIMP_RECTANGLE_PRECISION_INT", "int" },
+ { GIMP_RECTANGLE_PRECISION_DOUBLE, "GIMP_RECTANGLE_PRECISION_DOUBLE", "double" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_RECTANGLE_PRECISION_INT, "GIMP_RECTANGLE_PRECISION_INT", NULL },
+ { GIMP_RECTANGLE_PRECISION_DOUBLE, "GIMP_RECTANGLE_PRECISION_DOUBLE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpRectanglePrecision", values);
+ gimp_type_set_translation_context (type, "rectangle-precision");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_transform_3d_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_TRANSFORM_3D_MODE_CAMERA, "GIMP_TRANSFORM_3D_MODE_CAMERA", "camera" },
+ { GIMP_TRANSFORM_3D_MODE_MOVE, "GIMP_TRANSFORM_3D_MODE_MOVE", "move" },
+ { GIMP_TRANSFORM_3D_MODE_ROTATE, "GIMP_TRANSFORM_3D_MODE_ROTATE", "rotate" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_TRANSFORM_3D_MODE_CAMERA, "GIMP_TRANSFORM_3D_MODE_CAMERA", NULL },
+ { GIMP_TRANSFORM_3D_MODE_MOVE, "GIMP_TRANSFORM_3D_MODE_MOVE", NULL },
+ { GIMP_TRANSFORM_3D_MODE_ROTATE, "GIMP_TRANSFORM_3D_MODE_ROTATE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpTransform3DMode", values);
+ gimp_type_set_translation_context (type, "transform3-dmode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_transform_function_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_TRANSFORM_FUNCTION_NONE, "GIMP_TRANSFORM_FUNCTION_NONE", "none" },
+ { GIMP_TRANSFORM_FUNCTION_MOVE, "GIMP_TRANSFORM_FUNCTION_MOVE", "move" },
+ { GIMP_TRANSFORM_FUNCTION_SCALE, "GIMP_TRANSFORM_FUNCTION_SCALE", "scale" },
+ { GIMP_TRANSFORM_FUNCTION_ROTATE, "GIMP_TRANSFORM_FUNCTION_ROTATE", "rotate" },
+ { GIMP_TRANSFORM_FUNCTION_SHEAR, "GIMP_TRANSFORM_FUNCTION_SHEAR", "shear" },
+ { GIMP_TRANSFORM_FUNCTION_PERSPECTIVE, "GIMP_TRANSFORM_FUNCTION_PERSPECTIVE", "perspective" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_TRANSFORM_FUNCTION_NONE, "GIMP_TRANSFORM_FUNCTION_NONE", NULL },
+ { GIMP_TRANSFORM_FUNCTION_MOVE, "GIMP_TRANSFORM_FUNCTION_MOVE", NULL },
+ { GIMP_TRANSFORM_FUNCTION_SCALE, "GIMP_TRANSFORM_FUNCTION_SCALE", NULL },
+ { GIMP_TRANSFORM_FUNCTION_ROTATE, "GIMP_TRANSFORM_FUNCTION_ROTATE", NULL },
+ { GIMP_TRANSFORM_FUNCTION_SHEAR, "GIMP_TRANSFORM_FUNCTION_SHEAR", NULL },
+ { GIMP_TRANSFORM_FUNCTION_PERSPECTIVE, "GIMP_TRANSFORM_FUNCTION_PERSPECTIVE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpTransformFunction", values);
+ gimp_type_set_translation_context (type, "transform-function");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_transform_handle_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_HANDLE_MODE_ADD_TRANSFORM, "GIMP_HANDLE_MODE_ADD_TRANSFORM", "add-transform" },
+ { GIMP_HANDLE_MODE_MOVE, "GIMP_HANDLE_MODE_MOVE", "move" },
+ { GIMP_HANDLE_MODE_REMOVE, "GIMP_HANDLE_MODE_REMOVE", "remove" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_HANDLE_MODE_ADD_TRANSFORM, NC_("transform-handle-mode", "Add / Transform"), NULL },
+ { GIMP_HANDLE_MODE_MOVE, NC_("transform-handle-mode", "Move"), NULL },
+ { GIMP_HANDLE_MODE_REMOVE, NC_("transform-handle-mode", "Remove"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpTransformHandleMode", values);
+ gimp_type_set_translation_context (type, "transform-handle-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_vector_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_VECTOR_MODE_DESIGN, "GIMP_VECTOR_MODE_DESIGN", "design" },
+ { GIMP_VECTOR_MODE_EDIT, "GIMP_VECTOR_MODE_EDIT", "edit" },
+ { GIMP_VECTOR_MODE_MOVE, "GIMP_VECTOR_MODE_MOVE", "move" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_VECTOR_MODE_DESIGN, NC_("vector-mode", "Design"), NULL },
+ { GIMP_VECTOR_MODE_EDIT, NC_("vector-mode", "Edit"), NULL },
+ { GIMP_VECTOR_MODE_MOVE, NC_("vector-mode", "Move"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpVectorMode", values);
+ gimp_type_set_translation_context (type, "vector-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_zoom_focus_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_ZOOM_FOCUS_BEST_GUESS, "GIMP_ZOOM_FOCUS_BEST_GUESS", "best-guess" },
+ { GIMP_ZOOM_FOCUS_POINTER, "GIMP_ZOOM_FOCUS_POINTER", "pointer" },
+ { GIMP_ZOOM_FOCUS_IMAGE_CENTER, "GIMP_ZOOM_FOCUS_IMAGE_CENTER", "image-center" },
+ { GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS, "GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS", "retain-centering-else-best-guess" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_ZOOM_FOCUS_BEST_GUESS, "GIMP_ZOOM_FOCUS_BEST_GUESS", NULL },
+ { GIMP_ZOOM_FOCUS_POINTER, "GIMP_ZOOM_FOCUS_POINTER", NULL },
+ { GIMP_ZOOM_FOCUS_IMAGE_CENTER, "GIMP_ZOOM_FOCUS_IMAGE_CENTER", NULL },
+ { GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS, "GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpZoomFocus", values);
+ gimp_type_set_translation_context (type, "zoom-focus");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+
+/* Generated data ends here */
+
diff --git a/app/display/display-enums.h b/app/display/display-enums.h
new file mode 100644
index 0000000..4cc84a8
--- /dev/null
+++ b/app/display/display-enums.h
@@ -0,0 +1,275 @@
+/* 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/>.
+ */
+
+#ifndef __DISPLAY_ENUMS_H__
+#define __DISPLAY_ENUMS_H__
+
+
+#define GIMP_TYPE_BUTTON_PRESS_TYPE (gimp_button_press_type_get_type ())
+
+GType gimp_button_press_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_BUTTON_PRESS_NORMAL,
+ GIMP_BUTTON_PRESS_DOUBLE,
+ GIMP_BUTTON_PRESS_TRIPLE
+} GimpButtonPressType;
+
+
+#define GIMP_TYPE_BUTTON_RELEASE_TYPE (gimp_button_release_type_get_type ())
+
+GType gimp_button_release_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_BUTTON_RELEASE_NORMAL,
+ GIMP_BUTTON_RELEASE_CANCEL,
+ GIMP_BUTTON_RELEASE_CLICK,
+ GIMP_BUTTON_RELEASE_NO_MOTION
+} GimpButtonReleaseType;
+
+
+#define GIMP_TYPE_COMPASS_ORIENTATION (gimp_compass_orientation_get_type ())
+
+GType gimp_compass_orientation_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_COMPASS_ORIENTATION_AUTO, /*< desc="Auto" >*/
+ GIMP_COMPASS_ORIENTATION_HORIZONTAL, /*< desc="Horizontal" >*/
+ GIMP_COMPASS_ORIENTATION_VERTICAL /*< desc="Vertical" >*/
+} GimpCompassOrientation;
+
+
+#define GIMP_TYPE_CURSOR_PRECISION (gimp_cursor_precision_get_type ())
+
+GType gimp_cursor_precision_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER,
+ GIMP_CURSOR_PRECISION_SUBPIXEL
+} GimpCursorPrecision;
+
+
+#define GIMP_TYPE_GUIDES_TYPE (gimp_guides_type_get_type ())
+
+GType gimp_guides_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_GUIDES_NONE, /*< desc="No guides" >*/
+ GIMP_GUIDES_CENTER_LINES, /*< desc="Center lines" >*/
+ GIMP_GUIDES_THIRDS, /*< desc="Rule of thirds" >*/
+ GIMP_GUIDES_FIFTHS, /*< desc="Rule of fifths" >*/
+ GIMP_GUIDES_GOLDEN, /*< desc="Golden sections" >*/
+ GIMP_GUIDES_DIAGONALS, /*< desc="Diagonal lines" >*/
+ GIMP_GUIDES_N_LINES, /*< desc="Number of lines" >*/
+ GIMP_GUIDES_SPACING /*< desc="Line spacing" >*/
+} GimpGuidesType;
+
+
+#define GIMP_TYPE_HANDLE_TYPE (gimp_handle_type_get_type ())
+
+GType gimp_handle_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_HANDLE_SQUARE,
+ GIMP_HANDLE_DASHED_SQUARE,
+ GIMP_HANDLE_FILLED_SQUARE,
+ GIMP_HANDLE_CIRCLE,
+ GIMP_HANDLE_DASHED_CIRCLE,
+ GIMP_HANDLE_FILLED_CIRCLE,
+ GIMP_HANDLE_DIAMOND,
+ GIMP_HANDLE_DASHED_DIAMOND,
+ GIMP_HANDLE_FILLED_DIAMOND,
+ GIMP_HANDLE_CROSS,
+ GIMP_HANDLE_CROSSHAIR
+} GimpHandleType;
+
+
+#define GIMP_TYPE_HANDLE_ANCHOR (gimp_handle_anchor_get_type ())
+
+GType gimp_handle_anchor_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_HANDLE_ANCHOR_CENTER,
+ GIMP_HANDLE_ANCHOR_NORTH,
+ GIMP_HANDLE_ANCHOR_NORTH_WEST,
+ GIMP_HANDLE_ANCHOR_NORTH_EAST,
+ GIMP_HANDLE_ANCHOR_SOUTH,
+ GIMP_HANDLE_ANCHOR_SOUTH_WEST,
+ GIMP_HANDLE_ANCHOR_SOUTH_EAST,
+ GIMP_HANDLE_ANCHOR_WEST,
+ GIMP_HANDLE_ANCHOR_EAST
+} GimpHandleAnchor;
+
+
+#define GIMP_TYPE_LIMIT_TYPE (gimp_limit_type_get_type ())
+
+GType gimp_limit_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_LIMIT_CIRCLE,
+ GIMP_LIMIT_SQUARE,
+ GIMP_LIMIT_DIAMOND,
+ GIMP_LIMIT_HORIZONTAL,
+ GIMP_LIMIT_VERTICAL
+} GimpLimitType;
+
+
+#define GIMP_TYPE_PATH_STYLE (gimp_path_style_get_type ())
+
+GType gimp_path_style_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_PATH_STYLE_DEFAULT,
+ GIMP_PATH_STYLE_VECTORS,
+ GIMP_PATH_STYLE_OUTLINE
+} GimpPathStyle;
+
+
+#define GIMP_TYPE_RECTANGLE_CONSTRAINT (gimp_rectangle_constraint_get_type ())
+
+GType gimp_rectangle_constraint_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_RECTANGLE_CONSTRAIN_NONE,
+ GIMP_RECTANGLE_CONSTRAIN_IMAGE,
+ GIMP_RECTANGLE_CONSTRAIN_DRAWABLE
+} GimpRectangleConstraint;
+
+
+#define GIMP_TYPE_RECTANGLE_FIXED_RULE (gimp_rectangle_fixed_rule_get_type ())
+
+GType gimp_rectangle_fixed_rule_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_RECTANGLE_FIXED_ASPECT, /*< desc="Aspect ratio" >*/
+ GIMP_RECTANGLE_FIXED_WIDTH, /*< desc="Width" >*/
+ GIMP_RECTANGLE_FIXED_HEIGHT, /*< desc="Height" >*/
+ GIMP_RECTANGLE_FIXED_SIZE, /*< desc="Size" >*/
+} GimpRectangleFixedRule;
+
+
+#define GIMP_TYPE_RECTANGLE_PRECISION (gimp_rectangle_precision_get_type ())
+
+GType gimp_rectangle_precision_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_RECTANGLE_PRECISION_INT,
+ GIMP_RECTANGLE_PRECISION_DOUBLE,
+} GimpRectanglePrecision;
+
+
+#define GIMP_TYPE_TRANSFORM_3D_MODE (gimp_transform_3d_mode_get_type ())
+
+GType gimp_transform_3d_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< lowercase_name=gimp_transform_3d_mode >*/
+{
+ GIMP_TRANSFORM_3D_MODE_CAMERA,
+ GIMP_TRANSFORM_3D_MODE_MOVE,
+ GIMP_TRANSFORM_3D_MODE_ROTATE
+} GimpTransform3DMode;
+
+
+#define GIMP_TYPE_TRANSFORM_FUNCTION (gimp_transform_function_get_type ())
+
+GType gimp_transform_function_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_TRANSFORM_FUNCTION_NONE,
+ GIMP_TRANSFORM_FUNCTION_MOVE,
+ GIMP_TRANSFORM_FUNCTION_SCALE,
+ GIMP_TRANSFORM_FUNCTION_ROTATE,
+ GIMP_TRANSFORM_FUNCTION_SHEAR,
+ GIMP_TRANSFORM_FUNCTION_PERSPECTIVE
+} GimpTransformFunction;
+
+
+#define GIMP_TYPE_TRANSFORM_HANDLE_MODE (gimp_transform_handle_mode_get_type ())
+
+GType gimp_transform_handle_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_HANDLE_MODE_ADD_TRANSFORM, /*< desc="Add / Transform" >*/
+ GIMP_HANDLE_MODE_MOVE, /*< desc="Move" >*/
+ GIMP_HANDLE_MODE_REMOVE /*< desc="Remove" >*/
+} GimpTransformHandleMode;
+
+
+#define GIMP_TYPE_VECTOR_MODE (gimp_vector_mode_get_type ())
+
+GType gimp_vector_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_VECTOR_MODE_DESIGN, /*< desc="Design" >*/
+ GIMP_VECTOR_MODE_EDIT, /*< desc="Edit" >*/
+ GIMP_VECTOR_MODE_MOVE /*< desc="Move" >*/
+} GimpVectorMode;
+
+
+#define GIMP_TYPE_ZOOM_FOCUS (gimp_zoom_focus_get_type ())
+
+GType gimp_zoom_focus_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ /* Make a best guess */
+ GIMP_ZOOM_FOCUS_BEST_GUESS,
+
+ /* Use the mouse cursor (if within canvas) */
+ GIMP_ZOOM_FOCUS_POINTER,
+
+ /* Use the image center */
+ GIMP_ZOOM_FOCUS_IMAGE_CENTER,
+
+ /* If the image is centered, retain the centering. Else use
+ * _BEST_GUESS
+ */
+ GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS
+
+} GimpZoomFocus;
+
+
+/*
+ * non-registered enums; register them if needed
+ */
+
+
+typedef enum /*< pdb-skip, skip >*/
+{
+ GIMP_HIT_NONE,
+ GIMP_HIT_INDIRECT,
+ GIMP_HIT_DIRECT
+} GimpHit;
+
+
+#endif /* __DISPLAY_ENUMS_H__ */
diff --git a/app/display/display-types.h b/app/display/display-types.h
new file mode 100644
index 0000000..6de751d
--- /dev/null
+++ b/app/display/display-types.h
@@ -0,0 +1,53 @@
+/* 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/>.
+ */
+
+#ifndef __DISPLAY_TYPES_H__
+#define __DISPLAY_TYPES_H__
+
+
+#include "propgui/propgui-types.h"
+
+#include "display/display-enums.h"
+
+
+typedef struct _GimpCanvas GimpCanvas;
+typedef struct _GimpCanvasGroup GimpCanvasGroup;
+typedef struct _GimpCanvasItem GimpCanvasItem;
+
+typedef struct _GimpDisplay GimpDisplay;
+typedef struct _GimpDisplayShell GimpDisplayShell;
+typedef struct _GimpMotionBuffer GimpMotionBuffer;
+
+typedef struct _GimpImageWindow GimpImageWindow;
+typedef struct _GimpMultiWindowStrategy GimpMultiWindowStrategy;
+typedef struct _GimpSingleWindowStrategy GimpSingleWindowStrategy;
+
+typedef struct _GimpCursorView GimpCursorView;
+typedef struct _GimpNavigationEditor GimpNavigationEditor;
+typedef struct _GimpScaleComboBox GimpScaleComboBox;
+typedef struct _GimpStatusbar GimpStatusbar;
+
+typedef struct _GimpToolDialog GimpToolDialog;
+typedef struct _GimpToolGui GimpToolGui;
+typedef struct _GimpToolWidget GimpToolWidget;
+typedef struct _GimpToolWidgetGroup GimpToolWidgetGroup;
+
+typedef struct _GimpDisplayXfer GimpDisplayXfer;
+typedef struct _Selection Selection;
+
+
+#endif /* __DISPLAY_TYPES_H__ */
diff --git a/app/display/gimpcanvas-style.c b/app/display/gimpcanvas-style.c
new file mode 100644
index 0000000..8d47961
--- /dev/null
+++ b/app/display/gimpcanvas-style.c
@@ -0,0 +1,458 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvas-style.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+#include "core/gimpgrid.h"
+#include "core/gimplayer.h"
+
+#include "gimpcanvas-style.h"
+
+/* Styles for common and custom guides. */
+static const GimpRGB guide_normal_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB guide_normal_bg = { 0.0, 0.8, 1.0, 1.0 };
+static const GimpRGB guide_active_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB guide_active_bg = { 1.0, 0.0, 0.0, 1.0 };
+
+static const GimpRGB guide_mirror_normal_fg = { 1.0, 1.0, 1.0, 1.0 };
+static const GimpRGB guide_mirror_normal_bg = { 0.0, 1.0, 0.0, 1.0 };
+static const GimpRGB guide_mirror_active_fg = { 0.0, 1.0, 0.0, 1.0 };
+static const GimpRGB guide_mirror_active_bg = { 1.0, 0.0, 0.0, 1.0 };
+
+static const GimpRGB guide_mandala_normal_fg = { 1.0, 1.0, 1.0, 1.0 };
+static const GimpRGB guide_mandala_normal_bg = { 0.0, 1.0, 1.0, 1.0 };
+static const GimpRGB guide_mandala_active_fg = { 0.0, 1.0, 1.0, 1.0 };
+static const GimpRGB guide_mandala_active_bg = { 1.0, 0.0, 0.0, 1.0 };
+
+static const GimpRGB guide_split_normal_fg = { 1.0, 1.0, 1.0, 1.0 };
+static const GimpRGB guide_split_normal_bg = { 1.0, 0.0, 1.0, 1.0 };
+static const GimpRGB guide_split_active_fg = { 1.0, 0.0, 1.0, 1.0 };
+static const GimpRGB guide_split_active_bg = { 1.0, 0.0, 0.0, 1.0 };
+
+/* Styles for other canvas items. */
+static const GimpRGB sample_point_normal = { 0.0, 0.8, 1.0, 1.0 };
+static const GimpRGB sample_point_active = { 1.0, 0.0, 0.0, 1.0 };
+
+static const GimpRGB layer_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB layer_bg = { 1.0, 1.0, 0.0, 1.0 };
+
+static const GimpRGB layer_group_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB layer_group_bg = { 0.0, 1.0, 1.0, 1.0 };
+
+static const GimpRGB layer_mask_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB layer_mask_bg = { 0.0, 1.0, 0.0, 1.0 };
+
+static const GimpRGB canvas_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB canvas_bg = { 1.0, 0.5, 0.0, 1.0 };
+
+static const GimpRGB selection_out_fg = { 1.0, 1.0, 1.0, 1.0 };
+static const GimpRGB selection_out_bg = { 0.5, 0.5, 0.5, 1.0 };
+
+static const GimpRGB selection_in_fg = { 0.0, 0.0, 0.0, 1.0 };
+static const GimpRGB selection_in_bg = { 1.0, 1.0, 1.0, 1.0 };
+
+static const GimpRGB vectors_normal_bg = { 1.0, 1.0, 1.0, 0.6 };
+static const GimpRGB vectors_normal_fg = { 0.0, 0.0, 1.0, 0.8 };
+
+static const GimpRGB vectors_active_bg = { 1.0, 1.0, 1.0, 0.6 };
+static const GimpRGB vectors_active_fg = { 1.0, 0.0, 0.0, 0.8 };
+
+static const GimpRGB outline_bg = { 1.0, 1.0, 1.0, 0.6 };
+static const GimpRGB outline_fg = { 0.0, 0.0, 0.0, 0.8 };
+
+static const GimpRGB passe_partout = { 0.0, 0.0, 0.0, 1.0 };
+
+static const GimpRGB tool_bg = { 0.0, 0.0, 0.0, 0.4 };
+static const GimpRGB tool_fg = { 1.0, 1.0, 1.0, 0.8 };
+static const GimpRGB tool_fg_highlight = { 1.0, 0.8, 0.2, 0.8 };
+
+
+/* public functions */
+
+void
+gimp_canvas_set_guide_style (GtkWidget *canvas,
+ cairo_t *cr,
+ GimpGuideStyle style,
+ gboolean active,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ cairo_pattern_t *pattern;
+ GimpRGB normal_fg;
+ GimpRGB normal_bg;
+ GimpRGB active_fg;
+ GimpRGB active_bg;
+ gdouble line_width;
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ switch (style)
+ {
+ case GIMP_GUIDE_STYLE_NORMAL:
+ normal_fg = guide_normal_fg;
+ normal_bg = guide_normal_bg;
+ active_fg = guide_active_fg;
+ active_bg = guide_active_bg;
+ line_width = 1.0;
+ break;
+
+ case GIMP_GUIDE_STYLE_MIRROR:
+ normal_fg = guide_mirror_normal_fg;
+ normal_bg = guide_mirror_normal_bg;
+ active_fg = guide_mirror_active_fg;
+ active_bg = guide_mirror_active_bg;
+ line_width = 1.0;
+ break;
+
+ case GIMP_GUIDE_STYLE_MANDALA:
+ normal_fg = guide_mandala_normal_fg;
+ normal_bg = guide_mandala_normal_bg;
+ active_fg = guide_mandala_active_fg;
+ active_bg = guide_mandala_active_bg;
+ line_width = 1.0;
+ break;
+
+ case GIMP_GUIDE_STYLE_SPLIT_VIEW:
+ normal_fg = guide_split_normal_fg;
+ normal_bg = guide_split_normal_bg;
+ active_fg = guide_split_active_fg;
+ active_bg = guide_split_active_bg;
+ line_width = 1.0;
+ break;
+
+ default: /* GIMP_GUIDE_STYLE_NONE */
+ /* This should not happen. */
+ g_return_if_reached ();
+ }
+
+ cairo_set_line_width (cr, line_width);
+
+ if (active)
+ pattern = gimp_cairo_pattern_create_stipple (&active_fg, &active_bg, 0,
+ offset_x, offset_y);
+ else
+ pattern = gimp_cairo_pattern_create_stipple (&normal_fg, &normal_bg, 0,
+ offset_x, offset_y);
+
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+}
+
+void
+gimp_canvas_set_sample_point_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean active)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+
+ if (active)
+ gimp_cairo_set_source_rgb (cr, &sample_point_active);
+ else
+ gimp_cairo_set_source_rgb (cr, &sample_point_normal);
+}
+
+void
+gimp_canvas_set_grid_style (GtkWidget *canvas,
+ cairo_t *cr,
+ GimpGrid *grid,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ GimpRGB fg;
+ GimpRGB bg;
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (GIMP_IS_GRID (grid));
+
+ cairo_set_line_width (cr, 1.0);
+
+ gimp_grid_get_fgcolor (grid, &fg);
+
+ switch (gimp_grid_get_style (grid))
+ {
+ cairo_pattern_t *pattern;
+
+ case GIMP_GRID_ON_OFF_DASH:
+ case GIMP_GRID_DOUBLE_DASH:
+ if (grid->style == GIMP_GRID_DOUBLE_DASH)
+ {
+ gimp_grid_get_bgcolor (grid, &bg);
+
+ pattern = gimp_cairo_pattern_create_stipple (&fg, &bg, 0,
+ offset_x, offset_y);
+ }
+ else
+ {
+ gimp_rgba_set (&bg, 0.0, 0.0, 0.0, 0.0);
+
+ pattern = gimp_cairo_pattern_create_stipple (&fg, &bg, 0,
+ offset_x, offset_y);
+ }
+
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+ break;
+
+ case GIMP_GRID_DOTS:
+ case GIMP_GRID_INTERSECTIONS:
+ case GIMP_GRID_SOLID:
+ gimp_cairo_set_source_rgb (cr, &fg);
+ break;
+ }
+}
+
+void
+gimp_canvas_set_pen_style (GtkWidget *canvas,
+ cairo_t *cr,
+ const GimpRGB *color,
+ gint width)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (color != NULL);
+
+ cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
+ cairo_set_line_width (cr, width);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
+
+ gimp_cairo_set_source_rgb (cr, color);
+}
+
+void
+gimp_canvas_set_layer_style (GtkWidget *canvas,
+ cairo_t *cr,
+ GimpLayer *layer,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+
+ if (gimp_layer_get_mask (layer) &&
+ gimp_layer_get_edit_mask (layer))
+ {
+ pattern = gimp_cairo_pattern_create_stipple (&layer_mask_fg,
+ &layer_mask_bg,
+ 0,
+ offset_x, offset_y);
+ }
+ else if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ {
+ pattern = gimp_cairo_pattern_create_stipple (&layer_group_fg,
+ &layer_group_bg,
+ 0,
+ offset_x, offset_y);
+ }
+ else
+ {
+ pattern = gimp_cairo_pattern_create_stipple (&layer_fg,
+ &layer_bg,
+ 0,
+ offset_x, offset_y);
+ }
+
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+}
+
+void
+gimp_canvas_set_canvas_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+
+ pattern = gimp_cairo_pattern_create_stipple (&canvas_fg,
+ &canvas_bg,
+ 0,
+ offset_x, offset_y);
+
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+}
+
+void
+gimp_canvas_set_selection_out_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+
+ pattern = gimp_cairo_pattern_create_stipple (&selection_out_fg,
+ &selection_out_bg,
+ 0,
+ offset_x, offset_y);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+}
+
+void
+gimp_canvas_set_selection_in_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gint index,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+
+ pattern = gimp_cairo_pattern_create_stipple (&selection_in_fg,
+ &selection_in_bg,
+ index,
+ offset_x, offset_y);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+}
+
+void
+gimp_canvas_set_vectors_bg_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean active)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 3.0);
+
+ if (active)
+ gimp_cairo_set_source_rgba (cr, &vectors_active_bg);
+ else
+ gimp_cairo_set_source_rgba (cr, &vectors_normal_bg);
+}
+
+void
+gimp_canvas_set_vectors_fg_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean active)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+
+ if (active)
+ gimp_cairo_set_source_rgba (cr, &vectors_active_fg);
+ else
+ gimp_cairo_set_source_rgba (cr, &vectors_normal_fg);
+}
+
+void
+gimp_canvas_set_outline_bg_style (GtkWidget *canvas,
+ cairo_t *cr)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+ gimp_cairo_set_source_rgba (cr, &outline_bg);
+}
+
+void
+gimp_canvas_set_outline_fg_style (GtkWidget *canvas,
+ cairo_t *cr)
+{
+ static const double dashes[] = { 4.0, 4.0 };
+
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+ gimp_cairo_set_source_rgba (cr, &outline_fg);
+ cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0);
+}
+
+void
+gimp_canvas_set_passe_partout_style (GtkWidget *canvas,
+ cairo_t *cr)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ gimp_cairo_set_source_rgba (cr, &passe_partout);
+}
+
+void
+gimp_canvas_set_tool_bg_style (GtkWidget *canvas,
+ cairo_t *cr)
+{
+ g_return_if_fail (GTK_IS_WIDGET (canvas));
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 3.0);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
+
+ gimp_cairo_set_source_rgba (cr, &tool_bg);
+}
+
+void
+gimp_canvas_set_tool_fg_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean highlight)
+{
+ g_return_if_fail (cr != NULL);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
+
+ if (highlight)
+ gimp_cairo_set_source_rgba (cr, &tool_fg_highlight);
+ else
+ gimp_cairo_set_source_rgba (cr, &tool_fg);
+}
diff --git a/app/display/gimpcanvas-style.h b/app/display/gimpcanvas-style.h
new file mode 100644
index 0000000..3a37b98
--- /dev/null
+++ b/app/display/gimpcanvas-style.h
@@ -0,0 +1,81 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdisplayshell-style.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_STYLE_H__
+#define __GIMP_CANVAS_STYLE_H__
+
+
+void gimp_canvas_set_guide_style (GtkWidget *canvas,
+ cairo_t *cr,
+ GimpGuideStyle style,
+ gboolean active,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_canvas_set_sample_point_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean active);
+void gimp_canvas_set_grid_style (GtkWidget *canvas,
+ cairo_t *cr,
+ GimpGrid *grid,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_canvas_set_pen_style (GtkWidget *canvas,
+ cairo_t *cr,
+ const GimpRGB *color,
+ gint width);
+void gimp_canvas_set_layer_style (GtkWidget *canvas,
+ cairo_t *cr,
+ GimpLayer *layer,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_canvas_set_canvas_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_canvas_set_selection_out_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_canvas_set_selection_in_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gint index,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_canvas_set_vectors_bg_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean active);
+void gimp_canvas_set_vectors_fg_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean active);
+void gimp_canvas_set_outline_bg_style (GtkWidget *canvas,
+ cairo_t *cr);
+void gimp_canvas_set_outline_fg_style (GtkWidget *canvas,
+ cairo_t *cr);
+void gimp_canvas_set_passe_partout_style (GtkWidget *canvas,
+ cairo_t *cr);
+
+void gimp_canvas_set_tool_bg_style (GtkWidget *canvas,
+ cairo_t *cr);
+void gimp_canvas_set_tool_fg_style (GtkWidget *canvas,
+ cairo_t *cr,
+ gboolean highlight);
+
+
+#endif /* __GIMP_CANVAS_STYLE_H__ */
diff --git a/app/display/gimpcanvas.c b/app/display/gimpcanvas.c
new file mode 100644
index 0000000..46b9ff5
--- /dev/null
+++ b/app/display/gimpcanvas.c
@@ -0,0 +1,298 @@
+/* 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 "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvas.h"
+
+#include "gimp-intl.h"
+
+
+#define MAX_BATCH_SIZE 32000
+
+
+enum
+{
+ PROP_0,
+ PROP_CONFIG
+};
+
+
+/* local function prototypes */
+
+static void gimp_canvas_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_canvas_unrealize (GtkWidget *widget);
+static void gimp_canvas_style_set (GtkWidget *widget,
+ GtkStyle *prev_style);
+static gboolean gimp_canvas_focus_in_event (GtkWidget *widget,
+ GdkEventFocus *event);
+static gboolean gimp_canvas_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event);
+static gboolean gimp_canvas_focus (GtkWidget *widget,
+ GtkDirectionType direction);
+
+
+G_DEFINE_TYPE (GimpCanvas, gimp_canvas, GIMP_TYPE_OVERLAY_BOX)
+
+#define parent_class gimp_canvas_parent_class
+
+
+static void
+gimp_canvas_class_init (GimpCanvasClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_set_property;
+ object_class->get_property = gimp_canvas_get_property;
+
+ widget_class->unrealize = gimp_canvas_unrealize;
+ widget_class->style_set = gimp_canvas_style_set;
+ widget_class->focus_in_event = gimp_canvas_focus_in_event;
+ widget_class->focus_out_event = gimp_canvas_focus_out_event;
+ widget_class->focus = gimp_canvas_focus;
+
+ g_object_class_install_property (object_class, PROP_CONFIG,
+ g_param_spec_object ("config", NULL, NULL,
+ GIMP_TYPE_DISPLAY_CONFIG,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_canvas_init (GimpCanvas *canvas)
+{
+ GtkWidget *widget = GTK_WIDGET (canvas);
+
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_widget_add_events (widget, GIMP_CANVAS_EVENT_MASK);
+ gtk_widget_set_extension_events (widget, GDK_EXTENSION_EVENTS_ALL);
+}
+
+static void
+gimp_canvas_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvas *canvas = GIMP_CANVAS (object);
+
+ switch (property_id)
+ {
+ case PROP_CONFIG:
+ canvas->config = g_value_get_object (value); /* don't dup */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvas *canvas = GIMP_CANVAS (object);
+
+ switch (property_id)
+ {
+ case PROP_CONFIG:
+ g_value_set_object (value, canvas->config);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_unrealize (GtkWidget *widget)
+{
+ GimpCanvas *canvas = GIMP_CANVAS (widget);
+
+ g_clear_object (&canvas->layout);
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static void
+gimp_canvas_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ GimpCanvas *canvas = GIMP_CANVAS (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
+
+ g_clear_object (&canvas->layout);
+}
+
+static gboolean
+gimp_canvas_focus_in_event (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ /* don't allow the default impl to invalidate the whole widget,
+ * we don't draw a focus indicator anyway.
+ */
+ return FALSE;
+}
+
+static gboolean
+gimp_canvas_focus_out_event (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ /* see focus-in-event
+ */
+ return FALSE;
+}
+
+static gboolean
+gimp_canvas_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ GtkWidget *focus = gtk_container_get_focus_child (GTK_CONTAINER (widget));
+
+ /* override GtkContainer's focus() implementation which would always
+ * give focus to the canvas because it is focussable. Instead, try
+ * navigating in the focused overlay child first, and use
+ * GtkContainer's default implementation only if that fails (which
+ * happens when focus navigation leaves the overlay child).
+ */
+
+ if (focus && gtk_widget_child_focus (focus, direction))
+ return TRUE;
+
+ return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction);
+}
+
+
+/* public functions */
+
+/**
+ * gimp_canvas_new:
+ *
+ * Creates a new #GimpCanvas widget.
+ *
+ * The #GimpCanvas widget is a #GtkDrawingArea abstraction. It manages
+ * a set of graphic contexts for drawing on a GIMP display. If you
+ * draw using a #GimpCanvasStyle, #GimpCanvas makes sure that the
+ * associated #GdkGC is created. All drawing on the canvas needs to
+ * happen by means of the #GimpCanvas drawing functions. Besides from
+ * not needing a #GdkGC pointer, the #GimpCanvas drawing functions
+ * look and work like their #GdkDrawable counterparts. #GimpCanvas
+ * gracefully handles attempts to draw on the unrealized widget.
+ *
+ * Return value: a new #GimpCanvas widget
+ **/
+GtkWidget *
+gimp_canvas_new (GimpDisplayConfig *config)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_CONFIG (config), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS,
+ "name", "gimp-canvas",
+ "config", config,
+ NULL);
+}
+
+/**
+ * gimp_canvas_get_layout:
+ * @canvas: a #GimpCanvas widget
+ * @format: a standard printf() format string.
+ * @Varargs: the parameters to insert into the format string.
+ *
+ * Returns a layout which can be used for
+ * pango_cairo_show_layout(). The layout belongs to the canvas and
+ * should not be freed, not should a pointer to it be kept around
+ * after drawing.
+ *
+ * Returns: a #PangoLayout owned by the canvas.
+ **/
+PangoLayout *
+gimp_canvas_get_layout (GimpCanvas *canvas,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *text;
+
+ if (! canvas->layout)
+ canvas->layout = gtk_widget_create_pango_layout (GTK_WIDGET (canvas),
+ NULL);
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ pango_layout_set_text (canvas->layout, text, -1);
+ g_free (text);
+
+ return canvas->layout;
+}
+
+/**
+ * gimp_canvas_set_bg_color:
+ * @canvas: a #GimpCanvas widget
+ * @color: a color in #GimpRGB format
+ *
+ * Sets the background color of the canvas's window. This
+ * is the color the canvas is set to if it is cleared.
+ **/
+void
+gimp_canvas_set_bg_color (GimpCanvas *canvas,
+ GimpRGB *color)
+{
+ GtkWidget *widget = GTK_WIDGET (canvas);
+ GdkColormap *colormap;
+ GdkColor gdk_color;
+
+ if (! gtk_widget_get_realized (widget))
+ return;
+
+ gimp_rgb_get_gdk_color (color, &gdk_color);
+
+ colormap = gdk_drawable_get_colormap (gtk_widget_get_window (widget));
+ g_return_if_fail (colormap != NULL);
+ gdk_colormap_alloc_color (colormap, &gdk_color, FALSE, TRUE);
+
+ gdk_window_set_background (gtk_widget_get_window (widget), &gdk_color);
+
+ gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
diff --git a/app/display/gimpcanvas.h b/app/display/gimpcanvas.h
new file mode 100644
index 0000000..dc52be8
--- /dev/null
+++ b/app/display/gimpcanvas.h
@@ -0,0 +1,74 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_H__
+#define __GIMP_CANVAS_H__
+
+
+#include "widgets/gimpoverlaybox.h"
+
+
+#define GIMP_CANVAS_EVENT_MASK (GDK_EXPOSURE_MASK | \
+ GDK_POINTER_MOTION_MASK | \
+ GDK_BUTTON_PRESS_MASK | \
+ GDK_BUTTON_RELEASE_MASK | \
+ GDK_STRUCTURE_MASK | \
+ GDK_ENTER_NOTIFY_MASK | \
+ GDK_LEAVE_NOTIFY_MASK | \
+ GDK_FOCUS_CHANGE_MASK | \
+ GDK_KEY_PRESS_MASK | \
+ GDK_KEY_RELEASE_MASK | \
+ GDK_PROXIMITY_OUT_MASK)
+
+
+#define GIMP_TYPE_CANVAS (gimp_canvas_get_type ())
+#define GIMP_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS, GimpCanvas))
+#define GIMP_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS, GimpCanvasClass))
+#define GIMP_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS))
+#define GIMP_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS))
+#define GIMP_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS, GimpCanvasClass))
+
+
+typedef struct _GimpCanvasClass GimpCanvasClass;
+
+struct _GimpCanvas
+{
+ GimpOverlayBox parent_instance;
+
+ GimpDisplayConfig *config;
+ PangoLayout *layout;
+};
+
+struct _GimpCanvasClass
+{
+ GimpOverlayBoxClass parent_class;
+};
+
+
+GType gimp_canvas_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_canvas_new (GimpDisplayConfig *config);
+
+PangoLayout * gimp_canvas_get_layout (GimpCanvas *canvas,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+void gimp_canvas_set_bg_color (GimpCanvas *canvas,
+ GimpRGB *color);
+
+
+#endif /* __GIMP_CANVAS_H__ */
diff --git a/app/display/gimpcanvasarc.c b/app/display/gimpcanvasarc.c
new file mode 100644
index 0000000..a4041b2
--- /dev/null
+++ b/app/display/gimpcanvasarc.c
@@ -0,0 +1,369 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasarc.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+
+#include "gimpcanvasarc.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CENTER_X,
+ PROP_CENTER_Y,
+ PROP_RADIUS_X,
+ PROP_RADIUS_Y,
+ PROP_START_ANGLE,
+ PROP_SLICE_ANGLE,
+ PROP_FILLED
+};
+
+
+typedef struct _GimpCanvasArcPrivate GimpCanvasArcPrivate;
+
+struct _GimpCanvasArcPrivate
+{
+ gdouble center_x;
+ gdouble center_y;
+ gdouble radius_x;
+ gdouble radius_y;
+ gdouble start_angle;
+ gdouble slice_angle;
+ gboolean filled;
+};
+
+#define GET_PRIVATE(arc) \
+ ((GimpCanvasArcPrivate *) gimp_canvas_arc_get_instance_private ((GimpCanvasArc *) (arc)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_arc_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_arc_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_arc_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_arc_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasArc, gimp_canvas_arc,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_arc_parent_class
+
+
+static void
+gimp_canvas_arc_class_init (GimpCanvasArcClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_arc_set_property;
+ object_class->get_property = gimp_canvas_arc_get_property;
+
+ item_class->draw = gimp_canvas_arc_draw;
+ item_class->get_extents = gimp_canvas_arc_get_extents;
+
+ g_object_class_install_property (object_class, PROP_CENTER_X,
+ g_param_spec_double ("center-x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_CENTER_Y,
+ g_param_spec_double ("center-y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_RADIUS_X,
+ g_param_spec_double ("radius-x", NULL, NULL,
+ 0, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_RADIUS_Y,
+ g_param_spec_double ("radius-y", NULL, NULL,
+ 0, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_START_ANGLE,
+ g_param_spec_double ("start-angle", NULL, NULL,
+ -1000, 1000, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SLICE_ANGLE,
+ g_param_spec_double ("slice-angle", NULL, NULL,
+ -1000, 1000, 2 * G_PI,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_FILLED,
+ g_param_spec_boolean ("filled", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_arc_init (GimpCanvasArc *arc)
+{
+}
+
+static void
+gimp_canvas_arc_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasArcPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_CENTER_X:
+ private->center_x = g_value_get_double (value);
+ break;
+ case PROP_CENTER_Y:
+ private->center_y = g_value_get_double (value);
+ break;
+ case PROP_RADIUS_X:
+ private->radius_x = g_value_get_double (value);
+ break;
+ case PROP_RADIUS_Y:
+ private->radius_y = g_value_get_double (value);
+ break;
+ case PROP_START_ANGLE:
+ private->start_angle = g_value_get_double (value);
+ break;
+ case PROP_SLICE_ANGLE:
+ private->slice_angle = g_value_get_double (value);
+ break;
+ case PROP_FILLED:
+ private->filled = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_arc_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasArcPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_CENTER_X:
+ g_value_set_double (value, private->center_x);
+ break;
+ case PROP_CENTER_Y:
+ g_value_set_double (value, private->center_y);
+ break;
+ case PROP_RADIUS_X:
+ g_value_set_double (value, private->radius_x);
+ break;
+ case PROP_RADIUS_Y:
+ g_value_set_double (value, private->radius_y);
+ break;
+ case PROP_START_ANGLE:
+ g_value_set_double (value, private->start_angle);
+ break;
+ case PROP_SLICE_ANGLE:
+ g_value_set_double (value, private->slice_angle);
+ break;
+ case PROP_FILLED:
+ g_value_set_boolean (value, private->filled);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_arc_transform (GimpCanvasItem *item,
+ gdouble *center_x,
+ gdouble *center_y,
+ gdouble *radius_x,
+ gdouble *radius_y)
+{
+ GimpCanvasArcPrivate *private = GET_PRIVATE (item);
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_item_transform_xy_f (item,
+ private->center_x - private->radius_x,
+ private->center_y - private->radius_y,
+ &x1, &y1);
+ gimp_canvas_item_transform_xy_f (item,
+ private->center_x + private->radius_x,
+ private->center_y + private->radius_y,
+ &x2, &y2);
+
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+
+ *center_x = (x1 + x2) / 2.0;
+ *center_y = (y1 + y2) / 2.0;
+
+ *radius_x = (x2 - x1) / 2.0;
+ *radius_y = (y2 - y1) / 2.0;
+
+ if (! private->filled)
+ {
+ *radius_x = MAX (*radius_x - 0.5, 0.0);
+ *radius_y = MAX (*radius_y - 0.5, 0.0);
+ }
+
+ /* avoid cairo_scale (cr, 0.0, 0.0) */
+ if (*radius_x == 0.0) *radius_x = 0.000001;
+ if (*radius_y == 0.0) *radius_y = 0.000001;
+}
+
+static void
+gimp_canvas_arc_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasArcPrivate *private = GET_PRIVATE (item);
+ gdouble center_x, center_y;
+ gdouble radius_x, radius_y;
+
+ gimp_canvas_arc_transform (item,
+ &center_x, &center_y,
+ &radius_x, &radius_y);
+
+ cairo_save (cr);
+ cairo_translate (cr, center_x, center_y);
+ cairo_scale (cr, radius_x, radius_y);
+ gimp_cairo_arc (cr, 0.0, 0.0, 1.0,
+ private->start_angle, private->slice_angle);
+ cairo_restore (cr);
+
+ if (private->filled)
+ _gimp_canvas_item_fill (item, cr);
+ else
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_arc_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasArcPrivate *private = GET_PRIVATE (item);
+ cairo_region_t *region;
+ cairo_rectangle_int_t rectangle;
+ gdouble center_x, center_y;
+ gdouble radius_x, radius_y;
+
+ gimp_canvas_arc_transform (item,
+ &center_x, &center_y,
+ &radius_x, &radius_y);
+
+ rectangle.x = floor (center_x - radius_x - 1.5);
+ rectangle.y = floor (center_y - radius_y - 1.5);
+ rectangle.width = ceil (center_x + radius_x + 1.5) - rectangle.x;
+ rectangle.height = ceil (center_y + radius_y + 1.5) - rectangle.y;
+
+ region = cairo_region_create_rectangle (&rectangle);
+
+ if (! private->filled &&
+ rectangle.width > 64 * 1.43 &&
+ rectangle.height > 64 * 1.43)
+ {
+ radius_x *= 0.7;
+ radius_y *= 0.7;
+
+ rectangle.x = ceil (center_x - radius_x + 1.5);
+ rectangle.y = ceil (center_y - radius_y + 1.5);
+ rectangle.width = floor (center_x + radius_x - 1.5) - rectangle.x;
+ rectangle.height = floor (center_y + radius_y - 1.5) - rectangle.y;
+
+ cairo_region_subtract_rectangle (region, &rectangle);
+ }
+
+ return region;
+}
+
+GimpCanvasItem *
+gimp_canvas_arc_new (GimpDisplayShell *shell,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble start_angle,
+ gdouble slice_angle,
+ gboolean filled)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_ARC,
+ "shell", shell,
+ "center-x", center_x,
+ "center-y", center_y,
+ "radius-x", radius_x,
+ "radius-y", radius_y,
+ "start-angle", start_angle,
+ "slice-angle", slice_angle,
+ "filled", filled,
+ NULL);
+}
+
+void
+gimp_canvas_arc_set (GimpCanvasItem *arc,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble start_angle,
+ gdouble slice_angle)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ARC (arc));
+
+ gimp_canvas_item_begin_change (arc);
+ g_object_set (arc,
+ "center-x", center_x,
+ "center-y", center_y,
+ "radius-x", radius_x,
+ "radius-y", radius_y,
+ "start-angle", start_angle,
+ "slice-angle", slice_angle,
+ NULL);
+ gimp_canvas_item_end_change (arc);
+}
diff --git a/app/display/gimpcanvasarc.h b/app/display/gimpcanvasarc.h
new file mode 100644
index 0000000..1896352
--- /dev/null
+++ b/app/display/gimpcanvasarc.h
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasarc.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_ARC_H__
+#define __GIMP_CANVAS_ARC_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_ARC (gimp_canvas_arc_get_type ())
+#define GIMP_CANVAS_ARC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_ARC, GimpCanvasArc))
+#define GIMP_CANVAS_ARC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_ARC, GimpCanvasArcClass))
+#define GIMP_IS_CANVAS_ARC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_ARC))
+#define GIMP_IS_CANVAS_ARC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_ARC))
+#define GIMP_CANVAS_ARC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_ARC, GimpCanvasArcClass))
+
+
+typedef struct _GimpCanvasArc GimpCanvasArc;
+typedef struct _GimpCanvasArcClass GimpCanvasArcClass;
+
+struct _GimpCanvasArc
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasArcClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_arc_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_arc_new (GimpDisplayShell *shell,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble start_angle,
+ gdouble slice_angle,
+ gboolean filled);
+
+void gimp_canvas_arc_set (GimpCanvasItem *arc,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble start_angle,
+ gdouble slice_angle);
+
+#endif /* __GIMP_CANVAS_ARC_H__ */
diff --git a/app/display/gimpcanvasboundary.c b/app/display/gimpcanvasboundary.c
new file mode 100644
index 0000000..a6cab9d
--- /dev/null
+++ b/app/display/gimpcanvasboundary.c
@@ -0,0 +1,384 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasboundary.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+#include "core/gimp-transform-utils.h"
+#include "core/gimpboundary.h"
+#include "core/gimpparamspecs.h"
+
+#include "gimpcanvasboundary.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SEGS,
+ PROP_TRANSFORM,
+ PROP_OFFSET_X,
+ PROP_OFFSET_Y
+};
+
+
+typedef struct _GimpCanvasBoundaryPrivate GimpCanvasBoundaryPrivate;
+
+struct _GimpCanvasBoundaryPrivate
+{
+ GimpBoundSeg *segs;
+ gint n_segs;
+ GimpMatrix3 *transform;
+ gdouble offset_x;
+ gdouble offset_y;
+};
+
+#define GET_PRIVATE(boundary) \
+ ((GimpCanvasBoundaryPrivate *) gimp_canvas_boundary_get_instance_private ((GimpCanvasBoundary *) (boundary)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_boundary_finalize (GObject *object);
+static void gimp_canvas_boundary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_boundary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_boundary_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_boundary_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasBoundary, gimp_canvas_boundary,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_boundary_parent_class
+
+
+static void
+gimp_canvas_boundary_class_init (GimpCanvasBoundaryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_boundary_finalize;
+ object_class->set_property = gimp_canvas_boundary_set_property;
+ object_class->get_property = gimp_canvas_boundary_get_property;
+
+ item_class->draw = gimp_canvas_boundary_draw;
+ item_class->get_extents = gimp_canvas_boundary_get_extents;
+
+ g_object_class_install_property (object_class, PROP_SEGS,
+ gimp_param_spec_array ("segs", NULL, NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TRANSFORM,
+ g_param_spec_pointer ("transform", NULL, NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_X,
+ g_param_spec_double ("offset-x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_Y,
+ g_param_spec_double ("offset-y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_boundary_init (GimpCanvasBoundary *boundary)
+{
+ gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (boundary),
+ CAIRO_LINE_CAP_SQUARE);
+}
+
+static void
+gimp_canvas_boundary_finalize (GObject *object)
+{
+ GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object);
+
+ g_clear_pointer (&private->segs, g_free);
+ private->n_segs = 0;
+
+ g_clear_pointer (&private->transform, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_boundary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_SEGS:
+ break;
+
+ case PROP_TRANSFORM:
+ {
+ GimpMatrix3 *transform = g_value_get_pointer (value);
+ if (private->transform)
+ g_free (private->transform);
+ if (transform)
+ private->transform = g_memdup (transform, sizeof (GimpMatrix3));
+ else
+ private->transform = NULL;
+ }
+ break;
+
+ case PROP_OFFSET_X:
+ private->offset_x = g_value_get_double (value);
+ break;
+ case PROP_OFFSET_Y:
+ private->offset_y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_boundary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_SEGS:
+ break;
+
+ case PROP_TRANSFORM:
+ g_value_set_pointer (value, private->transform);
+ break;
+
+ case PROP_OFFSET_X:
+ g_value_set_double (value, private->offset_x);
+ break;
+ case PROP_OFFSET_Y:
+ g_value_set_double (value, private->offset_y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_boundary_transform (GimpCanvasItem *item,
+ GimpSegment *segs,
+ gint *n_segs)
+{
+ GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item);
+ gint i;
+
+ if (private->transform)
+ {
+ gint n = 0;
+
+ for (i = 0; i < private->n_segs; i++)
+ {
+ GimpVector2 vertices[2];
+ GimpVector2 t_vertices[2];
+ gint n_t_vertices;
+
+ vertices[0] = (GimpVector2) { private->segs[i].x1, private->segs[i].y1 };
+ vertices[1] = (GimpVector2) { private->segs[i].x2, private->segs[i].y2 };
+
+ gimp_transform_polygon (private->transform, vertices, 2, FALSE,
+ t_vertices, &n_t_vertices);
+
+ if (n_t_vertices == 2)
+ {
+ gimp_canvas_item_transform_xy (item,
+ t_vertices[0].x + private->offset_x,
+ t_vertices[0].y + private->offset_y,
+ &segs[n].x1, &segs[n].y1);
+ gimp_canvas_item_transform_xy (item,
+ t_vertices[1].x + private->offset_x,
+ t_vertices[1].y + private->offset_y,
+ &segs[n].x2, &segs[n].y2);
+
+ n++;
+ }
+ }
+
+ *n_segs = n;
+ }
+ else
+ {
+ for (i = 0; i < private->n_segs; i++)
+ {
+ gimp_canvas_item_transform_xy (item,
+ private->segs[i].x1 + private->offset_x,
+ private->segs[i].y1 + private->offset_y,
+ &segs[i].x1,
+ &segs[i].y1);
+ gimp_canvas_item_transform_xy (item,
+ private->segs[i].x2 + private->offset_x,
+ private->segs[i].y2 + private->offset_y,
+ &segs[i].x2,
+ &segs[i].y2);
+
+ /* If this segment is a closing segment && the segments lie inside
+ * the region, OR if this is an opening segment and the segments
+ * lie outside the region...
+ * we need to transform it by one display pixel
+ */
+ if (! private->segs[i].open)
+ {
+ /* If it is vertical */
+ if (segs[i].x1 == segs[i].x2)
+ {
+ segs[i].x1 -= 1;
+ segs[i].x2 -= 1;
+ }
+ else
+ {
+ segs[i].y1 -= 1;
+ segs[i].y2 -= 1;
+ }
+ }
+ }
+
+ *n_segs = private->n_segs;
+ }
+}
+
+static void
+gimp_canvas_boundary_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item);
+ GimpSegment *segs;
+ gint n_segs;
+
+ segs = g_new0 (GimpSegment, private->n_segs);
+
+ gimp_canvas_boundary_transform (item, segs, &n_segs);
+
+ gimp_cairo_segments (cr, segs, n_segs);
+
+ _gimp_canvas_item_stroke (item, cr);
+
+ g_free (segs);
+}
+
+static cairo_region_t *
+gimp_canvas_boundary_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item);
+ cairo_rectangle_int_t rectangle;
+ GimpSegment *segs;
+ gint n_segs;
+ gint x1, y1, x2, y2;
+ gint i;
+
+ segs = g_new0 (GimpSegment, private->n_segs);
+
+ gimp_canvas_boundary_transform (item, segs, &n_segs);
+
+ if (n_segs == 0)
+ {
+ g_free (segs);
+
+ return cairo_region_create ();
+ }
+
+ x1 = MIN (segs[0].x1, segs[0].x2);
+ y1 = MIN (segs[0].y1, segs[0].y2);
+ x2 = MAX (segs[0].x1, segs[0].x2);
+ y2 = MAX (segs[0].y1, segs[0].y2);
+
+ for (i = 1; i < n_segs; i++)
+ {
+ gint x3 = MIN (segs[i].x1, segs[i].x2);
+ gint y3 = MIN (segs[i].y1, segs[i].y2);
+ gint x4 = MAX (segs[i].x1, segs[i].x2);
+ gint y4 = MAX (segs[i].y1, segs[i].y2);
+
+ x1 = MIN (x1, x3);
+ y1 = MIN (y1, y3);
+ x2 = MAX (x2, x4);
+ y2 = MAX (y2, y4);
+ }
+
+ g_free (segs);
+
+ rectangle.x = x1 - 2;
+ rectangle.y = y1 - 2;
+ rectangle.width = x2 - x1 + 4;
+ rectangle.height = y2 - y1 + 4;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+GimpCanvasItem *
+gimp_canvas_boundary_new (GimpDisplayShell *shell,
+ const GimpBoundSeg *segs,
+ gint n_segs,
+ GimpMatrix3 *transform,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ GimpCanvasItem *item;
+ GimpCanvasBoundaryPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ item = g_object_new (GIMP_TYPE_CANVAS_BOUNDARY,
+ "shell", shell,
+ "transform", transform,
+ "offset-x", offset_x,
+ "offset-y", offset_y,
+ NULL);
+ private = GET_PRIVATE (item);
+
+ /* puke */
+ private->segs = g_memdup (segs, n_segs * sizeof (GimpBoundSeg));
+ private->n_segs = n_segs;
+
+ return item;
+}
diff --git a/app/display/gimpcanvasboundary.h b/app/display/gimpcanvasboundary.h
new file mode 100644
index 0000000..527f89e
--- /dev/null
+++ b/app/display/gimpcanvasboundary.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995
+ * Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasboundary.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_BOUNDARY_H__
+#define __GIMP_CANVAS_BOUNDARY_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_BOUNDARY (gimp_canvas_boundary_get_type ())
+#define GIMP_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_BOUNDARY, GimpCanvasBoundary))
+#define GIMP_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_BOUNDARY, GimpCanvasBoundaryClass))
+#define GIMP_IS_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_BOUNDARY))
+#define GIMP_IS_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_BOUNDARY))
+#define GIMP_CANVAS_BOUNDARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_BOUNDARY, GimpCanvasBoundaryClass))
+
+
+typedef struct _GimpCanvasBoundary GimpCanvasBoundary;
+typedef struct _GimpCanvasBoundaryClass GimpCanvasBoundaryClass;
+
+struct _GimpCanvasBoundary
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasBoundaryClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_boundary_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_boundary_new (GimpDisplayShell *shell,
+ const GimpBoundSeg *segs,
+ gint n_segs,
+ GimpMatrix3 *transform,
+ gdouble offset_x,
+ gdouble offset_y);
+
+
+#endif /* __GIMP_CANVAS_BOUNDARY_H__ */
diff --git a/app/display/gimpcanvasbufferpreview.c b/app/display/gimpcanvasbufferpreview.c
new file mode 100644
index 0000000..858bab6
--- /dev/null
+++ b/app/display/gimpcanvasbufferpreview.c
@@ -0,0 +1,263 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasbufferpreview.c
+ * Copyright (C) 2013 Marek Dvoroznak <dvoromar@gmail.com>
+ *
+ * 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 <cairo/cairo.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display/display-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvasbufferpreview.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scroll.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER
+};
+
+
+typedef struct _GimpCanvasBufferPreviewPrivate GimpCanvasBufferPreviewPrivate;
+
+struct _GimpCanvasBufferPreviewPrivate
+{
+ GeglBuffer *buffer;
+};
+
+
+#define GET_PRIVATE(transform_preview) \
+ ((GimpCanvasBufferPreviewPrivate *) gimp_canvas_buffer_preview_get_instance_private ((GimpCanvasBufferPreview *) (transform_preview)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_buffer_preview_dispose (GObject *object);
+static void gimp_canvas_buffer_preview_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_buffer_preview_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_canvas_buffer_preview_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_buffer_preview_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_buffer_preview_compute_bounds (GimpCanvasItem *item,
+ cairo_rectangle_int_t *bounds);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasBufferPreview, gimp_canvas_buffer_preview,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_buffer_preview_parent_class
+
+
+static void
+gimp_canvas_buffer_preview_class_init (GimpCanvasBufferPreviewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->dispose = gimp_canvas_buffer_preview_dispose;
+ object_class->set_property = gimp_canvas_buffer_preview_set_property;
+ object_class->get_property = gimp_canvas_buffer_preview_get_property;
+
+ item_class->draw = gimp_canvas_buffer_preview_draw;
+ item_class->get_extents = gimp_canvas_buffer_preview_get_extents;
+
+ g_object_class_install_property (object_class, PROP_BUFFER,
+ g_param_spec_object ("buffer",
+ NULL, NULL,
+ GEGL_TYPE_BUFFER,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_buffer_preview_init (GimpCanvasBufferPreview *transform_preview)
+{
+}
+
+static void
+gimp_canvas_buffer_preview_dispose (GObject *object)
+{
+ GimpCanvasBufferPreviewPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->buffer);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_canvas_buffer_preview_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasBufferPreviewPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_set_object (&private->buffer, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_buffer_preview_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasBufferPreviewPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, private->buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_buffer_preview_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ GeglBuffer *buffer = GET_PRIVATE (item)->buffer;
+ cairo_surface_t *area;
+ guchar *data;
+ cairo_rectangle_int_t rectangle;
+
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ gimp_canvas_buffer_preview_compute_bounds (item, &rectangle);
+
+ area = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ rectangle.width,
+ rectangle.height);
+
+ data = cairo_image_surface_get_data (area);
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (rectangle.x + shell->offset_x,
+ rectangle.y + shell->offset_y,
+ rectangle.width,
+ rectangle.height),
+ shell->scale_x,
+ babl_format ("cairo-ARGB32"),
+ data,
+ cairo_image_surface_get_stride (area),
+ GEGL_ABYSS_NONE);
+
+ cairo_surface_flush (area);
+ cairo_surface_mark_dirty (area);
+
+ cairo_set_source_surface (cr, area, rectangle.x, rectangle.y);
+ cairo_rectangle (cr,
+ rectangle.x, rectangle.y,
+ rectangle.width, rectangle.height);
+ cairo_fill (cr);
+
+ cairo_surface_destroy (area);
+}
+
+static void
+gimp_canvas_buffer_preview_compute_bounds (GimpCanvasItem *item,
+ cairo_rectangle_int_t *bounds)
+{
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ GeglBuffer *buffer = GET_PRIVATE (item)->buffer;
+ GeglRectangle extent;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ extent = *gegl_buffer_get_extent (buffer);
+
+ gimp_canvas_item_transform_xy_f (item,
+ extent.x,
+ extent.y,
+ &x1, &y1);
+ gimp_canvas_item_transform_xy_f (item,
+ extent.x + extent.width,
+ extent.y + extent.height,
+ &x2, &y2);
+
+ extent.x = floor (x1);
+ extent.y = floor (y1);
+ extent.width = ceil (x2) - extent.x;
+ extent.height = ceil (y2) - extent.y;
+
+ gegl_rectangle_intersect (&extent,
+ &extent,
+ GEGL_RECTANGLE (0,
+ 0,
+ shell->disp_width,
+ shell->disp_height));
+
+ bounds->x = extent.x;
+ bounds->y = extent.y;
+ bounds->width = extent.width;
+ bounds->height = extent.height;
+}
+
+static cairo_region_t *
+gimp_canvas_buffer_preview_get_extents (GimpCanvasItem *item)
+{
+ cairo_rectangle_int_t rectangle;
+
+ gimp_canvas_buffer_preview_compute_bounds (item, &rectangle);
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+GimpCanvasItem *
+gimp_canvas_buffer_preview_new (GimpDisplayShell *shell,
+ GeglBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_BUFFER_PREVIEW,
+ "shell", shell,
+ "buffer", buffer,
+ NULL);
+}
diff --git a/app/display/gimpcanvasbufferpreview.h b/app/display/gimpcanvasbufferpreview.h
new file mode 100644
index 0000000..28d8806
--- /dev/null
+++ b/app/display/gimpcanvasbufferpreview.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasbufferpreview.h
+ * Copyright (C) 2013 Marek Dvoroznak <dvoromar@gmail.com>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_BUFFER_PREVIEW_H__
+#define __GIMP_CANVAS_BUFFER_PREVIEW_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_BUFFER_PREVIEW (gimp_canvas_buffer_preview_get_type ())
+#define GIMP_CANVAS_BUFFER_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_BUFFER_PREVIEW, GimpCanvasBufferPreview))
+#define GIMP_CANVAS_BUFFER_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_BUFFER_PREVIEW, GimpCanvasBufferPreviewClass))
+#define GIMP_IS_CANVAS_BUFFER_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_BUFFER_PREVIEW))
+#define GIMP_IS_CANVAS_BUFFER_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_BUFFER_PREVIEW))
+#define GIMP_CANVAS_BUFFER_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_BUFFER_PREVIEW, GimpCanvasBufferPreviewClass))
+
+
+typedef struct _GimpCanvasBufferPreview GimpCanvasBufferPreview;
+typedef struct _GimpCanvasBufferPreviewClass GimpCanvasBufferPreviewClass;
+
+struct _GimpCanvasBufferPreview
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasBufferPreviewClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_buffer_preview_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_buffer_preview_new (GimpDisplayShell *shell,
+ GeglBuffer *buffer);
+
+
+#endif /* __GIMP_CANVAS_BUFFER_PREVIEW_H__ */
diff --git a/app/display/gimpcanvascanvasboundary.c b/app/display/gimpcanvascanvasboundary.c
new file mode 100644
index 0000000..01ac5a3
--- /dev/null
+++ b/app/display/gimpcanvascanvasboundary.c
@@ -0,0 +1,270 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvascanvasboundary.c
+ * Copyright (C) 2019 Ell
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvascanvasboundary.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE
+};
+
+
+typedef struct _GimpCanvasCanvasBoundaryPrivate GimpCanvasCanvasBoundaryPrivate;
+
+struct _GimpCanvasCanvasBoundaryPrivate
+{
+ GimpImage *image;
+};
+
+#define GET_PRIVATE(canvas_boundary) \
+ ((GimpCanvasCanvasBoundaryPrivate *) gimp_canvas_canvas_boundary_get_instance_private ((GimpCanvasCanvasBoundary *) (canvas_boundary)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_canvas_boundary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_canvas_boundary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_canvas_boundary_finalize (GObject *object);
+static void gimp_canvas_canvas_boundary_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_canvas_boundary_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_canvas_boundary_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasCanvasBoundary, gimp_canvas_canvas_boundary,
+ GIMP_TYPE_CANVAS_RECTANGLE)
+
+#define parent_class gimp_canvas_canvas_boundary_parent_class
+
+
+static void
+gimp_canvas_canvas_boundary_class_init (GimpCanvasCanvasBoundaryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_canvas_boundary_set_property;
+ object_class->get_property = gimp_canvas_canvas_boundary_get_property;
+ object_class->finalize = gimp_canvas_canvas_boundary_finalize;
+
+ item_class->draw = gimp_canvas_canvas_boundary_draw;
+ item_class->get_extents = gimp_canvas_canvas_boundary_get_extents;
+ item_class->stroke = gimp_canvas_canvas_boundary_stroke;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image", NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_canvas_boundary_init (GimpCanvasCanvasBoundary *canvas_boundary)
+{
+}
+
+static void
+gimp_canvas_canvas_boundary_finalize (GObject *object)
+{
+ GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (object);
+
+ if (private->image)
+ g_object_remove_weak_pointer (G_OBJECT (private->image),
+ (gpointer) &private->image);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_canvas_boundary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ if (private->image)
+ g_object_remove_weak_pointer (G_OBJECT (private->image),
+ (gpointer) &private->image);
+ private->image = g_value_get_object (value); /* don't ref */
+ if (private->image)
+ g_object_add_weak_pointer (G_OBJECT (private->image),
+ (gpointer) &private->image);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_canvas_boundary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, private->image);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_canvas_boundary_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (item);
+
+ if (private->image)
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_canvas_boundary_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (item);
+
+ if (private->image)
+ return GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item);
+
+ return NULL;
+}
+
+static void
+gimp_canvas_canvas_boundary_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+
+ gimp_canvas_set_canvas_style (gimp_canvas_item_get_canvas (item), cr,
+ shell->offset_x, shell->offset_y);
+ cairo_stroke (cr);
+}
+
+GimpCanvasItem *
+gimp_canvas_canvas_boundary_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_CANVAS_BOUNDARY,
+ "shell", shell,
+ NULL);
+}
+
+void
+gimp_canvas_canvas_boundary_set_image (GimpCanvasCanvasBoundary *boundary,
+ GimpImage *image)
+{
+ GimpCanvasCanvasBoundaryPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_CANVAS_BOUNDARY (boundary));
+ g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image));
+
+ private = GET_PRIVATE (boundary);
+
+ if (image != private->image)
+ {
+ gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary));
+
+ if (image)
+ {
+ g_object_set (boundary,
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ "width", (gdouble) gimp_image_get_width (image),
+ "height", (gdouble) gimp_image_get_height (image),
+ NULL);
+ }
+
+ g_object_set (boundary,
+ "image", image,
+ NULL);
+
+ gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary));
+ }
+ else if (image && image == private->image)
+ {
+ gint lx, ly, lw, lh;
+ gdouble x, y, w ,h;
+
+ lx = 0;
+ ly = 0;
+ lw = gimp_image_get_width (image);
+ lh = gimp_image_get_height (image);
+
+ g_object_get (boundary,
+ "x", &x,
+ "y", &y,
+ "width", &w,
+ "height", &h,
+ NULL);
+
+ if (lx != (gint) x ||
+ ly != (gint) y ||
+ lw != (gint) w ||
+ lh != (gint) h)
+ {
+ gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary));
+
+ g_object_set (boundary,
+ "x", (gdouble) lx,
+ "y", (gdouble) ly,
+ "width", (gdouble) lw,
+ "height", (gdouble) lh,
+ NULL);
+
+ gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary));
+ }
+ }
+}
diff --git a/app/display/gimpcanvascanvasboundary.h b/app/display/gimpcanvascanvasboundary.h
new file mode 100644
index 0000000..c8fe16f
--- /dev/null
+++ b/app/display/gimpcanvascanvasboundary.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvascanvasvboundary.h
+ * Copyright (C) 2019 Ell
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_CANVAS_BOUNDARY_H__
+#define __GIMP_CANVAS_CANVAS_BOUNDARY_H__
+
+
+#include "gimpcanvasrectangle.h"
+
+
+#define GIMP_TYPE_CANVAS_CANVAS_BOUNDARY (gimp_canvas_canvas_boundary_get_type ())
+#define GIMP_CANVAS_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, GimpCanvasCanvasBoundary))
+#define GIMP_CANVAS_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, GimpCanvasCanvasBoundaryClass))
+#define GIMP_IS_CANVAS_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY))
+#define GIMP_IS_CANVAS_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY))
+#define GIMP_CANVAS_CANVAS_BOUNDARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, GimpCanvasCanvasBoundaryClass))
+
+
+typedef struct _GimpCanvasCanvasBoundary GimpCanvasCanvasBoundary;
+typedef struct _GimpCanvasCanvasBoundaryClass GimpCanvasCanvasBoundaryClass;
+
+struct _GimpCanvasCanvasBoundary
+{
+ GimpCanvasRectangle parent_instance;
+};
+
+struct _GimpCanvasCanvasBoundaryClass
+{
+ GimpCanvasRectangleClass parent_class;
+};
+
+
+GType gimp_canvas_canvas_boundary_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_canvas_boundary_new (GimpDisplayShell *shell);
+
+void gimp_canvas_canvas_boundary_set_image (GimpCanvasCanvasBoundary *boundary,
+ GimpImage *image);
+
+
+#endif /* __GIMP_CANVAS_CANVAS_BOUNDARY_H__ */
diff --git a/app/display/gimpcanvascorner.c b/app/display/gimpcanvascorner.c
new file mode 100644
index 0000000..50aebfb
--- /dev/null
+++ b/app/display/gimpcanvascorner.c
@@ -0,0 +1,468 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvascorner.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvascorner.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_ANCHOR,
+ PROP_CORNER_WIDTH,
+ PROP_CORNER_HEIGHT,
+ PROP_OUTSIDE
+};
+
+
+typedef struct _GimpCanvasCornerPrivate GimpCanvasCornerPrivate;
+
+struct _GimpCanvasCornerPrivate
+{
+ gdouble x;
+ gdouble y;
+ gdouble width;
+ gdouble height;
+ GimpHandleAnchor anchor;
+ gint corner_width;
+ gint corner_height;
+ gboolean outside;
+};
+
+#define GET_PRIVATE(corner) \
+ ((GimpCanvasCornerPrivate *) gimp_canvas_corner_get_instance_private ((GimpCanvasCorner *) (corner)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_corner_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_corner_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_corner_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_corner_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasCorner, gimp_canvas_corner,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_corner_parent_class
+
+
+static void
+gimp_canvas_corner_class_init (GimpCanvasCornerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_corner_set_property;
+ object_class->get_property = gimp_canvas_corner_get_property;
+
+ item_class->draw = gimp_canvas_corner_draw;
+ item_class->get_extents = gimp_canvas_corner_get_extents;
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_double ("width", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_double ("height", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ANCHOR,
+ g_param_spec_enum ("anchor", NULL, NULL,
+ GIMP_TYPE_HANDLE_ANCHOR,
+ GIMP_HANDLE_ANCHOR_CENTER,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_CORNER_WIDTH,
+ g_param_spec_int ("corner-width", NULL, NULL,
+ 3, GIMP_MAX_IMAGE_SIZE, 3,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_CORNER_HEIGHT,
+ g_param_spec_int ("corner-height", NULL, NULL,
+ 3, GIMP_MAX_IMAGE_SIZE, 3,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_OUTSIDE,
+ g_param_spec_boolean ("outside", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_corner_init (GimpCanvasCorner *corner)
+{
+}
+
+static void
+gimp_canvas_corner_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasCornerPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_double (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_double (value);
+ break;
+ case PROP_ANCHOR:
+ private->anchor = g_value_get_enum (value);
+ break;
+ case PROP_CORNER_WIDTH:
+ private->corner_width = g_value_get_int (value);
+ break;
+ case PROP_CORNER_HEIGHT:
+ private->corner_height = g_value_get_int (value);
+ break;
+ case PROP_OUTSIDE:
+ private->outside = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_corner_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasCornerPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case PROP_WIDTH:
+ g_value_set_double (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_double (value, private->height);
+ break;
+ case PROP_ANCHOR:
+ g_value_set_enum (value, private->anchor);
+ break;
+ case PROP_CORNER_WIDTH:
+ g_value_set_int (value, private->corner_width);
+ break;
+ case PROP_CORNER_HEIGHT:
+ g_value_set_int (value, private->corner_height);
+ break;
+ case PROP_OUTSIDE:
+ g_value_set_boolean (value, private->outside);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_corner_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *w,
+ gdouble *h)
+{
+ GimpCanvasCornerPrivate *private = GET_PRIVATE (item);
+ gdouble rx, ry;
+ gdouble rw, rh;
+ gint top_and_bottom_handle_x_offset;
+ gint left_and_right_handle_y_offset;
+
+ gimp_canvas_item_transform_xy_f (item,
+ MIN (private->x,
+ private->x + private->width),
+ MIN (private->y,
+ private->y + private->height),
+ &rx, &ry);
+ gimp_canvas_item_transform_xy_f (item,
+ MAX (private->x,
+ private->x + private->width),
+ MAX (private->y,
+ private->y + private->height),
+ &rw, &rh);
+
+ rw -= rx;
+ rh -= ry;
+
+ rx = floor (rx) + 0.5;
+ ry = floor (ry) + 0.5;
+ rw = ceil (rw) - 1.0;
+ rh = ceil (rh) - 1.0;
+
+ top_and_bottom_handle_x_offset = (rw - private->corner_width) / 2;
+ left_and_right_handle_y_offset = (rh - private->corner_height) / 2;
+
+ *w = private->corner_width;
+ *h = private->corner_height;
+
+ switch (private->anchor)
+ {
+ case GIMP_HANDLE_ANCHOR_CENTER:
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_WEST:
+ if (private->outside)
+ {
+ *x = rx - private->corner_width;
+ *y = ry - private->corner_height;
+ }
+ else
+ {
+ *x = rx;
+ *y = ry;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_EAST:
+ if (private->outside)
+ {
+ *x = rx + rw;
+ *y = ry - private->corner_height;
+ }
+ else
+ {
+ *x = rx + rw - private->corner_width;
+ *y = ry;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
+ if (private->outside)
+ {
+ *x = rx - private->corner_width;
+ *y = ry + rh;
+ }
+ else
+ {
+ *x = rx;
+ *y = ry + rh - private->corner_height;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
+ if (private->outside)
+ {
+ *x = rx + rw;
+ *y = ry + rh;
+ }
+ else
+ {
+ *x = rx + rw - private->corner_width;
+ *y = ry + rh - private->corner_height;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH:
+ if (private->outside)
+ {
+ *x = rx;
+ *y = ry - private->corner_height;
+ *w = rw;
+ }
+ else
+ {
+ *x = rx + top_and_bottom_handle_x_offset;
+ *y = ry;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH:
+ if (private->outside)
+ {
+ *x = rx;
+ *y = ry + rh;
+ *w = rw;
+ }
+ else
+ {
+ *x = rx + top_and_bottom_handle_x_offset;
+ *y = ry + rh - private->corner_height;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_WEST:
+ if (private->outside)
+ {
+ *x = rx - private->corner_width;
+ *y = ry;
+ *h = rh;
+ }
+ else
+ {
+ *x = rx;
+ *y = ry + left_and_right_handle_y_offset;
+ }
+ break;
+
+ case GIMP_HANDLE_ANCHOR_EAST:
+ if (private->outside)
+ {
+ *x = rx + rw;
+ *y = ry;
+ *h = rh;
+ }
+ else
+ {
+ *x = rx + rw - private->corner_width;
+ *y = ry + left_and_right_handle_y_offset;
+ }
+ break;
+ }
+}
+
+static void
+gimp_canvas_corner_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_corner_transform (item, &x, &y, &w, &h);
+
+ cairo_rectangle (cr, x, y, w, h);
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_corner_get_extents (GimpCanvasItem *item)
+{
+ cairo_rectangle_int_t rectangle;
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_corner_transform (item, &x, &y, &w, &h);
+
+ rectangle.x = floor (x - 1.5);
+ rectangle.y = floor (y - 1.5);
+ rectangle.width = ceil (w + 3.0);
+ rectangle.height = ceil (h + 3.0);
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+GimpCanvasItem *
+gimp_canvas_corner_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpHandleAnchor anchor,
+ gint corner_width,
+ gint corner_height,
+ gboolean outside)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_CORNER,
+ "shell", shell,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ "anchor", anchor,
+ "corner-width", corner_width,
+ "corner-height", corner_height,
+ "outside", outside,
+ NULL);
+}
+
+void
+gimp_canvas_corner_set (GimpCanvasItem *corner,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gint corner_width,
+ gint corner_height,
+ gboolean outside)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_CORNER (corner));
+
+ gimp_canvas_item_begin_change (corner);
+ g_object_set (corner,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ "corner-width", corner_width,
+ "corner-height", corner_height,
+ "outside", outside,
+ NULL);
+ gimp_canvas_item_end_change (corner);
+}
diff --git a/app/display/gimpcanvascorner.h b/app/display/gimpcanvascorner.h
new file mode 100644
index 0000000..47cc53b
--- /dev/null
+++ b/app/display/gimpcanvascorner.h
@@ -0,0 +1,72 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvascorner.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_CORNER_H__
+#define __GIMP_CANVAS_CORNER_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_CORNER (gimp_canvas_corner_get_type ())
+#define GIMP_CANVAS_CORNER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_CORNER, GimpCanvasCorner))
+#define GIMP_CANVAS_CORNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_CORNER, GimpCanvasCornerClass))
+#define GIMP_IS_CANVAS_CORNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_CORNER))
+#define GIMP_IS_CANVAS_CORNER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_CORNER))
+#define GIMP_CANVAS_CORNER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_CORNER, GimpCanvasCornerClass))
+
+
+typedef struct _GimpCanvasCorner GimpCanvasCorner;
+typedef struct _GimpCanvasCornerClass GimpCanvasCornerClass;
+
+struct _GimpCanvasCorner
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasCornerClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_corner_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_corner_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpHandleAnchor anchor,
+ gint corner_width,
+ gint corner_height,
+ gboolean outside);
+
+void gimp_canvas_corner_set (GimpCanvasItem *corner,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gint corner_width,
+ gint corner_height,
+ gboolean outside);
+
+
+#endif /* __GIMP_CANVAS_CORNER_H__ */
diff --git a/app/display/gimpcanvascursor.c b/app/display/gimpcanvascursor.c
new file mode 100644
index 0000000..581fa76
--- /dev/null
+++ b/app/display/gimpcanvascursor.c
@@ -0,0 +1,228 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvascursor.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+
+#include "gimpcanvascursor.h"
+#include "gimpdisplayshell.h"
+
+
+#define GIMP_CURSOR_SIZE 7
+
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y
+};
+
+
+typedef struct _GimpCanvasCursorPrivate GimpCanvasCursorPrivate;
+
+struct _GimpCanvasCursorPrivate
+{
+ gdouble x;
+ gdouble y;
+};
+
+#define GET_PRIVATE(cursor) \
+ ((GimpCanvasCursorPrivate *) gimp_canvas_cursor_get_instance_private ((GimpCanvasCursor *) (cursor)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_cursor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_cursor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_cursor_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_cursor_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasCursor, gimp_canvas_cursor,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_cursor_parent_class
+
+
+static void
+gimp_canvas_cursor_class_init (GimpCanvasCursorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_cursor_set_property;
+ object_class->get_property = gimp_canvas_cursor_get_property;
+
+ item_class->draw = gimp_canvas_cursor_draw;
+ item_class->get_extents = gimp_canvas_cursor_get_extents;
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_cursor_init (GimpCanvasCursor *cursor)
+{
+ gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (cursor),
+ CAIRO_LINE_CAP_SQUARE);
+}
+
+static void
+gimp_canvas_cursor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasCursorPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_cursor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasCursorPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_cursor_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasCursorPrivate *private = GET_PRIVATE (item);
+ gdouble x, y;
+
+ x = floor (private->x) + 0.5;
+ y = floor (private->y) + 0.5;
+
+ cairo_move_to (cr, x - GIMP_CURSOR_SIZE, y);
+ cairo_line_to (cr, x + GIMP_CURSOR_SIZE, y);
+
+ cairo_move_to (cr, x, y - GIMP_CURSOR_SIZE);
+ cairo_line_to (cr, x, y + GIMP_CURSOR_SIZE);
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_cursor_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasCursorPrivate *private = GET_PRIVATE (item);
+ cairo_rectangle_int_t rectangle;
+ gdouble x, y;
+
+ x = floor (private->x) + 0.5;
+ y = floor (private->y) + 0.5;
+
+ rectangle.x = floor (x - GIMP_CURSOR_SIZE - 1.5);
+ rectangle.y = floor (y - GIMP_CURSOR_SIZE - 1.5);
+ rectangle.width = ceil (x + GIMP_CURSOR_SIZE + 1.5) - rectangle.x;
+ rectangle.height = ceil (y + GIMP_CURSOR_SIZE + 1.5) - rectangle.y;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+GimpCanvasItem *
+gimp_canvas_cursor_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_CURSOR,
+ "shell", shell,
+ NULL);
+}
+
+void
+gimp_canvas_cursor_set (GimpCanvasItem *cursor,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasCursorPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_CURSOR (cursor));
+
+ private = GET_PRIVATE (cursor);
+
+ if (private->x != x || private->y != y)
+ {
+ gimp_canvas_item_begin_change (cursor);
+
+ g_object_set (cursor,
+ "x", x,
+ "y", y,
+ NULL);
+
+ gimp_canvas_item_end_change (cursor);
+ }
+}
diff --git a/app/display/gimpcanvascursor.h b/app/display/gimpcanvascursor.h
new file mode 100644
index 0000000..b518563
--- /dev/null
+++ b/app/display/gimpcanvascursor.h
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvascursor.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_CURSOR_H__
+#define __GIMP_CANVAS_CURSOR_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_CURSOR (gimp_canvas_cursor_get_type ())
+#define GIMP_CANVAS_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_CURSOR, GimpCanvasCursor))
+#define GIMP_CANVAS_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_CURSOR, GimpCanvasCursorClass))
+#define GIMP_IS_CANVAS_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_CURSOR))
+#define GIMP_IS_CANVAS_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_CURSOR))
+#define GIMP_CANVAS_CURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_CURSOR, GimpCanvasCursorClass))
+
+
+typedef struct _GimpCanvasCursor GimpCanvasCursor;
+typedef struct _GimpCanvasCursorClass GimpCanvasCursorClass;
+
+struct _GimpCanvasCursor
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasCursorClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_cursor_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_cursor_new (GimpDisplayShell *shell);
+
+void gimp_canvas_cursor_set (GimpCanvasItem *cursor,
+ gdouble x,
+ gdouble y);
+
+
+#endif /* __GIMP_CANVAS_CURSOR_H__ */
diff --git a/app/display/gimpcanvasgrid.c b/app/display/gimpcanvasgrid.c
new file mode 100644
index 0000000..23b46ee
--- /dev/null
+++ b/app/display/gimpcanvasgrid.c
@@ -0,0 +1,414 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasgrid.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "display-types.h"
+
+#include "core/gimpgrid.h"
+#include "core/gimpimage.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvasgrid.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scale.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_GRID,
+ PROP_GRID_STYLE
+};
+
+
+typedef struct _GimpCanvasGridPrivate GimpCanvasGridPrivate;
+
+struct _GimpCanvasGridPrivate
+{
+ GimpGrid *grid;
+ gboolean grid_style;
+};
+
+#define GET_PRIVATE(grid) \
+ ((GimpCanvasGridPrivate *) gimp_canvas_grid_get_instance_private ((GimpCanvasGrid *) (grid)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_grid_finalize (GObject *object);
+static void gimp_canvas_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_grid_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_grid_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_grid_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGrid, gimp_canvas_grid,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_grid_parent_class
+
+
+static void
+gimp_canvas_grid_class_init (GimpCanvasGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_grid_finalize;
+ object_class->set_property = gimp_canvas_grid_set_property;
+ object_class->get_property = gimp_canvas_grid_get_property;
+
+ item_class->draw = gimp_canvas_grid_draw;
+ item_class->get_extents = gimp_canvas_grid_get_extents;
+ item_class->stroke = gimp_canvas_grid_stroke;
+
+ g_object_class_install_property (object_class, PROP_GRID,
+ g_param_spec_object ("grid", NULL, NULL,
+ GIMP_TYPE_GRID,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_GRID_STYLE,
+ g_param_spec_boolean ("grid-style",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_grid_init (GimpCanvasGrid *grid)
+{
+ GimpCanvasGridPrivate *private = GET_PRIVATE (grid);
+
+ private->grid = g_object_new (GIMP_TYPE_GRID, NULL);
+}
+
+static void
+gimp_canvas_grid_finalize (GObject *object)
+{
+ GimpCanvasGridPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->grid);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasGridPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_GRID:
+ {
+ GimpGrid *grid = g_value_get_object (value);
+ if (grid)
+ gimp_config_sync (G_OBJECT (grid), G_OBJECT (private->grid), 0);
+ }
+ break;
+ case PROP_GRID_STYLE:
+ private->grid_style = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasGridPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_GRID:
+ g_value_set_object (value, private->grid);
+ break;
+ case PROP_GRID_STYLE:
+ g_value_set_boolean (value, private->grid_style);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_grid_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasGridPrivate *private = GET_PRIVATE (item);
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ gdouble xspacing, yspacing;
+ gdouble xoffset, yoffset;
+ gboolean vert, horz;
+ gdouble dx1, dy1, dx2, dy2;
+ gint x1, y1, x2, y2;
+ gdouble dx, dy;
+ gint x, y;
+
+#define CROSSHAIR 2
+
+ gimp_grid_get_spacing (private->grid, &xspacing, &yspacing);
+ gimp_grid_get_offset (private->grid, &xoffset, &yoffset);
+
+ g_return_if_fail (xspacing >= 0.0 &&
+ yspacing >= 0.0);
+
+ xspacing *= shell->scale_x;
+ yspacing *= shell->scale_y;
+
+ xoffset *= shell->scale_x;
+ yoffset *= shell->scale_y;
+
+ /* skip grid drawing when the space between grid lines starts
+ * disappearing, see bug #599267.
+ */
+ vert = (xspacing >= 2.0);
+ horz = (yspacing >= 2.0);
+
+ if (! vert && ! horz)
+ return;
+
+ cairo_clip_extents (cr, &dx1, &dy1, &dx2, &dy2);
+
+ x1 = floor (dx1) - 1;
+ y1 = floor (dy1) - 1;
+ x2 = ceil (dx2) + 1;
+ y2 = ceil (dy2) + 1;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ GeglRectangle bounds;
+
+ gimp_display_shell_scale_get_image_unrotated_bounds (
+ shell,
+ &bounds.x, &bounds.y, &bounds.width, &bounds.height);
+
+ if (! gegl_rectangle_intersect (&bounds,
+ &bounds,
+ GEGL_RECTANGLE (x1, y1,
+ x2 - x1, y2 - y1)))
+ {
+ return;
+ }
+
+ x1 = bounds.x;
+ y1 = bounds.y;
+ x2 = bounds.x + bounds.width;
+ y2 = bounds.y + bounds.height;
+ }
+
+ switch (gimp_grid_get_style (private->grid))
+ {
+ case GIMP_GRID_INTERSECTIONS:
+ x1 -= CROSSHAIR;
+ y1 -= CROSSHAIR;
+ x2 += CROSSHAIR;
+ y2 += CROSSHAIR;
+ break;
+
+ case GIMP_GRID_DOTS:
+ case GIMP_GRID_ON_OFF_DASH:
+ case GIMP_GRID_DOUBLE_DASH:
+ case GIMP_GRID_SOLID:
+ break;
+ }
+
+ xoffset = fmod (xoffset - shell->offset_x - x1, xspacing);
+ yoffset = fmod (yoffset - shell->offset_y - y1, yspacing);
+
+ if (xoffset < 0.0)
+ xoffset += xspacing;
+
+ if (yoffset < 0.0)
+ yoffset += yspacing;
+
+ switch (gimp_grid_get_style (private->grid))
+ {
+ case GIMP_GRID_DOTS:
+ if (vert && horz)
+ {
+ for (dx = x1 + xoffset; dx <= x2; dx += xspacing)
+ {
+ x = RINT (dx);
+
+ for (dy = y1 + yoffset; dy <= y2; dy += yspacing)
+ {
+ y = RINT (dy);
+
+ cairo_move_to (cr, x, y + 0.5);
+ cairo_line_to (cr, x + 1.0, y + 0.5);
+ }
+ }
+ }
+ break;
+
+ case GIMP_GRID_INTERSECTIONS:
+ if (vert && horz)
+ {
+ for (dx = x1 + xoffset; dx <= x2; dx += xspacing)
+ {
+ x = RINT (dx);
+
+ for (dy = y1 + yoffset; dy <= y2; dy += yspacing)
+ {
+ y = RINT (dy);
+
+ cairo_move_to (cr, x + 0.5, y - CROSSHAIR);
+ cairo_line_to (cr, x + 0.5, y + CROSSHAIR + 1.0);
+
+ cairo_move_to (cr, x - CROSSHAIR, y + 0.5);
+ cairo_line_to (cr, x + CROSSHAIR + 1.0, y + 0.5);
+ }
+ }
+ }
+ break;
+
+ case GIMP_GRID_ON_OFF_DASH:
+ case GIMP_GRID_DOUBLE_DASH:
+ case GIMP_GRID_SOLID:
+ if (vert)
+ {
+ for (dx = x1 + xoffset; dx < x2; dx += xspacing)
+ {
+ x = RINT (dx);
+
+ cairo_move_to (cr, x + 0.5, y1);
+ cairo_line_to (cr, x + 0.5, y2);
+ }
+ }
+
+ if (horz)
+ {
+ for (dy = y1 + yoffset; dy < y2; dy += yspacing)
+ {
+ y = RINT (dy);
+
+ cairo_move_to (cr, x1, y + 0.5);
+ cairo_line_to (cr, x2, y + 0.5);
+ }
+ }
+ break;
+ }
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_grid_get_extents (GimpCanvasItem *item)
+{
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ GimpImage *image = gimp_canvas_item_get_image (item);
+ cairo_rectangle_int_t rectangle;
+
+ if (! image)
+ return NULL;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gint w, h;
+
+ w = gimp_image_get_width (image);
+ h = gimp_image_get_height (image);
+
+ gimp_canvas_item_transform_xy_f (item, 0, 0, &x1, &y1);
+ gimp_canvas_item_transform_xy_f (item, w, h, &x2, &y2);
+
+ rectangle.x = floor (x1);
+ rectangle.y = floor (y1);
+ rectangle.width = ceil (x2) - rectangle.x;
+ rectangle.height = ceil (y2) - rectangle.y;
+ }
+ else
+ {
+ gimp_canvas_item_untransform_viewport (item,
+ &rectangle.x,
+ &rectangle.y,
+ &rectangle.width,
+ &rectangle.height);
+ }
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static void
+gimp_canvas_grid_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasGridPrivate *private = GET_PRIVATE (item);
+
+ if (private->grid_style)
+ {
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+
+ gimp_canvas_set_grid_style (gimp_canvas_item_get_canvas (item), cr,
+ private->grid,
+ shell->offset_x, shell->offset_y);
+ cairo_stroke (cr);
+ }
+ else
+ {
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr);
+ }
+}
+
+GimpCanvasItem *
+gimp_canvas_grid_new (GimpDisplayShell *shell,
+ GimpGrid *grid)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (grid == NULL || GIMP_IS_GRID (grid), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_GRID,
+ "shell", shell,
+ "grid", grid,
+ NULL);
+}
diff --git a/app/display/gimpcanvasgrid.h b/app/display/gimpcanvasgrid.h
new file mode 100644
index 0000000..391d2ea
--- /dev/null
+++ b/app/display/gimpcanvasgrid.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasgrid.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_GRID_H__
+#define __GIMP_CANVAS_GRID_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_GRID (gimp_canvas_grid_get_type ())
+#define GIMP_CANVAS_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_GRID, GimpCanvasGrid))
+#define GIMP_CANVAS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_GRID, GimpCanvasGridClass))
+#define GIMP_IS_CANVAS_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_GRID))
+#define GIMP_IS_CANVAS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_GRID))
+#define GIMP_CANVAS_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_GRID, GimpCanvasGridClass))
+
+
+typedef struct _GimpCanvasGrid GimpCanvasGrid;
+typedef struct _GimpCanvasGridClass GimpCanvasGridClass;
+
+struct _GimpCanvasGrid
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasGridClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_grid_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_grid_new (GimpDisplayShell *shell,
+ GimpGrid *grid);
+
+
+#endif /* __GIMP_CANVAS_GRID_H__ */
diff --git a/app/display/gimpcanvasgroup.c b/app/display/gimpcanvasgroup.c
new file mode 100644
index 0000000..13d00ff
--- /dev/null
+++ b/app/display/gimpcanvasgroup.c
@@ -0,0 +1,387 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasgroup.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvasgroup.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_GROUP_STROKING,
+ PROP_GROUP_FILLING
+};
+
+
+struct _GimpCanvasGroupPrivate
+{
+ GQueue *items;
+ gboolean group_stroking;
+ gboolean group_filling;
+};
+
+
+/* local function prototypes */
+
+static void gimp_canvas_group_finalize (GObject *object);
+static void gimp_canvas_group_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_group_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_group_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_group_get_extents (GimpCanvasItem *item);
+static gboolean gimp_canvas_group_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+static void gimp_canvas_group_child_update (GimpCanvasItem *item,
+ cairo_region_t *region,
+ GimpCanvasGroup *group);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGroup, gimp_canvas_group,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_group_parent_class
+
+
+static void
+gimp_canvas_group_class_init (GimpCanvasGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_group_finalize;
+ object_class->set_property = gimp_canvas_group_set_property;
+ object_class->get_property = gimp_canvas_group_get_property;
+
+ item_class->draw = gimp_canvas_group_draw;
+ item_class->get_extents = gimp_canvas_group_get_extents;
+ item_class->hit = gimp_canvas_group_hit;
+
+ g_object_class_install_property (object_class, PROP_GROUP_STROKING,
+ g_param_spec_boolean ("group-stroking",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_GROUP_FILLING,
+ g_param_spec_boolean ("group-filling",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_group_init (GimpCanvasGroup *group)
+{
+ group->priv = gimp_canvas_group_get_instance_private (group);
+
+ group->priv->items = g_queue_new ();
+}
+
+static void
+gimp_canvas_group_finalize (GObject *object)
+{
+ GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object);
+ GimpCanvasItem *item;
+
+ while ((item = g_queue_peek_head (group->priv->items)))
+ gimp_canvas_group_remove_item (group, item);
+
+ g_queue_free (group->priv->items);
+ group->priv->items = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_group_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object);
+
+ switch (property_id)
+ {
+ case PROP_GROUP_STROKING:
+ group->priv->group_stroking = g_value_get_boolean (value);
+ break;
+ case PROP_GROUP_FILLING:
+ group->priv->group_filling = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_group_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object);
+
+ switch (property_id)
+ {
+ case PROP_GROUP_STROKING:
+ g_value_set_boolean (value, group->priv->group_stroking);
+ break;
+ case PROP_GROUP_FILLING:
+ g_value_set_boolean (value, group->priv->group_filling);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_group_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item);
+ GList *list;
+
+ for (list = group->priv->items->head; list; list = g_list_next (list))
+ {
+ GimpCanvasItem *sub_item = list->data;
+
+ gimp_canvas_item_draw (sub_item, cr);
+ }
+
+ if (group->priv->group_stroking)
+ _gimp_canvas_item_stroke (item, cr);
+
+ if (group->priv->group_filling)
+ _gimp_canvas_item_fill (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_group_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item);
+ cairo_region_t *region = NULL;
+ GList *list;
+
+ for (list = group->priv->items->head; list; list = g_list_next (list))
+ {
+ GimpCanvasItem *sub_item = list->data;
+ cairo_region_t *sub_region = gimp_canvas_item_get_extents (sub_item);
+
+ if (! region)
+ {
+ region = sub_region;
+ }
+ else if (sub_region)
+ {
+ cairo_region_union (region, sub_region);
+ cairo_region_destroy (sub_region);
+ }
+ }
+
+ return region;
+}
+
+static gboolean
+gimp_canvas_group_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item);
+ GList *list;
+
+ for (list = group->priv->items->head; list; list = g_list_next (list))
+ {
+ if (gimp_canvas_item_hit (list->data, x, y))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_canvas_group_child_update (GimpCanvasItem *item,
+ cairo_region_t *region,
+ GimpCanvasGroup *group)
+{
+ if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group)))
+ _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region);
+}
+
+
+/* public functions */
+
+GimpCanvasItem *
+gimp_canvas_group_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_GROUP,
+ "shell", shell,
+ NULL);
+}
+
+void
+gimp_canvas_group_add_item (GimpCanvasGroup *group,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (GIMP_CANVAS_ITEM (group) != item);
+
+ if (group->priv->group_stroking)
+ gimp_canvas_item_suspend_stroking (item);
+
+ if (group->priv->group_filling)
+ gimp_canvas_item_suspend_filling (item);
+
+ g_queue_push_tail (group->priv->items, g_object_ref (item));
+
+ if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group)))
+ {
+ cairo_region_t *region = gimp_canvas_item_get_extents (item);
+
+ if (region)
+ {
+ _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region);
+ cairo_region_destroy (region);
+ }
+ }
+
+ g_signal_connect (item, "update",
+ G_CALLBACK (gimp_canvas_group_child_update),
+ group);
+}
+
+void
+gimp_canvas_group_remove_item (GimpCanvasGroup *group,
+ GimpCanvasItem *item)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ list = g_queue_find (group->priv->items, item);
+
+ g_return_if_fail (list != NULL);
+
+ g_queue_delete_link (group->priv->items, list);
+
+ if (group->priv->group_stroking)
+ gimp_canvas_item_resume_stroking (item);
+
+ if (group->priv->group_filling)
+ gimp_canvas_item_resume_filling (item);
+
+ if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group)))
+ {
+ cairo_region_t *region = gimp_canvas_item_get_extents (item);
+
+ if (region)
+ {
+ _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region);
+ cairo_region_destroy (region);
+ }
+ }
+
+ g_signal_handlers_disconnect_by_func (item,
+ gimp_canvas_group_child_update,
+ group);
+
+ g_object_unref (item);
+}
+
+void
+gimp_canvas_group_set_group_stroking (GimpCanvasGroup *group,
+ gboolean group_stroking)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+
+ if (group->priv->group_stroking != group_stroking)
+ {
+ GList *list;
+
+ gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (group));
+
+ g_object_set (group,
+ "group-stroking", group_stroking ? TRUE : FALSE,
+ NULL);
+
+ for (list = group->priv->items->head; list; list = g_list_next (list))
+ {
+ if (group->priv->group_stroking)
+ gimp_canvas_item_suspend_stroking (list->data);
+ else
+ gimp_canvas_item_resume_stroking (list->data);
+ }
+
+ gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (group));
+ }
+}
+
+void
+gimp_canvas_group_set_group_filling (GimpCanvasGroup *group,
+ gboolean group_filling)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+
+ if (group->priv->group_filling != group_filling)
+ {
+ GList *list;
+
+ gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (group));
+
+ g_object_set (group,
+ "group-filling", group_filling ? TRUE : FALSE,
+ NULL);
+
+ for (list = group->priv->items->head; list; list = g_list_next (list))
+ {
+ if (group->priv->group_filling)
+ gimp_canvas_item_suspend_filling (list->data);
+ else
+ gimp_canvas_item_resume_filling (list->data);
+ }
+
+ gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (group));
+ }
+}
diff --git a/app/display/gimpcanvasgroup.h b/app/display/gimpcanvasgroup.h
new file mode 100644
index 0000000..d2fb9cc
--- /dev/null
+++ b/app/display/gimpcanvasgroup.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasgroup.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_GROUP_H__
+#define __GIMP_CANVAS_GROUP_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_GROUP (gimp_canvas_group_get_type ())
+#define GIMP_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_GROUP, GimpCanvasGroup))
+#define GIMP_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_GROUP, GimpCanvasGroupClass))
+#define GIMP_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_GROUP))
+#define GIMP_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_GROUP))
+#define GIMP_CANVAS_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_GROUP, GimpCanvasGroupClass))
+
+
+typedef struct _GimpCanvasGroupPrivate GimpCanvasGroupPrivate;
+typedef struct _GimpCanvasGroupClass GimpCanvasGroupClass;
+
+struct _GimpCanvasGroup
+{
+ GimpCanvasItem parent_instance;
+
+ GimpCanvasGroupPrivate *priv;
+
+};
+
+struct _GimpCanvasGroupClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_group_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_group_new (GimpDisplayShell *shell);
+
+void gimp_canvas_group_add_item (GimpCanvasGroup *group,
+ GimpCanvasItem *item);
+void gimp_canvas_group_remove_item (GimpCanvasGroup *group,
+ GimpCanvasItem *item);
+
+void gimp_canvas_group_set_group_stroking (GimpCanvasGroup *group,
+ gboolean group_stroking);
+void gimp_canvas_group_set_group_filling (GimpCanvasGroup *group,
+ gboolean group_filling);
+
+
+#endif /* __GIMP_CANVAS_GROUP_H__ */
diff --git a/app/display/gimpcanvasguide.c b/app/display/gimpcanvasguide.c
new file mode 100644
index 0000000..5712633
--- /dev/null
+++ b/app/display/gimpcanvasguide.c
@@ -0,0 +1,295 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasguide.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvasguide.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_POSITION,
+ PROP_STYLE
+};
+
+
+typedef struct _GimpCanvasGuidePrivate GimpCanvasGuidePrivate;
+
+struct _GimpCanvasGuidePrivate
+{
+ GimpOrientationType orientation;
+ gint position;
+
+ GimpGuideStyle style;
+};
+
+#define GET_PRIVATE(guide) \
+ ((GimpCanvasGuidePrivate *) gimp_canvas_guide_get_instance_private ((GimpCanvasGuide *) (guide)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_guide_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_guide_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_guide_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_guide_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_guide_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGuide, gimp_canvas_guide,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_guide_parent_class
+
+
+static void
+gimp_canvas_guide_class_init (GimpCanvasGuideClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_guide_set_property;
+ object_class->get_property = gimp_canvas_guide_get_property;
+
+ item_class->draw = gimp_canvas_guide_draw;
+ item_class->get_extents = gimp_canvas_guide_get_extents;
+ item_class->stroke = gimp_canvas_guide_stroke;
+
+ g_object_class_install_property (object_class, PROP_ORIENTATION,
+ g_param_spec_enum ("orientation", NULL, NULL,
+ GIMP_TYPE_ORIENTATION_TYPE,
+ GIMP_ORIENTATION_HORIZONTAL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_POSITION,
+ g_param_spec_int ("position", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_STYLE,
+ g_param_spec_enum ("style", NULL, NULL,
+ GIMP_TYPE_GUIDE_STYLE,
+ GIMP_GUIDE_STYLE_NONE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_guide_init (GimpCanvasGuide *guide)
+{
+}
+
+static void
+gimp_canvas_guide_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasGuidePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ private->orientation = g_value_get_enum (value);
+ break;
+ case PROP_POSITION:
+ private->position = g_value_get_int (value);
+ break;
+ case PROP_STYLE:
+ private->style = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_guide_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasGuidePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, private->orientation);
+ break;
+ case PROP_POSITION:
+ g_value_set_int (value, private->position);
+ break;
+ case PROP_STYLE:
+ g_value_set_enum (value, private->style);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_guide_transform (GimpCanvasItem *item,
+ gdouble *x1,
+ gdouble *y1,
+ gdouble *x2,
+ gdouble *y2)
+{
+ GimpCanvasGuidePrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+ GtkAllocation allocation;
+ gint max_outside;
+ gint x, y;
+
+ gtk_widget_get_allocation (canvas, &allocation);
+
+ max_outside = allocation.width + allocation.height;
+
+ *x1 = -max_outside;
+ *y1 = -max_outside;
+ *x2 = allocation.width + max_outside;
+ *y2 = allocation.height + max_outside;
+
+ switch (private->orientation)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_canvas_item_transform_xy (item, 0, private->position, &x, &y);
+ *y1 = *y2 = y + 0.5;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_canvas_item_transform_xy (item, private->position, 0, &x, &y);
+ *x1 = *x2 = x + 0.5;
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ return;
+ }
+}
+
+static void
+gimp_canvas_guide_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_guide_transform (item, &x1, &y1, &x2, &y2);
+
+ cairo_move_to (cr, x1, y1);
+ cairo_line_to (cr, x2, y2);
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_guide_get_extents (GimpCanvasItem *item)
+{
+ cairo_rectangle_int_t rectangle;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_guide_transform (item, &x1, &y1, &x2, &y2);
+
+ rectangle.x = MIN (x1, x2) - 1.5;
+ rectangle.y = MIN (y1, y2) - 1.5;
+ rectangle.width = ABS (x2 - x1) + 3.0;
+ rectangle.height = ABS (y2 - y1) + 3.0;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static void
+gimp_canvas_guide_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasGuidePrivate *private = GET_PRIVATE (item);
+
+ if (private->style != GIMP_GUIDE_STYLE_NONE)
+ {
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+
+ gimp_canvas_set_guide_style (gimp_canvas_item_get_canvas (item), cr,
+ private->style,
+ gimp_canvas_item_get_highlight (item),
+ shell->offset_x, shell->offset_y);
+ cairo_stroke (cr);
+ }
+ else
+ {
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr);
+ }
+}
+
+GimpCanvasItem *
+gimp_canvas_guide_new (GimpDisplayShell *shell,
+ GimpOrientationType orientation,
+ gint position,
+ GimpGuideStyle style)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_GUIDE,
+ "shell", shell,
+ "orientation", orientation,
+ "position", position,
+ "style", style,
+ NULL);
+}
+
+void
+gimp_canvas_guide_set (GimpCanvasItem *guide,
+ GimpOrientationType orientation,
+ gint position)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_GUIDE (guide));
+
+ gimp_canvas_item_begin_change (guide);
+
+ g_object_set (guide,
+ "orientation", orientation,
+ "position", position,
+ NULL);
+
+ gimp_canvas_item_end_change (guide);
+}
diff --git a/app/display/gimpcanvasguide.h b/app/display/gimpcanvasguide.h
new file mode 100644
index 0000000..54b1696
--- /dev/null
+++ b/app/display/gimpcanvasguide.h
@@ -0,0 +1,62 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasguide.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_GUIDE_H__
+#define __GIMP_CANVAS_GUIDE_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_GUIDE (gimp_canvas_guide_get_type ())
+#define GIMP_CANVAS_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_GUIDE, GimpCanvasGuide))
+#define GIMP_CANVAS_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_GUIDE, GimpCanvasGuideClass))
+#define GIMP_IS_CANVAS_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_GUIDE))
+#define GIMP_IS_CANVAS_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_GUIDE))
+#define GIMP_CANVAS_GUIDE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_GUIDE, GimpCanvasGuideClass))
+
+
+typedef struct _GimpCanvasGuide GimpCanvasGuide;
+typedef struct _GimpCanvasGuideClass GimpCanvasGuideClass;
+
+struct _GimpCanvasGuide
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasGuideClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_guide_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_guide_new (GimpDisplayShell *shell,
+ GimpOrientationType orientation,
+ gint position,
+ GimpGuideStyle style);
+
+void gimp_canvas_guide_set (GimpCanvasItem *guide,
+ GimpOrientationType orientation,
+ gint position);
+
+
+#endif /* __GIMP_CANVAS_GUIDE_H__ */
diff --git a/app/display/gimpcanvashandle.c b/app/display/gimpcanvashandle.c
new file mode 100644
index 0000000..954eead
--- /dev/null
+++ b/app/display/gimpcanvashandle.c
@@ -0,0 +1,703 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvashandle.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpdisplayshell.h"
+
+
+#define N_DASHES 8
+#define DASH_ON_RATIO 0.3
+#define DASH_OFF_RATIO 0.7
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_ANCHOR,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_START_ANGLE,
+ PROP_SLICE_ANGLE
+};
+
+
+typedef struct _GimpCanvasHandlePrivate GimpCanvasHandlePrivate;
+
+struct _GimpCanvasHandlePrivate
+{
+ GimpHandleType type;
+ GimpHandleAnchor anchor;
+ gdouble x;
+ gdouble y;
+ gint width;
+ gint height;
+ gdouble start_angle;
+ gdouble slice_angle;
+};
+
+#define GET_PRIVATE(handle) \
+ ((GimpCanvasHandlePrivate *) gimp_canvas_handle_get_instance_private ((GimpCanvasHandle *) (handle)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_handle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_handle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_handle_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_handle_get_extents (GimpCanvasItem *item);
+static gboolean gimp_canvas_handle_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasHandle, gimp_canvas_handle,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_handle_parent_class
+
+
+static void
+gimp_canvas_handle_class_init (GimpCanvasHandleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_handle_set_property;
+ object_class->get_property = gimp_canvas_handle_get_property;
+
+ item_class->draw = gimp_canvas_handle_draw;
+ item_class->get_extents = gimp_canvas_handle_get_extents;
+ item_class->hit = gimp_canvas_handle_hit;
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL, NULL,
+ GIMP_TYPE_HANDLE_TYPE,
+ GIMP_HANDLE_CROSS,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ANCHOR,
+ g_param_spec_enum ("anchor", NULL, NULL,
+ GIMP_TYPE_HANDLE_ANCHOR,
+ GIMP_HANDLE_ANCHOR_CENTER,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_int ("width", NULL, NULL,
+ 3, 1001, 7,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_int ("height", NULL, NULL,
+ 3, 1001, 7,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_START_ANGLE,
+ g_param_spec_double ("start-angle", NULL, NULL,
+ -1000, 1000, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SLICE_ANGLE,
+ g_param_spec_double ("slice-angle", NULL, NULL,
+ -1000, 1000, 2 * G_PI,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_handle_init (GimpCanvasHandle *handle)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (handle);
+
+ gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (handle),
+ CAIRO_LINE_CAP_SQUARE);
+
+ private->start_angle = 0.0;
+ private->slice_angle = 2.0 * G_PI;
+}
+
+static void
+gimp_canvas_handle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ private->type = g_value_get_enum (value);
+ break;
+ case PROP_ANCHOR:
+ private->anchor = g_value_get_enum (value);
+ break;
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_int (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_int (value);
+ break;
+ case PROP_START_ANGLE:
+ private->start_angle = g_value_get_double (value);
+ break;
+ case PROP_SLICE_ANGLE:
+ private->slice_angle = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_handle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ g_value_set_enum (value, private->type);
+ break;
+ case PROP_ANCHOR:
+ g_value_set_enum (value, private->anchor);
+ break;
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case PROP_WIDTH:
+ g_value_set_int (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, private->height);
+ break;
+ case PROP_START_ANGLE:
+ g_value_set_double (value, private->start_angle);
+ break;
+ case PROP_SLICE_ANGLE:
+ g_value_set_double (value, private->slice_angle);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_handle_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (item);
+
+ gimp_canvas_item_transform_xy_f (item,
+ private->x, private->y,
+ x, y);
+
+ switch (private->type)
+ {
+ case GIMP_HANDLE_SQUARE:
+ case GIMP_HANDLE_DASHED_SQUARE:
+ case GIMP_HANDLE_FILLED_SQUARE:
+ gimp_canvas_item_shift_to_north_west (private->anchor,
+ *x, *y,
+ private->width,
+ private->height,
+ x, y);
+ break;
+
+ case GIMP_HANDLE_CIRCLE:
+ case GIMP_HANDLE_DASHED_CIRCLE:
+ case GIMP_HANDLE_FILLED_CIRCLE:
+ case GIMP_HANDLE_CROSS:
+ case GIMP_HANDLE_CROSSHAIR:
+ case GIMP_HANDLE_DIAMOND:
+ case GIMP_HANDLE_DASHED_DIAMOND:
+ case GIMP_HANDLE_FILLED_DIAMOND:
+ gimp_canvas_item_shift_to_center (private->anchor,
+ *x, *y,
+ private->width,
+ private->height,
+ x, y);
+ break;
+
+ default:
+ break;
+ }
+
+ *x = floor (*x) + 0.5;
+ *y = floor (*y) + 0.5;
+}
+
+static void
+gimp_canvas_handle_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (item);
+ gdouble x, y, tx, ty;
+
+ gimp_canvas_handle_transform (item, &x, &y);
+
+ gimp_canvas_item_transform_xy_f (item,
+ private->x, private->y,
+ &tx, &ty);
+
+ tx = floor (tx) + 0.5;
+ ty = floor (ty) + 0.5;
+
+ switch (private->type)
+ {
+ case GIMP_HANDLE_SQUARE:
+ case GIMP_HANDLE_DASHED_SQUARE:
+ case GIMP_HANDLE_FILLED_SQUARE:
+ case GIMP_HANDLE_DIAMOND:
+ case GIMP_HANDLE_DASHED_DIAMOND:
+ case GIMP_HANDLE_FILLED_DIAMOND:
+ case GIMP_HANDLE_CROSS:
+ cairo_save (cr);
+ cairo_translate (cr, tx, ty);
+ cairo_rotate (cr, private->start_angle);
+ cairo_translate (cr, -tx, -ty);
+
+ switch (private->type)
+ {
+ case GIMP_HANDLE_SQUARE:
+ case GIMP_HANDLE_DASHED_SQUARE:
+ if (private->type == GIMP_HANDLE_DASHED_SQUARE)
+ {
+ gdouble circ;
+ gdouble dashes[2];
+
+ cairo_save (cr);
+
+ circ = 2.0 * ((private->width - 1.0) + (private->height - 1.0));
+
+ dashes[0] = (circ / N_DASHES) * DASH_ON_RATIO;
+ dashes[1] = (circ / N_DASHES) * DASH_OFF_RATIO;
+
+ cairo_set_dash (cr, dashes, 2, dashes[0] / 2.0);
+ }
+
+ cairo_rectangle (cr, x, y, private->width - 1.0, private->height - 1.0);
+ _gimp_canvas_item_stroke (item, cr);
+
+ if (private->type == GIMP_HANDLE_DASHED_SQUARE)
+ cairo_restore (cr);
+ break;
+
+ case GIMP_HANDLE_FILLED_SQUARE:
+ cairo_rectangle (cr, x - 0.5, y - 0.5, private->width, private->height);
+ _gimp_canvas_item_fill (item, cr);
+ break;
+
+ case GIMP_HANDLE_DIAMOND:
+ case GIMP_HANDLE_DASHED_DIAMOND:
+ case GIMP_HANDLE_FILLED_DIAMOND:
+ if (private->type == GIMP_HANDLE_DASHED_DIAMOND)
+ {
+ gdouble circ;
+ gdouble dashes[2];
+
+ cairo_save (cr);
+
+ circ = 4.0 * hypot ((gdouble) private->width / 2.0,
+ (gdouble) private->height / 2.0);
+
+ dashes[0] = (circ / N_DASHES) * DASH_ON_RATIO;
+ dashes[1] = (circ / N_DASHES) * DASH_OFF_RATIO;
+
+ cairo_set_dash (cr, dashes, 2, dashes[0] / 2.0);
+ }
+
+ cairo_move_to (cr, x, y - (gdouble) private->height / 2.0);
+ cairo_line_to (cr, x + (gdouble) private->width / 2.0, y);
+ cairo_line_to (cr, x, y + (gdouble) private->height / 2.0);
+ cairo_line_to (cr, x - (gdouble) private->width / 2.0, y);
+ cairo_line_to (cr, x, y - (gdouble) private->height / 2.0);
+ if (private->type == GIMP_HANDLE_DIAMOND ||
+ private->type == GIMP_HANDLE_DASHED_DIAMOND)
+ _gimp_canvas_item_stroke (item, cr);
+ else
+ _gimp_canvas_item_fill (item, cr);
+
+ if (private->type == GIMP_HANDLE_DASHED_SQUARE)
+ cairo_restore (cr);
+ break;
+
+ case GIMP_HANDLE_CROSS:
+ cairo_move_to (cr, x - private->width / 2.0, y);
+ cairo_line_to (cr, x + private->width / 2.0 - 0.5, y);
+ cairo_move_to (cr, x, y - private->height / 2.0);
+ cairo_line_to (cr, x, y + private->height / 2.0 - 0.5);
+ _gimp_canvas_item_stroke (item, cr);
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ }
+
+ cairo_restore (cr);
+ break;
+
+ case GIMP_HANDLE_CIRCLE:
+ case GIMP_HANDLE_DASHED_CIRCLE:
+ if (private->type == GIMP_HANDLE_DASHED_CIRCLE)
+ {
+ gdouble circ;
+ gdouble dashes[2];
+
+ cairo_save (cr);
+
+ circ = 2.0 * G_PI * (private->width / 2.0);
+
+ dashes[0] = (circ / N_DASHES) * DASH_ON_RATIO;
+ dashes[1] = (circ / N_DASHES) * DASH_OFF_RATIO;
+
+ cairo_set_dash (cr, dashes, 2, dashes[0] / 2.0);
+ }
+
+ gimp_cairo_arc (cr, x, y, private->width / 2.0,
+ private->start_angle,
+ private->slice_angle);
+
+ _gimp_canvas_item_stroke (item, cr);
+
+ if (private->type == GIMP_HANDLE_DASHED_CIRCLE)
+ cairo_restore (cr);
+ break;
+
+ case GIMP_HANDLE_FILLED_CIRCLE:
+ cairo_move_to (cr, x, y);
+
+ gimp_cairo_arc (cr, x, y, (gdouble) private->width / 2.0,
+ private->start_angle,
+ private->slice_angle);
+
+ _gimp_canvas_item_fill (item, cr);
+ break;
+
+ case GIMP_HANDLE_CROSSHAIR:
+ cairo_move_to (cr, x - private->width / 2.0, y);
+ cairo_line_to (cr, x - private->width * 0.4, y);
+
+ cairo_move_to (cr, x + private->width / 2.0 - 0.5, y);
+ cairo_line_to (cr, x + private->width * 0.4, y);
+
+ cairo_move_to (cr, x, y - private->height / 2.0);
+ cairo_line_to (cr, x, y - private->height * 0.4 - 0.5);
+
+ cairo_move_to (cr, x, y + private->height / 2.0 - 0.5);
+ cairo_line_to (cr, x, y + private->height * 0.4 - 0.5);
+
+ _gimp_canvas_item_stroke (item, cr);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static cairo_region_t *
+gimp_canvas_handle_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (item);
+ cairo_rectangle_int_t rectangle;
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_handle_transform (item, &x, &y);
+
+ switch (private->type)
+ {
+ case GIMP_HANDLE_SQUARE:
+ case GIMP_HANDLE_DASHED_SQUARE:
+ case GIMP_HANDLE_FILLED_SQUARE:
+ w = private->width * (sqrt(2) - 1) / 2;
+ h = private->height * (sqrt(2) - 1) / 2;
+ rectangle.x = x - 1.5 - w;
+ rectangle.y = y - 1.5 - h;
+ rectangle.width = private->width + 3.0 + w * 2;
+ rectangle.height = private->height + 3.0 + h * 2;
+ break;
+
+ case GIMP_HANDLE_CIRCLE:
+ case GIMP_HANDLE_DASHED_CIRCLE:
+ case GIMP_HANDLE_FILLED_CIRCLE:
+ case GIMP_HANDLE_CROSS:
+ case GIMP_HANDLE_CROSSHAIR:
+ case GIMP_HANDLE_DIAMOND:
+ case GIMP_HANDLE_DASHED_DIAMOND:
+ case GIMP_HANDLE_FILLED_DIAMOND:
+ rectangle.x = x - private->width / 2.0 - 2.0;
+ rectangle.y = y - private->height / 2.0 - 2.0;
+ rectangle.width = private->width + 4.0;
+ rectangle.height = private->height + 4.0;
+ break;
+
+ default:
+ break;
+ }
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static gboolean
+gimp_canvas_handle_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasHandlePrivate *private = GET_PRIVATE (item);
+ gdouble handle_tx, handle_ty;
+ gdouble mx, my, tx, ty, mmx, mmy;
+ gdouble diamond_offset_x = 0.0;
+ gdouble diamond_offset_y = 0.0;
+ gdouble angle = -private->start_angle;
+
+ gimp_canvas_handle_transform (item, &handle_tx, &handle_ty);
+
+ gimp_canvas_item_transform_xy_f (item,
+ x, y,
+ &mx, &my);
+
+ switch (private->type)
+ {
+ case GIMP_HANDLE_DIAMOND:
+ case GIMP_HANDLE_DASHED_DIAMOND:
+ case GIMP_HANDLE_FILLED_DIAMOND:
+ angle -= G_PI / 4.0;
+ diamond_offset_x = private->width / 2.0;
+ diamond_offset_y = private->height / 2.0;
+ case GIMP_HANDLE_SQUARE:
+ case GIMP_HANDLE_DASHED_SQUARE:
+ case GIMP_HANDLE_FILLED_SQUARE:
+ gimp_canvas_item_transform_xy_f (item,
+ private->x, private->y,
+ &tx, &ty);
+ mmx = mx - tx; mmy = my - ty;
+ mx = cos (angle) * mmx - sin (angle) * mmy + tx + diamond_offset_x;
+ my = sin (angle) * mmx + cos (angle) * mmy + ty + diamond_offset_y;
+ return mx > handle_tx && mx < handle_tx + private->width &&
+ my > handle_ty && my < handle_ty + private->height;
+
+ case GIMP_HANDLE_CIRCLE:
+ case GIMP_HANDLE_DASHED_CIRCLE:
+ case GIMP_HANDLE_FILLED_CIRCLE:
+ case GIMP_HANDLE_CROSS:
+ case GIMP_HANDLE_CROSSHAIR:
+ {
+ gint width = private->width;
+
+ if (width != private->height)
+ width = (width + private->height) / 2;
+
+ width /= 2;
+
+ return ((SQR (handle_tx - mx) + SQR (handle_ty - my)) < SQR (width));
+ }
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+GimpCanvasItem *
+gimp_canvas_handle_new (GimpDisplayShell *shell,
+ GimpHandleType type,
+ GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_HANDLE,
+ "shell", shell,
+ "type", type,
+ "anchor", anchor,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ NULL);
+}
+
+void
+gimp_canvas_handle_get_position (GimpCanvasItem *handle,
+ gdouble *x,
+ gdouble *y)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle));
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ g_object_get (handle,
+ "x", x,
+ "y", y,
+ NULL);
+}
+
+void
+gimp_canvas_handle_set_position (GimpCanvasItem *handle,
+ gdouble x,
+ gdouble y)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle));
+
+ gimp_canvas_item_begin_change (handle);
+
+ g_object_set (handle,
+ "x", x,
+ "y", y,
+ NULL);
+
+ gimp_canvas_item_end_change (handle);
+}
+
+gint
+gimp_canvas_handle_calc_size (GimpCanvasItem *item,
+ gdouble mouse_x,
+ gdouble mouse_y,
+ gint normal_size,
+ gint hover_size)
+{
+ gdouble x, y;
+ gdouble distance;
+ gdouble size;
+ gint full_threshold_sq = SQR (hover_size / 2) * 9;
+ gint partial_threshold_sq = full_threshold_sq * 5;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_HANDLE (item), normal_size);
+
+ gimp_canvas_handle_get_position (item, &x, &y);
+ distance = gimp_canvas_item_transform_distance_square (item,
+ mouse_x,
+ mouse_y,
+ x, y);
+
+ /* calculate the handle size based on distance from the cursor */
+ size = (1.0 - (distance - full_threshold_sq) /
+ (partial_threshold_sq - full_threshold_sq));
+
+ size = CLAMP (size, 0.0, 1.0);
+
+ return (gint) CLAMP ((size * hover_size),
+ normal_size,
+ hover_size);
+}
+
+void
+gimp_canvas_handle_get_size (GimpCanvasItem *handle,
+ gint *width,
+ gint *height)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle));
+ g_return_if_fail (width != NULL);
+ g_return_if_fail (height != NULL);
+
+ g_object_get (handle,
+ "width", width,
+ "height", height,
+ NULL);
+}
+
+void
+gimp_canvas_handle_set_size (GimpCanvasItem *handle,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle));
+
+ gimp_canvas_item_begin_change (handle);
+
+ g_object_set (handle,
+ "width", width,
+ "height", height,
+ NULL);
+
+ gimp_canvas_item_end_change (handle);
+}
+
+void
+gimp_canvas_handle_set_angles (GimpCanvasItem *handle,
+ gdouble start_angle,
+ gdouble slice_angle)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle));
+
+ gimp_canvas_item_begin_change (handle);
+
+ g_object_set (handle,
+ "start-angle", start_angle,
+ "slice-angle", slice_angle,
+ NULL);
+
+ gimp_canvas_item_end_change (handle);
+}
diff --git a/app/display/gimpcanvashandle.h b/app/display/gimpcanvashandle.h
new file mode 100644
index 0000000..0dea957
--- /dev/null
+++ b/app/display/gimpcanvashandle.h
@@ -0,0 +1,92 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvashandle.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_HANDLE_H__
+#define __GIMP_CANVAS_HANDLE_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_CANVAS_HANDLE_SIZE_CIRCLE 13
+#define GIMP_CANVAS_HANDLE_SIZE_CROSS 15
+#define GIMP_CANVAS_HANDLE_SIZE_CROSSHAIR 43
+#define GIMP_CANVAS_HANDLE_SIZE_LARGE 25
+#define GIMP_CANVAS_HANDLE_SIZE_SMALL 7
+
+
+#define GIMP_TYPE_CANVAS_HANDLE (gimp_canvas_handle_get_type ())
+#define GIMP_CANVAS_HANDLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_HANDLE, GimpCanvasHandle))
+#define GIMP_CANVAS_HANDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_HANDLE, GimpCanvasHandleClass))
+#define GIMP_IS_CANVAS_HANDLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_HANDLE))
+#define GIMP_IS_CANVAS_HANDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_HANDLE))
+#define GIMP_CANVAS_HANDLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_HANDLE, GimpCanvasHandleClass))
+
+
+typedef struct _GimpCanvasHandle GimpCanvasHandle;
+typedef struct _GimpCanvasHandleClass GimpCanvasHandleClass;
+
+struct _GimpCanvasHandle
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasHandleClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_handle_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_handle_new (GimpDisplayShell *shell,
+ GimpHandleType type,
+ GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height);
+
+void gimp_canvas_handle_get_position (GimpCanvasItem *handle,
+ gdouble *x,
+ gdouble *y);
+void gimp_canvas_handle_set_position (GimpCanvasItem *handle,
+ gdouble x,
+ gdouble y);
+
+gint gimp_canvas_handle_calc_size (GimpCanvasItem *item,
+ gdouble mouse_x,
+ gdouble mouse_y,
+ gint normal_size,
+ gint hover_size);
+
+void gimp_canvas_handle_get_size (GimpCanvasItem *handle,
+ gint *width,
+ gint *height);
+void gimp_canvas_handle_set_size (GimpCanvasItem *handle,
+ gint width,
+ gint height);
+
+void gimp_canvas_handle_set_angles (GimpCanvasItem *handle,
+ gdouble start_handle,
+ gdouble slice_handle);
+
+
+#endif /* __GIMP_CANVAS_HANDLE_H__ */
diff --git a/app/display/gimpcanvasitem-utils.c b/app/display/gimpcanvasitem-utils.c
new file mode 100644
index 0000000..4c23592
--- /dev/null
+++ b/app/display/gimpcanvasitem-utils.c
@@ -0,0 +1,474 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasitem-utils.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "display-types.h"
+
+#include "core/gimpimage.h"
+
+#include "vectors/gimpanchor.h"
+#include "vectors/gimpbezierstroke.h"
+#include "vectors/gimpvectors.h"
+
+#include "gimpcanvasitem.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+
+
+gboolean
+gimp_canvas_item_on_handle (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y,
+ GimpHandleType type,
+ gdouble handle_x,
+ gdouble handle_y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor)
+{
+ GimpDisplayShell *shell;
+ gdouble tx, ty;
+ gdouble handle_tx, handle_ty;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+
+ shell = gimp_canvas_item_get_shell (item);
+
+ gimp_display_shell_zoom_xy_f (shell,
+ x, y,
+ &tx, &ty);
+ gimp_display_shell_zoom_xy_f (shell,
+ handle_x, handle_y,
+ &handle_tx, &handle_ty);
+
+ switch (type)
+ {
+ case GIMP_HANDLE_SQUARE:
+ case GIMP_HANDLE_FILLED_SQUARE:
+ case GIMP_HANDLE_CROSS:
+ case GIMP_HANDLE_CROSSHAIR:
+ gimp_canvas_item_shift_to_north_west (anchor,
+ handle_tx, handle_ty,
+ width, height,
+ &handle_tx, &handle_ty);
+
+ return (tx == CLAMP (tx, handle_tx, handle_tx + width) &&
+ ty == CLAMP (ty, handle_ty, handle_ty + height));
+
+ case GIMP_HANDLE_CIRCLE:
+ case GIMP_HANDLE_FILLED_CIRCLE:
+ gimp_canvas_item_shift_to_center (anchor,
+ handle_tx, handle_ty,
+ width, height,
+ &handle_tx, &handle_ty);
+
+ /* FIXME */
+ if (width != height)
+ width = (width + height) / 2;
+
+ width /= 2;
+
+ return ((SQR (handle_tx - tx) + SQR (handle_ty - ty)) < SQR (width));
+
+ default:
+ g_warning ("%s: invalid handle type %d", G_STRFUNC, type);
+ break;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_canvas_item_on_vectors_handle (GimpCanvasItem *item,
+ GimpVectors *vectors,
+ const GimpCoords *coord,
+ gint width,
+ gint height,
+ GimpAnchorType preferred,
+ gboolean exclusive,
+ GimpAnchor **ret_anchor,
+ GimpStroke **ret_stroke)
+{
+ GimpStroke *stroke = NULL;
+ GimpStroke *pref_stroke = NULL;
+ GimpAnchor *anchor = NULL;
+ GimpAnchor *pref_anchor = NULL;
+ gdouble dx, dy;
+ gdouble pref_mindist = -1;
+ gdouble mindist = -1;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
+ g_return_val_if_fail (coord != NULL, FALSE);
+
+ if (ret_anchor) *ret_anchor = NULL;
+ if (ret_stroke) *ret_stroke = NULL;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ GList *anchor_list;
+ GList *list;
+
+ anchor_list = g_list_concat (gimp_stroke_get_draw_anchors (stroke),
+ gimp_stroke_get_draw_controls (stroke));
+
+ for (list = anchor_list; list; list = g_list_next (list))
+ {
+ dx = coord->x - GIMP_ANCHOR (list->data)->position.x;
+ dy = coord->y - GIMP_ANCHOR (list->data)->position.y;
+
+ if (mindist < 0 || mindist > dx * dx + dy * dy)
+ {
+ mindist = dx * dx + dy * dy;
+ anchor = GIMP_ANCHOR (list->data);
+
+ if (ret_stroke)
+ *ret_stroke = stroke;
+ }
+
+ if ((pref_mindist < 0 || pref_mindist > dx * dx + dy * dy) &&
+ GIMP_ANCHOR (list->data)->type == preferred)
+ {
+ pref_mindist = dx * dx + dy * dy;
+ pref_anchor = GIMP_ANCHOR (list->data);
+ pref_stroke = stroke;
+ }
+ }
+
+ g_list_free (anchor_list);
+ }
+
+ /* If the data passed into ret_anchor is a preferred anchor, return it. */
+ if (ret_anchor && *ret_anchor &&
+ gimp_canvas_item_on_handle (item,
+ coord->x,
+ coord->y,
+ GIMP_HANDLE_CIRCLE,
+ (*ret_anchor)->position.x,
+ (*ret_anchor)->position.y,
+ width, height,
+ GIMP_HANDLE_ANCHOR_CENTER) &&
+ (*ret_anchor)->type == preferred)
+ {
+ if (ret_stroke) *ret_stroke = pref_stroke;
+
+ return TRUE;
+ }
+
+ if (pref_anchor && gimp_canvas_item_on_handle (item,
+ coord->x,
+ coord->y,
+ GIMP_HANDLE_CIRCLE,
+ pref_anchor->position.x,
+ pref_anchor->position.y,
+ width, height,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ if (ret_anchor) *ret_anchor = pref_anchor;
+ if (ret_stroke) *ret_stroke = pref_stroke;
+
+ return TRUE;
+ }
+ else if (!exclusive && anchor &&
+ gimp_canvas_item_on_handle (item,
+ coord->x,
+ coord->y,
+ GIMP_HANDLE_CIRCLE,
+ anchor->position.x,
+ anchor->position.y,
+ width, height,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ if (ret_anchor)
+ *ret_anchor = anchor;
+
+ /* *ret_stroke already set correctly. */
+ return TRUE;
+ }
+
+ if (ret_anchor)
+ *ret_anchor = NULL;
+ if (ret_stroke)
+ *ret_stroke = NULL;
+
+ return FALSE;
+}
+
+gboolean
+gimp_canvas_item_on_vectors_curve (GimpCanvasItem *item,
+ GimpVectors *vectors,
+ const GimpCoords *coord,
+ gint width,
+ gint height,
+ GimpCoords *ret_coords,
+ gdouble *ret_pos,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ GimpStroke **ret_stroke)
+{
+ GimpStroke *stroke = NULL;
+ GimpAnchor *segment_start;
+ GimpAnchor *segment_end;
+ GimpCoords min_coords = GIMP_COORDS_DEFAULT_VALUES;
+ GimpCoords cur_coords;
+ gdouble min_dist, cur_dist, cur_pos;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
+ g_return_val_if_fail (coord != NULL, FALSE);
+
+ if (ret_coords) *ret_coords = *coord;
+ if (ret_pos) *ret_pos = -1.0;
+ if (ret_segment_start) *ret_segment_start = NULL;
+ if (ret_segment_end) *ret_segment_end = NULL;
+ if (ret_stroke) *ret_stroke = NULL;
+
+ min_dist = -1.0;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ cur_dist = gimp_stroke_nearest_point_get (stroke, coord, 1.0,
+ &cur_coords,
+ &segment_start,
+ &segment_end,
+ &cur_pos);
+
+ if (cur_dist >= 0 && (min_dist < 0 || cur_dist < min_dist))
+ {
+ min_dist = cur_dist;
+ min_coords = cur_coords;
+
+ if (ret_coords) *ret_coords = cur_coords;
+ if (ret_pos) *ret_pos = cur_pos;
+ if (ret_segment_start) *ret_segment_start = segment_start;
+ if (ret_segment_end) *ret_segment_end = segment_end;
+ if (ret_stroke) *ret_stroke = stroke;
+ }
+ }
+
+ if (min_dist >= 0 &&
+ gimp_canvas_item_on_handle (item,
+ coord->x,
+ coord->y,
+ GIMP_HANDLE_CIRCLE,
+ min_coords.x,
+ min_coords.y,
+ width, height,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_canvas_item_on_vectors (GimpCanvasItem *item,
+ const GimpCoords *coords,
+ gint width,
+ gint height,
+ GimpCoords *ret_coords,
+ gdouble *ret_pos,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ GimpStroke **ret_stroke,
+ GimpVectors **ret_vectors)
+{
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GList *all_vectors;
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+
+ shell = gimp_canvas_item_get_shell (item);
+ image = gimp_display_get_image (shell->display);
+
+ if (ret_coords) *ret_coords = *coords;
+ if (ret_pos) *ret_pos = -1.0;
+ if (ret_segment_start) *ret_segment_start = NULL;
+ if (ret_segment_end) *ret_segment_end = NULL;
+ if (ret_stroke) *ret_stroke = NULL;
+ if (ret_vectors) *ret_vectors = NULL;
+
+ all_vectors = gimp_image_get_vectors_list (image);
+
+ for (list = all_vectors; list; list = g_list_next (list))
+ {
+ GimpVectors *vectors = list->data;
+
+ if (! gimp_item_get_visible (GIMP_ITEM (vectors)))
+ continue;
+
+ if (gimp_canvas_item_on_vectors_curve (item,
+ vectors, coords,
+ width, height,
+ ret_coords,
+ ret_pos,
+ ret_segment_start,
+ ret_segment_end,
+ ret_stroke))
+ {
+ if (ret_vectors)
+ *ret_vectors = vectors;
+
+ g_list_free (all_vectors);
+
+ return TRUE;
+ }
+ }
+
+ g_list_free (all_vectors);
+
+ return FALSE;
+}
+
+void
+gimp_canvas_item_shift_to_north_west (GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ gdouble *shifted_x,
+ gdouble *shifted_y)
+{
+ switch (anchor)
+ {
+ case GIMP_HANDLE_ANCHOR_CENTER:
+ x -= width / 2;
+ y -= height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH:
+ x -= width / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_WEST:
+ /* nothing, this is the default */
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_EAST:
+ x -= width;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH:
+ x -= width / 2;
+ y -= height;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
+ y -= height;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
+ x -= width;
+ y -= height;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_WEST:
+ y -= height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_EAST:
+ x -= width;
+ y -= height / 2;
+ break;
+
+ default:
+ break;
+ }
+
+ if (shifted_x)
+ *shifted_x = x;
+
+ if (shifted_y)
+ *shifted_y = y;
+}
+
+void
+gimp_canvas_item_shift_to_center (GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ gdouble *shifted_x,
+ gdouble *shifted_y)
+{
+ switch (anchor)
+ {
+ case GIMP_HANDLE_ANCHOR_CENTER:
+ /* nothing, this is the default */
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH:
+ y += height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_WEST:
+ x += width / 2;
+ y += height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_EAST:
+ x -= width / 2;
+ y += height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH:
+ y -= height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
+ x += width / 2;
+ y -= height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
+ x -= width / 2;
+ y -= height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_WEST:
+ x += width / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_EAST:
+ x -= width / 2;
+ break;
+
+ default:
+ break;
+ }
+
+ if (shifted_x)
+ *shifted_x = x;
+
+ if (shifted_y)
+ *shifted_y = y;
+}
diff --git a/app/display/gimpcanvasitem-utils.h b/app/display/gimpcanvasitem-utils.h
new file mode 100644
index 0000000..240385e
--- /dev/null
+++ b/app/display/gimpcanvasitem-utils.h
@@ -0,0 +1,81 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasitem-utils.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_ITEM_UTILS_H__
+#define __GIMP_CANVAS_ITEM_UTILS_H__
+
+
+gboolean gimp_canvas_item_on_handle (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y,
+ GimpHandleType type,
+ gdouble handle_x,
+ gdouble handle_y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor);
+
+gboolean gimp_canvas_item_on_vectors_handle (GimpCanvasItem *item,
+ GimpVectors *vectors,
+ const GimpCoords *coord,
+ gint width,
+ gint height,
+ GimpAnchorType preferred,
+ gboolean exclusive,
+ GimpAnchor **ret_anchor,
+ GimpStroke **ret_stroke);
+gboolean gimp_canvas_item_on_vectors_curve (GimpCanvasItem *item,
+ GimpVectors *vectors,
+ const GimpCoords *coord,
+ gint width,
+ gint height,
+ GimpCoords *ret_coords,
+ gdouble *ret_pos,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ GimpStroke **ret_stroke);
+gboolean gimp_canvas_item_on_vectors (GimpCanvasItem *item,
+ const GimpCoords *coords,
+ gint width,
+ gint height,
+ GimpCoords *ret_coords,
+ gdouble *ret_pos,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ GimpStroke **ret_stroke,
+ GimpVectors **ret_vectors);
+
+void gimp_canvas_item_shift_to_north_west (GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ gdouble *shifted_x,
+ gdouble *shifted_y);
+void gimp_canvas_item_shift_to_center (GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ gdouble *shifted_x,
+ gdouble *shifted_y);
+
+
+#endif /* __GIMP_CANVAS_ITEM_UTILS_H__ */
diff --git a/app/display/gimpcanvasitem.c b/app/display/gimpcanvasitem.c
new file mode 100644
index 0000000..560210a
--- /dev/null
+++ b/app/display/gimpcanvasitem.c
@@ -0,0 +1,731 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasitem.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "display-types.h"
+
+#include "core/gimpmarshal.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvasitem.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SHELL,
+ PROP_VISIBLE,
+ PROP_LINE_CAP,
+ PROP_HIGHLIGHT
+};
+
+enum
+{
+ UPDATE,
+ LAST_SIGNAL
+};
+
+
+struct _GimpCanvasItemPrivate
+{
+ GimpDisplayShell *shell;
+ gboolean visible;
+ cairo_line_cap_t line_cap;
+ gboolean highlight;
+ gint suspend_stroking;
+ gint suspend_filling;
+ gint change_count;
+ cairo_region_t *change_region;
+};
+
+
+/* local function prototypes */
+
+static void gimp_canvas_item_dispose (GObject *object);
+static void gimp_canvas_item_constructed (GObject *object);
+static void gimp_canvas_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_item_dispatch_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs);
+
+static void gimp_canvas_item_real_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_item_real_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_item_real_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+static void gimp_canvas_item_real_fill (GimpCanvasItem *item,
+ cairo_t *cr);
+static gboolean gimp_canvas_item_real_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasItem, gimp_canvas_item, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_canvas_item_parent_class
+
+static guint item_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_canvas_item_class_init (GimpCanvasItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_canvas_item_dispose;
+ object_class->constructed = gimp_canvas_item_constructed;
+ object_class->set_property = gimp_canvas_item_set_property;
+ object_class->get_property = gimp_canvas_item_get_property;
+ object_class->dispatch_properties_changed = gimp_canvas_item_dispatch_properties_changed;
+
+ klass->update = NULL;
+ klass->draw = gimp_canvas_item_real_draw;
+ klass->get_extents = gimp_canvas_item_real_get_extents;
+ klass->stroke = gimp_canvas_item_real_stroke;
+ klass->fill = gimp_canvas_item_real_fill;
+ klass->hit = gimp_canvas_item_real_hit;
+
+ item_signals[UPDATE] =
+ g_signal_new ("update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpCanvasItemClass, update),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ g_object_class_install_property (object_class, PROP_SHELL,
+ g_param_spec_object ("shell",
+ NULL, NULL,
+ GIMP_TYPE_DISPLAY_SHELL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_VISIBLE,
+ g_param_spec_boolean ("visible",
+ NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_LINE_CAP,
+ g_param_spec_int ("line-cap",
+ NULL, NULL,
+ CAIRO_LINE_CAP_BUTT,
+ CAIRO_LINE_CAP_SQUARE,
+ CAIRO_LINE_CAP_ROUND,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HIGHLIGHT,
+ g_param_spec_boolean ("highlight",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_item_init (GimpCanvasItem *item)
+{
+ GimpCanvasItemPrivate *private;
+
+ item->private = gimp_canvas_item_get_instance_private (item);
+ private = item->private;
+
+ private->shell = NULL;
+ private->visible = TRUE;
+ private->line_cap = CAIRO_LINE_CAP_ROUND;
+ private->highlight = FALSE;
+ private->suspend_stroking = 0;
+ private->suspend_filling = 0;
+ private->change_count = 1; /* avoid emissions during construction */
+ private->change_region = NULL;
+}
+
+static void
+gimp_canvas_item_constructed (GObject *object)
+{
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (object);
+
+ gimp_assert (GIMP_IS_DISPLAY_SHELL (item->private->shell));
+
+ item->private->change_count = 0; /* undo hack from init() */
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+}
+
+static void
+gimp_canvas_item_dispose (GObject *object)
+{
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (object);
+
+ item->private->change_count++; /* avoid emissions during destruction */
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_canvas_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (object);
+ GimpCanvasItemPrivate *private = item->private;
+
+ switch (property_id)
+ {
+ case PROP_SHELL:
+ private->shell = g_value_get_object (value); /* don't ref */
+ break;
+ case PROP_VISIBLE:
+ private->visible = g_value_get_boolean (value);
+ break;
+ case PROP_LINE_CAP:
+ private->line_cap = g_value_get_int (value);
+ break;
+ case PROP_HIGHLIGHT:
+ private->highlight = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (object);
+ GimpCanvasItemPrivate *private = item->private;
+
+ switch (property_id)
+ {
+ case PROP_SHELL:
+ g_value_set_object (value, private->shell);
+ break;
+ case PROP_VISIBLE:
+ g_value_set_boolean (value, private->visible);
+ break;
+ case PROP_LINE_CAP:
+ g_value_set_int (value, private->line_cap);
+ break;
+ case PROP_HIGHLIGHT:
+ g_value_set_boolean (value, private->highlight);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_item_dispatch_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs)
+{
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (object);
+
+ G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object,
+ n_pspecs,
+ pspecs);
+
+ if (_gimp_canvas_item_needs_update (item))
+ {
+ cairo_region_t *region = gimp_canvas_item_get_extents (item);
+
+ if (region)
+ {
+ g_signal_emit (object, item_signals[UPDATE], 0,
+ region);
+ cairo_region_destroy (region);
+ }
+ }
+}
+
+static void
+gimp_canvas_item_real_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ g_warn_if_reached ();
+}
+
+static cairo_region_t *
+gimp_canvas_item_real_get_extents (GimpCanvasItem *item)
+{
+ return NULL;
+}
+
+static void
+gimp_canvas_item_real_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ cairo_set_line_cap (cr, item->private->line_cap);
+
+ gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr);
+ cairo_stroke_preserve (cr);
+
+ gimp_canvas_set_tool_fg_style (gimp_canvas_item_get_canvas (item), cr,
+ item->private->highlight);
+ cairo_stroke (cr);
+}
+
+static void
+gimp_canvas_item_real_fill (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr);
+ cairo_set_line_width (cr, 2.0);
+ cairo_stroke_preserve (cr);
+
+ gimp_canvas_set_tool_fg_style (gimp_canvas_item_get_canvas (item), cr,
+ item->private->highlight);
+ cairo_fill (cr);
+}
+
+static gboolean
+gimp_canvas_item_real_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ return FALSE;
+}
+
+
+/* public functions */
+
+GimpDisplayShell *
+gimp_canvas_item_get_shell (GimpCanvasItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL);
+
+ return item->private->shell;
+}
+
+GimpImage *
+gimp_canvas_item_get_image (GimpCanvasItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL);
+
+ return gimp_display_get_image (item->private->shell->display);
+}
+
+GtkWidget *
+gimp_canvas_item_get_canvas (GimpCanvasItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL);
+
+ return item->private->shell->canvas;
+}
+
+void
+gimp_canvas_item_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (cr != NULL);
+
+ if (item->private->visible)
+ {
+ cairo_save (cr);
+ GIMP_CANVAS_ITEM_GET_CLASS (item)->draw (item, cr);
+ cairo_restore (cr);
+ }
+}
+
+cairo_region_t *
+gimp_canvas_item_get_extents (GimpCanvasItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL);
+
+ if (item->private->visible)
+ return GIMP_CANVAS_ITEM_GET_CLASS (item)->get_extents (item);
+
+ return NULL;
+}
+
+gboolean
+gimp_canvas_item_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+
+ if (item->private->visible)
+ return GIMP_CANVAS_ITEM_GET_CLASS (item)->hit (item, x, y);
+
+ return FALSE;
+}
+
+void
+gimp_canvas_item_set_visible (GimpCanvasItem *item,
+ gboolean visible)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ if (item->private->visible != visible)
+ {
+ gimp_canvas_item_begin_change (item);
+ g_object_set (G_OBJECT (item),
+ "visible", visible,
+ NULL);
+ gimp_canvas_item_end_change (item);
+ }
+}
+
+gboolean
+gimp_canvas_item_get_visible (GimpCanvasItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+
+ return item->private->visible;
+}
+
+void
+gimp_canvas_item_set_line_cap (GimpCanvasItem *item,
+ cairo_line_cap_t line_cap)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ if (item->private->line_cap != line_cap)
+ {
+ gimp_canvas_item_begin_change (item);
+ g_object_set (G_OBJECT (item),
+ "line-cap", line_cap,
+ NULL);
+ gimp_canvas_item_end_change (item);
+ }
+}
+
+void
+gimp_canvas_item_set_highlight (GimpCanvasItem *item,
+ gboolean highlight)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ if (item->private->highlight != highlight)
+ {
+ g_object_set (G_OBJECT (item),
+ "highlight", highlight,
+ NULL);
+ }
+}
+
+gboolean
+gimp_canvas_item_get_highlight (GimpCanvasItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
+
+ return item->private->highlight;
+}
+
+void
+gimp_canvas_item_begin_change (GimpCanvasItem *item)
+{
+ GimpCanvasItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ private = item->private;
+
+ private->change_count++;
+
+ if (private->change_count == 1 &&
+ g_signal_has_handler_pending (item, item_signals[UPDATE], 0, FALSE))
+ {
+ private->change_region = gimp_canvas_item_get_extents (item);
+ }
+}
+
+void
+gimp_canvas_item_end_change (GimpCanvasItem *item)
+{
+ GimpCanvasItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ private = item->private;
+
+ g_return_if_fail (private->change_count > 0);
+
+ private->change_count--;
+
+ if (private->change_count == 0)
+ {
+ if (g_signal_has_handler_pending (item, item_signals[UPDATE], 0, FALSE))
+ {
+ cairo_region_t *region = gimp_canvas_item_get_extents (item);
+
+ if (! region)
+ {
+ region = private->change_region;
+ }
+ else if (private->change_region)
+ {
+ cairo_region_union (region, private->change_region);
+ cairo_region_destroy (private->change_region);
+ }
+
+ private->change_region = NULL;
+
+ if (region)
+ {
+ g_signal_emit (item, item_signals[UPDATE], 0,
+ region);
+ cairo_region_destroy (region);
+ }
+ }
+ else
+ {
+ g_clear_pointer (&private->change_region, cairo_region_destroy);
+ }
+ }
+}
+
+void
+gimp_canvas_item_suspend_stroking (GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ item->private->suspend_stroking++;
+}
+
+void
+gimp_canvas_item_resume_stroking (GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ g_return_if_fail (item->private->suspend_stroking > 0);
+
+ item->private->suspend_stroking--;
+}
+
+void
+gimp_canvas_item_suspend_filling (GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ item->private->suspend_filling++;
+}
+
+void
+gimp_canvas_item_resume_filling (GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ g_return_if_fail (item->private->suspend_filling > 0);
+
+ item->private->suspend_filling--;
+}
+
+void
+gimp_canvas_item_transform (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (cr != NULL);
+
+ private = item->private;
+
+ cairo_translate (cr, -private->shell->offset_x, -private->shell->offset_y);
+ cairo_scale (cr, private->shell->scale_x, private->shell->scale_y);
+}
+
+void
+gimp_canvas_item_transform_xy (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y,
+ gint *tx,
+ gint *ty)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_display_shell_zoom_xy (item->private->shell, x, y, tx, ty);
+}
+
+void
+gimp_canvas_item_transform_xy_f (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y,
+ gdouble *tx,
+ gdouble *ty)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_display_shell_zoom_xy_f (item->private->shell, x, y, tx, ty);
+}
+
+/**
+ * gimp_canvas_item_transform_distance:
+ * @item: a #GimpCanvasItem
+ * @x1: start point X in image coordinates
+ * @y1: start point Y in image coordinates
+ * @x2: end point X in image coordinates
+ * @y2: end point Y in image coordinates
+ *
+ * If you just need to compare distances, consider to use
+ * gimp_canvas_item_transform_distance_square() instead.
+ *
+ * Returns: the distance between the given points in display coordinates
+ **/
+gdouble
+gimp_canvas_item_transform_distance (GimpCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ return sqrt (gimp_canvas_item_transform_distance_square (item,
+ x1, y1, x2, y2));
+}
+
+/**
+ * gimp_canvas_item_transform_distance_square:
+ * @item: a #GimpCanvasItem
+ * @x1: start point X in image coordinates
+ * @y1: start point Y in image coordinates
+ * @x2: end point X in image coordinates
+ * @y2: end point Y in image coordinates
+ *
+ * This function is more effective than
+ * gimp_canvas_item_transform_distance() as it doesn't perform a
+ * sqrt(). Use this if you just need to compare distances.
+ *
+ * Returns: the square of the distance between the given points in
+ * display coordinates
+ **/
+gdouble
+gimp_canvas_item_transform_distance_square (GimpCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), 0.0);
+
+ gimp_canvas_item_transform_xy_f (item, x1, y1, &tx1, &ty1);
+ gimp_canvas_item_transform_xy_f (item, x2, y2, &tx2, &ty2);
+
+ return SQR (tx2 - tx1) + SQR (ty2 - ty1);
+}
+
+void
+gimp_canvas_item_untransform_viewport (GimpCanvasItem *item,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ GimpDisplayShell *shell;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ shell = item->private->shell;
+
+ gimp_display_shell_unrotate_bounds (shell,
+ 0.0, 0.0,
+ shell->disp_width, shell->disp_height,
+ &x1, &y1,
+ &x2, &y2);
+
+ *x = floor (x1);
+ *y = floor (y1);
+ *w = ceil (x2) - *x;
+ *h = ceil (y2) - *y;
+}
+
+
+/* protected functions */
+
+void
+_gimp_canvas_item_update (GimpCanvasItem *item,
+ cairo_region_t *region)
+{
+ g_signal_emit (item, item_signals[UPDATE], 0,
+ region);
+}
+
+gboolean
+_gimp_canvas_item_needs_update (GimpCanvasItem *item)
+{
+ return (item->private->change_count == 0 &&
+ g_signal_has_handler_pending (item, item_signals[UPDATE], 0, FALSE));
+}
+
+void
+_gimp_canvas_item_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ if (item->private->suspend_filling > 0)
+ g_warning ("_gimp_canvas_item_stroke() on an item that is in a filling group");
+
+ if (item->private->suspend_stroking == 0)
+ {
+ GIMP_CANVAS_ITEM_GET_CLASS (item)->stroke (item, cr);
+ }
+ else
+ {
+ cairo_new_sub_path (cr);
+ }
+}
+
+void
+_gimp_canvas_item_fill (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ if (item->private->suspend_stroking > 0)
+ g_warning ("_gimp_canvas_item_fill() on an item that is in a stroking group");
+
+ if (item->private->suspend_filling == 0)
+ {
+ GIMP_CANVAS_ITEM_GET_CLASS (item)->fill (item, cr);
+ }
+ else
+ {
+ cairo_new_sub_path (cr);
+ }
+}
diff --git a/app/display/gimpcanvasitem.h b/app/display/gimpcanvasitem.h
new file mode 100644
index 0000000..2004260
--- /dev/null
+++ b/app/display/gimpcanvasitem.h
@@ -0,0 +1,147 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasitem.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_ITEM_H__
+#define __GIMP_CANVAS_ITEM_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_CANVAS_ITEM (gimp_canvas_item_get_type ())
+#define GIMP_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_ITEM, GimpCanvasItem))
+#define GIMP_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_ITEM, GimpCanvasItemClass))
+#define GIMP_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_ITEM))
+#define GIMP_IS_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_ITEM))
+#define GIMP_CANVAS_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_ITEM, GimpCanvasItemClass))
+
+
+typedef struct _GimpCanvasItemPrivate GimpCanvasItemPrivate;
+typedef struct _GimpCanvasItemClass GimpCanvasItemClass;
+
+struct _GimpCanvasItem
+{
+ GimpObject parent_instance;
+
+ GimpCanvasItemPrivate *private;
+};
+
+struct _GimpCanvasItemClass
+{
+ GimpObjectClass parent_class;
+
+ /* signals */
+ void (* update) (GimpCanvasItem *item,
+ cairo_region_t *region);
+
+ /* virtual functions */
+ void (* draw) (GimpCanvasItem *item,
+ cairo_t *cr);
+ cairo_region_t * (* get_extents) (GimpCanvasItem *item);
+
+ void (* stroke) (GimpCanvasItem *item,
+ cairo_t *cr);
+ void (* fill) (GimpCanvasItem *item,
+ cairo_t *cr);
+
+ gboolean (* hit) (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+};
+
+
+GType gimp_canvas_item_get_type (void) G_GNUC_CONST;
+
+GimpDisplayShell * gimp_canvas_item_get_shell (GimpCanvasItem *item);
+GimpImage * gimp_canvas_item_get_image (GimpCanvasItem *item);
+GtkWidget * gimp_canvas_item_get_canvas (GimpCanvasItem *item);
+
+void gimp_canvas_item_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+cairo_region_t * gimp_canvas_item_get_extents (GimpCanvasItem *item);
+
+gboolean gimp_canvas_item_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+void gimp_canvas_item_set_visible (GimpCanvasItem *item,
+ gboolean visible);
+gboolean gimp_canvas_item_get_visible (GimpCanvasItem *item);
+
+void gimp_canvas_item_set_line_cap (GimpCanvasItem *item,
+ cairo_line_cap_t line_cap);
+
+void gimp_canvas_item_set_highlight (GimpCanvasItem *item,
+ gboolean highlight);
+gboolean gimp_canvas_item_get_highlight (GimpCanvasItem *item);
+
+void gimp_canvas_item_begin_change (GimpCanvasItem *item);
+void gimp_canvas_item_end_change (GimpCanvasItem *item);
+
+void gimp_canvas_item_suspend_stroking (GimpCanvasItem *item);
+void gimp_canvas_item_resume_stroking (GimpCanvasItem *item);
+
+void gimp_canvas_item_suspend_filling (GimpCanvasItem *item);
+void gimp_canvas_item_resume_filling (GimpCanvasItem *item);
+
+void gimp_canvas_item_transform (GimpCanvasItem *item,
+ cairo_t *cr);
+void gimp_canvas_item_transform_xy (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y,
+ gint *tx,
+ gint *ty);
+void gimp_canvas_item_transform_xy_f (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y,
+ gdouble *tx,
+ gdouble *ty);
+gdouble gimp_canvas_item_transform_distance
+ (GimpCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+gdouble gimp_canvas_item_transform_distance_square
+ (GimpCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+void gimp_canvas_item_untransform_viewport
+ (GimpCanvasItem *item,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+
+
+/* protected */
+
+void _gimp_canvas_item_update (GimpCanvasItem *item,
+ cairo_region_t *region);
+gboolean _gimp_canvas_item_needs_update (GimpCanvasItem *item);
+void _gimp_canvas_item_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+void _gimp_canvas_item_fill (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+#endif /* __GIMP_CANVAS_ITEM_H__ */
diff --git a/app/display/gimpcanvaslayerboundary.c b/app/display/gimpcanvaslayerboundary.c
new file mode 100644
index 0000000..0e4b43b
--- /dev/null
+++ b/app/display/gimpcanvaslayerboundary.c
@@ -0,0 +1,322 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaslayerboundary.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimplayer.h"
+#include "core/gimplayer-floating-selection.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvaslayerboundary.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_LAYER,
+ PROP_EDIT_MASK
+};
+
+
+typedef struct _GimpCanvasLayerBoundaryPrivate GimpCanvasLayerBoundaryPrivate;
+
+struct _GimpCanvasLayerBoundaryPrivate
+{
+ GimpLayer *layer;
+ gboolean edit_mask;
+};
+
+#define GET_PRIVATE(layer_boundary) \
+ ((GimpCanvasLayerBoundaryPrivate *) gimp_canvas_layer_boundary_get_instance_private ((GimpCanvasLayerBoundary *) (layer_boundary)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_layer_boundary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_layer_boundary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_layer_boundary_finalize (GObject *object);
+static void gimp_canvas_layer_boundary_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_layer_boundary_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_layer_boundary_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasLayerBoundary, gimp_canvas_layer_boundary,
+ GIMP_TYPE_CANVAS_RECTANGLE)
+
+#define parent_class gimp_canvas_layer_boundary_parent_class
+
+
+static void
+gimp_canvas_layer_boundary_class_init (GimpCanvasLayerBoundaryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_layer_boundary_set_property;
+ object_class->get_property = gimp_canvas_layer_boundary_get_property;
+ object_class->finalize = gimp_canvas_layer_boundary_finalize;
+
+ item_class->draw = gimp_canvas_layer_boundary_draw;
+ item_class->get_extents = gimp_canvas_layer_boundary_get_extents;
+ item_class->stroke = gimp_canvas_layer_boundary_stroke;
+
+ g_object_class_install_property (object_class, PROP_LAYER,
+ g_param_spec_object ("layer", NULL, NULL,
+ GIMP_TYPE_LAYER,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_EDIT_MASK,
+ g_param_spec_boolean ("edit-mask", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_layer_boundary_init (GimpCanvasLayerBoundary *layer_boundary)
+{
+}
+
+static void
+gimp_canvas_layer_boundary_finalize (GObject *object)
+{
+ GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (object);
+
+ if (private->layer)
+ g_object_remove_weak_pointer (G_OBJECT (private->layer),
+ (gpointer) &private->layer);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_layer_boundary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER:
+ if (private->layer)
+ g_object_remove_weak_pointer (G_OBJECT (private->layer),
+ (gpointer) &private->layer);
+ private->layer = g_value_get_object (value); /* don't ref */
+ if (private->layer)
+ g_object_add_weak_pointer (G_OBJECT (private->layer),
+ (gpointer) &private->layer);
+ break;
+ case PROP_EDIT_MASK:
+ private->edit_mask = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_layer_boundary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER:
+ g_value_set_object (value, private->layer);
+ break;
+ case PROP_EDIT_MASK:
+ g_value_set_boolean (value, private->edit_mask);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_layer_boundary_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (item);
+
+ if (private->layer)
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_layer_boundary_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (item);
+
+ if (private->layer)
+ return GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item);
+
+ return NULL;
+}
+
+static void
+gimp_canvas_layer_boundary_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (item);
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+
+ gimp_canvas_set_layer_style (gimp_canvas_item_get_canvas (item), cr,
+ private->layer,
+ shell->offset_x, shell->offset_y);
+ cairo_stroke (cr);
+}
+
+GimpCanvasItem *
+gimp_canvas_layer_boundary_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_LAYER_BOUNDARY,
+ "shell", shell,
+ NULL);
+}
+
+void
+gimp_canvas_layer_boundary_set_layer (GimpCanvasLayerBoundary *boundary,
+ GimpLayer *layer)
+{
+ GimpCanvasLayerBoundaryPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_LAYER_BOUNDARY (boundary));
+ g_return_if_fail (layer == NULL || GIMP_IS_LAYER (layer));
+
+ private = GET_PRIVATE (boundary);
+
+ if (layer && gimp_layer_is_floating_sel (layer))
+ {
+ GimpDrawable *drawable;
+
+ drawable = gimp_layer_get_floating_sel_drawable (layer);
+
+ if (GIMP_IS_CHANNEL (drawable))
+ {
+ /* if the owner drawable is a channel, show no outline */
+
+ layer = NULL;
+ }
+ else
+ {
+ /* otherwise, set the layer to the owner drawable */
+
+ layer = GIMP_LAYER (drawable);
+ }
+ }
+
+ if (layer != private->layer)
+ {
+ gboolean edit_mask = FALSE;
+
+ gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary));
+
+ if (layer)
+ {
+ GimpItem *item = GIMP_ITEM (layer);
+
+ edit_mask = (gimp_layer_get_mask (layer) &&
+ gimp_layer_get_edit_mask (layer));
+
+ g_object_set (boundary,
+ "x", (gdouble) gimp_item_get_offset_x (item),
+ "y", (gdouble) gimp_item_get_offset_y (item),
+ "width", (gdouble) gimp_item_get_width (item),
+ "height", (gdouble) gimp_item_get_height (item),
+ NULL);
+ }
+
+ g_object_set (boundary,
+ "layer", layer,
+ "edit-mask", edit_mask,
+ NULL);
+
+ gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary));
+ }
+ else if (layer && layer == private->layer)
+ {
+ GimpItem *item = GIMP_ITEM (layer);
+ gint lx, ly, lw, lh;
+ gdouble x, y, w ,h;
+ gboolean edit_mask;
+
+ lx = gimp_item_get_offset_x (item);
+ ly = gimp_item_get_offset_y (item);
+ lw = gimp_item_get_width (item);
+ lh = gimp_item_get_height (item);
+
+ edit_mask = (gimp_layer_get_mask (layer) &&
+ gimp_layer_get_edit_mask (layer));
+
+ g_object_get (boundary,
+ "x", &x,
+ "y", &y,
+ "width", &w,
+ "height", &h,
+ NULL);
+
+ if (lx != (gint) x ||
+ ly != (gint) y ||
+ lw != (gint) w ||
+ lh != (gint) h ||
+ edit_mask != private->edit_mask)
+ {
+ gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary));
+
+ g_object_set (boundary,
+ "x", (gdouble) lx,
+ "y", (gdouble) ly,
+ "width", (gdouble) lw,
+ "height", (gdouble) lh,
+ "edit-mask", edit_mask,
+ NULL);
+
+ gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary));
+ }
+ }
+}
diff --git a/app/display/gimpcanvaslayerboundary.h b/app/display/gimpcanvaslayerboundary.h
new file mode 100644
index 0000000..5f90131
--- /dev/null
+++ b/app/display/gimpcanvaslayerboundary.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaslayerboundary.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_LAYER_BOUNDARY_H__
+#define __GIMP_CANVAS_LAYER_BOUNDARY_H__
+
+
+#include "gimpcanvasrectangle.h"
+
+
+#define GIMP_TYPE_CANVAS_LAYER_BOUNDARY (gimp_canvas_layer_boundary_get_type ())
+#define GIMP_CANVAS_LAYER_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_LAYER_BOUNDARY, GimpCanvasLayerBoundary))
+#define GIMP_CANVAS_LAYER_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_LAYER_BOUNDARY, GimpCanvasLayerBoundaryClass))
+#define GIMP_IS_CANVAS_LAYER_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_LAYER_BOUNDARY))
+#define GIMP_IS_CANVAS_LAYER_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_LAYER_BOUNDARY))
+#define GIMP_CANVAS_LAYER_BOUNDARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_LAYER_BOUNDARY, GimpCanvasLayerBoundaryClass))
+
+
+typedef struct _GimpCanvasLayerBoundary GimpCanvasLayerBoundary;
+typedef struct _GimpCanvasLayerBoundaryClass GimpCanvasLayerBoundaryClass;
+
+struct _GimpCanvasLayerBoundary
+{
+ GimpCanvasRectangle parent_instance;
+};
+
+struct _GimpCanvasLayerBoundaryClass
+{
+ GimpCanvasRectangleClass parent_class;
+};
+
+
+GType gimp_canvas_layer_boundary_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_layer_boundary_new (GimpDisplayShell *shell);
+
+void gimp_canvas_layer_boundary_set_layer (GimpCanvasLayerBoundary *boundary,
+ GimpLayer *layer);
+
+
+#endif /* __GIMP_CANVAS_LAYER_BOUNDARY_H__ */
diff --git a/app/display/gimpcanvaslimit.c b/app/display/gimpcanvaslimit.c
new file mode 100644
index 0000000..05dacbb
--- /dev/null
+++ b/app/display/gimpcanvaslimit.c
@@ -0,0 +1,760 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaslimit.c
+ * Copyright (C) 2020 Ell
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+
+#include "gimpcanvaslimit.h"
+#include "gimpdisplayshell.h"
+
+
+#define DASH_LENGTH 4.0
+#define HIT_DISTANCE 16.0
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_X,
+ PROP_Y,
+ PROP_RADIUS,
+ PROP_ASPECT_RATIO,
+ PROP_ANGLE,
+ PROP_DASHED
+};
+
+
+typedef struct _GimpCanvasLimitPrivate GimpCanvasLimitPrivate;
+
+struct _GimpCanvasLimitPrivate
+{
+ GimpLimitType type;
+
+ gdouble x;
+ gdouble y;
+ gdouble radius;
+ gdouble aspect_ratio;
+ gdouble angle;
+
+ gboolean dashed;
+};
+
+#define GET_PRIVATE(limit) \
+ ((GimpCanvasLimitPrivate *) gimp_canvas_limit_get_instance_private ((GimpCanvasLimit *) (limit)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_limit_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_limit_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_canvas_limit_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_limit_get_extents (GimpCanvasItem *item);
+static gboolean gimp_canvas_limit_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasLimit, gimp_canvas_limit,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_limit_parent_class
+
+
+/* private functions */
+
+static void
+gimp_canvas_limit_class_init (GimpCanvasLimitClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_limit_set_property;
+ object_class->get_property = gimp_canvas_limit_get_property;
+
+ item_class->draw = gimp_canvas_limit_draw;
+ item_class->get_extents = gimp_canvas_limit_get_extents;
+ item_class->hit = gimp_canvas_limit_hit;
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL, NULL,
+ GIMP_TYPE_LIMIT_TYPE,
+ GIMP_LIMIT_CIRCLE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_RADIUS,
+ g_param_spec_double ("radius", NULL, NULL,
+ 0.0,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ASPECT_RATIO,
+ g_param_spec_double ("aspect-ratio", NULL, NULL,
+ -1.0,
+ +1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ANGLE,
+ g_param_spec_double ("angle", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_DASHED,
+ g_param_spec_boolean ("dashed", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_limit_init (GimpCanvasLimit *limit)
+{
+}
+
+static void
+gimp_canvas_limit_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasLimitPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ priv->type = g_value_get_enum (value);
+ break;
+
+ case PROP_X:
+ priv->x = g_value_get_double (value);
+ break;
+
+ case PROP_Y:
+ priv->y = g_value_get_double (value);
+ break;
+
+ case PROP_RADIUS:
+ priv->radius = g_value_get_double (value);
+ break;
+
+ case PROP_ASPECT_RATIO:
+ priv->aspect_ratio = g_value_get_double (value);
+ break;
+
+ case PROP_ANGLE:
+ priv->angle = g_value_get_double (value);
+ break;
+
+ case PROP_DASHED:
+ priv->dashed = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_limit_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasLimitPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ g_value_set_enum (value, priv->type);
+ break;
+
+ case PROP_X:
+ g_value_set_double (value, priv->x);
+ break;
+
+ case PROP_Y:
+ g_value_set_double (value, priv->y);
+ break;
+
+ case PROP_RADIUS:
+ g_value_set_double (value, priv->radius);
+ break;
+
+ case PROP_ASPECT_RATIO:
+ g_value_set_double (value, priv->aspect_ratio);
+ break;
+
+ case PROP_ANGLE:
+ g_value_set_double (value, priv->angle);
+ break;
+
+ case PROP_DASHED:
+ g_value_set_boolean (value, priv->dashed);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_limit_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *rx,
+ gdouble *ry)
+{
+ GimpCanvasLimit *limit = GIMP_CANVAS_LIMIT (item);
+ GimpCanvasLimitPrivate *priv = GET_PRIVATE (item);
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble min_radius = 0.0;
+
+ gimp_canvas_limit_get_radii (limit, rx, ry);
+
+ gimp_canvas_item_transform_xy_f (item,
+ priv->x - *rx, priv->y - *ry,
+ &x1, &y1);
+ gimp_canvas_item_transform_xy_f (item,
+ priv->x + *rx, priv->y + *ry,
+ &x2, &y2);
+
+ x1 = floor (x1) + 0.5;
+ y1 = floor (y1) + 0.5;
+
+ x2 = floor (x2) + 0.5;
+ y2 = floor (y2) + 0.5;
+
+ *x = (x1 + x2) / 2.0;
+ *y = (y1 + y2) / 2.0;
+
+ *rx = (x2 - x1) / 2.0;
+ *ry = (y2 - y1) / 2.0;
+
+ switch (priv->type)
+ {
+ case GIMP_LIMIT_CIRCLE:
+ case GIMP_LIMIT_SQUARE:
+ min_radius = 2.0;
+ break;
+
+ case GIMP_LIMIT_DIAMOND:
+ min_radius = 3.0;
+ break;
+
+ case GIMP_LIMIT_HORIZONTAL:
+ case GIMP_LIMIT_VERTICAL:
+ min_radius = 1.0;
+ break;
+ }
+
+ *rx = MAX (*rx, min_radius);
+ *ry = MAX (*ry, min_radius);
+}
+
+static void
+gimp_canvas_limit_paint (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasLimitPrivate *priv = GET_PRIVATE (item);
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ gdouble x, y;
+ gdouble rx, ry;
+ gdouble inf;
+
+ gimp_canvas_limit_transform (item,
+ &x, &y,
+ &rx, &ry);
+
+ cairo_save (cr);
+
+ cairo_translate (cr, x, y);
+ cairo_rotate (cr, priv->angle);
+ cairo_scale (cr, rx, ry);
+
+ inf = MAX (x, shell->disp_width - x) +
+ MAX (y, shell->disp_height - y);
+
+ switch (priv->type)
+ {
+ case GIMP_LIMIT_CIRCLE:
+ cairo_arc (cr, 0.0, 0.0, 1.0, 0.0, 2.0 * G_PI);
+ break;
+
+ case GIMP_LIMIT_SQUARE:
+ cairo_rectangle (cr, -1.0, -1.0, 2.0, 2.0);
+ break;
+
+ case GIMP_LIMIT_DIAMOND:
+ cairo_move_to (cr, 0.0, -1.0);
+ cairo_line_to (cr, +1.0, 0.0);
+ cairo_line_to (cr, 0.0, +1.0);
+ cairo_line_to (cr, -1.0, 0.0);
+ cairo_close_path (cr);
+ break;
+
+ case GIMP_LIMIT_HORIZONTAL:
+ cairo_move_to (cr, -inf / rx, -1.0);
+ cairo_line_to (cr, +inf / rx, -1.0);
+
+ cairo_move_to (cr, -inf / rx, +1.0);
+ cairo_line_to (cr, +inf / rx, +1.0);
+ break;
+
+ case GIMP_LIMIT_VERTICAL:
+ cairo_move_to (cr, -1.0, -inf / ry);
+ cairo_line_to (cr, -1.0, +inf / ry);
+
+ cairo_move_to (cr, +1.0, -inf / ry);
+ cairo_line_to (cr, +1.0, +inf / ry);
+ break;
+ }
+
+ cairo_restore (cr);
+}
+
+static void
+gimp_canvas_limit_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasLimitPrivate *priv = GET_PRIVATE (item);
+
+ gimp_canvas_limit_paint (item, cr);
+
+ cairo_save (cr);
+
+ if (priv->dashed)
+ cairo_set_dash (cr, (const gdouble[]) {DASH_LENGTH}, 1, 0.0);
+
+ _gimp_canvas_item_stroke (item, cr);
+
+ cairo_restore (cr);
+}
+
+static cairo_region_t *
+gimp_canvas_limit_get_extents (GimpCanvasItem *item)
+{
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ cairo_rectangle_int_t rectangle;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+
+ cr = cairo_create (surface);
+
+ gimp_canvas_limit_paint (item, cr);
+
+ cairo_path_extents (cr,
+ &x1, &y1,
+ &x2, &y2);
+
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (surface);
+
+ rectangle.x = floor (x1 - 1.5);
+ rectangle.y = floor (y1 - 1.5);
+ rectangle.width = ceil (x2 + 1.5) - rectangle.x;
+ rectangle.height = ceil (y2 + 1.5) - rectangle.y;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static gboolean
+gimp_canvas_limit_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasLimit *limit = GIMP_CANVAS_LIMIT (item);
+ gdouble bx, by;
+
+ gimp_canvas_limit_boundary_point (limit,
+ x, y,
+ &bx, &by);
+
+ return gimp_canvas_item_transform_distance (item,
+ x, y,
+ bx, by) <= HIT_DISTANCE;
+}
+
+
+/* public functions */
+
+GimpCanvasItem *
+gimp_canvas_limit_new (GimpDisplayShell *shell,
+ GimpLimitType type,
+ gdouble x,
+ gdouble y,
+ gdouble radius,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean dashed)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_LIMIT,
+ "shell", shell,
+ "type", type,
+ "x", x,
+ "y", y,
+ "radius", radius,
+ "aspect-ratio", aspect_ratio,
+ "angle", angle,
+ "dashed", dashed,
+ NULL);
+}
+
+void
+gimp_canvas_limit_get_radii (GimpCanvasLimit *limit,
+ gdouble *rx,
+ gdouble *ry)
+{
+ GimpCanvasLimitPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_CANVAS_LIMIT (limit));
+
+ priv = GET_PRIVATE (limit);
+
+ if (priv->aspect_ratio >= 0.0)
+ {
+ if (rx) *rx = priv->radius;
+ if (ry) *ry = priv->radius * (1.0 - priv->aspect_ratio);
+ }
+ else
+ {
+ if (rx) *rx = priv->radius * (1.0 + priv->aspect_ratio);
+ if (ry) *ry = priv->radius;
+ }
+}
+
+gboolean
+gimp_canvas_limit_is_inside (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasLimitPrivate *priv;
+ GimpVector2 p;
+ gdouble rx, ry;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_LIMIT (limit), FALSE);
+
+ priv = GET_PRIVATE (limit);
+
+ gimp_canvas_limit_get_radii (limit, &rx, &ry);
+
+ if (rx == 0.0 || ry == 0.0)
+ return FALSE;
+
+ p.x = x - priv->x;
+ p.y = y - priv->y;
+
+ gimp_vector2_rotate (&p, +priv->angle);
+
+ p.x = fabs (p.x / rx);
+ p.y = fabs (p.y / ry);
+
+ switch (priv->type)
+ {
+ case GIMP_LIMIT_CIRCLE:
+ return gimp_vector2_length (&p) < 1.0;
+
+ case GIMP_LIMIT_SQUARE:
+ return p.x < 1.0 && p.y < 1.0;
+
+ case GIMP_LIMIT_DIAMOND:
+ return p.x + p.y < 1.0;
+
+ case GIMP_LIMIT_HORIZONTAL:
+ return p.y < 1.0;
+
+ case GIMP_LIMIT_VERTICAL:
+ return p.x < 1.0;
+ }
+
+ g_return_val_if_reached (FALSE);
+}
+
+void
+gimp_canvas_limit_boundary_point (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y,
+ gdouble *bx,
+ gdouble *by)
+{
+ GimpCanvasLimitPrivate *priv;
+ GimpVector2 p;
+ gdouble rx, ry;
+ gboolean flip_x = FALSE;
+ gboolean flip_y = FALSE;
+
+ g_return_if_fail (GIMP_IS_CANVAS_LIMIT (limit));
+ g_return_if_fail (bx != NULL);
+ g_return_if_fail (by != NULL);
+
+ priv = GET_PRIVATE (limit);
+
+ gimp_canvas_limit_get_radii (limit, &rx, &ry);
+
+ p.x = x - priv->x;
+ p.y = y - priv->y;
+
+ gimp_vector2_rotate (&p, +priv->angle);
+
+ if (p.x < 0.0)
+ {
+ p.x = -p.x;
+
+ flip_x = TRUE;
+ }
+
+ if (p.y < 0.0)
+ {
+ p.y = -p.y;
+
+ flip_y = TRUE;
+ }
+
+ switch (priv->type)
+ {
+ case GIMP_LIMIT_CIRCLE:
+ if (rx == ry)
+ {
+ gimp_vector2_normalize (&p);
+
+ gimp_vector2_mul (&p, rx);
+ }
+ else
+ {
+ gdouble a0 = 0.0;
+ gdouble a1 = G_PI / 2.0;
+ gdouble a;
+ gint i;
+
+ for (i = 0; i < 20; i++)
+ {
+ GimpVector2 r;
+ GimpVector2 n;
+
+ a = (a0 + a1) / 2.0;
+
+ r.x = p.x - rx * cos (a);
+ r.y = p.y - ry * sin (a);
+
+ n.x = 1.0;
+ n.y = tan (a) * rx / ry;
+
+ if (gimp_vector2_cross_product (&r, &n).x >= 0.0)
+ a1 = a;
+ else
+ a0 = a;
+ }
+
+ a = (a0 + a1) / 2.0;
+
+ p.x = rx * cos (a);
+ p.y = ry * sin (a);
+ }
+ break;
+
+ case GIMP_LIMIT_SQUARE:
+ if (p.x <= rx || p.y <= ry)
+ {
+ if (rx - p.x <= ry - p.y)
+ p.x = rx;
+ else
+ p.y = ry;
+ }
+ else
+ {
+ p.x = rx;
+ p.y = ry;
+ }
+ break;
+
+ case GIMP_LIMIT_DIAMOND:
+ {
+ GimpVector2 l;
+ GimpVector2 r;
+ gdouble t;
+
+ l.x = rx;
+ l.y = -ry;
+
+ r.x = p.x;
+ r.y = p.y - ry;
+
+ t = gimp_vector2_inner_product (&r, &l) /
+ gimp_vector2_inner_product (&l, &l);
+ t = CLAMP (t, 0.0, 1.0);
+
+ p.x = rx * t;
+ p.y = ry * (1.0 - t);
+ }
+ break;
+
+ case GIMP_LIMIT_HORIZONTAL:
+ p.y = ry;
+ break;
+
+ case GIMP_LIMIT_VERTICAL:
+ p.x = rx;
+ break;
+ }
+
+ if (flip_x)
+ p.x = -p.x;
+
+ if (flip_y)
+ p.y = -p.y;
+
+ gimp_vector2_rotate (&p, -priv->angle);
+
+ *bx = priv->x + p.x;
+ *by = priv->y + p.y;
+}
+
+gdouble
+gimp_canvas_limit_boundary_radius (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasLimitPrivate *priv;
+ GimpVector2 p;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_LIMIT (limit), 0.0);
+
+ priv = GET_PRIVATE (limit);
+
+ p.x = x - priv->x;
+ p.y = y - priv->y;
+
+ gimp_vector2_rotate (&p, +priv->angle);
+
+ p.x = fabs (p.x);
+ p.y = fabs (p.y);
+
+ if (priv->aspect_ratio >= 0.0)
+ p.y /= 1.0 - priv->aspect_ratio;
+ else
+ p.x /= 1.0 + priv->aspect_ratio;
+
+ switch (priv->type)
+ {
+ case GIMP_LIMIT_CIRCLE:
+ return gimp_vector2_length (&p);
+
+ case GIMP_LIMIT_SQUARE:
+ return MAX (p.x, p.y);
+
+ case GIMP_LIMIT_DIAMOND:
+ return p.x + p.y;
+
+ case GIMP_LIMIT_HORIZONTAL:
+ return p.y;
+
+ case GIMP_LIMIT_VERTICAL:
+ return p.x;
+ }
+
+ g_return_val_if_reached (0.0);
+}
+
+void
+gimp_canvas_limit_center_point (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y,
+ gdouble *cx,
+ gdouble *cy)
+{
+ GimpCanvasLimitPrivate *priv;
+ GimpVector2 p;
+
+ g_return_if_fail (GIMP_IS_CANVAS_LIMIT (limit));
+ g_return_if_fail (cx != NULL);
+ g_return_if_fail (cy != NULL);
+
+ priv = GET_PRIVATE (limit);
+
+ p.x = x - priv->x;
+ p.y = y - priv->y;
+
+ gimp_vector2_rotate (&p, +priv->angle);
+
+ switch (priv->type)
+ {
+ case GIMP_LIMIT_CIRCLE:
+ case GIMP_LIMIT_SQUARE:
+ case GIMP_LIMIT_DIAMOND:
+ p.x = 0.0;
+ p.y = 0.0;
+ break;
+
+ case GIMP_LIMIT_HORIZONTAL:
+ p.y = 0.0;
+ break;
+
+ case GIMP_LIMIT_VERTICAL:
+ p.x = 0.0;
+ break;
+ }
+
+ gimp_vector2_rotate (&p, -priv->angle);
+
+ *cx = priv->x + p.x;
+ *cy = priv->y + p.y;
+}
diff --git a/app/display/gimpcanvaslimit.h b/app/display/gimpcanvaslimit.h
new file mode 100644
index 0000000..cf6ffc8
--- /dev/null
+++ b/app/display/gimpcanvaslimit.h
@@ -0,0 +1,84 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaslimit.h
+ * Copyright (C) 2020 Ell
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_LIMIT_H__
+#define __GIMP_CANVAS_LIMIT_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_LIMIT (gimp_canvas_limit_get_type ())
+#define GIMP_CANVAS_LIMIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_LIMIT, GimpCanvasLimit))
+#define GIMP_CANVAS_LIMIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_LIMIT, GimpCanvasLimitClass))
+#define GIMP_IS_CANVAS_LIMIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_LIMIT))
+#define GIMP_IS_CANVAS_LIMIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_LIMIT))
+#define GIMP_CANVAS_LIMIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_LIMIT, GimpCanvasLimitClass))
+
+
+typedef struct _GimpCanvasLimit GimpCanvasLimit;
+typedef struct _GimpCanvasLimitClass GimpCanvasLimitClass;
+
+struct _GimpCanvasLimit
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasLimitClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_limit_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_limit_new (GimpDisplayShell *shell,
+ GimpLimitType type,
+ gdouble x,
+ gdouble y,
+ gdouble radius,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean dashed);
+
+void gimp_canvas_limit_get_radii (GimpCanvasLimit *limit,
+ gdouble *rx,
+ gdouble *ry);
+
+gboolean gimp_canvas_limit_is_inside (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y);
+void gimp_canvas_limit_boundary_point (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y,
+ gdouble *bx,
+ gdouble *by);
+gdouble gimp_canvas_limit_boundary_radius (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y);
+
+void gimp_canvas_limit_center_point (GimpCanvasLimit *limit,
+ gdouble x,
+ gdouble y,
+ gdouble *cx,
+ gdouble *cy);
+
+
+#endif /* __GIMP_CANVAS_LIMIT_H__ */
diff --git a/app/display/gimpcanvasline.c b/app/display/gimpcanvasline.c
new file mode 100644
index 0000000..38a00c8
--- /dev/null
+++ b/app/display/gimpcanvasline.c
@@ -0,0 +1,281 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasline.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvasline.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2
+};
+
+
+typedef struct _GimpCanvasLinePrivate GimpCanvasLinePrivate;
+
+struct _GimpCanvasLinePrivate
+{
+ gdouble x1;
+ gdouble y1;
+ gdouble x2;
+ gdouble y2;
+};
+
+#define GET_PRIVATE(line) \
+ ((GimpCanvasLinePrivate *) gimp_canvas_line_get_instance_private ((GimpCanvasLine *) (line)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_line_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_line_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_line_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_line_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasLine, gimp_canvas_line,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_line_parent_class
+
+
+static void
+gimp_canvas_line_class_init (GimpCanvasLineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_line_set_property;
+ object_class->get_property = gimp_canvas_line_get_property;
+
+ item_class->draw = gimp_canvas_line_draw;
+ item_class->get_extents = gimp_canvas_line_get_extents;
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_double ("x1", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_double ("y1", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_double ("x2", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_double ("y2", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_line_init (GimpCanvasLine *line)
+{
+}
+
+static void
+gimp_canvas_line_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasLinePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X1:
+ private->x1 = g_value_get_double (value);
+ break;
+ case PROP_Y1:
+ private->y1 = g_value_get_double (value);
+ break;
+ case PROP_X2:
+ private->x2 = g_value_get_double (value);
+ break;
+ case PROP_Y2:
+ private->y2 = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_line_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasLinePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X1:
+ g_value_set_double (value, private->x1);
+ break;
+ case PROP_Y1:
+ g_value_set_double (value, private->y1);
+ break;
+ case PROP_X2:
+ g_value_set_double (value, private->x2);
+ break;
+ case PROP_Y2:
+ g_value_set_double (value, private->y2);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_line_transform (GimpCanvasItem *item,
+ gdouble *x1,
+ gdouble *y1,
+ gdouble *x2,
+ gdouble *y2)
+{
+ GimpCanvasLinePrivate *private = GET_PRIVATE (item);
+
+ gimp_canvas_item_transform_xy_f (item,
+ private->x1, private->y1,
+ x1, y1);
+ gimp_canvas_item_transform_xy_f (item,
+ private->x2, private->y2,
+ x2, y2);
+
+ *x1 = floor (*x1) + 0.5;
+ *y1 = floor (*y1) + 0.5;
+ *x2 = floor (*x2) + 0.5;
+ *y2 = floor (*y2) + 0.5;
+}
+
+static void
+gimp_canvas_line_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_line_transform (item, &x1, &y1, &x2, &y2);
+
+ cairo_move_to (cr, x1, y1);
+ cairo_line_to (cr, x2, y2);
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_line_get_extents (GimpCanvasItem *item)
+{
+ cairo_rectangle_int_t rectangle;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_line_transform (item, &x1, &y1, &x2, &y2);
+
+ if (x1 == x2 || y1 == y2)
+ {
+ rectangle.x = MIN (x1, x2) - 1.5;
+ rectangle.y = MIN (y1, y2) - 1.5;
+ rectangle.width = ABS (x2 - x1) + 3.0;
+ rectangle.height = ABS (y2 - y1) + 3.0;
+ }
+ else
+ {
+ rectangle.x = floor (MIN (x1, x2) - 2.5);
+ rectangle.y = floor (MIN (y1, y2) - 2.5);
+ rectangle.width = ceil (ABS (x2 - x1) + 5.0);
+ rectangle.height = ceil (ABS (y2 - y1) + 5.0);
+ }
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+GimpCanvasItem *
+gimp_canvas_line_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_LINE,
+ "shell", shell,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+}
+
+void
+gimp_canvas_line_set (GimpCanvasItem *line,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_LINE (line));
+
+ gimp_canvas_item_begin_change (line);
+
+ g_object_set (line,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+
+ gimp_canvas_item_end_change (line);
+}
diff --git a/app/display/gimpcanvasline.h b/app/display/gimpcanvasline.h
new file mode 100644
index 0000000..6f3f5f2
--- /dev/null
+++ b/app/display/gimpcanvasline.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasline.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_LINE_H__
+#define __GIMP_CANVAS_LINE_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_LINE (gimp_canvas_line_get_type ())
+#define GIMP_CANVAS_LINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_LINE, GimpCanvasLine))
+#define GIMP_CANVAS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_LINE, GimpCanvasLineClass))
+#define GIMP_IS_CANVAS_LINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_LINE))
+#define GIMP_IS_CANVAS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_LINE))
+#define GIMP_CANVAS_LINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_LINE, GimpCanvasLineClass))
+
+
+typedef struct _GimpCanvasLine GimpCanvasLine;
+typedef struct _GimpCanvasLineClass GimpCanvasLineClass;
+
+struct _GimpCanvasLine
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasLineClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_line_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_line_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+void gimp_canvas_line_set (GimpCanvasItem *line,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+
+#endif /* __GIMP_CANVAS_LINE_H__ */
diff --git a/app/display/gimpcanvaspassepartout.c b/app/display/gimpcanvaspassepartout.c
new file mode 100644
index 0000000..21e7307
--- /dev/null
+++ b/app/display/gimpcanvaspassepartout.c
@@ -0,0 +1,235 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspassepartout.c
+ * Copyright (C) 2010 Sven Neumann <sven@gimp.org>
+ *
+ * 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 "display-types.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpcanvaspassepartout.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scale.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_OPACITY,
+};
+
+
+typedef struct _GimpCanvasPassePartoutPrivate GimpCanvasPassePartoutPrivate;
+
+struct _GimpCanvasPassePartoutPrivate
+{
+ gdouble opacity;
+};
+
+#define GET_PRIVATE(item) \
+ ((GimpCanvasPassePartoutPrivate *) gimp_canvas_passe_partout_get_instance_private ((GimpCanvasPassePartout *) (item)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_passe_partout_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_passe_partout_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_passe_partout_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_passe_partout_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_passe_partout_fill (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPassePartout, gimp_canvas_passe_partout,
+ GIMP_TYPE_CANVAS_RECTANGLE)
+
+#define parent_class gimp_canvas_passe_partout_parent_class
+
+
+static void
+gimp_canvas_passe_partout_class_init (GimpCanvasPassePartoutClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_passe_partout_set_property;
+ object_class->get_property = gimp_canvas_passe_partout_get_property;
+
+ item_class->draw = gimp_canvas_passe_partout_draw;
+ item_class->get_extents = gimp_canvas_passe_partout_get_extents;
+ item_class->fill = gimp_canvas_passe_partout_fill;
+
+ g_object_class_install_property (object_class, PROP_OPACITY,
+ g_param_spec_double ("opacity", NULL, NULL,
+ 0.0,
+ 1.0, 0.5,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_passe_partout_init (GimpCanvasPassePartout *passe_partout)
+{
+}
+
+static void
+gimp_canvas_passe_partout_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPassePartoutPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_OPACITY:
+ priv->opacity = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_passe_partout_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPassePartoutPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_OPACITY:
+ g_value_set_double (value, priv->opacity);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_passe_partout_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ gint x, y;
+ gint w, h;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ x = -shell->offset_x;
+ y = -shell->offset_y;
+
+ gimp_display_shell_scale_get_image_size (shell, &w, &h);
+ }
+ else
+ {
+ gimp_canvas_item_untransform_viewport (item, &x, &y, &w, &h);
+ }
+
+ cairo_rectangle (cr, x, y, w, h);
+
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_passe_partout_get_extents (GimpCanvasItem *item)
+{
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ cairo_rectangle_int_t rectangle;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ cairo_region_t *inner;
+ cairo_region_t *outer;
+
+ rectangle.x = - shell->offset_x;
+ rectangle.y = - shell->offset_y;
+ gimp_display_shell_scale_get_image_size (shell,
+ &rectangle.width,
+ &rectangle.height);
+
+ outer = cairo_region_create_rectangle (&rectangle);
+
+ inner = GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item);
+
+ cairo_region_xor (outer, inner);
+
+ cairo_region_destroy (inner);
+
+ return outer;
+ }
+ else
+ {
+ gimp_canvas_item_untransform_viewport (item,
+ &rectangle.x,
+ &rectangle.y,
+ &rectangle.width,
+ &rectangle.height);
+
+ return cairo_region_create_rectangle (&rectangle);
+ }
+}
+
+static void
+gimp_canvas_passe_partout_fill (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasPassePartoutPrivate *priv = GET_PRIVATE (item);
+
+ cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_clip (cr);
+
+ gimp_canvas_set_passe_partout_style (gimp_canvas_item_get_canvas (item), cr);
+ cairo_paint_with_alpha (cr, priv->opacity);
+}
+
+GimpCanvasItem *
+gimp_canvas_passe_partout_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_PASSE_PARTOUT,
+ "shell", shell,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ "filled", TRUE,
+ NULL);
+}
diff --git a/app/display/gimpcanvaspassepartout.h b/app/display/gimpcanvaspassepartout.h
new file mode 100644
index 0000000..7356e3a
--- /dev/null
+++ b/app/display/gimpcanvaspassepartout.h
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspassepartout.h
+ * Copyright (C) 2010 Sven Neumann <sven@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_PASSE_PARTOUT_H__
+#define __GIMP_CANVAS_PASSE_PARTOUT_H__
+
+
+#include "gimpcanvasrectangle.h"
+
+
+#define GIMP_TYPE_CANVAS_PASSE_PARTOUT (gimp_canvas_passe_partout_get_type ())
+#define GIMP_CANVAS_PASSE_PARTOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PASSE_PARTOUT, GimpCanvasPassePartout))
+#define GIMP_CANVAS_PASSE_PARTOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PASSE_PARTOUT, GimpCanvasPassePartoutClass))
+#define GIMP_IS_CANVAS_PASSE_PARTOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PASSE_PARTOUT))
+#define GIMP_IS_CANVAS_PASSE_PARTOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PASSE_PARTOUT))
+#define GIMP_CANVAS_PASSE_PARTOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PASSE_PARTOUT, GimpCanvasPassePartoutClass))
+
+
+typedef struct _GimpCanvasPassePartout GimpCanvasPassePartout;
+typedef struct _GimpCanvasPassePartoutClass GimpCanvasPassePartoutClass;
+
+struct _GimpCanvasPassePartout
+{
+ GimpCanvasRectangle parent_instance;
+};
+
+struct _GimpCanvasPassePartoutClass
+{
+ GimpCanvasRectangleClass parent_class;
+};
+
+
+GType gimp_canvas_passe_partout_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_passe_partout_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height);
+
+
+#endif /* __GIMP_CANVAS_PASSE_PARTOUT_H__ */
diff --git a/app/display/gimpcanvaspath.c b/app/display/gimpcanvaspath.c
new file mode 100644
index 0000000..71a001f
--- /dev/null
+++ b/app/display/gimpcanvaspath.c
@@ -0,0 +1,352 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspath.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpbezierdesc.h"
+#include "core/gimpparamspecs.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvaspath.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ PROP_X,
+ PROP_Y,
+ PROP_FILLED,
+ PROP_PATH_STYLE
+};
+
+
+typedef struct _GimpCanvasPathPrivate GimpCanvasPathPrivate;
+
+struct _GimpCanvasPathPrivate
+{
+ cairo_path_t *path;
+ gdouble x;
+ gdouble y;
+ gboolean filled;
+ GimpPathStyle path_style;
+};
+
+#define GET_PRIVATE(path) \
+ ((GimpCanvasPathPrivate *) gimp_canvas_path_get_instance_private ((GimpCanvasPath *) (path)))
+
+/* local function prototypes */
+
+static void gimp_canvas_path_finalize (GObject *object);
+static void gimp_canvas_path_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_path_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_path_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_path_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_path_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPath, gimp_canvas_path,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_path_parent_class
+
+
+static void
+gimp_canvas_path_class_init (GimpCanvasPathClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_path_finalize;
+ object_class->set_property = gimp_canvas_path_set_property;
+ object_class->get_property = gimp_canvas_path_get_property;
+
+ item_class->draw = gimp_canvas_path_draw;
+ item_class->get_extents = gimp_canvas_path_get_extents;
+ item_class->stroke = gimp_canvas_path_stroke;
+
+ g_object_class_install_property (object_class, PROP_PATH,
+ g_param_spec_boxed ("path", NULL, NULL,
+ GIMP_TYPE_BEZIER_DESC,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_FILLED,
+ g_param_spec_boolean ("filled", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+
+ g_object_class_install_property (object_class, PROP_PATH_STYLE,
+ g_param_spec_enum ("path-style", NULL, NULL,
+ GIMP_TYPE_PATH_STYLE,
+ GIMP_PATH_STYLE_DEFAULT,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_path_init (GimpCanvasPath *path)
+{
+}
+
+static void
+gimp_canvas_path_finalize (GObject *object)
+{
+ GimpCanvasPathPrivate *private = GET_PRIVATE (object);
+
+ if (private->path)
+ {
+ gimp_bezier_desc_free (private->path);
+ private->path = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_path_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPathPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_PATH:
+ if (private->path)
+ gimp_bezier_desc_free (private->path);
+ private->path = g_value_dup_boxed (value);
+ break;
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case PROP_FILLED:
+ private->filled = g_value_get_boolean (value);
+ break;
+ case PROP_PATH_STYLE:
+ private->path_style = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_path_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPathPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_PATH:
+ g_value_set_boxed (value, private->path);
+ break;
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case PROP_FILLED:
+ g_value_set_boolean (value, private->filled);
+ break;
+ case PROP_PATH_STYLE:
+ g_value_set_enum (value, private->path_style);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_path_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasPathPrivate *private = GET_PRIVATE (item);
+
+ if (private->path)
+ {
+ cairo_save (cr);
+ gimp_canvas_item_transform (item, cr);
+ cairo_translate (cr, private->x, private->y);
+
+ cairo_append_path (cr, private->path);
+ cairo_restore (cr);
+
+ if (private->filled)
+ _gimp_canvas_item_fill (item, cr);
+ else
+ _gimp_canvas_item_stroke (item, cr);
+ }
+}
+
+static cairo_region_t *
+gimp_canvas_path_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasPathPrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+
+ if (private->path && gtk_widget_get_realized (canvas))
+ {
+ cairo_t *cr;
+ cairo_rectangle_int_t rectangle;
+ gdouble x1, y1, x2, y2;
+
+ cr = gdk_cairo_create (gtk_widget_get_window (canvas));
+
+ cairo_save (cr);
+ gimp_canvas_item_transform (item, cr);
+ cairo_translate (cr, private->x, private->y);
+
+ cairo_append_path (cr, private->path);
+ cairo_restore (cr);
+
+ cairo_path_extents (cr, &x1, &y1, &x2, &y2);
+
+ cairo_destroy (cr);
+
+ if (private->filled)
+ {
+ rectangle.x = floor (x1 - 1.0);
+ rectangle.y = floor (y1 - 1.0);
+ rectangle.width = ceil (x2 + 1.0) - rectangle.x;
+ rectangle.height = ceil (y2 + 1.0) - rectangle.y;
+ }
+ else
+ {
+ rectangle.x = floor (x1 - 1.5);
+ rectangle.y = floor (y1 - 1.5);
+ rectangle.width = ceil (x2 + 1.5) - rectangle.x;
+ rectangle.height = ceil (y2 + 1.5) - rectangle.y;
+ }
+
+ return cairo_region_create_rectangle (&rectangle);
+ }
+
+ return NULL;
+}
+
+static void
+gimp_canvas_path_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasPathPrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+ gboolean active;
+
+ switch (private->path_style)
+ {
+ case GIMP_PATH_STYLE_VECTORS:
+ active = gimp_canvas_item_get_highlight (item);
+
+ gimp_canvas_set_vectors_bg_style (canvas, cr, active);
+ cairo_stroke_preserve (cr);
+
+ gimp_canvas_set_vectors_fg_style (canvas, cr, active);
+ cairo_stroke (cr);
+ break;
+
+ case GIMP_PATH_STYLE_OUTLINE:
+ gimp_canvas_set_outline_bg_style (canvas, cr);
+ cairo_stroke_preserve (cr);
+
+ gimp_canvas_set_outline_fg_style (canvas, cr);
+ cairo_stroke (cr);
+ break;
+
+ case GIMP_PATH_STYLE_DEFAULT:
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr);
+ break;
+ }
+}
+
+GimpCanvasItem *
+gimp_canvas_path_new (GimpDisplayShell *shell,
+ const GimpBezierDesc *bezier,
+ gdouble x,
+ gdouble y,
+ gboolean filled,
+ GimpPathStyle style)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_PATH,
+ "shell", shell,
+ "path", bezier,
+ "x", x,
+ "y", y,
+ "filled", filled,
+ "path-style", style,
+ NULL);
+}
+
+void
+gimp_canvas_path_set (GimpCanvasItem *path,
+ const GimpBezierDesc *bezier)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_PATH (path));
+
+ gimp_canvas_item_begin_change (path);
+
+ g_object_set (path,
+ "path", bezier,
+ NULL);
+
+ gimp_canvas_item_end_change (path);
+}
diff --git a/app/display/gimpcanvaspath.h b/app/display/gimpcanvaspath.h
new file mode 100644
index 0000000..03fbb01
--- /dev/null
+++ b/app/display/gimpcanvaspath.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995
+ * Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspolygon.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_PATH_H__
+#define __GIMP_CANVAS_PATH_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_PATH (gimp_canvas_path_get_type ())
+#define GIMP_CANVAS_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PATH, GimpCanvasPath))
+#define GIMP_CANVAS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PATH, GimpCanvasPathClass))
+#define GIMP_IS_CANVAS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PATH))
+#define GIMP_IS_CANVAS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PATH))
+#define GIMP_CANVAS_PATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PATH, GimpCanvasPathClass))
+
+
+typedef struct _GimpCanvasPath GimpCanvasPath;
+typedef struct _GimpCanvasPathClass GimpCanvasPathClass;
+
+struct _GimpCanvasPath
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasPathClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_path_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_path_new (GimpDisplayShell *shell,
+ const GimpBezierDesc *bezier,
+ gdouble x,
+ gdouble y,
+ gboolean filled,
+ GimpPathStyle style);
+
+void gimp_canvas_path_set (GimpCanvasItem *path,
+ const GimpBezierDesc *bezier);
+
+
+#endif /* __GIMP_CANVAS_PATH_H__ */
diff --git a/app/display/gimpcanvaspen.c b/app/display/gimpcanvaspen.c
new file mode 100644
index 0000000..d0d01f9
--- /dev/null
+++ b/app/display/gimpcanvaspen.c
@@ -0,0 +1,231 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspen.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpcontext.h"
+#include "core/gimpparamspecs.h"
+
+#include "gimpcanvas-style.h"
+#include "gimpcanvaspen.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_COLOR,
+ PROP_WIDTH
+};
+
+
+typedef struct _GimpCanvasPenPrivate GimpCanvasPenPrivate;
+
+struct _GimpCanvasPenPrivate
+{
+ GimpRGB color;
+ gint width;
+};
+
+#define GET_PRIVATE(pen) \
+ ((GimpCanvasPenPrivate *) gimp_canvas_pen_get_instance_private ((GimpCanvasPen *) (pen)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_pen_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_pen_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static cairo_region_t * gimp_canvas_pen_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_pen_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPen, gimp_canvas_pen,
+ GIMP_TYPE_CANVAS_POLYGON)
+
+#define parent_class gimp_canvas_pen_parent_class
+
+
+static void
+gimp_canvas_pen_class_init (GimpCanvasPenClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_pen_set_property;
+ object_class->get_property = gimp_canvas_pen_get_property;
+
+ item_class->get_extents = gimp_canvas_pen_get_extents;
+ item_class->stroke = gimp_canvas_pen_stroke;
+
+ g_object_class_install_property (object_class, PROP_COLOR,
+ gimp_param_spec_rgb ("color", NULL, NULL,
+ FALSE, NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_int ("width", NULL, NULL,
+ 1, G_MAXINT, 1,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_pen_init (GimpCanvasPen *pen)
+{
+}
+
+static void
+gimp_canvas_pen_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPenPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_COLOR:
+ gimp_value_get_rgb (value, &private->color);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_pen_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPenPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_COLOR:
+ gimp_value_set_rgb (value, &private->color);
+ break;
+ case PROP_WIDTH:
+ g_value_set_int (value, private->width);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static cairo_region_t *
+gimp_canvas_pen_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasPenPrivate *private = GET_PRIVATE (item);
+ cairo_region_t *region;
+
+ region = GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item);
+
+ if (region)
+ {
+ cairo_rectangle_int_t rectangle;
+
+ cairo_region_get_extents (region, &rectangle);
+
+ rectangle.x -= ceil (private->width / 2.0);
+ rectangle.y -= ceil (private->width / 2.0);
+ rectangle.width += private->width + 1;
+ rectangle.height += private->width + 1;
+
+ cairo_region_union_rectangle (region, &rectangle);
+ }
+
+ return region;
+}
+
+static void
+gimp_canvas_pen_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasPenPrivate *private = GET_PRIVATE (item);
+
+ gimp_canvas_set_pen_style (gimp_canvas_item_get_canvas (item), cr,
+ &private->color, private->width);
+ cairo_stroke (cr);
+}
+
+GimpCanvasItem *
+gimp_canvas_pen_new (GimpDisplayShell *shell,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpContext *context,
+ GimpActiveColor color,
+ gint width)
+{
+ GimpCanvasItem *item;
+ GimpArray *array;
+ GimpRGB rgb;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (points != NULL && n_points > 1, NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ array = gimp_array_new ((const guint8 *) points,
+ n_points * sizeof (GimpVector2), TRUE);
+
+ switch (color)
+ {
+ case GIMP_ACTIVE_COLOR_FOREGROUND:
+ gimp_context_get_foreground (context, &rgb);
+ break;
+
+ case GIMP_ACTIVE_COLOR_BACKGROUND:
+ gimp_context_get_background (context, &rgb);
+ break;
+ }
+
+ item = g_object_new (GIMP_TYPE_CANVAS_PEN,
+ "shell", shell,
+ "points", array,
+ "color", &rgb,
+ "width", width,
+ NULL);
+
+ gimp_array_free (array);
+
+ return item;
+}
diff --git a/app/display/gimpcanvaspen.h b/app/display/gimpcanvaspen.h
new file mode 100644
index 0000000..3380790
--- /dev/null
+++ b/app/display/gimpcanvaspen.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995
+ * Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspen.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_PEN_H__
+#define __GIMP_CANVAS_PEN_H__
+
+
+#include "gimpcanvaspolygon.h"
+
+
+#define GIMP_TYPE_CANVAS_PEN (gimp_canvas_pen_get_type ())
+#define GIMP_CANVAS_PEN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PEN, GimpCanvasPen))
+#define GIMP_CANVAS_PEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PEN, GimpCanvasPenClass))
+#define GIMP_IS_CANVAS_PEN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PEN))
+#define GIMP_IS_CANVAS_PEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PEN))
+#define GIMP_CANVAS_PEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PEN, GimpCanvasPenClass))
+
+
+typedef struct _GimpCanvasPen GimpCanvasPen;
+typedef struct _GimpCanvasPenClass GimpCanvasPenClass;
+
+struct _GimpCanvasPen
+{
+ GimpCanvasPolygon parent_instance;
+};
+
+struct _GimpCanvasPenClass
+{
+ GimpCanvasPolygonClass parent_class;
+};
+
+
+GType gimp_canvas_pen_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_pen_new (GimpDisplayShell *shell,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpContext *context,
+ GimpActiveColor color,
+ gint width);
+
+
+#endif /* __GIMP_CANVAS_PEN_H__ */
diff --git a/app/display/gimpcanvaspolygon.c b/app/display/gimpcanvaspolygon.c
new file mode 100644
index 0000000..9c670e7
--- /dev/null
+++ b/app/display/gimpcanvaspolygon.c
@@ -0,0 +1,505 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspolygon.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimpparamspecs.h"
+
+#include "gimpcanvaspolygon.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_POINTS,
+ PROP_TRANSFORM,
+ PROP_FILLED
+};
+
+
+typedef struct _GimpCanvasPolygonPrivate GimpCanvasPolygonPrivate;
+
+struct _GimpCanvasPolygonPrivate
+{
+ GimpVector2 *points;
+ gint n_points;
+ GimpMatrix3 *transform;
+ gboolean filled;
+};
+
+#define GET_PRIVATE(polygon) \
+ ((GimpCanvasPolygonPrivate *) gimp_canvas_polygon_get_instance_private ((GimpCanvasPolygon *) (polygon)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_polygon_finalize (GObject *object);
+static void gimp_canvas_polygon_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_polygon_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_polygon_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_polygon_get_extents (GimpCanvasItem *item);
+static gboolean gimp_canvas_polygon_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPolygon, gimp_canvas_polygon,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_polygon_parent_class
+
+
+static void
+gimp_canvas_polygon_class_init (GimpCanvasPolygonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_polygon_finalize;
+ object_class->set_property = gimp_canvas_polygon_set_property;
+ object_class->get_property = gimp_canvas_polygon_get_property;
+
+ item_class->draw = gimp_canvas_polygon_draw;
+ item_class->get_extents = gimp_canvas_polygon_get_extents;
+ item_class->hit = gimp_canvas_polygon_hit;
+
+ g_object_class_install_property (object_class, PROP_POINTS,
+ gimp_param_spec_array ("points", NULL, NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TRANSFORM,
+ g_param_spec_pointer ("transform", NULL, NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_FILLED,
+ g_param_spec_boolean ("filled", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_polygon_init (GimpCanvasPolygon *polygon)
+{
+}
+
+static void
+gimp_canvas_polygon_finalize (GObject *object)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (object);
+
+ g_clear_pointer (&private->points, g_free);
+ private->n_points = 0;
+
+ g_clear_pointer (&private->transform, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_polygon_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_POINTS:
+ {
+ GimpArray *array = g_value_get_boxed (value);
+
+ g_clear_pointer (&private->points, g_free);
+ private->n_points = 0;
+
+ if (array)
+ {
+ private->points = g_memdup (array->data, array->length);
+ private->n_points = array->length / sizeof (GimpVector2);
+ }
+ }
+ break;
+
+ case PROP_TRANSFORM:
+ {
+ GimpMatrix3 *transform = g_value_get_pointer (value);
+ if (private->transform)
+ g_free (private->transform);
+ if (transform)
+ private->transform = g_memdup (transform, sizeof (GimpMatrix3));
+ else
+ private->transform = NULL;
+ }
+ break;
+
+ case PROP_FILLED:
+ private->filled = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_polygon_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_POINTS:
+ if (private->points)
+ {
+ GimpArray *array;
+
+ array = gimp_array_new ((const guint8 *) private->points,
+ private->n_points * sizeof (GimpVector2),
+ FALSE);
+ g_value_take_boxed (value, array);
+ }
+ else
+ {
+ g_value_set_boxed (value, NULL);
+ }
+ break;
+
+ case PROP_TRANSFORM:
+ g_value_set_pointer (value, private->transform);
+ break;
+
+ case PROP_FILLED:
+ g_value_set_boolean (value, private->filled);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_polygon_transform (GimpCanvasItem *item,
+ GimpVector2 *points,
+ gint *n_points)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (item);
+ gint i;
+
+ if (private->transform)
+ {
+ gimp_transform_polygon (private->transform,
+ private->points, private->n_points, FALSE,
+ points, n_points);
+
+ for (i = 0; i < *n_points; i++)
+ {
+ gimp_canvas_item_transform_xy_f (item,
+ points[i].x,
+ points[i].y,
+ &points[i].x,
+ &points[i].y);
+
+ points[i].x = floor (points[i].x) + 0.5;
+ points[i].y = floor (points[i].y) + 0.5;
+ }
+ }
+ else
+ {
+ for (i = 0; i < private->n_points; i++)
+ {
+ gimp_canvas_item_transform_xy_f (item,
+ private->points[i].x,
+ private->points[i].y,
+ &points[i].x,
+ &points[i].y);
+
+ points[i].x = floor (points[i].x) + 0.5;
+ points[i].y = floor (points[i].y) + 0.5;
+ }
+
+ *n_points = private->n_points;
+ }
+}
+
+static void
+gimp_canvas_polygon_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (item);
+ GimpVector2 *points;
+ gint n_points;
+ gint i;
+
+ if (! private->points)
+ return;
+
+ n_points = private->n_points;
+
+ if (private->transform)
+ n_points = 3 * n_points / 2;
+
+ points = g_new0 (GimpVector2, n_points);
+
+ gimp_canvas_polygon_transform (item, points, &n_points);
+
+ if (n_points < 2)
+ {
+ g_free (points);
+
+ return;
+ }
+
+ cairo_move_to (cr, points[0].x, points[0].y);
+
+ for (i = 1; i < n_points; i++)
+ {
+ cairo_line_to (cr, points[i].x, points[i].y);
+ }
+
+ if (private->filled)
+ _gimp_canvas_item_fill (item, cr);
+ else
+ _gimp_canvas_item_stroke (item, cr);
+
+ g_free (points);
+}
+
+static cairo_region_t *
+gimp_canvas_polygon_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (item);
+ cairo_rectangle_int_t rectangle;
+ GimpVector2 *points;
+ gint n_points;
+ gint x1, y1, x2, y2;
+ gint i;
+
+ if (! private->points)
+ return NULL;
+
+ n_points = private->n_points;
+
+ if (private->transform)
+ n_points = 3 * n_points / 2;
+
+ points = g_new0 (GimpVector2, n_points);
+
+ gimp_canvas_polygon_transform (item, points, &n_points);
+
+ if (n_points < 2)
+ {
+ g_free (points);
+
+ return NULL;
+ }
+
+ x1 = floor (points[0].x - 1.5);
+ y1 = floor (points[0].y - 1.5);
+ x2 = x1 + 3;
+ y2 = y1 + 3;
+
+ for (i = 1; i < n_points; i++)
+ {
+ gint x3 = floor (points[i].x - 1.5);
+ gint y3 = floor (points[i].y - 1.5);
+ gint x4 = x3 + 3;
+ gint y4 = y3 + 3;
+
+ x1 = MIN (x1, x3);
+ y1 = MIN (y1, y3);
+ x2 = MAX (x2, x4);
+ y2 = MAX (y2, y4);
+ }
+
+ g_free (points);
+
+ rectangle.x = x1;
+ rectangle.y = y1;
+ rectangle.width = x2 - x1;
+ rectangle.height = y2 - y1;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static gboolean
+gimp_canvas_polygon_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ GimpCanvasPolygonPrivate *private = GET_PRIVATE (item);
+ GimpVector2 *points;
+ gint n_points;
+ gdouble tx, ty;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ gboolean hit;
+ gint i;
+
+ if (! private->points)
+ return FALSE;
+
+ gimp_canvas_item_transform_xy_f (item, x, y, &tx, &ty);
+
+ n_points = private->n_points;
+
+ if (private->transform)
+ n_points = 3 * n_points / 2;
+
+ points = g_new0 (GimpVector2, n_points);
+
+ gimp_canvas_polygon_transform (item, points, &n_points);
+
+ if (n_points < 2)
+ {
+ g_free (points);
+
+ return FALSE;
+ }
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 1, 1);
+ cr = cairo_create (surface);
+ cairo_surface_destroy (surface);
+
+ cairo_move_to (cr, points[0].x, points[0].y);
+
+ for (i = 1; i < private->n_points; i++)
+ {
+ cairo_line_to (cr, points[i].x, points[i].y);
+ }
+
+ g_free (points);
+
+ hit = cairo_in_fill (cr, tx, ty);
+
+ cairo_destroy (cr);
+
+ return hit;
+}
+
+GimpCanvasItem *
+gimp_canvas_polygon_new (GimpDisplayShell *shell,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpMatrix3 *transform,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+ GimpArray *array;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (points == NULL || n_points > 0, NULL);
+
+ array = gimp_array_new ((const guint8 *) points,
+ n_points * sizeof (GimpVector2), TRUE);
+
+ item = g_object_new (GIMP_TYPE_CANVAS_POLYGON,
+ "shell", shell,
+ "transform", transform,
+ "filled", filled,
+ "points", array,
+ NULL);
+
+ gimp_array_free (array);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_canvas_polygon_new_from_coords (GimpDisplayShell *shell,
+ const GimpCoords *coords,
+ gint n_coords,
+ GimpMatrix3 *transform,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+ GimpVector2 *points;
+ GimpArray *array;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (coords == NULL || n_coords > 0, NULL);
+
+ points = g_new (GimpVector2, n_coords);
+
+ for (i = 0; i < n_coords; i++)
+ {
+ points[i].x = coords[i].x;
+ points[i].y = coords[i].y;
+ }
+
+ array = gimp_array_new ((const guint8 *) points,
+ n_coords * sizeof (GimpVector2), TRUE);
+
+ item = g_object_new (GIMP_TYPE_CANVAS_POLYGON,
+ "shell", shell,
+ "transform", transform,
+ "filled", filled,
+ "points", array,
+ NULL);
+
+ gimp_array_free (array);
+ g_free (points);
+
+ return item;
+}
+
+void
+gimp_canvas_polygon_set_points (GimpCanvasItem *polygon,
+ const GimpVector2 *points,
+ gint n_points)
+{
+ GimpArray *array;
+
+ g_return_if_fail (GIMP_IS_CANVAS_POLYGON (polygon));
+ g_return_if_fail (points == NULL || n_points > 0);
+
+ array = gimp_array_new ((const guint8 *) points,
+ n_points * sizeof (GimpVector2), TRUE);
+
+ gimp_canvas_item_begin_change (polygon);
+ g_object_set (polygon,
+ "points", array,
+ NULL);
+ gimp_canvas_item_end_change (polygon);
+
+ gimp_array_free (array);
+}
diff --git a/app/display/gimpcanvaspolygon.h b/app/display/gimpcanvaspolygon.h
new file mode 100644
index 0000000..f68e36a
--- /dev/null
+++ b/app/display/gimpcanvaspolygon.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995
+ * Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvaspolygon.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_POLYGON_H__
+#define __GIMP_CANVAS_POLYGON_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_POLYGON (gimp_canvas_polygon_get_type ())
+#define GIMP_CANVAS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_POLYGON, GimpCanvasPolygon))
+#define GIMP_CANVAS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_POLYGON, GimpCanvasPolygonClass))
+#define GIMP_IS_CANVAS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_POLYGON))
+#define GIMP_IS_CANVAS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_POLYGON))
+#define GIMP_CANVAS_POLYGON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_POLYGON, GimpCanvasPolygonClass))
+
+
+typedef struct _GimpCanvasPolygon GimpCanvasPolygon;
+typedef struct _GimpCanvasPolygonClass GimpCanvasPolygonClass;
+
+struct _GimpCanvasPolygon
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasPolygonClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_polygon_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_polygon_new (GimpDisplayShell *shell,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpMatrix3 *transform,
+ gboolean filled);
+GimpCanvasItem * gimp_canvas_polygon_new_from_coords (GimpDisplayShell *shell,
+ const GimpCoords *coords,
+ gint n_coords,
+ GimpMatrix3 *transform,
+ gboolean filled);
+
+void gimp_canvas_polygon_set_points (GimpCanvasItem *polygon,
+ const GimpVector2 *points,
+ gint n_points);
+
+
+#endif /* __GIMP_CANVAS_POLYGON_H__ */
diff --git a/app/display/gimpcanvasprogress.c b/app/display/gimpcanvasprogress.c
new file mode 100644
index 0000000..4def1ac
--- /dev/null
+++ b/app/display/gimpcanvasprogress.c
@@ -0,0 +1,459 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasprogress.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpprogress.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvas-style.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpcanvasprogress.h"
+#include "gimpdisplayshell.h"
+
+
+#define BORDER 5
+#define RADIUS 20
+
+#define MIN_UPDATE_INTERVAL 50000 /* microseconds */
+
+
+enum
+{
+ PROP_0,
+ PROP_ANCHOR,
+ PROP_X,
+ PROP_Y
+};
+
+
+typedef struct _GimpCanvasProgressPrivate GimpCanvasProgressPrivate;
+
+struct _GimpCanvasProgressPrivate
+{
+ GimpHandleAnchor anchor;
+ gdouble x;
+ gdouble y;
+
+ gchar *text;
+ gdouble value;
+
+ guint64 last_update_time;
+};
+
+#define GET_PRIVATE(progress) \
+ ((GimpCanvasProgressPrivate *) gimp_canvas_progress_get_instance_private ((GimpCanvasProgress *) (progress)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_progress_iface_init (GimpProgressInterface *iface);
+
+static void gimp_canvas_progress_finalize (GObject *object);
+static void gimp_canvas_progress_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_progress_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_progress_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_progress_get_extents (GimpCanvasItem *item);
+static gboolean gimp_canvas_progress_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y);
+
+static GimpProgress * gimp_canvas_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+static void gimp_canvas_progress_end (GimpProgress *progress);
+static gboolean gimp_canvas_progress_is_active (GimpProgress *progress);
+static void gimp_canvas_progress_set_text (GimpProgress *progress,
+ const gchar *message);
+static void gimp_canvas_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+static gdouble gimp_canvas_progress_get_value (GimpProgress *progress);
+static void gimp_canvas_progress_pulse (GimpProgress *progress);
+static gboolean gimp_canvas_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpCanvasProgress, gimp_canvas_progress,
+ GIMP_TYPE_CANVAS_ITEM,
+ G_ADD_PRIVATE (GimpCanvasProgress)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
+ gimp_canvas_progress_iface_init))
+
+#define parent_class gimp_canvas_progress_parent_class
+
+
+static void
+gimp_canvas_progress_class_init (GimpCanvasProgressClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_progress_finalize;
+ object_class->set_property = gimp_canvas_progress_set_property;
+ object_class->get_property = gimp_canvas_progress_get_property;
+
+ item_class->draw = gimp_canvas_progress_draw;
+ item_class->get_extents = gimp_canvas_progress_get_extents;
+ item_class->hit = gimp_canvas_progress_hit;
+
+ g_object_class_install_property (object_class, PROP_ANCHOR,
+ g_param_spec_enum ("anchor", NULL, NULL,
+ GIMP_TYPE_HANDLE_ANCHOR,
+ GIMP_HANDLE_ANCHOR_CENTER,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_canvas_progress_start;
+ iface->end = gimp_canvas_progress_end;
+ iface->is_active = gimp_canvas_progress_is_active;
+ iface->set_text = gimp_canvas_progress_set_text;
+ iface->set_value = gimp_canvas_progress_set_value;
+ iface->get_value = gimp_canvas_progress_get_value;
+ iface->pulse = gimp_canvas_progress_pulse;
+ iface->message = gimp_canvas_progress_message;
+}
+
+static void
+gimp_canvas_progress_init (GimpCanvasProgress *progress)
+{
+}
+
+static void
+gimp_canvas_progress_finalize (GObject *object)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (object);
+
+ g_clear_pointer (&private->text, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_progress_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_ANCHOR:
+ private->anchor = g_value_get_enum (value);
+ break;
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_progress_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_ANCHOR:
+ g_value_set_enum (value, private->anchor);
+ break;
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static PangoLayout *
+gimp_canvas_progress_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y,
+ gint *width,
+ gint *height)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+ PangoLayout *layout;
+
+ layout = gimp_canvas_get_layout (GIMP_CANVAS (canvas), "%s",
+ private->text);
+
+ pango_layout_get_pixel_size (layout, width, height);
+
+ *width = MAX (*width, 2 * RADIUS);
+
+ *width += 2 * BORDER;
+ *height += 3 * BORDER + 2 * RADIUS;
+
+ gimp_canvas_item_transform_xy_f (item,
+ private->x, private->y,
+ x, y);
+
+ gimp_canvas_item_shift_to_north_west (private->anchor,
+ *x, *y,
+ *width,
+ *height,
+ x, y);
+
+ *x = floor (*x);
+ *y = floor (*y);
+
+ return layout;
+}
+
+static void
+gimp_canvas_progress_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+ gdouble x, y;
+ gint width, height;
+
+ gimp_canvas_progress_transform (item, &x, &y, &width, &height);
+
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + width, y);
+ cairo_line_to (cr, x + width, y + height - BORDER - 2 * RADIUS);
+ cairo_line_to (cr, x + 2 * BORDER + 2 * RADIUS, y + height - BORDER - 2 * RADIUS);
+ cairo_arc (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS,
+ BORDER + RADIUS, 0, G_PI);
+ cairo_close_path (cr);
+
+ _gimp_canvas_item_fill (item, cr);
+
+ cairo_move_to (cr, x + BORDER, y + BORDER);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+ pango_cairo_show_layout (cr,
+ gimp_canvas_get_layout (GIMP_CANVAS (canvas),
+ "%s", private->text));
+
+ gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr);
+ cairo_arc (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS,
+ RADIUS, - G_PI / 2.0, 2 * G_PI - G_PI / 2.0);
+ cairo_fill (cr);
+
+ cairo_set_source_rgba (cr, 0.0, 1.0, 0.0, 1.0);
+ cairo_move_to (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS);
+ cairo_arc (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS,
+ RADIUS, - G_PI / 2.0, 2 * G_PI * private->value - G_PI / 2.0);
+ cairo_fill (cr);
+}
+
+static cairo_region_t *
+gimp_canvas_progress_get_extents (GimpCanvasItem *item)
+{
+ cairo_rectangle_int_t rectangle;
+ gdouble x, y;
+ gint width, height;
+
+ gimp_canvas_progress_transform (item, &x, &y, &width, &height);
+
+ /* add 1px on each side because fill()'s default impl does the same */
+ rectangle.x = (gint) x - 1;
+ rectangle.y = (gint) y - 1;
+ rectangle.width = width + 2;
+ rectangle.height = height + 2;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static gboolean
+gimp_canvas_progress_hit (GimpCanvasItem *item,
+ gdouble x,
+ gdouble y)
+{
+ gdouble px, py;
+ gint pwidth, pheight;
+ gdouble tx, ty;
+
+ gimp_canvas_progress_transform (item, &px, &py, &pwidth, &pheight);
+ gimp_canvas_item_transform_xy_f (item, x, y, &tx, &ty);
+
+ pheight -= BORDER + 2 * RADIUS;
+
+ return (tx >= px && tx < (px + pwidth) &&
+ ty >= py && ty < (py + pheight));
+}
+
+static GimpProgress *
+gimp_canvas_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (progress);
+
+ gimp_canvas_progress_set_text (progress, message);
+
+ private->last_update_time = g_get_monotonic_time ();
+
+ return progress;
+}
+
+static void
+gimp_canvas_progress_end (GimpProgress *progress)
+{
+}
+
+static gboolean
+gimp_canvas_progress_is_active (GimpProgress *progress)
+{
+ return TRUE;
+}
+
+static void
+gimp_canvas_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (progress);
+ cairo_region_t *old_region;
+ cairo_region_t *new_region;
+
+ old_region = gimp_canvas_item_get_extents (GIMP_CANVAS_ITEM (progress));
+
+ if (private->text)
+ g_free (private->text);
+
+ private->text = g_strdup (message);
+
+ new_region = gimp_canvas_item_get_extents (GIMP_CANVAS_ITEM (progress));
+
+ cairo_region_union (new_region, old_region);
+ cairo_region_destroy (old_region);
+
+ _gimp_canvas_item_update (GIMP_CANVAS_ITEM (progress), new_region);
+
+ cairo_region_destroy (new_region);
+}
+
+static void
+gimp_canvas_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (progress);
+
+ if (percentage != private->value)
+ {
+ guint64 time = g_get_monotonic_time ();
+
+ private->value = percentage;
+
+ if (time - private->last_update_time >= MIN_UPDATE_INTERVAL)
+ {
+ cairo_region_t *region;
+
+ private->last_update_time = time;
+
+ region = gimp_canvas_item_get_extents (GIMP_CANVAS_ITEM (progress));
+
+ _gimp_canvas_item_update (GIMP_CANVAS_ITEM (progress), region);
+
+ cairo_region_destroy (region);
+ }
+ }
+}
+
+static gdouble
+gimp_canvas_progress_get_value (GimpProgress *progress)
+{
+ GimpCanvasProgressPrivate *private = GET_PRIVATE (progress);
+
+ return private->value;
+}
+
+static void
+gimp_canvas_progress_pulse (GimpProgress *progress)
+{
+}
+
+static gboolean
+gimp_canvas_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ return FALSE;
+}
+
+GimpCanvasItem *
+gimp_canvas_progress_new (GimpDisplayShell *shell,
+ GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_PROGRESS,
+ "shell", shell,
+ "anchor", anchor,
+ "x", x,
+ "y", y,
+ NULL);
+}
diff --git a/app/display/gimpcanvasprogress.h b/app/display/gimpcanvasprogress.h
new file mode 100644
index 0000000..1f6a9c7
--- /dev/null
+++ b/app/display/gimpcanvasprogress.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasprogress.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_PROGRESS_H__
+#define __GIMP_CANVAS_PROGRESS_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_PROGRESS (gimp_canvas_progress_get_type ())
+#define GIMP_CANVAS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PROGRESS, GimpCanvasProgress))
+#define GIMP_CANVAS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PROGRESS, GimpCanvasProgressClass))
+#define GIMP_IS_CANVAS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PROGRESS))
+#define GIMP_IS_CANVAS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PROGRESS))
+#define GIMP_CANVAS_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PROGRESS, GimpCanvasProgressClass))
+
+
+typedef struct _GimpCanvasProgress GimpCanvasProgress;
+typedef struct _GimpCanvasProgressClass GimpCanvasProgressClass;
+
+struct _GimpCanvasProgress
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasProgressClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_progress_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_progress_new (GimpDisplayShell *shell,
+ GimpHandleAnchor anchor,
+ gdouble x,
+ gdouble y);
+
+
+#endif /* __GIMP_CANVAS_PROGRESS_H__ */
diff --git a/app/display/gimpcanvasproxygroup.c b/app/display/gimpcanvasproxygroup.c
new file mode 100644
index 0000000..a979f24
--- /dev/null
+++ b/app/display/gimpcanvasproxygroup.c
@@ -0,0 +1,197 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasproxygroup.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvasproxygroup.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0
+};
+
+
+typedef struct _GimpCanvasProxyGroupPrivate GimpCanvasProxyGroupPrivate;
+
+struct _GimpCanvasProxyGroupPrivate
+{
+ GHashTable *proxy_hash;
+};
+
+#define GET_PRIVATE(proxy_group) \
+ ((GimpCanvasProxyGroupPrivate *) gimp_canvas_proxy_group_get_instance_private ((GimpCanvasProxyGroup *) (proxy_group)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_proxy_group_finalize (GObject *object);
+static void gimp_canvas_proxy_group_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_proxy_group_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasProxyGroup, gimp_canvas_proxy_group,
+ GIMP_TYPE_CANVAS_GROUP)
+
+#define parent_class gimp_canvas_proxy_group_parent_class
+
+
+static void
+gimp_canvas_proxy_group_class_init (GimpCanvasProxyGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_canvas_proxy_group_finalize;
+ object_class->set_property = gimp_canvas_proxy_group_set_property;
+ object_class->get_property = gimp_canvas_proxy_group_get_property;
+}
+
+static void
+gimp_canvas_proxy_group_init (GimpCanvasProxyGroup *proxy_group)
+{
+ GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (proxy_group);
+
+ private->proxy_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+gimp_canvas_proxy_group_finalize (GObject *object)
+{
+ GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (object);
+
+ g_clear_pointer (&private->proxy_hash, g_hash_table_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_canvas_proxy_group_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ /* GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (object); */
+
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_proxy_group_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ /* GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (object); */
+
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GimpCanvasItem *
+gimp_canvas_proxy_group_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_PROXY_GROUP,
+ "shell", shell,
+ NULL);
+}
+
+void
+gimp_canvas_proxy_group_add_item (GimpCanvasProxyGroup *group,
+ gpointer object,
+ GimpCanvasItem *proxy_item)
+{
+ GimpCanvasProxyGroupPrivate *private;
+
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (proxy_item));
+ g_return_if_fail (GIMP_CANVAS_ITEM (group) != proxy_item);
+
+ private = GET_PRIVATE (group);
+
+ g_return_if_fail (g_hash_table_lookup (private->proxy_hash, object) ==
+ NULL);
+
+ g_hash_table_insert (private->proxy_hash, object, proxy_item);
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (group), proxy_item);
+}
+
+void
+gimp_canvas_proxy_group_remove_item (GimpCanvasProxyGroup *group,
+ gpointer object)
+{
+ GimpCanvasProxyGroupPrivate *private;
+ GimpCanvasItem *proxy_item;
+
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+ g_return_if_fail (object != NULL);
+
+ private = GET_PRIVATE (group);
+
+ proxy_item = g_hash_table_lookup (private->proxy_hash, object);
+
+ g_return_if_fail (proxy_item != NULL);
+
+ g_hash_table_remove (private->proxy_hash, object);
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (group), proxy_item);
+}
+
+GimpCanvasItem *
+gimp_canvas_proxy_group_get_item (GimpCanvasProxyGroup *group,
+ gpointer object)
+{
+ GimpCanvasProxyGroupPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_CANVAS_GROUP (group), NULL);
+ g_return_val_if_fail (object != NULL, NULL);
+
+ private = GET_PRIVATE (group);
+
+ return g_hash_table_lookup (private->proxy_hash, object);
+}
diff --git a/app/display/gimpcanvasproxygroup.h b/app/display/gimpcanvasproxygroup.h
new file mode 100644
index 0000000..be25787
--- /dev/null
+++ b/app/display/gimpcanvasproxygroup.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasproxygroup.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_PROXY_GROUP_H__
+#define __GIMP_CANVAS_PROXY_GROUP_H__
+
+
+#include "gimpcanvasgroup.h"
+
+
+#define GIMP_TYPE_CANVAS_PROXY_GROUP (gimp_canvas_proxy_group_get_type ())
+#define GIMP_CANVAS_PROXY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PROXY_GROUP, GimpCanvasProxyGroup))
+#define GIMP_CANVAS_PROXY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PROXY_GROUP, GimpCanvasProxyGroupClass))
+#define GIMP_IS_CANVAS_PROXY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PROXY_GROUP))
+#define GIMP_IS_CANVAS_PROXY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PROXY_GROUP))
+#define GIMP_CANVAS_PROXY_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PROXY_GROUP, GimpCanvasProxyGroupClass))
+
+
+typedef struct _GimpCanvasProxyGroup GimpCanvasProxyGroup;
+typedef struct _GimpCanvasProxyGroupClass GimpCanvasProxyGroupClass;
+
+struct _GimpCanvasProxyGroup
+{
+ GimpCanvasGroup parent_instance;
+};
+
+struct _GimpCanvasProxyGroupClass
+{
+ GimpCanvasGroupClass parent_class;
+};
+
+
+GType gimp_canvas_proxy_group_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_proxy_group_new (GimpDisplayShell *shell);
+
+void gimp_canvas_proxy_group_add_item (GimpCanvasProxyGroup *group,
+ gpointer object,
+ GimpCanvasItem *proxy_item);
+void gimp_canvas_proxy_group_remove_item (GimpCanvasProxyGroup *group,
+ gpointer object);
+GimpCanvasItem * gimp_canvas_proxy_group_get_item (GimpCanvasProxyGroup *group,
+ gpointer object);
+
+
+#endif /* __GIMP_CANVAS_PROXY_GROUP_H__ */
diff --git a/app/display/gimpcanvasrectangle.c b/app/display/gimpcanvasrectangle.c
new file mode 100644
index 0000000..7ef7351
--- /dev/null
+++ b/app/display/gimpcanvasrectangle.c
@@ -0,0 +1,360 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasrectangle.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvasrectangle.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_FILLED
+};
+
+
+typedef struct _GimpCanvasRectanglePrivate GimpCanvasRectanglePrivate;
+
+struct _GimpCanvasRectanglePrivate
+{
+ gdouble x;
+ gdouble y;
+ gdouble width;
+ gdouble height;
+ gboolean filled;
+};
+
+#define GET_PRIVATE(rectangle) \
+ ((GimpCanvasRectanglePrivate *) gimp_canvas_rectangle_get_instance_private ((GimpCanvasRectangle *) (rectangle)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_rectangle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_rectangle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_rectangle_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_rectangle_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasRectangle, gimp_canvas_rectangle,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_rectangle_parent_class
+
+
+static void
+gimp_canvas_rectangle_class_init (GimpCanvasRectangleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_rectangle_set_property;
+ object_class->get_property = gimp_canvas_rectangle_get_property;
+
+ item_class->draw = gimp_canvas_rectangle_draw;
+ item_class->get_extents = gimp_canvas_rectangle_get_extents;
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_double ("width", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_double ("height", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_FILLED,
+ g_param_spec_boolean ("filled", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_rectangle_init (GimpCanvasRectangle *rectangle)
+{
+}
+
+static void
+gimp_canvas_rectangle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasRectanglePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_double (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_double (value);
+ break;
+ case PROP_FILLED:
+ private->filled = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_rectangle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasRectanglePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case PROP_WIDTH:
+ g_value_set_double (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_double (value, private->height);
+ break;
+ case PROP_FILLED:
+ g_value_set_boolean (value, private->filled);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_rectangle_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *w,
+ gdouble *h)
+{
+ GimpCanvasRectanglePrivate *private = GET_PRIVATE (item);
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_item_transform_xy_f (item,
+ MIN (private->x,
+ private->x + private->width),
+ MIN (private->y,
+ private->y + private->height),
+ &x1, &y1);
+ gimp_canvas_item_transform_xy_f (item,
+ MAX (private->x,
+ private->x + private->width),
+ MAX (private->y,
+ private->y + private->height),
+ &x2, &y2);
+
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+
+ if (private->filled)
+ {
+ *x = x1;
+ *y = y1;
+ *w = x2 - x1;
+ *h = y2 - y1;
+ }
+ else
+ {
+ *x = x1 + 0.5;
+ *y = y1 + 0.5;
+ *w = x2 - 0.5 - *x;
+ *h = y2 - 0.5 - *y;
+
+ *w = MAX (0.0, *w);
+ *h = MAX (0.0, *h);
+ }
+}
+
+static void
+gimp_canvas_rectangle_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasRectanglePrivate *private = GET_PRIVATE (item);
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_rectangle_transform (item, &x, &y, &w, &h);
+
+ cairo_rectangle (cr, x, y, w, h);
+
+ if (private->filled)
+ _gimp_canvas_item_fill (item, cr);
+ else
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_rectangle_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasRectanglePrivate *private = GET_PRIVATE (item);
+ cairo_rectangle_int_t rectangle;
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_rectangle_transform (item, &x, &y, &w, &h);
+
+ if (private->filled)
+ {
+ rectangle.x = floor (x - 1.0);
+ rectangle.y = floor (y - 1.0);
+ rectangle.width = ceil (w + 2.0);
+ rectangle.height = ceil (h + 2.0);
+
+ return cairo_region_create_rectangle (&rectangle);
+ }
+ else if (w > 64 && h > 64)
+ {
+ cairo_region_t *region;
+
+ /* left */
+ rectangle.x = floor (x - 1.5);
+ rectangle.y = floor (y - 1.5);
+ rectangle.width = 3.0;
+ rectangle.height = ceil (h + 3.0);
+
+ region = cairo_region_create_rectangle (&rectangle);
+
+ /* right */
+ rectangle.x = floor (x + w - 1.5);
+
+ cairo_region_union_rectangle (region, &rectangle);
+
+ /* top */
+ rectangle.x = floor (x - 1.5);
+ rectangle.y = floor (y - 1.5);
+ rectangle.width = ceil (w + 3.0);
+ rectangle.height = 3.0;
+
+ cairo_region_union_rectangle (region, &rectangle);
+
+ /* bottom */
+ rectangle.y = floor (y + h - 1.5);
+
+ cairo_region_union_rectangle (region, &rectangle);
+
+ return region;
+ }
+ else
+ {
+ rectangle.x = floor (x - 1.5);
+ rectangle.y = floor (y - 1.5);
+ rectangle.width = ceil (w + 3.0);
+ rectangle.height = ceil (h + 3.0);
+
+ return cairo_region_create_rectangle (&rectangle);
+ }
+}
+
+GimpCanvasItem *
+gimp_canvas_rectangle_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gboolean filled)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_RECTANGLE,
+ "shell", shell,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ "filled", filled,
+ NULL);
+}
+
+void
+gimp_canvas_rectangle_set (GimpCanvasItem *rectangle,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_RECTANGLE (rectangle));
+
+ gimp_canvas_item_begin_change (rectangle);
+
+ g_object_set (rectangle,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ NULL);
+
+ gimp_canvas_item_end_change (rectangle);
+}
diff --git a/app/display/gimpcanvasrectangle.h b/app/display/gimpcanvasrectangle.h
new file mode 100644
index 0000000..6a96c06
--- /dev/null
+++ b/app/display/gimpcanvasrectangle.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasrectangle.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_RECTANGLE_H__
+#define __GIMP_CANVAS_RECTANGLE_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_RECTANGLE (gimp_canvas_rectangle_get_type ())
+#define GIMP_CANVAS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_RECTANGLE, GimpCanvasRectangle))
+#define GIMP_CANVAS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_RECTANGLE, GimpCanvasRectangleClass))
+#define GIMP_IS_CANVAS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_RECTANGLE))
+#define GIMP_IS_CANVAS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_RECTANGLE))
+#define GIMP_CANVAS_RECTANGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_RECTANGLE, GimpCanvasRectangleClass))
+
+
+typedef struct _GimpCanvasRectangle GimpCanvasRectangle;
+typedef struct _GimpCanvasRectangleClass GimpCanvasRectangleClass;
+
+struct _GimpCanvasRectangle
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasRectangleClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_rectangle_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_rectangle_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gboolean filled);
+
+void gimp_canvas_rectangle_set (GimpCanvasItem *rectangle,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height);
+
+
+#endif /* __GIMP_CANVAS_RECTANGLE_H__ */
diff --git a/app/display/gimpcanvasrectangleguides.c b/app/display/gimpcanvasrectangleguides.c
new file mode 100644
index 0000000..b7cc07d
--- /dev/null
+++ b/app/display/gimpcanvasrectangleguides.c
@@ -0,0 +1,422 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasrectangleguides.c
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvasrectangleguides.h"
+#include "gimpdisplayshell.h"
+
+
+#define SQRT5 2.236067977
+
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_TYPE,
+ PROP_N_GUIDES
+};
+
+
+typedef struct _GimpCanvasRectangleGuidesPrivate GimpCanvasRectangleGuidesPrivate;
+
+struct _GimpCanvasRectangleGuidesPrivate
+{
+ gdouble x;
+ gdouble y;
+ gdouble width;
+ gdouble height;
+ GimpGuidesType type;
+ gint n_guides;
+};
+
+#define GET_PRIVATE(rectangle) \
+ ((GimpCanvasRectangleGuidesPrivate *) gimp_canvas_rectangle_guides_get_instance_private ((GimpCanvasRectangleGuides *) (rectangle)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_rectangle_guides_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_rectangle_guides_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_rectangle_guides_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_rectangle_guides_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasRectangleGuides,
+ gimp_canvas_rectangle_guides, GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_rectangle_guides_parent_class
+
+
+static void
+gimp_canvas_rectangle_guides_class_init (GimpCanvasRectangleGuidesClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_rectangle_guides_set_property;
+ object_class->get_property = gimp_canvas_rectangle_guides_get_property;
+
+ item_class->draw = gimp_canvas_rectangle_guides_draw;
+ item_class->get_extents = gimp_canvas_rectangle_guides_get_extents;
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_double ("width", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_double ("height", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL, NULL,
+ GIMP_TYPE_GUIDES_TYPE,
+ GIMP_GUIDES_NONE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_N_GUIDES,
+ g_param_spec_int ("n-guides", NULL, NULL,
+ 1, 128, 4,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_rectangle_guides_init (GimpCanvasRectangleGuides *rectangle)
+{
+}
+
+static void
+gimp_canvas_rectangle_guides_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_double (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_double (value);
+ break;
+ case PROP_TYPE:
+ private->type = g_value_get_enum (value);
+ break;
+ case PROP_N_GUIDES:
+ private->n_guides = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_rectangle_guides_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case PROP_WIDTH:
+ g_value_set_double (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_double (value, private->height);
+ break;
+ case PROP_TYPE:
+ g_value_set_enum (value, private->type);
+ break;
+ case PROP_N_GUIDES:
+ g_value_set_int (value, private->n_guides);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_rectangle_guides_transform (GimpCanvasItem *item,
+ gdouble *x1,
+ gdouble *y1,
+ gdouble *x2,
+ gdouble *y2)
+{
+ GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (item);
+
+ gimp_canvas_item_transform_xy_f (item,
+ MIN (private->x,
+ private->x + private->width),
+ MIN (private->y,
+ private->y + private->height),
+ x1, y1);
+ gimp_canvas_item_transform_xy_f (item,
+ MAX (private->x,
+ private->x + private->width),
+ MAX (private->y,
+ private->y + private->height),
+ x2, y2);
+
+ *x1 = floor (*x1) + 0.5;
+ *y1 = floor (*y1) + 0.5;
+ *x2 = ceil (*x2) - 0.5;
+ *y2 = ceil (*y2) - 0.5;
+
+ *x2 = MAX (*x1, *x2);
+ *y2 = MAX (*y1, *y2);
+}
+
+static void
+draw_hline (cairo_t *cr,
+ gdouble x1,
+ gdouble x2,
+ gdouble y)
+{
+ y = floor (y) + 0.5;
+
+ cairo_move_to (cr, x1, y);
+ cairo_line_to (cr, x2, y);
+}
+
+static void
+draw_vline (cairo_t *cr,
+ gdouble y1,
+ gdouble y2,
+ gdouble x)
+{
+ x = floor (x) + 0.5;
+
+ cairo_move_to (cr, x, y1);
+ cairo_line_to (cr, x, y2);
+}
+
+static void
+gimp_canvas_rectangle_guides_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (item);
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gint i;
+
+ gimp_canvas_rectangle_guides_transform (item, &x1, &y1, &x2, &y2);
+
+ switch (private->type)
+ {
+ case GIMP_GUIDES_NONE:
+ break;
+
+ case GIMP_GUIDES_CENTER_LINES:
+ draw_hline (cr, x1, x2, (y1 + y2) / 2);
+ draw_vline (cr, y1, y2, (x1 + x2) / 2);
+ break;
+
+ case GIMP_GUIDES_THIRDS:
+ draw_hline (cr, x1, x2, (2 * y1 + y2) / 3);
+ draw_hline (cr, x1, x2, ( y1 + 2 * y2) / 3);
+
+ draw_vline (cr, y1, y2, (2 * x1 + x2) / 3);
+ draw_vline (cr, y1, y2, ( x1 + 2 * x2) / 3);
+ break;
+
+ case GIMP_GUIDES_FIFTHS:
+ for (i = 0; i < 5; i++)
+ {
+ draw_hline (cr, x1, x2, y1 + i * (y2 - y1) / 5);
+ draw_vline (cr, y1, y2, x1 + i * (x2 - x1) / 5);
+ }
+ break;
+
+ case GIMP_GUIDES_GOLDEN:
+ draw_hline (cr, x1, x2, (2 * y1 + (1 + SQRT5) * y2) / (3 + SQRT5));
+ draw_hline (cr, x1, x2, ((1 + SQRT5) * y1 + 2 * y2) / (3 + SQRT5));
+
+ draw_vline (cr, y1, y2, (2 * x1 + (1 + SQRT5) * x2) / (3 + SQRT5));
+ draw_vline (cr, y1, y2, ((1 + SQRT5) * x1 + 2 * x2) / (3 + SQRT5));
+ break;
+
+ /* This code implements the method of diagonals discovered by
+ * Edwin Westhoff - see http://www.diagonalmethod.info/
+ */
+ case GIMP_GUIDES_DIAGONALS:
+ {
+ /* the side of the largest square that can be
+ * fitted in whole into the rectangle (x1, y1), (x2, y2)
+ */
+ const gdouble square_side = MIN (x2 - x1, y2 - y1);
+
+ /* diagonal from the top-left edge */
+ cairo_move_to (cr, x1, y1);
+ cairo_line_to (cr, x1 + square_side, y1 + square_side);
+
+ /* diagonal from the top-right edge */
+ cairo_move_to (cr, x2, y1);
+ cairo_line_to (cr, x2 - square_side, y1 + square_side);
+
+ /* diagonal from the bottom-left edge */
+ cairo_move_to (cr, x1, y2);
+ cairo_line_to (cr, x1 + square_side, y2 - square_side);
+
+ /* diagonal from the bottom-right edge */
+ cairo_move_to (cr, x2, y2);
+ cairo_line_to (cr, x2 - square_side, y2 - square_side);
+ }
+ break;
+
+ case GIMP_GUIDES_N_LINES:
+ for (i = 0; i < private->n_guides; i++)
+ {
+ draw_hline (cr, x1, x2, y1 + i * (y2 - y1) / private->n_guides);
+ draw_vline (cr, y1, y2, x1 + i * (x2 - x1) / private->n_guides);
+ }
+ break;
+
+ case GIMP_GUIDES_SPACING:
+ break;
+ }
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_rectangle_guides_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (item);
+
+ if (private->type != GIMP_GUIDES_NONE)
+ {
+ cairo_rectangle_int_t rectangle;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_canvas_rectangle_guides_transform (item, &x1, &y1, &x2, &y2);
+
+ rectangle.x = floor (x1 - 1.5);
+ rectangle.y = floor (y1 - 1.5);
+ rectangle.width = ceil (x2 - x1 + 3.0);
+ rectangle.height = ceil (y2 - y1 + 3.0);
+
+ return cairo_region_create_rectangle (&rectangle);
+ }
+
+ return NULL;
+}
+
+GimpCanvasItem *
+gimp_canvas_rectangle_guides_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpGuidesType type,
+ gint n_guides)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_RECTANGLE_GUIDES,
+ "shell", shell,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ "type", type,
+ "n-guides", n_guides,
+ NULL);
+}
+
+void
+gimp_canvas_rectangle_guides_set (GimpCanvasItem *rectangle,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpGuidesType type,
+ gint n_guides)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_RECTANGLE_GUIDES (rectangle));
+
+ gimp_canvas_item_begin_change (rectangle);
+
+ g_object_set (rectangle,
+ "x", x,
+ "y", y,
+ "width", width,
+ "height", height,
+ "type", type,
+ "n-guides", n_guides,
+ NULL);
+
+ gimp_canvas_item_end_change (rectangle);
+}
diff --git a/app/display/gimpcanvasrectangleguides.h b/app/display/gimpcanvasrectangleguides.h
new file mode 100644
index 0000000..1d37d15
--- /dev/null
+++ b/app/display/gimpcanvasrectangleguides.h
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvasrectangleguides.h
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_RECTANGLE_GUIDES_H__
+#define __GIMP_CANVAS_RECTANGLE_GUIDES_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_RECTANGLE_GUIDES (gimp_canvas_rectangle_guides_get_type ())
+#define GIMP_CANVAS_RECTANGLE_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, GimpCanvasRectangleGuides))
+#define GIMP_CANVAS_RECTANGLE_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, GimpCanvasRectangleGuidesClass))
+#define GIMP_IS_CANVAS_RECTANGLE_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES))
+#define GIMP_IS_CANVAS_RECTANGLE_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES))
+#define GIMP_CANVAS_RECTANGLE_GUIDES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, GimpCanvasRectangleGuidesClass))
+
+
+typedef struct _GimpCanvasRectangleGuides GimpCanvasRectangleGuides;
+typedef struct _GimpCanvasRectangleGuidesClass GimpCanvasRectangleGuidesClass;
+
+struct _GimpCanvasRectangleGuides
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasRectangleGuidesClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_rectangle_guides_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_rectangle_guides_new (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpGuidesType type,
+ gint n_guides);
+
+void gimp_canvas_rectangle_guides_set (GimpCanvasItem *rectangle,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpGuidesType type,
+ gint n_guides);
+
+
+#endif /* __GIMP_CANVAS_RECTANGLE_GUIDES_H__ */
diff --git a/app/display/gimpcanvassamplepoint.c b/app/display/gimpcanvassamplepoint.c
new file mode 100644
index 0000000..913a5b7
--- /dev/null
+++ b/app/display/gimpcanvassamplepoint.c
@@ -0,0 +1,356 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvassamplepoint.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvas-style.h"
+#include "gimpcanvassamplepoint.h"
+#include "gimpdisplayshell.h"
+
+
+#define GIMP_SAMPLE_POINT_DRAW_SIZE 14
+
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y,
+ PROP_INDEX,
+ PROP_SAMPLE_POINT_STYLE
+};
+
+
+typedef struct _GimpCanvasSamplePointPrivate GimpCanvasSamplePointPrivate;
+
+struct _GimpCanvasSamplePointPrivate
+{
+ gint x;
+ gint y;
+ gint index;
+ gboolean sample_point_style;
+};
+
+#define GET_PRIVATE(sample_point) \
+ ((GimpCanvasSamplePointPrivate *) gimp_canvas_sample_point_get_instance_private ((GimpCanvasSamplePoint *) (sample_point)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_sample_point_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_sample_point_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_sample_point_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_sample_point_get_extents (GimpCanvasItem *item);
+static void gimp_canvas_sample_point_stroke (GimpCanvasItem *item,
+ cairo_t *cr);
+static void gimp_canvas_sample_point_fill (GimpCanvasItem *item,
+ cairo_t *cr);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasSamplePoint, gimp_canvas_sample_point,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_sample_point_parent_class
+
+
+static void
+gimp_canvas_sample_point_class_init (GimpCanvasSamplePointClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_sample_point_set_property;
+ object_class->get_property = gimp_canvas_sample_point_get_property;
+
+ item_class->draw = gimp_canvas_sample_point_draw;
+ item_class->get_extents = gimp_canvas_sample_point_get_extents;
+ item_class->stroke = gimp_canvas_sample_point_stroke;
+ item_class->fill = gimp_canvas_sample_point_fill;
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_int ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_int ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_INDEX,
+ g_param_spec_int ("index", NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SAMPLE_POINT_STYLE,
+ g_param_spec_boolean ("sample-point-style",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_sample_point_init (GimpCanvasSamplePoint *sample_point)
+{
+}
+
+static void
+gimp_canvas_sample_point_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ private->x = g_value_get_int (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_int (value);
+ break;
+ case PROP_INDEX:
+ private->index = g_value_get_int (value);
+ break;
+ case PROP_SAMPLE_POINT_STYLE:
+ private->sample_point_style = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_sample_point_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_int (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_int (value, private->x);
+ break;
+ case PROP_INDEX:
+ g_value_set_int (value, private->x);
+ break;
+ case PROP_SAMPLE_POINT_STYLE:
+ g_value_set_boolean (value, private->sample_point_style);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_sample_point_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item);
+
+ gimp_canvas_item_transform_xy_f (item,
+ private->x + 0.5,
+ private->y + 0.5,
+ x, y);
+
+ *x = floor (*x) + 0.5;
+ *y = floor (*y) + 0.5;
+}
+
+#define HALF_SIZE (GIMP_SAMPLE_POINT_DRAW_SIZE / 2)
+
+static void
+gimp_canvas_sample_point_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+ PangoLayout *layout;
+ gdouble x, y;
+ gint x1, x2, y1, y2;
+
+ gimp_canvas_sample_point_transform (item, &x, &y);
+
+ x1 = x - GIMP_SAMPLE_POINT_DRAW_SIZE;
+ x2 = x + GIMP_SAMPLE_POINT_DRAW_SIZE;
+ y1 = y - GIMP_SAMPLE_POINT_DRAW_SIZE;
+ y2 = y + GIMP_SAMPLE_POINT_DRAW_SIZE;
+
+ cairo_move_to (cr, x, y1);
+ cairo_line_to (cr, x, y1 + HALF_SIZE);
+
+ cairo_move_to (cr, x, y2);
+ cairo_line_to (cr, x, y2 - HALF_SIZE);
+
+ cairo_move_to (cr, x1, y);
+ cairo_line_to (cr, x1 + HALF_SIZE, y);
+
+ cairo_move_to (cr, x2, y);
+ cairo_line_to (cr, x2 - HALF_SIZE, y);
+
+ cairo_arc_negative (cr, x, y, HALF_SIZE, 0.0, 0.5 * G_PI);
+
+ _gimp_canvas_item_stroke (item, cr);
+
+ layout = gimp_canvas_get_layout (GIMP_CANVAS (canvas),
+ "%d", private->index);
+
+ cairo_move_to (cr, x + 3, y + 3);
+ pango_cairo_show_layout (cr, layout);
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_sample_point_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item);
+ GtkWidget *canvas = gimp_canvas_item_get_canvas (item);
+ cairo_rectangle_int_t rectangle;
+ PangoLayout *layout;
+ PangoRectangle ink;
+ gdouble x, y;
+ gint x1, x2, y1, y2;
+
+ gimp_canvas_sample_point_transform (item, &x, &y);
+
+ x1 = floor (x - GIMP_SAMPLE_POINT_DRAW_SIZE);
+ x2 = ceil (x + GIMP_SAMPLE_POINT_DRAW_SIZE);
+ y1 = floor (y - GIMP_SAMPLE_POINT_DRAW_SIZE);
+ y2 = ceil (y + GIMP_SAMPLE_POINT_DRAW_SIZE);
+
+ layout = gimp_canvas_get_layout (GIMP_CANVAS (canvas),
+ "%d", private->index);
+
+ pango_layout_get_extents (layout, &ink, NULL);
+
+ x2 = MAX (x2, 3 + ink.width);
+ y2 = MAX (y2, 3 + ink.height);
+
+ rectangle.x = x1 - 1.5;
+ rectangle.y = y1 - 1.5;
+ rectangle.width = x2 - x1 + 3.0;
+ rectangle.height = y2 - y1 + 3.0;
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+static void
+gimp_canvas_sample_point_stroke (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item);
+
+ if (private->sample_point_style)
+ {
+ gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr);
+ cairo_stroke_preserve (cr);
+
+ gimp_canvas_set_sample_point_style (gimp_canvas_item_get_canvas (item), cr,
+ gimp_canvas_item_get_highlight (item));
+ cairo_stroke (cr);
+ }
+ else
+ {
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr);
+ }
+}
+
+static void
+gimp_canvas_sample_point_fill (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item);
+
+ if (private->sample_point_style)
+ {
+ gimp_canvas_set_sample_point_style (gimp_canvas_item_get_canvas (item), cr,
+ gimp_canvas_item_get_highlight (item));
+ cairo_fill (cr);
+ }
+ else
+ {
+ GIMP_CANVAS_ITEM_CLASS (parent_class)->fill (item, cr);
+ }
+}
+
+GimpCanvasItem *
+gimp_canvas_sample_point_new (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint index,
+ gboolean sample_point_style)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_SAMPLE_POINT,
+ "shell", shell,
+ "x", x,
+ "y", y,
+ "index", index,
+ "sample-point-style", sample_point_style,
+ NULL);
+}
+
+void
+gimp_canvas_sample_point_set (GimpCanvasItem *sample_point,
+ gint x,
+ gint y)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_SAMPLE_POINT (sample_point));
+
+ gimp_canvas_item_begin_change (sample_point);
+
+ g_object_set (sample_point,
+ "x", x,
+ "y", y,
+ NULL);
+
+ gimp_canvas_item_end_change (sample_point);
+}
diff --git a/app/display/gimpcanvassamplepoint.h b/app/display/gimpcanvassamplepoint.h
new file mode 100644
index 0000000..00ac0d5
--- /dev/null
+++ b/app/display/gimpcanvassamplepoint.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvassamplepoint.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_SAMPLE_POINT_H__
+#define __GIMP_CANVAS_SAMPLE_POINT_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_SAMPLE_POINT (gimp_canvas_sample_point_get_type ())
+#define GIMP_CANVAS_SAMPLE_POINT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_SAMPLE_POINT, GimpCanvasSamplePoint))
+#define GIMP_CANVAS_SAMPLE_POINT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_SAMPLE_POINT, GimpCanvasSamplePointClass))
+#define GIMP_IS_CANVAS_SAMPLE_POINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_SAMPLE_POINT))
+#define GIMP_IS_CANVAS_SAMPLE_POINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_SAMPLE_POINT))
+#define GIMP_CANVAS_SAMPLE_POINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_SAMPLE_POINT, GimpCanvasSamplePointClass))
+
+
+typedef struct _GimpCanvasSamplePoint GimpCanvasSamplePoint;
+typedef struct _GimpCanvasSamplePointClass GimpCanvasSamplePointClass;
+
+struct _GimpCanvasSamplePoint
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasSamplePointClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_sample_point_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_sample_point_new (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint index,
+ gboolean sample_point_style);
+
+void gimp_canvas_sample_point_set (GimpCanvasItem *sample_point,
+ gint x,
+ gint y);
+
+
+#endif /* __GIMP_CANVAS_SAMPLE_POINT_H__ */
diff --git a/app/display/gimpcanvastextcursor.c b/app/display/gimpcanvastextcursor.c
new file mode 100644
index 0000000..aaa8c5f
--- /dev/null
+++ b/app/display/gimpcanvastextcursor.c
@@ -0,0 +1,386 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvastextcursor.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpcanvastextcursor.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_OVERWRITE,
+ PROP_DIRECTION
+};
+
+
+typedef struct _GimpCanvasTextCursorPrivate GimpCanvasTextCursorPrivate;
+
+struct _GimpCanvasTextCursorPrivate
+{
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+ gboolean overwrite;
+ GimpTextDirection direction;
+};
+
+#define GET_PRIVATE(text_cursor) \
+ ((GimpCanvasTextCursorPrivate *) gimp_canvas_text_cursor_get_instance_private ((GimpCanvasTextCursor *) (text_cursor)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_text_cursor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_text_cursor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_text_cursor_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_text_cursor_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasTextCursor, gimp_canvas_text_cursor,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_text_cursor_parent_class
+
+
+static void
+gimp_canvas_text_cursor_class_init (GimpCanvasTextCursorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_text_cursor_set_property;
+ object_class->get_property = gimp_canvas_text_cursor_get_property;
+
+ item_class->draw = gimp_canvas_text_cursor_draw;
+ item_class->get_extents = gimp_canvas_text_cursor_get_extents;
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_int ("x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_int ("y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_int ("width", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_int ("height", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_OVERWRITE,
+ g_param_spec_boolean ("overwrite", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_DIRECTION,
+ g_param_spec_enum ("direction", NULL, NULL,
+ gimp_text_direction_get_type(),
+ GIMP_TEXT_DIRECTION_LTR,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_text_cursor_init (GimpCanvasTextCursor *text_cursor)
+{
+}
+
+static void
+gimp_canvas_text_cursor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasTextCursorPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ private->x = g_value_get_int (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_int (value);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_int (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_int (value);
+ break;
+ case PROP_OVERWRITE:
+ private->overwrite = g_value_get_boolean (value);
+ break;
+ case PROP_DIRECTION:
+ private->direction = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_text_cursor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasTextCursorPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_X:
+ g_value_set_int (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_int (value, private->y);
+ break;
+ case PROP_WIDTH:
+ g_value_set_int (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, private->height);
+ break;
+ case PROP_OVERWRITE:
+ g_value_set_boolean (value, private->overwrite);
+ break;
+ case PROP_DIRECTION:
+ g_value_set_enum (value, private->direction);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_text_cursor_transform (GimpCanvasItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *w,
+ gdouble *h)
+{
+ GimpCanvasTextCursorPrivate *private = GET_PRIVATE (item);
+
+ gimp_canvas_item_transform_xy_f (item,
+ MIN (private->x,
+ private->x + private->width),
+ MIN (private->y,
+ private->y + private->height),
+ x, y);
+ gimp_canvas_item_transform_xy_f (item,
+ MAX (private->x,
+ private->x + private->width),
+ MAX (private->y,
+ private->y + private->height),
+ w, h);
+
+ *w -= *x;
+ *h -= *y;
+
+ *x = floor (*x) + 0.5;
+ *y = floor (*y) + 0.5;
+ switch (private->direction)
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ *x = *x - *w;
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ *y = *y + *h;
+ break;
+ }
+
+ if (private->overwrite)
+ {
+ *w = ceil (*w) - 1.0;
+ *h = ceil (*h) - 1.0;
+ }
+ else
+ {
+ switch (private->direction)
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ *w = 0;
+ *h = ceil (*h) - 1.0;
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ *w = ceil (*w) - 1.0;
+ *h = 0;
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ *w = ceil (*w) - 1.0;
+ *h = 0;
+ break;
+ }
+ }
+}
+
+static void
+gimp_canvas_text_cursor_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasTextCursorPrivate *private = GET_PRIVATE (item);
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_text_cursor_transform (item, &x, &y, &w, &h);
+
+ if (private->overwrite)
+ {
+ cairo_rectangle (cr, x, y, w, h);
+ }
+ else
+ {
+ switch (private->direction)
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x, y + h);
+
+ cairo_move_to (cr, x - 3.0, y);
+ cairo_line_to (cr, x + 3.0, y);
+
+ cairo_move_to (cr, x - 3.0, y + h);
+ cairo_line_to (cr, x + 3.0, y + h);
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + w, y);
+
+ cairo_move_to (cr, x, y - 3.0);
+ cairo_line_to (cr, x, y + 3.0);
+
+ cairo_move_to (cr, x + w, y - 3.0);
+ cairo_line_to (cr, x + w, y + 3.0);
+ break;
+ }
+ }
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_text_cursor_get_extents (GimpCanvasItem *item)
+{
+ GimpCanvasTextCursorPrivate *private = GET_PRIVATE (item);
+ cairo_rectangle_int_t rectangle;
+ gdouble x, y;
+ gdouble w, h;
+
+ gimp_canvas_text_cursor_transform (item, &x, &y, &w, &h);
+
+ if (private->overwrite)
+ {
+ rectangle.x = floor (x - 1.5);
+ rectangle.y = floor (y - 1.5);
+ rectangle.width = ceil (w + 3.0);
+ rectangle.height = ceil (h + 3.0);
+ }
+ else
+ {
+ switch (private->direction)
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ rectangle.x = floor (x - 4.5);
+ rectangle.y = floor (y - 1.5);
+ rectangle.width = ceil (9.0);
+ rectangle.height = ceil (h + 3.0);
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ rectangle.x = floor (x - 1.5);
+ rectangle.y = floor (y - 4.5);
+ rectangle.width = ceil (w + 3.0);
+ rectangle.height = ceil (9.0);
+ break;
+ }
+ }
+
+ return cairo_region_create_rectangle (&rectangle);
+}
+
+GimpCanvasItem *
+gimp_canvas_text_cursor_new (GimpDisplayShell *shell,
+ PangoRectangle *cursor,
+ gboolean overwrite,
+ GimpTextDirection direction)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (cursor != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_TEXT_CURSOR,
+ "shell", shell,
+ "x", cursor->x,
+ "y", cursor->y,
+ "width", cursor->width,
+ "height", cursor->height,
+ "overwrite", overwrite,
+ "direction", direction,
+ NULL);
+}
diff --git a/app/display/gimpcanvastextcursor.h b/app/display/gimpcanvastextcursor.h
new file mode 100644
index 0000000..210b2a2
--- /dev/null
+++ b/app/display/gimpcanvastextcursor.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvastextcursor.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_TEXT_CURSOR_H__
+#define __GIMP_CANVAS_TEXT_CURSOR_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_TEXT_CURSOR (gimp_canvas_text_cursor_get_type ())
+#define GIMP_CANVAS_TEXT_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_TEXT_CURSOR, GimpCanvasTextCursor))
+#define GIMP_CANVAS_TEXT_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_TEXT_CURSOR, GimpCanvasTextCursorClass))
+#define GIMP_IS_CANVAS_TEXT_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_TEXT_CURSOR))
+#define GIMP_IS_CANVAS_TEXT_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_TEXT_CURSOR))
+#define GIMP_CANVAS_TEXT_CURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_TEXT_CURSOR, GimpCanvasTextCursorClass))
+
+
+typedef struct _GimpCanvasTextCursor GimpCanvasTextCursor;
+typedef struct _GimpCanvasTextCursorClass GimpCanvasTextCursorClass;
+
+struct _GimpCanvasTextCursor
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasTextCursorClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_text_cursor_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_text_cursor_new (GimpDisplayShell *shell,
+ PangoRectangle *cursor,
+ gboolean overwrite,
+ GimpTextDirection direction);
+
+
+#endif /* __GIMP_CANVAS_RECTANGLE_H__ */
diff --git a/app/display/gimpcanvastransformguides.c b/app/display/gimpcanvastransformguides.c
new file mode 100644
index 0000000..ba6c4cb
--- /dev/null
+++ b/app/display/gimpcanvastransformguides.c
@@ -0,0 +1,683 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvastransformguides.c
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+
+#include "gimpcanvastransformguides.h"
+#include "gimpdisplayshell.h"
+
+
+#define SQRT5 2.236067977
+
+
+enum
+{
+ PROP_0,
+ PROP_TRANSFORM,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ PROP_TYPE,
+ PROP_N_GUIDES,
+ PROP_CLIP
+};
+
+
+typedef struct _GimpCanvasTransformGuidesPrivate GimpCanvasTransformGuidesPrivate;
+
+struct _GimpCanvasTransformGuidesPrivate
+{
+ GimpMatrix3 transform;
+ gdouble x1, y1;
+ gdouble x2, y2;
+ GimpGuidesType type;
+ gint n_guides;
+ gboolean clip;
+};
+
+#define GET_PRIVATE(transform) \
+ ((GimpCanvasTransformGuidesPrivate *) gimp_canvas_transform_guides_get_instance_private ((GimpCanvasTransformGuides *) (transform)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_transform_guides_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_transform_guides_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_transform_guides_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_transform_guides_get_extents (GimpCanvasItem *item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasTransformGuides,
+ gimp_canvas_transform_guides, GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_transform_guides_parent_class
+
+
+static void
+gimp_canvas_transform_guides_class_init (GimpCanvasTransformGuidesClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->set_property = gimp_canvas_transform_guides_set_property;
+ object_class->get_property = gimp_canvas_transform_guides_get_property;
+
+ item_class->draw = gimp_canvas_transform_guides_draw;
+ item_class->get_extents = gimp_canvas_transform_guides_get_extents;
+
+ g_object_class_install_property (object_class, PROP_TRANSFORM,
+ gimp_param_spec_matrix3 ("transform",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_double ("x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_double ("y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_double ("x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_double ("y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL, NULL,
+ GIMP_TYPE_GUIDES_TYPE,
+ GIMP_GUIDES_NONE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_N_GUIDES,
+ g_param_spec_int ("n-guides", NULL, NULL,
+ 1, 128, 4,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_CLIP,
+ g_param_spec_boolean ("clip", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_transform_guides_init (GimpCanvasTransformGuides *transform)
+{
+}
+
+static void
+gimp_canvas_transform_guides_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TRANSFORM:
+ {
+ GimpMatrix3 *transform = g_value_get_boxed (value);
+
+ if (transform)
+ private->transform = *transform;
+ else
+ gimp_matrix3_identity (&private->transform);
+ }
+ break;
+
+ case PROP_X1:
+ private->x1 = g_value_get_double (value);
+ break;
+
+ case PROP_Y1:
+ private->y1 = g_value_get_double (value);
+ break;
+
+ case PROP_X2:
+ private->x2 = g_value_get_double (value);
+ break;
+
+ case PROP_Y2:
+ private->y2 = g_value_get_double (value);
+ break;
+
+ case PROP_TYPE:
+ private->type = g_value_get_enum (value);
+ break;
+
+ case PROP_N_GUIDES:
+ private->n_guides = g_value_get_int (value);
+ break;
+
+ case PROP_CLIP:
+ private->clip = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_transform_guides_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TRANSFORM:
+ g_value_set_boxed (value, &private->transform);
+ break;
+
+ case PROP_X1:
+ g_value_set_double (value, private->x1);
+ break;
+
+ case PROP_Y1:
+ g_value_set_double (value, private->y1);
+ break;
+
+ case PROP_X2:
+ g_value_set_double (value, private->x2);
+ break;
+
+ case PROP_Y2:
+ g_value_set_double (value, private->y2);
+ break;
+
+ case PROP_TYPE:
+ g_value_set_enum (value, private->type);
+ break;
+
+ case PROP_N_GUIDES:
+ g_value_set_int (value, private->n_guides);
+ break;
+
+ case PROP_CLIP:
+ g_value_set_boolean (value, private->clip);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gimp_canvas_transform_guides_transform (GimpCanvasItem *item,
+ GimpVector2 *t_vertices,
+ gint *n_t_vertices)
+{
+ GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (item);
+ GimpVector2 vertices[4];
+
+ vertices[0] = (GimpVector2) { private->x1, private->y1 };
+ vertices[1] = (GimpVector2) { private->x2, private->y1 };
+ vertices[2] = (GimpVector2) { private->x2, private->y2 };
+ vertices[3] = (GimpVector2) { private->x1, private->y2 };
+
+ if (private->clip)
+ {
+ gimp_transform_polygon (&private->transform, vertices, 4, TRUE,
+ t_vertices, n_t_vertices);
+
+ return TRUE;
+ }
+ else
+ {
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ gimp_matrix3_transform_point (&private->transform,
+ vertices[i].x, vertices[i].y,
+ &t_vertices[i].x, &t_vertices[i].y);
+ }
+
+ *n_t_vertices = 4;
+
+ return gimp_transform_polygon_is_convex (t_vertices[0].x, t_vertices[0].y,
+ t_vertices[1].x, t_vertices[1].y,
+ t_vertices[3].x, t_vertices[3].y,
+ t_vertices[2].x, t_vertices[2].y);
+ }
+}
+
+static void
+draw_line (cairo_t *cr,
+ GimpCanvasItem *item,
+ GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (item);
+ GimpVector2 vertices[2];
+ GimpVector2 t_vertices[2];
+ gint n_t_vertices;
+
+ vertices[0] = (GimpVector2) { x1, y1 };
+ vertices[1] = (GimpVector2) { x2, y2 };
+
+ if (private->clip)
+ {
+ gimp_transform_polygon (transform, vertices, 2, FALSE,
+ t_vertices, &n_t_vertices);
+ }
+ else
+ {
+ gint i;
+
+ for (i = 0; i < 2; i++)
+ {
+ gimp_matrix3_transform_point (transform,
+ vertices[i].x, vertices[i].y,
+ &t_vertices[i].x, &t_vertices[i].y);
+ }
+
+ n_t_vertices = 2;
+ }
+
+ if (n_t_vertices == 2)
+ {
+ gint i;
+
+ for (i = 0; i < 2; i++)
+ {
+ GimpVector2 v;
+
+ gimp_canvas_item_transform_xy_f (item,
+ t_vertices[i].x, t_vertices[i].y,
+ &v.x, &v.y);
+
+ v.x = floor (v.x) + 0.5;
+ v.y = floor (v.y) + 0.5;
+
+ if (i == 0)
+ cairo_move_to (cr, v.x, v.y);
+ else
+ cairo_line_to (cr, v.x, v.y);
+ }
+ }
+}
+
+static void
+draw_hline (cairo_t *cr,
+ GimpCanvasItem *item,
+ GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble x2,
+ gdouble y)
+{
+ draw_line (cr, item, transform, x1, y, x2, y);
+}
+
+static void
+draw_vline (cairo_t *cr,
+ GimpCanvasItem *item,
+ GimpMatrix3 *transform,
+ gdouble y1,
+ gdouble y2,
+ gdouble x)
+{
+ draw_line (cr, item, transform, x, y1, x, y2);
+}
+
+static void
+gimp_canvas_transform_guides_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (item);
+ GimpVector2 t_vertices[5];
+ gint n_t_vertices;
+ gboolean convex;
+ gint i;
+
+ convex = gimp_canvas_transform_guides_transform (item,
+ t_vertices, &n_t_vertices);
+
+ if (n_t_vertices < 2)
+ return;
+
+ for (i = 0; i < n_t_vertices; i++)
+ {
+ GimpVector2 v;
+
+ gimp_canvas_item_transform_xy_f (item, t_vertices[i].x, t_vertices[i].y,
+ &v.x, &v.y);
+
+ v.x = floor (v.x) + 0.5;
+ v.y = floor (v.y) + 0.5;
+
+ if (i == 0)
+ cairo_move_to (cr, v.x, v.y);
+ else
+ cairo_line_to (cr, v.x, v.y);
+ }
+
+ cairo_close_path (cr);
+
+ if (! convex || n_t_vertices < 3)
+ {
+ _gimp_canvas_item_stroke (item, cr);
+ return;
+ }
+
+ switch (private->type)
+ {
+ case GIMP_GUIDES_NONE:
+ break;
+
+ case GIMP_GUIDES_CENTER_LINES:
+ draw_hline (cr, item, &private->transform,
+ private->x1, private->x2, (private->y1 + private->y2) / 2);
+ draw_vline (cr, item, &private->transform,
+ private->y1, private->y2, (private->x1 + private->x2) / 2);
+ break;
+
+ case GIMP_GUIDES_THIRDS:
+ draw_hline (cr, item, &private->transform,
+ private->x1, private->x2, (2 * private->y1 + private->y2) / 3);
+ draw_hline (cr, item, &private->transform,
+ private->x1, private->x2, (private->y1 + 2 * private->y2) / 3);
+
+ draw_vline (cr, item, &private->transform,
+ private->y1, private->y2, (2 * private->x1 + private->x2) / 3);
+ draw_vline (cr, item, &private->transform,
+ private->y1, private->y2, (private->x1 + 2 * private->x2) / 3);
+ break;
+
+ case GIMP_GUIDES_FIFTHS:
+ for (i = 0; i < 5; i++)
+ {
+ draw_hline (cr, item, &private->transform,
+ private->x1, private->x2,
+ private->y1 + i * (private->y2 - private->y1) / 5);
+ draw_vline (cr, item, &private->transform,
+ private->y1, private->y2,
+ private->x1 + i * (private->x2 - private->x1) / 5);
+ }
+ break;
+
+ case GIMP_GUIDES_GOLDEN:
+ draw_hline (cr, item, &private->transform,
+ private->x1, private->x2,
+ (2 * private->y1 + (1 + SQRT5) * private->y2) / (3 + SQRT5));
+ draw_hline (cr, item, &private->transform,
+ private->x1, private->x2,
+ ((1 + SQRT5) * private->y1 + 2 * private->y2) / (3 + SQRT5));
+
+ draw_vline (cr, item, &private->transform,
+ private->y1, private->y2,
+ (2 * private->x1 + (1 + SQRT5) * private->x2) / (3 + SQRT5));
+ draw_vline (cr, item, &private->transform,
+ private->y1, private->y2,
+ ((1 + SQRT5) * private->x1 + 2 * private->x2) / (3 + SQRT5));
+ break;
+
+ /* This code implements the method of diagonals discovered by
+ * Edwin Westhoff - see http://www.diagonalmethod.info/
+ */
+ case GIMP_GUIDES_DIAGONALS:
+ {
+ /* the side of the largest square that can be
+ * fitted in whole into the rectangle (x1, y1), (x2, y2)
+ */
+ const gdouble square_side = MIN (private->x2 - private->x1,
+ private->y2 - private->y1);
+
+ /* diagonal from the top-left edge */
+ draw_line (cr, item, &private->transform,
+ private->x1, private->y1,
+ private->x1 + square_side,
+ private->y1 + square_side);
+
+ /* diagonal from the top-right edge */
+ draw_line (cr, item, &private->transform,
+ private->x2, private->y1,
+ private->x2 - square_side,
+ private->y1 + square_side);
+
+ /* diagonal from the bottom-left edge */
+ draw_line (cr, item, &private->transform,
+ private->x1, private->y2,
+ private->x1 + square_side,
+ private->y2 - square_side);
+
+ /* diagonal from the bottom-right edge */
+ draw_line (cr, item, &private->transform,
+ private->x2, private->y2,
+ private->x2 - square_side,
+ private->y2 - square_side);
+ }
+ break;
+
+ case GIMP_GUIDES_N_LINES:
+ case GIMP_GUIDES_SPACING:
+ {
+ gint width, height;
+ gint ngx, ngy;
+
+ width = MAX (1, private->x2 - private->x1);
+ height = MAX (1, private->y2 - private->y1);
+
+ /* the MIN() in the code below limits the grid to one line
+ * every 5 image pixels, see bug 772667.
+ */
+
+ if (private->type == GIMP_GUIDES_N_LINES)
+ {
+ if (width <= height)
+ {
+ ngx = private->n_guides;
+ ngx = MIN (ngx, width / 5);
+
+ ngy = ngx * MAX (1, height / width);
+ ngy = MIN (ngy, height / 5);
+ }
+ else
+ {
+ ngy = private->n_guides;
+ ngy = MIN (ngy, height / 5);
+
+ ngx = ngy * MAX (1, width / height);
+ ngx = MIN (ngx, width / 5);
+ }
+ }
+ else /* GIMP_GUIDES_SPACING */
+ {
+ gint grid_size = MAX (2, private->n_guides);
+
+ ngx = width / grid_size;
+ ngx = MIN (ngx, width / 5);
+
+ ngy = height / grid_size;
+ ngy = MIN (ngy, height / 5);
+ }
+
+ for (i = 1; i <= ngx; i++)
+ {
+ gdouble x = private->x1 + (((gdouble) i) / (ngx + 1) *
+ (private->x2 - private->x1));
+
+ draw_line (cr, item, &private->transform,
+ x, private->y1,
+ x, private->y2);
+ }
+
+ for (i = 1; i <= ngy; i++)
+ {
+ gdouble y = private->y1 + (((gdouble) i) / (ngy + 1) *
+ (private->y2 - private->y1));
+
+ draw_line (cr, item, &private->transform,
+ private->x1, y,
+ private->x2, y);
+ }
+ }
+ }
+
+ _gimp_canvas_item_stroke (item, cr);
+}
+
+static cairo_region_t *
+gimp_canvas_transform_guides_get_extents (GimpCanvasItem *item)
+{
+ GimpVector2 t_vertices[5];
+ gint n_t_vertices;
+ GimpVector2 top_left;
+ GimpVector2 bottom_right;
+ cairo_rectangle_int_t extents;
+ gint i;
+
+ gimp_canvas_transform_guides_transform (item, t_vertices, &n_t_vertices);
+
+ if (n_t_vertices < 2)
+ return cairo_region_create ();
+
+ for (i = 0; i < n_t_vertices; i++)
+ {
+ GimpVector2 v;
+
+ gimp_canvas_item_transform_xy_f (item,
+ t_vertices[i].x, t_vertices[i].y,
+ &v.x, &v.y);
+
+ if (i == 0)
+ {
+ top_left = bottom_right = v;
+ }
+ else
+ {
+ top_left.x = MIN (top_left.x, v.x);
+ top_left.y = MIN (top_left.y, v.y);
+
+ bottom_right.x = MAX (bottom_right.x, v.x);
+ bottom_right.y = MAX (bottom_right.y, v.y);
+ }
+ }
+
+ extents.x = (gint) floor (top_left.x - 1.5);
+ extents.y = (gint) floor (top_left.y - 1.5);
+ extents.width = (gint) ceil (bottom_right.x + 1.5) - extents.x;
+ extents.height = (gint) ceil (bottom_right.y + 1.5) - extents.y;
+
+ return cairo_region_create_rectangle (&extents);
+}
+
+GimpCanvasItem *
+gimp_canvas_transform_guides_new (GimpDisplayShell *shell,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpGuidesType type,
+ gint n_guides,
+ gboolean clip)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_TRANSFORM_GUIDES,
+ "shell", shell,
+ "transform", transform,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "type", type,
+ "n-guides", n_guides,
+ "clip", clip,
+ NULL);
+}
+
+void
+gimp_canvas_transform_guides_set (GimpCanvasItem *guides,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpGuidesType type,
+ gint n_guides,
+ gboolean clip)
+{
+ g_return_if_fail (GIMP_IS_CANVAS_TRANSFORM_GUIDES (guides));
+
+ gimp_canvas_item_begin_change (guides);
+
+ g_object_set (guides,
+ "transform", transform,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "type", type,
+ "n-guides", n_guides,
+ "clip", clip,
+ NULL);
+
+ gimp_canvas_item_end_change (guides);
+}
diff --git a/app/display/gimpcanvastransformguides.h b/app/display/gimpcanvastransformguides.h
new file mode 100644
index 0000000..4358501
--- /dev/null
+++ b/app/display/gimpcanvastransformguides.h
@@ -0,0 +1,73 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvastransformguides.h
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_TRANSFORM_GUIDES_H__
+#define __GIMP_CANVAS_TRANSFORM_GUIDES_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_TRANSFORM_GUIDES (gimp_canvas_transform_guides_get_type ())
+#define GIMP_CANVAS_TRANSFORM_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, GimpCanvasTransformGuides))
+#define GIMP_CANVAS_TRANSFORM_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, GimpCanvasTransformGuidesClass))
+#define GIMP_IS_CANVAS_TRANSFORM_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES))
+#define GIMP_IS_CANVAS_TRANSFORM_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES))
+#define GIMP_CANVAS_TRANSFORM_GUIDES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, GimpCanvasTransformGuidesClass))
+
+
+typedef struct _GimpCanvasTransformGuides GimpCanvasTransformGuides;
+typedef struct _GimpCanvasTransformGuidesClass GimpCanvasTransformGuidesClass;
+
+struct _GimpCanvasTransformGuides
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasTransformGuidesClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_transform_guides_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_transform_guides_new (GimpDisplayShell *shell,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpGuidesType type,
+ gint n_guides,
+ gboolean clip);
+
+void gimp_canvas_transform_guides_set (GimpCanvasItem *guides,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpGuidesType type,
+ gint n_guides,
+ gboolean clip);
+
+
+#endif /* __GIMP_CANVAS_TRANSFORM_GUIDES_H__ */
diff --git a/app/display/gimpcanvastransformpreview.c b/app/display/gimpcanvastransformpreview.c
new file mode 100644
index 0000000..8c8d950
--- /dev/null
+++ b/app/display/gimpcanvastransformpreview.c
@@ -0,0 +1,791 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvastransformpreview.c
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display/display-types.h"
+
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimp-gegl-utils.h"
+#include "gegl/gimptilehandlervalidate.h"
+
+#include "core/gimp-transform-resize.h"
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+#include "core/gimpchannel.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer.h"
+#include "core/gimppickable.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvastransformpreview.h"
+#include "gimpdisplayshell.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PICKABLE,
+ PROP_TRANSFORM,
+ PROP_CLIP,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ PROP_OPACITY
+};
+
+
+typedef struct _GimpCanvasTransformPreviewPrivate GimpCanvasTransformPreviewPrivate;
+
+struct _GimpCanvasTransformPreviewPrivate
+{
+ GimpPickable *pickable;
+ GimpMatrix3 transform;
+ GimpTransformResize clip;
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble opacity;
+
+ GeglNode *node;
+ GeglNode *source_node;
+ GeglNode *convert_format_node;
+ GeglNode *layer_mask_source_node;
+ GeglNode *layer_mask_opacity_node;
+ GeglNode *mask_source_node;
+ GeglNode *mask_translate_node;
+ GeglNode *mask_crop_node;
+ GeglNode *opacity_node;
+ GeglNode *cache_node;
+ GeglNode *transform_node;
+
+ GimpPickable *node_pickable;
+ GimpDrawable *node_layer_mask;
+ GimpDrawable *node_mask;
+ GeglRectangle node_rect;
+ gdouble node_opacity;
+ GimpMatrix3 node_matrix;
+ GeglNode *node_output;
+};
+
+#define GET_PRIVATE(transform_preview) \
+ ((GimpCanvasTransformPreviewPrivate *) gimp_canvas_transform_preview_get_instance_private ((GimpCanvasTransformPreview *) (transform_preview)))
+
+
+/* local function prototypes */
+
+static void gimp_canvas_transform_preview_dispose (GObject *object);
+static void gimp_canvas_transform_preview_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_canvas_transform_preview_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_canvas_transform_preview_draw (GimpCanvasItem *item,
+ cairo_t *cr);
+static cairo_region_t * gimp_canvas_transform_preview_get_extents (GimpCanvasItem *item);
+
+static void gimp_canvas_transform_preview_layer_changed (GimpLayer *layer,
+ GimpCanvasTransformPreview *transform_preview);
+
+static void gimp_canvas_transform_preview_set_pickable (GimpCanvasTransformPreview *transform_preview,
+ GimpPickable *pickable);
+static void gimp_canvas_transform_preview_sync_node (GimpCanvasTransformPreview *transform_preview);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasTransformPreview,
+ gimp_canvas_transform_preview,
+ GIMP_TYPE_CANVAS_ITEM)
+
+#define parent_class gimp_canvas_transform_preview_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_canvas_transform_preview_class_init (GimpCanvasTransformPreviewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass);
+
+ object_class->dispose = gimp_canvas_transform_preview_dispose;
+ object_class->set_property = gimp_canvas_transform_preview_set_property;
+ object_class->get_property = gimp_canvas_transform_preview_get_property;
+
+ item_class->draw = gimp_canvas_transform_preview_draw;
+ item_class->get_extents = gimp_canvas_transform_preview_get_extents;
+
+ g_object_class_install_property (object_class, PROP_PICKABLE,
+ g_param_spec_object ("pickable",
+ NULL, NULL,
+ GIMP_TYPE_PICKABLE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TRANSFORM,
+ gimp_param_spec_matrix3 ("transform",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_CLIP,
+ g_param_spec_enum ("clip",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_RESIZE,
+ GIMP_TRANSFORM_RESIZE_ADJUST,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_double ("x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_double ("y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_double ("x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_double ("y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_OPACITY,
+ g_param_spec_double ("opacity",
+ NULL, NULL,
+ 0.0, 1.0, 1.0,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_canvas_transform_preview_init (GimpCanvasTransformPreview *transform_preview)
+{
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (transform_preview);
+
+ private->clip = GIMP_TRANSFORM_RESIZE_ADJUST;
+ private->opacity = 1.0;
+}
+
+static void
+gimp_canvas_transform_preview_dispose (GObject *object)
+{
+ GimpCanvasTransformPreview *transform_preview = GIMP_CANVAS_TRANSFORM_PREVIEW (object);
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->node);
+
+ gimp_canvas_transform_preview_set_pickable (transform_preview, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_canvas_transform_preview_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasTransformPreview *transform_preview = GIMP_CANVAS_TRANSFORM_PREVIEW (object);
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_PICKABLE:
+ gimp_canvas_transform_preview_set_pickable (transform_preview,
+ g_value_get_object (value));
+ break;
+
+ case PROP_TRANSFORM:
+ {
+ GimpMatrix3 *transform = g_value_get_boxed (value);
+
+ if (transform)
+ private->transform = *transform;
+ else
+ gimp_matrix3_identity (&private->transform);
+ }
+ break;
+
+ case PROP_CLIP:
+ private->clip = g_value_get_enum (value);
+ break;
+
+ case PROP_X1:
+ private->x1 = g_value_get_double (value);
+ break;
+
+ case PROP_Y1:
+ private->y1 = g_value_get_double (value);
+ break;
+
+ case PROP_X2:
+ private->x2 = g_value_get_double (value);
+ break;
+
+ case PROP_Y2:
+ private->y2 = g_value_get_double (value);
+ break;
+
+ case PROP_OPACITY:
+ private->opacity = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_canvas_transform_preview_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_PICKABLE:
+ g_value_set_object (value, private->pickable);
+ break;
+
+ case PROP_TRANSFORM:
+ g_value_set_boxed (value, &private->transform);
+ break;
+
+ case PROP_CLIP:
+ g_value_set_enum (value, private->clip);
+ break;
+
+ case PROP_X1:
+ g_value_set_double (value, private->x1);
+ break;
+
+ case PROP_Y1:
+ g_value_set_double (value, private->y1);
+ break;
+
+ case PROP_X2:
+ g_value_set_double (value, private->x2);
+ break;
+
+ case PROP_Y2:
+ g_value_set_double (value, private->y2);
+ break;
+
+ case PROP_OPACITY:
+ g_value_set_double (value, private->opacity);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gimp_canvas_transform_preview_transform (GimpCanvasItem *item,
+ cairo_rectangle_int_t *extents)
+{
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (item);
+ gint x1, y1;
+ gint x2, y2;
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+
+ if (! gimp_transform_resize_boundary (&private->transform,
+ private->clip,
+ private->x1, private->y1,
+ private->x2, private->y2,
+ &x1, &y1,
+ &x2, &y2))
+ {
+ return FALSE;
+ }
+
+ gimp_canvas_item_transform_xy_f (item, x1, y1, &tx1, &ty1);
+ gimp_canvas_item_transform_xy_f (item, x2, y2, &tx2, &ty2);
+
+ extents->x = (gint) floor (tx1);
+ extents->y = (gint) floor (ty1);
+ extents->width = (gint) ceil (tx2) - extents->x;
+ extents->height = (gint) ceil (ty2) - extents->y;
+
+ return TRUE;
+}
+
+static void
+gimp_canvas_transform_preview_draw (GimpCanvasItem *item,
+ cairo_t *cr)
+{
+ GimpCanvasTransformPreview *transform_preview = GIMP_CANVAS_TRANSFORM_PREVIEW (item);
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (item);
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ cairo_rectangle_int_t extents;
+ gdouble clip_x1, clip_y1;
+ gdouble clip_x2, clip_y2;
+ GeglRectangle bounds;
+ cairo_surface_t *surface;
+ guchar *surface_data;
+ gint surface_stride;
+
+ if (! gimp_canvas_transform_preview_transform (item, &extents))
+ return;
+
+ cairo_clip_extents (cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
+
+ clip_x1 = floor (clip_x1);
+ clip_y1 = floor (clip_y1);
+ clip_x2 = ceil (clip_x2);
+ clip_y2 = ceil (clip_y2);
+
+ if (! gegl_rectangle_intersect (&bounds,
+ GEGL_RECTANGLE (extents.x,
+ extents.y,
+ extents.width,
+ extents.height),
+ GEGL_RECTANGLE (clip_x1,
+ clip_y1,
+ clip_x2 - clip_x1,
+ clip_y2 - clip_y1)))
+ {
+ return;
+ }
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ bounds.width, bounds.height);
+
+ g_return_if_fail (surface != NULL);
+
+ surface_data = cairo_image_surface_get_data (surface);
+ surface_stride = cairo_image_surface_get_stride (surface);
+
+ gimp_canvas_transform_preview_sync_node (transform_preview);
+
+ gegl_node_blit (private->node_output, 1.0,
+ GEGL_RECTANGLE (bounds.x + shell->offset_x,
+ bounds.y + shell->offset_y,
+ bounds.width,
+ bounds.height),
+ babl_format ("cairo-ARGB32"), surface_data, surface_stride,
+ GEGL_BLIT_CACHE);
+
+ cairo_surface_mark_dirty (surface);
+
+ cairo_set_source_surface (cr, surface, bounds.x, bounds.y);
+ cairo_rectangle (cr, bounds.x, bounds.y, bounds.width, bounds.height);
+ cairo_fill (cr);
+
+ cairo_surface_destroy (surface);
+}
+
+static cairo_region_t *
+gimp_canvas_transform_preview_get_extents (GimpCanvasItem *item)
+{
+ cairo_rectangle_int_t rectangle;
+
+ if (gimp_canvas_transform_preview_transform (item, &rectangle))
+ return cairo_region_create_rectangle (&rectangle);
+
+ return NULL;
+}
+
+static void
+gimp_canvas_transform_preview_layer_changed (GimpLayer *layer,
+ GimpCanvasTransformPreview *transform_preview)
+{
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (transform_preview);
+
+ gimp_canvas_item_begin_change (item);
+ gimp_canvas_item_end_change (item);
+}
+
+static void
+gimp_canvas_transform_preview_set_pickable (GimpCanvasTransformPreview *transform_preview,
+ GimpPickable *pickable)
+{
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (transform_preview);
+
+ if (private->pickable && GIMP_IS_LAYER (private->pickable))
+ {
+ g_signal_handlers_disconnect_by_func (
+ private->pickable,
+ gimp_canvas_transform_preview_layer_changed,
+ transform_preview);
+ }
+
+ g_set_object (&private->pickable, pickable);
+
+ if (pickable && GIMP_IS_LAYER (pickable))
+ {
+ g_signal_connect (pickable, "opacity-changed",
+ G_CALLBACK (gimp_canvas_transform_preview_layer_changed),
+ transform_preview);
+ g_signal_connect (pickable, "mask-changed",
+ G_CALLBACK (gimp_canvas_transform_preview_layer_changed),
+ transform_preview);
+ g_signal_connect (pickable, "apply-mask-changed",
+ G_CALLBACK (gimp_canvas_transform_preview_layer_changed),
+ transform_preview);
+ g_signal_connect (pickable, "show-mask-changed",
+ G_CALLBACK (gimp_canvas_transform_preview_layer_changed),
+ transform_preview);
+ }
+}
+
+static void
+gimp_canvas_transform_preview_sync_node (GimpCanvasTransformPreview *transform_preview)
+{
+ GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (transform_preview);
+ GimpCanvasItem *item = GIMP_CANVAS_ITEM (transform_preview);
+ GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
+ GimpImage *image = gimp_canvas_item_get_image (item);
+ GimpPickable *pickable = private->pickable;
+ GimpDrawable *layer_mask = NULL;
+ GimpDrawable *mask = NULL;
+ gdouble opacity = private->opacity;
+ gint offset_x = 0;
+ gint offset_y = 0;
+ GimpMatrix3 matrix;
+
+ if (! private->node)
+ {
+ private->node = gegl_node_new ();
+
+ private->source_node =
+ gegl_node_new_child (private->node,
+ "operation", "gimp:buffer-source-validate",
+ NULL);
+
+ private->convert_format_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:convert-format",
+ NULL);
+
+ private->layer_mask_source_node =
+ gegl_node_new_child (private->node,
+ "operation", "gimp:buffer-source-validate",
+ NULL);
+
+ private->layer_mask_opacity_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:opacity",
+ NULL);
+
+ private->mask_source_node =
+ gegl_node_new_child (private->node,
+ "operation", "gimp:buffer-source-validate",
+ NULL);
+
+ private->mask_translate_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:translate",
+ NULL);
+
+ private->mask_crop_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:crop",
+ "width", 0.0,
+ "height", 0.0,
+ NULL);
+
+ private->opacity_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:opacity",
+ NULL);
+
+ private->cache_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:cache",
+ NULL);
+
+ private->transform_node =
+ gegl_node_new_child (private->node,
+ "operation", "gegl:transform",
+ "near-z", GIMP_TRANSFORM_NEAR_Z,
+ "sampler", GIMP_INTERPOLATION_NONE,
+ NULL);
+
+ gegl_node_link_many (private->source_node,
+ private->convert_format_node,
+ private->transform_node,
+ NULL);
+
+ gegl_node_connect_to (private->layer_mask_source_node, "output",
+ private->layer_mask_opacity_node, "aux");
+
+ gegl_node_link_many (private->mask_source_node,
+ private->mask_translate_node,
+ private->mask_crop_node,
+ NULL);
+
+ private->node_pickable = NULL;
+ private->node_layer_mask = NULL;
+ private->node_mask = NULL;
+ private->node_rect = *GEGL_RECTANGLE (0, 0, 0, 0);
+ private->node_opacity = 1.0;
+ gimp_matrix3_identity (&private->node_matrix);
+ private->node_output = private->transform_node;
+ }
+
+ if (GIMP_IS_ITEM (pickable))
+ {
+ gimp_item_get_offset (GIMP_ITEM (private->pickable),
+ &offset_x, &offset_y);
+
+ if (gimp_item_mask_bounds (GIMP_ITEM (pickable),
+ NULL, NULL, NULL, NULL))
+ {
+ mask = GIMP_DRAWABLE (gimp_image_get_mask (image));
+ }
+
+ if (GIMP_IS_LAYER (pickable))
+ {
+ GimpLayer *layer = GIMP_LAYER (pickable);
+
+ opacity *= gimp_layer_get_opacity (layer);
+
+ layer_mask = GIMP_DRAWABLE (gimp_layer_get_mask (layer));
+
+ if (layer_mask)
+ {
+ if (gimp_layer_get_show_mask (layer) && ! mask)
+ {
+ pickable = GIMP_PICKABLE (layer_mask);
+ layer_mask = NULL;
+ }
+ else if (! gimp_layer_get_apply_mask (layer))
+ {
+ layer_mask = NULL;
+ }
+ }
+ }
+ }
+
+ gimp_matrix3_identity (&matrix);
+ gimp_matrix3_translate (&matrix, offset_x, offset_y);
+ gimp_matrix3_mult (&private->transform, &matrix);
+ gimp_matrix3_scale (&matrix, shell->scale_x, shell->scale_y);
+
+ if (pickable != private->node_pickable)
+ {
+ GeglBuffer *buffer;
+
+ gimp_pickable_flush (pickable);
+
+ buffer = gimp_pickable_get_buffer (pickable);
+
+ if (gimp_tile_handler_validate_get_assigned (buffer))
+ buffer = gimp_gegl_buffer_dup (buffer);
+ else
+ buffer = g_object_ref (buffer);
+
+ gegl_node_set (private->source_node,
+ "buffer", buffer,
+ NULL);
+ gegl_node_set (private->convert_format_node,
+ "format", gimp_pickable_get_format_with_alpha (pickable),
+ NULL);
+
+ g_object_unref (buffer);
+ }
+
+ if (layer_mask != private->node_layer_mask)
+ {
+ gegl_node_set (private->layer_mask_source_node,
+ "buffer", layer_mask ?
+ gimp_drawable_get_buffer (layer_mask) :
+ NULL,
+ NULL);
+ }
+
+ if (mask)
+ {
+ GeglRectangle rect;
+
+ rect.x = offset_x;
+ rect.y = offset_y;
+ rect.width = gimp_item_get_width (GIMP_ITEM (private->pickable));
+ rect.height = gimp_item_get_height (GIMP_ITEM (private->pickable));
+
+ if (mask != private->node_mask)
+ {
+ gegl_node_set (private->mask_source_node,
+ "buffer", gimp_drawable_get_buffer (mask),
+ NULL);
+ }
+
+ if (! gegl_rectangle_equal (&rect, &private->node_rect))
+ {
+ private->node_rect = rect;
+
+ gegl_node_set (private->mask_translate_node,
+ "x", (gdouble) -rect.x,
+ "y", (gdouble) -rect.y,
+ NULL);
+
+ gegl_node_set (private->mask_crop_node,
+ "width", (gdouble) rect.width,
+ "height", (gdouble) rect.height,
+ NULL);
+ }
+
+ if (! private->node_mask)
+ {
+ gegl_node_connect_to (private->mask_crop_node, "output",
+ private->opacity_node, "aux");
+ }
+ }
+ else if (private->node_mask)
+ {
+ gegl_node_disconnect (private->opacity_node, "aux");
+ }
+
+ if (opacity != private->node_opacity)
+ {
+ gegl_node_set (private->opacity_node,
+ "value", opacity,
+ NULL);
+ }
+
+ if (layer_mask != private->node_layer_mask ||
+ mask != private->node_mask ||
+ (opacity != 1.0) != (private->node_opacity != 1.0))
+ {
+ GeglNode *output = private->source_node;
+
+ if (layer_mask && ! mask)
+ {
+ gegl_node_link (output, private->layer_mask_opacity_node);
+ output = private->layer_mask_opacity_node;
+ }
+ else
+ {
+ gegl_node_disconnect (private->layer_mask_opacity_node, "input");
+ }
+
+ if (mask || (opacity != 1.0))
+ {
+ gegl_node_link (output, private->opacity_node);
+ output = private->opacity_node;
+ }
+ else
+ {
+ gegl_node_disconnect (private->opacity_node, "input");
+ }
+
+ if (output == private->source_node)
+ {
+ gegl_node_disconnect (private->cache_node, "input");
+
+ gegl_node_link (output, private->convert_format_node);
+ output = private->convert_format_node;
+ }
+ else
+ {
+ gegl_node_disconnect (private->convert_format_node, "input");
+
+ gegl_node_link (output, private->cache_node);
+ output = private->cache_node;
+ }
+
+ gegl_node_link (output, private->transform_node);
+ output = private->transform_node;
+
+ if (layer_mask && mask)
+ {
+ gegl_node_link (output, private->layer_mask_opacity_node);
+ output = private->layer_mask_opacity_node;
+ }
+
+ private->node_output = output;
+ }
+
+ if (memcmp (&matrix, &private->node_matrix, sizeof (matrix)))
+ {
+ private->node_matrix = matrix;
+
+ gimp_gegl_node_set_matrix (private->transform_node, &matrix);
+ }
+
+ private->node_pickable = pickable;
+ private->node_layer_mask = layer_mask;
+ private->node_mask = mask;
+ private->node_opacity = opacity;
+}
+
+
+/* public functions */
+
+
+GimpCanvasItem *
+gimp_canvas_transform_preview_new (GimpDisplayShell *shell,
+ GimpPickable *pickable,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+ g_return_val_if_fail (transform != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW,
+ "shell", shell,
+ "pickable", pickable,
+ "transform", transform,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+}
diff --git a/app/display/gimpcanvastransformpreview.h b/app/display/gimpcanvastransformpreview.h
new file mode 100644
index 0000000..a82d50c
--- /dev/null
+++ b/app/display/gimpcanvastransformpreview.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcanvastransformpreview.h
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CANVAS_TRANSFORM_PREVIEW_H__
+#define __GIMP_CANVAS_TRANSFORM_PREVIEW_H__
+
+
+#include "gimpcanvasitem.h"
+
+
+#define GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW (gimp_canvas_transform_preview_get_type ())
+#define GIMP_CANVAS_TRANSFORM_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, GimpCanvasTransformPreview))
+#define GIMP_CANVAS_TRANSFORM_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, GimpCanvasTransformPreviewClass))
+#define GIMP_IS_CANVAS_TRANSFORM_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW))
+#define GIMP_IS_CANVAS_TRANSFORM_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW))
+#define GIMP_CANVAS_TRANSFORM_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, GimpCanvasTransformPreviewClass))
+
+
+typedef struct _GimpCanvasTransformPreview GimpCanvasTransformPreview;
+typedef struct _GimpCanvasTransformPreviewClass GimpCanvasTransformPreviewClass;
+
+struct _GimpCanvasTransformPreview
+{
+ GimpCanvasItem parent_instance;
+};
+
+struct _GimpCanvasTransformPreviewClass
+{
+ GimpCanvasItemClass parent_class;
+};
+
+
+GType gimp_canvas_transform_preview_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_canvas_transform_preview_new (GimpDisplayShell *shell,
+ GimpPickable *pickable,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+
+#endif /* __GIMP_CANVAS_TRANSFORM_PREVIEW_H__ */
diff --git a/app/display/gimpcursorview.c b/app/display/gimpcursorview.c
new file mode 100644
index 0000000..846df91
--- /dev/null
+++ b/app/display/gimpcursorview.c
@@ -0,0 +1,887 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcursorview.c
+ * Copyright (C) 2005-2016 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-pick-color.h"
+#include "core/gimpitem.h"
+
+#include "widgets/gimpcolorframe.h"
+#include "widgets/gimpdocked.h"
+#include "widgets/gimpmenufactory.h"
+#include "widgets/gimpsessioninfo-aux.h"
+
+#include "gimpcursorview.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SAMPLE_MERGED
+};
+
+
+struct _GimpCursorViewPrivate
+{
+ GimpEditor parent_instance;
+
+ GtkWidget *coord_hbox;
+ GtkWidget *selection_hbox;
+ GtkWidget *color_hbox;
+
+ GtkWidget *pixel_x_label;
+ GtkWidget *pixel_y_label;
+ GtkWidget *unit_x_label;
+ GtkWidget *unit_y_label;
+ GtkWidget *selection_x_label;
+ GtkWidget *selection_y_label;
+ GtkWidget *selection_width_label;
+ GtkWidget *selection_height_label;
+ GtkWidget *color_frame_1;
+ GtkWidget *color_frame_2;
+
+ gboolean sample_merged;
+
+ GimpContext *context;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpUnit unit;
+
+ guint cursor_idle_id;
+ GimpImage *cursor_image;
+ GimpUnit cursor_unit;
+ gdouble cursor_x;
+ gdouble cursor_y;
+};
+
+
+static void gimp_cursor_view_docked_iface_init (GimpDockedInterface *iface);
+
+static void gimp_cursor_view_dispose (GObject *object);
+static void gimp_cursor_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_cursor_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_cursor_view_style_set (GtkWidget *widget,
+ GtkStyle *prev_style);
+
+static void gimp_cursor_view_set_aux_info (GimpDocked *docked,
+ GList *aux_info);
+static GList * gimp_cursor_view_get_aux_info (GimpDocked *docked);
+
+static void gimp_cursor_view_set_context (GimpDocked *docked,
+ GimpContext *context);
+static void gimp_cursor_view_image_changed (GimpCursorView *view,
+ GimpImage *image,
+ GimpContext *context);
+static void gimp_cursor_view_mask_changed (GimpCursorView *view,
+ GimpImage *image);
+static void gimp_cursor_view_diplay_changed (GimpCursorView *view,
+ GimpDisplay *display,
+ GimpContext *context);
+static void gimp_cursor_view_shell_unit_changed (GimpCursorView *view,
+ GParamSpec *pspec,
+ GimpDisplayShell *shell);
+static void gimp_cursor_view_format_as_unit (GimpUnit unit,
+ gchar *output_buf,
+ gint output_buf_size,
+ gdouble pixel_value,
+ gdouble image_res);
+static void gimp_cursor_view_set_label_italic (GtkWidget *label,
+ gboolean italic);
+static void gimp_cursor_view_update_selection_info (GimpCursorView *view,
+ GimpImage *image,
+ GimpUnit unit);
+static gboolean gimp_cursor_view_cursor_idle (GimpCursorView *view);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpCursorView, gimp_cursor_view, GIMP_TYPE_EDITOR,
+ G_ADD_PRIVATE (GimpCursorView)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
+ gimp_cursor_view_docked_iface_init))
+
+#define parent_class gimp_cursor_view_parent_class
+
+static GimpDockedInterface *parent_docked_iface = NULL;
+
+
+static void
+gimp_cursor_view_class_init (GimpCursorViewClass* klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gimp_cursor_view_dispose;
+ object_class->get_property = gimp_cursor_view_get_property;
+ object_class->set_property = gimp_cursor_view_set_property;
+
+ widget_class->style_set = gimp_cursor_view_style_set;
+
+ g_object_class_install_property (object_class, PROP_SAMPLE_MERGED,
+ g_param_spec_boolean ("sample-merged",
+ NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_cursor_view_init (GimpCursorView *view)
+{
+ GtkWidget *frame;
+ GtkWidget *table;
+ GtkWidget *toggle;
+ gint content_spacing;
+
+ view->priv = gimp_cursor_view_get_instance_private (view);
+
+ view->priv->sample_merged = TRUE;
+ view->priv->context = NULL;
+ view->priv->shell = NULL;
+ view->priv->image = NULL;
+ view->priv->unit = GIMP_UNIT_PIXEL;
+ view->priv->cursor_idle_id = 0;
+
+ gtk_widget_style_get (GTK_WIDGET (view),
+ "content-spacing", &content_spacing,
+ NULL);
+
+
+ /* cursor information */
+
+ view->priv->coord_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL,
+ content_spacing);
+ gtk_box_set_homogeneous (GTK_BOX (view->priv->coord_hbox), TRUE);
+ gtk_box_pack_start (GTK_BOX (view), view->priv->coord_hbox,
+ FALSE, FALSE, 0);
+ gtk_widget_show (view->priv->coord_hbox);
+
+ view->priv->selection_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL,
+ content_spacing);
+ gtk_box_set_homogeneous (GTK_BOX (view->priv->selection_hbox), TRUE);
+ gtk_box_pack_start (GTK_BOX (view), view->priv->selection_hbox,
+ FALSE, FALSE, 0);
+ gtk_widget_show (view->priv->selection_hbox);
+
+
+ /* Pixels */
+
+ frame = gimp_frame_new (_("Pixels"));
+ gtk_box_pack_start (GTK_BOX (view->priv->coord_hbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ view->priv->pixel_x_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->pixel_x_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
+ _("X"), 0.5, 0.5,
+ view->priv->pixel_x_label, 1, FALSE);
+
+ view->priv->pixel_y_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->pixel_y_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ _("Y"), 0.5, 0.5,
+ view->priv->pixel_y_label, 1, FALSE);
+
+
+ /* Units */
+
+ frame = gimp_frame_new (_("Units"));
+ gtk_box_pack_start (GTK_BOX (view->priv->coord_hbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ view->priv->unit_x_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->unit_x_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
+ _("X"), 0.5, 0.5,
+ view->priv->unit_x_label, 1, FALSE);
+
+ view->priv->unit_y_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->unit_y_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ _("Y"), 0.5, 0.5,
+ view->priv->unit_y_label, 1, FALSE);
+
+
+ /* Selection Bounding Box */
+
+ frame = gimp_frame_new (_("Selection"));
+ gtk_box_pack_start (GTK_BOX (view->priv->selection_hbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ gimp_help_set_help_data (frame, _("The selection's bounding box"), NULL);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ view->priv->selection_x_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->selection_x_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
+ _("X"), 0.5, 0.5,
+ view->priv->selection_x_label, 1, FALSE);
+
+ view->priv->selection_y_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->selection_y_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ _("Y"), 0.5, 0.5,
+ view->priv->selection_y_label, 1, FALSE);
+
+ frame = gimp_frame_new ("");
+ gtk_box_pack_start (GTK_BOX (view->priv->selection_hbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ view->priv->selection_width_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->selection_width_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
+ /* Width */
+ _("W"), 0.5, 0.5,
+ view->priv->selection_width_label, 1, FALSE);
+
+ view->priv->selection_height_label = gtk_label_new (_("n/a"));
+ gtk_label_set_xalign (GTK_LABEL (view->priv->selection_height_label), 1.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ /* Height */
+ _("H"), 0.5, 0.5,
+ view->priv->selection_height_label, 1, FALSE);
+
+
+ /* color information */
+
+ view->priv->color_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL,
+ content_spacing);
+ gtk_box_set_homogeneous (GTK_BOX (view->priv->color_hbox), TRUE);
+ gtk_box_pack_start (GTK_BOX (view), view->priv->color_hbox, FALSE, FALSE, 0);
+ gtk_widget_show (view->priv->color_hbox);
+
+ view->priv->color_frame_1 = gimp_color_frame_new ();
+ gimp_color_frame_set_mode (GIMP_COLOR_FRAME (view->priv->color_frame_1),
+ GIMP_COLOR_PICK_MODE_PIXEL);
+ gimp_color_frame_set_ellipsize (GIMP_COLOR_FRAME (view->priv->color_frame_1),
+ PANGO_ELLIPSIZE_END);
+ gtk_box_pack_start (GTK_BOX (view->priv->color_hbox), view->priv->color_frame_1,
+ TRUE, TRUE, 0);
+ gtk_widget_show (view->priv->color_frame_1);
+
+ view->priv->color_frame_2 = gimp_color_frame_new ();
+ gimp_color_frame_set_mode (GIMP_COLOR_FRAME (view->priv->color_frame_2),
+ GIMP_COLOR_PICK_MODE_RGB_PERCENT);
+ gtk_box_pack_start (GTK_BOX (view->priv->color_hbox), view->priv->color_frame_2,
+ TRUE, TRUE, 0);
+ gtk_widget_show (view->priv->color_frame_2);
+
+ /* sample merged toggle */
+
+ toggle = gimp_prop_check_button_new (G_OBJECT (view), "sample-merged",
+ _("_Sample Merged"));
+ gtk_box_pack_start (GTK_BOX (view), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+}
+
+static void
+gimp_cursor_view_docked_iface_init (GimpDockedInterface *iface)
+{
+ parent_docked_iface = g_type_interface_peek_parent (iface);
+
+ if (! parent_docked_iface)
+ parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED);
+
+ iface->set_aux_info = gimp_cursor_view_set_aux_info;
+ iface->get_aux_info = gimp_cursor_view_get_aux_info;
+ iface->set_context = gimp_cursor_view_set_context;
+}
+
+static void
+gimp_cursor_view_dispose (GObject *object)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (object);
+
+ if (view->priv->context)
+ gimp_docked_set_context (GIMP_DOCKED (view), NULL);
+
+ if (view->priv->cursor_idle_id)
+ {
+ g_source_remove (view->priv->cursor_idle_id);
+ view->priv->cursor_idle_id = 0;
+ }
+
+ g_clear_object (&view->priv->cursor_image);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_cursor_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (object);
+
+ switch (property_id)
+ {
+ case PROP_SAMPLE_MERGED:
+ view->priv->sample_merged = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_cursor_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (object);
+
+ switch (property_id)
+ {
+ case PROP_SAMPLE_MERGED:
+ g_value_set_boolean (value, view->priv->sample_merged);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+#define AUX_INFO_FRAME_1_MODE "frame-1-mode"
+#define AUX_INFO_FRAME_2_MODE "frame-2-mode"
+
+static void
+gimp_cursor_view_set_aux_info (GimpDocked *docked,
+ GList *aux_info)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (docked);
+ GList *list;
+
+ parent_docked_iface->set_aux_info (docked, aux_info);
+
+ for (list = aux_info; list; list = g_list_next (list))
+ {
+ GimpSessionInfoAux *aux = list->data;
+ GtkWidget *frame = NULL;
+
+ if (! strcmp (aux->name, AUX_INFO_FRAME_1_MODE))
+ frame = view->priv->color_frame_1;
+ else if (! strcmp (aux->name, AUX_INFO_FRAME_2_MODE))
+ frame = view->priv->color_frame_2;
+
+ if (frame)
+ {
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+
+ enum_class = g_type_class_peek (GIMP_TYPE_COLOR_PICK_MODE);
+ enum_value = g_enum_get_value_by_nick (enum_class, aux->value);
+
+ if (enum_value)
+ gimp_color_frame_set_mode (GIMP_COLOR_FRAME (frame),
+ enum_value->value);
+ }
+ }
+}
+
+static GList *
+gimp_cursor_view_get_aux_info (GimpDocked *docked)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (docked);
+ GList *aux_info;
+ const gchar *nick;
+ GimpSessionInfoAux *aux;
+
+ aux_info = parent_docked_iface->get_aux_info (docked);
+
+ if (gimp_enum_get_value (GIMP_TYPE_COLOR_PICK_MODE,
+ GIMP_COLOR_FRAME (view->priv->color_frame_1)->pick_mode,
+ NULL, &nick, NULL, NULL))
+ {
+ aux = gimp_session_info_aux_new (AUX_INFO_FRAME_1_MODE, nick);
+ aux_info = g_list_append (aux_info, aux);
+ }
+
+ if (gimp_enum_get_value (GIMP_TYPE_COLOR_PICK_MODE,
+ GIMP_COLOR_FRAME (view->priv->color_frame_2)->pick_mode,
+ NULL, &nick, NULL, NULL))
+ {
+ aux = gimp_session_info_aux_new (AUX_INFO_FRAME_2_MODE, nick);
+ aux_info = g_list_append (aux_info, aux);
+ }
+
+ return aux_info;
+}
+
+static void
+gimp_cursor_view_format_as_unit (GimpUnit unit,
+ gchar *output_buf,
+ gint output_buf_size,
+ gdouble pixel_value,
+ gdouble image_res)
+{
+ gchar format_buf[32];
+ gdouble value;
+ gint unit_digits = 0;
+ const gchar *unit_str = "";
+
+ value = gimp_pixels_to_units (pixel_value, unit, image_res);
+
+ if (unit != GIMP_UNIT_PIXEL)
+ {
+ unit_digits = gimp_unit_get_scaled_digits (unit, image_res);
+ unit_str = gimp_unit_get_abbreviation (unit);
+ }
+
+ g_snprintf (format_buf, sizeof (format_buf),
+ "%%.%df %s", unit_digits, unit_str);
+
+ g_snprintf (output_buf, output_buf_size, format_buf, value);
+}
+
+static void
+gimp_cursor_view_set_label_italic (GtkWidget *label,
+ gboolean italic)
+{
+ PangoStyle attribute = italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL;
+
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, attribute,
+ -1);
+}
+
+static void
+gimp_cursor_view_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (widget);
+ gint content_spacing;
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
+
+ gtk_widget_style_get (GTK_WIDGET (view),
+ "content-spacing", &content_spacing,
+ NULL);
+
+ gtk_box_set_spacing (GTK_BOX (view->priv->coord_hbox), content_spacing);
+ gtk_box_set_spacing (GTK_BOX (view->priv->selection_hbox), content_spacing);
+ gtk_box_set_spacing (GTK_BOX (view->priv->color_hbox), content_spacing);
+}
+
+static void
+gimp_cursor_view_set_context (GimpDocked *docked,
+ GimpContext *context)
+{
+ GimpCursorView *view = GIMP_CURSOR_VIEW (docked);
+ GimpColorConfig *config = NULL;
+ GimpDisplay *display = NULL;
+ GimpImage *image = NULL;
+
+ if (context == view->priv->context)
+ return;
+
+ if (view->priv->context)
+ {
+ g_signal_handlers_disconnect_by_func (view->priv->context,
+ gimp_cursor_view_diplay_changed,
+ view);
+ g_signal_handlers_disconnect_by_func (view->priv->context,
+ gimp_cursor_view_image_changed,
+ view);
+
+ g_object_unref (view->priv->context);
+ }
+
+ view->priv->context = context;
+
+ if (view->priv->context)
+ {
+ g_object_ref (view->priv->context);
+
+ g_signal_connect_swapped (view->priv->context, "display-changed",
+ G_CALLBACK (gimp_cursor_view_diplay_changed),
+ view);
+
+ g_signal_connect_swapped (view->priv->context, "image-changed",
+ G_CALLBACK (gimp_cursor_view_image_changed),
+ view);
+
+ config = context->gimp->config->color_management;
+ display = gimp_context_get_display (context);
+ image = gimp_context_get_image (context);
+ }
+
+ gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (view->priv->color_frame_1),
+ config);
+ gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (view->priv->color_frame_2),
+ config);
+
+ gimp_cursor_view_diplay_changed (view, display, view->priv->context);
+ gimp_cursor_view_image_changed (view, image, view->priv->context);
+}
+
+static void
+gimp_cursor_view_image_changed (GimpCursorView *view,
+ GimpImage *image,
+ GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CURSOR_VIEW (view));
+
+ if (image == view->priv->image)
+ return;
+
+ if (view->priv->image)
+ {
+ g_signal_handlers_disconnect_by_func (view->priv->image,
+ gimp_cursor_view_mask_changed,
+ view);
+ }
+
+ view->priv->image = image;
+
+ if (view->priv->image)
+ {
+ g_signal_connect_swapped (view->priv->image, "mask-changed",
+ G_CALLBACK (gimp_cursor_view_mask_changed),
+ view);
+ }
+
+ gimp_cursor_view_mask_changed (view, view->priv->image);
+}
+
+static void
+gimp_cursor_view_mask_changed (GimpCursorView *view,
+ GimpImage *image)
+{
+ gimp_cursor_view_update_selection_info (view,
+ view->priv->image,
+ view->priv->unit);
+}
+
+static void
+gimp_cursor_view_diplay_changed (GimpCursorView *view,
+ GimpDisplay *display,
+ GimpContext *context)
+{
+ GimpDisplayShell *shell = NULL;
+
+ if (display)
+ shell = gimp_display_get_shell (display);
+
+ if (view->priv->shell)
+ {
+ g_signal_handlers_disconnect_by_func (view->priv->shell,
+ gimp_cursor_view_shell_unit_changed,
+ view);
+ }
+
+ view->priv->shell = shell;
+
+ if (view->priv->shell)
+ {
+ g_signal_connect_swapped (view->priv->shell, "notify::unit",
+ G_CALLBACK (gimp_cursor_view_shell_unit_changed),
+ view);
+ }
+
+ gimp_cursor_view_shell_unit_changed (view,
+ NULL,
+ view->priv->shell);
+}
+
+static void
+gimp_cursor_view_shell_unit_changed (GimpCursorView *view,
+ GParamSpec *pspec,
+ GimpDisplayShell *shell)
+{
+ GimpUnit new_unit = GIMP_UNIT_PIXEL;
+
+ if (shell)
+ {
+ new_unit = gimp_display_shell_get_unit (shell);
+ }
+
+ if (view->priv->unit != new_unit)
+ {
+ gimp_cursor_view_update_selection_info (view, view->priv->image, new_unit);
+ view->priv->unit = new_unit;
+ }
+}
+
+static void
+gimp_cursor_view_update_selection_info (GimpCursorView *view,
+ GimpImage *image,
+ GimpUnit unit)
+{
+ gint x, y, width, height;
+
+ if (image &&
+ gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ &x, &y, &width, &height))
+ {
+ gdouble xres, yres;
+ gchar buf[32];
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), x, xres);
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_x_label), buf);
+
+ gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), y, yres);
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_y_label), buf);
+
+ gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), width, xres);
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_width_label), buf);
+
+ gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), height, yres);
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_height_label), buf);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_x_label),
+ _("n/a"));
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_y_label),
+ _("n/a"));
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_width_label),
+ _("n/a"));
+ gtk_label_set_text (GTK_LABEL (view->priv->selection_height_label),
+ _("n/a"));
+ }
+}
+
+static gboolean
+gimp_cursor_view_cursor_idle (GimpCursorView *view)
+{
+
+ if (view->priv->cursor_image)
+ {
+ GimpImage *image = view->priv->cursor_image;
+ GimpUnit unit = view->priv->cursor_unit;
+ gdouble x = view->priv->cursor_x;
+ gdouble y = view->priv->cursor_y;
+ gboolean in_image;
+ gchar buf[32];
+ const Babl *sample_format;
+ gdouble pixel[4];
+ GimpRGB color;
+ gdouble xres;
+ gdouble yres;
+ gint int_x;
+ gint int_y;
+
+ if (unit == GIMP_UNIT_PIXEL)
+ unit = gimp_image_get_unit (image);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ in_image = (x >= 0.0 && x < gimp_image_get_width (image) &&
+ y >= 0.0 && y < gimp_image_get_height (image));
+
+ g_snprintf (buf, sizeof (buf), "%d", (gint) floor (x));
+ gtk_label_set_text (GTK_LABEL (view->priv->pixel_x_label), buf);
+ gimp_cursor_view_set_label_italic (view->priv->pixel_x_label, ! in_image);
+
+ g_snprintf (buf, sizeof (buf), "%d", (gint) floor (y));
+ gtk_label_set_text (GTK_LABEL (view->priv->pixel_y_label), buf);
+ gimp_cursor_view_set_label_italic (view->priv->pixel_y_label, ! in_image);
+
+ gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), x, xres);
+ gtk_label_set_text (GTK_LABEL (view->priv->unit_x_label), buf);
+ gimp_cursor_view_set_label_italic (view->priv->unit_x_label, ! in_image);
+
+ gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), y, yres);
+ gtk_label_set_text (GTK_LABEL (view->priv->unit_y_label), buf);
+ gimp_cursor_view_set_label_italic (view->priv->unit_y_label, ! in_image);
+
+ int_x = (gint) floor (x);
+ int_y = (gint) floor (y);
+
+ if (gimp_image_pick_color (image, NULL,
+ int_x, int_y,
+ view->priv->shell->show_all,
+ view->priv->sample_merged,
+ FALSE, 0.0,
+ &sample_format, pixel, &color))
+ {
+ gimp_color_frame_set_color (GIMP_COLOR_FRAME (view->priv->color_frame_1),
+ FALSE, sample_format, pixel, &color,
+ int_x, int_y);
+ gimp_color_frame_set_color (GIMP_COLOR_FRAME (view->priv->color_frame_2),
+ FALSE, sample_format, pixel, &color,
+ int_x, int_y);
+ }
+ else
+ {
+ gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_1));
+ gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_2));
+ }
+
+ /* Show the selection info from the image under the cursor if any */
+ gimp_cursor_view_update_selection_info (view,
+ image,
+ view->priv->cursor_unit);
+
+ g_clear_object (&view->priv->cursor_image);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (view->priv->pixel_x_label), _("n/a"));
+ gtk_label_set_text (GTK_LABEL (view->priv->pixel_y_label), _("n/a"));
+ gtk_label_set_text (GTK_LABEL (view->priv->unit_x_label), _("n/a"));
+ gtk_label_set_text (GTK_LABEL (view->priv->unit_y_label), _("n/a"));
+
+ gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_1));
+ gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_2));
+
+ /* Start showing selection info from the active image again */
+ gimp_cursor_view_update_selection_info (view,
+ view->priv->image,
+ view->priv->unit);
+ }
+
+ view->priv->cursor_idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+
+/* public functions */
+
+GtkWidget *
+gimp_cursor_view_new (GimpMenuFactory *menu_factory)
+{
+ g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL);
+
+ return g_object_new (GIMP_TYPE_CURSOR_VIEW,
+ "menu-factory", menu_factory,
+ "menu-identifier", "<CursorInfo>",
+ "ui-path", "/cursor-info-popup",
+ NULL);
+}
+
+void
+gimp_cursor_view_set_sample_merged (GimpCursorView *view,
+ gboolean sample_merged)
+{
+ g_return_if_fail (GIMP_IS_CURSOR_VIEW (view));
+
+ sample_merged = sample_merged ? TRUE : FALSE;
+
+ if (view->priv->sample_merged != sample_merged)
+ {
+ view->priv->sample_merged = sample_merged;
+
+ g_object_notify (G_OBJECT (view), "sample-merged");
+ }
+}
+
+gboolean
+gimp_cursor_view_get_sample_merged (GimpCursorView *view)
+{
+ g_return_val_if_fail (GIMP_IS_CURSOR_VIEW (view), FALSE);
+
+ return view->priv->sample_merged;
+}
+
+void
+gimp_cursor_view_update_cursor (GimpCursorView *view,
+ GimpImage *image,
+ GimpUnit shell_unit,
+ gdouble x,
+ gdouble y)
+{
+ g_return_if_fail (GIMP_IS_CURSOR_VIEW (view));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_clear_object (&view->priv->cursor_image);
+
+ view->priv->cursor_image = g_object_ref (image);
+ view->priv->cursor_unit = shell_unit;
+ view->priv->cursor_x = x;
+ view->priv->cursor_y = y;
+
+ if (view->priv->cursor_idle_id == 0)
+ {
+ view->priv->cursor_idle_id =
+ g_idle_add ((GSourceFunc) gimp_cursor_view_cursor_idle, view);
+ }
+}
+
+void
+gimp_cursor_view_clear_cursor (GimpCursorView *view)
+{
+ g_return_if_fail (GIMP_IS_CURSOR_VIEW (view));
+
+ g_clear_object (&view->priv->cursor_image);
+
+ if (view->priv->cursor_idle_id == 0)
+ {
+ view->priv->cursor_idle_id =
+ g_idle_add ((GSourceFunc) gimp_cursor_view_cursor_idle, view);
+ }
+}
diff --git a/app/display/gimpcursorview.h b/app/display/gimpcursorview.h
new file mode 100644
index 0000000..f538041
--- /dev/null
+++ b/app/display/gimpcursorview.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcursorview.h
+ * Copyright (C) 2005-2016 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_CURSOR_VIEW_H__
+#define __GIMP_CURSOR_VIEW_H__
+
+
+#include "widgets/gimpeditor.h"
+
+
+#define GIMP_TYPE_CURSOR_VIEW (gimp_cursor_view_get_type ())
+#define GIMP_CURSOR_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CURSOR_VIEW, GimpCursorView))
+#define GIMP_CURSOR_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CURSOR_VIEW, GimpCursorViewClass))
+#define GIMP_IS_CURSOR_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CURSOR_VIEW))
+#define GIMP_IS_CURSOR_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CURSOR_VIEW))
+#define GIMP_CURSOR_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CURSOR_VIEW, GimpCursorViewClass))
+
+
+typedef struct _GimpCursorViewClass GimpCursorViewClass;
+typedef struct _GimpCursorViewPrivate GimpCursorViewPrivate;
+
+struct _GimpCursorView
+{
+ GimpEditor parent_instance;
+
+ GimpCursorViewPrivate *priv;
+};
+
+struct _GimpCursorViewClass
+{
+ GimpEditorClass parent_class;
+};
+
+
+GType gimp_cursor_view_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_cursor_view_new (GimpMenuFactory *menu_factory);
+
+void gimp_cursor_view_set_sample_merged (GimpCursorView *view,
+ gboolean sample_merged);
+gboolean gimp_cursor_view_get_sample_merged (GimpCursorView *view);
+
+void gimp_cursor_view_update_cursor (GimpCursorView *view,
+ GimpImage *image,
+ GimpUnit shell_unit,
+ gdouble x,
+ gdouble y);
+void gimp_cursor_view_clear_cursor (GimpCursorView *view);
+
+
+#endif /* __GIMP_CURSOR_VIEW_H__ */
diff --git a/app/display/gimpdisplay-foreach.c b/app/display/gimpdisplay-foreach.c
new file mode 100644
index 0000000..440113e
--- /dev/null
+++ b/app/display/gimpdisplay-foreach.c
@@ -0,0 +1,308 @@
+/* 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 "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimplist.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplay-foreach.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-cursor.h"
+
+
+gboolean
+gimp_displays_dirty (Gimp *gimp)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ for (list = gimp_get_display_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplay *display = list->data;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (image && gimp_image_is_dirty (image))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_displays_image_dirty_callback (GimpImage *image,
+ GimpDirtyMask dirty_mask,
+ GimpContainer *container)
+{
+ if (gimp_image_is_dirty (image) &&
+ gimp_image_get_display_count (image) > 0 &&
+ ! gimp_container_have (container, GIMP_OBJECT (image)))
+ gimp_container_add (container, GIMP_OBJECT (image));
+}
+
+static void
+gimp_displays_dirty_images_disconnect (GimpContainer *dirty_container,
+ GimpContainer *global_container)
+{
+ GQuark handler;
+
+ handler = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dirty_container),
+ "clean-handler"));
+ gimp_container_remove_handler (global_container, handler);
+
+ handler = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dirty_container),
+ "dirty-handler"));
+ gimp_container_remove_handler (global_container, handler);
+}
+
+static void
+gimp_displays_image_clean_callback (GimpImage *image,
+ GimpDirtyMask dirty_mask,
+ GimpContainer *container)
+{
+ if (! gimp_image_is_dirty (image))
+ gimp_container_remove (container, GIMP_OBJECT (image));
+}
+
+GimpContainer *
+gimp_displays_get_dirty_images (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp_displays_dirty (gimp))
+ {
+ GimpContainer *container = gimp_list_new_weak (GIMP_TYPE_IMAGE, FALSE);
+ GList *list;
+ GQuark handler;
+
+ handler =
+ gimp_container_add_handler (gimp->images, "clean",
+ G_CALLBACK (gimp_displays_image_dirty_callback),
+ container);
+ g_object_set_data (G_OBJECT (container), "clean-handler",
+ GINT_TO_POINTER (handler));
+
+ handler =
+ gimp_container_add_handler (gimp->images, "dirty",
+ G_CALLBACK (gimp_displays_image_dirty_callback),
+ container);
+ g_object_set_data (G_OBJECT (container), "dirty-handler",
+ GINT_TO_POINTER (handler));
+
+ g_signal_connect_object (container, "disconnect",
+ G_CALLBACK (gimp_displays_dirty_images_disconnect),
+ G_OBJECT (gimp->images), 0);
+
+ gimp_container_add_handler (container, "clean",
+ G_CALLBACK (gimp_displays_image_clean_callback),
+ container);
+ gimp_container_add_handler (container, "dirty",
+ G_CALLBACK (gimp_displays_image_clean_callback),
+ container);
+
+ for (list = gimp_get_image_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpImage *image = list->data;
+
+ if (gimp_image_is_dirty (image) &&
+ gimp_image_get_display_count (image) > 0)
+ gimp_container_add (container, GIMP_OBJECT (image));
+ }
+
+ return container;
+ }
+
+ return NULL;
+}
+
+/**
+ * gimp_displays_delete:
+ * @gimp:
+ *
+ * Calls gimp_display_delete() an all displays in the display list.
+ * This closes all displays, including the first one which is usually
+ * kept open.
+ */
+void
+gimp_displays_delete (Gimp *gimp)
+{
+ /* this removes the GimpDisplay from the list, so do a while loop
+ * "around" the first element to get them all
+ */
+ while (! gimp_container_is_empty (gimp->displays))
+ {
+ GimpDisplay *display = gimp_get_display_iter (gimp)->data;
+
+ gimp_display_delete (display);
+ }
+}
+
+/**
+ * gimp_displays_close:
+ * @gimp:
+ *
+ * Calls gimp_display_close() an all displays in the display list. The
+ * first display will remain open without an image.
+ */
+void
+gimp_displays_close (Gimp *gimp)
+{
+ GList *list;
+ GList *iter;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ list = g_list_copy (gimp_get_display_iter (gimp));
+
+ for (iter = list; iter; iter = g_list_next (iter))
+ {
+ GimpDisplay *display = iter->data;
+
+ gimp_display_close (display);
+ }
+
+ g_list_free (list);
+}
+
+void
+gimp_displays_reconnect (Gimp *gimp,
+ GimpImage *old,
+ GimpImage *new)
+{
+ GList *contexts = NULL;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_IMAGE (old));
+ g_return_if_fail (GIMP_IS_IMAGE (new));
+
+ /* check which contexts refer to old_image */
+ for (list = gimp->context_list; list; list = g_list_next (list))
+ {
+ GimpContext *context = list->data;
+
+ if (gimp_context_get_image (context) == old)
+ contexts = g_list_prepend (contexts, list->data);
+ }
+
+ /* set the new_image on the remembered contexts (in reverse order,
+ * since older contexts are usually the parents of newer
+ * ones). Also, update the contexts before the displays, or we
+ * might run into menu update functions that would see an
+ * inconsistent state (display = new, context = old), and thus
+ * inadvertently call actions as if the user had selected a menu
+ * item.
+ */
+ g_list_foreach (contexts, (GFunc) gimp_context_set_image, new);
+ g_list_free (contexts);
+
+ for (list = gimp_get_display_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplay *display = list->data;
+
+ if (gimp_display_get_image (display) == old)
+ gimp_display_set_image (display, new);
+ }
+}
+
+gint
+gimp_displays_get_num_visible (Gimp *gimp)
+{
+ GList *list;
+ gint visible = 0;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), 0);
+
+ for (list = gimp_get_display_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplay *display = list->data;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ if (gtk_widget_is_drawable (GTK_WIDGET (shell)))
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
+
+ if (GTK_IS_WINDOW (toplevel))
+ {
+ GdkWindow *window = gtk_widget_get_window (toplevel);
+ GdkWindowState state = gdk_window_get_state (window);
+
+ if ((state & (GDK_WINDOW_STATE_WITHDRAWN |
+ GDK_WINDOW_STATE_ICONIFIED)) == 0)
+ {
+ visible++;
+ }
+ }
+ }
+ }
+
+ return visible;
+}
+
+void
+gimp_displays_set_busy (Gimp *gimp)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ for (list = gimp_get_display_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplayShell *shell =
+ gimp_display_get_shell (GIMP_DISPLAY (list->data));
+
+ gimp_display_shell_set_override_cursor (shell, (GimpCursorType) GDK_WATCH);
+ }
+}
+
+void
+gimp_displays_unset_busy (Gimp *gimp)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ for (list = gimp_get_display_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplayShell *shell =
+ gimp_display_get_shell (GIMP_DISPLAY (list->data));
+
+ gimp_display_shell_unset_override_cursor (shell);
+ }
+}
diff --git a/app/display/gimpdisplay-foreach.h b/app/display/gimpdisplay-foreach.h
new file mode 100644
index 0000000..09fae7c
--- /dev/null
+++ b/app/display/gimpdisplay-foreach.h
@@ -0,0 +1,36 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_FOREACH_H__
+#define __GIMP_DISPLAY_FOREACH_H__
+
+
+gboolean gimp_displays_dirty (Gimp *gimp);
+GimpContainer * gimp_displays_get_dirty_images (Gimp *gimp);
+void gimp_displays_delete (Gimp *gimp);
+void gimp_displays_close (Gimp *gimp);
+void gimp_displays_reconnect (Gimp *gimp,
+ GimpImage *old,
+ GimpImage *new);
+
+gint gimp_displays_get_num_visible (Gimp *gimp);
+
+void gimp_displays_set_busy (Gimp *gimp);
+void gimp_displays_unset_busy (Gimp *gimp);
+
+
+#endif /* __GIMP_DISPLAY_FOREACH_H__ */
diff --git a/app/display/gimpdisplay-handlers.c b/app/display/gimpdisplay-handlers.c
new file mode 100644
index 0000000..94cb863
--- /dev/null
+++ b/app/display/gimpdisplay-handlers.c
@@ -0,0 +1,128 @@
+/* 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 "display-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplay-handlers.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_update_handler (GimpProjection *projection,
+ gboolean now,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpDisplay *display);
+
+static void gimp_display_bounds_changed_handler (GimpImage *image,
+ gint old_x,
+ gint old_y,
+ GimpDisplay *display);
+static void gimp_display_flush_handler (GimpImage *image,
+ gboolean invalidate_preview,
+ GimpDisplay *display);
+
+
+/* public functions */
+
+void
+gimp_display_connect (GimpDisplay *display)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ image = gimp_display_get_image (display);
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_connect (gimp_image_get_projection (image), "update",
+ G_CALLBACK (gimp_display_update_handler),
+ display);
+
+ g_signal_connect (image, "bounds-changed",
+ G_CALLBACK (gimp_display_bounds_changed_handler),
+ display);
+ g_signal_connect (image, "flush",
+ G_CALLBACK (gimp_display_flush_handler),
+ display);
+}
+
+void
+gimp_display_disconnect (GimpDisplay *display)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ image = gimp_display_get_image (display);
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_flush_handler,
+ display);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_bounds_changed_handler,
+ display);
+
+ g_signal_handlers_disconnect_by_func (gimp_image_get_projection (image),
+ gimp_display_update_handler,
+ display);
+}
+
+
+/* private functions */
+
+static void
+gimp_display_update_handler (GimpProjection *projection,
+ gboolean now,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpDisplay *display)
+{
+ gimp_display_update_area (display, now, x, y, w, h);
+}
+
+static void
+gimp_display_bounds_changed_handler (GimpImage *image,
+ gint old_x,
+ gint old_y,
+ GimpDisplay *display)
+{
+ gimp_display_update_bounding_box (display);
+}
+
+static void
+gimp_display_flush_handler (GimpImage *image,
+ gboolean invalidate_preview,
+ GimpDisplay *display)
+{
+ gimp_display_flush (display);
+}
diff --git a/app/display/gimpdisplay-handlers.h b/app/display/gimpdisplay-handlers.h
new file mode 100644
index 0000000..34c547c
--- /dev/null
+++ b/app/display/gimpdisplay-handlers.h
@@ -0,0 +1,26 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_HANDLERS_H__
+#define __GIMP_DISPLAY_HANDLERS_H__
+
+
+void gimp_display_connect (GimpDisplay *display);
+void gimp_display_disconnect (GimpDisplay *display);
+
+
+#endif /* __GIMP_DISPLAY_HANDLERS_H__ */
diff --git a/app/display/gimpdisplay.c b/app/display/gimpdisplay.c
new file mode 100644
index 0000000..bf8a238
--- /dev/null
+++ b/app/display/gimpdisplay.c
@@ -0,0 +1,985 @@
+/* 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 "display-types.h"
+#include "tools/tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpprogress.h"
+
+#include "widgets/gimpdialogfactory.h"
+
+#include "tools/gimptool.h"
+#include "tools/tool_manager.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplay-handlers.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-handlers.h"
+#include "gimpdisplayshell-icon.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-scrollbars.h"
+#include "gimpdisplayshell-title.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpimagewindow.h"
+
+#include "gimp-intl.h"
+
+
+#define FLUSH_NOW_INTERVAL (G_TIME_SPAN_SECOND / 60)
+
+#define PAINT_AREA_CHUNK_WIDTH 32
+#define PAINT_AREA_CHUNK_HEIGHT 32
+
+
+enum
+{
+ PROP_0,
+ PROP_ID,
+ PROP_GIMP,
+ PROP_IMAGE,
+ PROP_SHELL
+};
+
+
+typedef struct _GimpDisplayPrivate GimpDisplayPrivate;
+
+struct _GimpDisplayPrivate
+{
+ gint ID; /* unique identifier for this display */
+
+ GimpImage *image; /* pointer to the associated image */
+ gint instance; /* the instance # of this display as
+ * taken from the image at creation */
+
+ GeglRectangle bounding_box;
+
+ GtkWidget *shell;
+ cairo_region_t *update_region;
+
+ guint64 last_flush_now;
+};
+
+#define GIMP_DISPLAY_GET_PRIVATE(display) \
+ ((GimpDisplayPrivate *) gimp_display_get_instance_private ((GimpDisplay *) (display)))
+
+
+/* local function prototypes */
+
+static void gimp_display_progress_iface_init (GimpProgressInterface *iface);
+
+static void gimp_display_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_display_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GimpProgress * gimp_display_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+static void gimp_display_progress_end (GimpProgress *progress);
+static gboolean gimp_display_progress_is_active (GimpProgress *progress);
+static void gimp_display_progress_set_text (GimpProgress *progress,
+ const gchar *message);
+static void gimp_display_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+static gdouble gimp_display_progress_get_value (GimpProgress *progress);
+static void gimp_display_progress_pulse (GimpProgress *progress);
+static guint32 gimp_display_progress_get_window_id (GimpProgress *progress);
+static gboolean gimp_display_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+static void gimp_display_progress_canceled (GimpProgress *progress,
+ GimpDisplay *display);
+
+static void gimp_display_flush_whenever (GimpDisplay *display,
+ gboolean now);
+static void gimp_display_paint_area (GimpDisplay *display,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpDisplay, gimp_display, GIMP_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpDisplay)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
+ gimp_display_progress_iface_init))
+
+#define parent_class gimp_display_parent_class
+
+
+static void
+gimp_display_class_init (GimpDisplayClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_display_set_property;
+ object_class->get_property = gimp_display_get_property;
+
+ g_object_class_install_property (object_class, PROP_ID,
+ g_param_spec_int ("id",
+ NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp",
+ NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image",
+ NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_SHELL,
+ g_param_spec_object ("shell",
+ NULL, NULL,
+ GIMP_TYPE_DISPLAY_SHELL,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_display_init (GimpDisplay *display)
+{
+}
+
+static void
+gimp_display_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_display_progress_start;
+ iface->end = gimp_display_progress_end;
+ iface->is_active = gimp_display_progress_is_active;
+ iface->set_text = gimp_display_progress_set_text;
+ iface->set_value = gimp_display_progress_set_value;
+ iface->get_value = gimp_display_progress_get_value;
+ iface->pulse = gimp_display_progress_pulse;
+ iface->get_window_id = gimp_display_progress_get_window_id;
+ iface->message = gimp_display_progress_message;
+}
+
+static void
+gimp_display_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDisplay *display = GIMP_DISPLAY (object);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ {
+ gint ID;
+
+ display->gimp = g_value_get_object (value); /* don't ref the gimp */
+ display->config = GIMP_DISPLAY_CONFIG (display->gimp->config);
+
+ do
+ {
+ ID = display->gimp->next_display_ID++;
+
+ if (display->gimp->next_display_ID == G_MAXINT)
+ display->gimp->next_display_ID = 1;
+ }
+ while (gimp_display_get_by_ID (display->gimp, ID));
+
+ private->ID = ID;
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_display_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDisplay *display = GIMP_DISPLAY (object);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_int (value, private->ID);
+ break;
+
+ case PROP_GIMP:
+ g_value_set_object (value, display->gimp);
+ break;
+
+ case PROP_IMAGE:
+ g_value_set_object (value, private->image);
+ break;
+
+ case PROP_SHELL:
+ g_value_set_object (value, private->shell);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GimpProgress *
+gimp_display_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ return gimp_progress_start (GIMP_PROGRESS (private->shell), cancellable,
+ "%s", message);
+
+ return NULL;
+}
+
+static void
+gimp_display_progress_end (GimpProgress *progress)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ gimp_progress_end (GIMP_PROGRESS (private->shell));
+}
+
+static gboolean
+gimp_display_progress_is_active (GimpProgress *progress)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ return gimp_progress_is_active (GIMP_PROGRESS (private->shell));
+
+ return FALSE;
+}
+
+static void
+gimp_display_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ gimp_progress_set_text_literal (GIMP_PROGRESS (private->shell), message);
+}
+
+static void
+gimp_display_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ gimp_progress_set_value (GIMP_PROGRESS (private->shell), percentage);
+}
+
+static gdouble
+gimp_display_progress_get_value (GimpProgress *progress)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ return gimp_progress_get_value (GIMP_PROGRESS (private->shell));
+
+ return 0.0;
+}
+
+static void
+gimp_display_progress_pulse (GimpProgress *progress)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ gimp_progress_pulse (GIMP_PROGRESS (private->shell));
+}
+
+static guint32
+gimp_display_progress_get_window_id (GimpProgress *progress)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ return gimp_progress_get_window_id (GIMP_PROGRESS (private->shell));
+
+ return 0;
+}
+
+static gboolean
+gimp_display_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ GimpDisplay *display = GIMP_DISPLAY (progress);
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->shell)
+ return gimp_progress_message (GIMP_PROGRESS (private->shell), gimp,
+ severity, domain, message);
+
+ return FALSE;
+}
+
+static void
+gimp_display_progress_canceled (GimpProgress *progress,
+ GimpDisplay *display)
+{
+ gimp_progress_cancel (GIMP_PROGRESS (display));
+}
+
+
+/* public functions */
+
+GimpDisplay *
+gimp_display_new (Gimp *gimp,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale,
+ GimpUIManager *popup_manager,
+ GimpDialogFactory *dialog_factory,
+ GdkScreen *screen,
+ gint monitor)
+{
+ GimpDisplay *display;
+ GimpDisplayPrivate *private;
+ GimpImageWindow *window = NULL;
+ GimpDisplayShell *shell;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+ /* If there isn't an interface, never create a display */
+ if (gimp->no_interface)
+ return NULL;
+
+ display = g_object_new (GIMP_TYPE_DISPLAY,
+ "gimp", gimp,
+ NULL);
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ /* refs the image */
+ if (image)
+ gimp_display_set_image (display, image);
+
+ /* get an image window */
+ if (GIMP_GUI_CONFIG (display->config)->single_window_mode)
+ {
+ GimpDisplay *active_display;
+
+ active_display = gimp_context_get_display (gimp_get_user_context (gimp));
+
+ if (! active_display)
+ {
+ active_display =
+ GIMP_DISPLAY (gimp_container_get_first_child (gimp->displays));
+ }
+
+ if (active_display)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (active_display);
+
+ window = gimp_display_shell_get_window (shell);
+ }
+ }
+
+ if (! window)
+ {
+ window = gimp_image_window_new (gimp,
+ private->image,
+ dialog_factory,
+ screen,
+ monitor);
+ }
+
+ /* create the shell for the image */
+ private->shell = gimp_display_shell_new (display, unit, scale,
+ popup_manager,
+ screen,
+ monitor);
+
+ shell = gimp_display_get_shell (display);
+
+ gimp_display_update_bounding_box (display);
+
+ gimp_image_window_add_shell (window, shell);
+ gimp_display_shell_present (shell);
+
+ /* make sure the docks are visible, in case all other image windows
+ * are iconified, see bug #686544.
+ */
+ gimp_dialog_factory_show_with_display (dialog_factory);
+
+ g_signal_connect (gimp_display_shell_get_statusbar (shell), "cancel",
+ G_CALLBACK (gimp_display_progress_canceled),
+ display);
+
+ /* add the display to the list */
+ gimp_container_add (gimp->displays, GIMP_OBJECT (display));
+
+ return display;
+}
+
+/**
+ * gimp_display_delete:
+ * @display:
+ *
+ * Closes the display and removes it from the display list. You should
+ * not call this function directly, use gimp_display_close() instead.
+ */
+void
+gimp_display_delete (GimpDisplay *display)
+{
+ GimpDisplayPrivate *private;
+ GimpTool *active_tool;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ /* remove the display from the list */
+ gimp_container_remove (display->gimp->displays, GIMP_OBJECT (display));
+
+ /* unrefs the image */
+ gimp_display_set_image (display, NULL);
+
+ active_tool = tool_manager_get_active (display->gimp);
+
+ if (active_tool && active_tool->focus_display == display)
+ tool_manager_focus_display_active (display->gimp, NULL);
+
+ if (private->shell)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ /* set private->shell to NULL *before* destroying the shell.
+ * all callbacks in gimpdisplayshell-callbacks.c will check
+ * this pointer and do nothing if the shell is in destruction.
+ */
+ private->shell = NULL;
+
+ if (window)
+ {
+ if (gimp_image_window_get_n_shells (window) > 1)
+ {
+ g_object_ref (shell);
+
+ gimp_image_window_remove_shell (window, shell);
+ gtk_widget_destroy (GTK_WIDGET (shell));
+
+ g_object_unref (shell);
+ }
+ else
+ {
+ gimp_image_window_destroy (window);
+ }
+ }
+ else
+ {
+ g_object_unref (shell);
+ }
+ }
+
+ g_object_unref (display);
+}
+
+/**
+ * gimp_display_close:
+ * @display:
+ *
+ * Closes the display. If this is the last display, it will remain
+ * open, but without an image.
+ */
+void
+gimp_display_close (GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ if (gimp_container_get_n_children (display->gimp->displays) > 1)
+ {
+ gimp_display_delete (display);
+ }
+ else
+ {
+ gimp_display_empty (display);
+ }
+}
+
+gint
+gimp_display_get_ID (GimpDisplay *display)
+{
+ GimpDisplayPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), -1);
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ return private->ID;
+}
+
+GimpDisplay *
+gimp_display_get_by_ID (Gimp *gimp,
+ gint ID)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ for (list = gimp_get_display_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpDisplay *display = list->data;
+
+ if (gimp_display_get_ID (display) == ID)
+ return display;
+ }
+
+ return NULL;
+}
+
+/**
+ * gimp_display_get_action_name:
+ * @display:
+ *
+ * Returns: The action name for the given display. The action name
+ * depends on the display ID. The result must be freed with g_free().
+ **/
+gchar *
+gimp_display_get_action_name (GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ return g_strdup_printf ("windows-display-%04d",
+ gimp_display_get_ID (display));
+}
+
+Gimp *
+gimp_display_get_gimp (GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ return display->gimp;
+}
+
+GimpImage *
+gimp_display_get_image (GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ return GIMP_DISPLAY_GET_PRIVATE (display)->image;
+}
+
+void
+gimp_display_set_image (GimpDisplay *display,
+ GimpImage *image)
+{
+ GimpDisplayPrivate *private;
+ GimpImage *old_image = NULL;
+ GimpDisplayShell *shell;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image));
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ shell = gimp_display_get_shell (display);
+
+ if (private->image)
+ {
+ /* stop any active tool */
+ tool_manager_control_active (display->gimp, GIMP_TOOL_ACTION_HALT,
+ display);
+
+ gimp_display_shell_disconnect (shell);
+
+ gimp_display_disconnect (display);
+
+ g_clear_pointer (&private->update_region, cairo_region_destroy);
+
+ gimp_image_dec_display_count (private->image);
+
+ /* set private->image before unrefing because there may be code
+ * that listens for image removals and then iterates the
+ * display list to find a valid display.
+ */
+ old_image = private->image;
+
+#if 0
+ g_print ("%s: image->ref_count before unrefing: %d\n",
+ G_STRFUNC, G_OBJECT (old_image)->ref_count);
+#endif
+ }
+
+ private->image = image;
+
+ if (image)
+ {
+#if 0
+ g_print ("%s: image->ref_count before refing: %d\n",
+ G_STRFUNC, G_OBJECT (image)->ref_count);
+#endif
+
+ g_object_ref (image);
+
+ private->instance = gimp_image_get_instance_count (image);
+ gimp_image_inc_instance_count (image);
+
+ gimp_image_inc_display_count (image);
+
+ gimp_display_connect (display);
+
+ if (shell)
+ gimp_display_shell_connect (shell);
+ }
+
+ if (old_image)
+ g_object_unref (old_image);
+
+ gimp_display_update_bounding_box (display);
+
+ if (shell)
+ {
+ if (image)
+ {
+ gimp_display_shell_reconnect (shell);
+ }
+ else
+ {
+ gimp_display_shell_title_update (shell);
+ gimp_display_shell_icon_update (shell);
+ }
+ }
+
+ if (old_image != image)
+ g_object_notify (G_OBJECT (display), "image");
+}
+
+gint
+gimp_display_get_instance (GimpDisplay *display)
+{
+ GimpDisplayPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), 0);
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ return private->instance;
+}
+
+GimpDisplayShell *
+gimp_display_get_shell (GimpDisplay *display)
+{
+ GimpDisplayPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ return GIMP_DISPLAY_SHELL (private->shell);
+}
+
+void
+gimp_display_empty (GimpDisplay *display)
+{
+ GimpDisplayPrivate *private;
+ GList *iter;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ g_return_if_fail (GIMP_IS_IMAGE (private->image));
+
+ for (iter = display->gimp->context_list; iter; iter = g_list_next (iter))
+ {
+ GimpContext *context = iter->data;
+
+ if (gimp_context_get_display (context) == display)
+ gimp_context_set_image (context, NULL);
+ }
+
+ gimp_display_set_image (display, NULL);
+
+ gimp_display_shell_empty (gimp_display_get_shell (display));
+}
+
+void
+gimp_display_fill (GimpDisplay *display,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale)
+{
+ GimpDisplayPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ g_return_if_fail (private->image == NULL);
+
+ gimp_display_set_image (display, image);
+
+ gimp_display_shell_fill (gimp_display_get_shell (display),
+ image, unit, scale);
+}
+
+void
+gimp_display_update_bounding_box (GimpDisplay *display)
+{
+ GimpDisplayPrivate *private;
+ GimpDisplayShell *shell;
+ GeglRectangle bounding_box = {};
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+ shell = gimp_display_get_shell (display);
+
+ if (shell)
+ {
+ bounding_box = gimp_display_shell_get_bounding_box (shell);
+
+ if (! gegl_rectangle_equal (&bounding_box, &private->bounding_box))
+ {
+ GeglRectangle diff_rects[4];
+ gint n_diff_rects;
+ gint i;
+
+ n_diff_rects = gegl_rectangle_subtract (diff_rects,
+ &private->bounding_box,
+ &bounding_box);
+
+ for (i = 0; i < n_diff_rects; i++)
+ {
+ gimp_display_paint_area (display,
+ diff_rects[i].x, diff_rects[i].y,
+ diff_rects[i].width, diff_rects[i].height);
+ }
+
+ private->bounding_box = bounding_box;
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+ gimp_display_shell_scrollbars_update (shell);
+ }
+ }
+ else
+ {
+ private->bounding_box = bounding_box;
+ }
+}
+
+void
+gimp_display_update_area (GimpDisplay *display,
+ gboolean now,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GimpDisplayPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (now)
+ {
+ gimp_display_paint_area (display, x, y, w, h);
+ }
+ else
+ {
+ cairo_rectangle_int_t rect;
+ gint image_width;
+ gint image_height;
+
+ image_width = gimp_image_get_width (private->image);
+ image_height = gimp_image_get_height (private->image);
+
+ rect.x = CLAMP (x, 0, image_width);
+ rect.y = CLAMP (y, 0, image_height);
+ rect.width = CLAMP (x + w, 0, image_width) - rect.x;
+ rect.height = CLAMP (y + h, 0, image_height) - rect.y;
+
+ if (private->update_region)
+ cairo_region_union_rectangle (private->update_region, &rect);
+ else
+ private->update_region = cairo_region_create_rectangle (&rect);
+ }
+}
+
+void
+gimp_display_flush (GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ /* FIXME: we can end up being called during shell construction if "show all"
+ * is enabled by default, in which case the shell's display pointer is still
+ * NULL
+ */
+ if (gimp_display_get_shell (display))
+ gimp_display_flush_whenever (display, FALSE);
+}
+
+void
+gimp_display_flush_now (GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ gimp_display_flush_whenever (display, TRUE);
+}
+
+
+/* private functions */
+
+static void
+gimp_display_flush_whenever (GimpDisplay *display,
+ gboolean now)
+{
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+
+ if (private->update_region)
+ {
+ gint n_rects = cairo_region_num_rectangles (private->update_region);
+ gint i;
+
+ for (i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect;
+
+ cairo_region_get_rectangle (private->update_region,
+ i, &rect);
+
+ gimp_display_paint_area (display,
+ rect.x,
+ rect.y,
+ rect.width,
+ rect.height);
+ }
+
+ g_clear_pointer (&private->update_region, cairo_region_destroy);
+ }
+
+ if (now)
+ {
+ guint64 now = g_get_monotonic_time ();
+
+ if ((now - private->last_flush_now) > FLUSH_NOW_INTERVAL)
+ {
+ gimp_display_shell_flush (gimp_display_get_shell (display), TRUE);
+
+ private->last_flush_now = now;
+ }
+ }
+ else
+ {
+ gimp_display_shell_flush (gimp_display_get_shell (display), now);
+ }
+}
+
+static void
+gimp_display_paint_area (GimpDisplay *display,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GeglRectangle rect;
+ gint x1, y1, x2, y2;
+ gdouble x1_f, y1_f, x2_f, y2_f;
+
+ if (! gegl_rectangle_intersect (&rect,
+ &private->bounding_box,
+ GEGL_RECTANGLE (x, y, w, h)))
+ {
+ return;
+ }
+
+ /* display the area */
+ gimp_display_shell_transform_bounds (shell,
+ rect.x,
+ rect.y,
+ rect.x + rect.width,
+ rect.y + rect.height,
+ &x1_f, &y1_f, &x2_f, &y2_f);
+
+ /* make sure to expose a superset of the transformed sub-pixel expose
+ * area, not a subset. bug #126942. --mitch
+ *
+ * also accommodate for spill introduced by potential box filtering.
+ * (bug #474509). --simon
+ */
+ x1 = floor (x1_f - 0.5);
+ y1 = floor (y1_f - 0.5);
+ x2 = ceil (x2_f + 0.5);
+ y2 = ceil (y2_f + 0.5);
+
+ /* align transformed area to a coarse grid, to simplify the
+ * invalidated area
+ */
+ x1 = floor ((gdouble) x1 / PAINT_AREA_CHUNK_WIDTH) * PAINT_AREA_CHUNK_WIDTH;
+ y1 = floor ((gdouble) y1 / PAINT_AREA_CHUNK_HEIGHT) * PAINT_AREA_CHUNK_HEIGHT;
+ x2 = ceil ((gdouble) x2 / PAINT_AREA_CHUNK_WIDTH) * PAINT_AREA_CHUNK_WIDTH;
+ y2 = ceil ((gdouble) y2 / PAINT_AREA_CHUNK_HEIGHT) * PAINT_AREA_CHUNK_HEIGHT;
+
+ gimp_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1);
+}
diff --git a/app/display/gimpdisplay.h b/app/display/gimpdisplay.h
new file mode 100644
index 0000000..7e676b0
--- /dev/null
+++ b/app/display/gimpdisplay.h
@@ -0,0 +1,99 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_H__
+#define __GIMP_DISPLAY_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_DISPLAY (gimp_display_get_type ())
+#define GIMP_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DISPLAY, GimpDisplay))
+#define GIMP_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DISPLAY, GimpDisplayClass))
+#define GIMP_IS_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DISPLAY))
+#define GIMP_IS_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DISPLAY))
+#define GIMP_DISPLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DISPLAY, GimpDisplayClass))
+
+
+typedef struct _GimpDisplayClass GimpDisplayClass;
+
+struct _GimpDisplay
+{
+ GimpObject parent_instance;
+
+ Gimp *gimp;
+ GimpDisplayConfig *config;
+
+};
+
+struct _GimpDisplayClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_display_get_type (void) G_GNUC_CONST;
+
+GimpDisplay * gimp_display_new (Gimp *gimp,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale,
+ GimpUIManager *popup_manager,
+ GimpDialogFactory *dialog_factory,
+ GdkScreen *screen,
+ gint monitor);
+void gimp_display_delete (GimpDisplay *display);
+void gimp_display_close (GimpDisplay *display);
+
+gint gimp_display_get_ID (GimpDisplay *display);
+GimpDisplay * gimp_display_get_by_ID (Gimp *gimp,
+ gint ID);
+
+gchar * gimp_display_get_action_name (GimpDisplay *display);
+
+Gimp * gimp_display_get_gimp (GimpDisplay *display);
+
+GimpImage * gimp_display_get_image (GimpDisplay *display);
+void gimp_display_set_image (GimpDisplay *display,
+ GimpImage *image);
+
+gint gimp_display_get_instance (GimpDisplay *display);
+
+GimpDisplayShell * gimp_display_get_shell (GimpDisplay *display);
+
+void gimp_display_empty (GimpDisplay *display);
+void gimp_display_fill (GimpDisplay *display,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale);
+
+void gimp_display_update_bounding_box
+ (GimpDisplay *display);
+
+void gimp_display_update_area (GimpDisplay *display,
+ gboolean now,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+void gimp_display_flush (GimpDisplay *display);
+void gimp_display_flush_now (GimpDisplay *display);
+
+
+#endif /* __GIMP_DISPLAY_H__ */
diff --git a/app/display/gimpdisplayshell-actions.c b/app/display/gimpdisplayshell-actions.c
new file mode 100644
index 0000000..fa0b5ef
--- /dev/null
+++ b/app/display/gimpdisplayshell-actions.c
@@ -0,0 +1,149 @@
+/* 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 "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+
+#include "widgets/gimpactiongroup.h"
+#include "widgets/gimpuimanager.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-actions.h"
+#include "gimpimagewindow.h"
+
+
+void
+gimp_display_shell_set_action_sensitive (GimpDisplayShell *shell,
+ const gchar *action,
+ gboolean sensitive)
+{
+ GimpImageWindow *window;
+ GimpContext *context;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (action != NULL);
+
+ window = gimp_display_shell_get_window (shell);
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+ GimpActionGroup *action_group;
+
+ action_group = gimp_ui_manager_get_action_group (manager, "view");
+
+ if (action_group)
+ gimp_action_group_set_action_sensitive (action_group, action, sensitive);
+ }
+
+ context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (context))
+ {
+ GimpActionGroup *action_group;
+
+ action_group = gimp_ui_manager_get_action_group (shell->popup_manager,
+ "view");
+
+ if (action_group)
+ gimp_action_group_set_action_sensitive (action_group, action, sensitive);
+ }
+}
+
+void
+gimp_display_shell_set_action_active (GimpDisplayShell *shell,
+ const gchar *action,
+ gboolean active)
+{
+ GimpImageWindow *window;
+ GimpContext *context;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (action != NULL);
+
+ window = gimp_display_shell_get_window (shell);
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+ GimpActionGroup *action_group;
+
+ action_group = gimp_ui_manager_get_action_group (manager, "view");
+
+ if (action_group)
+ gimp_action_group_set_action_active (action_group, action, active);
+ }
+
+ context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (context))
+ {
+ GimpActionGroup *action_group;
+
+ action_group = gimp_ui_manager_get_action_group (shell->popup_manager,
+ "view");
+
+ if (action_group)
+ gimp_action_group_set_action_active (action_group, action, active);
+ }
+}
+
+void
+gimp_display_shell_set_action_color (GimpDisplayShell *shell,
+ const gchar *action,
+ const GimpRGB *color)
+{
+ GimpImageWindow *window;
+ GimpContext *context;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (action != NULL);
+
+ window = gimp_display_shell_get_window (shell);
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+ GimpActionGroup *action_group;
+
+ action_group = gimp_ui_manager_get_action_group (manager, "view");
+
+ if (action_group)
+ gimp_action_group_set_action_color (action_group, action, color, FALSE);
+ }
+
+ context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (context))
+ {
+ GimpActionGroup *action_group;
+
+ action_group = gimp_ui_manager_get_action_group (shell->popup_manager,
+ "view");
+
+ if (action_group)
+ gimp_action_group_set_action_color (action_group, action, color, FALSE);
+ }
+}
diff --git a/app/display/gimpdisplayshell-actions.h b/app/display/gimpdisplayshell-actions.h
new file mode 100644
index 0000000..5d4a4da
--- /dev/null
+++ b/app/display/gimpdisplayshell-actions.h
@@ -0,0 +1,33 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_ACTIONS_H__
+#define __GIMP_DISPLAY_SHELL_ACTIONS_H__
+
+
+void gimp_display_shell_set_action_sensitive (GimpDisplayShell *shell,
+ const gchar *action,
+ gboolean sensitive);
+void gimp_display_shell_set_action_active (GimpDisplayShell *shell,
+ const gchar *action,
+ gboolean active);
+void gimp_display_shell_set_action_color (GimpDisplayShell *shell,
+ const gchar *action,
+ const GimpRGB *color);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_ACTIONS_H__ */
diff --git a/app/display/gimpdisplayshell-appearance.c b/app/display/gimpdisplayshell-appearance.c
new file mode 100644
index 0000000..1080acf
--- /dev/null
+++ b/app/display/gimpdisplayshell-appearance.c
@@ -0,0 +1,606 @@
+/* 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 "display-types.h"
+
+#include "config/gimpdisplayoptions.h"
+
+#include "core/gimpimage.h"
+
+#include "widgets/gimpdockcolumns.h"
+#include "widgets/gimprender.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvasitem.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-actions.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-selection.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-scrollbars.h"
+#include "gimpimagewindow.h"
+#include "gimpstatusbar.h"
+
+
+/* local function prototypes */
+
+static GimpDisplayOptions * appearance_get_options (GimpDisplayShell *shell);
+
+
+/* public functions */
+
+void
+gimp_display_shell_appearance_update (GimpDisplayShell *shell)
+{
+ GimpDisplayOptions *options;
+ GimpImageWindow *window;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+ window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ GimpDockColumns *left_docks;
+ GimpDockColumns *right_docks;
+ gboolean fullscreen;
+ gboolean has_grip;
+
+ fullscreen = gimp_image_window_get_fullscreen (window);
+
+ gimp_display_shell_set_action_active (shell, "view-fullscreen",
+ fullscreen);
+
+ left_docks = gimp_image_window_get_left_docks (window);
+ right_docks = gimp_image_window_get_right_docks (window);
+
+ has_grip = (! fullscreen &&
+ ! (left_docks && gimp_dock_columns_get_docks (left_docks)) &&
+ ! (right_docks && gimp_dock_columns_get_docks (right_docks)));
+
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (shell->statusbar),
+ has_grip);
+ }
+
+ gimp_display_shell_set_show_menubar (shell,
+ options->show_menubar);
+ gimp_display_shell_set_show_statusbar (shell,
+ options->show_statusbar);
+
+ gimp_display_shell_set_show_rulers (shell,
+ options->show_rulers);
+ gimp_display_shell_set_show_scrollbars (shell,
+ options->show_scrollbars);
+ gimp_display_shell_set_show_selection (shell,
+ options->show_selection);
+ gimp_display_shell_set_show_layer (shell,
+ options->show_layer_boundary);
+ gimp_display_shell_set_show_canvas (shell,
+ options->show_canvas_boundary);
+ gimp_display_shell_set_show_guides (shell,
+ options->show_guides);
+ gimp_display_shell_set_show_grid (shell,
+ options->show_grid);
+ gimp_display_shell_set_show_sample_points (shell,
+ options->show_sample_points);
+ gimp_display_shell_set_padding (shell,
+ options->padding_mode,
+ &options->padding_color);
+ gimp_display_shell_set_padding_in_show_all (shell,
+ options->padding_in_show_all);
+}
+
+void
+gimp_display_shell_set_show_menubar (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+ GimpImageWindow *window;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+ window = gimp_display_shell_get_window (shell);
+
+ g_object_set (options, "show-menubar", show, NULL);
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell));
+ gimp_image_window_set_show_menubar (window, show);
+ }
+
+ gimp_display_shell_set_action_active (shell, "view-show-menubar", show);
+}
+
+gboolean
+gimp_display_shell_get_show_menubar (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_menubar;
+}
+
+void
+gimp_display_shell_set_show_statusbar (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-statusbar", show, NULL);
+
+ gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell));
+ gimp_statusbar_set_visible (GIMP_STATUSBAR (shell->statusbar), show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-statusbar", show);
+}
+
+gboolean
+gimp_display_shell_get_show_statusbar (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_statusbar;
+}
+
+void
+gimp_display_shell_set_show_rulers (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-rulers", show, NULL);
+
+ gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell));
+ gtk_widget_set_visible (shell->origin, show);
+ gtk_widget_set_visible (shell->hrule, show);
+ gtk_widget_set_visible (shell->vrule, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-rulers", show);
+}
+
+gboolean
+gimp_display_shell_get_show_rulers (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_rulers;
+}
+
+void
+gimp_display_shell_set_show_scrollbars (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-scrollbars", show, NULL);
+
+ gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell));
+ gtk_widget_set_visible (shell->nav_ebox, show);
+ gtk_widget_set_visible (shell->hsb, show);
+ gtk_widget_set_visible (shell->vsb, show);
+ gtk_widget_set_visible (shell->quick_mask_button, show);
+ gtk_widget_set_visible (shell->zoom_button, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-scrollbars", show);
+}
+
+gboolean
+gimp_display_shell_get_show_scrollbars (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_scrollbars;
+}
+
+void
+gimp_display_shell_set_show_selection (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-selection", show, NULL);
+
+ gimp_display_shell_selection_set_show (shell, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-selection", show);
+}
+
+gboolean
+gimp_display_shell_get_show_selection (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_selection;
+}
+
+void
+gimp_display_shell_set_show_layer (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-layer-boundary", show, NULL);
+
+ gimp_canvas_item_set_visible (shell->layer_boundary, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-layer-boundary", show);
+}
+
+gboolean
+gimp_display_shell_get_show_layer (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_layer_boundary;
+}
+
+void
+gimp_display_shell_set_show_canvas (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-canvas-boundary", show, NULL);
+
+ gimp_canvas_item_set_visible (shell->canvas_boundary,
+ show && shell->show_all);
+
+ gimp_display_shell_set_action_active (shell, "view-show-canvas-boundary", show);
+}
+
+gboolean
+gimp_display_shell_get_show_canvas (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_canvas_boundary;
+}
+
+void
+gimp_display_shell_update_show_canvas (GimpDisplayShell *shell)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ gimp_canvas_item_set_visible (shell->canvas_boundary,
+ options->show_canvas_boundary &&
+ shell->show_all);
+}
+
+void
+gimp_display_shell_set_show_guides (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-guides", show, NULL);
+
+ gimp_canvas_item_set_visible (shell->guides, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-guides", show);
+}
+
+gboolean
+gimp_display_shell_get_show_guides (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_guides;
+}
+
+void
+gimp_display_shell_set_show_grid (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-grid", show, NULL);
+
+ gimp_canvas_item_set_visible (shell->grid, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-grid", show);
+}
+
+gboolean
+gimp_display_shell_get_show_grid (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_grid;
+}
+
+void
+gimp_display_shell_set_show_sample_points (GimpDisplayShell *shell,
+ gboolean show)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "show-sample-points", show, NULL);
+
+ gimp_canvas_item_set_visible (shell->sample_points, show);
+
+ gimp_display_shell_set_action_active (shell, "view-show-sample-points", show);
+}
+
+gboolean
+gimp_display_shell_get_show_sample_points (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->show_sample_points;
+}
+
+void
+gimp_display_shell_set_snap_to_grid (GimpDisplayShell *shell,
+ gboolean snap)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "snap-to-grid", snap, NULL);
+}
+
+gboolean
+gimp_display_shell_get_snap_to_grid (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->snap_to_grid;
+}
+
+void
+gimp_display_shell_set_snap_to_guides (GimpDisplayShell *shell,
+ gboolean snap)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "snap-to-guides", snap, NULL);
+}
+
+gboolean
+gimp_display_shell_get_snap_to_guides (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->snap_to_guides;
+}
+
+void
+gimp_display_shell_set_snap_to_canvas (GimpDisplayShell *shell,
+ gboolean snap)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "snap-to-canvas", snap, NULL);
+}
+
+gboolean
+gimp_display_shell_get_snap_to_canvas (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->snap_to_canvas;
+}
+
+void
+gimp_display_shell_set_snap_to_vectors (GimpDisplayShell *shell,
+ gboolean snap)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ g_object_set (options, "snap-to-path", snap, NULL);
+}
+
+gboolean
+gimp_display_shell_get_snap_to_vectors (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->snap_to_path;
+}
+
+void
+gimp_display_shell_set_padding (GimpDisplayShell *shell,
+ GimpCanvasPaddingMode padding_mode,
+ const GimpRGB *padding_color)
+{
+ GimpDisplayOptions *options;
+ GimpRGB color;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (padding_color != NULL);
+
+ options = appearance_get_options (shell);
+ color = *padding_color;
+
+ switch (padding_mode)
+ {
+ case GIMP_CANVAS_PADDING_MODE_DEFAULT:
+ if (shell->canvas)
+ {
+ GtkStyle *style;
+
+ gtk_widget_ensure_style (shell->canvas);
+
+ style = gtk_widget_get_style (shell->canvas);
+
+ gimp_rgb_set_gdk_color (&color, style->bg + GTK_STATE_NORMAL);
+ }
+ break;
+
+ case GIMP_CANVAS_PADDING_MODE_LIGHT_CHECK:
+ color = *gimp_render_light_check_color ();
+ break;
+
+ case GIMP_CANVAS_PADDING_MODE_DARK_CHECK:
+ color = *gimp_render_dark_check_color ();
+ break;
+
+ case GIMP_CANVAS_PADDING_MODE_CUSTOM:
+ case GIMP_CANVAS_PADDING_MODE_RESET:
+ break;
+ }
+
+ g_object_set (options,
+ "padding-mode", padding_mode,
+ "padding-color", &color,
+ NULL);
+
+ gimp_canvas_set_bg_color (GIMP_CANVAS (shell->canvas), &color);
+
+ gimp_display_shell_set_action_color (shell, "view-padding-color-menu",
+ &options->padding_color);
+}
+
+void
+gimp_display_shell_get_padding (GimpDisplayShell *shell,
+ GimpCanvasPaddingMode *padding_mode,
+ GimpRGB *padding_color)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ if (padding_mode)
+ *padding_mode = options->padding_mode;
+
+ if (padding_color)
+ *padding_color = options->padding_color;
+}
+
+void
+gimp_display_shell_set_padding_in_show_all (GimpDisplayShell *shell,
+ gboolean keep)
+{
+ GimpDisplayOptions *options;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ options = appearance_get_options (shell);
+
+ if (options->padding_in_show_all != keep)
+ {
+ g_object_set (options, "padding-in-show-all", keep, NULL);
+
+ if (shell->display)
+ {
+ gimp_display_shell_scroll_clamp_and_update (shell);
+ gimp_display_shell_scrollbars_update (shell);
+
+ gimp_display_shell_expose_full (shell);
+ }
+
+ gimp_display_shell_set_action_active (shell,
+ "view-padding-color-in-show-all",
+ keep);
+
+ g_object_notify (G_OBJECT (shell), "infinite-canvas");
+ }
+}
+
+gboolean
+gimp_display_shell_get_padding_in_show_all (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return appearance_get_options (shell)->padding_in_show_all;
+}
+
+
+/* private functions */
+
+static GimpDisplayOptions *
+appearance_get_options (GimpDisplayShell *shell)
+{
+ if (gimp_display_get_image (shell->display))
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window && gimp_image_window_get_fullscreen (window))
+ return shell->fullscreen_options;
+ else
+ return shell->options;
+ }
+
+ return shell->no_image_options;
+}
diff --git a/app/display/gimpdisplayshell-appearance.h b/app/display/gimpdisplayshell-appearance.h
new file mode 100644
index 0000000..0c67649
--- /dev/null
+++ b/app/display/gimpdisplayshell-appearance.h
@@ -0,0 +1,92 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_APPEARANCE_H__
+#define __GIMP_DISPLAY_SHELL_APPEARANCE_H__
+
+
+void gimp_display_shell_appearance_update (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_menubar (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_menubar (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_statusbar (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_statusbar (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_rulers (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_rulers (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_scrollbars (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_scrollbars (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_selection (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_selection (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_layer (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_layer (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_canvas (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_canvas (GimpDisplayShell *shell);
+void gimp_display_shell_update_show_canvas (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_grid (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_grid (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_guides (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_guides (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_snap_to_grid (GimpDisplayShell *shell,
+ gboolean snap);
+gboolean gimp_display_shell_get_snap_to_grid (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_show_sample_points (GimpDisplayShell *shell,
+ gboolean show);
+gboolean gimp_display_shell_get_show_sample_points (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_snap_to_guides (GimpDisplayShell *shell,
+ gboolean snap);
+gboolean gimp_display_shell_get_snap_to_guides (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_snap_to_canvas (GimpDisplayShell *shell,
+ gboolean snap);
+gboolean gimp_display_shell_get_snap_to_canvas (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_snap_to_vectors (GimpDisplayShell *shell,
+ gboolean snap);
+gboolean gimp_display_shell_get_snap_to_vectors (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_padding (GimpDisplayShell *shell,
+ GimpCanvasPaddingMode mode,
+ const GimpRGB *color);
+void gimp_display_shell_get_padding (GimpDisplayShell *shell,
+ GimpCanvasPaddingMode *mode,
+ GimpRGB *color);
+void gimp_display_shell_set_padding_in_show_all (GimpDisplayShell *shell,
+ gboolean keep);
+gboolean gimp_display_shell_get_padding_in_show_all (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_APPEARANCE_H__ */
diff --git a/app/display/gimpdisplayshell-autoscroll.c b/app/display/gimpdisplayshell-autoscroll.c
new file mode 100644
index 0000000..39cf252
--- /dev/null
+++ b/app/display/gimpdisplayshell-autoscroll.c
@@ -0,0 +1,184 @@
+/* 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 "display-types.h"
+
+#include "widgets/gimpdeviceinfo.h"
+#include "widgets/gimpdeviceinfo-coords.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-autoscroll.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-transform.h"
+
+#include "tools/tools-types.h"
+#include "tools/tool_manager.h"
+#include "tools/gimptool.h"
+#include "tools/gimptoolcontrol.h"
+
+
+#define AUTOSCROLL_DT 20
+#define AUTOSCROLL_DX 0.1
+
+
+typedef struct
+{
+ GdkEventMotion *mevent;
+ GimpDeviceInfo *device;
+ guint32 time;
+ GdkModifierType state;
+ guint timeout_id;
+} ScrollInfo;
+
+
+/* local function prototypes */
+
+static gboolean gimp_display_shell_autoscroll_timeout (gpointer data);
+
+
+/* public functions */
+
+void
+gimp_display_shell_autoscroll_start (GimpDisplayShell *shell,
+ GdkModifierType state,
+ GdkEventMotion *mevent)
+{
+ ScrollInfo *info;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->scroll_info)
+ return;
+
+ info = g_slice_new0 (ScrollInfo);
+
+ info->mevent = mevent;
+ info->device = gimp_device_info_get_by_device (mevent->device);
+ info->time = gdk_event_get_time ((GdkEvent *) mevent);
+ info->state = state;
+ info->timeout_id = g_timeout_add (AUTOSCROLL_DT,
+ gimp_display_shell_autoscroll_timeout,
+ shell);
+
+ shell->scroll_info = info;
+}
+
+void
+gimp_display_shell_autoscroll_stop (GimpDisplayShell *shell)
+{
+ ScrollInfo *info;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->scroll_info)
+ return;
+
+ info = shell->scroll_info;
+
+ if (info->timeout_id)
+ {
+ g_source_remove (info->timeout_id);
+ info->timeout_id = 0;
+ }
+
+ g_slice_free (ScrollInfo, info);
+ shell->scroll_info = NULL;
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_display_shell_autoscroll_timeout (gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ ScrollInfo *info = shell->scroll_info;
+ GimpCoords device_coords;
+ GimpCoords image_coords;
+ gint dx = 0;
+ gint dy = 0;
+
+ gimp_device_info_get_device_coords (info->device,
+ gtk_widget_get_window (shell->canvas),
+ &device_coords);
+
+ if (device_coords.x < 0)
+ dx = device_coords.x;
+ else if (device_coords.x > shell->disp_width)
+ dx = device_coords.x - shell->disp_width;
+
+ if (device_coords.y < 0)
+ dy = device_coords.y;
+ else if (device_coords.y > shell->disp_height)
+ dy = device_coords.y - shell->disp_height;
+
+ if (dx || dy)
+ {
+ GimpDisplay *display = shell->display;
+ GimpTool *active_tool = tool_manager_get_active (display->gimp);
+ gint scroll_amount_x = AUTOSCROLL_DX * dx;
+ gint scroll_amount_y = AUTOSCROLL_DX * dy;
+
+ info->time += AUTOSCROLL_DT;
+
+ gimp_display_shell_scroll_unoverscrollify (shell,
+ scroll_amount_x,
+ scroll_amount_y,
+ &scroll_amount_x,
+ &scroll_amount_y);
+
+ gimp_display_shell_scroll (shell,
+ scroll_amount_x,
+ scroll_amount_y);
+
+ gimp_display_shell_untransform_coords (shell,
+ &device_coords,
+ &image_coords);
+
+ if (gimp_tool_control_get_snap_to (active_tool->control))
+ {
+ gint x, y, width, height;
+
+ gimp_tool_control_get_snap_offsets (active_tool->control,
+ &x, &y, &width, &height);
+
+ gimp_display_shell_snap_coords (shell,
+ &image_coords,
+ x, y, width, height);
+ }
+
+ tool_manager_motion_active (display->gimp,
+ &image_coords,
+ info->time, info->state,
+ display);
+
+ return TRUE;
+ }
+ else
+ {
+ g_slice_free (ScrollInfo, info);
+ shell->scroll_info = NULL;
+
+ return FALSE;
+ }
+}
diff --git a/app/display/gimpdisplayshell-autoscroll.h b/app/display/gimpdisplayshell-autoscroll.h
new file mode 100644
index 0000000..c030759
--- /dev/null
+++ b/app/display/gimpdisplayshell-autoscroll.h
@@ -0,0 +1,28 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_AUTOSCROLL_H__
+#define __GIMP_DISPLAY_SHELL_AUTOSCROLL_H__
+
+
+void gimp_display_shell_autoscroll_start (GimpDisplayShell *shell,
+ GdkModifierType state,
+ GdkEventMotion *mevent);
+void gimp_display_shell_autoscroll_stop (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_AUTOSCROLL_H__ */
diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c
new file mode 100644
index 0000000..9b65529
--- /dev/null
+++ b/app/display/gimpdisplayshell-callbacks.c
@@ -0,0 +1,647 @@
+/* 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))
+ {
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (gtk_widget_get_window (shell->canvas));
+ gdk_cairo_region (cr, eevent->region);
+ cairo_clip (cr);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ gimp_display_shell_canvas_draw_image (shell, cr);
+ }
+ else
+ {
+ 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_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 */
+}
diff --git a/app/display/gimpdisplayshell-callbacks.h b/app/display/gimpdisplayshell-callbacks.h
new file mode 100644
index 0000000..de1861f
--- /dev/null
+++ b/app/display/gimpdisplayshell-callbacks.h
@@ -0,0 +1,48 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_CALLBACKS_H__
+#define __GIMP_DISPLAY_SHELL_CALLBACKS_H__
+
+
+void gimp_display_shell_canvas_realize (GtkWidget *widget,
+ GimpDisplayShell *shell);
+void gimp_display_shell_canvas_realize_after (GtkWidget *widget,
+ GimpDisplayShell *shell);
+void gimp_display_shell_canvas_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc,
+ GimpDisplayShell *shell);
+gboolean gimp_display_shell_canvas_expose (GtkWidget *widget,
+ GdkEventExpose *eevent,
+ GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_origin_button_press (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_quick_mask_button_press (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell);
+void gimp_display_shell_quick_mask_toggled (GtkWidget *widget,
+ GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_navigation_button_press (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_CALLBACKS_H__ */
diff --git a/app/display/gimpdisplayshell-close.c b/app/display/gimpdisplayshell-close.c
new file mode 100644
index 0000000..b7d7c16
--- /dev/null
+++ b/app/display/gimpdisplayshell-close.c
@@ -0,0 +1,447 @@
+/* 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 <time.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpmessagebox.h"
+#include "widgets/gimpmessagedialog.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-close.h"
+#include "gimpimagewindow.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_close_dialog (GimpDisplayShell *shell,
+ GimpImage *image);
+static void gimp_display_shell_close_name_changed (GimpImage *image,
+ GimpMessageBox *box);
+static void gimp_display_shell_close_exported (GimpImage *image,
+ GFile *file,
+ GimpMessageBox *box);
+static gboolean gimp_display_shell_close_time_changed (GimpMessageBox *box);
+static void gimp_display_shell_close_response (GtkWidget *widget,
+ gboolean close,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_close_accel_marshal(GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+static void gimp_time_since (gint64 then,
+ gint *hours,
+ gint *minutes);
+
+
+/* public functions */
+
+void
+gimp_display_shell_close (GimpDisplayShell *shell,
+ gboolean kill_it)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ /* FIXME: gimp_busy HACK not really appropriate here because we only
+ * want to prevent the busy image and display to be closed. --Mitch
+ */
+ if (shell->display->gimp->busy)
+ return;
+
+ /* If the image has been modified, give the user a chance to save
+ * it before nuking it--this only applies if its the last view
+ * to an image canvas. (a image with disp_count = 1)
+ */
+ if (! kill_it &&
+ image &&
+ gimp_image_get_display_count (image) == 1 &&
+ gimp_image_is_dirty (image))
+ {
+ /* If there's a save dialog active for this image, then raise it.
+ * (see bug #511965)
+ */
+ GtkWidget *dialog = g_object_get_data (G_OBJECT (image),
+ "gimp-file-save-dialog");
+ if (dialog)
+ {
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+ else
+ {
+ gimp_display_shell_close_dialog (shell, image);
+ }
+ }
+ else if (image)
+ {
+ gimp_display_close (shell->display);
+ }
+ else
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ /* Activate the action instead of simply calling gimp_exit(), so
+ * the quit action's sensitivity is taken into account.
+ */
+ gimp_ui_manager_activate_action (manager, "file", "file-quit");
+ }
+ }
+}
+
+
+/* private functions */
+
+#define RESPONSE_SAVE 1
+
+
+static void
+gimp_display_shell_close_dialog (GimpDisplayShell *shell,
+ GimpImage *image)
+{
+ GtkWidget *dialog;
+ GimpMessageBox *box;
+ GtkWidget *label;
+ GtkAccelGroup *accel_group;
+ GClosure *closure;
+ GSource *source;
+ guint accel_key;
+ GdkModifierType accel_mods;
+ gchar *title;
+ gchar *accel_string;
+ gchar *hint;
+ gchar *markup;
+ GFile *file;
+
+ if (shell->close_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (shell->close_dialog));
+ return;
+ }
+
+ file = gimp_image_get_file (image);
+
+ title = g_strdup_printf (_("Close %s"), gimp_image_get_display_name (image));
+
+ shell->close_dialog =
+ dialog = gimp_message_dialog_new (title, GIMP_ICON_DOCUMENT_SAVE,
+ GTK_WIDGET (shell),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ gimp_standard_help_func, NULL,
+
+ file ?
+ _("_Save") :
+ _("Save _As"), RESPONSE_SAVE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Discard Changes"), GTK_RESPONSE_CLOSE,
+ NULL);
+
+ g_free (title);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ RESPONSE_SAVE,
+ GTK_RESPONSE_CLOSE,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &shell->close_dialog);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gimp_display_shell_close_response),
+ shell);
+
+ /* connect <Primary>D to the quit/close button */
+ accel_group = gtk_accel_group_new ();
+ gtk_window_add_accel_group (GTK_WINDOW (shell->close_dialog), accel_group);
+ g_object_unref (accel_group);
+
+ closure = g_closure_new_object (sizeof (GClosure),
+ G_OBJECT (shell->close_dialog));
+ g_closure_set_marshal (closure, gimp_display_shell_close_accel_marshal);
+ gtk_accelerator_parse ("<Primary>D", &accel_key, &accel_mods);
+ gtk_accel_group_connect (accel_group, accel_key, accel_mods, 0, closure);
+
+ box = GIMP_MESSAGE_DIALOG (dialog)->box;
+
+ accel_string = gtk_accelerator_get_label (accel_key, accel_mods);
+ hint = g_strdup_printf (_("Press %s to discard all changes and close the image."),
+ accel_string);
+ markup = g_strdup_printf ("<i><small>%s</small></i>", hint);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ g_free (markup);
+ g_free (hint);
+ g_free (accel_string);
+
+ g_signal_connect_object (image, "name-changed",
+ G_CALLBACK (gimp_display_shell_close_name_changed),
+ box, 0);
+ g_signal_connect_object (image, "exported",
+ G_CALLBACK (gimp_display_shell_close_exported),
+ box, 0);
+
+ gimp_display_shell_close_name_changed (image, box);
+
+ closure =
+ g_cclosure_new_object (G_CALLBACK (gimp_display_shell_close_time_changed),
+ G_OBJECT (box));
+
+ /* update every 10 seconds */
+ source = g_timeout_source_new_seconds (10);
+ g_source_set_closure (source, closure);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ /* The dialog is destroyed with the shell, so it should be safe
+ * to hold an image pointer for the lifetime of the dialog.
+ */
+ g_object_set_data (G_OBJECT (box), "gimp-image", image);
+
+ gimp_display_shell_close_time_changed (box);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+gimp_display_shell_close_name_changed (GimpImage *image,
+ GimpMessageBox *box)
+{
+ GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (box));
+
+ if (GTK_IS_WINDOW (window))
+ {
+ gchar *title = g_strdup_printf (_("Close %s"),
+ gimp_image_get_display_name (image));
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+ g_free (title);
+ }
+
+ gimp_message_box_set_primary_text (box,
+ _("Save the changes to image '%s' "
+ "before closing?"),
+ gimp_image_get_display_name (image));
+}
+
+static void
+gimp_display_shell_close_exported (GimpImage *image,
+ GFile *file,
+ GimpMessageBox *box)
+{
+ gimp_display_shell_close_time_changed (box);
+}
+
+static gboolean
+gimp_display_shell_close_time_changed (GimpMessageBox *box)
+{
+ GimpImage *image = g_object_get_data (G_OBJECT (box), "gimp-image");
+ gint64 dirty_time = gimp_image_get_dirty_time (image);
+ gchar *time_text = NULL;
+ gchar *export_text = NULL;
+
+ if (dirty_time)
+ {
+ gint hours = 0;
+ gint minutes = 0;
+
+ gimp_time_since (dirty_time, &hours, &minutes);
+
+ if (hours > 0)
+ {
+ if (hours > 1 || minutes == 0)
+ {
+ time_text =
+ g_strdup_printf (ngettext ("If you don't save the image, "
+ "changes from the last hour "
+ "will be lost.",
+ "If you don't save the image, "
+ "changes from the last %d "
+ "hours will be lost.",
+ hours), hours);
+ }
+ else
+ {
+ time_text =
+ g_strdup_printf (ngettext ("If you don't save the image, "
+ "changes from the last hour "
+ "and %d minute will be lost.",
+ "If you don't save the image, "
+ "changes from the last hour "
+ "and %d minutes will be lost.",
+ minutes), minutes);
+ }
+ }
+ else
+ {
+ time_text =
+ g_strdup_printf (ngettext ("If you don't save the image, "
+ "changes from the last minute "
+ "will be lost.",
+ "If you don't save the image, "
+ "changes from the last %d "
+ "minutes will be lost.",
+ minutes), minutes);
+ }
+ }
+
+ if (! gimp_image_is_export_dirty (image))
+ {
+ GFile *file;
+
+ file = gimp_image_get_exported_file (image);
+ if (! file)
+ file = gimp_image_get_imported_file (image);
+
+ export_text = g_strdup_printf (_("The image has been exported to '%s'."),
+ gimp_file_get_utf8_name (file));
+ }
+
+ if (time_text && export_text)
+ gimp_message_box_set_text (box, "%s\n\n%s", time_text, export_text);
+ else if (time_text || export_text)
+ gimp_message_box_set_text (box, "%s", time_text ? time_text : export_text);
+ else
+ gimp_message_box_set_text (box, "%s", time_text);
+
+ g_free (time_text);
+ g_free (export_text);
+
+ return TRUE;
+}
+
+static void
+gimp_display_shell_close_response (GtkWidget *widget,
+ gint response_id,
+ GimpDisplayShell *shell)
+{
+ gtk_widget_destroy (widget);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_CLOSE:
+ gimp_display_close (shell->display);
+ break;
+
+ case RESPONSE_SAVE:
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_image_window_set_active_shell (window, shell);
+
+ gimp_ui_manager_activate_action (manager,
+ "file", "file-save-and-close");
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gimp_display_shell_close_accel_marshal (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data)
+{
+ gtk_dialog_response (GTK_DIALOG (closure->data), GTK_RESPONSE_CLOSE);
+
+ /* we handled the accelerator */
+ g_value_set_boolean (return_value, TRUE);
+}
+
+static void
+gimp_time_since (gint64 then,
+ gint *hours,
+ gint *minutes)
+{
+ gint64 now = time (NULL);
+ gint64 diff = 1 + now - then;
+
+ g_return_if_fail (now >= then);
+
+ /* first round up to the nearest minute */
+ diff = (diff + 59) / 60;
+
+ /* then optionally round minutes to multiples of 5 or 10 */
+ if (diff > 50)
+ diff = ((diff + 8) / 10) * 10;
+ else if (diff > 20)
+ diff = ((diff + 3) / 5) * 5;
+
+ /* determine full hours */
+ if (diff >= 60)
+ {
+ *hours = diff / 60;
+ diff = (diff % 60);
+ }
+
+ /* round up to full hours for 2 and more */
+ if (*hours > 1 && diff > 0)
+ {
+ *hours += 1;
+ diff = 0;
+ }
+
+ *minutes = diff;
+}
diff --git a/app/display/gimpdisplayshell-close.h b/app/display/gimpdisplayshell-close.h
new file mode 100644
index 0000000..3d55650
--- /dev/null
+++ b/app/display/gimpdisplayshell-close.h
@@ -0,0 +1,26 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_CLOSE_H__
+#define __GIMP_DISPLAY_SHELL_CLOSE_H__
+
+
+void gimp_display_shell_close (GimpDisplayShell *shell,
+ gboolean kill_it);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_CLOSE_H__ */
diff --git a/app/display/gimpdisplayshell-cursor.c b/app/display/gimpdisplayshell-cursor.c
new file mode 100644
index 0000000..5a536d5
--- /dev/null
+++ b/app/display/gimpdisplayshell-cursor.c
@@ -0,0 +1,295 @@
+/* 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 "display-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimpimage.h"
+
+#include "widgets/gimpcursor.h"
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpdockcontainer.h"
+#include "widgets/gimpsessioninfo.h"
+
+#include "gimpcanvascursor.h"
+#include "gimpdisplay.h"
+#include "gimpcursorview.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-cursor.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpstatusbar.h"
+
+
+static void gimp_display_shell_real_set_cursor (GimpDisplayShell *shell,
+ GimpCursorType cursor_type,
+ GimpToolCursorType tool_cursor,
+ GimpCursorModifier modifier,
+ gboolean always_install);
+
+
+/* public functions */
+
+void
+gimp_display_shell_set_cursor (GimpDisplayShell *shell,
+ GimpCursorType cursor_type,
+ GimpToolCursorType tool_cursor,
+ GimpCursorModifier modifier)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->using_override_cursor)
+ {
+ gimp_display_shell_real_set_cursor (shell,
+ cursor_type,
+ tool_cursor,
+ modifier,
+ FALSE);
+ }
+}
+
+void
+gimp_display_shell_unset_cursor (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->using_override_cursor)
+ {
+ gimp_display_shell_real_set_cursor (shell,
+ (GimpCursorType) -1, 0, 0, FALSE);
+ }
+}
+
+void
+gimp_display_shell_set_override_cursor (GimpDisplayShell *shell,
+ GimpCursorType cursor_type)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->using_override_cursor ||
+ (shell->using_override_cursor &&
+ shell->override_cursor != cursor_type))
+ {
+ shell->override_cursor = cursor_type;
+ shell->using_override_cursor = TRUE;
+
+ gimp_cursor_set (shell->canvas,
+ shell->cursor_handedness,
+ cursor_type,
+ GIMP_TOOL_CURSOR_NONE,
+ GIMP_CURSOR_MODIFIER_NONE);
+ }
+}
+
+void
+gimp_display_shell_unset_override_cursor (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->using_override_cursor)
+ {
+ shell->using_override_cursor = FALSE;
+
+ gimp_display_shell_real_set_cursor (shell,
+ shell->current_cursor,
+ shell->tool_cursor,
+ shell->cursor_modifier,
+ TRUE);
+ }
+}
+
+void
+gimp_display_shell_update_software_cursor (GimpDisplayShell *shell,
+ GimpCursorPrecision precision,
+ gint display_x,
+ gint display_y,
+ gdouble image_x,
+ gdouble image_y)
+{
+ GimpImageWindow *image_window;
+ GimpDialogFactory *factory;
+ GimpStatusbar *statusbar;
+ GtkWidget *widget;
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ if (shell->draw_cursor &&
+ shell->proximity &&
+ display_x >= 0 &&
+ display_y >= 0)
+ {
+ gimp_canvas_item_begin_change (shell->cursor);
+
+ gimp_canvas_cursor_set (shell->cursor,
+ display_x,
+ display_y);
+ gimp_canvas_item_set_visible (shell->cursor, TRUE);
+
+ gimp_canvas_item_end_change (shell->cursor);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (shell->cursor, FALSE);
+ }
+
+ /* use the passed image_coords for the statusbar because they are
+ * possibly snapped...
+ */
+ statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_statusbar_update_cursor (statusbar, precision, image_x, image_y);
+
+ image_window = gimp_display_shell_get_window (shell);
+ factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window));
+
+ widget = gimp_dialog_factory_find_widget (factory, "gimp-cursor-view");
+
+ if (widget)
+ {
+ GtkWidget *cursor_view = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (cursor_view)
+ {
+ gint t_x = -1;
+ gint t_y = -1;
+
+ /* ...but use the unsnapped display_coords for the info window */
+ if (display_x >= 0 && display_y >= 0)
+ gimp_display_shell_untransform_xy (shell, display_x, display_y,
+ &t_x, &t_y, FALSE);
+
+ gimp_cursor_view_update_cursor (GIMP_CURSOR_VIEW (cursor_view),
+ image, shell->unit,
+ t_x, t_y);
+ }
+ }
+}
+
+void
+gimp_display_shell_clear_software_cursor (GimpDisplayShell *shell)
+{
+ GimpImageWindow *image_window;
+ GimpDialogFactory *factory;
+ GimpStatusbar *statusbar;
+ GtkWidget *widget;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_canvas_item_set_visible (shell->cursor, FALSE);
+
+ statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_statusbar_clear_cursor (statusbar);
+
+ image_window = gimp_display_shell_get_window (shell);
+ factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window));
+
+ widget = gimp_dialog_factory_find_widget (factory, "gimp-cursor-view");
+
+ if (widget)
+ {
+ GtkWidget *cursor_view = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (cursor_view)
+ gimp_cursor_view_clear_cursor (GIMP_CURSOR_VIEW (cursor_view));
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_real_set_cursor (GimpDisplayShell *shell,
+ GimpCursorType cursor_type,
+ GimpToolCursorType tool_cursor,
+ GimpCursorModifier modifier,
+ gboolean always_install)
+{
+ GimpHandedness cursor_handedness;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (cursor_type == (GimpCursorType) -1)
+ {
+ shell->current_cursor = cursor_type;
+
+ if (gtk_widget_is_drawable (shell->canvas))
+ gdk_window_set_cursor (gtk_widget_get_window (shell->canvas), NULL);
+
+ return;
+ }
+
+ if (cursor_type != GIMP_CURSOR_NONE &&
+ cursor_type != GIMP_CURSOR_BAD)
+ {
+ switch (shell->display->config->cursor_mode)
+ {
+ case GIMP_CURSOR_MODE_TOOL_ICON:
+ break;
+
+ case GIMP_CURSOR_MODE_TOOL_CROSSHAIR:
+ if (cursor_type < GIMP_CURSOR_CORNER_TOP ||
+ cursor_type > GIMP_CURSOR_SIDE_TOP_LEFT)
+ {
+ /* the corner and side cursors count as crosshair, so leave
+ * them and override everything else
+ */
+ cursor_type = GIMP_CURSOR_CROSSHAIR_SMALL;
+ }
+ break;
+
+ case GIMP_CURSOR_MODE_CROSSHAIR:
+ cursor_type = GIMP_CURSOR_CROSSHAIR;
+ tool_cursor = GIMP_TOOL_CURSOR_NONE;
+
+ if (modifier != GIMP_CURSOR_MODIFIER_BAD)
+ {
+ /* the bad modifier is always shown */
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+ }
+ break;
+ }
+ }
+
+ cursor_type = gimp_cursor_rotate (cursor_type, shell->rotate_angle);
+
+ cursor_handedness = GIMP_GUI_CONFIG (shell->display->config)->cursor_handedness;
+
+ if (shell->cursor_handedness != cursor_handedness ||
+ shell->current_cursor != cursor_type ||
+ shell->tool_cursor != tool_cursor ||
+ shell->cursor_modifier != modifier ||
+ always_install)
+ {
+ shell->cursor_handedness = cursor_handedness;
+ shell->current_cursor = cursor_type;
+ shell->tool_cursor = tool_cursor;
+ shell->cursor_modifier = modifier;
+
+ gimp_cursor_set (shell->canvas,
+ cursor_handedness,
+ cursor_type, tool_cursor, modifier);
+ }
+}
diff --git a/app/display/gimpdisplayshell-cursor.h b/app/display/gimpdisplayshell-cursor.h
new file mode 100644
index 0000000..8f434c6
--- /dev/null
+++ b/app/display/gimpdisplayshell-cursor.h
@@ -0,0 +1,47 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_CURSOR_H__
+#define __GIMP_DISPLAY_SHELL_CURSOR_H__
+
+
+/* functions dealing with the normal windowing system cursor */
+
+void gimp_display_shell_set_cursor (GimpDisplayShell *shell,
+ GimpCursorType cursor_type,
+ GimpToolCursorType tool_cursor,
+ GimpCursorModifier modifier);
+void gimp_display_shell_unset_cursor (GimpDisplayShell *shell);
+void gimp_display_shell_set_override_cursor (GimpDisplayShell *shell,
+ GimpCursorType cursor_type);
+void gimp_display_shell_unset_override_cursor (GimpDisplayShell *shell);
+
+
+/* functions dealing with the software cursor that is drawn to the
+ * canvas by GIMP
+ */
+
+void gimp_display_shell_update_software_cursor (GimpDisplayShell *shell,
+ GimpCursorPrecision precision,
+ gint display_x,
+ gint display_y,
+ gdouble image_x,
+ gdouble image_y);
+void gimp_display_shell_clear_software_cursor (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_CURSOR_H__ */
diff --git a/app/display/gimpdisplayshell-dnd.c b/app/display/gimpdisplayshell-dnd.c
new file mode 100644
index 0000000..152ca45
--- /dev/null
+++ b/app/display/gimpdisplayshell-dnd.c
@@ -0,0 +1,770 @@
+/* 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimp-edit.h"
+#include "core/gimpbuffer.h"
+#include "core/gimpdrawable-edit.h"
+#include "core/gimpfilloptions.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-new.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimplayer.h"
+#include "core/gimplayer-new.h"
+#include "core/gimplayermask.h"
+#include "core/gimppattern.h"
+#include "core/gimpprogress.h"
+
+#include "file/file-open.h"
+
+#include "text/gimptext.h"
+#include "text/gimptextlayer.h"
+
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpvectors-import.h"
+
+#include "widgets/gimpdnd.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-dnd.h"
+#include "gimpdisplayshell-transform.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_drop_drawable (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data);
+static void gimp_display_shell_drop_vectors (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data);
+static void gimp_display_shell_drop_svg (GtkWidget *widget,
+ gint x,
+ gint y,
+ const guchar *svg_data,
+ gsize svg_data_length,
+ gpointer data);
+static void gimp_display_shell_drop_pattern (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data);
+static void gimp_display_shell_drop_color (GtkWidget *widget,
+ gint x,
+ gint y,
+ const GimpRGB *color,
+ gpointer data);
+static void gimp_display_shell_drop_buffer (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data);
+static void gimp_display_shell_drop_uri_list (GtkWidget *widget,
+ gint x,
+ gint y,
+ GList *uri_list,
+ gpointer data);
+static void gimp_display_shell_drop_component (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpImage *image,
+ GimpChannelType component,
+ gpointer data);
+static void gimp_display_shell_drop_pixbuf (GtkWidget *widget,
+ gint x,
+ gint y,
+ GdkPixbuf *pixbuf,
+ gpointer data);
+
+
+/* public functions */
+
+void
+gimp_display_shell_dnd_init (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_LAYER,
+ gimp_display_shell_drop_drawable,
+ shell);
+ gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_LAYER_MASK,
+ gimp_display_shell_drop_drawable,
+ shell);
+ gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_CHANNEL,
+ gimp_display_shell_drop_drawable,
+ shell);
+ gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_VECTORS,
+ gimp_display_shell_drop_vectors,
+ shell);
+ gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_PATTERN,
+ gimp_display_shell_drop_pattern,
+ shell);
+ gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_BUFFER,
+ gimp_display_shell_drop_buffer,
+ shell);
+ gimp_dnd_color_dest_add (shell->canvas,
+ gimp_display_shell_drop_color,
+ shell);
+ gimp_dnd_component_dest_add (shell->canvas,
+ gimp_display_shell_drop_component,
+ shell);
+ gimp_dnd_uri_list_dest_add (shell->canvas,
+ gimp_display_shell_drop_uri_list,
+ shell);
+ gimp_dnd_svg_dest_add (shell->canvas,
+ gimp_display_shell_drop_svg,
+ shell);
+ gimp_dnd_pixbuf_dest_add (shell->canvas,
+ gimp_display_shell_drop_pixbuf,
+ shell);
+}
+
+
+/* private functions */
+
+/*
+ * Position the dropped item in the middle of the viewport.
+ */
+static void
+gimp_display_shell_dnd_position_item (GimpDisplayShell *shell,
+ GimpImage *image,
+ GimpItem *item)
+{
+ gint item_width = gimp_item_get_width (item);
+ gint item_height = gimp_item_get_height (item);
+ gint off_x, off_y;
+
+ if (item_width >= gimp_image_get_width (image) &&
+ item_height >= gimp_image_get_height (image))
+ {
+ off_x = (gimp_image_get_width (image) - item_width) / 2;
+ off_y = (gimp_image_get_height (image) - item_height) / 2;
+ }
+ else
+ {
+ gint x, y;
+ gint width, height;
+
+ gimp_display_shell_untransform_viewport (
+ shell,
+ ! gimp_display_shell_get_infinite_canvas (shell),
+ &x, &y, &width, &height);
+
+ off_x = x + (width - item_width) / 2;
+ off_y = y + (height - item_height) / 2;
+ }
+
+ gimp_item_translate (item,
+ off_x - gimp_item_get_offset_x (item),
+ off_y - gimp_item_get_offset_y (item),
+ FALSE);
+}
+
+static void
+gimp_display_shell_dnd_flush (GimpDisplayShell *shell,
+ GimpImage *image)
+{
+ gimp_display_shell_present (shell);
+
+ gimp_image_flush (image);
+
+ gimp_context_set_display (gimp_get_user_context (shell->display->gimp),
+ shell->display);
+}
+
+static void
+gimp_display_shell_drop_drawable (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GType new_type;
+ GimpItem *new_item;
+
+ GIMP_LOG (DND, NULL);
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! image)
+ {
+ image = gimp_image_new_from_drawable (shell->display->gimp,
+ GIMP_DRAWABLE (viewable));
+ gimp_create_display (shell->display->gimp, image, GIMP_UNIT_PIXEL, 1.0,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget));
+ g_object_unref (image);
+
+ return;
+ }
+
+ if (GIMP_IS_LAYER (viewable))
+ new_type = G_TYPE_FROM_INSTANCE (viewable);
+ else
+ new_type = GIMP_TYPE_LAYER;
+
+ new_item = gimp_item_convert (GIMP_ITEM (viewable), image, new_type);
+
+ if (new_item)
+ {
+ GimpLayer *new_layer = GIMP_LAYER (new_item);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE,
+ _("Drop New Layer"));
+
+ gimp_display_shell_dnd_position_item (shell, image, new_item);
+
+ gimp_item_set_visible (new_item, TRUE, FALSE);
+ gimp_item_set_linked (new_item, FALSE, FALSE);
+
+ gimp_image_add_layer (image, new_layer,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_display_shell_dnd_flush (shell, image);
+ }
+}
+
+static void
+gimp_display_shell_drop_vectors (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpItem *new_item;
+
+ GIMP_LOG (DND, NULL);
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! image)
+ return;
+
+ new_item = gimp_item_convert (GIMP_ITEM (viewable),
+ image, G_TYPE_FROM_INSTANCE (viewable));
+
+ if (new_item)
+ {
+ GimpVectors *new_vectors = GIMP_VECTORS (new_item);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE,
+ _("Drop New Path"));
+
+ gimp_image_add_vectors (image, new_vectors,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_display_shell_dnd_flush (shell, image);
+ }
+}
+
+static void
+gimp_display_shell_drop_svg (GtkWidget *widget,
+ gint x,
+ gint y,
+ const guchar *svg_data,
+ gsize svg_data_len,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GError *error = NULL;
+
+ GIMP_LOG (DND, NULL);
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! image)
+ return;
+
+ if (! gimp_vectors_import_buffer (image,
+ (const gchar *) svg_data, svg_data_len,
+ TRUE, FALSE,
+ GIMP_IMAGE_ACTIVE_PARENT, -1,
+ NULL, &error))
+ {
+ gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display),
+ GIMP_MESSAGE_ERROR,
+ error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ gimp_display_shell_dnd_flush (shell, image);
+ }
+}
+
+static void
+gimp_display_shell_dnd_fill (GimpDisplayShell *shell,
+ GimpFillOptions *options,
+ const gchar *undo_desc)
+{
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpDrawable *drawable;
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! image)
+ return;
+
+ drawable = gimp_image_get_active_drawable (image);
+
+ if (! drawable)
+ return;
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display),
+ GIMP_MESSAGE_ERROR,
+ _("Cannot modify the pixels of layer groups."));
+ return;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display),
+ GIMP_MESSAGE_ERROR,
+ _("The active layer's pixels are locked."));
+ return;
+ }
+
+ /* FIXME: there should be a virtual method for this that the
+ * GimpTextLayer can override.
+ */
+ if (gimp_fill_options_get_style (options) == GIMP_FILL_STYLE_SOLID &&
+ gimp_item_is_text_layer (GIMP_ITEM (drawable)))
+ {
+ GimpRGB color;
+
+ gimp_context_get_foreground (GIMP_CONTEXT (options), &color);
+
+ gimp_text_layer_set (GIMP_TEXT_LAYER (drawable), NULL,
+ "color", &color,
+ NULL);
+ }
+ else
+ {
+ gimp_drawable_edit_fill (drawable, options, undo_desc);
+ }
+
+ gimp_display_shell_dnd_flush (shell, image);
+}
+
+static void
+gimp_display_shell_drop_pattern (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpViewable *viewable,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpFillOptions *options = gimp_fill_options_new (shell->display->gimp,
+ NULL, FALSE);
+
+ GIMP_LOG (DND, NULL);
+
+ gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN);
+ gimp_context_set_pattern (GIMP_CONTEXT (options), GIMP_PATTERN (viewable));
+
+ gimp_display_shell_dnd_fill (shell, options,
+ C_("undo-type", "Drop pattern to layer"));
+
+ g_object_unref (options);
+}
+
+static void
+gimp_display_shell_drop_color (GtkWidget *widget,
+ gint x,
+ gint y,
+ const GimpRGB *color,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpFillOptions *options = gimp_fill_options_new (shell->display->gimp,
+ NULL, FALSE);
+
+ GIMP_LOG (DND, NULL);
+
+ gimp_fill_options_set_style (options, GIMP_FILL_STYLE_SOLID);
+ gimp_context_set_foreground (GIMP_CONTEXT (options), color);
+
+ gimp_display_shell_dnd_fill (shell, options,
+ C_("undo-type", "Drop color to layer"));
+
+ g_object_unref (options);
+}
+
+static void
+gimp_display_shell_drop_buffer (GtkWidget *widget,
+ gint drop_x,
+ gint drop_y,
+ GimpViewable *viewable,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpDrawable *drawable;
+ GimpBuffer *buffer;
+ GimpPasteType paste_type;
+ gint x, y, width, height;
+
+ GIMP_LOG (DND, NULL);
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! image)
+ {
+ image = gimp_image_new_from_buffer (shell->display->gimp,
+ GIMP_BUFFER (viewable));
+ gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget));
+ g_object_unref (image);
+
+ return;
+ }
+
+ paste_type = GIMP_PASTE_TYPE_FLOATING;
+
+ drawable = gimp_image_get_active_drawable (image);
+
+ if (drawable)
+ {
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display),
+ GIMP_MESSAGE_INFO,
+ _("Pasted as new layer because the "
+ "target is a layer group."));
+
+ paste_type = GIMP_PASTE_TYPE_NEW_LAYER;
+ }
+ else if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display),
+ GIMP_MESSAGE_ERROR,
+ _("Pasted as new layer because the "
+ "target's pixels are locked."));
+
+ paste_type = GIMP_PASTE_TYPE_NEW_LAYER;
+ }
+ }
+
+ buffer = GIMP_BUFFER (viewable);
+
+ gimp_display_shell_untransform_viewport (
+ shell,
+ ! gimp_display_shell_get_infinite_canvas (shell),
+ &x, &y, &width, &height);
+
+ /* FIXME: popup a menu for selecting "Paste Into" */
+
+ gimp_edit_paste (image, drawable, GIMP_OBJECT (buffer),
+ paste_type, x, y, width, height);
+
+ gimp_display_shell_dnd_flush (shell, image);
+}
+
+static void
+gimp_display_shell_drop_uri_list (GtkWidget *widget,
+ gint x,
+ gint y,
+ GList *uri_list,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image;
+ GimpContext *context;
+ GList *list;
+ gboolean open_as_layers;
+
+ /* If the app is already being torn down, shell->display might be
+ * NULL here. Play it safe.
+ */
+ if (! shell->display)
+ return;
+
+ image = gimp_display_get_image (shell->display);
+ context = gimp_get_user_context (shell->display->gimp);
+
+ GIMP_LOG (DND, NULL);
+
+ open_as_layers = (image != NULL);
+
+ if (image)
+ g_object_ref (image);
+
+ for (list = uri_list; list; list = g_list_next (list))
+ {
+ GFile *file = g_file_new_for_uri (list->data);
+ GimpPDBStatusType status;
+ GError *error = NULL;
+ gboolean warn = FALSE;
+
+ if (! shell->display)
+ {
+ /* It seems as if GIMP is being torn down for quitting. Bail out. */
+ g_object_unref (file);
+ g_clear_object (&image);
+ return;
+ }
+
+ if (open_as_layers)
+ {
+ GList *new_layers;
+
+ new_layers = file_open_layers (shell->display->gimp, context,
+ GIMP_PROGRESS (shell->display),
+ image, FALSE,
+ file, GIMP_RUN_INTERACTIVE, NULL,
+ &status, &error);
+
+ if (new_layers)
+ {
+ gint x = 0;
+ gint y = 0;
+ gint width = gimp_image_get_width (image);
+ gint height = gimp_image_get_height (image);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ gimp_display_shell_untransform_viewport (
+ shell,
+ ! gimp_display_shell_get_infinite_canvas (shell),
+ &x, &y, &width, &height);
+ }
+
+ gimp_image_add_layers (image, new_layers,
+ GIMP_IMAGE_ACTIVE_PARENT, -1,
+ x, y, width, height,
+ _("Drop layers"));
+
+ g_list_free (new_layers);
+ }
+ else if (status != GIMP_PDB_CANCEL)
+ {
+ warn = TRUE;
+ }
+ }
+ else if (gimp_display_get_image (shell->display))
+ {
+ /* open any subsequent images in a new display */
+ GimpImage *new_image;
+
+ new_image = file_open_with_display (shell->display->gimp, context,
+ NULL,
+ file, FALSE,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget),
+ &status, &error);
+
+ if (! new_image && status != GIMP_PDB_CANCEL)
+ warn = TRUE;
+ }
+ else
+ {
+ /* open the first image in the empty display */
+ image = file_open_with_display (shell->display->gimp, context,
+ GIMP_PROGRESS (shell->display),
+ file, FALSE,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget),
+ &status, &error);
+
+ if (image)
+ {
+ g_object_ref (image);
+ }
+ else if (status != GIMP_PDB_CANCEL)
+ {
+ warn = TRUE;
+ }
+ }
+
+ /* Something above might have run a few rounds of the main loop. Check
+ * that shell->display is still there, otherwise ignore this as the app
+ * is being torn down for quitting.
+ */
+ if (warn && shell->display)
+ {
+ gimp_message (shell->display->gimp, G_OBJECT (shell->display),
+ GIMP_MESSAGE_ERROR,
+ _("Opening '%s' failed:\n\n%s"),
+ gimp_file_get_utf8_name (file), error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (file);
+ }
+
+ if (image)
+ gimp_display_shell_dnd_flush (shell, image);
+
+ g_clear_object (&image);
+}
+
+static void
+gimp_display_shell_drop_component (GtkWidget *widget,
+ gint x,
+ gint y,
+ GimpImage *image,
+ GimpChannelType component,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *dest_image = gimp_display_get_image (shell->display);
+ GimpChannel *channel;
+ GimpItem *new_item;
+ const gchar *desc;
+
+ GIMP_LOG (DND, NULL);
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! dest_image)
+ {
+ dest_image = gimp_image_new_from_component (image->gimp,
+ image, component);
+ gimp_create_display (dest_image->gimp, dest_image, GIMP_UNIT_PIXEL, 1.0,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget));
+ g_object_unref (dest_image);
+
+ return;
+ }
+
+ channel = gimp_channel_new_from_component (image, component, NULL, NULL);
+
+ new_item = gimp_item_convert (GIMP_ITEM (channel),
+ dest_image, GIMP_TYPE_LAYER);
+ g_object_unref (channel);
+
+ if (new_item)
+ {
+ GimpLayer *new_layer = GIMP_LAYER (new_item);
+
+ gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component,
+ NULL, NULL, &desc, NULL);
+ gimp_object_take_name (GIMP_OBJECT (new_layer),
+ g_strdup_printf (_("%s Channel Copy"), desc));
+
+ gimp_image_undo_group_start (dest_image, GIMP_UNDO_GROUP_EDIT_PASTE,
+ _("Drop New Layer"));
+
+ gimp_display_shell_dnd_position_item (shell, image, new_item);
+
+ gimp_image_add_layer (dest_image, new_layer,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ gimp_image_undo_group_end (dest_image);
+
+ gimp_display_shell_dnd_flush (shell, dest_image);
+ }
+}
+
+static void
+gimp_display_shell_drop_pixbuf (GtkWidget *widget,
+ gint x,
+ gint y,
+ GdkPixbuf *pixbuf,
+ gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpLayer *new_layer;
+ gboolean has_alpha = FALSE;
+
+ GIMP_LOG (DND, NULL);
+
+ if (shell->display->gimp->busy)
+ return;
+
+ if (! image)
+ {
+ image = gimp_image_new_from_pixbuf (shell->display->gimp, pixbuf,
+ _("Dropped Buffer"));
+ gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget));
+ g_object_unref (image);
+
+ return;
+ }
+
+ if (gdk_pixbuf_get_n_channels (pixbuf) == 2 ||
+ gdk_pixbuf_get_n_channels (pixbuf) == 4)
+ {
+ has_alpha = TRUE;
+ }
+
+ new_layer =
+ gimp_layer_new_from_pixbuf (pixbuf, image,
+ gimp_image_get_layer_format (image, has_alpha),
+ _("Dropped Buffer"),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image));
+
+ if (new_layer)
+ {
+ GimpItem *new_item = GIMP_ITEM (new_layer);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE,
+ _("Drop New Layer"));
+
+ gimp_display_shell_dnd_position_item (shell, image, new_item);
+
+ gimp_image_add_layer (image, new_layer,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_display_shell_dnd_flush (shell, image);
+ }
+}
diff --git a/app/display/gimpdisplayshell-dnd.h b/app/display/gimpdisplayshell-dnd.h
new file mode 100644
index 0000000..d1f6e99
--- /dev/null
+++ b/app/display/gimpdisplayshell-dnd.h
@@ -0,0 +1,25 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_DND_H__
+#define __GIMP_DISPLAY_SHELL_DND_H__
+
+
+void gimp_display_shell_dnd_init (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_DND_H__ */
diff --git a/app/display/gimpdisplayshell-draw.c b/app/display/gimpdisplayshell-draw.c
new file mode 100644
index 0000000..d6a63d5
--- /dev/null
+++ b/app/display/gimpdisplayshell-draw.c
@@ -0,0 +1,256 @@
+/* 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 "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimp-cairo.h"
+#include "core/gimp-utils.h"
+#include "core/gimpimage.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvas-style.h"
+#include "gimpcanvaspath.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-draw.h"
+#include "gimpdisplayshell-render.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpdisplayxfer.h"
+
+#ifdef GDK_WINDOWING_QUARTZ
+#import <AppKit/AppKit.h>
+#endif
+
+/* #define GIMP_DISPLAY_RENDER_ENABLE_SCALING 1 */
+
+
+/* public functions */
+
+void
+gimp_display_shell_draw_selection_out (GimpDisplayShell *shell,
+ cairo_t *cr,
+ GimpSegment *segs,
+ gint n_segs)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (segs != NULL && n_segs > 0);
+
+ gimp_canvas_set_selection_out_style (shell->canvas, cr,
+ shell->offset_x, shell->offset_y);
+
+ gimp_cairo_segments (cr, segs, n_segs);
+ cairo_stroke (cr);
+}
+
+void
+gimp_display_shell_draw_selection_in (GimpDisplayShell *shell,
+ cairo_t *cr,
+ cairo_pattern_t *mask,
+ gint index)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (mask != NULL);
+
+ gimp_canvas_set_selection_in_style (shell->canvas, cr, index,
+ shell->offset_x, shell->offset_y);
+
+ cairo_mask (cr, mask);
+}
+
+void
+gimp_display_shell_draw_checkerboard (GimpDisplayShell *shell,
+ cairo_t *cr)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (cr != NULL);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (G_UNLIKELY (! shell->checkerboard))
+ {
+ GimpCheckSize check_size;
+ GimpCheckType check_type;
+ guchar check_light;
+ guchar check_dark;
+ GimpRGB light;
+ GimpRGB dark;
+
+ g_object_get (shell->display->config,
+ "transparency-size", &check_size,
+ "transparency-type", &check_type,
+ NULL);
+
+ gimp_checks_get_shades (check_type, &check_light, &check_dark);
+ gimp_rgb_set_uchar (&light, check_light, check_light, check_light);
+ gimp_rgb_set_uchar (&dark, check_dark, check_dark, check_dark);
+
+ shell->checkerboard =
+ gimp_cairo_checkerboard_create (cr,
+ 1 << (check_size + 2), &light, &dark);
+ }
+
+ cairo_translate (cr, - shell->offset_x, - shell->offset_y);
+
+ if (gimp_image_get_component_visible (image, GIMP_CHANNEL_ALPHA))
+ cairo_set_source (cr, shell->checkerboard);
+ else
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+
+ cairo_paint (cr);
+}
+
+void
+gimp_display_shell_draw_image (GimpDisplayShell *shell,
+ cairo_t *cr,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ gdouble chunk_width;
+ gdouble chunk_height;
+ gdouble scale = 1.0;
+ gint n_rows;
+ gint n_cols;
+ gint r, c;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (gimp_display_get_image (shell->display));
+ g_return_if_fail (cr != NULL);
+
+ /* display the image in RENDER_BUF_WIDTH x RENDER_BUF_HEIGHT
+ * maximally-sized image-space chunks. adjust the screen-space
+ * chunk size as necessary, to accommodate for the display
+ * transform and window scale factor.
+ */
+ chunk_width = GIMP_DISPLAY_RENDER_BUF_WIDTH;
+ chunk_height = GIMP_DISPLAY_RENDER_BUF_HEIGHT;
+
+#ifdef GIMP_DISPLAY_RENDER_ENABLE_SCALING
+ /* if we had this future API, things would look pretty on hires (retina) */
+ scale *=
+ gdk_window_get_scale_factor (
+ gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (shell))));
+#elif defined(GDK_WINDOWING_QUARTZ)
+ /* gtk2/osx retina support */
+ if ([
+ [NSScreen mainScreen]
+ respondsToSelector: @selector(backingScaleFactor)
+ ]) {
+ for (NSScreen * screen in [NSScreen screens]) {
+ float s = [screen backingScaleFactor];
+ if (s > scale) scale = s;
+ }
+ }
+#endif
+
+ scale = MIN (scale, GIMP_DISPLAY_RENDER_MAX_SCALE);
+ scale *= MAX (shell->scale_x, shell->scale_y);
+
+ if (scale != shell->scale_x)
+ chunk_width = (chunk_width - 1.0) * (shell->scale_x / scale);
+ if (scale != shell->scale_y)
+ chunk_height = (chunk_height - 1.0) * (shell->scale_y / scale);
+
+ if (shell->rotate_untransform)
+ {
+ gdouble a = shell->rotate_angle * G_PI / 180.0;
+
+ chunk_width = chunk_height = (MIN (chunk_width, chunk_height) - 1.0) /
+ (fabs (sin (a)) + fabs (cos (a)));
+ }
+
+ /* divide the painted area to evenly-sized chunks */
+ n_rows = ceil (h / floor (chunk_height));
+ n_cols = ceil (w / floor (chunk_width));
+
+ for (r = 0; r < n_rows; r++)
+ {
+ gint y1 = y + (2 * r * h + n_rows) / (2 * n_rows);
+ gint y2 = y + (2 * (r + 1) * h + n_rows) / (2 * n_rows);
+
+ for (c = 0; c < n_cols; c++)
+ {
+ gint x1 = x + (2 * c * w + n_cols) / (2 * n_cols);
+ gint x2 = x + (2 * (c + 1) * w + n_cols) / (2 * n_cols);
+ gdouble ix1, iy1;
+ gdouble ix2, iy2;
+ gint ix, iy;
+ gint iw, ih;
+
+ /* map chunk from screen space to scaled image space */
+ gimp_display_shell_untransform_bounds_with_scale (
+ shell, scale,
+ x1, y1, x2, y2,
+ &ix1, &iy1, &ix2, &iy2);
+
+ ix = floor (ix1);
+ iy = floor (iy1);
+ iw = ceil (ix2) - ix;
+ ih = ceil (iy2) - iy;
+
+ cairo_save (cr);
+
+ /* clip to chunk bounds, in screen space */
+ cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+ cairo_clip (cr);
+
+ /* transform to scaled image space, and apply uneven scaling */
+ if (shell->rotate_transform)
+ cairo_transform (cr, shell->rotate_transform);
+ cairo_translate (cr, -shell->offset_x, -shell->offset_y);
+ cairo_scale (cr, shell->scale_x / scale, shell->scale_y / scale);
+
+ /* render image */
+ gimp_display_shell_render (shell, cr, ix, iy, iw, ih, scale);
+
+ cairo_restore (cr);
+
+ /* if the GIMP_BRICK_WALL environment variable is defined,
+ * show chunk bounds
+ */
+ {
+ static gint brick_wall = -1;
+
+ if (brick_wall < 0)
+ brick_wall = (g_getenv ("GIMP_BRICK_WALL") != NULL);
+
+ if (brick_wall)
+ {
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+ cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+ cairo_stroke (cr);
+ }
+ }
+ }
+ }
+}
diff --git a/app/display/gimpdisplayshell-draw.h b/app/display/gimpdisplayshell-draw.h
new file mode 100644
index 0000000..05dd2c1
--- /dev/null
+++ b/app/display/gimpdisplayshell-draw.h
@@ -0,0 +1,41 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_DRAW_H__
+#define __GIMP_DISPLAY_SHELL_DRAW_H__
+
+
+void gimp_display_shell_draw_selection_out (GimpDisplayShell *shell,
+ cairo_t *cr,
+ GimpSegment *segs,
+ gint n_segs);
+void gimp_display_shell_draw_selection_in (GimpDisplayShell *shell,
+ cairo_t *cr,
+ cairo_pattern_t *mask,
+ gint index);
+
+void gimp_display_shell_draw_checkerboard (GimpDisplayShell *shell,
+ cairo_t *cr);
+void gimp_display_shell_draw_image (GimpDisplayShell *shell,
+ cairo_t *cr,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_DRAW_H__ */
diff --git a/app/display/gimpdisplayshell-expose.c b/app/display/gimpdisplayshell-expose.c
new file mode 100644
index 0000000..2320a90
--- /dev/null
+++ b/app/display/gimpdisplayshell-expose.c
@@ -0,0 +1,76 @@
+/* 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 "display-types.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+
+
+void
+gimp_display_shell_expose_area (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gtk_widget_queue_draw_area (shell->canvas, x, y, w, h);
+}
+
+void
+gimp_display_shell_expose_region (GimpDisplayShell *shell,
+ cairo_region_t *region)
+{
+ GdkWindow *window;
+ gint n_rectangles;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (region != NULL);
+
+ if (! gtk_widget_get_realized (shell->canvas))
+ return;
+
+ window = gtk_widget_get_window (shell->canvas);
+ n_rectangles = cairo_region_num_rectangles (region);
+
+ for (i = 0; i < n_rectangles; i++)
+ {
+ cairo_rectangle_int_t rectangle;
+
+ cairo_region_get_rectangle (region, i, &rectangle);
+
+ gdk_window_invalidate_rect (window,
+ (GdkRectangle *) &rectangle,
+ TRUE);
+ }
+}
+
+void
+gimp_display_shell_expose_full (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gtk_widget_queue_draw (shell->canvas);
+}
diff --git a/app/display/gimpdisplayshell-expose.h b/app/display/gimpdisplayshell-expose.h
new file mode 100644
index 0000000..36d3519
--- /dev/null
+++ b/app/display/gimpdisplayshell-expose.h
@@ -0,0 +1,32 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_EXPOSE_H__
+#define __GIMP_DISPLAY_SHELL_EXPOSE_H__
+
+
+void gimp_display_shell_expose_area (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+void gimp_display_shell_expose_region (GimpDisplayShell *shell,
+ cairo_region_t *region);
+void gimp_display_shell_expose_full (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_EXPOSE_H__ */
diff --git a/app/display/gimpdisplayshell-filter-dialog.c b/app/display/gimpdisplayshell-filter-dialog.c
new file mode 100644
index 0000000..720529c
--- /dev/null
+++ b/app/display/gimpdisplayshell-filter-dialog.c
@@ -0,0 +1,151 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1999 Manish Singh
+ *
+ * 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 "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpviewable.h"
+
+#include "widgets/gimpcolordisplayeditor.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpviewabledialog.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-filter.h"
+#include "gimpdisplayshell-filter-dialog.h"
+
+#include "gimp-intl.h"
+
+
+typedef struct
+{
+ GimpDisplayShell *shell;
+ GtkWidget *dialog;
+
+ GimpColorDisplayStack *old_stack;
+} ColorDisplayDialog;
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_filter_dialog_response (GtkWidget *widget,
+ gint response_id,
+ ColorDisplayDialog *cdd);
+
+static void gimp_display_shell_filter_dialog_free (ColorDisplayDialog *cdd);
+
+
+/* public functions */
+
+GtkWidget *
+gimp_display_shell_filter_dialog_new (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+ ColorDisplayDialog *cdd;
+ GtkWidget *editor;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ image = gimp_display_get_image (shell->display);
+
+ cdd = g_slice_new0 (ColorDisplayDialog);
+
+ cdd->shell = shell;
+ cdd->dialog = gimp_viewable_dialog_new (GIMP_VIEWABLE (image),
+ gimp_get_user_context (shell->display->gimp),
+ _("Color Display Filters"),
+ "gimp-display-filters",
+ GIMP_ICON_DISPLAY_FILTER,
+ _("Configure Color Display Filters"),
+ GTK_WIDGET (cdd->shell),
+ gimp_standard_help_func,
+ GIMP_HELP_DISPLAY_FILTER_DIALOG,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_OK"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (cdd->dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (cdd->dialog), TRUE);
+
+ g_object_weak_ref (G_OBJECT (cdd->dialog),
+ (GWeakNotify) gimp_display_shell_filter_dialog_free, cdd);
+
+ g_signal_connect (cdd->dialog, "response",
+ G_CALLBACK (gimp_display_shell_filter_dialog_response),
+ cdd);
+
+ if (shell->filter_stack)
+ {
+ cdd->old_stack = gimp_color_display_stack_clone (shell->filter_stack);
+
+ g_object_weak_ref (G_OBJECT (cdd->dialog),
+ (GWeakNotify) g_object_unref, cdd->old_stack);
+ }
+ else
+ {
+ GimpColorDisplayStack *stack = gimp_color_display_stack_new ();
+
+ gimp_display_shell_filter_set (shell, stack);
+ g_object_unref (stack);
+ }
+
+ editor = gimp_color_display_editor_new (shell->display->gimp,
+ shell->filter_stack,
+ gimp_display_shell_get_color_config (shell),
+ GIMP_COLOR_MANAGED (shell));
+ gtk_container_set_border_width (GTK_CONTAINER (editor), 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (cdd->dialog))),
+ editor, TRUE, TRUE, 0);
+ gtk_widget_show (editor);
+
+ return cdd->dialog;
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_filter_dialog_response (GtkWidget *widget,
+ gint response_id,
+ ColorDisplayDialog *cdd)
+{
+ if (response_id != GTK_RESPONSE_OK)
+ gimp_display_shell_filter_set (cdd->shell, cdd->old_stack);
+
+ gtk_widget_destroy (GTK_WIDGET (cdd->dialog));
+}
+
+static void
+gimp_display_shell_filter_dialog_free (ColorDisplayDialog *cdd)
+{
+ g_slice_free (ColorDisplayDialog, cdd);
+}
diff --git a/app/display/gimpdisplayshell-filter-dialog.h b/app/display/gimpdisplayshell-filter-dialog.h
new file mode 100644
index 0000000..9f73a8b
--- /dev/null
+++ b/app/display/gimpdisplayshell-filter-dialog.h
@@ -0,0 +1,25 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1999 Manish Singh
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_FILTER_DIALOG_H__
+#define __GIMP_DISPLAY_SHELL_FILTER_DIALOG_H__
+
+
+GtkWidget * gimp_display_shell_filter_dialog_new (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_FILTER_DIALOG_H__ */
diff --git a/app/display/gimpdisplayshell-filter.c b/app/display/gimpdisplayshell-filter.c
new file mode 100644
index 0000000..1cf60e8
--- /dev/null
+++ b/app/display/gimpdisplayshell-filter.c
@@ -0,0 +1,116 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1999 Manish Singh
+ *
+ * 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-filter.h"
+#include "gimpdisplayshell-profile.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_filter_changed (GimpColorDisplayStack *stack,
+ GimpDisplayShell *shell);
+
+
+/* public functions */
+
+void
+gimp_display_shell_filter_set (GimpDisplayShell *shell,
+ GimpColorDisplayStack *stack)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (stack == NULL || GIMP_IS_COLOR_DISPLAY_STACK (stack));
+
+ if (stack == shell->filter_stack)
+ return;
+
+ if (shell->filter_stack)
+ {
+ g_signal_handlers_disconnect_by_func (shell->filter_stack,
+ gimp_display_shell_filter_changed,
+ shell);
+ }
+
+ g_set_object (&shell->filter_stack, stack);
+
+ if (shell->filter_stack)
+ {
+ g_signal_connect (shell->filter_stack, "changed",
+ G_CALLBACK (gimp_display_shell_filter_changed),
+ shell);
+ }
+
+ gimp_display_shell_filter_changed (NULL, shell);
+}
+
+gboolean
+gimp_display_shell_has_filter (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ if (shell->filter_stack)
+ {
+ GList *iter;
+
+ for (iter = shell->filter_stack->filters; iter; iter = g_list_next (iter))
+ {
+ if (gimp_color_display_get_enabled (GIMP_COLOR_DISPLAY (iter->data)))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_display_shell_filter_changed_idle (gpointer data)
+{
+ GimpDisplayShell *shell = data;
+
+ gimp_display_shell_profile_update (shell);
+ gimp_display_shell_expose_full (shell);
+
+ shell->filter_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+gimp_display_shell_filter_changed (GimpColorDisplayStack *stack,
+ GimpDisplayShell *shell)
+{
+ if (shell->filter_idle_id)
+ g_source_remove (shell->filter_idle_id);
+
+ shell->filter_idle_id =
+ g_idle_add_full (G_PRIORITY_LOW,
+ gimp_display_shell_filter_changed_idle,
+ shell, NULL);
+}
diff --git a/app/display/gimpdisplayshell-filter.h b/app/display/gimpdisplayshell-filter.h
new file mode 100644
index 0000000..7543a8b
--- /dev/null
+++ b/app/display/gimpdisplayshell-filter.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1999 Manish Singh
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_FILTER_H__
+#define __GIMP_DISPLAY_SHELL_FILTER_H__
+
+
+void gimp_display_shell_filter_set (GimpDisplayShell *shell,
+ GimpColorDisplayStack *stack);
+
+gboolean gimp_display_shell_has_filter (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_FILTER_H__ */
diff --git a/app/display/gimpdisplayshell-grab.c b/app/display/gimpdisplayshell-grab.c
new file mode 100644
index 0000000..d3bb550
--- /dev/null
+++ b/app/display/gimpdisplayshell-grab.c
@@ -0,0 +1,124 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdisplayshell-grab.c
+ *
+ * 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 "display-types.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-grab.h"
+
+
+gboolean
+gimp_display_shell_pointer_grab (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ GdkEventMask event_mask)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+ g_return_val_if_fail (shell->pointer_grabbed == FALSE, FALSE);
+
+ if (event)
+ {
+ GdkGrabStatus status;
+
+ status = gdk_pointer_grab (gtk_widget_get_window (shell->canvas),
+ FALSE, event_mask, NULL, NULL,
+ gdk_event_get_time (event));
+
+ if (status != GDK_GRAB_SUCCESS)
+ {
+ g_printerr ("%s: gdk_pointer_grab failed with status %d\n",
+ G_STRFUNC, status);
+ return FALSE;
+ }
+
+ shell->pointer_grab_time = gdk_event_get_time (event);
+ }
+
+ gtk_grab_add (shell->canvas);
+
+ shell->pointer_grabbed = TRUE;
+
+ return TRUE;
+}
+
+void
+gimp_display_shell_pointer_ungrab (GimpDisplayShell *shell,
+ const GdkEvent *event)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->pointer_grabbed == TRUE);
+
+ gtk_grab_remove (shell->canvas);
+
+ if (event)
+ {
+ gdk_display_pointer_ungrab (gtk_widget_get_display (shell->canvas),
+ shell->pointer_grab_time);
+
+ shell->pointer_grab_time = 0;
+ }
+
+ shell->pointer_grabbed = FALSE;
+}
+
+gboolean
+gimp_display_shell_keyboard_grab (GimpDisplayShell *shell,
+ const GdkEvent *event)
+{
+ GdkGrabStatus status;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (shell->keyboard_grabbed == FALSE, FALSE);
+
+ status = gdk_keyboard_grab (gtk_widget_get_window (shell->canvas),
+ FALSE,
+ gdk_event_get_time (event));
+
+ if (status != GDK_GRAB_SUCCESS)
+ {
+ g_printerr ("%s: gdk_keyboard_grab failed with status %d\n",
+ G_STRFUNC, status);
+ return FALSE;
+ }
+
+ shell->keyboard_grabbed = TRUE;
+ shell->keyboard_grab_time = gdk_event_get_time (event);
+
+ return TRUE;
+}
+
+void
+gimp_display_shell_keyboard_ungrab (GimpDisplayShell *shell,
+ const GdkEvent *event)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (event != NULL);
+ g_return_if_fail (shell->keyboard_grabbed == TRUE);
+
+ gdk_display_keyboard_ungrab (gtk_widget_get_display (shell->canvas),
+ shell->keyboard_grab_time);
+
+ shell->keyboard_grabbed = FALSE;
+ shell->keyboard_grab_time = 0;
+}
diff --git a/app/display/gimpdisplayshell-grab.h b/app/display/gimpdisplayshell-grab.h
new file mode 100644
index 0000000..915cf34
--- /dev/null
+++ b/app/display/gimpdisplayshell-grab.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdisplayshell-grab.h
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_GRAB_H__
+#define __GIMP_DISPLAY_SHELL_GRAB_H__
+
+
+gboolean gimp_display_shell_pointer_grab (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ GdkEventMask event_mask);
+void gimp_display_shell_pointer_ungrab (GimpDisplayShell *shell,
+ const GdkEvent *event);
+
+gboolean gimp_display_shell_keyboard_grab (GimpDisplayShell *shell,
+ const GdkEvent *event);
+void gimp_display_shell_keyboard_ungrab (GimpDisplayShell *shell,
+ const GdkEvent *event);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_GRAB_H__ */
diff --git a/app/display/gimpdisplayshell-handlers.c b/app/display/gimpdisplayshell-handlers.c
new file mode 100644
index 0000000..7680467
--- /dev/null
+++ b/app/display/gimpdisplayshell-handlers.c
@@ -0,0 +1,1239 @@
+/* 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 "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayoptions.h"
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimp-cairo.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-grid.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-quick-mask.h"
+#include "core/gimpimage-sample-points.h"
+#include "core/gimpitem.h"
+#include "core/gimpitemstack.h"
+#include "core/gimpsamplepoint.h"
+#include "core/gimptreehandler.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvascanvasboundary.h"
+#include "gimpcanvasguide.h"
+#include "gimpcanvaslayerboundary.h"
+#include "gimpcanvaspath.h"
+#include "gimpcanvasproxygroup.h"
+#include "gimpcanvassamplepoint.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-callbacks.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-handlers.h"
+#include "gimpdisplayshell-icon.h"
+#include "gimpdisplayshell-profile.h"
+#include "gimpdisplayshell-rulers.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-selection.h"
+#include "gimpdisplayshell-title.h"
+#include "gimpimagewindow.h"
+#include "gimpstatusbar.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_clean_dirty_handler (GimpImage *image,
+ GimpDirtyMask dirty_mask,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_undo_event_handler (GimpImage *image,
+ GimpUndoEvent event,
+ GimpUndo *undo,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_grid_notify_handler (GimpGrid *grid,
+ GParamSpec *pspec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_name_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_selection_invalidate_handler
+ (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_component_visibility_changed_handler
+ (GimpImage *image,
+ GimpChannelType channel,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_size_changed_detailed_handler
+ (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_resolution_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_quick_mask_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_guide_add_handler (GimpImage *image,
+ GimpGuide *guide,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_guide_remove_handler (GimpImage *image,
+ GimpGuide *guide,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_guide_move_handler (GimpImage *image,
+ GimpGuide *guide,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_sample_point_add_handler (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_sample_point_remove_handler(GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_sample_point_move_handler (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_invalidate_preview_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_mode_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_precision_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_profile_changed_handler (GimpColorManaged *image,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_saved_handler (GimpImage *image,
+ GFile *file,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_exported_handler (GimpImage *image,
+ GFile *file,
+ GimpDisplayShell *shell);
+
+static void gimp_display_shell_active_vectors_handler (GimpImage *image,
+ GimpDisplayShell *shell);
+
+static void gimp_display_shell_vectors_freeze_handler (GimpVectors *vectors,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_vectors_thaw_handler (GimpVectors *vectors,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_vectors_visible_handler (GimpVectors *vectors,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_vectors_add_handler (GimpContainer *container,
+ GimpVectors *vectors,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_vectors_remove_handler (GimpContainer *container,
+ GimpVectors *vectors,
+ GimpDisplayShell *shell);
+
+static void gimp_display_shell_check_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_title_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_nav_size_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_monitor_res_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_padding_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_ants_speed_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_quality_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_color_config_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_display_changed_handler (GimpContext *context,
+ GimpDisplay *display,
+ GimpDisplayShell *shell);
+
+
+/* public functions */
+
+void
+gimp_display_shell_connect (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+ GimpContainer *vectors;
+ GimpDisplayConfig *config;
+ GimpColorConfig *color_config;
+ GimpContext *user_context;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_DISPLAY (shell->display));
+
+ image = gimp_display_get_image (shell->display);
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ vectors = gimp_image_get_vectors (image);
+
+ config = shell->display->config;
+ color_config = GIMP_CORE_CONFIG (config)->color_management;
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ g_signal_connect (image, "clean",
+ G_CALLBACK (gimp_display_shell_clean_dirty_handler),
+ shell);
+ g_signal_connect (image, "dirty",
+ G_CALLBACK (gimp_display_shell_clean_dirty_handler),
+ shell);
+ g_signal_connect (image, "undo-event",
+ G_CALLBACK (gimp_display_shell_undo_event_handler),
+ shell);
+
+ g_signal_connect (gimp_image_get_grid (image), "notify",
+ G_CALLBACK (gimp_display_shell_grid_notify_handler),
+ shell);
+ g_object_set (shell->grid, "grid", gimp_image_get_grid (image), NULL);
+
+ g_signal_connect (image, "name-changed",
+ G_CALLBACK (gimp_display_shell_name_changed_handler),
+ shell);
+ g_signal_connect (image, "selection-invalidate",
+ G_CALLBACK (gimp_display_shell_selection_invalidate_handler),
+ shell);
+ g_signal_connect (image, "component-visibility-changed",
+ G_CALLBACK (gimp_display_shell_component_visibility_changed_handler),
+ shell);
+ g_signal_connect (image, "size-changed-detailed",
+ G_CALLBACK (gimp_display_shell_size_changed_detailed_handler),
+ shell);
+ g_signal_connect (image, "resolution-changed",
+ G_CALLBACK (gimp_display_shell_resolution_changed_handler),
+ shell);
+ g_signal_connect (image, "quick-mask-changed",
+ G_CALLBACK (gimp_display_shell_quick_mask_changed_handler),
+ shell);
+
+ g_signal_connect (image, "guide-added",
+ G_CALLBACK (gimp_display_shell_guide_add_handler),
+ shell);
+ g_signal_connect (image, "guide-removed",
+ G_CALLBACK (gimp_display_shell_guide_remove_handler),
+ shell);
+ g_signal_connect (image, "guide-moved",
+ G_CALLBACK (gimp_display_shell_guide_move_handler),
+ shell);
+ for (list = gimp_image_get_guides (image);
+ list;
+ list = g_list_next (list))
+ {
+ gimp_display_shell_guide_add_handler (image, list->data, shell);
+ }
+
+ g_signal_connect (image, "sample-point-added",
+ G_CALLBACK (gimp_display_shell_sample_point_add_handler),
+ shell);
+ g_signal_connect (image, "sample-point-removed",
+ G_CALLBACK (gimp_display_shell_sample_point_remove_handler),
+ shell);
+ g_signal_connect (image, "sample-point-moved",
+ G_CALLBACK (gimp_display_shell_sample_point_move_handler),
+ shell);
+ for (list = gimp_image_get_sample_points (image);
+ list;
+ list = g_list_next (list))
+ {
+ gimp_display_shell_sample_point_add_handler (image, list->data, shell);
+ }
+
+ g_signal_connect (image, "invalidate-preview",
+ G_CALLBACK (gimp_display_shell_invalidate_preview_handler),
+ shell);
+ g_signal_connect (image, "mode-changed",
+ G_CALLBACK (gimp_display_shell_mode_changed_handler),
+ shell);
+ g_signal_connect (image, "precision-changed",
+ G_CALLBACK (gimp_display_shell_precision_changed_handler),
+ shell);
+ g_signal_connect (image, "profile-changed",
+ G_CALLBACK (gimp_display_shell_profile_changed_handler),
+ shell);
+ g_signal_connect (image, "saved",
+ G_CALLBACK (gimp_display_shell_saved_handler),
+ shell);
+ g_signal_connect (image, "exported",
+ G_CALLBACK (gimp_display_shell_exported_handler),
+ shell);
+
+ g_signal_connect (image, "active-vectors-changed",
+ G_CALLBACK (gimp_display_shell_active_vectors_handler),
+ shell);
+
+ shell->vectors_freeze_handler =
+ gimp_tree_handler_connect (vectors, "freeze",
+ G_CALLBACK (gimp_display_shell_vectors_freeze_handler),
+ shell);
+ shell->vectors_thaw_handler =
+ gimp_tree_handler_connect (vectors, "thaw",
+ G_CALLBACK (gimp_display_shell_vectors_thaw_handler),
+ shell);
+ shell->vectors_visible_handler =
+ gimp_tree_handler_connect (vectors, "visibility-changed",
+ G_CALLBACK (gimp_display_shell_vectors_visible_handler),
+ shell);
+
+ g_signal_connect (vectors, "add",
+ G_CALLBACK (gimp_display_shell_vectors_add_handler),
+ shell);
+ g_signal_connect (vectors, "remove",
+ G_CALLBACK (gimp_display_shell_vectors_remove_handler),
+ shell);
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (vectors));
+ list;
+ list = g_list_next (list))
+ {
+ gimp_display_shell_vectors_add_handler (vectors, list->data, shell);
+ }
+
+ g_signal_connect (config,
+ "notify::transparency-size",
+ G_CALLBACK (gimp_display_shell_check_notify_handler),
+ shell);
+ g_signal_connect (config,
+ "notify::transparency-type",
+ G_CALLBACK (gimp_display_shell_check_notify_handler),
+ shell);
+
+ g_signal_connect (config,
+ "notify::image-title-format",
+ G_CALLBACK (gimp_display_shell_title_notify_handler),
+ shell);
+ g_signal_connect (config,
+ "notify::image-status-format",
+ G_CALLBACK (gimp_display_shell_title_notify_handler),
+ shell);
+ g_signal_connect (config,
+ "notify::navigation-preview-size",
+ G_CALLBACK (gimp_display_shell_nav_size_notify_handler),
+ shell);
+ g_signal_connect (config,
+ "notify::monitor-resolution-from-windowing-system",
+ G_CALLBACK (gimp_display_shell_monitor_res_notify_handler),
+ shell);
+ g_signal_connect (config,
+ "notify::monitor-xresolution",
+ G_CALLBACK (gimp_display_shell_monitor_res_notify_handler),
+ shell);
+ g_signal_connect (config,
+ "notify::monitor-yresolution",
+ G_CALLBACK (gimp_display_shell_monitor_res_notify_handler),
+ shell);
+
+ g_signal_connect (config->default_view,
+ "notify::padding-mode",
+ G_CALLBACK (gimp_display_shell_padding_notify_handler),
+ shell);
+ g_signal_connect (config->default_view,
+ "notify::padding-color",
+ G_CALLBACK (gimp_display_shell_padding_notify_handler),
+ shell);
+ g_signal_connect (config->default_fullscreen_view,
+ "notify::padding-mode",
+ G_CALLBACK (gimp_display_shell_padding_notify_handler),
+ shell);
+ g_signal_connect (config->default_fullscreen_view,
+ "notify::padding-color",
+ G_CALLBACK (gimp_display_shell_padding_notify_handler),
+ shell);
+
+ g_signal_connect (config,
+ "notify::marching-ants-speed",
+ G_CALLBACK (gimp_display_shell_ants_speed_notify_handler),
+ shell);
+
+ g_signal_connect (config,
+ "notify::zoom-quality",
+ G_CALLBACK (gimp_display_shell_quality_notify_handler),
+ shell);
+
+ g_signal_connect (color_config, "notify",
+ G_CALLBACK (gimp_display_shell_color_config_notify_handler),
+ shell);
+
+ g_signal_connect (user_context, "display-changed",
+ G_CALLBACK (gimp_display_shell_display_changed_handler),
+ shell);
+
+ gimp_display_shell_active_vectors_handler (image, shell);
+ gimp_display_shell_invalidate_preview_handler (image, shell);
+ gimp_display_shell_quick_mask_changed_handler (image, shell);
+ gimp_display_shell_profile_changed_handler (GIMP_COLOR_MANAGED (image),
+ shell);
+ gimp_display_shell_color_config_notify_handler (G_OBJECT (color_config),
+ NULL, /* sync all */
+ shell);
+
+ gimp_canvas_layer_boundary_set_layer (GIMP_CANVAS_LAYER_BOUNDARY (shell->layer_boundary),
+ gimp_image_get_active_layer (image));
+
+ gimp_canvas_canvas_boundary_set_image (GIMP_CANVAS_CANVAS_BOUNDARY (shell->canvas_boundary),
+ image);
+
+ if (shell->show_all)
+ {
+ gimp_image_inc_show_all_count (image);
+
+ gimp_image_flush (image);
+ }
+}
+
+void
+gimp_display_shell_disconnect (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+ GimpContainer *vectors;
+ GimpDisplayConfig *config;
+ GimpColorConfig *color_config;
+ GimpContext *user_context;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_DISPLAY (shell->display));
+
+ image = gimp_display_get_image (shell->display);
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ vectors = gimp_image_get_vectors (image);
+
+ config = shell->display->config;
+ color_config = GIMP_CORE_CONFIG (config)->color_management;
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ gimp_display_shell_icon_update_stop (shell);
+
+ gimp_canvas_layer_boundary_set_layer (GIMP_CANVAS_LAYER_BOUNDARY (shell->layer_boundary),
+ NULL);
+
+ gimp_canvas_canvas_boundary_set_image (GIMP_CANVAS_CANVAS_BOUNDARY (shell->canvas_boundary),
+ NULL);
+
+ g_signal_handlers_disconnect_by_func (user_context,
+ gimp_display_shell_display_changed_handler,
+ shell);
+
+ g_signal_handlers_disconnect_by_func (color_config,
+ gimp_display_shell_color_config_notify_handler,
+ shell);
+ shell->color_config_set = FALSE;
+
+ g_signal_handlers_disconnect_by_func (config,
+ gimp_display_shell_quality_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config,
+ gimp_display_shell_ants_speed_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config->default_fullscreen_view,
+ gimp_display_shell_padding_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config->default_view,
+ gimp_display_shell_padding_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config,
+ gimp_display_shell_monitor_res_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config,
+ gimp_display_shell_nav_size_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config,
+ gimp_display_shell_title_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (config,
+ gimp_display_shell_check_notify_handler,
+ shell);
+
+ g_signal_handlers_disconnect_by_func (vectors,
+ gimp_display_shell_vectors_remove_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (vectors,
+ gimp_display_shell_vectors_add_handler,
+ shell);
+
+ gimp_tree_handler_disconnect (shell->vectors_visible_handler);
+ shell->vectors_visible_handler = NULL;
+
+ gimp_tree_handler_disconnect (shell->vectors_thaw_handler);
+ shell->vectors_thaw_handler = NULL;
+
+ gimp_tree_handler_disconnect (shell->vectors_freeze_handler);
+ shell->vectors_freeze_handler = NULL;
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_active_vectors_handler,
+ shell);
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (vectors));
+ list;
+ list = g_list_next (list))
+ {
+ gimp_canvas_proxy_group_remove_item (GIMP_CANVAS_PROXY_GROUP (shell->vectors),
+ list->data);
+ }
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_exported_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_saved_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_profile_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_precision_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_mode_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_invalidate_preview_handler,
+ shell);
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_guide_add_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_guide_remove_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_guide_move_handler,
+ shell);
+ for (list = gimp_image_get_guides (image);
+ list;
+ list = g_list_next (list))
+ {
+ gimp_canvas_proxy_group_remove_item (GIMP_CANVAS_PROXY_GROUP (shell->guides),
+ list->data);
+ }
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_sample_point_add_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_sample_point_remove_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_sample_point_move_handler,
+ shell);
+ for (list = gimp_image_get_sample_points (image);
+ list;
+ list = g_list_next (list))
+ {
+ gimp_canvas_proxy_group_remove_item (GIMP_CANVAS_PROXY_GROUP (shell->sample_points),
+ list->data);
+ }
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_quick_mask_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_resolution_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_component_visibility_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_size_changed_detailed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_selection_invalidate_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_name_changed_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (gimp_image_get_grid (image),
+ gimp_display_shell_grid_notify_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_undo_event_handler,
+ shell);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_display_shell_clean_dirty_handler,
+ shell);
+
+ if (shell->show_all)
+ {
+ gimp_image_dec_show_all_count (image);
+
+ gimp_image_flush (image);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_clean_dirty_handler (GimpImage *image,
+ GimpDirtyMask dirty_mask,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_title_update (shell);
+}
+
+static void
+gimp_display_shell_undo_event_handler (GimpImage *image,
+ GimpUndoEvent event,
+ GimpUndo *undo,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_title_update (shell);
+}
+
+static void
+gimp_display_shell_grid_notify_handler (GimpGrid *grid,
+ GParamSpec *pspec,
+ GimpDisplayShell *shell)
+{
+ g_object_set (shell->grid, "grid", grid, NULL);
+}
+
+static void
+gimp_display_shell_name_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_title_update (shell);
+}
+
+static void
+gimp_display_shell_selection_invalidate_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_selection_undraw (shell);
+}
+
+static void
+gimp_display_shell_resolution_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_scale_update (shell);
+
+ if (shell->dot_for_dot)
+ {
+ if (shell->unit != GIMP_UNIT_PIXEL)
+ {
+ gimp_display_shell_rulers_update (shell);
+ }
+
+ gimp_display_shell_scaled (shell);
+ }
+ else
+ {
+ /* A resolution change has the same effect as a size change from
+ * a display shell point of view. Force a redraw of the display
+ * so that we don't get any display garbage.
+ */
+
+ GimpDisplayConfig *config = shell->display->config;
+ gboolean resize_window;
+
+ /* Resize windows only in multi-window mode */
+ resize_window = (config->resize_windows_on_resize &&
+ ! GIMP_GUI_CONFIG (config)->single_window_mode);
+
+ gimp_display_shell_scale_resize (shell, resize_window, FALSE);
+ }
+}
+
+static void
+gimp_display_shell_quick_mask_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ GtkImage *gtk_image;
+ gboolean quick_mask_state;
+
+ gtk_image = GTK_IMAGE (gtk_bin_get_child (GTK_BIN (shell->quick_mask_button)));
+
+ g_signal_handlers_block_by_func (shell->quick_mask_button,
+ gimp_display_shell_quick_mask_toggled,
+ shell);
+
+ quick_mask_state = gimp_image_get_quick_mask_state (image);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (shell->quick_mask_button),
+ quick_mask_state);
+
+ if (quick_mask_state)
+ gtk_image_set_from_icon_name (gtk_image, GIMP_ICON_QUICK_MASK_ON,
+ GTK_ICON_SIZE_MENU);
+ else
+ gtk_image_set_from_icon_name (gtk_image, GIMP_ICON_QUICK_MASK_OFF,
+ GTK_ICON_SIZE_MENU);
+
+ g_signal_handlers_unblock_by_func (shell->quick_mask_button,
+ gimp_display_shell_quick_mask_toggled,
+ shell);
+}
+
+static void
+gimp_display_shell_guide_add_handler (GimpImage *image,
+ GimpGuide *guide,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->guides);
+ GimpCanvasItem *item;
+ GimpGuideStyle style;
+
+ style = gimp_guide_get_style (guide);
+ item = gimp_canvas_guide_new (shell,
+ gimp_guide_get_orientation (guide),
+ gimp_guide_get_position (guide),
+ style);
+
+ gimp_canvas_proxy_group_add_item (group, guide, item);
+ g_object_unref (item);
+}
+
+static void
+gimp_display_shell_guide_remove_handler (GimpImage *image,
+ GimpGuide *guide,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->guides);
+
+ gimp_canvas_proxy_group_remove_item (group, guide);
+}
+
+static void
+gimp_display_shell_guide_move_handler (GimpImage *image,
+ GimpGuide *guide,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->guides);
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_proxy_group_get_item (group, guide);
+
+ gimp_canvas_guide_set (item,
+ gimp_guide_get_orientation (guide),
+ gimp_guide_get_position (guide));
+}
+
+static void
+gimp_display_shell_sample_point_add_handler (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->sample_points);
+ GimpCanvasItem *item;
+ GList *list;
+ gint x;
+ gint y;
+ gint i;
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+
+ item = gimp_canvas_sample_point_new (shell, x, y, 0, TRUE);
+
+ gimp_canvas_proxy_group_add_item (group, sample_point, item);
+ g_object_unref (item);
+
+ for (list = gimp_image_get_sample_points (image), i = 1;
+ list;
+ list = g_list_next (list), i++)
+ {
+ GimpSamplePoint *sample_point = list->data;
+
+ item = gimp_canvas_proxy_group_get_item (group, sample_point);
+
+ if (item)
+ g_object_set (item,
+ "index", i,
+ NULL);
+ }
+}
+
+static void
+gimp_display_shell_sample_point_remove_handler (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->sample_points);
+ GList *list;
+ gint i;
+
+ gimp_canvas_proxy_group_remove_item (group, sample_point);
+
+ for (list = gimp_image_get_sample_points (image), i = 1;
+ list;
+ list = g_list_next (list), i++)
+ {
+ GimpSamplePoint *sample_point = list->data;
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_proxy_group_get_item (group, sample_point);
+
+ if (item)
+ g_object_set (item,
+ "index", i,
+ NULL);
+ }
+}
+
+static void
+gimp_display_shell_sample_point_move_handler (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->sample_points);
+ GimpCanvasItem *item;
+ gint x;
+ gint y;
+
+ item = gimp_canvas_proxy_group_get_item (group, sample_point);
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+
+ gimp_canvas_sample_point_set (item, x, y);
+}
+
+static void
+gimp_display_shell_component_visibility_changed_handler (GimpImage *image,
+ GimpChannelType channel,
+ GimpDisplayShell *shell)
+{
+ if (channel == GIMP_CHANNEL_ALPHA && shell->show_all)
+ gimp_display_shell_expose_full (shell);
+}
+
+static void
+gimp_display_shell_size_changed_detailed_handler (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpDisplayShell *shell)
+{
+ GimpDisplayConfig *config = shell->display->config;
+ gboolean resize_window;
+
+ /* Resize windows only in multi-window mode */
+ resize_window = (config->resize_windows_on_resize &&
+ ! GIMP_GUI_CONFIG (config)->single_window_mode);
+
+ if (resize_window)
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ /* If the window is resized just center the image in it when it
+ * has change size
+ */
+ gimp_image_window_shrink_wrap (window, FALSE);
+ }
+ }
+ else
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+ gint new_width = gimp_image_get_width (image);
+ gint new_height = gimp_image_get_height (image);
+ gint scaled_previous_origin_x;
+ gint scaled_previous_origin_y;
+ gboolean horizontally;
+ gboolean vertically;
+
+ scaled_previous_origin_x = SCALEX (shell, previous_origin_x);
+ scaled_previous_origin_y = SCALEY (shell, previous_origin_y);
+
+ horizontally = (SCALEX (shell, previous_width) > shell->disp_width &&
+ SCALEX (shell, new_width) <= shell->disp_width);
+ vertically = (SCALEY (shell, previous_height) > shell->disp_height &&
+ SCALEY (shell, new_height) <= shell->disp_height);
+
+ gimp_display_shell_scroll_set_offset (shell,
+ shell->offset_x + scaled_previous_origin_x,
+ shell->offset_y + scaled_previous_origin_y);
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ gimp_display_shell_scroll_center_image (shell,
+ horizontally, vertically);
+ }
+
+ /* The above calls might not lead to a call to
+ * gimp_display_shell_scroll_clamp_and_update() and
+ * gimp_display_shell_expose_full() in all cases because when
+ * scaling the old and new scroll offset might be the same.
+ *
+ * We need them to be called in all cases, so simply call them
+ * explicitly here at the end
+ */
+ gimp_display_shell_scroll_clamp_and_update (shell);
+
+ gimp_display_shell_expose_full (shell);
+ }
+}
+
+static void
+gimp_display_shell_invalidate_preview_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_icon_update (shell);
+}
+
+static void
+gimp_display_shell_mode_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_profile_update (shell);
+}
+
+static void
+gimp_display_shell_precision_changed_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_profile_update (shell);
+}
+
+static void
+gimp_display_shell_profile_changed_handler (GimpColorManaged *image,
+ GimpDisplayShell *shell)
+{
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell));
+}
+
+static void
+gimp_display_shell_saved_handler (GimpImage *image,
+ GFile *file,
+ GimpDisplayShell *shell)
+{
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_statusbar_push_temp (statusbar, GIMP_MESSAGE_INFO,
+ GIMP_ICON_DOCUMENT_SAVE,
+ _("Image saved to '%s'"),
+ gimp_file_get_utf8_name (file));
+}
+
+static void
+gimp_display_shell_exported_handler (GimpImage *image,
+ GFile *file,
+ GimpDisplayShell *shell)
+{
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_statusbar_push_temp (statusbar, GIMP_MESSAGE_INFO,
+ GIMP_ICON_DOCUMENT_SAVE,
+ _("Image exported to '%s'"),
+ gimp_file_get_utf8_name (file));
+}
+
+static void
+gimp_display_shell_active_vectors_handler (GimpImage *image,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors);
+ GimpVectors *active = gimp_image_get_active_vectors (image);
+ GList *list;
+
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpVectors *vectors = list->data;
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_proxy_group_get_item (group, vectors);
+
+ gimp_canvas_item_set_highlight (item, vectors == active);
+ }
+}
+
+static void
+gimp_display_shell_vectors_freeze_handler (GimpVectors *vectors,
+ GimpDisplayShell *shell)
+{
+ /* do nothing */
+}
+
+static void
+gimp_display_shell_vectors_thaw_handler (GimpVectors *vectors,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors);
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_proxy_group_get_item (group, vectors);
+
+ gimp_canvas_path_set (item, gimp_vectors_get_bezier (vectors));
+}
+
+static void
+gimp_display_shell_vectors_visible_handler (GimpVectors *vectors,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors);
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_proxy_group_get_item (group, vectors);
+
+ gimp_canvas_item_set_visible (item,
+ gimp_item_get_visible (GIMP_ITEM (vectors)));
+}
+
+static void
+gimp_display_shell_vectors_add_handler (GimpContainer *container,
+ GimpVectors *vectors,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors);
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_path_new (shell,
+ gimp_vectors_get_bezier (vectors),
+ 0, 0,
+ FALSE,
+ GIMP_PATH_STYLE_VECTORS);
+ gimp_canvas_item_set_visible (item,
+ gimp_item_get_visible (GIMP_ITEM (vectors)));
+
+ gimp_canvas_proxy_group_add_item (group, vectors, item);
+ g_object_unref (item);
+}
+
+static void
+gimp_display_shell_vectors_remove_handler (GimpContainer *container,
+ GimpVectors *vectors,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors);
+
+ gimp_canvas_proxy_group_remove_item (group, vectors);
+}
+
+static void
+gimp_display_shell_check_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ GimpCanvasPaddingMode padding_mode;
+ GimpRGB padding_color;
+
+ g_clear_pointer (&shell->checkerboard, cairo_pattern_destroy);
+
+ gimp_display_shell_get_padding (shell, &padding_mode, &padding_color);
+
+ switch (padding_mode)
+ {
+ case GIMP_CANVAS_PADDING_MODE_LIGHT_CHECK:
+ case GIMP_CANVAS_PADDING_MODE_DARK_CHECK:
+ gimp_display_shell_set_padding (shell, padding_mode, &padding_color);
+ break;
+
+ default:
+ break;
+ }
+
+ gimp_display_shell_expose_full (shell);
+}
+
+static void
+gimp_display_shell_title_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_title_update (shell);
+}
+
+static void
+gimp_display_shell_nav_size_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ g_clear_pointer (&shell->nav_popup, gtk_widget_destroy);
+}
+
+static void
+gimp_display_shell_monitor_res_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ if (GIMP_DISPLAY_CONFIG (config)->monitor_res_from_gdk)
+ {
+ gimp_get_monitor_resolution (gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ &shell->monitor_xres,
+ &shell->monitor_yres);
+ }
+ else
+ {
+ shell->monitor_xres = GIMP_DISPLAY_CONFIG (config)->monitor_xres;
+ shell->monitor_yres = GIMP_DISPLAY_CONFIG (config)->monitor_yres;
+ }
+
+ gimp_display_shell_scale_update (shell);
+
+ if (! shell->dot_for_dot)
+ {
+ gimp_display_shell_scroll_clamp_and_update (shell);
+
+ gimp_display_shell_scaled (shell);
+
+ gimp_display_shell_expose_full (shell);
+ }
+}
+
+static void
+gimp_display_shell_padding_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ GimpDisplayConfig *display_config;
+ GimpImageWindow *window;
+ gboolean fullscreen;
+ GimpCanvasPaddingMode padding_mode;
+ GimpRGB padding_color;
+
+ display_config = shell->display->config;
+
+ window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ fullscreen = gimp_image_window_get_fullscreen (window);
+ else
+ fullscreen = FALSE;
+
+ /* if the user did not set the padding mode for this display explicitly */
+ if (! shell->fullscreen_options->padding_mode_set)
+ {
+ padding_mode = display_config->default_fullscreen_view->padding_mode;
+ padding_color = display_config->default_fullscreen_view->padding_color;
+
+ if (fullscreen)
+ {
+ gimp_display_shell_set_padding (shell, padding_mode, &padding_color);
+ }
+ else
+ {
+ shell->fullscreen_options->padding_mode = padding_mode;
+ shell->fullscreen_options->padding_color = padding_color;
+ }
+ }
+
+ /* if the user did not set the padding mode for this display explicitly */
+ if (! shell->options->padding_mode_set)
+ {
+ padding_mode = display_config->default_view->padding_mode;
+ padding_color = display_config->default_view->padding_color;
+
+ if (fullscreen)
+ {
+ shell->options->padding_mode = padding_mode;
+ shell->options->padding_color = padding_color;
+ }
+ else
+ {
+ gimp_display_shell_set_padding (shell, padding_mode, &padding_color);
+ }
+ }
+}
+
+static void
+gimp_display_shell_ants_speed_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_selection_pause (shell);
+ gimp_display_shell_selection_resume (shell);
+}
+
+static void
+gimp_display_shell_quality_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_expose_full (shell);
+}
+
+static void
+gimp_display_shell_color_config_notify_handler (GObject *config,
+ GParamSpec *param_spec,
+ GimpDisplayShell *shell)
+{
+ if (param_spec)
+ {
+ gboolean copy = TRUE;
+
+ if (! strcmp (param_spec->name, "mode") ||
+ ! strcmp (param_spec->name, "display-rendering-intent") ||
+ ! strcmp (param_spec->name, "display-use-black-point-compensation") ||
+ ! strcmp (param_spec->name, "printer-profile") ||
+ ! strcmp (param_spec->name, "simulation-rendering-intent") ||
+ ! strcmp (param_spec->name, "simulation-use-black-point-compensation") ||
+ ! strcmp (param_spec->name, "simulation-gamut-check"))
+ {
+ if (shell->color_config_set)
+ copy = FALSE;
+ }
+
+ if (copy)
+ {
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, param_spec->value_type);
+
+ g_object_get_property (config,
+ param_spec->name, &value);
+ g_object_set_property (G_OBJECT (shell->color_config),
+ param_spec->name, &value);
+
+ g_value_unset (&value);
+ }
+ }
+ else
+ {
+ gimp_config_copy (GIMP_CONFIG (config),
+ GIMP_CONFIG (shell->color_config),
+ 0);
+ shell->color_config_set = FALSE;
+ }
+}
+
+static void
+gimp_display_shell_display_changed_handler (GimpContext *context,
+ GimpDisplay *display,
+ GimpDisplayShell *shell)
+{
+ if (shell->display == display)
+ gimp_display_shell_update_priority_rect (shell);
+}
diff --git a/app/display/gimpdisplayshell-handlers.h b/app/display/gimpdisplayshell-handlers.h
new file mode 100644
index 0000000..49d743e
--- /dev/null
+++ b/app/display/gimpdisplayshell-handlers.h
@@ -0,0 +1,26 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_HANDLERS_H__
+#define __GIMP_DISPLAY_SHELL_HANDLERS_H__
+
+
+void gimp_display_shell_connect (GimpDisplayShell *shell);
+void gimp_display_shell_disconnect (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_HANDLERS_H__ */
diff --git a/app/display/gimpdisplayshell-icon.c b/app/display/gimpdisplayshell-icon.c
new file mode 100644
index 0000000..6168820
--- /dev/null
+++ b/app/display/gimpdisplayshell-icon.c
@@ -0,0 +1,140 @@
+/* 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-icon.h"
+
+
+#define GIMP_DISPLAY_UPDATE_ICON_TIMEOUT 1000
+
+static gboolean gimp_display_shell_icon_update_idle (gpointer data);
+
+
+/* public functions */
+
+void
+gimp_display_shell_icon_update (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_icon_update_stop (shell);
+
+ if (gimp_display_get_image (shell->display))
+ shell->icon_idle_id = g_timeout_add_full (G_PRIORITY_LOW,
+ GIMP_DISPLAY_UPDATE_ICON_TIMEOUT,
+ gimp_display_shell_icon_update_idle,
+ shell,
+ NULL);
+ else
+ gimp_display_shell_icon_update_idle (shell);
+}
+
+void
+gimp_display_shell_icon_update_stop (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->icon_idle_id)
+ {
+ g_source_remove (shell->icon_idle_id);
+ shell->icon_idle_id = 0;
+ }
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_display_shell_icon_update_idle (gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GdkPixbuf *icon = NULL;
+
+ shell->icon_idle_id = 0;
+
+ if (image)
+ {
+ Gimp *gimp = gimp_display_get_gimp (shell->display);
+ GdkPixbuf *pixbuf;
+ gint width;
+ gint height;
+ gdouble factor = ((gdouble) gimp_image_get_height (image) /
+ (gdouble) gimp_image_get_width (image));
+
+ if (factor >= 1)
+ {
+ height = MAX (shell->icon_size, 1);
+ width = MAX (((gdouble) shell->icon_size) / factor, 1);
+ }
+ else
+ {
+ height = MAX (((gdouble) shell->icon_size) * factor, 1);
+ width = MAX (shell->icon_size, 1);
+ }
+
+ pixbuf = gimp_viewable_get_pixbuf (GIMP_VIEWABLE (image),
+ gimp_get_user_context (gimp),
+ width, height);
+
+ icon = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ shell->icon_size, shell->icon_size);
+
+ memset (gdk_pixbuf_get_pixels (icon), 0,
+ gdk_pixbuf_get_height (icon) *
+ gdk_pixbuf_get_rowstride (icon));
+
+ gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height,
+ icon,
+ 0, shell->icon_size - height);
+
+ pixbuf = gimp_widget_load_icon (GTK_WIDGET (shell), "gimp-wilber-outline",
+ shell->icon_size_small);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ gdk_pixbuf_composite (pixbuf, icon,
+ shell->icon_size - width, 0,
+ width, height,
+ shell->icon_size - width, 0.0, 1.0, 1.0,
+ GDK_INTERP_NEAREST, 255);
+ g_object_unref (pixbuf);
+ }
+
+ g_object_set (shell, "icon", icon, NULL);
+
+ if (icon)
+ g_object_unref (icon);
+
+ return FALSE;
+}
diff --git a/app/display/gimpdisplayshell-icon.h b/app/display/gimpdisplayshell-icon.h
new file mode 100644
index 0000000..6d2a673
--- /dev/null
+++ b/app/display/gimpdisplayshell-icon.h
@@ -0,0 +1,26 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_ICON_H__
+#define __GIMP_DISPLAY_SHELL_ICON_H__
+
+
+void gimp_display_shell_icon_update (GimpDisplayShell *shell);
+void gimp_display_shell_icon_update_stop (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_ICON_H__ */
diff --git a/app/display/gimpdisplayshell-items.c b/app/display/gimpdisplayshell-items.c
new file mode 100644
index 0000000..caa381a
--- /dev/null
+++ b/app/display/gimpdisplayshell-items.c
@@ -0,0 +1,284 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdisplayshell-items.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "display-types.h"
+
+#include "gimpcanvascanvasboundary.h"
+#include "gimpcanvascursor.h"
+#include "gimpcanvasgrid.h"
+#include "gimpcanvaslayerboundary.h"
+#include "gimpcanvaspassepartout.h"
+#include "gimpcanvasproxygroup.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-items.h"
+#include "gimpdisplayshell-transform.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_item_update (GimpCanvasItem *item,
+ cairo_region_t *region,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_unrotated_item_update (GimpCanvasItem *item,
+ cairo_region_t *region,
+ GimpDisplayShell *shell);
+
+
+/* public functions */
+
+void
+gimp_display_shell_items_init (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ shell->canvas_item = gimp_canvas_group_new (shell);
+
+ shell->passe_partout = gimp_canvas_passe_partout_new (shell, 0, 0, 0, 0);
+ gimp_canvas_item_set_visible (shell->passe_partout, FALSE);
+ gimp_display_shell_add_item (shell, shell->passe_partout);
+ g_object_unref (shell->passe_partout);
+
+ shell->preview_items = gimp_canvas_group_new (shell);
+ gimp_display_shell_add_item (shell, shell->preview_items);
+ g_object_unref (shell->preview_items);
+
+ shell->vectors = gimp_canvas_proxy_group_new (shell);
+ gimp_display_shell_add_item (shell, shell->vectors);
+ g_object_unref (shell->vectors);
+
+ shell->grid = gimp_canvas_grid_new (shell, NULL);
+ gimp_canvas_item_set_visible (shell->grid, FALSE);
+ g_object_set (shell->grid, "grid-style", TRUE, NULL);
+ gimp_display_shell_add_item (shell, shell->grid);
+ g_object_unref (shell->grid);
+
+ shell->guides = gimp_canvas_proxy_group_new (shell);
+ gimp_display_shell_add_item (shell, shell->guides);
+ g_object_unref (shell->guides);
+
+ shell->sample_points = gimp_canvas_proxy_group_new (shell);
+ gimp_display_shell_add_item (shell, shell->sample_points);
+ g_object_unref (shell->sample_points);
+
+ shell->canvas_boundary = gimp_canvas_canvas_boundary_new (shell);
+ gimp_canvas_item_set_visible (shell->canvas_boundary, FALSE);
+ gimp_display_shell_add_item (shell, shell->canvas_boundary);
+ g_object_unref (shell->canvas_boundary);
+
+ shell->layer_boundary = gimp_canvas_layer_boundary_new (shell);
+ gimp_canvas_item_set_visible (shell->layer_boundary, FALSE);
+ gimp_display_shell_add_item (shell, shell->layer_boundary);
+ g_object_unref (shell->layer_boundary);
+
+ shell->tool_items = gimp_canvas_group_new (shell);
+ gimp_display_shell_add_item (shell, shell->tool_items);
+ g_object_unref (shell->tool_items);
+
+ g_signal_connect (shell->canvas_item, "update",
+ G_CALLBACK (gimp_display_shell_item_update),
+ shell);
+
+ shell->unrotated_item = gimp_canvas_group_new (shell);
+
+ shell->cursor = gimp_canvas_cursor_new (shell);
+ gimp_canvas_item_set_visible (shell->cursor, FALSE);
+ gimp_display_shell_add_unrotated_item (shell, shell->cursor);
+ g_object_unref (shell->cursor);
+
+ g_signal_connect (shell->unrotated_item, "update",
+ G_CALLBACK (gimp_display_shell_unrotated_item_update),
+ shell);
+}
+
+void
+gimp_display_shell_items_free (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->canvas_item)
+ {
+ g_signal_handlers_disconnect_by_func (shell->canvas_item,
+ gimp_display_shell_item_update,
+ shell);
+
+ g_clear_object (&shell->canvas_item);
+
+ shell->passe_partout = NULL;
+ shell->preview_items = NULL;
+ shell->vectors = NULL;
+ shell->grid = NULL;
+ shell->guides = NULL;
+ shell->sample_points = NULL;
+ shell->canvas_boundary = NULL;
+ shell->layer_boundary = NULL;
+ shell->tool_items = NULL;
+ }
+
+ if (shell->unrotated_item)
+ {
+ g_signal_handlers_disconnect_by_func (shell->unrotated_item,
+ gimp_display_shell_unrotated_item_update,
+ shell);
+
+ g_clear_object (&shell->unrotated_item);
+
+ shell->cursor = NULL;
+ }
+}
+
+void
+gimp_display_shell_add_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->canvas_item), item);
+}
+
+void
+gimp_display_shell_remove_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->canvas_item), item);
+}
+
+void
+gimp_display_shell_add_preview_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->preview_items), item);
+}
+
+void
+gimp_display_shell_remove_preview_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->preview_items), item);
+}
+
+void
+gimp_display_shell_add_unrotated_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->unrotated_item), item);
+}
+
+void
+gimp_display_shell_remove_unrotated_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->unrotated_item), item);
+}
+
+void
+gimp_display_shell_add_tool_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->tool_items), item);
+}
+
+void
+gimp_display_shell_remove_tool_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->tool_items), item);
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_item_update (GimpCanvasItem *item,
+ cairo_region_t *region,
+ GimpDisplayShell *shell)
+{
+ if (shell->rotate_transform)
+ {
+ gint n_rects;
+ gint i;
+
+ n_rects = cairo_region_num_rectangles (region);
+
+ for (i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect;
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+ gint x1, y1, x2, y2;
+
+ cairo_region_get_rectangle (region, i, &rect);
+
+ gimp_display_shell_rotate_bounds (shell,
+ rect.x, rect.y,
+ rect.x + rect.width,
+ rect.y + rect.height,
+ &tx1, &ty1, &tx2, &ty2);
+
+ x1 = floor (tx1 - 0.5);
+ y1 = floor (ty1 - 0.5);
+ x2 = ceil (tx2 + 0.5);
+ y2 = ceil (ty2 + 0.5);
+
+ gimp_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1);
+ }
+ }
+ else
+ {
+ gimp_display_shell_expose_region (shell, region);
+ }
+}
+
+static void
+gimp_display_shell_unrotated_item_update (GimpCanvasItem *item,
+ cairo_region_t *region,
+ GimpDisplayShell *shell)
+{
+ gimp_display_shell_expose_region (shell, region);
+}
diff --git a/app/display/gimpdisplayshell-items.h b/app/display/gimpdisplayshell-items.h
new file mode 100644
index 0000000..84f169e
--- /dev/null
+++ b/app/display/gimpdisplayshell-items.h
@@ -0,0 +1,49 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdisplayshell-items.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_ITEMS_H__
+#define __GIMP_DISPLAY_SHELL_ITEMS_H__
+
+
+void gimp_display_shell_items_init (GimpDisplayShell *shell);
+void gimp_display_shell_items_free (GimpDisplayShell *shell);
+
+void gimp_display_shell_add_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+void gimp_display_shell_remove_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+
+void gimp_display_shell_add_preview_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+void gimp_display_shell_remove_preview_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+
+void gimp_display_shell_add_unrotated_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+void gimp_display_shell_remove_unrotated_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+
+void gimp_display_shell_add_tool_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+void gimp_display_shell_remove_tool_item (GimpDisplayShell *shell,
+ GimpCanvasItem *item);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_ITEMS_H__ */
diff --git a/app/display/gimpdisplayshell-layer-select.c b/app/display/gimpdisplayshell-layer-select.c
new file mode 100644
index 0000000..0618eea
--- /dev/null
+++ b/app/display/gimpdisplayshell-layer-select.c
@@ -0,0 +1,305 @@
+/* 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer.h"
+
+#include "widgets/gimpview.h"
+#include "widgets/gimpviewrenderer.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-layer-select.h"
+
+#include "gimp-intl.h"
+
+
+typedef struct
+{
+ GtkWidget *window;
+ GtkWidget *view;
+ GtkWidget *label;
+
+ GimpImage *image;
+ GimpLayer *orig_layer;
+} LayerSelect;
+
+
+/* local function prototypes */
+
+static LayerSelect * layer_select_new (GimpDisplayShell *shell,
+ GimpImage *image,
+ GimpLayer *layer,
+ gint view_size);
+static void layer_select_destroy (LayerSelect *layer_select,
+ guint32 time);
+static void layer_select_advance (LayerSelect *layer_select,
+ gint move);
+static gboolean layer_select_events (GtkWidget *widget,
+ GdkEvent *event,
+ LayerSelect *layer_select);
+
+
+/* public functions */
+
+void
+gimp_display_shell_layer_select_init (GimpDisplayShell *shell,
+ gint move,
+ guint32 time)
+{
+ LayerSelect *layer_select;
+ GimpImage *image;
+ GimpLayer *layer;
+ GdkGrabStatus status;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ layer = gimp_image_get_active_layer (image);
+
+ if (! layer)
+ return;
+
+ layer_select = layer_select_new (shell, image, layer,
+ image->gimp->config->layer_preview_size);
+ layer_select_advance (layer_select, move);
+
+ gtk_window_set_screen (GTK_WINDOW (layer_select->window),
+ gtk_widget_get_screen (GTK_WIDGET (shell)));
+
+ gtk_widget_show (layer_select->window);
+
+ status = gdk_keyboard_grab (gtk_widget_get_window (layer_select->window), FALSE, time);
+ if (status != GDK_GRAB_SUCCESS)
+ g_printerr ("gdk_keyboard_grab failed with status %d\n", status);
+}
+
+
+/* private functions */
+
+static LayerSelect *
+layer_select_new (GimpDisplayShell *shell,
+ GimpImage *image,
+ GimpLayer *layer,
+ gint view_size)
+{
+ LayerSelect *layer_select;
+ GtkWidget *frame1;
+ GtkWidget *frame2;
+ GtkWidget *hbox;
+ GtkWidget *alignment;
+
+ layer_select = g_slice_new0 (LayerSelect);
+
+ layer_select->image = image;
+ layer_select->orig_layer = layer;
+
+ layer_select->window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_role (GTK_WINDOW (layer_select->window), "gimp-layer-select");
+ gtk_window_set_title (GTK_WINDOW (layer_select->window), _("Layer Select"));
+ gtk_window_set_position (GTK_WINDOW (layer_select->window), GTK_WIN_POS_MOUSE);
+ gtk_widget_set_events (layer_select->window,
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_BUTTON_PRESS_MASK);
+
+ g_signal_connect (layer_select->window, "event",
+ G_CALLBACK (layer_select_events),
+ layer_select);
+
+ frame1 = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame1), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (layer_select->window), frame1);
+ gtk_widget_show (frame1);
+
+ frame2 = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame2), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (frame1), frame2);
+ gtk_widget_show (frame2);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+ gtk_container_add (GTK_CONTAINER (frame2), hbox);
+ gtk_widget_show (hbox);
+
+ /* The view */
+ alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+ gtk_box_pack_start (GTK_BOX (hbox), alignment, FALSE, FALSE, 0);
+ gtk_widget_show (alignment);
+
+ layer_select->view =
+ gimp_view_new_by_types (gimp_get_user_context (image->gimp),
+ GIMP_TYPE_VIEW,
+ GIMP_TYPE_LAYER,
+ view_size, 1, FALSE);
+ gimp_view_renderer_set_color_config (GIMP_VIEW (layer_select->view)->renderer,
+ gimp_display_shell_get_color_config (shell));
+ gimp_view_set_viewable (GIMP_VIEW (layer_select->view),
+ GIMP_VIEWABLE (layer));
+ gtk_container_add (GTK_CONTAINER (alignment), layer_select->view);
+ gtk_widget_show (layer_select->view);
+ gtk_widget_show (alignment);
+
+ /* the layer name label */
+ layer_select->label = gtk_label_new (gimp_object_get_name (layer));
+ gtk_box_pack_start (GTK_BOX (hbox), layer_select->label, FALSE, FALSE, 0);
+ gtk_widget_show (layer_select->label);
+
+ return layer_select;
+}
+
+static void
+layer_select_destroy (LayerSelect *layer_select,
+ guint32 time)
+{
+ gdk_display_keyboard_ungrab (gtk_widget_get_display (layer_select->window),
+ time);
+
+ gtk_widget_destroy (layer_select->window);
+
+ if (layer_select->orig_layer !=
+ gimp_image_get_active_layer (layer_select->image))
+ {
+ gimp_image_flush (layer_select->image);
+ }
+
+ g_slice_free (LayerSelect, layer_select);
+}
+
+static void
+layer_select_advance (LayerSelect *layer_select,
+ gint move)
+{
+ GimpLayer *active_layer;
+ GimpLayer *next_layer;
+ GList *layers;
+ gint n_layers;
+ gint index;
+
+ if (move == 0)
+ return;
+
+ /* If there is a floating selection, allow no advancement */
+ if (gimp_image_get_floating_selection (layer_select->image))
+ return;
+
+ active_layer = gimp_image_get_active_layer (layer_select->image);
+
+ layers = gimp_image_get_layer_list (layer_select->image);
+ n_layers = g_list_length (layers);
+
+ index = g_list_index (layers, active_layer);
+ index += move;
+
+ if (index < 0)
+ index = n_layers - 1;
+ else if (index >= n_layers)
+ index = 0;
+
+ next_layer = g_list_nth_data (layers, index);
+
+ g_list_free (layers);
+
+ if (next_layer && next_layer != active_layer)
+ {
+ active_layer = gimp_image_set_active_layer (layer_select->image,
+ next_layer);
+
+ if (active_layer)
+ {
+ gimp_view_set_viewable (GIMP_VIEW (layer_select->view),
+ GIMP_VIEWABLE (active_layer));
+ gtk_label_set_text (GTK_LABEL (layer_select->label),
+ gimp_object_get_name (active_layer));
+ }
+ }
+}
+
+static gboolean
+layer_select_events (GtkWidget *widget,
+ GdkEvent *event,
+ LayerSelect *layer_select)
+{
+ GdkEventKey *kevent;
+ GdkEventButton *bevent;
+
+ switch (event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ bevent = (GdkEventButton *) event;
+
+ layer_select_destroy (layer_select, bevent->time);
+ break;
+
+ case GDK_KEY_PRESS:
+ kevent = (GdkEventKey *) event;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Tab:
+ layer_select_advance (layer_select, 1);
+ break;
+ case GDK_KEY_ISO_Left_Tab:
+ layer_select_advance (layer_select, -1);
+ break;
+ }
+ return TRUE;
+ break;
+
+ case GDK_KEY_RELEASE:
+ kevent = (GdkEventKey *) event;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Alt_L: case GDK_KEY_Alt_R:
+ kevent->state &= ~GDK_MOD1_MASK;
+ break;
+ case GDK_KEY_Control_L: case GDK_KEY_Control_R:
+ kevent->state &= ~GDK_CONTROL_MASK;
+ break;
+ case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
+ kevent->state &= ~GDK_SHIFT_MASK;
+ break;
+ }
+
+ if (! (kevent->state & GDK_CONTROL_MASK))
+ layer_select_destroy (layer_select, kevent->time);
+
+ return TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
diff --git a/app/display/gimpdisplayshell-layer-select.h b/app/display/gimpdisplayshell-layer-select.h
new file mode 100644
index 0000000..20546c6
--- /dev/null
+++ b/app/display/gimpdisplayshell-layer-select.h
@@ -0,0 +1,27 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_LAYER_SELECT_H__
+#define __GIMP_DISPLAY_SHELL_LAYER_SELECT_H__
+
+
+void gimp_display_shell_layer_select_init (GimpDisplayShell *shell,
+ gint move,
+ guint32 time);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_LAYER_SELECT_H__ */
diff --git a/app/display/gimpdisplayshell-profile.c b/app/display/gimpdisplayshell-profile.c
new file mode 100644
index 0000000..71a6c94
--- /dev/null
+++ b/app/display/gimpdisplayshell-profile.c
@@ -0,0 +1,336 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 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 "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "core/gimpimage.h"
+#include "core/gimpprojectable.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-actions.h"
+#include "gimpdisplayshell-filter.h"
+#include "gimpdisplayshell-profile.h"
+#include "gimpdisplayxfer.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_profile_free (GimpDisplayShell *shell);
+
+static void gimp_display_shell_color_config_notify (GimpColorConfig *config,
+ const GParamSpec *pspec,
+ GimpDisplayShell *shell);
+
+
+/* public functions */
+
+void
+gimp_display_shell_profile_init (GimpDisplayShell *shell)
+{
+ GimpColorConfig *color_config;
+
+ color_config = GIMP_CORE_CONFIG (shell->display->config)->color_management;
+
+ shell->color_config = gimp_config_duplicate (GIMP_CONFIG (color_config));
+
+ /* use after so we are called after the profile cache is invalidated
+ * in gimp_widget_get_color_transform()
+ */
+ g_signal_connect_after (shell->color_config, "notify",
+ G_CALLBACK (gimp_display_shell_color_config_notify),
+ shell);
+}
+
+void
+gimp_display_shell_profile_finalize (GimpDisplayShell *shell)
+{
+ g_clear_object (&shell->color_config);
+
+ gimp_display_shell_profile_free (shell);
+}
+
+void
+gimp_display_shell_profile_update (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+ GimpColorProfile *src_profile;
+ const Babl *src_format;
+ GimpColorProfile *filter_profile;
+ const Babl *filter_format;
+ const Babl *dest_format;
+
+ gimp_display_shell_profile_free (shell);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (! image)
+ return;
+
+ src_profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (shell));
+
+ if (! src_profile)
+ return;
+
+ src_format = gimp_projectable_get_format (GIMP_PROJECTABLE (image));
+
+ if (gimp_display_shell_has_filter (shell))
+ {
+ filter_format = shell->filter_format;
+ filter_profile = gimp_babl_format_get_color_profile (filter_format);
+ }
+ else
+ {
+ filter_format = src_format;
+ filter_profile = src_profile;
+ }
+
+ if (! gimp_display_shell_profile_can_convert_to_u8 (shell))
+ {
+ dest_format = shell->filter_format;
+ }
+ else
+ {
+ dest_format = babl_format ("R'G'B'A u8");
+ }
+
+#if 0
+ g_printerr ("src_profile: %s\n"
+ "src_format: %s\n"
+ "filter_profile: %s\n"
+ "filter_format: %s\n"
+ "dest_format: %s\n",
+ gimp_color_profile_get_label (src_profile),
+ babl_get_name (src_format),
+ gimp_color_profile_get_label (filter_profile),
+ babl_get_name (filter_format),
+ babl_get_name (dest_format));
+#endif
+
+ if (! gimp_color_transform_can_gegl_copy (src_profile, filter_profile))
+ {
+ shell->filter_transform =
+ gimp_color_transform_new (src_profile,
+ src_format,
+ filter_profile,
+ filter_format,
+ GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
+ GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION |
+ GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE);
+ }
+
+ shell->profile_transform =
+ gimp_widget_get_color_transform (gtk_widget_get_toplevel (GTK_WIDGET (shell)),
+ gimp_display_shell_get_color_config (shell),
+ filter_profile,
+ filter_format,
+ dest_format);
+
+ if (shell->filter_transform || shell->profile_transform)
+ {
+ gint w = GIMP_DISPLAY_RENDER_BUF_WIDTH;
+ gint h = GIMP_DISPLAY_RENDER_BUF_HEIGHT;
+
+ shell->profile_data =
+ gegl_malloc (w * h * babl_format_get_bytes_per_pixel (src_format));
+
+ shell->profile_stride =
+ w * babl_format_get_bytes_per_pixel (src_format);
+
+ shell->profile_buffer =
+ gegl_buffer_linear_new_from_data (shell->profile_data,
+ src_format,
+ GEGL_RECTANGLE (0, 0, w, h),
+ GEGL_AUTO_ROWSTRIDE,
+ (GDestroyNotify) gegl_free,
+ shell->profile_data);
+ }
+}
+
+gboolean
+gimp_display_shell_profile_can_convert_to_u8 (GimpDisplayShell *shell)
+{
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ GimpComponentType component_type;
+
+ if (! gimp_display_shell_has_filter (shell))
+ component_type = gimp_image_get_component_type (image);
+ else
+ component_type = gimp_babl_format_get_component_type (shell->filter_format);
+
+ switch (component_type)
+ {
+ case GIMP_COMPONENT_TYPE_U8:
+#if 0
+ /* would like to convert directly for these too, but it
+ * produces inferior results, see bug 750874
+ */
+ case GIMP_COMPONENT_TYPE_U16:
+ case GIMP_COMPONENT_TYPE_U32:
+#endif
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_profile_free (GimpDisplayShell *shell)
+{
+ g_clear_object (&shell->profile_transform);
+ g_clear_object (&shell->filter_transform);
+ g_clear_object (&shell->profile_buffer);
+ shell->profile_data = NULL;
+ shell->profile_stride = 0;
+}
+
+static void
+gimp_display_shell_color_config_notify (GimpColorConfig *config,
+ const GParamSpec *pspec,
+ GimpDisplayShell *shell)
+{
+ if (! strcmp (pspec->name, "mode") ||
+ ! strcmp (pspec->name, "display-rendering-intent") ||
+ ! strcmp (pspec->name, "display-use-black-point-compensation") ||
+ ! strcmp (pspec->name, "simulation-rendering-intent") ||
+ ! strcmp (pspec->name, "simulation-use-black-point-compensation") ||
+ ! strcmp (pspec->name, "simulation-gamut-check"))
+ {
+ gboolean managed = FALSE;
+ gboolean softproof = FALSE;
+ const gchar *action = NULL;
+
+#define SET_SENSITIVE(action, sensitive) \
+ gimp_display_shell_set_action_sensitive (shell, action, sensitive);
+
+#define SET_ACTIVE(action, active) \
+ gimp_display_shell_set_action_active (shell, action, active);
+
+ switch (gimp_color_config_get_mode (config))
+ {
+ case GIMP_COLOR_MANAGEMENT_OFF:
+ break;
+
+ case GIMP_COLOR_MANAGEMENT_DISPLAY:
+ managed = TRUE;
+ break;
+
+ case GIMP_COLOR_MANAGEMENT_SOFTPROOF:
+ managed = TRUE;
+ softproof = TRUE;
+ break;
+ }
+
+ SET_ACTIVE ("view-color-management-enable", managed);
+ SET_ACTIVE ("view-color-management-softproof", softproof);
+
+ switch (gimp_color_config_get_display_intent (config))
+ {
+ case GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL:
+ action = "view-display-intent-perceptual";
+ break;
+
+ case GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC:
+ action = "view-display-intent-relative-colorimetric";
+ break;
+
+ case GIMP_COLOR_RENDERING_INTENT_SATURATION:
+ action = "view-display-intent-saturation";
+ break;
+
+ case GIMP_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
+ action = "view-display-intent-absolute-colorimetric";
+ break;
+ }
+
+ SET_SENSITIVE ("view-display-intent-perceptual", managed);
+ SET_SENSITIVE ("view-display-intent-relative-colorimetric", managed);
+ SET_SENSITIVE ("view-display-intent-saturation", managed);
+ SET_SENSITIVE ("view-display-intent-absolute-colorimetric", managed);
+
+ SET_ACTIVE (action, TRUE);
+
+ SET_SENSITIVE ("view-display-black-point-compensation", managed);
+ SET_ACTIVE ("view-display-black-point-compensation",
+ gimp_color_config_get_display_bpc (config));
+
+ SET_SENSITIVE ("view-softproof-profile", softproof);
+
+ switch (gimp_color_config_get_simulation_intent (config))
+ {
+ case GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL:
+ action = "view-softproof-intent-perceptual";
+ break;
+
+ case GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC:
+ action = "view-softproof-intent-relative-colorimetric";
+ break;
+
+ case GIMP_COLOR_RENDERING_INTENT_SATURATION:
+ action = "view-softproof-intent-saturation";
+ break;
+
+ case GIMP_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
+ action = "view-softproof-intent-absolute-colorimetric";
+ break;
+ }
+
+ SET_SENSITIVE ("view-softproof-intent-perceptual", softproof);
+ SET_SENSITIVE ("view-softproof-intent-relative-colorimetric", softproof);
+ SET_SENSITIVE ("view-softproof-intent-saturation", softproof);
+ SET_SENSITIVE ("view-softproof-intent-absolute-colorimetric", softproof);
+
+ SET_ACTIVE (action, TRUE);
+
+ SET_SENSITIVE ("view-softproof-black-point-compensation", softproof);
+ SET_ACTIVE ("view-softproof-black-point-compensation",
+ gimp_color_config_get_simulation_bpc (config));
+
+ SET_SENSITIVE ("view-softproof-gamut-check", softproof);
+ SET_ACTIVE ("view-softproof-gamut-check",
+ gimp_color_config_get_simulation_gamut_check (config));
+ }
+
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell));
+}
diff --git a/app/display/gimpdisplayshell-profile.h b/app/display/gimpdisplayshell-profile.h
new file mode 100644
index 0000000..e390cac
--- /dev/null
+++ b/app/display/gimpdisplayshell-profile.h
@@ -0,0 +1,30 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_PROFILE_H__
+#define __GIMP_DISPLAY_SHELL_PROFILE_H__
+
+
+void gimp_display_shell_profile_init (GimpDisplayShell *shell);
+void gimp_display_shell_profile_finalize (GimpDisplayShell *shell);
+
+void gimp_display_shell_profile_update (GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_profile_can_convert_to_u8 (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_PROFILE_H__ */
diff --git a/app/display/gimpdisplayshell-progress.c b/app/display/gimpdisplayshell-progress.c
new file mode 100644
index 0000000..4314a84
--- /dev/null
+++ b/app/display/gimpdisplayshell-progress.c
@@ -0,0 +1,163 @@
+/* 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 "display-types.h"
+
+#include "core/gimpprogress.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-progress.h"
+#include "gimpstatusbar.h"
+
+
+static GimpProgress *
+gimp_display_shell_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ return gimp_progress_start (GIMP_PROGRESS (statusbar), cancellable,
+ "%s", message);
+}
+
+static void
+gimp_display_shell_progress_end (GimpProgress *progress)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_progress_end (GIMP_PROGRESS (statusbar));
+}
+
+static gboolean
+gimp_display_shell_progress_is_active (GimpProgress *progress)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ return gimp_progress_is_active (GIMP_PROGRESS (statusbar));
+}
+
+static void
+gimp_display_shell_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_progress_set_text_literal (GIMP_PROGRESS (statusbar), message);
+}
+
+static void
+gimp_display_shell_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_progress_set_value (GIMP_PROGRESS (statusbar), percentage);
+}
+
+static gdouble
+gimp_display_shell_progress_get_value (GimpProgress *progress)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ return gimp_progress_get_value (GIMP_PROGRESS (statusbar));
+}
+
+static void
+gimp_display_shell_progress_pulse (GimpProgress *progress)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_progress_pulse (GIMP_PROGRESS (statusbar));
+}
+
+static guint32
+gimp_display_shell_progress_get_window_id (GimpProgress *progress)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (progress));
+
+ if (GTK_IS_WINDOW (toplevel))
+ return gimp_window_get_native_id (GTK_WINDOW (toplevel));
+
+ return 0;
+}
+
+static gboolean
+gimp_display_shell_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress);
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+
+ switch (severity)
+ {
+ case GIMP_MESSAGE_ERROR:
+ case GIMP_MESSAGE_BUG_WARNING:
+ case GIMP_MESSAGE_BUG_CRITICAL:
+ /* error messages are never handled here */
+ break;
+
+ case GIMP_MESSAGE_WARNING:
+ /* warning messages go to the statusbar, if it's visible */
+ if (! gimp_statusbar_get_visible (statusbar))
+ break;
+ else
+ return gimp_progress_message (GIMP_PROGRESS (statusbar), gimp,
+ severity, domain, message);
+
+ case GIMP_MESSAGE_INFO:
+ /* info messages go to the statusbar;
+ * if they are not handled there, they are swallowed
+ */
+ gimp_progress_message (GIMP_PROGRESS (statusbar), gimp,
+ severity, domain, message);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_display_shell_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_display_shell_progress_start;
+ iface->end = gimp_display_shell_progress_end;
+ iface->is_active = gimp_display_shell_progress_is_active;
+ iface->set_text = gimp_display_shell_progress_set_text;
+ iface->set_value = gimp_display_shell_progress_set_value;
+ iface->get_value = gimp_display_shell_progress_get_value;
+ iface->pulse = gimp_display_shell_progress_pulse;
+ iface->get_window_id = gimp_display_shell_progress_get_window_id;
+ iface->message = gimp_display_shell_progress_message;
+}
diff --git a/app/display/gimpdisplayshell-progress.h b/app/display/gimpdisplayshell-progress.h
new file mode 100644
index 0000000..d697fa1
--- /dev/null
+++ b/app/display/gimpdisplayshell-progress.h
@@ -0,0 +1,28 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_PROGRESS_H__
+#define __GIMP_DISPLAY_SHELL_PROGRESS_H__
+
+
+#include "core/gimpprogress.h"
+
+
+void gimp_display_shell_progress_iface_init (GimpProgressInterface *iface);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_PROGRESS_H__ */
diff --git a/app/display/gimpdisplayshell-render.c b/app/display/gimpdisplayshell-render.c
new file mode 100644
index 0000000..a1525bf
--- /dev/null
+++ b/app/display/gimpdisplayshell-render.c
@@ -0,0 +1,351 @@
+/* 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 "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpprojectable.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpdisplayshell-filter.h"
+#include "gimpdisplayshell-profile.h"
+#include "gimpdisplayshell-render.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayxfer.h"
+
+
+void
+gimp_display_shell_render (GimpDisplayShell *shell,
+ cairo_t *cr,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gdouble scale)
+{
+ GimpImage *image;
+ GeglBuffer *buffer;
+#ifdef USE_NODE_BLIT
+ GeglNode *node;
+#endif
+ GeglAbyssPolicy abyss_policy;
+ cairo_surface_t *xfer;
+ gint xfer_src_x;
+ gint xfer_src_y;
+ gint mask_src_x = 0;
+ gint mask_src_y = 0;
+ gint cairo_stride;
+ guchar *cairo_data;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (w > 0 && w <= GIMP_DISPLAY_RENDER_BUF_WIDTH);
+ g_return_if_fail (h > 0 && h <= GIMP_DISPLAY_RENDER_BUF_HEIGHT);
+ g_return_if_fail (scale > 0.0);
+
+ image = gimp_display_get_image (shell->display);
+ buffer = gimp_pickable_get_buffer (
+ gimp_display_shell_get_pickable (shell));
+#ifdef USE_NODE_BLIT
+ node = gimp_projectable_get_graph (GIMP_PROJECTABLE (image));
+
+ gimp_projectable_begin_render (GIMP_PROJECTABLE (image));
+#endif
+
+ if (shell->show_all)
+ abyss_policy = GEGL_ABYSS_NONE;
+ else
+ abyss_policy = GEGL_ABYSS_CLAMP;
+
+ xfer = gimp_display_xfer_get_surface (shell->xfer, w, h,
+ &xfer_src_x, &xfer_src_y);
+
+ cairo_stride = cairo_image_surface_get_stride (xfer);
+ cairo_data = cairo_image_surface_get_data (xfer) +
+ xfer_src_y * cairo_stride + xfer_src_x * 4;
+
+ if (shell->profile_transform ||
+ gimp_display_shell_has_filter (shell))
+ {
+ gboolean can_convert_to_u8;
+
+ /* if there is a profile transform or a display filter, we need
+ * to use temp buffers
+ */
+
+ can_convert_to_u8 = gimp_display_shell_profile_can_convert_to_u8 (shell);
+
+ /* create the filter buffer if we have filters, or can't convert
+ * to u8 directly
+ */
+ if ((gimp_display_shell_has_filter (shell) || ! can_convert_to_u8) &&
+ ! shell->filter_buffer)
+ {
+ gint fw = GIMP_DISPLAY_RENDER_BUF_WIDTH;
+ gint fh = GIMP_DISPLAY_RENDER_BUF_HEIGHT;
+
+ shell->filter_data =
+ gegl_malloc (fw * fh *
+ babl_format_get_bytes_per_pixel (shell->filter_format));
+
+ shell->filter_stride =
+ fw * babl_format_get_bytes_per_pixel (shell->filter_format);
+
+ shell->filter_buffer =
+ gegl_buffer_linear_new_from_data (shell->filter_data,
+ shell->filter_format,
+ GEGL_RECTANGLE (0, 0, fw, fh),
+ GEGL_AUTO_ROWSTRIDE,
+ (GDestroyNotify) gegl_free,
+ shell->filter_data);
+ }
+
+ if (! gimp_display_shell_has_filter (shell) || shell->filter_transform)
+ {
+ /* if there are no filters, or there is a filter transform,
+ * load the projection pixels into the profile_buffer
+ */
+#ifndef USE_NODE_BLIT
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (x, y, w, h), scale,
+ gimp_projectable_get_format (GIMP_PROJECTABLE (image)),
+ shell->profile_data, shell->profile_stride,
+ abyss_policy);
+#else
+ gegl_node_blit (node,
+ scale, GEGL_RECTANGLE (x, y, w, h),
+ gimp_projectable_get_format (GIMP_PROJECTABLE (image)),
+ shell->profile_data, shell->profile_stride,
+ GEGL_BLIT_CACHE);
+#endif
+ }
+ else
+ {
+ /* otherwise, load the pixels directly into the filter_buffer
+ */
+#ifndef USE_NODE_BLIT
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (x, y, w, h), scale,
+ shell->filter_format,
+ shell->filter_data, shell->filter_stride,
+ abyss_policy);
+#else
+ gegl_node_blit (node,
+ scale, GEGL_RECTANGLE (x, y, w, h),
+ shell->filter_format,
+ shell->filter_data, shell->filter_stride,
+ GEGL_BLIT_CACHE);
+#endif
+ }
+
+ /* if there is a filter transform, convert the pixels from
+ * the profile_buffer to the filter_buffer
+ */
+ if (shell->filter_transform)
+ {
+ gimp_color_transform_process_buffer (shell->filter_transform,
+ shell->profile_buffer,
+ GEGL_RECTANGLE (0, 0, w, h),
+ shell->filter_buffer,
+ GEGL_RECTANGLE (0, 0, w, h));
+ }
+
+ /* if there are filters, apply them
+ */
+ if (gimp_display_shell_has_filter (shell))
+ {
+ GeglBuffer *filter_buffer;
+
+ /* shift the filter_buffer so that the area passed to
+ * the filters is the real render area, allowing for
+ * position-dependent filters
+ */
+ filter_buffer = g_object_new (GEGL_TYPE_BUFFER,
+ "source", shell->filter_buffer,
+ "shift-x", -x,
+ "shift-y", -y,
+ NULL);
+
+ /* convert the filter_buffer in place
+ */
+ gimp_color_display_stack_convert_buffer (shell->filter_stack,
+ filter_buffer,
+ GEGL_RECTANGLE (x, y, w, h));
+
+ g_object_unref (filter_buffer);
+ }
+
+ /* if there is a profile transform...
+ */
+ if (shell->profile_transform)
+ {
+ if (gimp_display_shell_has_filter (shell))
+ {
+ /* if we have filters, convert the pixels in the filter_buffer
+ * in-place
+ */
+ gimp_color_transform_process_buffer (shell->profile_transform,
+ shell->filter_buffer,
+ GEGL_RECTANGLE (0, 0, w, h),
+ shell->filter_buffer,
+ GEGL_RECTANGLE (0, 0, w, h));
+ }
+ else if (! can_convert_to_u8)
+ {
+ /* otherwise, if we can't convert to u8 directly, convert
+ * the pixels from the profile_buffer to the filter_buffer
+ */
+ gimp_color_transform_process_buffer (shell->profile_transform,
+ shell->profile_buffer,
+ GEGL_RECTANGLE (0, 0, w, h),
+ shell->filter_buffer,
+ GEGL_RECTANGLE (0, 0, w, h));
+ }
+ else
+ {
+ GeglBuffer *buffer =
+ gegl_buffer_linear_new_from_data (cairo_data,
+ babl_format ("cairo-ARGB32"),
+ GEGL_RECTANGLE (0, 0, w, h),
+ cairo_stride,
+ NULL, NULL);
+
+ /* otherwise, convert the profile_buffer directly into
+ * the cairo_buffer
+ */
+ gimp_color_transform_process_buffer (shell->profile_transform,
+ shell->profile_buffer,
+ GEGL_RECTANGLE (0, 0, w, h),
+ buffer,
+ GEGL_RECTANGLE (0, 0, w, h));
+ g_object_unref (buffer);
+ }
+ }
+
+ /* finally, copy the filter buffer to the cairo-ARGB32 buffer,
+ * if necessary
+ */
+ if (gimp_display_shell_has_filter (shell) || ! can_convert_to_u8)
+ {
+ gegl_buffer_get (shell->filter_buffer,
+ GEGL_RECTANGLE (0, 0, w, h), 1.0,
+ babl_format ("cairo-ARGB32"),
+ cairo_data, cairo_stride,
+ GEGL_ABYSS_NONE);
+ }
+ }
+ else
+ {
+ /* otherwise we can copy the projection pixels straight to the
+ * cairo-ARGB32 buffer
+ */
+#ifndef USE_NODE_BLIT
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (x, y, w, h), scale,
+ babl_format ("cairo-ARGB32"),
+ cairo_data, cairo_stride,
+ abyss_policy);
+#else
+ gegl_node_blit (node,
+ scale, GEGL_RECTANGLE (x, y, w, h),
+ babl_format ("cairo-ARGB32"),
+ cairo_data, cairo_stride,
+ GEGL_BLIT_CACHE);
+#endif
+ }
+
+#ifdef USE_NODE_BLIT
+ gimp_projectable_end_render (GIMP_PROJECTABLE (image));
+#endif
+
+ if (shell->mask)
+ {
+ if (! shell->mask_surface)
+ {
+ shell->mask_surface =
+ cairo_image_surface_create (CAIRO_FORMAT_A8,
+ GIMP_DISPLAY_RENDER_BUF_WIDTH,
+ GIMP_DISPLAY_RENDER_BUF_HEIGHT);
+ }
+
+ cairo_surface_mark_dirty (shell->mask_surface);
+
+ cairo_stride = cairo_image_surface_get_stride (shell->mask_surface);
+ cairo_data = cairo_image_surface_get_data (shell->mask_surface) +
+ mask_src_y * cairo_stride + mask_src_x;
+
+ gegl_buffer_get (shell->mask,
+ GEGL_RECTANGLE (x - floor (shell->mask_offset_x * scale),
+ y - floor (shell->mask_offset_y * scale),
+ w, h),
+ scale,
+ babl_format ("Y u8"),
+ cairo_data, cairo_stride,
+ GEGL_ABYSS_NONE);
+
+ if (shell->mask_inverted)
+ {
+ gint mask_height = h;
+
+ while (mask_height--)
+ {
+ gint mask_width = w;
+ guchar *d = cairo_data;
+
+ while (mask_width--)
+ {
+ guchar inv = 255 - *d;
+
+ *d++ = inv;
+ }
+
+ cairo_data += cairo_stride;
+ }
+ }
+ }
+
+ /* put it to the screen */
+ cairo_set_source_surface (cr, xfer,
+ x - xfer_src_x,
+ y - xfer_src_y);
+ cairo_paint (cr);
+
+ if (shell->mask)
+ {
+ gimp_cairo_set_source_rgba (cr, &shell->mask_color);
+ cairo_mask_surface (cr, shell->mask_surface,
+ x - mask_src_x,
+ y - mask_src_y);
+ }
+}
diff --git a/app/display/gimpdisplayshell-render.h b/app/display/gimpdisplayshell-render.h
new file mode 100644
index 0000000..7b4a644
--- /dev/null
+++ b/app/display/gimpdisplayshell-render.h
@@ -0,0 +1,29 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_RENDER_H__
+#define __GIMP_DISPLAY_SHELL_RENDER_H__
+
+void gimp_display_shell_render (GimpDisplayShell *shell,
+ cairo_t *cr,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gdouble scale);
+
+#endif /* __GIMP_DISPLAY_SHELL_RENDER_H__ */
diff --git a/app/display/gimpdisplayshell-rotate-dialog.c b/app/display/gimpdisplayshell-rotate-dialog.c
new file mode 100644
index 0000000..c899235
--- /dev/null
+++ b/app/display/gimpdisplayshell-rotate-dialog.c
@@ -0,0 +1,293 @@
+/* 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/gimpviewable.h"
+
+#include "widgets/gimpdial.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpviewabledialog.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-rotate.h"
+#include "gimpdisplayshell-rotate-dialog.h"
+
+#include "gimp-intl.h"
+
+
+#define RESPONSE_RESET 1
+
+
+typedef struct
+{
+ GimpDisplayShell *shell;
+ GtkAdjustment *rotate_adj;
+ gdouble old_angle;
+} RotateDialogData;
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_rotate_dialog_response (GtkWidget *widget,
+ gint response_id,
+ RotateDialogData *dialog);
+static void gimp_display_shell_rotate_dialog_free (RotateDialogData *dialog);
+
+static void rotate_adjustment_changed (GtkAdjustment *adj,
+ RotateDialogData *dialog);
+static void display_shell_rotated (GimpDisplayShell *shell,
+ RotateDialogData *dialog);
+
+static gboolean deg_to_rad (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data);
+static gboolean rad_to_deg (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data);
+
+
+/* public functions */
+
+/**
+ * gimp_display_shell_rotate_dialog:
+ * @shell: the #GimpDisplayShell
+ *
+ * Constructs and displays a dialog allowing the user to enter a
+ * custom display rotate.
+ **/
+void
+gimp_display_shell_rotate_dialog (GimpDisplayShell *shell)
+{
+ RotateDialogData *data;
+ GimpImage *image;
+ GtkWidget *toplevel;
+ GtkWidget *hbox;
+ GtkWidget *spin;
+ GtkWidget *dial;
+ GtkWidget *label;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->rotate_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (shell->rotate_dialog));
+ return;
+ }
+
+ image = gimp_display_get_image (shell->display);
+
+ data = g_slice_new (RotateDialogData);
+
+ data->shell = shell;
+ data->old_angle = shell->rotate_angle;
+
+ shell->rotate_dialog =
+ gimp_viewable_dialog_new (GIMP_VIEWABLE (image),
+ gimp_get_user_context (shell->display->gimp),
+ _("Rotate View"), "display-rotate",
+ GIMP_ICON_OBJECT_ROTATE_180,
+ _("Select Rotation Angle"),
+ GTK_WIDGET (shell),
+ gimp_standard_help_func,
+ GIMP_HELP_VIEW_ROTATE_OTHER,
+
+ _("_Reset"), RESPONSE_RESET,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_OK"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (shell->rotate_dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_object_weak_ref (G_OBJECT (shell->rotate_dialog),
+ (GWeakNotify) gimp_display_shell_rotate_dialog_free, data);
+
+ g_object_add_weak_pointer (G_OBJECT (shell->rotate_dialog),
+ (gpointer) &shell->rotate_dialog);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
+
+ gtk_window_set_transient_for (GTK_WINDOW (shell->rotate_dialog),
+ GTK_WINDOW (toplevel));
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (shell->rotate_dialog), TRUE);
+
+ g_signal_connect (shell->rotate_dialog, "response",
+ G_CALLBACK (gimp_display_shell_rotate_dialog_response),
+ data);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell->rotate_dialog))),
+ hbox, TRUE, TRUE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new (_("Angle:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ data->rotate_adj = (GtkAdjustment *)
+ gtk_adjustment_new (shell->rotate_angle, 0.0, 360.0, 1, 15, 0);
+ spin = gimp_spin_button_new (data->rotate_adj, 1.0, 2);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
+ gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
+ gtk_widget_show (spin);
+
+ label = gtk_label_new (_("degrees"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ dial = gimp_dial_new ();
+ g_object_set (dial,
+ "size", 32,
+ "background", GIMP_CIRCLE_BACKGROUND_PLAIN,
+ "draw-beta", FALSE,
+ NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), dial, FALSE, FALSE, 0);
+ gtk_widget_show (dial);
+
+ g_object_bind_property_full (data->rotate_adj, "value",
+ dial, "alpha",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE,
+ deg_to_rad,
+ rad_to_deg,
+ NULL, NULL);
+
+ g_signal_connect (data->rotate_adj, "value-changed",
+ G_CALLBACK (rotate_adjustment_changed),
+ data);
+ g_signal_connect (shell, "rotated",
+ G_CALLBACK (display_shell_rotated),
+ data);
+
+ gtk_widget_show (shell->rotate_dialog);
+}
+
+static void
+gimp_display_shell_rotate_dialog_response (GtkWidget *widget,
+ gint response_id,
+ RotateDialogData *dialog)
+{
+ switch (response_id)
+ {
+ case RESPONSE_RESET:
+ gtk_adjustment_set_value (dialog->rotate_adj, 0.0);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ gtk_adjustment_set_value (dialog->rotate_adj, dialog->old_angle);
+ /* fall thru */
+
+ default:
+ gtk_widget_destroy (dialog->shell->rotate_dialog);
+ break;
+ }
+}
+
+static void
+gimp_display_shell_rotate_dialog_free (RotateDialogData *dialog)
+{
+ g_signal_handlers_disconnect_by_func (dialog->shell,
+ display_shell_rotated,
+ dialog);
+
+ g_slice_free (RotateDialogData, dialog);
+}
+
+static void
+rotate_adjustment_changed (GtkAdjustment *adj,
+ RotateDialogData *dialog)
+{
+ gdouble angle = gtk_adjustment_get_value (dialog->rotate_adj);
+
+ g_signal_handlers_block_by_func (dialog->shell,
+ display_shell_rotated,
+ dialog);
+
+ gimp_display_shell_rotate_to (dialog->shell, angle);
+
+ g_signal_handlers_unblock_by_func (dialog->shell,
+ display_shell_rotated,
+ dialog);
+}
+
+static void
+display_shell_rotated (GimpDisplayShell *shell,
+ RotateDialogData *dialog)
+{
+ g_signal_handlers_block_by_func (dialog->rotate_adj,
+ rotate_adjustment_changed,
+ dialog);
+
+ gtk_adjustment_set_value (dialog->rotate_adj, shell->rotate_angle);
+
+ g_signal_handlers_unblock_by_func (dialog->rotate_adj,
+ rotate_adjustment_changed,
+ dialog);
+}
+
+static gboolean
+deg_to_rad (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ gdouble value = g_value_get_double (from_value);
+
+ value = 360.0 - value;
+
+ value *= G_PI / 180.0;
+
+ g_value_set_double (to_value, value);
+
+ return TRUE;
+}
+
+static gboolean
+rad_to_deg (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ gdouble value = g_value_get_double (from_value);
+
+ value *= 180.0 / G_PI;
+
+ value = 360.0 - value;
+
+ g_value_set_double (to_value, value);
+
+ return TRUE;
+}
diff --git a/app/display/gimpdisplayshell-rotate-dialog.h b/app/display/gimpdisplayshell-rotate-dialog.h
new file mode 100644
index 0000000..5222d13
--- /dev/null
+++ b/app/display/gimpdisplayshell-rotate-dialog.h
@@ -0,0 +1,25 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_ROTATE_DIALOG_H__
+#define __GIMP_DISPLAY_SHELL_ROTATE_DIALOG_H__
+
+
+void gimp_display_shell_rotate_dialog (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_ROTATE_DIALOG_H__ */
diff --git a/app/display/gimpdisplayshell-rotate.c b/app/display/gimpdisplayshell-rotate.c
new file mode 100644
index 0000000..d14f2a5
--- /dev/null
+++ b/app/display/gimpdisplayshell-rotate.c
@@ -0,0 +1,251 @@
+/* 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 <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-rotate.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-transform.h"
+
+
+#define ANGLE_EPSILON 1e-3
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_save_viewport_center (GimpDisplayShell *shell,
+ gdouble *x,
+ gdouble *y);
+static void gimp_display_shell_restore_viewport_center (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y);
+
+
+/* public functions */
+
+void
+gimp_display_shell_flip (GimpDisplayShell *shell,
+ gboolean flip_horizontally,
+ gboolean flip_vertically)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ flip_horizontally = flip_horizontally ? TRUE : FALSE;
+ flip_vertically = flip_vertically ? TRUE : FALSE;
+
+ if (flip_horizontally != shell->flip_horizontally ||
+ flip_vertically != shell->flip_vertically)
+ {
+ gdouble cx, cy;
+
+ /* Maintain the current center of the viewport. */
+ gimp_display_shell_save_viewport_center (shell, &cx, &cy);
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ /* Adjust the rotation angle so that the image gets reflected across the
+ * horizontal, and/or vertical, axes in screen space, regardless of the
+ * current rotation.
+ */
+ if (flip_horizontally == shell->flip_horizontally ||
+ flip_vertically == shell->flip_vertically)
+ {
+ if (shell->rotate_angle != 0.0)
+ shell->rotate_angle = 360.0 - shell->rotate_angle;
+ }
+
+ shell->flip_horizontally = flip_horizontally;
+ shell->flip_vertically = flip_vertically;
+
+ gimp_display_shell_rotated (shell);
+
+ gimp_display_shell_restore_viewport_center (shell, cx, cy);
+
+ gimp_display_shell_expose_full (shell);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+ }
+}
+
+void
+gimp_display_shell_rotate (GimpDisplayShell *shell,
+ gdouble delta)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_rotate_to (shell, shell->rotate_angle + delta);
+}
+
+void
+gimp_display_shell_rotate_to (GimpDisplayShell *shell,
+ gdouble value)
+{
+ gdouble cx, cy;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ /* Maintain the current center of the viewport. */
+ gimp_display_shell_save_viewport_center (shell, &cx, &cy);
+
+ /* Make sure the angle is within the range [0, 360). */
+ value = fmod (value, 360.0);
+ if (value < 0.0)
+ value += 360.0;
+
+ shell->rotate_angle = value;
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+
+ gimp_display_shell_rotated (shell);
+
+ gimp_display_shell_restore_viewport_center (shell, cx, cy);
+
+ gimp_display_shell_expose_full (shell);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+}
+
+void
+gimp_display_shell_rotate_drag (GimpDisplayShell *shell,
+ gdouble last_x,
+ gdouble last_y,
+ gdouble cur_x,
+ gdouble cur_y,
+ gboolean constrain)
+{
+ gdouble pivot_x, pivot_y;
+ gdouble src_x, src_y, src_angle;
+ gdouble dest_x, dest_y, dest_angle;
+ gdouble delta_angle;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ /* Rotate the image around the center of the viewport. */
+ pivot_x = shell->disp_width / 2.0;
+ pivot_y = shell->disp_height / 2.0;
+
+ src_x = last_x - pivot_x;
+ src_y = last_y - pivot_y;
+ src_angle = atan2 (src_y, src_x);
+
+ dest_x = cur_x - pivot_x;
+ dest_y = cur_y - pivot_y;
+ dest_angle = atan2 (dest_y, dest_x);
+
+ delta_angle = dest_angle - src_angle;
+
+ shell->rotate_drag_angle += 180.0 * delta_angle / G_PI;
+
+ gimp_display_shell_rotate_to (shell,
+ constrain ?
+ RINT (shell->rotate_drag_angle / 15.0) * 15.0 :
+ shell->rotate_drag_angle);
+}
+
+void
+gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ g_clear_pointer (&shell->rotate_transform, g_free);
+ g_clear_pointer (&shell->rotate_untransform, g_free);
+
+ if (fabs (shell->rotate_angle) < ANGLE_EPSILON ||
+ fabs (360.0 - shell->rotate_angle) < ANGLE_EPSILON)
+ shell->rotate_angle = 0.0;
+
+ if ((shell->rotate_angle != 0.0 ||
+ shell->flip_horizontally ||
+ shell->flip_vertically) &&
+ gimp_display_get_image (shell->display))
+ {
+ gint image_width, image_height;
+ gdouble cx, cy;
+
+ shell->rotate_transform = g_new (cairo_matrix_t, 1);
+ shell->rotate_untransform = g_new (cairo_matrix_t, 1);
+
+ gimp_display_shell_scale_get_image_size (shell,
+ &image_width, &image_height);
+
+ cx = -shell->offset_x + image_width / 2;
+ cy = -shell->offset_y + image_height / 2;
+
+ cairo_matrix_init_translate (shell->rotate_transform, cx, cy);
+
+ if (shell->rotate_angle != 0.0)
+ cairo_matrix_rotate (shell->rotate_transform,
+ shell->rotate_angle / 180.0 * G_PI);
+
+ if (shell->flip_horizontally)
+ cairo_matrix_scale (shell->rotate_transform, -1.0, 1.0);
+
+ if (shell->flip_vertically)
+ cairo_matrix_scale (shell->rotate_transform, 1.0, -1.0);
+
+ cairo_matrix_translate (shell->rotate_transform, -cx, -cy);
+
+ *shell->rotate_untransform = *shell->rotate_transform;
+ cairo_matrix_invert (shell->rotate_untransform);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_save_viewport_center (GimpDisplayShell *shell,
+ gdouble *x,
+ gdouble *y)
+{
+ gimp_display_shell_unrotate_xy_f (shell,
+ shell->disp_width / 2,
+ shell->disp_height / 2,
+ x, y);
+}
+
+static void
+gimp_display_shell_restore_viewport_center (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y)
+{
+ gimp_display_shell_rotate_xy_f (shell, x, y, &x, &y);
+
+ x += shell->offset_x - shell->disp_width / 2;
+ y += shell->offset_y - shell->disp_height / 2;
+
+ gimp_display_shell_scroll_set_offset (shell, RINT (x), RINT (y));
+}
diff --git a/app/display/gimpdisplayshell-rotate.h b/app/display/gimpdisplayshell-rotate.h
new file mode 100644
index 0000000..d1ffa05
--- /dev/null
+++ b/app/display/gimpdisplayshell-rotate.h
@@ -0,0 +1,40 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_ROTATE_H__
+#define __GIMP_DISPLAY_SHELL_ROTATE_H__
+
+
+void gimp_display_shell_flip (GimpDisplayShell *shell,
+ gboolean flip_horizontally,
+ gboolean flip_vertically);
+
+void gimp_display_shell_rotate (GimpDisplayShell *shell,
+ gdouble delta);
+void gimp_display_shell_rotate_to (GimpDisplayShell *shell,
+ gdouble value);
+void gimp_display_shell_rotate_drag (GimpDisplayShell *shell,
+ gdouble last_x,
+ gdouble last_y,
+ gdouble cur_x,
+ gdouble cur_y,
+ gboolean constrain);
+
+void gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_ROTATE_H__ */
diff --git a/app/display/gimpdisplayshell-rulers.c b/app/display/gimpdisplayshell-rulers.c
new file mode 100644
index 0000000..79253c8
--- /dev/null
+++ b/app/display/gimpdisplayshell-rulers.c
@@ -0,0 +1,181 @@
+/* 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 <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-rulers.h"
+#include "gimpdisplayshell-scale.h"
+
+
+/**
+ * gimp_display_shell_rulers_update:
+ * @shell:
+ *
+ **/
+void
+gimp_display_shell_rulers_update (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+ gint image_width;
+ gint image_height;
+ gdouble offset_x = 0.0;
+ gdouble offset_y = 0.0;
+ gdouble scale_x = 1.0;
+ gdouble scale_y = 1.0;
+ gdouble resolution_x = 1.0;
+ gdouble resolution_y = 1.0;
+ gdouble horizontal_lower;
+ gdouble horizontal_upper;
+ gdouble horizontal_max_size;
+ gdouble vertical_lower;
+ gdouble vertical_upper;
+ gdouble vertical_max_size;
+
+ if (! shell->display)
+ return;
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ gint image_x, image_y;
+ gdouble res_x, res_y;
+
+ gimp_display_shell_scale_get_image_bounds (shell,
+ &image_x, &image_y,
+ &image_width, &image_height);
+
+ gimp_display_shell_get_rotated_scale (shell, &scale_x, &scale_y);
+
+ image_width /= scale_x;
+ image_height /= scale_y;
+
+ offset_x = shell->offset_x - image_x;
+ offset_y = shell->offset_y - image_y;
+
+ gimp_image_get_resolution (image, &res_x, &res_y);
+
+ if (shell->rotate_angle == 0.0 || res_x == res_y)
+ {
+ resolution_x = res_x;
+ resolution_y = res_y;
+ }
+ else
+ {
+ gdouble cos_a = cos (G_PI * shell->rotate_angle / 180.0);
+ gdouble sin_a = sin (G_PI * shell->rotate_angle / 180.0);
+
+ if (shell->dot_for_dot)
+ {
+ resolution_x = 1.0 / sqrt (SQR (cos_a / res_x) +
+ SQR (sin_a / res_y));
+ resolution_y = 1.0 / sqrt (SQR (cos_a / res_y) +
+ SQR (sin_a / res_x));
+ }
+ else
+ {
+ resolution_x = sqrt (SQR (res_x * cos_a) + SQR (res_y * sin_a));
+ resolution_y = sqrt (SQR (res_y * cos_a) + SQR (res_x * sin_a));
+ }
+ }
+ }
+ else
+ {
+ image_width = shell->disp_width;
+ image_height = shell->disp_height;
+ }
+
+ /* Initialize values */
+
+ horizontal_lower = 0;
+ vertical_lower = 0;
+
+ if (image)
+ {
+ horizontal_upper = gimp_pixels_to_units (shell->disp_width / scale_x,
+ shell->unit,
+ resolution_x);
+ horizontal_max_size = gimp_pixels_to_units (MAX (image_width,
+ image_height),
+ shell->unit,
+ resolution_x);
+
+ vertical_upper = gimp_pixels_to_units (shell->disp_height / scale_y,
+ shell->unit,
+ resolution_y);
+ vertical_max_size = gimp_pixels_to_units (MAX (image_width,
+ image_height),
+ shell->unit,
+ resolution_y);
+ }
+ else
+ {
+ horizontal_upper = image_width;
+ horizontal_max_size = MAX (image_width, image_height);
+
+ vertical_upper = image_height;
+ vertical_max_size = MAX (image_width, image_height);
+ }
+
+
+ /* Adjust due to scrolling */
+
+ if (image)
+ {
+ offset_x *= horizontal_upper / shell->disp_width;
+ offset_y *= vertical_upper / shell->disp_height;
+
+ horizontal_lower += offset_x;
+ horizontal_upper += offset_x;
+
+ vertical_lower += offset_y;
+ vertical_upper += offset_y;
+ }
+
+ /* Finally setup the actual rulers */
+
+ gimp_ruler_set_range (GIMP_RULER (shell->hrule),
+ horizontal_lower,
+ horizontal_upper,
+ horizontal_max_size);
+
+ gimp_ruler_set_unit (GIMP_RULER (shell->hrule),
+ shell->unit);
+
+ gimp_ruler_set_range (GIMP_RULER (shell->vrule),
+ vertical_lower,
+ vertical_upper,
+ vertical_max_size);
+
+ gimp_ruler_set_unit (GIMP_RULER (shell->vrule),
+ shell->unit);
+}
diff --git a/app/display/gimpdisplayshell-rulers.h b/app/display/gimpdisplayshell-rulers.h
new file mode 100644
index 0000000..117a8f0
--- /dev/null
+++ b/app/display/gimpdisplayshell-rulers.h
@@ -0,0 +1,25 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_RULERS_H__
+#define __GIMP_DISPLAY_SHELL_RULERS_H__
+
+
+void gimp_display_shell_rulers_update (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_RULERS_H__ */
diff --git a/app/display/gimpdisplayshell-scale-dialog.c b/app/display/gimpdisplayshell-scale-dialog.c
new file mode 100644
index 0000000..f3d2eab
--- /dev/null
+++ b/app/display/gimpdisplayshell-scale-dialog.c
@@ -0,0 +1,293 @@
+/* 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/gimpviewable.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpviewabledialog.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scale-dialog.h"
+
+#include "gimp-intl.h"
+
+
+#define SCALE_EPSILON 0.0001
+#define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON)
+
+
+typedef struct
+{
+ GimpDisplayShell *shell;
+ GimpZoomModel *model;
+ GtkAdjustment *scale_adj;
+ GtkAdjustment *num_adj;
+ GtkAdjustment *denom_adj;
+} ScaleDialogData;
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_scale_dialog_response (GtkWidget *widget,
+ gint response_id,
+ ScaleDialogData *dialog);
+static void gimp_display_shell_scale_dialog_free (ScaleDialogData *dialog);
+
+static void update_zoom_values (GtkAdjustment *adj,
+ ScaleDialogData *dialog);
+
+
+
+/* public functions */
+
+/**
+ * gimp_display_shell_scale_dialog:
+ * @shell: the #GimpDisplayShell
+ *
+ * Constructs and displays a dialog allowing the user to enter a
+ * custom display scale.
+ **/
+void
+gimp_display_shell_scale_dialog (GimpDisplayShell *shell)
+{
+ ScaleDialogData *data;
+ GimpImage *image;
+ GtkWidget *toplevel;
+ GtkWidget *hbox;
+ GtkWidget *table;
+ GtkWidget *spin;
+ GtkWidget *label;
+ gint num, denom, row;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->scale_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (shell->scale_dialog));
+ return;
+ }
+
+ if (SCALE_EQUALS (shell->other_scale, 0.0))
+ {
+ /* other_scale not yet initialized */
+ shell->other_scale = gimp_zoom_model_get_factor (shell->zoom);
+ }
+
+ image = gimp_display_get_image (shell->display);
+
+ data = g_slice_new (ScaleDialogData);
+
+ data->shell = shell;
+ data->model = g_object_new (GIMP_TYPE_ZOOM_MODEL,
+ "value", fabs (shell->other_scale),
+ NULL);
+
+ shell->scale_dialog =
+ gimp_viewable_dialog_new (GIMP_VIEWABLE (image),
+ gimp_get_user_context (shell->display->gimp),
+ _("Zoom Ratio"), "display_scale",
+ "zoom-original",
+ _("Select Zoom Ratio"),
+ GTK_WIDGET (shell),
+ gimp_standard_help_func,
+ GIMP_HELP_VIEW_ZOOM_OTHER,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_OK"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (shell->scale_dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_object_weak_ref (G_OBJECT (shell->scale_dialog),
+ (GWeakNotify) gimp_display_shell_scale_dialog_free, data);
+ g_object_weak_ref (G_OBJECT (shell->scale_dialog),
+ (GWeakNotify) g_object_unref, data->model);
+
+ g_object_add_weak_pointer (G_OBJECT (shell->scale_dialog),
+ (gpointer) &shell->scale_dialog);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
+
+ gtk_window_set_transient_for (GTK_WINDOW (shell->scale_dialog),
+ GTK_WINDOW (toplevel));
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (shell->scale_dialog), TRUE);
+
+ g_signal_connect (shell->scale_dialog, "response",
+ G_CALLBACK (gimp_display_shell_scale_dialog_response),
+ data);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (table), 12);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell->scale_dialog))),
+ table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ row = 0;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Zoom ratio:"), 0.0, 0.5,
+ hbox, 1, FALSE);
+
+ gimp_zoom_model_get_fraction (data->model, &num, &denom);
+
+ data->num_adj = (GtkAdjustment *)
+ gtk_adjustment_new (num, 1, 256, 1, 8, 0);
+ spin = gimp_spin_button_new (data->num_adj, 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
+ gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
+ gtk_widget_show (spin);
+
+ label = gtk_label_new (":");
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ data->denom_adj = (GtkAdjustment *)
+ gtk_adjustment_new (denom, 1, 256, 1, 8, 0);
+ spin = gimp_spin_button_new (data->denom_adj, 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
+ gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
+ gtk_widget_show (spin);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Zoom:"), 0.0, 0.5,
+ hbox, 1, FALSE);
+
+ data->scale_adj = (GtkAdjustment *)
+ gtk_adjustment_new (fabs (shell->other_scale) * 100,
+ 100.0 / 256.0, 25600.0,
+ 10, 50, 0);
+ spin = gimp_spin_button_new (data->scale_adj, 1.0, 2);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
+ gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
+ gtk_widget_show (spin);
+
+ label = gtk_label_new ("%");
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ g_signal_connect (data->scale_adj, "value-changed",
+ G_CALLBACK (update_zoom_values), data);
+ g_signal_connect (data->num_adj, "value-changed",
+ G_CALLBACK (update_zoom_values), data);
+ g_signal_connect (data->denom_adj, "value-changed",
+ G_CALLBACK (update_zoom_values), data);
+
+ gtk_widget_show (shell->scale_dialog);
+}
+
+static void
+gimp_display_shell_scale_dialog_response (GtkWidget *widget,
+ gint response_id,
+ ScaleDialogData *dialog)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ gdouble scale;
+
+ scale = gtk_adjustment_get_value (dialog->scale_adj);
+
+ gimp_display_shell_scale (dialog->shell,
+ GIMP_ZOOM_TO,
+ scale / 100.0,
+ GIMP_ZOOM_FOCUS_BEST_GUESS);
+ }
+ else
+ {
+ /* need to emit "scaled" to get the menu updated */
+ gimp_display_shell_scaled (dialog->shell);
+ }
+
+ dialog->shell->other_scale = - fabs (dialog->shell->other_scale);
+
+ gtk_widget_destroy (dialog->shell->scale_dialog);
+}
+
+static void
+gimp_display_shell_scale_dialog_free (ScaleDialogData *dialog)
+{
+ g_slice_free (ScaleDialogData, dialog);
+}
+
+static void
+update_zoom_values (GtkAdjustment *adj,
+ ScaleDialogData *dialog)
+{
+ gint num, denom;
+ gdouble scale;
+
+ g_signal_handlers_block_by_func (dialog->scale_adj,
+ update_zoom_values,
+ dialog);
+ g_signal_handlers_block_by_func (dialog->num_adj,
+ update_zoom_values,
+ dialog);
+ g_signal_handlers_block_by_func (dialog->denom_adj,
+ update_zoom_values,
+ dialog);
+
+ if (adj == dialog->scale_adj)
+ {
+ scale = gtk_adjustment_get_value (dialog->scale_adj);
+
+ gimp_zoom_model_zoom (dialog->model, GIMP_ZOOM_TO, scale / 100.0);
+ gimp_zoom_model_get_fraction (dialog->model, &num, &denom);
+
+ gtk_adjustment_set_value (dialog->num_adj, num);
+ gtk_adjustment_set_value (dialog->denom_adj, denom);
+ }
+ else /* fraction adjustments */
+ {
+ scale = (gtk_adjustment_get_value (dialog->num_adj) /
+ gtk_adjustment_get_value (dialog->denom_adj));
+
+ gtk_adjustment_set_value (dialog->scale_adj, scale * 100);
+ }
+
+ g_signal_handlers_unblock_by_func (dialog->scale_adj,
+ update_zoom_values,
+ dialog);
+ g_signal_handlers_unblock_by_func (dialog->num_adj,
+ update_zoom_values,
+ dialog);
+ g_signal_handlers_unblock_by_func (dialog->denom_adj,
+ update_zoom_values,
+ dialog);
+}
diff --git a/app/display/gimpdisplayshell-scale-dialog.h b/app/display/gimpdisplayshell-scale-dialog.h
new file mode 100644
index 0000000..eb83065
--- /dev/null
+++ b/app/display/gimpdisplayshell-scale-dialog.h
@@ -0,0 +1,25 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_SCALE_DIALOG_H__
+#define __GIMP_DISPLAY_SHELL_SCALE_DIALOG_H__
+
+
+void gimp_display_shell_scale_dialog (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_SCALE_DIALOG_H__ */
diff --git a/app/display/gimpdisplayshell-scale.c b/app/display/gimpdisplayshell-scale.c
new file mode 100644
index 0000000..482a9fe
--- /dev/null
+++ b/app/display/gimpdisplayshell-scale.c
@@ -0,0 +1,1449 @@
+/* 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 <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-rotate.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpimagewindow.h"
+
+
+#define SCALE_TIMEOUT 2
+#define SCALE_EPSILON 0.0001
+#define ALMOST_CENTERED_THRESHOLD 2
+
+#define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON)
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_scale_get_screen_resolution
+ (GimpDisplayShell *shell,
+ gdouble *xres,
+ gdouble *yres);
+static void gimp_display_shell_scale_get_image_size_for_scale
+ (GimpDisplayShell *shell,
+ gdouble scale,
+ gint *w,
+ gint *h);
+static void gimp_display_shell_calculate_scale_x_and_y
+ (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble *scale_x,
+ gdouble *scale_y);
+
+static void gimp_display_shell_scale_to (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble viewport_x,
+ gdouble viewport_y);
+static void gimp_display_shell_scale_fit_or_fill (GimpDisplayShell *shell,
+ gboolean fill);
+
+static gboolean gimp_display_shell_scale_image_starts_to_fit
+ (GimpDisplayShell *shell,
+ gdouble new_scale,
+ gdouble current_scale,
+ gboolean *vertically,
+ gboolean *horizontally);
+static gboolean gimp_display_shell_scale_viewport_coord_almost_centered
+ (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gboolean *horizontally,
+ gboolean *vertically);
+
+static void gimp_display_shell_scale_get_image_center_viewport
+ (GimpDisplayShell *shell,
+ gint *image_center_x,
+ gint *image_center_y);
+
+
+static void gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell,
+ gdouble new_scale,
+ gdouble current_scale,
+ gdouble *x,
+ gdouble *y,
+ GimpZoomFocus zoom_focus);
+
+
+/* public functions */
+
+/**
+ * gimp_display_shell_scale_revert:
+ * @shell: the #GimpDisplayShell
+ *
+ * Reverts the display to the previously used scale. If no previous
+ * scale exist, then the call does nothing.
+ *
+ * Return value: %TRUE if the scale was reverted, otherwise %FALSE.
+ **/
+gboolean
+gimp_display_shell_scale_revert (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ /* don't bother if no scale has been set */
+ if (shell->last_scale < SCALE_EPSILON)
+ return FALSE;
+
+ shell->last_scale_time = 0;
+
+ gimp_display_shell_scale_by_values (shell,
+ shell->last_scale,
+ shell->last_offset_x,
+ shell->last_offset_y,
+ FALSE); /* don't resize the window */
+
+ return TRUE;
+}
+
+/**
+ * gimp_display_shell_scale_can_revert:
+ * @shell: the #GimpDisplayShell
+ *
+ * Return value: %TRUE if a previous display scale exists, otherwise %FALSE.
+ **/
+gboolean
+gimp_display_shell_scale_can_revert (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return (shell->last_scale > SCALE_EPSILON);
+}
+
+/**
+ * gimp_display_shell_scale_save_revert_values:
+ * @shell:
+ *
+ * Handle the updating of the Revert Zoom variables.
+ **/
+void
+gimp_display_shell_scale_save_revert_values (GimpDisplayShell *shell)
+{
+ guint now;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ now = time (NULL);
+
+ if (now - shell->last_scale_time >= SCALE_TIMEOUT)
+ {
+ shell->last_scale = gimp_zoom_model_get_factor (shell->zoom);
+ shell->last_offset_x = shell->offset_x;
+ shell->last_offset_y = shell->offset_y;
+ }
+
+ shell->last_scale_time = now;
+}
+
+/**
+ * gimp_display_shell_scale_set_dot_for_dot:
+ * @shell: the #GimpDisplayShell
+ * @dot_for_dot: whether "Dot for Dot" should be enabled
+ *
+ * If @dot_for_dot is set to %TRUE then the "Dot for Dot" mode (where image and
+ * screen pixels are of the same size) is activated. Dually, the mode is
+ * disabled if @dot_for_dot is %FALSE.
+ **/
+void
+gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell,
+ gboolean dot_for_dot)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (dot_for_dot != shell->dot_for_dot)
+ {
+ GimpDisplayConfig *config = shell->display->config;
+ gboolean resize_window;
+
+ /* Resize windows only in multi-window mode */
+ resize_window = (config->resize_windows_on_zoom &&
+ ! GIMP_GUI_CONFIG (config)->single_window_mode);
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ shell->dot_for_dot = dot_for_dot;
+
+ gimp_display_shell_scale_update (shell);
+
+ gimp_display_shell_scale_resize (shell, resize_window, FALSE);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+ }
+}
+
+/**
+ * gimp_display_shell_scale_get_image_size:
+ * @shell:
+ * @w:
+ * @h:
+ *
+ * Gets the size of the rendered image after it has been scaled.
+ *
+ **/
+void
+gimp_display_shell_scale_get_image_size (GimpDisplayShell *shell,
+ gint *w,
+ gint *h)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_scale_get_image_size_for_scale (shell,
+ gimp_zoom_model_get_factor (shell->zoom),
+ w, h);
+}
+
+/**
+ * gimp_display_shell_scale_get_image_bounds:
+ * @shell:
+ * @x:
+ * @y:
+ * @w:
+ * @h:
+ *
+ * Gets the screen-space boudning box of the image, after it has
+ * been transformed (i.e., scaled, rotated, and scrolled).
+ **/
+void
+gimp_display_shell_scale_get_image_bounds (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ GimpImage *image;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ gimp_display_shell_transform_bounds (shell,
+ 0, 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ &x1, &y1,
+ &x2, &y2);
+
+ x1 = ceil (x1);
+ y1 = ceil (y1);
+ x2 = floor (x2);
+ y2 = floor (y2);
+
+ if (x) *x = x1 + shell->offset_x;
+ if (y) *y = y1 + shell->offset_y;
+ if (w) *w = x2 - x1;
+ if (h) *h = y2 - y1;
+}
+
+/**
+ * gimp_display_shell_scale_get_image_unrotated_bounds:
+ * @shell:
+ * @x:
+ * @y:
+ * @w:
+ * @h:
+ *
+ * Gets the screen-space boudning box of the image, after it has
+ * been scaled and scrolled, but before it has been rotated.
+ **/
+void
+gimp_display_shell_scale_get_image_unrotated_bounds (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ if (x) *x = -shell->offset_x;
+ if (y) *y = -shell->offset_y;
+ if (w) *w = floor (gimp_image_get_width (image) * shell->scale_x);
+ if (h) *h = floor (gimp_image_get_height (image) * shell->scale_y);
+}
+
+/**
+ * gimp_display_shell_scale_get_image_bounding_box:
+ * @shell:
+ * @x:
+ * @y:
+ * @w:
+ * @h:
+ *
+ * Gets the screen-space boudning box of the image content, after it has
+ * been transformed (i.e., scaled, rotated, and scrolled).
+ **/
+void
+gimp_display_shell_scale_get_image_bounding_box (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ GeglRectangle bounding_box;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ bounding_box = gimp_display_shell_get_bounding_box (shell);
+
+ gimp_display_shell_transform_bounds (shell,
+ bounding_box.x,
+ bounding_box.y,
+ bounding_box.x + bounding_box.width,
+ bounding_box.y + bounding_box.height,
+ &x1, &y1,
+ &x2, &y2);
+
+ if (! shell->show_all)
+ {
+ x1 = ceil (x1);
+ y1 = ceil (y1);
+ x2 = floor (x2);
+ y2 = floor (y2);
+ }
+ else
+ {
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+ }
+
+ if (x) *x = x1 + shell->offset_x;
+ if (y) *y = y1 + shell->offset_y;
+ if (w) *w = x2 - x1;
+ if (h) *h = y2 - y1;
+}
+
+/**
+ * gimp_display_shell_scale_get_image_unrotated_bounding_box:
+ * @shell:
+ * @x:
+ * @y:
+ * @w:
+ * @h:
+ *
+ * Gets the screen-space boudning box of the image content, after it has
+ * been scaled and scrolled, but before it has been rotated.
+ **/
+void
+gimp_display_shell_scale_get_image_unrotated_bounding_box (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ GeglRectangle bounding_box;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ bounding_box = gimp_display_shell_get_bounding_box (shell);
+
+ x1 = bounding_box.x * shell->scale_x -
+ shell->offset_x;
+ y1 = bounding_box.y * shell->scale_y -
+ shell->offset_y;
+
+ x2 = (bounding_box.x + bounding_box.width) * shell->scale_x -
+ shell->offset_x;
+ y2 = (bounding_box.y + bounding_box.height) * shell->scale_y -
+ shell->offset_y;
+
+ if (! shell->show_all)
+ {
+ x1 = ceil (x1);
+ y1 = ceil (y1);
+ x2 = floor (x2);
+ y2 = floor (y2);
+ }
+ else
+ {
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+ }
+
+ if (x) *x = x1;
+ if (y) *y = y1;
+ if (w) *w = x2 - x1;
+ if (h) *h = y2 - y1;
+}
+
+/**
+ * gimp_display_shell_scale_image_is_within_viewport:
+ * @shell:
+ *
+ * Returns: %TRUE if the (scaled) image is smaller than and within the
+ * viewport.
+ **/
+gboolean
+gimp_display_shell_scale_image_is_within_viewport (GimpDisplayShell *shell,
+ gboolean *horizontally,
+ gboolean *vertically)
+{
+ gboolean horizontally_dummy, vertically_dummy;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ if (! horizontally) horizontally = &horizontally_dummy;
+ if (! vertically) vertically = &vertically_dummy;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ gint sx, sy;
+ gint sw, sh;
+
+ gimp_display_shell_scale_get_image_bounding_box (shell,
+ &sx, &sy, &sw, &sh);
+
+ sx -= shell->offset_x;
+ sy -= shell->offset_y;
+
+ *horizontally = sx >= 0 && sx + sw <= shell->disp_width;
+ *vertically = sy >= 0 && sy + sh <= shell->disp_height;
+ }
+ else
+ {
+ *horizontally = FALSE;
+ *vertically = FALSE;
+ }
+
+ return *vertically && *horizontally;
+}
+
+/* We used to calculate the scale factor in the SCALEFACTOR_X() and
+ * SCALEFACTOR_Y() macros. But since these are rather frequently
+ * called and the values rarely change, we now store them in the
+ * shell and call this function whenever they need to be recalculated.
+ */
+void
+gimp_display_shell_scale_update (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ gimp_display_shell_calculate_scale_x_and_y (shell,
+ gimp_zoom_model_get_factor (shell->zoom),
+ &shell->scale_x,
+ &shell->scale_y);
+ }
+ else
+ {
+ shell->scale_x = 1.0;
+ shell->scale_y = 1.0;
+ }
+}
+
+/**
+ * gimp_display_shell_scale:
+ * @shell: the #GimpDisplayShell
+ * @zoom_type: whether to zoom in, out or to a specific scale
+ * @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO
+ *
+ * This function figures out the context of the zoom and behaves
+ * appropriately thereafter.
+ *
+ **/
+void
+gimp_display_shell_scale (GimpDisplayShell *shell,
+ GimpZoomType zoom_type,
+ gdouble new_scale,
+ GimpZoomFocus zoom_focus)
+{
+ GimpDisplayConfig *config;
+ gdouble current_scale;
+ gboolean resize_window;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->canvas != NULL);
+
+ current_scale = gimp_zoom_model_get_factor (shell->zoom);
+
+ if (zoom_type != GIMP_ZOOM_TO)
+ new_scale = gimp_zoom_model_zoom_step (zoom_type, current_scale);
+
+ if (SCALE_EQUALS (new_scale, current_scale))
+ return;
+
+ config = shell->display->config;
+
+ /* Resize windows only in multi-window mode */
+ resize_window = (config->resize_windows_on_zoom &&
+ ! GIMP_GUI_CONFIG (config)->single_window_mode);
+
+ if (resize_window)
+ {
+ /* If the window is resized on zoom, simply do the zoom and get
+ * things rolling
+ */
+ gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale);
+
+ gimp_display_shell_scale_resize (shell, TRUE, FALSE);
+ }
+ else
+ {
+ gdouble x, y;
+ gint image_center_x;
+ gint image_center_y;
+
+ gimp_display_shell_scale_get_zoom_focus (shell,
+ new_scale,
+ current_scale,
+ &x,
+ &y,
+ zoom_focus);
+ gimp_display_shell_scale_get_image_center_viewport (shell,
+ &image_center_x,
+ &image_center_y);
+
+ gimp_display_shell_scale_to (shell, new_scale, x, y);
+
+ /* skip centering magic if pointer focus was requested */
+ if (zoom_focus != GIMP_ZOOM_FOCUS_POINTER)
+ {
+ gboolean starts_fitting_horiz;
+ gboolean starts_fitting_vert;
+ gboolean zoom_focus_almost_centered_horiz;
+ gboolean zoom_focus_almost_centered_vert;
+ gboolean image_center_almost_centered_horiz;
+ gboolean image_center_almost_centered_vert;
+
+ /* If an image axis started to fit due to zooming out or if
+ * the focus point is as good as in the center, center on
+ * that axis
+ */
+ gimp_display_shell_scale_image_starts_to_fit (shell,
+ new_scale,
+ current_scale,
+ &starts_fitting_horiz,
+ &starts_fitting_vert);
+
+ gimp_display_shell_scale_viewport_coord_almost_centered (shell,
+ x,
+ y,
+ &zoom_focus_almost_centered_horiz,
+ &zoom_focus_almost_centered_vert);
+ gimp_display_shell_scale_viewport_coord_almost_centered (shell,
+ image_center_x,
+ image_center_y,
+ &image_center_almost_centered_horiz,
+ &image_center_almost_centered_vert);
+
+ gimp_display_shell_scroll_center_image (shell,
+ starts_fitting_horiz ||
+ (zoom_focus_almost_centered_horiz &&
+ image_center_almost_centered_horiz),
+ starts_fitting_vert ||
+ (zoom_focus_almost_centered_vert &&
+ image_center_almost_centered_vert));
+ }
+ }
+}
+
+/**
+ * gimp_display_shell_scale_to_rectangle:
+ * @shell: the #GimpDisplayShell
+ * @zoom_type: whether to zoom in or out
+ * @x: retangle's x in image coordinates
+ * @y: retangle's y in image coordinates
+ * @width: retangle's width in image coordinates
+ * @height: retangle's height in image coordinates
+ * @resize_window: whether the display window should be resized
+ *
+ * Scales and scrolls to a specific image rectangle
+ **/
+void
+gimp_display_shell_scale_to_rectangle (GimpDisplayShell *shell,
+ GimpZoomType zoom_type,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gboolean resize_window)
+{
+ gdouble current_scale;
+ gdouble new_scale;
+ gdouble factor = 1.0;
+ gint offset_x = 0;
+ gint offset_y = 0;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_transform_bounds (shell,
+ x, y,
+ x + width, y + height,
+ &x, &y,
+ &width, &height);
+
+ /* Convert scrolled (x1, y1, x2, y2) to unscrolled (x, y, width, height). */
+ width -= x;
+ height -= y;
+ x += shell->offset_x;
+ y += shell->offset_y;
+
+ width = MAX (1.0, width);
+ height = MAX (1.0, height);
+
+ current_scale = gimp_zoom_model_get_factor (shell->zoom);
+
+ switch (zoom_type)
+ {
+ case GIMP_ZOOM_IN:
+ factor = MIN ((shell->disp_width / width),
+ (shell->disp_height / height));
+ break;
+
+ case GIMP_ZOOM_OUT:
+ factor = MAX ((width / shell->disp_width),
+ (height / shell->disp_height));
+ break;
+
+ default:
+ g_return_if_reached ();
+ break;
+ }
+
+ new_scale = current_scale * factor;
+
+ switch (zoom_type)
+ {
+ case GIMP_ZOOM_IN:
+ /* move the center of the rectangle to the center of the
+ * viewport:
+ *
+ * new_offset = center of rectangle in new scale screen coords
+ * including offset
+ * -
+ * center of viewport in screen coords without
+ * offset
+ */
+ offset_x = RINT (factor * (x + width / 2.0) - (shell->disp_width / 2));
+ offset_y = RINT (factor * (y + height / 2.0) - (shell->disp_height / 2));
+ break;
+
+ case GIMP_ZOOM_OUT:
+ /* move the center of the viewport to the center of the
+ * rectangle:
+ *
+ * new_offset = center of viewport in new scale screen coords
+ * including offset
+ * -
+ * center of rectangle in screen coords without
+ * offset
+ */
+ offset_x = RINT (factor * (shell->offset_x + shell->disp_width / 2) -
+ ((x + width / 2.0) - shell->offset_x));
+
+ offset_y = RINT (factor * (shell->offset_y + shell->disp_height / 2) -
+ ((y + height / 2.0) - shell->offset_y));
+ break;
+
+ default:
+ break;
+ }
+
+ if (new_scale != current_scale ||
+ offset_x != shell->offset_x ||
+ offset_y != shell->offset_y)
+ {
+ gimp_display_shell_scale_by_values (shell,
+ new_scale,
+ offset_x, offset_y,
+ resize_window);
+ }
+}
+
+/**
+ * gimp_display_shell_scale_fit_in:
+ * @shell: the #GimpDisplayShell
+ *
+ * Sets the scale such that the entire image precisely fits in the
+ * display area.
+ **/
+void
+gimp_display_shell_scale_fit_in (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_scale_fit_or_fill (shell,
+ /* fill = */ FALSE);
+ }
+
+/**
+ * gimp_display_shell_scale_fill:
+ * @shell: the #GimpDisplayShell
+ *
+ * Sets the scale such that the entire display area is precisely
+ * filled by the image.
+ **/
+void
+gimp_display_shell_scale_fill (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_scale_fit_or_fill (shell,
+ /* fill = */ TRUE);
+}
+
+/**
+ * gimp_display_shell_scale_by_values:
+ * @shell: the #GimpDisplayShell
+ * @scale: the new scale
+ * @offset_x: the new X offset
+ * @offset_y: the new Y offset
+ * @resize_window: whether the display window should be resized
+ *
+ * Directly sets the image scale and image offsets used by the display. If
+ * @resize_window is %TRUE then the display window is resized to better
+ * accommodate the image, see gimp_display_shell_shrink_wrap().
+ **/
+void
+gimp_display_shell_scale_by_values (GimpDisplayShell *shell,
+ gdouble scale,
+ gint offset_x,
+ gint offset_y,
+ gboolean resize_window)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ /* Abort early if the values are all setup already. We don't
+ * want to inadvertently resize the window (bug #164281).
+ */
+ if (SCALE_EQUALS (gimp_zoom_model_get_factor (shell->zoom), scale) &&
+ shell->offset_x == offset_x &&
+ shell->offset_y == offset_y)
+ return;
+
+ gimp_display_shell_scale_save_revert_values (shell);
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);
+
+ shell->offset_x = offset_x;
+ shell->offset_y = offset_y;
+
+ gimp_display_shell_rotate_update_transform (shell);
+
+ gimp_display_shell_scale_resize (shell, resize_window, FALSE);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+}
+
+void
+gimp_display_shell_scale_drag (GimpDisplayShell *shell,
+ gdouble start_x,
+ gdouble start_y,
+ gdouble delta_x,
+ gdouble delta_y)
+{
+ gdouble scale;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ scale = gimp_zoom_model_get_factor (shell->zoom);
+
+ gimp_display_shell_push_zoom_focus_pointer_pos (shell, start_x, start_y);
+
+ if (delta_y > 0)
+ {
+ gimp_display_shell_scale (shell,
+ GIMP_ZOOM_TO,
+ scale * 1.1,
+ GIMP_ZOOM_FOCUS_POINTER);
+ }
+ else if (delta_y < 0)
+ {
+ gimp_display_shell_scale (shell,
+ GIMP_ZOOM_TO,
+ scale * 0.9,
+ GIMP_ZOOM_FOCUS_POINTER);
+ }
+}
+
+/**
+ * gimp_display_shell_scale_shrink_wrap:
+ * @shell: the #GimpDisplayShell
+ *
+ * Convenience function with the same functionality as
+ * gimp_display_shell_scale_resize(@shell, TRUE, grow_only).
+ **/
+void
+gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell,
+ gboolean grow_only)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_scale_resize (shell, TRUE, grow_only);
+}
+
+/**
+ * gimp_display_shell_scale_resize:
+ * @shell: the #GimpDisplayShell
+ * @resize_window: whether the display window should be resized
+ * @grow_only: whether shrinking of the window is allowed or not
+ *
+ * Function commonly called after a change in display scale to make the changes
+ * visible to the user. If @resize_window is %TRUE then the display window is
+ * resized to accommodate the display image as per
+ * gimp_display_shell_shrink_wrap().
+ **/
+void
+gimp_display_shell_scale_resize (GimpDisplayShell *shell,
+ gboolean resize_window,
+ gboolean grow_only)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ if (resize_window)
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ gimp_image_window_shrink_wrap (window, grow_only);
+ }
+ }
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+ gimp_display_shell_scaled (shell);
+
+ gimp_display_shell_expose_full (shell);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+}
+
+void
+gimp_display_shell_set_initial_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gint *display_width,
+ gint *display_height)
+{
+ GimpImage *image;
+ GdkScreen *screen;
+ gint image_width;
+ gint image_height;
+ gint shell_width;
+ gint shell_height;
+ gint screen_width;
+ gint screen_height;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (shell));
+
+ image_width = gimp_image_get_width (image);
+ image_height = gimp_image_get_height (image);
+
+ screen_width = gdk_screen_get_width (screen) * 0.75;
+ screen_height = gdk_screen_get_height (screen) * 0.75;
+
+ /* We need to zoom before we use SCALE[XY] */
+ gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);
+
+ shell_width = SCALEX (shell, image_width);
+ shell_height = SCALEY (shell, image_height);
+
+ if (shell->display->config->initial_zoom_to_fit)
+ {
+ /* Limit to the size of the screen... */
+ if (shell_width > screen_width || shell_height > screen_height)
+ {
+ gdouble new_scale;
+ gdouble current = gimp_zoom_model_get_factor (shell->zoom);
+
+ new_scale = current * MIN (((gdouble) screen_height) / shell_height,
+ ((gdouble) screen_width) / shell_width);
+
+ new_scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, new_scale);
+
+ /* Since zooming out might skip a zoom step we zoom in
+ * again and test if we are small enough.
+ */
+ gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO,
+ gimp_zoom_model_zoom_step (GIMP_ZOOM_IN,
+ new_scale));
+
+ if (SCALEX (shell, image_width) > screen_width ||
+ SCALEY (shell, image_height) > screen_height)
+ gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale);
+
+ shell_width = SCALEX (shell, image_width);
+ shell_height = SCALEY (shell, image_height);
+ }
+ }
+ else
+ {
+ /* Set up size like above, but do not zoom to fit. Useful when
+ * working on large images.
+ */
+ if (shell_width > screen_width)
+ shell_width = screen_width;
+
+ if (shell_height > screen_height)
+ shell_height = screen_height;
+ }
+
+ if (display_width)
+ *display_width = shell_width;
+
+ if (display_height)
+ *display_height = shell_height;
+}
+
+/**
+ * gimp_display_shell_get_rotated_scale:
+ * @shell: the #GimpDisplayShell
+ * @scale_x: horizontal scale output
+ * @scale_y: vertical scale output
+ *
+ * Returns the screen space horizontal and vertical scaling
+ * factors, taking rotation into account.
+ **/
+void
+gimp_display_shell_get_rotated_scale (GimpDisplayShell *shell,
+ gdouble *scale_x,
+ gdouble *scale_y)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->rotate_angle == 0.0 || shell->scale_x == shell->scale_y)
+ {
+ if (scale_x) *scale_x = shell->scale_x;
+ if (scale_y) *scale_y = shell->scale_y;
+ }
+ else
+ {
+ gdouble a = G_PI * shell->rotate_angle / 180.0;
+ gdouble cos_a = cos (a);
+ gdouble sin_a = sin (a);
+
+ if (scale_x) *scale_x = 1.0 / sqrt (SQR (cos_a / shell->scale_x) +
+ SQR (sin_a / shell->scale_y));
+
+ if (scale_y) *scale_y = 1.0 / sqrt (SQR (cos_a / shell->scale_y) +
+ SQR (sin_a / shell->scale_x));
+ }
+}
+
+/**
+ * gimp_display_shell_push_zoom_focus_pointer_pos:
+ * @shell:
+ * @x:
+ * @y:
+ *
+ * When the zoom focus mechanism asks for the pointer the next time,
+ * use @x and @y.
+ **/
+void
+gimp_display_shell_push_zoom_focus_pointer_pos (GimpDisplayShell *shell,
+ gint x,
+ gint y)
+{
+ GdkPoint *point = g_slice_new (GdkPoint);
+ point->x = x;
+ point->y = y;
+
+ g_queue_push_head (shell->zoom_focus_pointer_queue,
+ point);
+}
+
+
+/* private functions */
+
+static void
+gimp_display_shell_scale_get_screen_resolution (GimpDisplayShell *shell,
+ gdouble *xres,
+ gdouble *yres)
+{
+ gdouble x, y;
+
+ if (shell->dot_for_dot)
+ {
+ gimp_image_get_resolution (gimp_display_get_image (shell->display),
+ &x, &y);
+ }
+ else
+ {
+ x = shell->monitor_xres;
+ y = shell->monitor_yres;
+ }
+
+ if (xres) *xres = x;
+ if (yres) *yres = y;
+}
+
+/**
+ * gimp_display_shell_scale_get_image_size_for_scale:
+ * @shell:
+ * @scale:
+ * @w:
+ * @h:
+ *
+ **/
+static void
+gimp_display_shell_scale_get_image_size_for_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gint *w,
+ gint *h)
+{
+ GimpImage *image = gimp_display_get_image (shell->display);
+ gdouble scale_x;
+ gdouble scale_y;
+
+ gimp_display_shell_calculate_scale_x_and_y (shell, scale, &scale_x, &scale_y);
+
+ if (w) *w = scale_x * gimp_image_get_width (image);
+ if (h) *h = scale_y * gimp_image_get_height (image);
+}
+
+/**
+ * gimp_display_shell_calculate_scale_x_and_y:
+ * @shell:
+ * @scale:
+ * @scale_x:
+ * @scale_y:
+ *
+ **/
+static void
+gimp_display_shell_calculate_scale_x_and_y (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble *scale_x,
+ gdouble *scale_y)
+{
+ GimpImage *image = gimp_display_get_image (shell->display);
+ gdouble xres;
+ gdouble yres;
+ gdouble screen_xres;
+ gdouble screen_yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+ gimp_display_shell_scale_get_screen_resolution (shell,
+ &screen_xres, &screen_yres);
+
+ if (scale_x) *scale_x = scale * screen_xres / xres;
+ if (scale_y) *scale_y = scale * screen_yres / yres;
+}
+
+/**
+ * gimp_display_shell_scale_to:
+ * @shell:
+ * @scale:
+ * @viewport_x:
+ * @viewport_y:
+ *
+ * Zooms. The display offsets are adjusted so that the point specified
+ * by @x and @y doesn't change it's position on screen.
+ **/
+static void
+gimp_display_shell_scale_to (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble viewport_x,
+ gdouble viewport_y)
+{
+ gdouble image_x, image_y;
+ gdouble new_viewport_x, new_viewport_y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->display)
+ return;
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ gimp_display_shell_untransform_xy_f (shell,
+ viewport_x,
+ viewport_y,
+ &image_x,
+ &image_y);
+
+ /* Note that we never come here if we need to resize_windows_on_zoom
+ */
+ gimp_display_shell_scale_by_values (shell,
+ scale,
+ shell->offset_x,
+ shell->offset_y,
+ FALSE);
+
+ gimp_display_shell_transform_xy_f (shell,
+ image_x,
+ image_y,
+ &new_viewport_x,
+ &new_viewport_y);
+
+ gimp_display_shell_scroll (shell,
+ new_viewport_x - viewport_x,
+ new_viewport_y - viewport_y);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+}
+
+/**
+ * gimp_display_shell_scale_fit_or_fill:
+ * @shell: the #GimpDisplayShell
+ * @fill: whether to scale the image to fill the viewport,
+ * or fit inside the viewport
+ *
+ * A common implementation for gimp_display_shell_scale_{fit_in,fill}().
+ **/
+static void
+gimp_display_shell_scale_fit_or_fill (GimpDisplayShell *shell,
+ gboolean fill)
+{
+ GeglRectangle bounding_box;
+ gdouble image_x;
+ gdouble image_y;
+ gdouble image_width;
+ gdouble image_height;
+ gdouble current_scale;
+ gdouble zoom_factor;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ bounding_box.x = 0;
+ bounding_box.y = 0;
+ bounding_box.width = gimp_image_get_width (image);
+ bounding_box.height = gimp_image_get_height (image);
+ }
+ else
+ {
+ bounding_box = gimp_display_shell_get_bounding_box (shell);
+ }
+
+ gimp_display_shell_transform_bounds (shell,
+ bounding_box.x,
+ bounding_box.y,
+ bounding_box.x + bounding_box.width,
+ bounding_box.y + bounding_box.height,
+ &image_x,
+ &image_y,
+ &image_width,
+ &image_height);
+
+ image_width -= image_x;
+ image_height -= image_y;
+
+ current_scale = gimp_zoom_model_get_factor (shell->zoom);
+
+ if (fill)
+ {
+ zoom_factor = MAX (shell->disp_width / image_width,
+ shell->disp_height / image_height);
+ }
+ else
+ {
+ zoom_factor = MIN (shell->disp_width / image_width,
+ shell->disp_height / image_height);
+ }
+
+ gimp_display_shell_scale (shell,
+ GIMP_ZOOM_TO,
+ zoom_factor * current_scale,
+ GIMP_ZOOM_FOCUS_BEST_GUESS);
+
+ gimp_display_shell_scroll_center_content (shell, TRUE, TRUE);
+}
+
+static gboolean
+gimp_display_shell_scale_image_starts_to_fit (GimpDisplayShell *shell,
+ gdouble new_scale,
+ gdouble current_scale,
+ gboolean *vertically,
+ gboolean *horizontally)
+{
+ gboolean vertically_dummy;
+ gboolean horizontally_dummy;
+
+ if (! vertically) vertically = &vertically_dummy;
+ if (! horizontally) horizontally = &horizontally_dummy;
+
+ /* The image can only start to fit if we zoom out */
+ if (new_scale > current_scale ||
+ gimp_display_shell_get_infinite_canvas (shell))
+ {
+ *vertically = FALSE;
+ *horizontally = FALSE;
+ }
+ else
+ {
+ gint current_scale_width;
+ gint current_scale_height;
+ gint new_scale_width;
+ gint new_scale_height;
+
+ gimp_display_shell_scale_get_image_size_for_scale (shell,
+ current_scale,
+ &current_scale_width,
+ &current_scale_height);
+
+ gimp_display_shell_scale_get_image_size_for_scale (shell,
+ new_scale,
+ &new_scale_width,
+ &new_scale_height);
+
+ *vertically = (current_scale_width > shell->disp_width &&
+ new_scale_width <= shell->disp_width);
+ *horizontally = (current_scale_height > shell->disp_height &&
+ new_scale_height <= shell->disp_height);
+ }
+
+ return *vertically && *horizontally;
+}
+
+static gboolean
+gimp_display_shell_scale_image_stops_to_fit (GimpDisplayShell *shell,
+ gdouble new_scale,
+ gdouble current_scale,
+ gboolean *vertically,
+ gboolean *horizontally)
+{
+ return gimp_display_shell_scale_image_starts_to_fit (shell,
+ current_scale,
+ new_scale,
+ vertically,
+ horizontally);
+}
+
+/**
+ * gimp_display_shell_scale_viewport_coord_almost_centered:
+ * @shell:
+ * @x:
+ * @y:
+ * @horizontally:
+ * @vertically:
+ *
+ **/
+static gboolean
+gimp_display_shell_scale_viewport_coord_almost_centered (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gboolean *horizontally,
+ gboolean *vertically)
+{
+ gboolean local_horizontally = FALSE;
+ gboolean local_vertically = FALSE;
+ gint center_x = shell->disp_width / 2;
+ gint center_y = shell->disp_height / 2;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ local_horizontally = (x > center_x - ALMOST_CENTERED_THRESHOLD &&
+ x < center_x + ALMOST_CENTERED_THRESHOLD);
+
+ local_vertically = (y > center_y - ALMOST_CENTERED_THRESHOLD &&
+ y < center_y + ALMOST_CENTERED_THRESHOLD);
+ }
+
+ if (horizontally) *horizontally = local_horizontally;
+ if (vertically) *vertically = local_vertically;
+
+ return local_horizontally && local_vertically;
+}
+
+static void
+gimp_display_shell_scale_get_image_center_viewport (GimpDisplayShell *shell,
+ gint *image_center_x,
+ gint *image_center_y)
+{
+ gint sw, sh;
+
+ gimp_display_shell_scale_get_image_size (shell, &sw, &sh);
+
+ if (image_center_x) *image_center_x = -shell->offset_x + sw / 2;
+ if (image_center_y) *image_center_y = -shell->offset_y + sh / 2;
+}
+
+/**
+ * gimp_display_shell_scale_get_zoom_focus:
+ * @shell:
+ * @new_scale:
+ * @x:
+ * @y:
+ *
+ * Calculates the viewport coordinate to focus on when zooming
+ * independently for each axis.
+ **/
+static void
+gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell,
+ gdouble new_scale,
+ gdouble current_scale,
+ gdouble *x,
+ gdouble *y,
+ GimpZoomFocus zoom_focus)
+{
+ GtkWidget *window = GTK_WIDGET (gimp_display_shell_get_window (shell));
+ GdkEvent *event;
+ gint image_center_x;
+ gint image_center_y;
+ gint other_x;
+ gint other_y;
+
+ /* Calculate stops-to-fit focus point */
+ gimp_display_shell_scale_get_image_center_viewport (shell,
+ &image_center_x,
+ &image_center_y);
+
+ /* Calculate other focus point, default is the canvas center */
+ other_x = shell->disp_width / 2;
+ other_y = shell->disp_height / 2;
+
+ /* Center on the mouse position instead of the display center if
+ * one of the following conditions are fulfilled and pointer is
+ * within the canvas:
+ *
+ * (1) there's no current event (the action was triggered by an
+ * input controller)
+ * (2) the event originates from the canvas (a scroll event)
+ * (3) the event originates from the window (a key press event)
+ *
+ * Basically the only situation where we don't want to center on
+ * mouse position is if the action is being called from a menu.
+ */
+ event = gtk_get_current_event ();
+
+ if (! event ||
+ gtk_get_event_widget (event) == shell->canvas ||
+ gtk_get_event_widget (event) == window)
+ {
+ GdkPoint *point = g_queue_pop_head (shell->zoom_focus_pointer_queue);
+ gint canvas_pointer_x;
+ gint canvas_pointer_y;
+
+ if (point)
+ {
+ canvas_pointer_x = point->x;
+ canvas_pointer_y = point->y;
+
+ g_slice_free (GdkPoint, point);
+ }
+ else
+ {
+ gtk_widget_get_pointer (shell->canvas,
+ &canvas_pointer_x,
+ &canvas_pointer_y);
+ }
+
+ if (canvas_pointer_x >= 0 &&
+ canvas_pointer_y >= 0 &&
+ canvas_pointer_x < shell->disp_width &&
+ canvas_pointer_y < shell->disp_height)
+ {
+ other_x = canvas_pointer_x;
+ other_y = canvas_pointer_y;
+ }
+ }
+
+ if (zoom_focus == GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS)
+ {
+ if (gimp_display_shell_scale_viewport_coord_almost_centered (shell,
+ image_center_x,
+ image_center_y,
+ NULL,
+ NULL))
+ {
+ zoom_focus = GIMP_ZOOM_FOCUS_IMAGE_CENTER;
+ }
+ else
+ {
+ zoom_focus = GIMP_ZOOM_FOCUS_BEST_GUESS;
+ }
+ }
+
+ switch (zoom_focus)
+ {
+ case GIMP_ZOOM_FOCUS_POINTER:
+ *x = other_x;
+ *y = other_y;
+ break;
+
+ case GIMP_ZOOM_FOCUS_IMAGE_CENTER:
+ *x = image_center_x;
+ *y = image_center_y;
+ break;
+
+ case GIMP_ZOOM_FOCUS_BEST_GUESS:
+ default:
+ {
+ gboolean within_horizontally, within_vertically;
+ gboolean stops_horizontally, stops_vertically;
+
+ gimp_display_shell_scale_image_is_within_viewport (shell,
+ &within_horizontally,
+ &within_vertically);
+
+ gimp_display_shell_scale_image_stops_to_fit (shell,
+ new_scale,
+ current_scale,
+ &stops_horizontally,
+ &stops_vertically);
+
+ *x = within_horizontally && ! stops_horizontally ? image_center_x : other_x;
+ *y = within_vertically && ! stops_vertically ? image_center_y : other_y;
+ }
+ break;
+ }
+}
diff --git a/app/display/gimpdisplayshell-scale.h b/app/display/gimpdisplayshell-scale.h
new file mode 100644
index 0000000..b97f9fb
--- /dev/null
+++ b/app/display/gimpdisplayshell-scale.h
@@ -0,0 +1,108 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_SCALE_H__
+#define __GIMP_DISPLAY_SHELL_SCALE_H__
+
+
+gboolean gimp_display_shell_scale_revert (GimpDisplayShell *shell);
+gboolean gimp_display_shell_scale_can_revert (GimpDisplayShell *shell);
+void gimp_display_shell_scale_save_revert_values (GimpDisplayShell *shell);
+
+void gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell,
+ gboolean dot_for_dot);
+
+void gimp_display_shell_scale_get_image_size (GimpDisplayShell *shell,
+ gint *w,
+ gint *h);
+void gimp_display_shell_scale_get_image_bounds (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+void gimp_display_shell_scale_get_image_unrotated_bounds
+ (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+void gimp_display_shell_scale_get_image_bounding_box
+ (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+void gimp_display_shell_scale_get_image_unrotated_bounding_box
+ (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+gboolean gimp_display_shell_scale_image_is_within_viewport
+ (GimpDisplayShell *shell,
+ gboolean *horizontally,
+ gboolean *vertically);
+
+void gimp_display_shell_scale_update (GimpDisplayShell *shell);
+
+void gimp_display_shell_scale (GimpDisplayShell *shell,
+ GimpZoomType zoom_type,
+ gdouble scale,
+ GimpZoomFocus zoom_focus);
+void gimp_display_shell_scale_to_rectangle (GimpDisplayShell *shell,
+ GimpZoomType zoom_type,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gboolean resize_window);
+void gimp_display_shell_scale_fit_in (GimpDisplayShell *shell);
+void gimp_display_shell_scale_fill (GimpDisplayShell *shell);
+void gimp_display_shell_scale_by_values (GimpDisplayShell *shell,
+ gdouble scale,
+ gint offset_x,
+ gint offset_y,
+ gboolean resize_window);
+
+void gimp_display_shell_scale_drag (GimpDisplayShell *shell,
+ gdouble start_x,
+ gdouble start_y,
+ gdouble delta_x,
+ gdouble delta_y);
+
+void gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell,
+ gboolean grow_only);
+void gimp_display_shell_scale_resize (GimpDisplayShell *shell,
+ gboolean resize_window,
+ gboolean grow_only);
+void gimp_display_shell_set_initial_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gint *display_width,
+ gint *display_height);
+
+void gimp_display_shell_get_rotated_scale (GimpDisplayShell *shell,
+ gdouble *scale_x,
+ gdouble *scale_y);
+
+/* debug API for testing */
+
+void gimp_display_shell_push_zoom_focus_pointer_pos (GimpDisplayShell *shell,
+ gint x,
+ gint y);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_SCALE_H__ */
diff --git a/app/display/gimpdisplayshell-scroll.c b/app/display/gimpdisplayshell-scroll.c
new file mode 100644
index 0000000..ec5084d
--- /dev/null
+++ b/app/display/gimpdisplayshell-scroll.c
@@ -0,0 +1,529 @@
+/* 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 <stdlib.h>
+#include <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpcanvas.h"
+#include "gimpdisplay.h"
+#include "gimpdisplay-foreach.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-rotate.h"
+#include "gimpdisplayshell-rulers.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-scrollbars.h"
+#include "gimpdisplayshell-transform.h"
+
+
+#define OVERPAN_FACTOR 0.5
+
+
+/**
+ * gimp_display_shell_scroll:
+ * @shell:
+ * @x_offset:
+ * @y_offset:
+ *
+ * This function scrolls the image in the shell's viewport. It does
+ * actual scrolling of the pixels, so only the newly scrolled-in parts
+ * are freshly redrawn.
+ *
+ * Use it for incremental actual panning.
+ **/
+void
+gimp_display_shell_scroll (GimpDisplayShell *shell,
+ gint x_offset,
+ gint y_offset)
+{
+ gint old_x;
+ gint old_y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (x_offset == 0 && y_offset == 0)
+ return;
+
+ old_x = shell->offset_x;
+ old_y = shell->offset_y;
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ shell->offset_x += x_offset;
+ shell->offset_y += y_offset;
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+
+ /* the actual changes in offset */
+ x_offset = (shell->offset_x - old_x);
+ y_offset = (shell->offset_y - old_y);
+
+ if (x_offset || y_offset)
+ {
+ gimp_display_shell_scrolled (shell);
+
+ gimp_overlay_box_scroll (GIMP_OVERLAY_BOX (shell->canvas),
+ -x_offset, -y_offset);
+
+ }
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+}
+
+/**
+ * gimp_display_shell_scroll_set_offsets:
+ * @shell:
+ * @offset_x:
+ * @offset_y:
+ *
+ * This function scrolls the image in the shell's viewport. It redraws
+ * the entire canvas.
+ *
+ * Use it for setting the scroll offset on freshly scaled images or
+ * when the window is resized. For panning, use
+ * gimp_display_shell_scroll().
+ **/
+void
+gimp_display_shell_scroll_set_offset (GimpDisplayShell *shell,
+ gint offset_x,
+ gint offset_y)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->offset_x == offset_x &&
+ shell->offset_y == offset_y)
+ return;
+
+ gimp_display_shell_scale_save_revert_values (shell);
+
+ /* freeze the active tool */
+ gimp_display_shell_pause (shell);
+
+ shell->offset_x = offset_x;
+ shell->offset_y = offset_y;
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+
+ gimp_display_shell_scrolled (shell);
+
+ gimp_display_shell_expose_full (shell);
+
+ /* re-enable the active tool */
+ gimp_display_shell_resume (shell);
+}
+
+/**
+ * gimp_display_shell_scroll_clamp_and_update:
+ * @shell:
+ *
+ * Helper function for calling two functions that are commonly called
+ * in pairs.
+ **/
+void
+gimp_display_shell_scroll_clamp_and_update (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ if (! shell->show_all)
+ {
+ gint bounds_x;
+ gint bounds_y;
+ gint bounds_width;
+ gint bounds_height;
+ gint min_offset_x;
+ gint max_offset_x;
+ gint min_offset_y;
+ gint max_offset_y;
+ gint offset_x;
+ gint offset_y;
+
+ gimp_display_shell_rotate_update_transform (shell);
+
+ gimp_display_shell_scale_get_image_bounds (shell,
+ &bounds_x,
+ &bounds_y,
+ &bounds_width,
+ &bounds_height);
+
+ if (shell->disp_width < bounds_width)
+ {
+ min_offset_x = bounds_x -
+ shell->disp_width * OVERPAN_FACTOR;
+ max_offset_x = bounds_x + bounds_width -
+ shell->disp_width * (1.0 - OVERPAN_FACTOR);
+ }
+ else
+ {
+ gint overpan_amount;
+
+ overpan_amount = shell->disp_width -
+ bounds_width * (1.0 - OVERPAN_FACTOR);
+
+ min_offset_x = bounds_x -
+ overpan_amount;
+ max_offset_x = bounds_x + bounds_width - shell->disp_width +
+ overpan_amount;
+ }
+
+ if (shell->disp_height < bounds_height)
+ {
+ min_offset_y = bounds_y -
+ shell->disp_height * OVERPAN_FACTOR;
+ max_offset_y = bounds_y + bounds_height -
+ shell->disp_height * (1.0 - OVERPAN_FACTOR);
+ }
+ else
+ {
+ gint overpan_amount;
+
+ overpan_amount = shell->disp_height -
+ bounds_height * (1.0 - OVERPAN_FACTOR);
+
+ min_offset_y = bounds_y -
+ overpan_amount;
+ max_offset_y = bounds_y + bounds_height +
+ overpan_amount - shell->disp_height;
+ }
+
+ /* Clamp */
+
+ offset_x = CLAMP (shell->offset_x, min_offset_x, max_offset_x);
+ offset_y = CLAMP (shell->offset_y, min_offset_y, max_offset_y);
+
+ if (offset_x != shell->offset_x || offset_y != shell->offset_y)
+ {
+ shell->offset_x = offset_x;
+ shell->offset_y = offset_y;
+
+ gimp_display_shell_rotate_update_transform (shell);
+ }
+
+ /* Set scrollbar stepper sensitiity */
+
+ gimp_display_shell_scrollbars_update_steppers (shell,
+ min_offset_x,
+ max_offset_x,
+ min_offset_y,
+ max_offset_y);
+ }
+ else
+ {
+ /* Set scrollbar stepper sensitiity */
+
+ gimp_display_shell_scrollbars_update_steppers (shell,
+ G_MININT,
+ G_MAXINT,
+ G_MININT,
+ G_MAXINT);
+ }
+ }
+ else
+ {
+ shell->offset_x = 0;
+ shell->offset_y = 0;
+ }
+
+ gimp_display_shell_scrollbars_update (shell);
+ gimp_display_shell_rulers_update (shell);
+}
+
+/**
+ * gimp_display_shell_scroll_unoverscrollify:
+ * @shell:
+ * @in_offset_x:
+ * @in_offset_y:
+ * @out_offset_x:
+ * @out_offset_y:
+ *
+ * Takes a scroll offset and returns the offset that will not result
+ * in a scroll beyond the image border. If the image is already
+ * overscrolled, the return value is 0 for that given axis.
+ **/
+void
+gimp_display_shell_scroll_unoverscrollify (GimpDisplayShell *shell,
+ gint in_offset_x,
+ gint in_offset_y,
+ gint *out_offset_x,
+ gint *out_offset_y)
+{
+ gint sw, sh;
+ gint out_offset_x_dummy, out_offset_y_dummy;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! out_offset_x) out_offset_x = &out_offset_x_dummy;
+ if (! out_offset_y) out_offset_y = &out_offset_y_dummy;
+
+ *out_offset_x = in_offset_x;
+ *out_offset_y = in_offset_y;
+
+ if (! shell->show_all)
+ {
+ gimp_display_shell_scale_get_image_size (shell, &sw, &sh);
+
+ if (in_offset_x < 0)
+ {
+ *out_offset_x = MAX (in_offset_x,
+ MIN (0, 0 - shell->offset_x));
+ }
+ else if (in_offset_x > 0)
+ {
+ gint min_offset = sw - shell->disp_width;
+
+ *out_offset_x = MIN (in_offset_x,
+ MAX (0, min_offset - shell->offset_x));
+ }
+
+ if (in_offset_y < 0)
+ {
+ *out_offset_y = MAX (in_offset_y,
+ MIN (0, 0 - shell->offset_y));
+ }
+ else if (in_offset_y > 0)
+ {
+ gint min_offset = sh - shell->disp_height;
+
+ *out_offset_y = MIN (in_offset_y,
+ MAX (0, min_offset - shell->offset_y));
+ }
+ }
+}
+
+/**
+ * gimp_display_shell_scroll_center_image_xy:
+ * @shell:
+ * @image_x:
+ * @image_y:
+ *
+ * Center the viewport around the passed image coordinate
+ **/
+void
+gimp_display_shell_scroll_center_image_xy (GimpDisplayShell *shell,
+ gdouble image_x,
+ gdouble image_y)
+{
+ gint viewport_x;
+ gint viewport_y;
+
+ gimp_display_shell_transform_xy (shell,
+ image_x, image_y,
+ &viewport_x, &viewport_y);
+
+ gimp_display_shell_scroll (shell,
+ viewport_x - shell->disp_width / 2,
+ viewport_y - shell->disp_height / 2);
+}
+
+/**
+ * gimp_display_shell_scroll_center_image:
+ * @shell:
+ * @horizontally:
+ * @vertically:
+ *
+ * Centers the image in the display shell on the desired axes.
+ **/
+void
+gimp_display_shell_scroll_center_image (GimpDisplayShell *shell,
+ gboolean horizontally,
+ gboolean vertically)
+{
+ gint image_x;
+ gint image_y;
+ gint image_width;
+ gint image_height;
+ gint center_x;
+ gint center_y;
+ gint offset_x = 0;
+ gint offset_y = 0;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->display ||
+ ! gimp_display_get_image (shell->display) ||
+ (! vertically && ! horizontally))
+ return;
+
+ gimp_display_shell_scale_get_image_bounds (shell,
+ &image_x, &image_y,
+ &image_width, &image_height);
+
+ if (shell->disp_width > image_width)
+ {
+ image_x -= (shell->disp_width - image_width) / 2;
+ image_width = shell->disp_width;
+ }
+
+ if (shell->disp_height > image_height)
+ {
+ image_y -= (shell->disp_height - image_height) / 2;
+ image_height = shell->disp_height;
+ }
+
+ center_x = image_x + image_width / 2;
+ center_y = image_y + image_height / 2;
+
+ if (horizontally)
+ offset_x = center_x - shell->disp_width / 2 - shell->offset_x;
+
+ if (vertically)
+ offset_y = center_y - shell->disp_height / 2 - shell->offset_y;
+
+ gimp_display_shell_scroll (shell, offset_x, offset_y);
+}
+
+/**
+ * gimp_display_shell_scroll_center_image:
+ * @shell:
+ * @horizontally:
+ * @vertically:
+ *
+ * Centers the image content in the display shell on the desired axes.
+ **/
+void
+gimp_display_shell_scroll_center_content (GimpDisplayShell *shell,
+ gboolean horizontally,
+ gboolean vertically)
+{
+ gint content_x;
+ gint content_y;
+ gint content_width;
+ gint content_height;
+ gint center_x;
+ gint center_y;
+ gint offset_x = 0;
+ gint offset_y = 0;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->display ||
+ ! gimp_display_get_image (shell->display) ||
+ (! vertically && ! horizontally))
+ return;
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ gimp_display_shell_scale_get_image_bounds (shell,
+ &content_x,
+ &content_y,
+ &content_width,
+ &content_height);
+ }
+ else
+ {
+ gimp_display_shell_scale_get_image_bounding_box (shell,
+ &content_x,
+ &content_y,
+ &content_width,
+ &content_height);
+ }
+
+ if (shell->disp_width > content_width)
+ {
+ content_x -= (shell->disp_width - content_width) / 2;
+ content_width = shell->disp_width;
+ }
+
+ if (shell->disp_height > content_height)
+ {
+ content_y -= (shell->disp_height - content_height) / 2;
+ content_height = shell->disp_height;
+ }
+
+ center_x = content_x + content_width / 2;
+ center_y = content_y + content_height / 2;
+
+ if (horizontally)
+ offset_x = center_x - shell->disp_width / 2 - shell->offset_x;
+
+ if (vertically)
+ offset_y = center_y - shell->disp_height / 2 - shell->offset_y;
+
+ gimp_display_shell_scroll (shell, offset_x, offset_y);
+}
+
+/**
+ * gimp_display_shell_scroll_get_scaled_viewport:
+ * @shell:
+ * @x:
+ * @y:
+ * @w:
+ * @h:
+ *
+ * Gets the viewport in screen coordinates, with origin at (0, 0) in
+ * the image.
+ **/
+void
+gimp_display_shell_scroll_get_scaled_viewport (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ *x = shell->offset_x;
+ *y = shell->offset_y;
+ *w = shell->disp_width;
+ *h = shell->disp_height;
+}
+
+/**
+ * gimp_display_shell_scroll_get_viewport:
+ * @shell:
+ * @x:
+ * @y:
+ * @w:
+ * @h:
+ *
+ * Gets the viewport in image coordinates.
+ **/
+void
+gimp_display_shell_scroll_get_viewport (GimpDisplayShell *shell,
+ gdouble *x,
+ gdouble *y,
+ gdouble *w,
+ gdouble *h)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ *x = shell->offset_x / shell->scale_x;
+ *y = shell->offset_y / shell->scale_y;
+ *w = shell->disp_width / shell->scale_x;
+ *h = shell->disp_height / shell->scale_y;
+}
diff --git a/app/display/gimpdisplayshell-scroll.h b/app/display/gimpdisplayshell-scroll.h
new file mode 100644
index 0000000..fba444b
--- /dev/null
+++ b/app/display/gimpdisplayshell-scroll.h
@@ -0,0 +1,59 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_SCROLL_H__
+#define __GIMP_DISPLAY_SHELL_SCROLL_H__
+
+
+void gimp_display_shell_scroll (GimpDisplayShell *shell,
+ gint x_offset,
+ gint y_offset);
+void gimp_display_shell_scroll_set_offset (GimpDisplayShell *shell,
+ gint offset_x,
+ gint offset_y);
+
+void gimp_display_shell_scroll_clamp_and_update (GimpDisplayShell *shell);
+
+void gimp_display_shell_scroll_unoverscrollify (GimpDisplayShell *shell,
+ gint in_offset_x,
+ gint in_offset_y,
+ gint *out_offset_x,
+ gint *out_offset_y);
+
+void gimp_display_shell_scroll_center_image_xy (GimpDisplayShell *shell,
+ gdouble image_x,
+ gdouble image_y);
+void gimp_display_shell_scroll_center_image (GimpDisplayShell *shell,
+ gboolean horizontally,
+ gboolean vertically);
+void gimp_display_shell_scroll_center_content (GimpDisplayShell *shell,
+ gboolean horizontally,
+ gboolean vertically);
+
+void gimp_display_shell_scroll_get_scaled_viewport (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+void gimp_display_shell_scroll_get_viewport (GimpDisplayShell *shell,
+ gdouble *x,
+ gdouble *y,
+ gdouble *w,
+ gdouble *h);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_SCROLL_H__ */
diff --git a/app/display/gimpdisplayshell-scrollbars.c b/app/display/gimpdisplayshell-scrollbars.c
new file mode 100644
index 0000000..3696e01
--- /dev/null
+++ b/app/display/gimpdisplayshell-scrollbars.c
@@ -0,0 +1,244 @@
+/* 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 <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "display-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scrollbars.h"
+
+
+#define MINIMUM_STEP_AMOUNT 1.0
+
+
+/**
+ * gimp_display_shell_scrollbars_update:
+ * @shell:
+ *
+ **/
+void
+gimp_display_shell_scrollbars_update (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->display)
+ return;
+
+ /* Horizontal scrollbar */
+
+ g_object_freeze_notify (G_OBJECT (shell->hsbdata));
+
+ /* Update upper and lower value before we set the new value */
+ gimp_display_shell_scrollbars_setup_horizontal (shell, shell->offset_x);
+
+ g_object_set (shell->hsbdata,
+ "value", (gdouble) shell->offset_x,
+ "page-size", (gdouble) shell->disp_width,
+ "page-increment", (gdouble) shell->disp_width / 2,
+ NULL);
+
+ g_object_thaw_notify (G_OBJECT (shell->hsbdata)); /* emits "changed" */
+
+
+ /* Vertcal scrollbar */
+
+ g_object_freeze_notify (G_OBJECT (shell->vsbdata));
+
+ /* Update upper and lower value before we set the new value */
+ gimp_display_shell_scrollbars_setup_vertical (shell, shell->offset_y);
+
+ g_object_set (shell->vsbdata,
+ "value", (gdouble) shell->offset_y,
+ "page-size", (gdouble) shell->disp_height,
+ "page-increment", (gdouble) shell->disp_height / 2,
+ NULL);
+
+ g_object_thaw_notify (G_OBJECT (shell->vsbdata)); /* emits "changed" */
+}
+
+/**
+ * gimp_display_shell_scrollbars_setup_horizontal:
+ * @shell:
+ * @value:
+ *
+ * Setup the limits of the horizontal scrollbar
+ **/
+void
+gimp_display_shell_scrollbars_setup_horizontal (GimpDisplayShell *shell,
+ gdouble value)
+{
+ gint bounds_x;
+ gint bounds_width;
+ gint bounding_box_x;
+ gint bounding_box_width;
+ gint x1;
+ gint x2;
+ gdouble lower;
+ gdouble upper;
+ gdouble scale_x;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->display || ! gimp_display_get_image (shell->display))
+ return;
+
+ gimp_display_shell_scale_get_image_bounds (shell,
+ &bounds_x, NULL,
+ &bounds_width, NULL);
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ bounding_box_x = bounds_x;
+ bounding_box_width = bounds_width;
+ }
+ else
+ {
+ gimp_display_shell_scale_get_image_bounding_box (
+ shell,
+ &bounding_box_x, NULL,
+ &bounding_box_width, NULL);
+ }
+
+ x1 = bounding_box_x;
+ x2 = bounding_box_x + bounding_box_width;
+
+ x1 = MIN (x1, bounds_x + bounds_width / 2 - shell->disp_width / 2);
+ x2 = MAX (x2, bounds_x + bounds_width / 2 + (shell->disp_width + 1) / 2);
+
+ lower = MIN (value, x1);
+ upper = MAX (value + shell->disp_width, x2);
+
+ gimp_display_shell_get_rotated_scale (shell, &scale_x, NULL);
+
+ g_object_set (shell->hsbdata,
+ "lower", lower,
+ "upper", upper,
+ "step-increment", (gdouble) MAX (scale_x, MINIMUM_STEP_AMOUNT),
+ NULL);
+}
+
+/**
+ * gimp_display_shell_scrollbars_setup_vertical:
+ * @shell:
+ * @value:
+ *
+ * Setup the limits of the vertical scrollbar
+ **/
+void
+gimp_display_shell_scrollbars_setup_vertical (GimpDisplayShell *shell,
+ gdouble value)
+{
+ gint bounds_y;
+ gint bounds_height;
+ gint bounding_box_y;
+ gint bounding_box_height;
+ gint y1;
+ gint y2;
+ gdouble lower;
+ gdouble upper;
+ gdouble scale_y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (! shell->display || ! gimp_display_get_image (shell->display))
+ return;
+
+ gimp_display_shell_scale_get_image_bounds (shell,
+ NULL, &bounds_y,
+ NULL, &bounds_height);
+
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ {
+ bounding_box_y = bounds_y;
+ bounding_box_height = bounds_height;
+ }
+ else
+ {
+ gimp_display_shell_scale_get_image_bounding_box (
+ shell,
+ NULL, &bounding_box_y,
+ NULL, &bounding_box_height);
+ }
+
+ y1 = bounding_box_y;
+ y2 = bounding_box_y + bounding_box_height;
+
+ y1 = MIN (y1, bounds_y + bounds_height / 2 - shell->disp_height / 2);
+ y2 = MAX (y2, bounds_y + bounds_height / 2 + (shell->disp_height + 1) / 2);
+
+ lower = MIN (value, y1);
+ upper = MAX (value + shell->disp_height, y2);
+
+ gimp_display_shell_get_rotated_scale (shell, NULL, &scale_y);
+
+ g_object_set (shell->vsbdata,
+ "lower", lower,
+ "upper", upper,
+ "step-increment", (gdouble) MAX (scale_y, MINIMUM_STEP_AMOUNT),
+ NULL);
+}
+
+/**
+ * gimp_display_shell_scrollbars_update_steppers:
+ * @shell:
+ * @min_offset_x:
+ * @max_offset_x:
+ * @min_offset_y:
+ * @max_offset_y:
+ *
+ * Sets the scrollbars' stepper sensitivity which is set differently
+ * from its adjustment limits because we support overscrolling.
+ **/
+void
+gimp_display_shell_scrollbars_update_steppers (GimpDisplayShell *shell,
+ gint min_offset_x,
+ gint max_offset_x,
+ gint min_offset_y,
+ gint max_offset_y)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gtk_range_set_lower_stepper_sensitivity (GTK_RANGE (shell->hsb),
+ min_offset_x < shell->offset_x ?
+ GTK_SENSITIVITY_ON :
+ GTK_SENSITIVITY_OFF);
+
+ gtk_range_set_upper_stepper_sensitivity (GTK_RANGE (shell->hsb),
+ max_offset_x > shell->offset_x ?
+ GTK_SENSITIVITY_ON :
+ GTK_SENSITIVITY_OFF);
+
+ gtk_range_set_lower_stepper_sensitivity (GTK_RANGE (shell->vsb),
+ min_offset_y < shell->offset_y ?
+ GTK_SENSITIVITY_ON :
+ GTK_SENSITIVITY_OFF);
+
+ gtk_range_set_upper_stepper_sensitivity (GTK_RANGE (shell->vsb),
+ max_offset_y > shell->offset_y ?
+ GTK_SENSITIVITY_ON :
+ GTK_SENSITIVITY_OFF);
+}
diff --git a/app/display/gimpdisplayshell-scrollbars.h b/app/display/gimpdisplayshell-scrollbars.h
new file mode 100644
index 0000000..c15f802
--- /dev/null
+++ b/app/display/gimpdisplayshell-scrollbars.h
@@ -0,0 +1,36 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_SCROLLBARS_H__
+#define __GIMP_DISPLAY_SHELL_SCROLLBARS_H__
+
+
+void gimp_display_shell_scrollbars_update (GimpDisplayShell *shell);
+
+void gimp_display_shell_scrollbars_setup_horizontal (GimpDisplayShell *shell,
+ gdouble value);
+void gimp_display_shell_scrollbars_setup_vertical (GimpDisplayShell *shell,
+ gdouble value);
+
+void gimp_display_shell_scrollbars_update_steppers (GimpDisplayShell *shell,
+ gint min_offset_x,
+ gint max_offset_x,
+ gint min_offset_y,
+ gint max_offset_y);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_SCROLLBARS_H__ */
diff --git a/app/display/gimpdisplayshell-selection.c b/app/display/gimpdisplayshell-selection.c
new file mode 100644
index 0000000..3098ddc
--- /dev/null
+++ b/app/display/gimpdisplayshell-selection.c
@@ -0,0 +1,505 @@
+/* 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 "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimp-cairo.h"
+#include "core/gimpboundary.h"
+#include "core/gimpchannel.h"
+#include "core/gimpimage.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-draw.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-selection.h"
+#include "gimpdisplayshell-transform.h"
+
+
+struct _Selection
+{
+ GimpDisplayShell *shell; /* shell that owns the selection */
+
+ GimpSegment *segs_in; /* gdk segments of area boundary */
+ gint n_segs_in; /* number of segments in segs_in */
+
+ GimpSegment *segs_out; /* gdk segments of area boundary */
+ gint n_segs_out; /* number of segments in segs_out */
+
+ guint index; /* index of current stipple pattern */
+ gint paused; /* count of pause requests */
+ gboolean shell_visible; /* visility of the display shell */
+ gboolean show_selection; /* is the selection visible? */
+ guint timeout; /* timer for successive draws */
+ cairo_pattern_t *segs_in_mask; /* cache for rendered segments */
+};
+
+
+/* local function prototypes */
+
+static void selection_start (Selection *selection);
+static void selection_stop (Selection *selection);
+
+static void selection_draw (Selection *selection,
+ cairo_t *cr);
+static void selection_undraw (Selection *selection);
+
+static void selection_render_mask (Selection *selection);
+
+static void selection_zoom_segs (Selection *selection,
+ const GimpBoundSeg *src_segs,
+ GimpSegment *dest_segs,
+ gint n_segs);
+static void selection_generate_segs (Selection *selection);
+static void selection_free_segs (Selection *selection);
+
+static gboolean selection_start_timeout (Selection *selection);
+static gboolean selection_timeout (Selection *selection);
+
+static gboolean selection_window_state_event (GtkWidget *shell,
+ GdkEventWindowState *event,
+ Selection *selection);
+static gboolean selection_visibility_notify_event (GtkWidget *shell,
+ GdkEventVisibility *event,
+ Selection *selection);
+
+
+/* public functions */
+
+void
+gimp_display_shell_selection_init (GimpDisplayShell *shell)
+{
+ Selection *selection;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection == NULL);
+
+ selection = g_slice_new0 (Selection);
+
+ selection->shell = shell;
+ selection->shell_visible = TRUE;
+ selection->show_selection = gimp_display_shell_get_show_selection (shell);
+
+ shell->selection = selection;
+
+ g_signal_connect (shell, "window-state-event",
+ G_CALLBACK (selection_window_state_event),
+ selection);
+ g_signal_connect (shell, "visibility-notify-event",
+ G_CALLBACK (selection_visibility_notify_event),
+ selection);
+}
+
+void
+gimp_display_shell_selection_free (GimpDisplayShell *shell)
+{
+ Selection *selection;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection != NULL);
+
+ selection = shell->selection;
+
+ selection_stop (selection);
+
+ g_signal_handlers_disconnect_by_func (shell,
+ selection_window_state_event,
+ selection);
+ g_signal_handlers_disconnect_by_func (shell,
+ selection_visibility_notify_event,
+ selection);
+
+ selection_free_segs (selection);
+
+ g_slice_free (Selection, selection);
+
+ shell->selection = NULL;
+}
+
+void
+gimp_display_shell_selection_undraw (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection != NULL);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ selection_undraw (shell->selection);
+ }
+ else
+ {
+ selection_stop (shell->selection);
+ selection_free_segs (shell->selection);
+ }
+}
+
+void
+gimp_display_shell_selection_restart (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection != NULL);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ selection_start (shell->selection);
+ }
+}
+
+void
+gimp_display_shell_selection_pause (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection != NULL);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ if (shell->selection->paused == 0)
+ selection_stop (shell->selection);
+
+ shell->selection->paused++;
+ }
+}
+
+void
+gimp_display_shell_selection_resume (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection != NULL);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ shell->selection->paused--;
+
+ if (shell->selection->paused == 0)
+ selection_start (shell->selection);
+ }
+}
+
+void
+gimp_display_shell_selection_set_show (GimpDisplayShell *shell,
+ gboolean show)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->selection != NULL);
+
+ if (gimp_display_get_image (shell->display))
+ {
+ Selection *selection = shell->selection;
+
+ if (show != selection->show_selection)
+ {
+ selection_undraw (selection);
+
+ selection->show_selection = show;
+
+ selection_start (selection);
+ }
+ }
+}
+
+
+/* private functions */
+
+static void
+selection_start (Selection *selection)
+{
+ selection_stop (selection);
+
+ /* If this selection is paused, do not start it */
+ if (selection->paused == 0)
+ {
+ selection->timeout = g_idle_add ((GSourceFunc) selection_start_timeout,
+ selection);
+ }
+}
+
+static void
+selection_stop (Selection *selection)
+{
+ if (selection->timeout)
+ {
+ g_source_remove (selection->timeout);
+ selection->timeout = 0;
+ }
+}
+
+static void
+selection_draw (Selection *selection,
+ cairo_t *cr)
+{
+ if (selection->segs_in)
+ {
+ gimp_display_shell_draw_selection_in (selection->shell, cr,
+ selection->segs_in_mask,
+ selection->index % 8);
+ }
+}
+
+static void
+selection_undraw (Selection *selection)
+{
+ gint x, y, w, h;
+
+ selection_stop (selection);
+
+ if (gimp_display_shell_mask_bounds (selection->shell, &x, &y, &w, &h))
+ {
+ /* expose will restart the selection */
+ gimp_display_shell_expose_area (selection->shell, x, y, w, h);
+ }
+ else
+ {
+ selection_start (selection);
+ }
+}
+
+static void
+selection_render_mask (Selection *selection)
+{
+ GdkWindow *window;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ window = gtk_widget_get_window (selection->shell->canvas);
+ surface = gdk_window_create_similar_surface (window, CAIRO_CONTENT_ALPHA,
+ gdk_window_get_width (window),
+ gdk_window_get_height (window));
+ cr = cairo_create (surface);
+
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_line_width (cr, 1.0);
+
+ if (selection->shell->rotate_transform)
+ cairo_transform (cr, selection->shell->rotate_transform);
+
+ gimp_cairo_segments (cr,
+ selection->segs_in,
+ selection->n_segs_in);
+ cairo_stroke (cr);
+
+ selection->segs_in_mask = cairo_pattern_create_for_surface (surface);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+}
+
+static void
+selection_zoom_segs (Selection *selection,
+ const GimpBoundSeg *src_segs,
+ GimpSegment *dest_segs,
+ gint n_segs)
+{
+ const gint xclamp = selection->shell->disp_width + 1;
+ const gint yclamp = selection->shell->disp_height + 1;
+ gint i;
+
+ gimp_display_shell_zoom_segments (selection->shell,
+ src_segs, dest_segs, n_segs,
+ 0.0, 0.0);
+
+ for (i = 0; i < n_segs; i++)
+ {
+ if (! selection->shell->rotate_transform)
+ {
+ dest_segs[i].x1 = CLAMP (dest_segs[i].x1, -1, xclamp);
+ dest_segs[i].y1 = CLAMP (dest_segs[i].y1, -1, yclamp);
+
+ dest_segs[i].x2 = CLAMP (dest_segs[i].x2, -1, xclamp);
+ dest_segs[i].y2 = CLAMP (dest_segs[i].y2, -1, yclamp);
+ }
+
+ /* If this segment is a closing segment && the segments lie inside
+ * the region, OR if this is an opening segment and the segments
+ * lie outside the region...
+ * we need to transform it by one display pixel
+ */
+ if (! src_segs[i].open)
+ {
+ /* If it is vertical */
+ if (dest_segs[i].x1 == dest_segs[i].x2)
+ {
+ dest_segs[i].x1 -= 1;
+ dest_segs[i].x2 -= 1;
+ }
+ else
+ {
+ dest_segs[i].y1 -= 1;
+ dest_segs[i].y2 -= 1;
+ }
+ }
+ }
+}
+
+static void
+selection_generate_segs (Selection *selection)
+{
+ GimpImage *image = gimp_display_get_image (selection->shell->display);
+ const GimpBoundSeg *segs_in;
+ const GimpBoundSeg *segs_out;
+
+ /* Ask the image for the boundary of its selected region...
+ * Then transform that information into a new buffer of GimpSegments
+ */
+ gimp_channel_boundary (gimp_image_get_mask (image),
+ &segs_in, &segs_out,
+ &selection->n_segs_in, &selection->n_segs_out,
+ 0, 0, 0, 0);
+
+ if (selection->n_segs_in)
+ {
+ selection->segs_in = g_new (GimpSegment, selection->n_segs_in);
+ selection_zoom_segs (selection, segs_in,
+ selection->segs_in, selection->n_segs_in);
+
+ selection_render_mask (selection);
+ }
+ else
+ {
+ selection->segs_in = NULL;
+ }
+
+ /* Possible secondary boundary representation */
+ if (selection->n_segs_out)
+ {
+ selection->segs_out = g_new (GimpSegment, selection->n_segs_out);
+ selection_zoom_segs (selection, segs_out,
+ selection->segs_out, selection->n_segs_out);
+ }
+ else
+ {
+ selection->segs_out = NULL;
+ }
+}
+
+static void
+selection_free_segs (Selection *selection)
+{
+ g_clear_pointer (&selection->segs_in, g_free);
+ selection->n_segs_in = 0;
+
+ g_clear_pointer (&selection->segs_out, g_free);
+ selection->segs_out = NULL;
+
+ g_clear_pointer (&selection->segs_in_mask, cairo_pattern_destroy);
+}
+
+static gboolean
+selection_start_timeout (Selection *selection)
+{
+ selection_free_segs (selection);
+ selection->timeout = 0;
+
+ if (! gimp_display_get_image (selection->shell->display))
+ return FALSE;
+
+ selection_generate_segs (selection);
+
+ selection->index = 0;
+
+ /* Draw the ants */
+ if (selection->show_selection)
+ {
+ GimpDisplayConfig *config = selection->shell->display->config;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (gtk_widget_get_window (selection->shell->canvas));
+
+ selection_draw (selection, cr);
+
+ if (selection->segs_out)
+ {
+ if (selection->shell->rotate_transform)
+ cairo_transform (cr, selection->shell->rotate_transform);
+
+ gimp_display_shell_draw_selection_out (selection->shell, cr,
+ selection->segs_out,
+ selection->n_segs_out);
+ }
+
+ cairo_destroy (cr);
+
+ if (selection->segs_in && selection->shell_visible)
+ selection->timeout = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
+ config->marching_ants_speed,
+ (GSourceFunc) selection_timeout,
+ selection, NULL);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_timeout (Selection *selection)
+{
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (gtk_widget_get_window (selection->shell->canvas));
+
+ selection->index++;
+ selection_draw (selection, cr);
+
+ cairo_destroy (cr);
+
+ return TRUE;
+}
+
+static void
+selection_set_shell_visible (Selection *selection,
+ gboolean shell_visible)
+{
+ if (selection->shell_visible != shell_visible)
+ {
+ selection->shell_visible = shell_visible;
+
+ if (shell_visible)
+ selection_start (selection);
+ else
+ selection_stop (selection);
+ }
+}
+
+static gboolean
+selection_window_state_event (GtkWidget *shell,
+ GdkEventWindowState *event,
+ Selection *selection)
+{
+ selection_set_shell_visible (selection,
+ (event->new_window_state & (GDK_WINDOW_STATE_WITHDRAWN |
+ GDK_WINDOW_STATE_ICONIFIED)) == 0);
+
+ return FALSE;
+}
+
+static gboolean
+selection_visibility_notify_event (GtkWidget *shell,
+ GdkEventVisibility *event,
+ Selection *selection)
+{
+ selection_set_shell_visible (selection,
+ event->state != GDK_VISIBILITY_FULLY_OBSCURED);
+
+ return FALSE;
+}
diff --git a/app/display/gimpdisplayshell-selection.h b/app/display/gimpdisplayshell-selection.h
new file mode 100644
index 0000000..2f63c80
--- /dev/null
+++ b/app/display/gimpdisplayshell-selection.h
@@ -0,0 +1,35 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_SELECTION_H__
+#define __GIMP_DISPLAY_SHELL_SELECTION_H__
+
+
+void gimp_display_shell_selection_init (GimpDisplayShell *shell);
+void gimp_display_shell_selection_free (GimpDisplayShell *shell);
+
+void gimp_display_shell_selection_undraw (GimpDisplayShell *shell);
+void gimp_display_shell_selection_restart (GimpDisplayShell *shell);
+
+void gimp_display_shell_selection_pause (GimpDisplayShell *shell);
+void gimp_display_shell_selection_resume (GimpDisplayShell *shell);
+
+void gimp_display_shell_selection_set_show (GimpDisplayShell *shell,
+ gboolean show);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_SELECTION_H__ */
diff --git a/app/display/gimpdisplayshell-title.c b/app/display/gimpdisplayshell-title.c
new file mode 100644
index 0000000..ff356ce
--- /dev/null
+++ b/app/display/gimpdisplayshell-title.c
@@ -0,0 +1,564 @@
+/* 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "libgimpbase/gimpbase.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "core/gimpcontainer.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-color-profile.h"
+#include "core/gimpitem.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-title.h"
+#include "gimpstatusbar.h"
+
+#include "about.h"
+
+#include "gimp-intl.h"
+
+
+#define MAX_TITLE_BUF 512
+
+
+static gboolean gimp_display_shell_update_title_idle (gpointer data);
+static gint gimp_display_shell_format_title (GimpDisplayShell *display,
+ gchar *title,
+ gint title_len,
+ const gchar *format);
+
+
+/* public functions */
+
+void
+gimp_display_shell_title_update (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->title_idle_id)
+ g_source_remove (shell->title_idle_id);
+
+ shell->title_idle_id = g_idle_add (gimp_display_shell_update_title_idle,
+ shell);
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_display_shell_update_title_idle (gpointer data)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data);
+
+ shell->title_idle_id = 0;
+
+ if (gimp_display_get_image (shell->display))
+ {
+ GimpDisplayConfig *config = shell->display->config;
+ gchar title[MAX_TITLE_BUF];
+ gchar status[MAX_TITLE_BUF];
+ gint len;
+
+ /* format the title */
+ len = gimp_display_shell_format_title (shell, title, sizeof (title),
+ config->image_title_format);
+
+ if (len) /* U+2013 EN DASH */
+ len += g_strlcpy (title + len, " \342\200\223 ", sizeof (title) - len);
+
+ g_strlcpy (title + len, GIMP_ACRONYM, sizeof (title) - len);
+
+ /* format the statusbar */
+ gimp_display_shell_format_title (shell, status, sizeof (status),
+ config->image_status_format);
+
+ g_object_set (shell,
+ "title", title,
+ "status", status,
+ NULL);
+ }
+ else
+ {
+ g_object_set (shell,
+ "title", GIMP_NAME,
+ "status", " ",
+ NULL);
+ }
+
+ return FALSE;
+}
+
+static const gchar *
+gimp_display_shell_title_image_type (GimpImage *image)
+{
+ const gchar *name = "";
+
+ gimp_enum_get_value (GIMP_TYPE_IMAGE_BASE_TYPE,
+ gimp_image_get_base_type (image), NULL, NULL, &name, NULL);
+
+ return name;
+}
+
+static const gchar *
+gimp_display_shell_title_image_precision (GimpImage *image)
+{
+ const gchar *name = "";
+
+ gimp_enum_get_value (GIMP_TYPE_PRECISION,
+ gimp_image_get_precision (image), NULL, NULL, &name, NULL);
+
+ return name;
+}
+
+static gint print (gchar *buf,
+ gint len,
+ gint start,
+ const gchar *fmt,
+ ...) G_GNUC_PRINTF (4, 5);
+
+static gint
+print (gchar *buf,
+ gint len,
+ gint start,
+ const gchar *fmt,
+ ...)
+{
+ va_list args;
+ gint printed;
+
+ va_start (args, fmt);
+
+ printed = g_vsnprintf (buf + start, len - start, fmt, args);
+ if (printed < 0)
+ printed = len - start;
+
+ va_end (args);
+
+ return printed;
+}
+
+static gint
+gimp_display_shell_format_title (GimpDisplayShell *shell,
+ gchar *title,
+ gint title_len,
+ const gchar *format)
+{
+ GimpImage *image;
+ GimpDrawable *drawable;
+ gint num, denom;
+ gint i = 0;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), 0);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (! image)
+ {
+ title[0] = '\n';
+ return 0;
+ }
+
+ drawable = gimp_image_get_active_drawable (image);
+
+ gimp_zoom_model_get_fraction (shell->zoom, &num, &denom);
+
+ while (i < title_len && *format)
+ {
+ switch (*format)
+ {
+ case '%':
+ format++;
+ switch (*format)
+ {
+ case 0:
+ /* format string ends within %-sequence, print literal '%' */
+
+ case '%':
+ title[i++] = '%';
+ break;
+
+ case 'f': /* base filename */
+ i += print (title, title_len, i, "%s",
+ gimp_image_get_display_name (image));
+ break;
+
+ case 'F': /* full filename */
+ i += print (title, title_len, i, "%s",
+ gimp_image_get_display_path (image));
+ break;
+
+ case 'p': /* PDB id */
+ i += print (title, title_len, i, "%d", gimp_image_get_ID (image));
+ break;
+
+ case 'i': /* instance */
+ i += print (title, title_len, i, "%d",
+ gimp_display_get_instance (shell->display));
+ break;
+
+ case 't': /* image type */
+ i += print (title, title_len, i, "%s %s",
+ gimp_display_shell_title_image_type (image),
+ gimp_display_shell_title_image_precision (image));
+ break;
+
+ case 'T': /* drawable type */
+ if (drawable)
+ {
+ const Babl *format = gimp_drawable_get_format (drawable);
+
+ i += print (title, title_len, i, "%s",
+ gimp_babl_format_get_description (format));
+ }
+ break;
+
+ case 's': /* user source zoom factor */
+ i += print (title, title_len, i, "%d", denom);
+ break;
+
+ case 'd': /* user destination zoom factor */
+ i += print (title, title_len, i, "%d", num);
+ break;
+
+ case 'z': /* user zoom factor (percentage) */
+ {
+ gdouble scale = gimp_zoom_model_get_factor (shell->zoom);
+
+ i += print (title, title_len, i,
+ scale >= 0.15 ? "%.0f" : "%.2f", 100.0 * scale);
+ }
+ break;
+
+ case 'D': /* dirty flag */
+ if (format[1] == 0)
+ {
+ /* format string ends within %D-sequence, print literal '%D' */
+ i += print (title, title_len, i, "%%D");
+ break;
+ }
+ if (gimp_image_is_dirty (image))
+ title[i++] = format[1];
+ format++;
+ break;
+
+ case 'C': /* clean flag */
+ if (format[1] == 0)
+ {
+ /* format string ends within %C-sequence, print literal '%C' */
+ i += print (title, title_len, i, "%%C");
+ break;
+ }
+ if (! gimp_image_is_dirty (image))
+ title[i++] = format[1];
+ format++;
+ break;
+
+ case 'B': /* dirty flag (long) */
+ if (gimp_image_is_dirty (image))
+ i += print (title, title_len, i, "%s", _("(modified)"));
+ break;
+
+ case 'A': /* clean flag (long) */
+ if (! gimp_image_is_dirty (image))
+ i += print (title, title_len, i, "%s", _("(clean)"));
+ break;
+
+ case 'N': /* not-exported flag */
+ if (format[1] == 0)
+ {
+ /* format string ends within %E-sequence, print literal '%E' */
+ i += print (title, title_len, i, "%%N");
+ break;
+ }
+ if (gimp_image_is_export_dirty (image))
+ title[i++] = format[1];
+ format++;
+ break;
+
+ case 'E': /* exported flag */
+ if (format[1] == 0)
+ {
+ /* format string ends within %E-sequence, print literal '%E' */
+ i += print (title, title_len, i, "%%E");
+ break;
+ }
+ if (! gimp_image_is_export_dirty (image))
+ title[i++] = format[1];
+ format++;
+ break;
+
+ case 'm': /* memory used by image */
+ {
+ GimpObject *object = GIMP_OBJECT (image);
+ gchar *str;
+
+ str = g_format_size (gimp_object_get_memsize (object, NULL));
+ i += print (title, title_len, i, "%s", str);
+ g_free (str);
+ }
+ break;
+
+ case 'M': /* image size in megapixels */
+ i += print (title, title_len, i, "%.1f",
+ (gdouble) gimp_image_get_width (image) *
+ (gdouble) gimp_image_get_height (image) / 1000000.0);
+ break;
+
+ case 'l': /* number of layers */
+ i += print (title, title_len, i, "%d",
+ gimp_image_get_n_layers (image));
+ break;
+
+ case 'L': /* number of layers (long) */
+ {
+ gint num = gimp_image_get_n_layers (image);
+
+ i += print (title, title_len, i,
+ ngettext ("%d layer", "%d layers", num), num);
+ }
+ break;
+
+ case 'n': /* active drawable name */
+ if (drawable)
+ {
+ gchar *desc;
+
+ desc = gimp_viewable_get_description (GIMP_VIEWABLE (drawable),
+ NULL);
+ i += print (title, title_len, i, "%s", desc);
+ g_free (desc);
+ }
+ else
+ {
+ i += print (title, title_len, i, "%s", _("(none)"));
+ }
+ break;
+
+ case 'P': /* active drawable PDB id */
+ if (drawable)
+ i += print (title, title_len, i, "%d",
+ gimp_item_get_ID (GIMP_ITEM (drawable)));
+ else
+ i += print (title, title_len, i, "%s", _("(none)"));
+ break;
+
+ case 'W': /* width in real-world units */
+ if (shell->unit != GIMP_UNIT_PIXEL)
+ {
+ gdouble xres;
+ gdouble yres;
+ gchar unit_format[8];
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_snprintf (unit_format, sizeof (unit_format), "%%.%df",
+ gimp_unit_get_scaled_digits (shell->unit, xres));
+ i += print (title, title_len, i, unit_format,
+ gimp_pixels_to_units (gimp_image_get_width (image),
+ shell->unit, xres));
+ break;
+ }
+ /* else fallthru */
+
+ case 'w': /* width in pixels */
+ i += print (title, title_len, i, "%d",
+ gimp_image_get_width (image));
+ break;
+
+ case 'H': /* height in real-world units */
+ if (shell->unit != GIMP_UNIT_PIXEL)
+ {
+ gdouble xres;
+ gdouble yres;
+ gchar unit_format[8];
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_snprintf (unit_format, sizeof (unit_format), "%%.%df",
+ gimp_unit_get_scaled_digits (shell->unit, yres));
+ i += print (title, title_len, i, unit_format,
+ gimp_pixels_to_units (gimp_image_get_height (image),
+ shell->unit, yres));
+ break;
+ }
+ /* else fallthru */
+
+ case 'h': /* height in pixels */
+ i += print (title, title_len, i, "%d",
+ gimp_image_get_height (image));
+ break;
+
+ case 'u': /* unit symbol */
+ i += print (title, title_len, i, "%s",
+ gimp_unit_get_symbol (shell->unit));
+ break;
+
+ case 'U': /* unit abbreviation */
+ i += print (title, title_len, i, "%s",
+ gimp_unit_get_abbreviation (shell->unit));
+ break;
+
+ case 'X': /* drawable width in real world units */
+ if (drawable && shell->unit != GIMP_UNIT_PIXEL)
+ {
+ gdouble xres;
+ gdouble yres;
+ gchar unit_format[8];
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_snprintf (unit_format, sizeof (unit_format), "%%.%df",
+ gimp_unit_get_scaled_digits (shell->unit, xres));
+ i += print (title, title_len, i, unit_format,
+ gimp_pixels_to_units (gimp_item_get_width
+ (GIMP_ITEM (drawable)),
+ shell->unit, xres));
+ break;
+ }
+ /* else fallthru */
+
+ case 'x': /* drawable width in pixels */
+ if (drawable)
+ i += print (title, title_len, i, "%d",
+ gimp_item_get_width (GIMP_ITEM (drawable)));
+ break;
+
+ case 'Y': /* drawable height in real world units */
+ if (drawable && shell->unit != GIMP_UNIT_PIXEL)
+ {
+ gdouble xres;
+ gdouble yres;
+ gchar unit_format[8];
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_snprintf (unit_format, sizeof (unit_format), "%%.%df",
+ gimp_unit_get_scaled_digits (shell->unit, yres));
+ i += print (title, title_len, i, unit_format,
+ gimp_pixels_to_units (gimp_item_get_height
+ (GIMP_ITEM (drawable)),
+ shell->unit, yres));
+ break;
+ }
+ /* else fallthru */
+
+ case 'y': /* drawable height in pixels */
+ if (drawable)
+ i += print (title, title_len, i, "%d",
+ gimp_item_get_height (GIMP_ITEM (drawable)));
+ break;
+
+ case 'o': /* image's color profile name */
+ if (gimp_image_get_is_color_managed (image))
+ {
+ GimpColorManaged *managed = GIMP_COLOR_MANAGED (image);
+ GimpColorProfile *profile;
+
+ profile = gimp_color_managed_get_color_profile (managed);
+
+ i += print (title, title_len, i, "%s",
+ gimp_color_profile_get_label (profile));
+ }
+ else
+ {
+ i += print (title, title_len, i, "%s",
+ _("not color managed"));
+ }
+ break;
+
+ case 'e': /* display's offsets in pixels */
+ {
+ gdouble scale = gimp_zoom_model_get_factor (shell->zoom);
+ gdouble offset_x = shell->offset_x / scale;
+ gdouble offset_y = shell->offset_y / scale;
+
+ i += print (title, title_len, i,
+ scale >= 0.15 ? "%.0fx%.0f" : "%.2fx%.2f",
+ offset_x, offset_y);
+ }
+ break;
+
+ case 'r': /* view rotation angle in degrees */
+ {
+ i += print (title, title_len, i, "%.1f", shell->rotate_angle);
+ }
+ break;
+
+ case '\xc3': /* utf-8 extended char */
+ {
+ format ++;
+ switch (*format)
+ {
+ case '\xbe':
+ /* line actually written at 23:55 on an Easter Sunday */
+ i += print (title, title_len, i, "42");
+ break;
+
+ default:
+ /* in the case of an unhandled utf-8 extended char format
+ * leave the format string parsing as it was
+ */
+ format--;
+ break;
+ }
+ }
+ break;
+
+ /* Other cool things to be added:
+ * %r = xresolution
+ * %R = yresolution
+ * %ø = image's fractal dimension
+ * %þ = the answer to everything - (implemented)
+ */
+
+ default:
+ /* format string contains unknown %-sequence, print it literally */
+ i += print (title, title_len, i, "%%%c", *format);
+ break;
+ }
+ break;
+
+ default:
+ title[i++] = *format;
+ break;
+ }
+
+ format++;
+ }
+
+ title[MIN (i, title_len - 1)] = '\0';
+
+ return i;
+}
diff --git a/app/display/gimpdisplayshell-title.h b/app/display/gimpdisplayshell-title.h
new file mode 100644
index 0000000..0a3ed21
--- /dev/null
+++ b/app/display/gimpdisplayshell-title.h
@@ -0,0 +1,25 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_TITLE_H__
+#define __GIMP_DISPLAY_SHELL_TITLE_H__
+
+
+void gimp_display_shell_title_update (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_TITLE_H__ */
diff --git a/app/display/gimpdisplayshell-tool-events.c b/app/display/gimpdisplayshell-tool-events.c
new file mode 100644
index 0000000..714f39f
--- /dev/null
+++ b/app/display/gimpdisplayshell-tool-events.c
@@ -0,0 +1,2144 @@
+/* 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+#include "tools/tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimp-filter-history.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpitem.h"
+
+#include "widgets/gimpcontrollers.h"
+#include "widgets/gimpcontrollerkeyboard.h"
+#include "widgets/gimpcontrollermouse.h"
+#include "widgets/gimpcontrollerwheel.h"
+#include "widgets/gimpdeviceinfo.h"
+#include "widgets/gimpdeviceinfo-coords.h"
+#include "widgets/gimpdevicemanager.h"
+#include "widgets/gimpdevices.h"
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "tools/gimpguidetool.h"
+#include "tools/gimpmovetool.h"
+#include "tools/gimpsamplepointtool.h"
+#include "tools/gimptoolcontrol.h"
+#include "tools/tool_manager.h"
+
+#include "gimpcanvas.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-autoscroll.h"
+#include "gimpdisplayshell-cursor.h"
+#include "gimpdisplayshell-grab.h"
+#include "gimpdisplayshell-layer-select.h"
+#include "gimpdisplayshell-rotate.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-tool-events.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpimagewindow.h"
+#include "gimpmotionbuffer.h"
+#include "gimpstatusbar.h"
+
+#include "gimp-intl.h"
+#include "gimp-log.h"
+
+
+/* local function prototypes */
+
+static gboolean gimp_display_shell_canvas_tool_events_internal (GtkWidget *canvas,
+ GdkEvent *event,
+ GimpDisplayShell *shell,
+ GdkEvent **next_event);
+
+static GdkModifierType
+ gimp_display_shell_key_to_state (gint key);
+static GdkModifierType
+ gimp_display_shell_button_to_state (gint button);
+
+static void gimp_display_shell_proximity_in (GimpDisplayShell *shell);
+static void gimp_display_shell_proximity_out (GimpDisplayShell *shell);
+
+static void gimp_display_shell_check_device_cursor (GimpDisplayShell *shell);
+
+static void gimp_display_shell_start_scrolling (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ GdkModifierType state,
+ gint x,
+ gint y);
+static void gimp_display_shell_stop_scrolling (GimpDisplayShell *shell,
+ const GdkEvent *event);
+static void gimp_display_shell_handle_scrolling (GimpDisplayShell *shell,
+ GdkModifierType state,
+ gint x,
+ gint y);
+
+static void gimp_display_shell_space_pressed (GimpDisplayShell *shell,
+ const GdkEvent *event);
+static void gimp_display_shell_released (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ const GimpCoords *image_coords);
+
+static gboolean gimp_display_shell_tab_pressed (GimpDisplayShell *shell,
+ const GdkEventKey *event);
+
+static void gimp_display_shell_update_focus (GimpDisplayShell *shell,
+ gboolean focus_in,
+ const GimpCoords *image_coords,
+ GdkModifierType state);
+static void gimp_display_shell_update_cursor (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ const GimpCoords *image_coords,
+ GdkModifierType state,
+ gboolean update_software_cursor);
+
+static gboolean gimp_display_shell_initialize_tool (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GdkModifierType state);
+
+static void gimp_display_shell_get_event_coords (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ GimpCoords *display_coords,
+ GdkModifierType *state,
+ guint32 *time);
+static void gimp_display_shell_untransform_event_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords,
+ gboolean *update_software_cursor);
+
+static GdkEvent * gimp_display_shell_compress_motion (GdkEvent *initial_event,
+ GdkEvent **next_event);
+
+
+/* public functions */
+
+gboolean
+gimp_display_shell_events (GtkWidget *widget,
+ GdkEvent *event,
+ GimpDisplayShell *shell)
+{
+ Gimp *gimp;
+ gboolean set_display = FALSE;
+
+ /* are we in destruction? */
+ if (! shell->display || ! gimp_display_get_shell (shell->display))
+ return TRUE;
+
+ gimp = gimp_display_get_gimp (shell->display);
+
+ switch (event->type)
+ {
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ {
+ GdkEventKey *kevent = (GdkEventKey *) event;
+
+ if (gimp->busy)
+ return TRUE;
+
+ /* do not process most key events while BUTTON1 is down. We do this
+ * so tools keep the modifier state they were in when BUTTON1 was
+ * pressed and to prevent accelerators from being invoked.
+ */
+ if (kevent->state & GDK_BUTTON1_MASK)
+ {
+ if (kevent->keyval == GDK_KEY_Shift_L ||
+ kevent->keyval == GDK_KEY_Shift_R ||
+ kevent->keyval == GDK_KEY_Control_L ||
+ kevent->keyval == GDK_KEY_Control_R ||
+ kevent->keyval == GDK_KEY_Alt_L ||
+ kevent->keyval == GDK_KEY_Alt_R ||
+ kevent->keyval == GDK_KEY_Meta_L ||
+ kevent->keyval == GDK_KEY_Meta_R ||
+ kevent->keyval == GDK_KEY_space ||
+ kevent->keyval == GDK_KEY_KP_Space)
+ {
+ break;
+ }
+
+ return TRUE;
+ }
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left: case GDK_KEY_Right:
+ case GDK_KEY_Up: case GDK_KEY_Down:
+ case GDK_KEY_space:
+ case GDK_KEY_KP_Space:
+ case GDK_KEY_Tab:
+ case GDK_KEY_KP_Tab:
+ case GDK_KEY_ISO_Left_Tab:
+ case GDK_KEY_Alt_L: case GDK_KEY_Alt_R:
+ case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
+ case GDK_KEY_Control_L: case GDK_KEY_Control_R:
+ case GDK_KEY_Meta_L: case GDK_KEY_Meta_R:
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Escape:
+ break;
+
+ default:
+ if (shell->space_release_pending ||
+ shell->button1_release_pending ||
+ shell->scrolling)
+ return TRUE;
+ break;
+ }
+
+ set_display = TRUE;
+ break;
+ }
+
+ case GDK_BUTTON_PRESS:
+ case GDK_SCROLL:
+ set_display = TRUE;
+ break;
+
+ case GDK_FOCUS_CHANGE:
+ {
+ GdkEventFocus *fevent = (GdkEventFocus *) event;
+
+ if (fevent->in && shell->display->config->activate_on_focus)
+ set_display = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Setting the context's display automatically sets the image, too */
+ if (set_display)
+ gimp_context_set_display (gimp_get_user_context (gimp), shell->display);
+
+ return FALSE;
+}
+
+static gboolean
+gimp_display_shell_canvas_no_image_events (GtkWidget *canvas,
+ GdkEvent *event,
+ GimpDisplayShell *shell)
+{
+ switch (event->type)
+ {
+ case GDK_2BUTTON_PRESS:
+ {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ if (bevent->button == 1)
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_ui_manager_activate_action (manager, "file", "file-open");
+ }
+ return TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+ if (gdk_event_triggers_context_menu (event))
+ {
+ gimp_ui_manager_ui_popup (shell->popup_manager,
+ "/dummy-menubar/image-popup",
+ GTK_WIDGET (shell),
+ NULL, NULL, NULL, NULL);
+ return TRUE;
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ {
+ GdkEventKey *kevent = (GdkEventKey *) event;
+
+ if (kevent->keyval == GDK_KEY_Tab ||
+ kevent->keyval == GDK_KEY_KP_Tab ||
+ kevent->keyval == GDK_KEY_ISO_Left_Tab)
+ {
+ return gimp_display_shell_tab_pressed (shell, kevent);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
+ GdkEvent *event,
+ GimpDisplayShell *shell)
+{
+ GdkEvent *next_event = NULL;
+ gboolean return_val;
+
+ g_return_val_if_fail (gtk_widget_get_realized (canvas), FALSE);
+
+ return_val = gimp_display_shell_canvas_tool_events_internal (canvas,
+ event, shell,
+ &next_event);
+
+ if (next_event)
+ {
+ gtk_main_do_event (next_event);
+
+ gdk_event_free (next_event);
+ }
+
+ return return_val;
+}
+
+void
+gimp_display_shell_canvas_grab_notify (GtkWidget *canvas,
+ gboolean was_grabbed,
+ GimpDisplayShell *shell)
+{
+ GimpDisplay *display;
+ GimpImage *image;
+ Gimp *gimp;
+
+ /* are we in destruction? */
+ if (! shell->display || ! gimp_display_get_shell (shell->display))
+ return;
+
+ display = shell->display;
+ gimp = gimp_display_get_gimp (display);
+ image = gimp_display_get_image (display);
+
+ if (! image)
+ return;
+
+ GIMP_LOG (TOOL_EVENTS, "grab_notify (display %p): was_grabbed = %s",
+ display, was_grabbed ? "TRUE" : "FALSE");
+
+ if (! was_grabbed)
+ {
+ if (! gimp_image_is_empty (image))
+ {
+ GimpTool *active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool && active_tool->focus_display == display)
+ {
+ tool_manager_modifier_state_active (gimp, 0, display);
+ }
+ }
+ }
+}
+
+void
+gimp_display_shell_buffer_stroke (GimpMotionBuffer *buffer,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplayShell *shell)
+{
+ GimpDisplay *display = shell->display;
+ Gimp *gimp = gimp_display_get_gimp (display);
+ GimpTool *active_tool;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool &&
+ gimp_tool_control_is_active (active_tool->control))
+ {
+ tool_manager_motion_active (gimp,
+ coords, time, state,
+ display);
+ }
+}
+
+void
+gimp_display_shell_buffer_hover (GimpMotionBuffer *buffer,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplayShell *shell)
+{
+ GimpDisplay *display = shell->display;
+ Gimp *gimp = gimp_display_get_gimp (display);
+ GimpTool *active_tool;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool &&
+ ! gimp_tool_control_is_active (active_tool->control))
+ {
+ tool_manager_oper_update_active (gimp,
+ coords, state, proximity,
+ display);
+ }
+}
+
+static gboolean
+gimp_display_shell_ruler_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ GimpDisplayShell *shell,
+ GimpOrientationType orientation)
+{
+ GimpDisplay *display = shell->display;
+
+ if (display->gimp->busy)
+ return TRUE;
+
+ if (! gimp_display_get_image (display))
+ return TRUE;
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1)
+ {
+ GimpTool *active_tool = tool_manager_get_active (display->gimp);
+
+ if (active_tool)
+ {
+ gimp_display_shell_update_focus (shell, TRUE,
+ NULL, event->state);
+
+ if (gimp_display_shell_pointer_grab (shell, NULL, 0))
+ {
+ if (gimp_display_shell_keyboard_grab (shell,
+ (GdkEvent *) event))
+ {
+ if (event->state & gimp_get_toggle_behavior_mask ())
+ {
+ gimp_sample_point_tool_start_new (active_tool, display);
+ }
+ else
+ {
+ gimp_guide_tool_start_new (active_tool, display,
+ orientation);
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ gimp_display_shell_pointer_ungrab (shell, NULL);
+ }
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_display_shell_hruler_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ GimpDisplayShell *shell)
+{
+ return gimp_display_shell_ruler_button_press (widget, event, shell,
+ GIMP_ORIENTATION_HORIZONTAL);
+}
+
+gboolean
+gimp_display_shell_vruler_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ GimpDisplayShell *shell)
+{
+ return gimp_display_shell_ruler_button_press (widget, event, shell,
+ GIMP_ORIENTATION_VERTICAL);
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_display_shell_canvas_tool_events_internal (GtkWidget *canvas,
+ GdkEvent *event,
+ GimpDisplayShell *shell,
+ GdkEvent **next_event)
+{
+ GimpDisplay *display;
+ GimpImage *image;
+ Gimp *gimp;
+ GimpCoords display_coords;
+ GimpCoords image_coords;
+ GdkModifierType state;
+ guint32 time;
+ gboolean device_changed = FALSE;
+ gboolean return_val = FALSE;
+ gboolean update_sw_cursor = FALSE;
+
+ *next_event = NULL;
+
+ /* are we in destruction? */
+ if (! shell->display || ! gimp_display_get_shell (shell->display))
+ return TRUE;
+
+ /* set the active display before doing any other canvas event processing */
+ if (gimp_display_shell_events (canvas, event, shell))
+ return TRUE;
+
+ /* events on overlays have a different window, but these windows'
+ * user_data can still be the canvas, we need to check manually if
+ * the event's window and the canvas' window are different.
+ */
+ if (event->any.window != gtk_widget_get_window (canvas))
+ {
+ GtkWidget *event_widget;
+
+ gdk_window_get_user_data (event->any.window, (gpointer) &event_widget);
+
+ /* if the event came from a different window than the canvas',
+ * check if it came from a canvas child and bail out.
+ */
+ if (gtk_widget_get_ancestor (event_widget, GIMP_TYPE_CANVAS))
+ return FALSE;
+ }
+
+ display = shell->display;
+ gimp = gimp_display_get_gimp (display);
+ image = gimp_display_get_image (display);
+
+ if (! image)
+ return gimp_display_shell_canvas_no_image_events (canvas, event, shell);
+
+ GIMP_LOG (TOOL_EVENTS, "event (display %p): %s",
+ display, gimp_print_event (event));
+
+ /* See bug 771444 */
+ if (shell->pointer_grabbed &&
+ event->type == GDK_MOTION_NOTIFY)
+ {
+ GimpDeviceManager *manager = gimp_devices_get_manager (gimp);
+ GimpDeviceInfo *info;
+
+ info = gimp_device_manager_get_current_device (manager);
+
+ if (info->device != event->motion.device)
+ return FALSE;
+ }
+
+ /* Find out what device the event occurred upon */
+ if (! gimp->busy &&
+ ! shell->inferior_ignore_mode &&
+ gimp_devices_check_change (gimp, event))
+ {
+ gimp_display_shell_check_device_cursor (shell);
+ device_changed = TRUE;
+ }
+
+ gimp_display_shell_get_event_coords (shell, event,
+ &display_coords,
+ &state, &time);
+ gimp_display_shell_untransform_event_coords (shell,
+ &display_coords, &image_coords,
+ &update_sw_cursor);
+
+ /* If the device (and maybe the tool) has changed, update the new
+ * tool's state
+ */
+ if (device_changed && gtk_widget_has_focus (canvas))
+ {
+ gimp_display_shell_update_focus (shell, TRUE,
+ &image_coords, state);
+ }
+
+ switch (event->type)
+ {
+ case GDK_ENTER_NOTIFY:
+ {
+ GdkEventCrossing *cevent = (GdkEventCrossing *) event;
+
+ if (shell->inferior_ignore_mode &&
+ cevent->subwindow == NULL &&
+ cevent->mode == GDK_CROSSING_NORMAL)
+ {
+ shell->inferior_ignore_mode = FALSE;
+ gtk_widget_set_extension_events (shell->canvas,
+ GDK_EXTENSION_EVENTS_ALL);
+ }
+
+ if (cevent->mode != GDK_CROSSING_NORMAL)
+ return TRUE;
+
+ /* ignore enter notify while we have a grab */
+ if (shell->pointer_grabbed)
+ return TRUE;
+
+ gimp_display_shell_proximity_in (shell);
+ update_sw_cursor = TRUE;
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+ }
+ break;
+
+ case GDK_LEAVE_NOTIFY:
+ {
+ GdkEventCrossing *cevent = (GdkEventCrossing *) event;
+
+ if (! shell->inferior_ignore_mode &&
+ cevent->subwindow == NULL &&
+ cevent->mode == GDK_CROSSING_NORMAL &&
+ cevent->detail == GDK_NOTIFY_INFERIOR)
+ {
+ shell->inferior_ignore_mode = TRUE;
+ gtk_widget_set_extension_events (shell->canvas,
+ GDK_EXTENSION_EVENTS_NONE);
+ }
+
+ if (cevent->mode != GDK_CROSSING_NORMAL)
+ return TRUE;
+
+ /* ignore leave notify while we have a grab */
+ if (shell->pointer_grabbed)
+ return TRUE;
+
+ gimp_display_shell_proximity_out (shell);
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+ }
+ break;
+
+ case GDK_PROXIMITY_IN:
+ gimp_display_shell_proximity_in (shell);
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+ break;
+
+ case GDK_PROXIMITY_OUT:
+ gimp_display_shell_proximity_out (shell);
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+ break;
+
+ case GDK_FOCUS_CHANGE:
+ {
+ GdkEventFocus *fevent = (GdkEventFocus *) event;
+
+ if (fevent->in)
+ {
+ if (G_UNLIKELY (! gtk_widget_has_focus (canvas)))
+ g_warning ("%s: FOCUS_IN but canvas has no focus", G_STRFUNC);
+
+ /* ignore focus changes while we have a grab */
+ if (shell->pointer_grabbed)
+ return TRUE;
+
+ /* press modifier keys when the canvas gets the focus */
+ gimp_display_shell_update_focus (shell, TRUE,
+ &image_coords, state);
+ }
+ else
+ {
+ if (G_UNLIKELY (gtk_widget_has_focus (canvas)))
+ g_warning ("%s: FOCUS_OUT but canvas has focus", G_STRFUNC);
+
+ /* ignore focus changes while we have a grab */
+ if (shell->pointer_grabbed)
+ return TRUE;
+
+ /* release modifier keys when the canvas loses the focus */
+ gimp_display_shell_update_focus (shell, FALSE,
+ &image_coords, 0);
+ }
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+ {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ GdkModifierType button_state;
+
+ /* ignore new mouse events */
+ if (gimp->busy || shell->scrolling ||
+ shell->pointer_grabbed ||
+ shell->button1_release_pending)
+ return TRUE;
+
+ button_state = gimp_display_shell_button_to_state (bevent->button);
+
+ state |= button_state;
+
+ /* ignore new buttons while another button is down */
+ if (((state & (GDK_BUTTON1_MASK)) && (state & (GDK_BUTTON2_MASK |
+ GDK_BUTTON3_MASK))) ||
+ ((state & (GDK_BUTTON2_MASK)) && (state & (GDK_BUTTON1_MASK |
+ GDK_BUTTON3_MASK))) ||
+ ((state & (GDK_BUTTON3_MASK)) && (state & (GDK_BUTTON1_MASK |
+ GDK_BUTTON2_MASK))))
+ return TRUE;
+
+ /* focus the widget if it isn't; if the toplevel window
+ * already has focus, this will generate a FOCUS_IN on the
+ * canvas immediately, therefore we do this before logging
+ * the BUTTON_PRESS.
+ */
+ if (! gtk_widget_has_focus (canvas))
+ gtk_widget_grab_focus (canvas);
+
+ /* if the toplevel window didn't have focus, the above
+ * gtk_widget_grab_focus() didn't set the canvas' HAS_FOCUS
+ * flags, and didn't trigger a FOCUS_IN, but the tool needs
+ * to be set up correctly regardless, so simply do the
+ * same things here, it's safe to do them redundantly.
+ */
+ gimp_display_shell_update_focus (shell, TRUE,
+ &image_coords, state);
+ gimp_display_shell_update_cursor (shell, &display_coords,
+ &image_coords, state & ~button_state,
+ FALSE);
+
+ if (gdk_event_triggers_context_menu (event))
+ {
+ GimpUIManager *ui_manager;
+ const gchar *ui_path;
+
+ ui_manager = tool_manager_get_popup_active (gimp,
+ &image_coords, state,
+ display,
+ &ui_path);
+
+ if (ui_manager)
+ {
+ gimp_ui_manager_ui_popup (ui_manager,
+ ui_path,
+ GTK_WIDGET (shell),
+ NULL, NULL, NULL, NULL);
+ }
+ else
+ {
+ gimp_ui_manager_ui_popup (shell->popup_manager,
+ "/dummy-menubar/image-popup",
+ GTK_WIDGET (shell),
+ NULL, NULL, NULL, NULL);
+ }
+ }
+ else if (bevent->button == 1)
+ {
+ if (! gimp_display_shell_pointer_grab (shell, NULL, 0))
+ return TRUE;
+
+ if (! shell->space_release_pending)
+ if (! gimp_display_shell_keyboard_grab (shell, event))
+ {
+ gimp_display_shell_pointer_ungrab (shell, NULL);
+ return TRUE;
+ }
+
+ if (gimp_display_shell_initialize_tool (shell,
+ &image_coords, state))
+ {
+ GimpCoords last_motion;
+
+ /* Use the last evaluated velocity&direction instead of the
+ * button_press event's ones because the click is
+ * usually at the same spot as the last motion event
+ * which would give us bogus derivate dynamics.
+ */
+ gimp_motion_buffer_begin_stroke (shell->motion_buffer, time,
+ &last_motion);
+
+ image_coords.velocity = last_motion.velocity;
+ image_coords.direction = last_motion.direction;
+
+ tool_manager_button_press_active (gimp,
+ &image_coords,
+ time, state,
+ GIMP_BUTTON_PRESS_NORMAL,
+ display);
+ }
+ }
+ else if (bevent->button == 2)
+ {
+ gimp_display_shell_start_scrolling (shell, NULL, state,
+ bevent->x, bevent->y);
+ }
+
+ return_val = TRUE;
+ }
+ break;
+
+ case GDK_2BUTTON_PRESS:
+ {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ GimpTool *active_tool;
+
+ if (gimp->busy)
+ return TRUE;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (bevent->button == 1 &&
+ active_tool &&
+ gimp_tool_control_is_active (active_tool->control) &&
+ gimp_tool_control_get_wants_double_click (active_tool->control))
+ {
+ tool_manager_button_press_active (gimp,
+ &image_coords,
+ time, state,
+ GIMP_BUTTON_PRESS_DOUBLE,
+ display);
+ }
+
+ /* don't update the cursor again on double click */
+ return TRUE;
+ }
+ break;
+
+ case GDK_3BUTTON_PRESS:
+ {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ GimpTool *active_tool;
+
+ if (gimp->busy)
+ return TRUE;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (bevent->button == 1 &&
+ active_tool &&
+ gimp_tool_control_is_active (active_tool->control) &&
+ gimp_tool_control_get_wants_triple_click (active_tool->control))
+ {
+ tool_manager_button_press_active (gimp,
+ &image_coords,
+ time, state,
+ GIMP_BUTTON_PRESS_TRIPLE,
+ display);
+ }
+
+ /* don't update the cursor again on triple click */
+ return TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ GimpTool *active_tool;
+
+ gimp_display_shell_autoscroll_stop (shell);
+
+ if (bevent->button == 1 && shell->button1_release_pending)
+ {
+ gimp_display_shell_released (shell, event, NULL);
+ return TRUE;
+ }
+
+ if (gimp->busy)
+ return TRUE;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ state &= ~gimp_display_shell_button_to_state (bevent->button);
+
+ if (bevent->button == 1)
+ {
+ if (! shell->pointer_grabbed || shell->scrolling)
+ return TRUE;
+
+ if (! shell->space_release_pending)
+ gimp_display_shell_keyboard_ungrab (shell, event);
+
+ if (active_tool &&
+ (! gimp_image_is_empty (image) ||
+ gimp_tool_control_get_handle_empty_image (active_tool->control)))
+ {
+ gimp_motion_buffer_end_stroke (shell->motion_buffer);
+
+ if (gimp_tool_control_is_active (active_tool->control))
+ {
+ tool_manager_button_release_active (gimp,
+ &image_coords,
+ time, state,
+ display);
+ }
+ }
+
+ /* update the tool's modifier state because it didn't get
+ * key events while BUTTON1 was down
+ */
+ if (gtk_widget_has_focus (canvas))
+ gimp_display_shell_update_focus (shell, TRUE,
+ &image_coords, state);
+ else
+ gimp_display_shell_update_focus (shell, FALSE,
+ &image_coords, 0);
+
+ gimp_display_shell_pointer_ungrab (shell, NULL);
+ }
+ else if (bevent->button == 2)
+ {
+ if (shell->scrolling)
+ gimp_display_shell_stop_scrolling (shell, NULL);
+ }
+ else if (bevent->button == 3)
+ {
+ /* nop */
+ }
+ else
+ {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ GimpController *mouse = gimp_controllers_get_mouse (gimp);
+
+ if (!(shell->scrolling || shell->pointer_grabbed) &&
+ mouse && gimp_controller_mouse_button (GIMP_CONTROLLER_MOUSE (mouse),
+ bevent))
+ {
+ return TRUE;
+ }
+ }
+
+ return_val = TRUE;
+ }
+ break;
+
+ case GDK_SCROLL:
+ {
+ GdkEventScroll *sevent = (GdkEventScroll *) event;
+ GimpController *wheel = gimp_controllers_get_wheel (gimp);
+
+ if (! wheel ||
+ ! gimp_controller_wheel_scroll (GIMP_CONTROLLER_WHEEL (wheel),
+ sevent))
+ {
+ GdkScrollDirection direction = sevent->direction;
+
+ if (state & gimp_get_toggle_behavior_mask ())
+ {
+ switch (direction)
+ {
+ case GDK_SCROLL_UP:
+ gimp_display_shell_scale (shell,
+ GIMP_ZOOM_IN,
+ 0.0,
+ GIMP_ZOOM_FOCUS_POINTER);
+ break;
+
+ case GDK_SCROLL_DOWN:
+ gimp_display_shell_scale (shell,
+ GIMP_ZOOM_OUT,
+ 0.0,
+ GIMP_ZOOM_FOCUS_POINTER);
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ GtkAdjustment *adj = NULL;
+ gdouble value;
+
+ if (state & GDK_SHIFT_MASK)
+ switch (direction)
+ {
+ case GDK_SCROLL_UP: direction = GDK_SCROLL_LEFT; break;
+ case GDK_SCROLL_DOWN: direction = GDK_SCROLL_RIGHT; break;
+ case GDK_SCROLL_LEFT: direction = GDK_SCROLL_UP; break;
+ case GDK_SCROLL_RIGHT: direction = GDK_SCROLL_DOWN; break;
+ }
+
+ switch (direction)
+ {
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ adj = shell->hsbdata;
+ break;
+
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_DOWN:
+ adj = shell->vsbdata;
+ break;
+ }
+
+ value = (gtk_adjustment_get_value (adj) +
+ ((direction == GDK_SCROLL_UP ||
+ direction == GDK_SCROLL_LEFT) ?
+ -gtk_adjustment_get_page_increment (adj) / 2 :
+ gtk_adjustment_get_page_increment (adj) / 2));
+ value = CLAMP (value,
+ gtk_adjustment_get_lower (adj),
+ gtk_adjustment_get_upper (adj) -
+ gtk_adjustment_get_page_size (adj));
+
+ gtk_adjustment_set_value (adj, value);
+ }
+ }
+
+ gimp_display_shell_untransform_event_coords (shell,
+ &display_coords,
+ &image_coords,
+ &update_sw_cursor);
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+
+ return_val = TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ {
+ GdkEventMotion *mevent = (GdkEventMotion *) event;
+ GdkEvent *compressed_motion = NULL;
+ GimpMotionMode motion_mode = GIMP_MOTION_MODE_EXACT;
+ GimpTool *active_tool;
+
+ if (gimp->busy)
+ return TRUE;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool)
+ motion_mode = gimp_tool_control_get_motion_mode (active_tool->control);
+
+ if (shell->scrolling ||
+ motion_mode == GIMP_MOTION_MODE_COMPRESS)
+ {
+ compressed_motion = gimp_display_shell_compress_motion (event,
+ next_event);
+
+ if (compressed_motion && ! shell->scrolling)
+ {
+ gimp_display_shell_get_event_coords (shell,
+ compressed_motion,
+ &display_coords,
+ &state, &time);
+ gimp_display_shell_untransform_event_coords (shell,
+ &display_coords,
+ &image_coords,
+ NULL);
+ }
+ }
+
+ /* call proximity_in() here because the pointer might already
+ * be in proximity when the canvas starts to receive events,
+ * like when a new image has been created into an empty
+ * display
+ */
+ gimp_display_shell_proximity_in (shell);
+ update_sw_cursor = TRUE;
+
+ if (shell->scrolling)
+ {
+ GdkEventMotion *me = (compressed_motion ?
+ (GdkEventMotion *) compressed_motion :
+ mevent);
+
+ gimp_display_shell_handle_scrolling (shell, state, me->x, me->y);
+ }
+ else if (state & GDK_BUTTON1_MASK)
+ {
+ if (active_tool &&
+ gimp_tool_control_is_active (active_tool->control) &&
+ (! gimp_image_is_empty (image) ||
+ gimp_tool_control_get_handle_empty_image (active_tool->control)))
+ {
+ GdkTimeCoord **history_events;
+ gint n_history_events;
+ guint32 last_motion_time;
+
+ /* if the first mouse button is down, check for automatic
+ * scrolling...
+ */
+ if ((mevent->x < 0 ||
+ mevent->y < 0 ||
+ mevent->x > shell->disp_width ||
+ mevent->y > shell->disp_height) &&
+ ! gimp_tool_control_get_scroll_lock (active_tool->control))
+ {
+ gimp_display_shell_autoscroll_start (shell, state, mevent);
+ }
+
+ /* gdk_device_get_history() has several quirks. First
+ * is that events with borderline timestamps at both
+ * ends are included. Because of that we need to add 1
+ * to lower border. The second is due to poor X event
+ * resolution. We need to do -1 to ensure that the
+ * amount of events between timestamps is final or
+ * risk losing some.
+ */
+ last_motion_time =
+ gimp_motion_buffer_get_last_motion_time (shell->motion_buffer);
+
+ if (motion_mode == GIMP_MOTION_MODE_EXACT &&
+ shell->display->config->use_event_history &&
+ gdk_device_get_history (mevent->device, mevent->window,
+ last_motion_time + 1,
+ mevent->time - 1,
+ &history_events,
+ &n_history_events))
+ {
+ GimpDeviceInfo *device;
+ gint i;
+
+ device = gimp_device_info_get_by_device (mevent->device);
+
+ for (i = 0; i < n_history_events; i++)
+ {
+ gimp_device_info_get_time_coords (device,
+ history_events[i],
+ &display_coords);
+
+ gimp_display_shell_untransform_event_coords (shell,
+ &display_coords,
+ &image_coords,
+ NULL);
+
+ /* Early removal of useless events saves CPU time.
+ */
+ if (gimp_motion_buffer_motion_event (shell->motion_buffer,
+ &image_coords,
+ history_events[i]->time,
+ TRUE))
+ {
+ gimp_motion_buffer_request_stroke (shell->motion_buffer,
+ state,
+ history_events[i]->time);
+ }
+ }
+
+ gdk_device_free_history (history_events, n_history_events);
+ }
+ else
+ {
+ gboolean event_fill = (motion_mode == GIMP_MOTION_MODE_EXACT);
+
+ /* Early removal of useless events saves CPU time.
+ */
+ if (gimp_motion_buffer_motion_event (shell->motion_buffer,
+ &image_coords,
+ time,
+ event_fill))
+ {
+ gimp_motion_buffer_request_stroke (shell->motion_buffer,
+ state,
+ time);
+ }
+ }
+ }
+ }
+
+ if (! (state &
+ (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)))
+ {
+ /* Early removal of useless events saves CPU time.
+ * Pass event_fill = FALSE since we are only hovering.
+ */
+ if (gimp_motion_buffer_motion_event (shell->motion_buffer,
+ &image_coords,
+ time,
+ FALSE))
+ {
+ gimp_motion_buffer_request_hover (shell->motion_buffer,
+ state,
+ shell->proximity);
+ }
+ }
+
+ if (compressed_motion)
+ gdk_event_free (compressed_motion);
+
+ return_val = TRUE;
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ {
+ GdkEventKey *kevent = (GdkEventKey *) event;
+ GimpTool *active_tool;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (state & GDK_BUTTON1_MASK)
+ {
+ if (kevent->keyval == GDK_KEY_Alt_L ||
+ kevent->keyval == GDK_KEY_Alt_R ||
+ kevent->keyval == GDK_KEY_Shift_L ||
+ kevent->keyval == GDK_KEY_Shift_R ||
+ kevent->keyval == GDK_KEY_Control_L ||
+ kevent->keyval == GDK_KEY_Control_R ||
+ kevent->keyval == GDK_KEY_Meta_L ||
+ kevent->keyval == GDK_KEY_Meta_R)
+ {
+ GdkModifierType key;
+
+ key = gimp_display_shell_key_to_state (kevent->keyval);
+ state |= key;
+
+ if (active_tool &&
+ gimp_tool_control_is_active (active_tool->control) &&
+ ! gimp_image_is_empty (image))
+ {
+ tool_manager_active_modifier_state_active (gimp, state,
+ display);
+ }
+ }
+ }
+ else
+ {
+ gboolean arrow_key = FALSE;
+
+ tool_manager_focus_display_active (gimp, display);
+
+ if (gimp_tool_control_get_wants_all_key_events (active_tool->control))
+ {
+ if (tool_manager_key_press_active (gimp, kevent, display))
+ {
+ /* FIXME: need to do some of the stuff below, like
+ * calling oper_update()
+ */
+
+ return TRUE;
+ }
+ }
+
+ if (! gtk_widget_has_focus (shell->canvas))
+ {
+ /* The event was in an overlay widget and not handled
+ * there, make sure the overlay widgets are keyboard
+ * navigatable by letting the generic widget handlers
+ * deal with the event.
+ */
+ return FALSE;
+ }
+
+ if (gimp_display_shell_key_to_state (kevent->keyval) == GDK_MOD1_MASK)
+ /* Make sure the picked layer is reset. */
+ shell->picked_layer = NULL;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ case GDK_KEY_Right:
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ arrow_key = TRUE;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Escape:
+ if (! gimp_image_is_empty (image))
+ return_val = tool_manager_key_press_active (gimp,
+ kevent,
+ display);
+
+ if (! return_val)
+ {
+ GimpController *keyboard = gimp_controllers_get_keyboard (gimp);
+
+ if (keyboard)
+ return_val =
+ gimp_controller_keyboard_key_press (GIMP_CONTROLLER_KEYBOARD (keyboard),
+ kevent);
+ }
+
+ /* always swallow arrow keys, we don't want focus keynav */
+ if (! return_val)
+ return_val = arrow_key;
+ break;
+
+ case GDK_KEY_space:
+ case GDK_KEY_KP_Space:
+ if (shell->button1_release_pending)
+ shell->space_release_pending = TRUE;
+ else
+ gimp_display_shell_space_pressed (shell, event);
+ return_val = TRUE;
+ break;
+
+ case GDK_KEY_Tab:
+ case GDK_KEY_KP_Tab:
+ case GDK_KEY_ISO_Left_Tab:
+ gimp_display_shell_tab_pressed (shell, kevent);
+ return_val = TRUE;
+ break;
+
+ /* Update the state based on modifiers being pressed */
+ case GDK_KEY_Alt_L: case GDK_KEY_Alt_R:
+ case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
+ case GDK_KEY_Control_L: case GDK_KEY_Control_R:
+ case GDK_KEY_Meta_L: case GDK_KEY_Meta_R:
+ {
+ GdkModifierType key;
+
+ key = gimp_display_shell_key_to_state (kevent->keyval);
+ state |= key;
+
+ if (! gimp_image_is_empty (image))
+ tool_manager_modifier_state_active (gimp, state, display);
+ }
+ break;
+ }
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+ }
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ {
+ GdkEventKey *kevent = (GdkEventKey *) event;
+ GimpTool *active_tool;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (gimp_display_shell_key_to_state (kevent->keyval) == GDK_MOD1_MASK &&
+ shell->picked_layer)
+ {
+ GimpStatusbar *statusbar;
+
+ statusbar = gimp_display_shell_get_statusbar (shell);
+ gimp_statusbar_pop_temp (statusbar);
+
+ shell->picked_layer = NULL;
+ }
+
+ if ((state & GDK_BUTTON1_MASK) &&
+ (! shell->space_release_pending ||
+ (kevent->keyval != GDK_KEY_space &&
+ kevent->keyval != GDK_KEY_KP_Space)))
+ {
+ if (kevent->keyval == GDK_KEY_Alt_L ||
+ kevent->keyval == GDK_KEY_Alt_R ||
+ kevent->keyval == GDK_KEY_Shift_L ||
+ kevent->keyval == GDK_KEY_Shift_R ||
+ kevent->keyval == GDK_KEY_Control_L ||
+ kevent->keyval == GDK_KEY_Control_R ||
+ kevent->keyval == GDK_KEY_Meta_L ||
+ kevent->keyval == GDK_KEY_Meta_R)
+ {
+ GdkModifierType key;
+
+ key = gimp_display_shell_key_to_state (kevent->keyval);
+ state &= ~key;
+
+ if (active_tool &&
+ gimp_tool_control_is_active (active_tool->control) &&
+ ! gimp_image_is_empty (image))
+ {
+ tool_manager_active_modifier_state_active (gimp, state,
+ display);
+ }
+ }
+ }
+ else
+ {
+ tool_manager_focus_display_active (gimp, display);
+
+ if (gimp_tool_control_get_wants_all_key_events (active_tool->control))
+ {
+ if (tool_manager_key_release_active (gimp, kevent, display))
+ {
+ /* FIXME: need to do some of the stuff below, like
+ * calling oper_update()
+ */
+
+ return TRUE;
+ }
+ }
+
+ if (! gtk_widget_has_focus (shell->canvas))
+ {
+ /* The event was in an overlay widget and not handled
+ * there, make sure the overlay widgets are keyboard
+ * navigatable by letting the generic widget handlers
+ * deal with the event.
+ */
+ return FALSE;
+ }
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_space:
+ case GDK_KEY_KP_Space:
+ if ((state & GDK_BUTTON1_MASK))
+ {
+ shell->button1_release_pending = TRUE;
+ shell->space_release_pending = FALSE;
+ /* We need to ungrab the pointer in order to catch
+ * button release events.
+ */
+ if (shell->pointer_grabbed)
+ gimp_display_shell_pointer_ungrab (shell, event);
+ }
+ else
+ {
+ gimp_display_shell_released (shell, event, NULL);
+ }
+ return_val = TRUE;
+ break;
+
+ /* Update the state based on modifiers being pressed */
+ case GDK_KEY_Alt_L: case GDK_KEY_Alt_R:
+ case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
+ case GDK_KEY_Control_L: case GDK_KEY_Control_R:
+ case GDK_KEY_Meta_L: case GDK_KEY_Meta_R:
+ {
+ GdkModifierType key;
+
+ key = gimp_display_shell_key_to_state (kevent->keyval);
+ state &= ~key;
+
+ /* For all modifier keys: call the tools
+ * modifier_state *and* oper_update method so tools
+ * can choose if they are interested in the press
+ * itself or only in the resulting state
+ */
+ if (! gimp_image_is_empty (image))
+ tool_manager_modifier_state_active (gimp, state, display);
+ }
+ break;
+ }
+
+ tool_manager_oper_update_active (gimp,
+ &image_coords, state,
+ shell->proximity,
+ display);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* if we reached this point in gimp_busy mode, return now */
+ if (gimp->busy)
+ return return_val;
+
+ /* cursor update */
+ gimp_display_shell_update_cursor (shell, &display_coords, &image_coords,
+ state, update_sw_cursor);
+
+ return return_val;
+}
+
+static GdkModifierType
+gimp_display_shell_key_to_state (gint key)
+{
+ /* FIXME: need some proper GDK API to figure this */
+
+ switch (key)
+ {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ return GDK_MOD1_MASK;
+
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ return GDK_SHIFT_MASK;
+
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ return GDK_CONTROL_MASK;
+
+#ifdef GDK_WINDOWING_QUARTZ
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Meta_R:
+ return GDK_MOD2_MASK;
+#endif
+
+ default:
+ return 0;
+ }
+}
+
+static GdkModifierType
+gimp_display_shell_button_to_state (gint button)
+{
+ if (button == 1)
+ return GDK_BUTTON1_MASK;
+ else if (button == 2)
+ return GDK_BUTTON2_MASK;
+ else if (button == 3)
+ return GDK_BUTTON3_MASK;
+
+ return 0;
+}
+
+static void
+gimp_display_shell_proximity_in (GimpDisplayShell *shell)
+{
+ if (! shell->proximity)
+ {
+ shell->proximity = TRUE;
+
+ gimp_display_shell_check_device_cursor (shell);
+ }
+}
+
+static void
+gimp_display_shell_proximity_out (GimpDisplayShell *shell)
+{
+ if (shell->proximity)
+ {
+ shell->proximity = FALSE;
+
+ gimp_display_shell_clear_software_cursor (shell);
+ }
+}
+
+static void
+gimp_display_shell_check_device_cursor (GimpDisplayShell *shell)
+{
+ GimpDeviceManager *manager;
+ GimpDeviceInfo *current_device;
+
+ manager = gimp_devices_get_manager (shell->display->gimp);
+
+ current_device = gimp_device_manager_get_current_device (manager);
+
+ shell->draw_cursor = ! gimp_device_info_has_cursor (current_device);
+}
+
+static void
+gimp_display_shell_start_scrolling (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ GdkModifierType state,
+ gint x,
+ gint y)
+{
+ g_return_if_fail (! shell->scrolling);
+
+ gimp_display_shell_pointer_grab (shell, event, GDK_POINTER_MOTION_MASK);
+
+ shell->scrolling = TRUE;
+ shell->scroll_start_x = x;
+ shell->scroll_start_y = y;
+ shell->scroll_last_x = x;
+ shell->scroll_last_y = y;
+ shell->rotating = (state & gimp_get_extend_selection_mask ()) ? TRUE : FALSE;
+ shell->rotate_drag_angle = shell->rotate_angle;
+ shell->scaling = (state & gimp_get_toggle_behavior_mask ()) ? TRUE : FALSE;
+ shell->layer_picking = (state & GDK_MOD1_MASK) ? TRUE : FALSE;
+
+ if (shell->rotating)
+ {
+ gimp_display_shell_set_override_cursor (shell,
+ (GimpCursorType) GDK_EXCHANGE);
+ }
+ else if (shell->scaling)
+ {
+ gimp_display_shell_set_override_cursor (shell,
+ (GimpCursorType) GIMP_CURSOR_ZOOM);
+ }
+ else if (shell->layer_picking)
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpLayer *layer;
+ GimpCoords image_coords;
+ GimpCoords display_coords;
+ guint32 time;
+
+ gimp_display_shell_set_override_cursor (shell,
+ (GimpCursorType) GIMP_CURSOR_CROSSHAIR);
+
+ gimp_display_shell_get_event_coords (shell, event,
+ &display_coords,
+ &state, &time);
+ gimp_display_shell_untransform_event_coords (shell,
+ &display_coords, &image_coords,
+ NULL);
+ layer = gimp_image_pick_layer (image,
+ (gint) image_coords.x,
+ (gint) image_coords.y,
+ shell->picked_layer);
+
+ if (layer && ! gimp_image_get_floating_selection (image))
+ {
+ if (layer != gimp_image_get_active_layer (image))
+ {
+ GimpStatusbar *statusbar;
+
+ gimp_image_set_active_layer (image, layer);
+
+ statusbar = gimp_display_shell_get_statusbar (shell);
+ gimp_statusbar_push_temp (statusbar, GIMP_MESSAGE_INFO,
+ GIMP_ICON_LAYER,
+ _("Layer picked: '%s'"),
+ gimp_object_get_name (layer));
+ }
+ shell->picked_layer = layer;
+ }
+ }
+ else
+ gimp_display_shell_set_override_cursor (shell,
+ (GimpCursorType) GDK_FLEUR);
+}
+
+static void
+gimp_display_shell_stop_scrolling (GimpDisplayShell *shell,
+ const GdkEvent *event)
+{
+ g_return_if_fail (shell->scrolling);
+
+ gimp_display_shell_unset_override_cursor (shell);
+
+ shell->scrolling = FALSE;
+ shell->scroll_start_x = 0;
+ shell->scroll_start_y = 0;
+ shell->scroll_last_x = 0;
+ shell->scroll_last_y = 0;
+ shell->rotating = FALSE;
+ shell->rotate_drag_angle = 0.0;
+ shell->scaling = FALSE;
+ shell->layer_picking = FALSE;
+
+ /* We may have ungrabbed the pointer when space was released while
+ * mouse was down, to be able to catch a GDK_BUTTON_RELEASE event.
+ */
+ if (shell->pointer_grabbed)
+ gimp_display_shell_pointer_ungrab (shell, event);
+}
+
+static void
+gimp_display_shell_handle_scrolling (GimpDisplayShell *shell,
+ GdkModifierType state,
+ gint x,
+ gint y)
+{
+ g_return_if_fail (shell->scrolling);
+
+ if (shell->rotating)
+ {
+ gboolean constrain = (state & GDK_CONTROL_MASK) ? TRUE : FALSE;
+
+ gimp_display_shell_rotate_drag (shell,
+ shell->scroll_last_x,
+ shell->scroll_last_y,
+ x,
+ y,
+ constrain);
+ }
+ else if (shell->scaling)
+ {
+ gimp_display_shell_scale_drag (shell,
+ shell->scroll_start_x,
+ shell->scroll_start_y,
+ shell->scroll_last_x - x,
+ shell->scroll_last_y - y);
+ }
+ else if (shell->layer_picking)
+ {
+ /* Do nothing. We only pick the layer on click. */
+ }
+ else
+ {
+ gimp_display_shell_scroll (shell,
+ shell->scroll_last_x - x,
+ shell->scroll_last_y - y);
+ }
+
+ shell->scroll_last_x = x;
+ shell->scroll_last_y = y;
+}
+
+static void
+gimp_display_shell_space_pressed (GimpDisplayShell *shell,
+ const GdkEvent *event)
+{
+ Gimp *gimp = gimp_display_get_gimp (shell->display);
+
+ if (shell->space_release_pending || shell->scrolling)
+ return;
+
+ if (! gimp_display_shell_keyboard_grab (shell, event))
+ return;
+
+ switch (shell->display->config->space_bar_action)
+ {
+ case GIMP_SPACE_BAR_ACTION_NONE:
+ break;
+
+ case GIMP_SPACE_BAR_ACTION_PAN:
+ {
+ GimpDeviceManager *manager;
+ GimpDeviceInfo *current_device;
+ GimpCoords coords;
+ GdkModifierType state = 0;
+
+ manager = gimp_devices_get_manager (gimp);
+ current_device = gimp_device_manager_get_current_device (manager);
+
+ gimp_device_info_get_device_coords (current_device,
+ gtk_widget_get_window (shell->canvas),
+ &coords);
+ gdk_event_get_state (event, &state);
+
+ gimp_display_shell_start_scrolling (shell, event, state,
+ coords.x, coords.y);
+ }
+ break;
+
+ case GIMP_SPACE_BAR_ACTION_MOVE:
+ {
+ GimpTool *active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool || ! GIMP_IS_MOVE_TOOL (active_tool))
+ {
+ GdkModifierType state;
+
+ shell->space_shaded_tool =
+ gimp_object_get_name (active_tool->tool_info);
+
+ gimp_context_set_tool (gimp_get_user_context (gimp),
+ gimp_get_tool_info (gimp, "gimp-move-tool"));
+
+ gdk_event_get_state (event, &state);
+
+ gimp_display_shell_update_focus (shell, TRUE,
+ NULL, state);
+ }
+ }
+ break;
+ }
+
+ shell->space_release_pending = TRUE;
+}
+
+static void
+gimp_display_shell_released (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ const GimpCoords *image_coords)
+{
+ Gimp *gimp = gimp_display_get_gimp (shell->display);
+
+ if (! shell->space_release_pending &&
+ ! shell->button1_release_pending)
+ return;
+
+ switch (shell->display->config->space_bar_action)
+ {
+ case GIMP_SPACE_BAR_ACTION_NONE:
+ break;
+
+ case GIMP_SPACE_BAR_ACTION_PAN:
+ gimp_display_shell_stop_scrolling (shell, event);
+ break;
+
+ case GIMP_SPACE_BAR_ACTION_MOVE:
+ if (shell->space_shaded_tool)
+ {
+ gimp_context_set_tool (gimp_get_user_context (gimp),
+ gimp_get_tool_info (gimp,
+ shell->space_shaded_tool));
+ shell->space_shaded_tool = NULL;
+
+ if (gtk_widget_has_focus (shell->canvas))
+ {
+ GdkModifierType state;
+
+ gdk_event_get_state (event, &state);
+
+ gimp_display_shell_update_focus (shell, TRUE,
+ image_coords, state);
+ }
+ else
+ {
+ gimp_display_shell_update_focus (shell, FALSE,
+ image_coords, 0);
+ }
+ }
+ break;
+ }
+
+ gimp_display_shell_keyboard_ungrab (shell, event);
+
+ shell->space_release_pending = FALSE;
+ shell->button1_release_pending = FALSE;
+}
+
+static gboolean
+gimp_display_shell_tab_pressed (GimpDisplayShell *shell,
+ const GdkEventKey *kevent)
+{
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ if (kevent->state & GDK_CONTROL_MASK)
+ {
+ if (image && ! gimp_image_is_empty (image))
+ {
+ if (kevent->keyval == GDK_KEY_Tab ||
+ kevent->keyval == GDK_KEY_KP_Tab)
+ gimp_display_shell_layer_select_init (shell,
+ 1, kevent->time);
+ else
+ gimp_display_shell_layer_select_init (shell,
+ -1, kevent->time);
+
+ return TRUE;
+ }
+ }
+ else if (kevent->state & GDK_MOD1_MASK)
+ {
+ if (image)
+ {
+ if (kevent->keyval == GDK_KEY_Tab ||
+ kevent->keyval == GDK_KEY_KP_Tab)
+ gimp_ui_manager_activate_action (manager, "windows",
+ "windows-show-display-next");
+ else
+ gimp_ui_manager_activate_action (manager, "windows",
+ "windows-show-display-previous");
+
+ return TRUE;
+ }
+ }
+ else
+ {
+ gimp_ui_manager_activate_action (manager, "windows",
+ "windows-hide-docks");
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_display_shell_update_focus (GimpDisplayShell *shell,
+ gboolean focus_in,
+ const GimpCoords *image_coords,
+ GdkModifierType state)
+{
+ Gimp *gimp = gimp_display_get_gimp (shell->display);
+
+ if (focus_in)
+ {
+ tool_manager_focus_display_active (gimp, shell->display);
+ tool_manager_modifier_state_active (gimp, state, shell->display);
+ }
+ else
+ {
+ tool_manager_focus_display_active (gimp, NULL);
+ }
+
+ if (image_coords)
+ tool_manager_oper_update_active (gimp,
+ image_coords, state,
+ shell->proximity,
+ shell->display);
+}
+
+static void
+gimp_display_shell_update_cursor (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ const GimpCoords *image_coords,
+ GdkModifierType state,
+ gboolean update_software_cursor)
+{
+ GimpDisplay *display = shell->display;
+ Gimp *gimp = gimp_display_get_gimp (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpTool *active_tool;
+
+ if (! shell->display->config->cursor_updating)
+ return;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool)
+ {
+ if ((! gimp_image_is_empty (image) ||
+ gimp_tool_control_get_handle_empty_image (active_tool->control)) &&
+ ! (state & (GDK_BUTTON1_MASK |
+ GDK_BUTTON2_MASK |
+ GDK_BUTTON3_MASK)))
+ {
+ tool_manager_cursor_update_active (gimp,
+ image_coords, state,
+ display);
+ }
+ else if (gimp_image_is_empty (image) &&
+ ! gimp_tool_control_get_handle_empty_image (active_tool->control))
+ {
+ gimp_display_shell_set_cursor (shell,
+ GIMP_CURSOR_MOUSE,
+ gimp_tool_control_get_tool_cursor (active_tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ }
+ }
+ else
+ {
+ gimp_display_shell_set_cursor (shell,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_NONE,
+ GIMP_CURSOR_MODIFIER_BAD);
+ }
+
+ if (update_software_cursor)
+ {
+ GimpCursorPrecision precision = GIMP_CURSOR_PRECISION_PIXEL_CENTER;
+
+ if (active_tool)
+ precision = gimp_tool_control_get_precision (active_tool->control);
+
+ gimp_display_shell_update_software_cursor (shell,
+ precision,
+ (gint) display_coords->x,
+ (gint) display_coords->y,
+ image_coords->x,
+ image_coords->y);
+ }
+}
+
+static gboolean
+gimp_display_shell_initialize_tool (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GdkModifierType state)
+{
+ GimpDisplay *display = shell->display;
+ GimpImage *image = gimp_display_get_image (display);
+ Gimp *gimp = gimp_display_get_gimp (display);
+ gboolean initialized = FALSE;
+ GimpTool *active_tool;
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool &&
+ (! gimp_image_is_empty (image) ||
+ gimp_tool_control_get_handle_empty_image (active_tool->control)))
+ {
+ /* initialize the current tool if it has no drawable */
+ if (! active_tool->drawable)
+ {
+ initialized = tool_manager_initialize_active (gimp, display);
+ }
+ else if ((active_tool->drawable !=
+ gimp_image_get_active_drawable (image)) &&
+ (! gimp_tool_control_get_preserve (active_tool->control) &&
+ (gimp_tool_control_get_dirty_mask (active_tool->control) &
+ GIMP_DIRTY_ACTIVE_DRAWABLE)))
+ {
+ GimpProcedure *procedure = g_object_get_data (G_OBJECT (active_tool),
+ "gimp-gegl-procedure");
+
+ if (image == gimp_item_get_image (GIMP_ITEM (active_tool->drawable)))
+ {
+ /* When changing between drawables if the *same* image,
+ * stop the tool using its dirty action, so it doesn't
+ * get committed on tool change, in case its dirty action
+ * is HALT. This is a pure "probably better this way"
+ * decision because the user is likely changing their
+ * mind or was simply on the wrong layer. See bug #776370.
+ *
+ * See also issues #1180 and #1202 for cases where we
+ * actually *don't* want to halt the tool here, but rather
+ * commit it, hence the use of the tool's dirty action.
+ */
+ tool_manager_control_active (
+ gimp,
+ gimp_tool_control_get_dirty_action (active_tool->control),
+ active_tool->display);
+ }
+
+ if (procedure)
+ {
+ /* We can't just recreate an operation tool, we must
+ * make sure the right stuff gets set on it, so
+ * re-activate the procedure that created it instead of
+ * just calling gimp_context_tool_changed(). See
+ * GimpGeglProcedure and bug #776370.
+ */
+ GimpImageWindow *window;
+ GimpUIManager *manager;
+
+ window = gimp_display_shell_get_window (shell);
+ manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_filter_history_add (gimp, procedure);
+ gimp_ui_manager_activate_action (manager, "filters",
+ "filters-reshow");
+
+ /* the procedure already initialized the tool; don't
+ * reinitialize it below, since this can lead to errors.
+ */
+ initialized = TRUE;
+ }
+ else
+ {
+ /* create a new one, deleting the current */
+ gimp_context_tool_changed (gimp_get_user_context (gimp));
+ }
+
+ /* make sure the newly created tool has the right state */
+ gimp_display_shell_update_focus (shell, TRUE, image_coords, state);
+
+ if (! initialized)
+ initialized = tool_manager_initialize_active (gimp, display);
+ }
+ else
+ {
+ initialized = TRUE;
+ }
+ }
+
+ return initialized;
+}
+
+static void
+gimp_display_shell_get_event_coords (GimpDisplayShell *shell,
+ const GdkEvent *event,
+ GimpCoords *display_coords,
+ GdkModifierType *state,
+ guint32 *time)
+{
+ Gimp *gimp = gimp_display_get_gimp (shell->display);
+ GimpDeviceManager *manager;
+ GimpDeviceInfo *current_device;
+
+ manager = gimp_devices_get_manager (gimp);
+ current_device = gimp_device_manager_get_current_device (manager);
+
+ gimp_device_info_get_event_coords (current_device,
+ gtk_widget_get_window (shell->canvas),
+ event,
+ display_coords);
+
+ gimp_device_info_get_event_state (current_device,
+ gtk_widget_get_window (shell->canvas),
+ event,
+ state);
+
+ *time = gdk_event_get_time (event);
+}
+
+static void
+gimp_display_shell_untransform_event_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords,
+ gboolean *update_software_cursor)
+{
+ Gimp *gimp = gimp_display_get_gimp (shell->display);
+ GimpTool *active_tool;
+
+ /* GimpCoords passed to tools are ALWAYS in image coordinates */
+ gimp_display_shell_untransform_coords (shell,
+ display_coords,
+ image_coords);
+
+ active_tool = tool_manager_get_active (gimp);
+
+ if (active_tool && gimp_tool_control_get_snap_to (active_tool->control))
+ {
+ gint x, y, width, height;
+
+ gimp_tool_control_get_snap_offsets (active_tool->control,
+ &x, &y, &width, &height);
+
+ if (gimp_display_shell_snap_coords (shell,
+ image_coords,
+ x, y, width, height))
+ {
+ if (update_software_cursor)
+ *update_software_cursor = TRUE;
+ }
+ }
+}
+
+/* gimp_display_shell_compress_motion:
+ *
+ * This function walks the GDK event queue, seeking motion events at the
+ * front of the queue corresponding to the same widget as, and having
+ * similar characteristics to, `initial_event`. If it finds any it will
+ * remove them from the queue, and return the most recent motion event.
+ * Otherwise it will return NULL.
+ *
+ * If `*next_event` is non-NULL upon return, the caller must dispatch and
+ * free this event after handling the motion event.
+ *
+ * The gimp_display_shell_compress_motion function source may be re-used under
+ * the XFree86-style license. <adam@gimp.org>
+ */
+static GdkEvent *
+gimp_display_shell_compress_motion (GdkEvent *initial_event,
+ GdkEvent **next_event)
+{
+ GdkEvent *last_motion = NULL;
+ GtkWidget *widget;
+
+ *next_event = NULL;
+
+ if (initial_event->any.type != GDK_MOTION_NOTIFY)
+ return NULL;
+
+ widget = gtk_get_event_widget (initial_event);
+
+ while (gdk_events_pending ())
+ {
+ GdkEvent *event = gdk_event_get ();
+
+ if (!event)
+ {
+ /* Do nothing */
+ }
+ else if ((gtk_get_event_widget (event) == widget) &&
+ (event->any.type == GDK_MOTION_NOTIFY) &&
+ (event->any.window == initial_event->any.window) &&
+ (event->motion.state == initial_event->motion.state) &&
+ (event->motion.device == initial_event->motion.device))
+ {
+ /* Discard previous motion event */
+ if (last_motion)
+ gdk_event_free (last_motion);
+
+ last_motion = event;
+ }
+ else
+ {
+ /* Let the caller dispatch the event */
+ *next_event = event;
+
+ break;
+ }
+ }
+
+ return last_motion;
+}
diff --git a/app/display/gimpdisplayshell-tool-events.h b/app/display/gimpdisplayshell-tool-events.h
new file mode 100644
index 0000000..4458a92
--- /dev/null
+++ b/app/display/gimpdisplayshell-tool-events.h
@@ -0,0 +1,52 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_TOOL_EVENTS_H__
+#define __GIMP_DISPLAY_SHELL_TOOL_EVENTS_H__
+
+
+gboolean gimp_display_shell_events (GtkWidget *widget,
+ GdkEvent *event,
+ GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_canvas_tool_events (GtkWidget *widget,
+ GdkEvent *event,
+ GimpDisplayShell *shell);
+void gimp_display_shell_canvas_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed,
+ GimpDisplayShell *shell);
+
+void gimp_display_shell_buffer_stroke (GimpMotionBuffer *buffer,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplayShell *shell);
+void gimp_display_shell_buffer_hover (GimpMotionBuffer *buffer,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_hruler_button_press (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell);
+gboolean gimp_display_shell_vruler_button_press (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_TOOL_EVENT_H__ */
diff --git a/app/display/gimpdisplayshell-transform.c b/app/display/gimpdisplayshell-transform.c
new file mode 100644
index 0000000..f9cd2e5
--- /dev/null
+++ b/app/display/gimpdisplayshell-transform.c
@@ -0,0 +1,1025 @@
+/* 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 <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpboundary.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimp-utils.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-transform.h"
+
+
+/* local function prototypes */
+
+static void gimp_display_shell_transform_xy_f_noround (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+
+/* public functions */
+
+/**
+ * gimp_display_shell_zoom_coords:
+ * @shell: a #GimpDisplayShell
+ * @image_coords: image coordinates
+ * @display_coords: returns the corresponding display coordinates
+ *
+ * Zooms from image coordinates to display coordinates, so that
+ * objects can be rendered at the correct points on the display.
+ **/
+void
+gimp_display_shell_zoom_coords (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GimpCoords *display_coords)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (image_coords != NULL);
+ g_return_if_fail (display_coords != NULL);
+
+ *display_coords = *image_coords;
+
+ display_coords->x = SCALEX (shell, image_coords->x);
+ display_coords->y = SCALEY (shell, image_coords->y);
+
+ display_coords->x -= shell->offset_x;
+ display_coords->y -= shell->offset_y;
+}
+
+/**
+ * gimp_display_shell_unzoom_coords:
+ * @shell: a #GimpDisplayShell
+ * @display_coords: display coordinates
+ * @image_coords: returns the corresponding image coordinates
+ *
+ * Zooms from display coordinates to image coordinates, so that
+ * points on the display can be mapped to points in the image.
+ **/
+void
+gimp_display_shell_unzoom_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (display_coords != NULL);
+ g_return_if_fail (image_coords != NULL);
+
+ *image_coords = *display_coords;
+
+ image_coords->x += shell->offset_x;
+ image_coords->y += shell->offset_y;
+
+ image_coords->x /= shell->scale_x;
+ image_coords->y /= shell->scale_y;
+}
+
+/**
+ * gimp_display_shell_zoom_xy:
+ * @shell:
+ * @x:
+ * @y:
+ * @nx:
+ * @ny:
+ *
+ * Zooms an image coordinate to a shell coordinate.
+ **/
+void
+gimp_display_shell_zoom_xy (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gint *nx,
+ gint *ny)
+{
+ gint64 tx;
+ gint64 ty;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ tx = x * shell->scale_x;
+ ty = y * shell->scale_y;
+
+ tx -= shell->offset_x;
+ ty -= shell->offset_y;
+
+ /* The projected coordinates might overflow a gint in the case of
+ * big images at high zoom levels, so we clamp them here to avoid
+ * problems.
+ */
+ *nx = CLAMP (tx, G_MININT, G_MAXINT);
+ *ny = CLAMP (ty, G_MININT, G_MAXINT);
+}
+
+/**
+ * gimp_display_shell_unzoom_xy:
+ * @shell: a #GimpDisplayShell
+ * @x: x coordinate in display coordinates
+ * @y: y coordinate in display coordinates
+ * @nx: returns x oordinate in image coordinates
+ * @ny: returns y coordinate in image coordinates
+ * @round: if %TRUE, round the results to the nearest integer;
+ * if %FALSE, simply cast them to @gint.
+ *
+ * Zoom from display coordinates to image coordinates, so that
+ * points on the display can be mapped to the corresponding points
+ * in the image.
+ **/
+void
+gimp_display_shell_unzoom_xy (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint *nx,
+ gint *ny,
+ gboolean round)
+{
+ gint64 tx;
+ gint64 ty;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ if (round)
+ {
+ tx = SIGNED_ROUND (((gdouble) x + shell->offset_x) / shell->scale_x);
+ ty = SIGNED_ROUND (((gdouble) y + shell->offset_y) / shell->scale_y);
+ }
+ else
+ {
+ tx = ((gint64) x + shell->offset_x) / shell->scale_x;
+ ty = ((gint64) y + shell->offset_y) / shell->scale_y;
+ }
+
+ *nx = CLAMP (tx, G_MININT, G_MAXINT);
+ *ny = CLAMP (ty, G_MININT, G_MAXINT);
+}
+
+/**
+ * gimp_display_shell_zoom_xy_f:
+ * @shell: a #GimpDisplayShell
+ * @x: image x coordinate of point
+ * @y: image y coordinate of point
+ * @nx: returned shell canvas x coordinate
+ * @ny: returned shell canvas y coordinate
+ *
+ * Zooms from image coordinates to display shell canvas
+ * coordinates.
+ **/
+void
+gimp_display_shell_zoom_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ *nx = SCALEX (shell, x) - shell->offset_x;
+ *ny = SCALEY (shell, y) - shell->offset_y;
+}
+
+/**
+ * gimp_display_shell_unzoom_xy_f:
+ * @shell: a #GimpDisplayShell
+ * @x: x coordinate in display coordinates
+ * @y: y coordinate in display coordinates
+ * @nx: place to return x coordinate in image coordinates
+ * @ny: place to return y coordinate in image coordinates
+ *
+ * This function is identical to gimp_display_shell_unzoom_xy(),
+ * except that the input and output coordinates are doubles rather than
+ * ints, and consequently there is no option related to rounding.
+ **/
+void
+gimp_display_shell_unzoom_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ *nx = (x + shell->offset_x) / shell->scale_x;
+ *ny = (y + shell->offset_y) / shell->scale_y;
+}
+
+/**
+ * gimp_display_shell_zoom_segments:
+ * @shell: a #GimpDisplayShell
+ * @src_segs: array of segments in image coordinates
+ * @dest_segs: returns the corresponding segments in display coordinates
+ * @n_segs: number of segments
+ *
+ * Zooms from image coordinates to display coordinates, so that
+ * objects can be rendered at the correct points on the display.
+ **/
+void
+gimp_display_shell_zoom_segments (GimpDisplayShell *shell,
+ const GimpBoundSeg *src_segs,
+ GimpSegment *dest_segs,
+ gint n_segs,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ gint i;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ for (i = 0; i < n_segs ; i++)
+ {
+ gdouble x1, x2;
+ gdouble y1, y2;
+
+ x1 = src_segs[i].x1 + offset_x;
+ x2 = src_segs[i].x2 + offset_x;
+ y1 = src_segs[i].y1 + offset_y;
+ y2 = src_segs[i].y2 + offset_y;
+
+ dest_segs[i].x1 = SCALEX (shell, x1) - shell->offset_x;
+ dest_segs[i].x2 = SCALEX (shell, x2) - shell->offset_x;
+ dest_segs[i].y1 = SCALEY (shell, y1) - shell->offset_y;
+ dest_segs[i].y2 = SCALEY (shell, y2) - shell->offset_y;
+ }
+}
+
+/**
+ * gimp_display_shell_rotate_coords:
+ * @shell: a #GimpDisplayShell
+ * @image_coords: unrotated display coordinates
+ * @display_coords: returns the corresponding rotated display coordinates
+ *
+ * Rotates from unrotated display coordinates to rotated display
+ * coordinates, so that objects can be rendered at the correct points
+ * on the display.
+ **/
+void
+gimp_display_shell_rotate_coords (GimpDisplayShell *shell,
+ const GimpCoords *unrotated_coords,
+ GimpCoords *rotated_coords)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (unrotated_coords != NULL);
+ g_return_if_fail (rotated_coords != NULL);
+
+ *rotated_coords = *unrotated_coords;
+
+ if (shell->rotate_transform)
+ cairo_matrix_transform_point (shell->rotate_transform,
+ &rotated_coords->x,
+ &rotated_coords->y);
+}
+
+/**
+ * gimp_display_shell_unrotate_coords:
+ * @shell: a #GimpDisplayShell
+ * @display_coords: rotated display coordinates
+ * @image_coords: returns the corresponding unrotated display coordinates
+ *
+ * Rotates from rotated display coordinates to unrotated display coordinates.
+ **/
+void
+gimp_display_shell_unrotate_coords (GimpDisplayShell *shell,
+ const GimpCoords *rotated_coords,
+ GimpCoords *unrotated_coords)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (rotated_coords != NULL);
+ g_return_if_fail (unrotated_coords != NULL);
+
+ *unrotated_coords = *rotated_coords;
+
+ if (shell->rotate_untransform)
+ cairo_matrix_transform_point (shell->rotate_untransform,
+ &unrotated_coords->x,
+ &unrotated_coords->y);
+}
+
+/**
+ * gimp_display_shell_rotate_xy:
+ * @shell:
+ * @x:
+ * @y:
+ * @nx:
+ * @ny:
+ *
+ * Rotates an unrotated display coordinate to a rotated shell coordinate.
+ **/
+void
+gimp_display_shell_rotate_xy (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gint *nx,
+ gint *ny)
+{
+ gint64 tx;
+ gint64 ty;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ if (shell->rotate_transform)
+ cairo_matrix_transform_point (shell->rotate_transform, &x, &y);
+
+ tx = x;
+ ty = y;
+
+ /* The projected coordinates might overflow a gint in the case of
+ * big images at high zoom levels, so we clamp them here to avoid
+ * problems.
+ */
+ *nx = CLAMP (tx, G_MININT, G_MAXINT);
+ *ny = CLAMP (ty, G_MININT, G_MAXINT);
+}
+
+/**
+ * gimp_display_shell_unrotate_xy:
+ * @shell: a #GimpDisplayShell
+ * @x: x coordinate in rotated display coordinates
+ * @y: y coordinate in rotated display coordinates
+ * @nx: returns x oordinate in unrotated display coordinates
+ * @ny: returns y coordinate in unrotated display coordinates
+ *
+ * Rotate from rotated display coordinates to unrotated display
+ * coordinates.
+ **/
+void
+gimp_display_shell_unrotate_xy (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint *nx,
+ gint *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ if (shell->rotate_untransform)
+ {
+ gdouble fx = x;
+ gdouble fy = y;
+
+ cairo_matrix_transform_point (shell->rotate_untransform, &fx, &fy);
+
+ *nx = CLAMP (fx, G_MININT, G_MAXINT);
+ *ny = CLAMP (fy, G_MININT, G_MAXINT);
+ }
+ else
+ {
+ *nx = x;
+ *ny = y;
+ }
+}
+
+/**
+ * gimp_display_shell_rotate_xy_f:
+ * @shell: a #GimpDisplayShell
+ * @x: image x coordinate of point
+ * @y: image y coordinate of point
+ * @nx: returned shell canvas x coordinate
+ * @ny: returned shell canvas y coordinate
+ *
+ * Rotates from untransformed display coordinates to rotated display
+ * coordinates.
+ **/
+void
+gimp_display_shell_rotate_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ *nx = x;
+ *ny = y;
+
+ if (shell->rotate_transform)
+ cairo_matrix_transform_point (shell->rotate_transform, nx, ny);
+}
+
+/**
+ * gimp_display_shell_unrotate_xy_f:
+ * @shell: a #GimpDisplayShell
+ * @x: x coordinate in rotated display coordinates
+ * @y: y coordinate in rotated display coordinates
+ * @nx: place to return x coordinate in unrotated display coordinates
+ * @ny: place to return y coordinate in unrotated display coordinates
+ *
+ * This function is identical to gimp_display_shell_unrotate_xy(),
+ * except that the input and output coordinates are doubles rather
+ * than ints.
+ **/
+void
+gimp_display_shell_unrotate_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ *nx = x;
+ *ny = y;
+
+ if (shell->rotate_untransform)
+ cairo_matrix_transform_point (shell->rotate_untransform, nx, ny);
+}
+
+void
+gimp_display_shell_rotate_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2)
+{
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->rotate_transform)
+ {
+ gdouble tx1 = x1;
+ gdouble ty1 = y1;
+ gdouble tx2 = x1;
+ gdouble ty2 = y2;
+ gdouble tx3 = x2;
+ gdouble ty3 = y1;
+ gdouble tx4 = x2;
+ gdouble ty4 = y2;
+
+ cairo_matrix_transform_point (shell->rotate_transform, &tx1, &ty1);
+ cairo_matrix_transform_point (shell->rotate_transform, &tx2, &ty2);
+ cairo_matrix_transform_point (shell->rotate_transform, &tx3, &ty3);
+ cairo_matrix_transform_point (shell->rotate_transform, &tx4, &ty4);
+
+ *nx1 = MIN4 (tx1, tx2, tx3, tx4);
+ *ny1 = MIN4 (ty1, ty2, ty3, ty4);
+ *nx2 = MAX4 (tx1, tx2, tx3, tx4);
+ *ny2 = MAX4 (ty1, ty2, ty3, ty4);
+ }
+ else
+ {
+ *nx1 = x1;
+ *ny1 = y1;
+ *nx2 = x2;
+ *ny2 = y2;
+ }
+}
+
+void
+gimp_display_shell_unrotate_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->rotate_untransform)
+ {
+ gdouble tx1 = x1;
+ gdouble ty1 = y1;
+ gdouble tx2 = x1;
+ gdouble ty2 = y2;
+ gdouble tx3 = x2;
+ gdouble ty3 = y1;
+ gdouble tx4 = x2;
+ gdouble ty4 = y2;
+
+ cairo_matrix_transform_point (shell->rotate_untransform, &tx1, &ty1);
+ cairo_matrix_transform_point (shell->rotate_untransform, &tx2, &ty2);
+ cairo_matrix_transform_point (shell->rotate_untransform, &tx3, &ty3);
+ cairo_matrix_transform_point (shell->rotate_untransform, &tx4, &ty4);
+
+ *nx1 = MIN4 (tx1, tx2, tx3, tx4);
+ *ny1 = MIN4 (ty1, ty2, ty3, ty4);
+ *nx2 = MAX4 (tx1, tx2, tx3, tx4);
+ *ny2 = MAX4 (ty1, ty2, ty3, ty4);
+ }
+ else
+ {
+ *nx1 = x1;
+ *ny1 = y1;
+ *nx2 = x2;
+ *ny2 = y2;
+ }
+}
+
+/**
+ * gimp_display_shell_transform_coords:
+ * @shell: a #GimpDisplayShell
+ * @image_coords: image coordinates
+ * @display_coords: returns the corresponding display coordinates
+ *
+ * Transforms from image coordinates to display coordinates, so that
+ * objects can be rendered at the correct points on the display.
+ **/
+void
+gimp_display_shell_transform_coords (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GimpCoords *display_coords)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (image_coords != NULL);
+ g_return_if_fail (display_coords != NULL);
+
+ *display_coords = *image_coords;
+
+ display_coords->x = SCALEX (shell, image_coords->x);
+ display_coords->y = SCALEY (shell, image_coords->y);
+
+ display_coords->x -= shell->offset_x;
+ display_coords->y -= shell->offset_y;
+
+ if (shell->rotate_transform)
+ cairo_matrix_transform_point (shell->rotate_transform,
+ &display_coords->x,
+ &display_coords->y);
+}
+
+/**
+ * gimp_display_shell_untransform_coords:
+ * @shell: a #GimpDisplayShell
+ * @display_coords: display coordinates
+ * @image_coords: returns the corresponding image coordinates
+ *
+ * Transforms from display coordinates to image coordinates, so that
+ * points on the display can be mapped to points in the image.
+ **/
+void
+gimp_display_shell_untransform_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (display_coords != NULL);
+ g_return_if_fail (image_coords != NULL);
+
+ *image_coords = *display_coords;
+
+ if (shell->rotate_untransform)
+ cairo_matrix_transform_point (shell->rotate_untransform,
+ &image_coords->x,
+ &image_coords->y);
+
+ image_coords->x += shell->offset_x;
+ image_coords->y += shell->offset_y;
+
+ image_coords->x /= shell->scale_x;
+ image_coords->y /= shell->scale_y;
+
+ image_coords->xscale = shell->scale_x;
+ image_coords->yscale = shell->scale_y;
+ image_coords->angle = shell->rotate_angle / 360.0;
+ image_coords->reflect = shell->flip_horizontally ^ shell->flip_vertically;
+
+ if (shell->flip_vertically)
+ image_coords->angle += 0.5;
+}
+
+/**
+ * gimp_display_shell_transform_xy:
+ * @shell:
+ * @x:
+ * @y:
+ * @nx:
+ * @ny:
+ *
+ * Transforms an image coordinate to a shell coordinate.
+ **/
+void
+gimp_display_shell_transform_xy (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gint *nx,
+ gint *ny)
+{
+ gint64 tx;
+ gint64 ty;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ tx = x * shell->scale_x;
+ ty = y * shell->scale_y;
+
+ tx -= shell->offset_x;
+ ty -= shell->offset_y;
+
+ if (shell->rotate_transform)
+ {
+ gdouble fx = tx;
+ gdouble fy = ty;
+
+ cairo_matrix_transform_point (shell->rotate_transform, &fx, &fy);
+
+ tx = fx;
+ ty = fy;
+ }
+
+ /* The projected coordinates might overflow a gint in the case of
+ * big images at high zoom levels, so we clamp them here to avoid
+ * problems.
+ */
+ *nx = CLAMP (tx, G_MININT, G_MAXINT);
+ *ny = CLAMP (ty, G_MININT, G_MAXINT);
+}
+
+/**
+ * gimp_display_shell_untransform_xy:
+ * @shell: a #GimpDisplayShell
+ * @x: x coordinate in display coordinates
+ * @y: y coordinate in display coordinates
+ * @nx: returns x oordinate in image coordinates
+ * @ny: returns y coordinate in image coordinates
+ * @round: if %TRUE, round the results to the nearest integer;
+ * if %FALSE, simply cast them to @gint.
+ *
+ * Transform from display coordinates to image coordinates, so that
+ * points on the display can be mapped to the corresponding points
+ * in the image.
+ **/
+void
+gimp_display_shell_untransform_xy (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint *nx,
+ gint *ny,
+ gboolean round)
+{
+ gint64 tx;
+ gint64 ty;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ if (shell->rotate_untransform)
+ {
+ gdouble fx = x;
+ gdouble fy = y;
+
+ cairo_matrix_transform_point (shell->rotate_untransform, &fx, &fy);
+
+ x = fx;
+ y = fy;
+ }
+
+ if (round)
+ {
+ tx = SIGNED_ROUND (((gdouble) x + shell->offset_x) / shell->scale_x);
+ ty = SIGNED_ROUND (((gdouble) y + shell->offset_y) / shell->scale_y);
+ }
+ else
+ {
+ tx = ((gint64) x + shell->offset_x) / shell->scale_x;
+ ty = ((gint64) y + shell->offset_y) / shell->scale_y;
+ }
+
+ *nx = CLAMP (tx, G_MININT, G_MAXINT);
+ *ny = CLAMP (ty, G_MININT, G_MAXINT);
+}
+
+/**
+ * gimp_display_shell_transform_xy_f:
+ * @shell: a #GimpDisplayShell
+ * @x: image x coordinate of point
+ * @y: image y coordinate of point
+ * @nx: returned shell canvas x coordinate
+ * @ny: returned shell canvas y coordinate
+ *
+ * Transforms from image coordinates to display shell canvas
+ * coordinates.
+ **/
+void
+gimp_display_shell_transform_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ *nx = SCALEX (shell, x) - shell->offset_x;
+ *ny = SCALEY (shell, y) - shell->offset_y;
+
+ if (shell->rotate_transform)
+ cairo_matrix_transform_point (shell->rotate_transform, nx, ny);
+}
+
+/**
+ * gimp_display_shell_untransform_xy_f:
+ * @shell: a #GimpDisplayShell
+ * @x: x coordinate in display coordinates
+ * @y: y coordinate in display coordinates
+ * @nx: place to return x coordinate in image coordinates
+ * @ny: place to return y coordinate in image coordinates
+ *
+ * This function is identical to gimp_display_shell_untransform_xy(),
+ * except that the input and output coordinates are doubles rather than
+ * ints, and consequently there is no option related to rounding.
+ **/
+void
+gimp_display_shell_untransform_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx != NULL);
+ g_return_if_fail (ny != NULL);
+
+ if (shell->rotate_untransform)
+ cairo_matrix_transform_point (shell->rotate_untransform, &x, &y);
+
+ *nx = (x + shell->offset_x) / shell->scale_x;
+ *ny = (y + shell->offset_y) / shell->scale_y;
+}
+
+void
+gimp_display_shell_transform_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx1 != NULL);
+ g_return_if_fail (ny1 != NULL);
+ g_return_if_fail (nx2 != NULL);
+ g_return_if_fail (ny2 != NULL);
+
+ if (shell->rotate_transform)
+ {
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+ gdouble tx3, ty3;
+ gdouble tx4, ty4;
+
+ gimp_display_shell_transform_xy_f_noround (shell, x1, y1, &tx1, &ty1);
+ gimp_display_shell_transform_xy_f_noround (shell, x1, y2, &tx2, &ty2);
+ gimp_display_shell_transform_xy_f_noround (shell, x2, y1, &tx3, &ty3);
+ gimp_display_shell_transform_xy_f_noround (shell, x2, y2, &tx4, &ty4);
+
+ *nx1 = MIN4 (tx1, tx2, tx3, tx4);
+ *ny1 = MIN4 (ty1, ty2, ty3, ty4);
+ *nx2 = MAX4 (tx1, tx2, tx3, tx4);
+ *ny2 = MAX4 (ty1, ty2, ty3, ty4);
+ }
+ else
+ {
+ gimp_display_shell_transform_xy_f_noround (shell, x1, y1, nx1, ny1);
+ gimp_display_shell_transform_xy_f_noround (shell, x2, y2, nx2, ny2);
+ }
+}
+
+void
+gimp_display_shell_untransform_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (nx1 != NULL);
+ g_return_if_fail (ny1 != NULL);
+ g_return_if_fail (nx2 != NULL);
+ g_return_if_fail (ny2 != NULL);
+
+ if (shell->rotate_untransform)
+ {
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+ gdouble tx3, ty3;
+ gdouble tx4, ty4;
+
+ gimp_display_shell_untransform_xy_f (shell, x1, y1, &tx1, &ty1);
+ gimp_display_shell_untransform_xy_f (shell, x1, y2, &tx2, &ty2);
+ gimp_display_shell_untransform_xy_f (shell, x2, y1, &tx3, &ty3);
+ gimp_display_shell_untransform_xy_f (shell, x2, y2, &tx4, &ty4);
+
+ *nx1 = MIN4 (tx1, tx2, tx3, tx4);
+ *ny1 = MIN4 (ty1, ty2, ty3, ty4);
+ *nx2 = MAX4 (tx1, tx2, tx3, tx4);
+ *ny2 = MAX4 (ty1, ty2, ty3, ty4);
+ }
+ else
+ {
+ gimp_display_shell_untransform_xy_f (shell, x1, y1, nx1, ny1);
+ gimp_display_shell_untransform_xy_f (shell, x2, y2, nx2, ny2);
+ }
+}
+
+/* transforms a bounding box from image-space, uniformly scaled by a factor of
+ * 'scale', to display-space. this is equivalent to, but more accurate than,
+ * dividing the input by 'scale', and using
+ * gimp_display_shell_transform_bounds(), in particular, in that if 'scale'
+ * equals 'shell->scale_x' or 'shell->scale_y', there is no loss in accuracy
+ * in the corresponding dimension due to scaling (although there might be loss
+ * of accuracy due to rotation or translation.)
+ */
+void
+gimp_display_shell_transform_bounds_with_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2)
+{
+ gdouble factor_x;
+ gdouble factor_y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (scale > 0.0);
+ g_return_if_fail (nx1 != NULL);
+ g_return_if_fail (ny1 != NULL);
+ g_return_if_fail (nx2 != NULL);
+ g_return_if_fail (ny2 != NULL);
+
+ factor_x = shell->scale_x / scale;
+ factor_y = shell->scale_y / scale;
+
+ x1 = x1 * factor_x - shell->offset_x;
+ y1 = y1 * factor_y - shell->offset_y;
+ x2 = x2 * factor_x - shell->offset_x;
+ y2 = y2 * factor_y - shell->offset_y;
+
+ gimp_display_shell_rotate_bounds (shell,
+ x1, y1, x2, y2,
+ nx1, ny1, nx2, ny2);
+}
+
+/* transforms a bounding box from display-space to image-space, uniformly
+ * scaled by a factor of 'scale'. this is equivalent to, but more accurate
+ * than, using gimp_display_shell_untransform_bounds(), and multiplying the
+ * output by 'scale', in particular, in that if 'scale' equals 'shell->scale_x'
+ * or 'shell->scale_y', there is no loss in accuracy in the corresponding
+ * dimension due to scaling (although there might be loss of accuracy due to
+ * rotation or translation.)
+ */
+void
+gimp_display_shell_untransform_bounds_with_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2)
+{
+ gdouble factor_x;
+ gdouble factor_y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (scale > 0.0);
+ g_return_if_fail (nx1 != NULL);
+ g_return_if_fail (ny1 != NULL);
+ g_return_if_fail (nx2 != NULL);
+ g_return_if_fail (ny2 != NULL);
+
+ factor_x = scale / shell->scale_x;
+ factor_y = scale / shell->scale_y;
+
+ gimp_display_shell_unrotate_bounds (shell,
+ x1, y1, x2, y2,
+ nx1, ny1, nx2, ny2);
+
+ *nx1 = (*nx1 + shell->offset_x) * factor_x;
+ *ny1 = (*ny1 + shell->offset_y) * factor_y;
+ *nx2 = (*nx2 + shell->offset_x) * factor_x;
+ *ny2 = (*ny2 + shell->offset_y) * factor_y;
+}
+
+/**
+ * gimp_display_shell_untransform_viewport:
+ * @shell: a #GimpDisplayShell
+ * @clip: whether to clip the result to the image bounds
+ * @x: returns image x coordinate of display upper left corner
+ * @y: returns image y coordinate of display upper left corner
+ * @width: returns width of display measured in image coordinates
+ * @height: returns height of display measured in image coordinates
+ *
+ * This function calculates the part of the image, in image coordinates,
+ * that corresponds to the display viewport.
+ **/
+void
+gimp_display_shell_untransform_viewport (GimpDisplayShell *shell,
+ gboolean clip,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ gdouble x1, y1, x2, y2;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_untransform_bounds (shell,
+ 0, 0,
+ shell->disp_width, shell->disp_height,
+ &x1, &y1,
+ &x2, &y2);
+
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+
+ if (clip)
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ x1 = MAX (x1, 0);
+ y1 = MAX (y1, 0);
+ x2 = MIN (x2, gimp_image_get_width (image));
+ y2 = MIN (y2, gimp_image_get_height (image));
+ }
+
+ if (x) *x = x1;
+ if (y) *y = y1;
+ if (width) *width = x2 - x1;
+ if (height) *height = y2 - y1;
+}
+
+
+/* private functions */
+
+/* Same as gimp_display_shell_transform_xy_f(), but doesn't do any rounding
+ * for the transformed coordinates.
+ */
+static void
+gimp_display_shell_transform_xy_f_noround (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny)
+{
+ *nx = shell->scale_x * x - shell->offset_x;
+ *ny = shell->scale_y * y - shell->offset_y;
+
+ if (shell->rotate_transform)
+ cairo_matrix_transform_point (shell->rotate_transform, nx, ny);
+}
diff --git a/app/display/gimpdisplayshell-transform.h b/app/display/gimpdisplayshell-transform.h
new file mode 100644
index 0000000..1877b13
--- /dev/null
+++ b/app/display/gimpdisplayshell-transform.h
@@ -0,0 +1,200 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_TRANSFORM_H__
+#define __GIMP_DISPLAY_SHELL_TRANSFORM_H__
+
+
+/* zoom: functions to transform from image space to unrotated display
+ * space and back, taking into account scroll offset and scale
+ */
+
+void gimp_display_shell_zoom_coords (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GimpCoords *display_coords);
+void gimp_display_shell_unzoom_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords);
+
+void gimp_display_shell_zoom_xy (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gint *nx,
+ gint *ny);
+void gimp_display_shell_unzoom_xy (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint *nx,
+ gint *ny,
+ gboolean round);
+
+void gimp_display_shell_zoom_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+void gimp_display_shell_unzoom_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+
+void gimp_display_shell_zoom_segments (GimpDisplayShell *shell,
+ const GimpBoundSeg *src_segs,
+ GimpSegment *dest_segs,
+ gint n_segs,
+ gdouble offset_x,
+ gdouble offset_y);
+
+
+/* rotate: functions to transform from unrotated and unflipped but
+ * zoomed display space to rotated and filpped display space and back
+ */
+
+void gimp_display_shell_rotate_coords (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GimpCoords *display_coords);
+void gimp_display_shell_unrotate_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords);
+
+void gimp_display_shell_rotate_xy (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gint *nx,
+ gint *ny);
+void gimp_display_shell_unrotate_xy (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint *nx,
+ gint *ny);
+
+void gimp_display_shell_rotate_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+void gimp_display_shell_unrotate_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+
+void gimp_display_shell_rotate_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2);
+void gimp_display_shell_unrotate_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2);
+
+
+/* transform: functions to transform from image space to rotated
+ * display space and back, taking into account scroll offset, scale,
+ * rotation and flipping
+ */
+
+void gimp_display_shell_transform_coords (GimpDisplayShell *shell,
+ const GimpCoords *image_coords,
+ GimpCoords *display_coords);
+void gimp_display_shell_untransform_coords (GimpDisplayShell *shell,
+ const GimpCoords *display_coords,
+ GimpCoords *image_coords);
+
+void gimp_display_shell_transform_xy (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gint *nx,
+ gint *ny);
+void gimp_display_shell_untransform_xy (GimpDisplayShell *shell,
+ gint x,
+ gint y,
+ gint *nx,
+ gint *ny,
+ gboolean round);
+
+void gimp_display_shell_transform_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+void gimp_display_shell_untransform_xy_f (GimpDisplayShell *shell,
+ gdouble x,
+ gdouble y,
+ gdouble *nx,
+ gdouble *ny);
+
+void gimp_display_shell_transform_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2);
+void gimp_display_shell_untransform_bounds (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2);
+
+void gimp_display_shell_transform_bounds_with_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2);
+void gimp_display_shell_untransform_bounds_with_scale (GimpDisplayShell *shell,
+ gdouble scale,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *nx1,
+ gdouble *ny1,
+ gdouble *nx2,
+ gdouble *ny2);
+
+void gimp_display_shell_untransform_viewport (GimpDisplayShell *shell,
+ gboolean clip,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_TRANSFORM_H__ */
diff --git a/app/display/gimpdisplayshell-utils.c b/app/display/gimpdisplayshell-utils.c
new file mode 100644
index 0000000..8e85e71
--- /dev/null
+++ b/app/display/gimpdisplayshell-utils.c
@@ -0,0 +1,220 @@
+/* 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpimage.h"
+#include "core/gimpunit.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-utils.h"
+
+#include "gimp-intl.h"
+
+void
+gimp_display_shell_get_constrained_line_params (GimpDisplayShell *shell,
+ gdouble *offset_angle,
+ gdouble *xres,
+ gdouble *yres)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (offset_angle != NULL);
+ g_return_if_fail (xres != NULL);
+ g_return_if_fail (yres != NULL);
+
+ if (shell->flip_horizontally ^ shell->flip_vertically)
+ *offset_angle = +shell->rotate_angle;
+ else
+ *offset_angle = -shell->rotate_angle;
+
+ *xres = 1.0;
+ *yres = 1.0;
+
+ if (! shell->dot_for_dot)
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ if (image)
+ gimp_image_get_resolution (image, xres, yres);
+ }
+}
+
+void
+gimp_display_shell_constrain_line (GimpDisplayShell *shell,
+ gdouble start_x,
+ gdouble start_y,
+ gdouble *end_x,
+ gdouble *end_y,
+ gint n_snap_lines)
+{
+ gdouble offset_angle;
+ gdouble xres, yres;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (end_x != NULL);
+ g_return_if_fail (end_y != NULL);
+
+ gimp_display_shell_get_constrained_line_params (shell,
+ &offset_angle,
+ &xres, &yres);
+
+ gimp_constrain_line (start_x, start_y,
+ end_x, end_y,
+ n_snap_lines,
+ offset_angle,
+ xres, yres);
+}
+
+gdouble
+gimp_display_shell_constrain_angle (GimpDisplayShell *shell,
+ gdouble angle,
+ gint n_snap_lines)
+{
+ gdouble x, y;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), 0.0);
+
+ x = cos (angle);
+ y = sin (angle);
+
+ gimp_display_shell_constrain_line (shell,
+ 0.0, 0.0,
+ &x, &y,
+ n_snap_lines);
+
+ return atan2 (y, x);
+}
+
+/**
+ * gimp_display_shell_get_line_status:
+ * @status: initial status text.
+ * @separator: separator text between the line information and @status.
+ * @shell: #GimpDisplayShell this status text will be displayed for.
+ * @x1: abscissa of first point.
+ * @y1: ordinate of first point.
+ * @x2: abscissa of second point.
+ * @y2: ordinate of second point.
+ *
+ * Utility function to prepend the status message with a distance and
+ * angle value. Obviously this is only to be used for tools when it
+ * makes sense, and in particular when there is a concept of line. For
+ * instance when shift-clicking a painting tool or in the blend tool,
+ * etc.
+ * This utility prevents code duplication but also ensures a common
+ * display for every tool where such a status is needed. It will take
+ * into account the shell unit settings and will use the ideal digit
+ * precision according to current image resolution.
+ *
+ * Return value: a newly allocated string containing the enhanced status.
+ **/
+gchar *
+gimp_display_shell_get_line_status (GimpDisplayShell *shell,
+ const gchar *status,
+ const gchar *separator,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpImage *image;
+ gchar *enhanced_status;
+ gdouble xres;
+ gdouble yres;
+ gdouble dx, dy, pixel_dist;
+ gdouble angle;
+
+ image = gimp_display_get_image (shell->display);
+ if (! image)
+ {
+ /* This makes no sense to add line information when no image is
+ * attached to the display. */
+ return g_strdup (status);
+ }
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ xres = yres = 1.0;
+ else
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ dx = x2 - x1;
+ dy = y2 - y1;
+ pixel_dist = sqrt (SQR (dx) + SQR (dy));
+
+ if (dx)
+ {
+ angle = gimp_rad_to_deg (atan ((dy/yres) / (dx/xres)));
+ if (dx > 0)
+ {
+ if (dy > 0)
+ angle = 360.0 - angle;
+ else if (dy < 0)
+ angle = -angle;
+ }
+ else
+ {
+ angle = 180.0 - angle;
+ }
+ }
+ else if (dy)
+ {
+ angle = dy > 0 ? 270.0 : 90.0;
+ }
+ else
+ {
+ angle = 0.0;
+ }
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ {
+ enhanced_status = g_strdup_printf ("%.1f %s, %.2f\302\260%s%s",
+ pixel_dist, _("pixels"), angle,
+ separator, status);
+ }
+ else
+ {
+ gdouble inch_dist;
+ gdouble unit_dist;
+ gint digits = 0;
+
+ /* The distance in unit. */
+ inch_dist = sqrt (SQR (dx / xres) + SQR (dy / yres));
+ unit_dist = gimp_unit_get_factor (shell->unit) * inch_dist;
+
+ /* The ideal digit precision for unit in current resolution. */
+ if (inch_dist)
+ digits = gimp_unit_get_scaled_digits (shell->unit,
+ pixel_dist / inch_dist);
+
+ enhanced_status = g_strdup_printf ("%.*f %s, %.2f\302\260%s%s",
+ digits, unit_dist,
+ gimp_unit_get_symbol (shell->unit),
+ angle, separator, status);
+
+ }
+
+ return enhanced_status;
+}
diff --git a/app/display/gimpdisplayshell-utils.h b/app/display/gimpdisplayshell-utils.h
new file mode 100644
index 0000000..3eb52e3
--- /dev/null
+++ b/app/display/gimpdisplayshell-utils.h
@@ -0,0 +1,45 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_UTILS_H__
+#define __GIMP_DISPLAY_SHELL_UTILS_H__
+
+
+void gimp_display_shell_get_constrained_line_params (GimpDisplayShell *shell,
+ gdouble *offset_angle,
+ gdouble *xres,
+ gdouble *yres);
+void gimp_display_shell_constrain_line (GimpDisplayShell *shell,
+ gdouble start_x,
+ gdouble start_y,
+ gdouble *end_x,
+ gdouble *end_y,
+ gint n_snap_lines);
+gdouble gimp_display_shell_constrain_angle (GimpDisplayShell *shell,
+ gdouble angle,
+ gint n_snap_lines);
+
+gchar * gimp_display_shell_get_line_status (GimpDisplayShell *shell,
+ const gchar *status,
+ const gchar *separator,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_UTILS_H__ */
diff --git a/app/display/gimpdisplayshell.c b/app/display/gimpdisplayshell.c
new file mode 100644
index 0000000..6db94e4
--- /dev/null
+++ b/app/display/gimpdisplayshell.c
@@ -0,0 +1,2140 @@
+/* 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 <string.h>
+#include <stdlib.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+#include "tools/tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+#include "config/gimpdisplayconfig.h"
+#include "config/gimpdisplayoptions.h"
+
+#include "core/gimp.h"
+#include "core/gimp-utils.h"
+#include "core/gimpchannel.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-grid.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-snap.h"
+#include "core/gimppickable.h"
+#include "core/gimpprojectable.h"
+#include "core/gimpprojection.h"
+#include "core/gimpmarshal.h"
+#include "core/gimptemplate.h"
+
+#include "widgets/gimpdevices.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "tools/tool_manager.h"
+
+#include "gimpcanvas.h"
+#include "gimpcanvascanvasboundary.h"
+#include "gimpcanvaslayerboundary.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-callbacks.h"
+#include "gimpdisplayshell-cursor.h"
+#include "gimpdisplayshell-dnd.h"
+#include "gimpdisplayshell-expose.h"
+#include "gimpdisplayshell-filter.h"
+#include "gimpdisplayshell-handlers.h"
+#include "gimpdisplayshell-items.h"
+#include "gimpdisplayshell-profile.h"
+#include "gimpdisplayshell-progress.h"
+#include "gimpdisplayshell-render.h"
+#include "gimpdisplayshell-rotate.h"
+#include "gimpdisplayshell-rulers.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-scrollbars.h"
+#include "gimpdisplayshell-selection.h"
+#include "gimpdisplayshell-title.h"
+#include "gimpdisplayshell-tool-events.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpimagewindow.h"
+#include "gimpmotionbuffer.h"
+#include "gimpstatusbar.h"
+
+#include "about.h"
+#include "gimp-log.h"
+#include "gimp-priorities.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_POPUP_MANAGER,
+ PROP_INITIAL_SCREEN,
+ PROP_INITIAL_MONITOR,
+ PROP_DISPLAY,
+ PROP_UNIT,
+ PROP_TITLE,
+ PROP_STATUS,
+ PROP_ICON,
+ PROP_SHOW_ALL,
+ PROP_INFINITE_CANVAS
+};
+
+enum
+{
+ SCALED,
+ SCROLLED,
+ ROTATED,
+ RECONNECT,
+ LAST_SIGNAL
+};
+
+
+typedef struct _GimpDisplayShellOverlay GimpDisplayShellOverlay;
+
+struct _GimpDisplayShellOverlay
+{
+ gdouble image_x;
+ gdouble image_y;
+ GimpHandleAnchor anchor;
+ gint spacing_x;
+ gint spacing_y;
+};
+
+
+/* local function prototypes */
+
+static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface);
+
+static void gimp_display_shell_constructed (GObject *object);
+static void gimp_display_shell_dispose (GObject *object);
+static void gimp_display_shell_finalize (GObject *object);
+static void gimp_display_shell_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_display_shell_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_display_shell_unrealize (GtkWidget *widget);
+static void gimp_display_shell_unmap (GtkWidget *widget);
+static void gimp_display_shell_screen_changed (GtkWidget *widget,
+ GdkScreen *previous);
+static gboolean gimp_display_shell_popup_menu (GtkWidget *widget);
+
+static void gimp_display_shell_real_scaled (GimpDisplayShell *shell);
+static void gimp_display_shell_real_scrolled (GimpDisplayShell *shell);
+static void gimp_display_shell_real_rotated (GimpDisplayShell *shell);
+
+static const guint8 *
+ gimp_display_shell_get_icc_profile(GimpColorManaged *managed,
+ gsize *len);
+static GimpColorProfile *
+ gimp_display_shell_get_color_profile(GimpColorManaged *managed);
+static void gimp_display_shell_profile_changed(GimpColorManaged *managed);
+
+static void gimp_display_shell_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gpointer data);
+static void gimp_display_shell_zoom_button_callback
+ (GimpDisplayShell *shell,
+ GtkWidget *zoom_button);
+static void gimp_display_shell_sync_config (GimpDisplayShell *shell,
+ GimpDisplayConfig *config);
+
+static void gimp_display_shell_remove_overlay (GtkWidget *canvas,
+ GtkWidget *child,
+ GimpDisplayShell *shell);
+static void gimp_display_shell_transform_overlay (GimpDisplayShell *shell,
+ GtkWidget *child,
+ gdouble *x,
+ gdouble *y);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpDisplayShell, gimp_display_shell,
+ GTK_TYPE_EVENT_BOX,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
+ gimp_display_shell_progress_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_color_managed_iface_init))
+
+
+#define parent_class gimp_display_shell_parent_class
+
+static guint display_shell_signals[LAST_SIGNAL] = { 0 };
+
+
+static const gchar display_rc_style[] =
+ "style \"check-button-style\"\n"
+ "{\n"
+ " GtkToggleButton::child-displacement-x = 0\n"
+ " GtkToggleButton::child-displacement-y = 0\n"
+ "}\n"
+ "widget \"*\" style \"check-button-style\"";
+
+static void
+gimp_display_shell_class_init (GimpDisplayShellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ display_shell_signals[SCALED] =
+ g_signal_new ("scaled",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDisplayShellClass, scaled),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ display_shell_signals[SCROLLED] =
+ g_signal_new ("scrolled",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDisplayShellClass, scrolled),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ display_shell_signals[ROTATED] =
+ g_signal_new ("rotated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDisplayShellClass, rotated),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ display_shell_signals[RECONNECT] =
+ g_signal_new ("reconnect",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDisplayShellClass, reconnect),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = gimp_display_shell_constructed;
+ object_class->dispose = gimp_display_shell_dispose;
+ object_class->finalize = gimp_display_shell_finalize;
+ object_class->set_property = gimp_display_shell_set_property;
+ object_class->get_property = gimp_display_shell_get_property;
+
+ widget_class->unrealize = gimp_display_shell_unrealize;
+ widget_class->unmap = gimp_display_shell_unmap;
+ widget_class->screen_changed = gimp_display_shell_screen_changed;
+ widget_class->popup_menu = gimp_display_shell_popup_menu;
+
+ klass->scaled = gimp_display_shell_real_scaled;
+ klass->scrolled = gimp_display_shell_real_scrolled;
+ klass->rotated = gimp_display_shell_real_rotated;
+ klass->reconnect = NULL;
+
+ g_object_class_install_property (object_class, PROP_POPUP_MANAGER,
+ g_param_spec_object ("popup-manager",
+ NULL, NULL,
+ GIMP_TYPE_UI_MANAGER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_INITIAL_SCREEN,
+ g_param_spec_object ("initial-screen",
+ NULL, NULL,
+ GDK_TYPE_SCREEN,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_INITIAL_MONITOR,
+ g_param_spec_int ("initial-monitor",
+ NULL, NULL,
+ 0, 16, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_DISPLAY,
+ g_param_spec_object ("display", NULL, NULL,
+ GIMP_TYPE_DISPLAY,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_UNIT,
+ gimp_param_spec_unit ("unit", NULL, NULL,
+ TRUE, FALSE,
+ GIMP_UNIT_PIXEL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TITLE,
+ g_param_spec_string ("title", NULL, NULL,
+ GIMP_NAME,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_STATUS,
+ g_param_spec_string ("status", NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ICON,
+ g_param_spec_object ("icon", NULL, NULL,
+ GDK_TYPE_PIXBUF,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SHOW_ALL,
+ g_param_spec_boolean ("show-all",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_INFINITE_CANVAS,
+ g_param_spec_boolean ("infinite-canvas",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+
+ gtk_rc_parse_string (display_rc_style);
+}
+
+static void
+gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_icc_profile = gimp_display_shell_get_icc_profile;
+ iface->get_color_profile = gimp_display_shell_get_color_profile;
+ iface->profile_changed = gimp_display_shell_profile_changed;
+}
+
+static void
+gimp_display_shell_init (GimpDisplayShell *shell)
+{
+ shell->options = g_object_new (GIMP_TYPE_DISPLAY_OPTIONS, NULL);
+ shell->fullscreen_options = g_object_new (GIMP_TYPE_DISPLAY_OPTIONS_FULLSCREEN, NULL);
+ shell->no_image_options = g_object_new (GIMP_TYPE_DISPLAY_OPTIONS_NO_IMAGE, NULL);
+
+ shell->zoom = gimp_zoom_model_new ();
+ shell->dot_for_dot = TRUE;
+ shell->scale_x = 1.0;
+ shell->scale_y = 1.0;
+
+ shell->show_image = TRUE;
+
+ shell->show_all = FALSE;
+
+ gimp_display_shell_items_init (shell);
+
+ shell->icon_size = 128;
+ shell->icon_size_small = 96;
+
+ shell->cursor_handedness = GIMP_HANDEDNESS_RIGHT;
+ shell->current_cursor = (GimpCursorType) -1;
+ shell->tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ shell->cursor_modifier = GIMP_CURSOR_MODIFIER_NONE;
+ shell->override_cursor = (GimpCursorType) -1;
+
+ shell->filter_format = babl_format ("R'G'B'A float");
+
+ shell->motion_buffer = gimp_motion_buffer_new ();
+
+ g_signal_connect (shell->motion_buffer, "stroke",
+ G_CALLBACK (gimp_display_shell_buffer_stroke),
+ shell);
+ g_signal_connect (shell->motion_buffer, "hover",
+ G_CALLBACK (gimp_display_shell_buffer_hover),
+ shell);
+
+ shell->zoom_focus_pointer_queue = g_queue_new ();
+
+ gtk_widget_set_events (GTK_WIDGET (shell), (GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_FOCUS_CHANGE_MASK |
+ GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_SCROLL_MASK));
+
+ /* zoom model callback */
+ g_signal_connect_swapped (shell->zoom, "zoomed",
+ G_CALLBACK (gimp_display_shell_scale_update),
+ shell);
+
+ /* active display callback */
+ g_signal_connect (shell, "button-press-event",
+ G_CALLBACK (gimp_display_shell_events),
+ shell);
+ g_signal_connect (shell, "button-release-event",
+ G_CALLBACK (gimp_display_shell_events),
+ shell);
+ g_signal_connect (shell, "key-press-event",
+ G_CALLBACK (gimp_display_shell_events),
+ shell);
+
+ gimp_help_connect (GTK_WIDGET (shell), gimp_standard_help_func,
+ GIMP_HELP_IMAGE_WINDOW, NULL);
+}
+
+static void
+gimp_display_shell_constructed (GObject *object)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object);
+ GimpDisplayConfig *config;
+ GimpImage *image;
+ GtkWidget *main_vbox;
+ GtkWidget *upper_hbox;
+ GtkWidget *right_vbox;
+ GtkWidget *lower_hbox;
+ GtkWidget *inner_table;
+ GtkWidget *gtk_image;
+ GimpAction *action;
+ gint image_width;
+ gint image_height;
+ gint shell_width;
+ gint shell_height;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_UI_MANAGER (shell->popup_manager));
+ gimp_assert (GIMP_IS_DISPLAY (shell->display));
+
+ config = shell->display->config;
+ image = gimp_display_get_image (shell->display);
+
+ gimp_display_shell_profile_init (shell);
+
+ if (image)
+ {
+ image_width = gimp_image_get_width (image);
+ image_height = gimp_image_get_height (image);
+ }
+ else
+ {
+ /* These values are arbitrary. The width is determined by the
+ * menubar and the height is chosen to give a window aspect
+ * ratio of roughly 3:1 (as requested by the UI team).
+ */
+ image_width = GIMP_DEFAULT_IMAGE_WIDTH;
+ image_height = GIMP_DEFAULT_IMAGE_HEIGHT / 3;
+ }
+
+ shell->dot_for_dot = config->default_dot_for_dot;
+
+ if (config->monitor_res_from_gdk)
+ {
+ gimp_get_monitor_resolution (shell->initial_screen,
+ shell->initial_monitor,
+ &shell->monitor_xres, &shell->monitor_yres);
+ }
+ else
+ {
+ shell->monitor_xres = config->monitor_xres;
+ shell->monitor_yres = config->monitor_yres;
+ }
+
+ /* adjust the initial scale -- so that window fits on screen. */
+ if (image)
+ {
+ gimp_display_shell_set_initial_scale (shell, 1.0, //scale,
+ &shell_width, &shell_height);
+ }
+ else
+ {
+ shell_width = -1;
+ shell_height = image_height;
+ }
+
+ gimp_display_shell_sync_config (shell, config);
+
+ /* GtkTable widgets are not able to shrink a row/column correctly if
+ * widgets are attached with GTK_EXPAND even if those widgets have
+ * other rows/columns in their rowspan/colspan where they could
+ * nicely expand without disturbing the row/column which is supposed
+ * to shrink. --Mitch
+ *
+ * Changed the packing to use hboxes and vboxes which behave nicer:
+ *
+ * shell
+ * |
+ * +-- main_vbox
+ * |
+ * +-- upper_hbox
+ * | |
+ * | +-- inner_table
+ * | | |
+ * | | +-- origin
+ * | | +-- hruler
+ * | | +-- vruler
+ * | | +-- canvas
+ * | |
+ * | +-- right_vbox
+ * | |
+ * | +-- zoom_on_resize_button
+ * | +-- vscrollbar
+ * |
+ * +-- lower_hbox
+ * | |
+ * | +-- quick_mask
+ * | +-- hscrollbar
+ * | +-- navbutton
+ * |
+ * +-- statusbar
+ *
+ * Note that we separate "shell" and "main_vbox", so that we can make
+ * "shell" a GtkEventBox, giving it its own window. This isolates our
+ * events from those of our ancestors, avoiding some potential slowdowns,
+ * and making things generally smoother. See bug #778966.
+ */
+
+ /* first, set up the container hierarchy *********************************/
+
+ /* the root vbox */
+
+ main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (shell), main_vbox);
+ gtk_widget_show (main_vbox);
+
+ /* a hbox for the inner_table and the vertical scrollbar */
+ upper_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (main_vbox), upper_hbox, TRUE, TRUE, 0);
+ gtk_widget_show (upper_hbox);
+
+ /* the table containing origin, rulers and the canvas */
+ inner_table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacing (GTK_TABLE (inner_table), 0, 0);
+ gtk_table_set_row_spacing (GTK_TABLE (inner_table), 0, 0);
+ gtk_box_pack_start (GTK_BOX (upper_hbox), inner_table, TRUE, TRUE, 0);
+ gtk_widget_show (inner_table);
+
+ /* the vbox containing the color button and the vertical scrollbar */
+ right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1);
+ gtk_box_pack_start (GTK_BOX (upper_hbox), right_vbox, FALSE, FALSE, 0);
+ gtk_widget_show (right_vbox);
+
+ /* the hbox containing the quickmask button, vertical scrollbar and
+ * the navigation button
+ */
+ lower_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1);
+ gtk_box_pack_start (GTK_BOX (main_vbox), lower_hbox, FALSE, FALSE, 0);
+ gtk_widget_show (lower_hbox);
+
+ /* create the scrollbars *************************************************/
+
+ /* the horizontal scrollbar */
+ shell->hsbdata = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, image_width,
+ 1, 1, image_width));
+ shell->hsb = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, shell->hsbdata);
+ gtk_widget_set_can_focus (shell->hsb, FALSE);
+
+ /* the vertical scrollbar */
+ shell->vsbdata = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, image_height,
+ 1, 1, image_height));
+ shell->vsb = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, shell->vsbdata);
+ gtk_widget_set_can_focus (shell->vsb, FALSE);
+
+ /* create the contents of the inner_table ********************************/
+
+ /* the menu popup button */
+ shell->origin = gtk_event_box_new ();
+
+ gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_RIGHT,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (shell->origin), gtk_image);
+ gtk_widget_show (gtk_image);
+
+ g_signal_connect (shell->origin, "button-press-event",
+ G_CALLBACK (gimp_display_shell_origin_button_press),
+ shell);
+
+ gimp_help_set_help_data (shell->origin,
+ _("Access the image menu"),
+ GIMP_HELP_IMAGE_WINDOW_ORIGIN);
+
+ shell->canvas = gimp_canvas_new (config);
+ gtk_widget_set_size_request (shell->canvas, shell_width, shell_height);
+ gtk_container_set_border_width (GTK_CONTAINER (shell->canvas), 10);
+
+ g_signal_connect (shell->canvas, "remove",
+ G_CALLBACK (gimp_display_shell_remove_overlay),
+ shell);
+
+ gimp_display_shell_dnd_init (shell);
+ gimp_display_shell_selection_init (shell);
+
+ /* the horizontal ruler */
+ shell->hrule = gimp_ruler_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_events (GTK_WIDGET (shell->hrule),
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+ gimp_ruler_add_track_widget (GIMP_RULER (shell->hrule), shell->canvas);
+ g_signal_connect (shell->hrule, "button-press-event",
+ G_CALLBACK (gimp_display_shell_hruler_button_press),
+ shell);
+
+ gimp_help_set_help_data (shell->hrule, NULL, GIMP_HELP_IMAGE_WINDOW_RULER);
+
+ /* the vertical ruler */
+ shell->vrule = gimp_ruler_new (GTK_ORIENTATION_VERTICAL);
+ gtk_widget_set_events (GTK_WIDGET (shell->vrule),
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+ gimp_ruler_add_track_widget (GIMP_RULER (shell->vrule), shell->canvas);
+ g_signal_connect (shell->vrule, "button-press-event",
+ G_CALLBACK (gimp_display_shell_vruler_button_press),
+ shell);
+
+ gimp_help_set_help_data (shell->vrule, NULL, GIMP_HELP_IMAGE_WINDOW_RULER);
+
+ /* set the rulers as track widgets for each other, so we don't end up
+ * with one ruler wrongly being stuck a few pixels off while we are
+ * hovering the other
+ */
+ gimp_ruler_add_track_widget (GIMP_RULER (shell->hrule), shell->vrule);
+ gimp_ruler_add_track_widget (GIMP_RULER (shell->vrule), shell->hrule);
+
+ gimp_devices_add_widget (shell->display->gimp, shell->hrule);
+ gimp_devices_add_widget (shell->display->gimp, shell->vrule);
+
+ g_signal_connect (shell->canvas, "grab-notify",
+ G_CALLBACK (gimp_display_shell_canvas_grab_notify),
+ shell);
+
+ g_signal_connect (shell->canvas, "realize",
+ G_CALLBACK (gimp_display_shell_canvas_realize),
+ shell);
+ g_signal_connect (shell->canvas, "realize",
+ G_CALLBACK (gimp_display_shell_canvas_realize_after),
+ shell);
+ g_signal_connect (shell->canvas, "size-allocate",
+ G_CALLBACK (gimp_display_shell_canvas_size_allocate),
+ shell);
+ g_signal_connect (shell->canvas, "expose-event",
+ G_CALLBACK (gimp_display_shell_canvas_expose),
+ shell);
+
+ g_signal_connect (shell->canvas, "enter-notify-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "leave-notify-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "proximity-in-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "proximity-out-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "focus-in-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "focus-out-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "button-press-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "button-release-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "scroll-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "motion-notify-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "key-press-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+ g_signal_connect (shell->canvas, "key-release-event",
+ G_CALLBACK (gimp_display_shell_canvas_tool_events),
+ shell);
+
+ /* create the contents of the right_vbox *********************************/
+
+ shell->zoom_button = g_object_new (GTK_TYPE_CHECK_BUTTON,
+ "draw-indicator", FALSE,
+ "relief", GTK_RELIEF_NONE,
+ "width-request", 18,
+ "height-request", 18,
+ NULL);
+ gtk_widget_set_can_focus (shell->zoom_button, FALSE);
+
+ gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_ZOOM_FOLLOW_WINDOW,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (shell->zoom_button), gtk_image);
+ gtk_widget_show (gtk_image);
+
+ gimp_help_set_help_data (shell->zoom_button,
+ _("Zoom image when window size changes"),
+ GIMP_HELP_IMAGE_WINDOW_ZOOM_FOLLOW_BUTTON);
+
+ g_signal_connect_swapped (shell->zoom_button, "toggled",
+ G_CALLBACK (gimp_display_shell_zoom_button_callback),
+ shell);
+
+ /* create the contents of the lower_hbox *********************************/
+
+ /* the quick mask button */
+ shell->quick_mask_button = g_object_new (GTK_TYPE_CHECK_BUTTON,
+ "draw-indicator", FALSE,
+ "relief", GTK_RELIEF_NONE,
+ "width-request", 18,
+ "height-request", 18,
+ NULL);
+ gtk_widget_set_can_focus (shell->quick_mask_button, FALSE);
+
+ gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_QUICK_MASK_OFF,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (shell->quick_mask_button), gtk_image);
+ gtk_widget_show (gtk_image);
+
+ action = gimp_ui_manager_find_action (shell->popup_manager,
+ "quick-mask", "quick-mask-toggle");
+ if (action)
+ gimp_widget_set_accel_help (shell->quick_mask_button, action);
+ else
+ gimp_help_set_help_data (shell->quick_mask_button,
+ _("Toggle Quick Mask"),
+ GIMP_HELP_IMAGE_WINDOW_QUICK_MASK_BUTTON);
+
+ g_signal_connect (shell->quick_mask_button, "toggled",
+ G_CALLBACK (gimp_display_shell_quick_mask_toggled),
+ shell);
+ g_signal_connect (shell->quick_mask_button, "button-press-event",
+ G_CALLBACK (gimp_display_shell_quick_mask_button_press),
+ shell);
+
+ /* the navigation window button */
+ shell->nav_ebox = gtk_event_box_new ();
+
+ gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_NAVIGATION,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (shell->nav_ebox), gtk_image);
+ gtk_widget_show (gtk_image);
+
+ g_signal_connect (shell->nav_ebox, "button-press-event",
+ G_CALLBACK (gimp_display_shell_navigation_button_press),
+ shell);
+
+ gimp_help_set_help_data (shell->nav_ebox,
+ _("Navigate the image display"),
+ GIMP_HELP_IMAGE_WINDOW_NAV_BUTTON);
+
+ /* the statusbar ********************************************************/
+
+ shell->statusbar = gimp_statusbar_new ();
+ gimp_statusbar_set_shell (GIMP_STATUSBAR (shell->statusbar), shell);
+ gimp_help_set_help_data (shell->statusbar, NULL,
+ GIMP_HELP_IMAGE_WINDOW_STATUS_BAR);
+ gtk_box_pack_end (GTK_BOX (main_vbox), shell->statusbar, FALSE, FALSE, 0);
+
+ /* pack all the widgets **************************************************/
+
+ /* fill the inner_table */
+ gtk_table_attach (GTK_TABLE (inner_table), shell->origin, 0, 1, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_table_attach (GTK_TABLE (inner_table), shell->hrule, 1, 2, 0, 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_FILL, 0, 0);
+ gtk_table_attach (GTK_TABLE (inner_table), shell->vrule, 0, 1, 1, 2,
+ GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_table_attach (GTK_TABLE (inner_table), shell->canvas, 1, 2, 1, 2,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+
+ /* fill the right_vbox */
+ gtk_box_pack_start (GTK_BOX (right_vbox),
+ shell->zoom_button, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (right_vbox),
+ shell->vsb, TRUE, TRUE, 0);
+
+ /* fill the lower_hbox */
+ gtk_box_pack_start (GTK_BOX (lower_hbox),
+ shell->quick_mask_button, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (lower_hbox),
+ shell->hsb, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (lower_hbox),
+ shell->nav_ebox, FALSE, FALSE, 0);
+
+ /* show everything that is always shown ***********************************/
+
+ gtk_widget_show (GTK_WIDGET (shell->canvas));
+
+ if (image)
+ {
+ gimp_display_shell_connect (shell);
+
+ /* After connecting to the image we want to center it. Since we
+ * not even finished creating the display shell, we can safely
+ * assume we will get a size-allocate later.
+ */
+ shell->size_allocate_center_image = TRUE;
+ }
+ else
+ {
+#if 0
+ /* Disabled because it sets GDK_POINTER_MOTION_HINT on
+ * shell->canvas. For info see Bug 677375
+ */
+ gimp_help_set_help_data (shell->canvas,
+ _("Drop image files here to open them"),
+ NULL);
+#endif
+
+ gimp_statusbar_empty (GIMP_STATUSBAR (shell->statusbar));
+ }
+
+ /* make sure the information is up-to-date */
+ gimp_display_shell_scale_update (shell);
+
+ gimp_display_shell_set_show_all (shell, config->default_show_all);
+}
+
+static void
+gimp_display_shell_dispose (GObject *object)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object);
+
+ if (shell->display && gimp_display_get_shell (shell->display))
+ gimp_display_shell_disconnect (shell);
+
+ shell->popup_manager = NULL;
+
+ if (shell->selection)
+ gimp_display_shell_selection_free (shell);
+
+ gimp_display_shell_filter_set (shell, NULL);
+
+ if (shell->filter_idle_id)
+ {
+ g_source_remove (shell->filter_idle_id);
+ shell->filter_idle_id = 0;
+ }
+
+ g_clear_pointer (&shell->mask_surface, cairo_surface_destroy);
+ g_clear_pointer (&shell->checkerboard, cairo_pattern_destroy);
+
+ gimp_display_shell_profile_finalize (shell);
+
+ g_clear_object (&shell->filter_buffer);
+ shell->filter_data = NULL;
+ shell->filter_stride = 0;
+
+ g_clear_object (&shell->mask);
+
+ gimp_display_shell_items_free (shell);
+
+ g_clear_object (&shell->motion_buffer);
+
+ g_clear_pointer (&shell->zoom_focus_pointer_queue, g_queue_free);
+
+ if (shell->title_idle_id)
+ {
+ g_source_remove (shell->title_idle_id);
+ shell->title_idle_id = 0;
+ }
+
+ if (shell->fill_idle_id)
+ {
+ g_source_remove (shell->fill_idle_id);
+ shell->fill_idle_id = 0;
+ }
+
+ g_clear_pointer (&shell->nav_popup, gtk_widget_destroy);
+
+ if (shell->blink_timeout_id)
+ {
+ g_source_remove (shell->blink_timeout_id);
+ shell->blink_timeout_id = 0;
+ }
+
+ shell->display = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_display_shell_finalize (GObject *object)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object);
+
+ g_clear_object (&shell->zoom);
+ g_clear_pointer (&shell->rotate_transform, g_free);
+ g_clear_pointer (&shell->rotate_untransform, g_free);
+ g_clear_object (&shell->options);
+ g_clear_object (&shell->fullscreen_options);
+ g_clear_object (&shell->no_image_options);
+ g_clear_pointer (&shell->title, g_free);
+ g_clear_pointer (&shell->status, g_free);
+ g_clear_object (&shell->icon);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_display_shell_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object);
+
+ switch (property_id)
+ {
+ case PROP_POPUP_MANAGER:
+ shell->popup_manager = g_value_get_object (value);
+ break;
+ case PROP_INITIAL_SCREEN:
+ shell->initial_screen = g_value_get_object (value);
+ break;
+ case PROP_INITIAL_MONITOR:
+ shell->initial_monitor = g_value_get_int (value);
+ break;
+ case PROP_DISPLAY:
+ shell->display = g_value_get_object (value);
+ break;
+ case PROP_UNIT:
+ gimp_display_shell_set_unit (shell, g_value_get_int (value));
+ break;
+ case PROP_TITLE:
+ g_free (shell->title);
+ shell->title = g_value_dup_string (value);
+ break;
+ case PROP_STATUS:
+ g_free (shell->status);
+ shell->status = g_value_dup_string (value);
+ break;
+ case PROP_ICON:
+ if (shell->icon)
+ g_object_unref (shell->icon);
+ shell->icon = g_value_dup_object (value);
+ break;
+ case PROP_SHOW_ALL:
+ gimp_display_shell_set_show_all (shell, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_display_shell_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object);
+
+ switch (property_id)
+ {
+ case PROP_POPUP_MANAGER:
+ g_value_set_object (value, shell->popup_manager);
+ break;
+ case PROP_INITIAL_SCREEN:
+ g_value_set_object (value, shell->initial_screen);
+ break;
+ case PROP_INITIAL_MONITOR:
+ g_value_set_int (value, shell->initial_monitor);
+ break;
+ case PROP_DISPLAY:
+ g_value_set_object (value, shell->display);
+ break;
+ case PROP_UNIT:
+ g_value_set_int (value, shell->unit);
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, shell->title);
+ break;
+ case PROP_STATUS:
+ g_value_set_string (value, shell->status);
+ break;
+ case PROP_ICON:
+ g_value_set_object (value, shell->icon);
+ break;
+ case PROP_SHOW_ALL:
+ g_value_set_boolean (value, shell->show_all);
+ break;
+ case PROP_INFINITE_CANVAS:
+ g_value_set_boolean (value,
+ gimp_display_shell_get_infinite_canvas (shell));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_display_shell_unrealize (GtkWidget *widget)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget);
+
+ if (shell->nav_popup)
+ gtk_widget_unrealize (shell->nav_popup);
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static void
+gimp_display_shell_unmap (GtkWidget *widget)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget);
+
+ gimp_display_shell_selection_undraw (shell);
+
+ GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+gimp_display_shell_screen_changed (GtkWidget *widget,
+ GdkScreen *previous)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget);
+
+ if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
+ GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous);
+
+ if (shell->display->config->monitor_res_from_gdk)
+ {
+ gimp_get_monitor_resolution (gtk_widget_get_screen (widget),
+ gimp_widget_get_monitor (widget),
+ &shell->monitor_xres,
+ &shell->monitor_yres);
+ }
+ else
+ {
+ shell->monitor_xres = shell->display->config->monitor_xres;
+ shell->monitor_yres = shell->display->config->monitor_yres;
+ }
+}
+
+static gboolean
+gimp_display_shell_popup_menu (GtkWidget *widget)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget);
+
+ gimp_context_set_display (gimp_get_user_context (shell->display->gimp),
+ shell->display);
+
+ gimp_ui_manager_ui_popup (shell->popup_manager, "/dummy-menubar/image-popup",
+ GTK_WIDGET (shell),
+ gimp_display_shell_menu_position,
+ shell->origin,
+ NULL, NULL);
+
+ return TRUE;
+}
+
+static void
+gimp_display_shell_real_scaled (GimpDisplayShell *shell)
+{
+ GimpContext *user_context;
+
+ if (! shell->display)
+ return;
+
+ gimp_display_shell_title_update (shell);
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (user_context))
+ {
+ gimp_display_shell_update_priority_rect (shell);
+
+ gimp_ui_manager_update (shell->popup_manager, shell->display);
+ }
+}
+
+static void
+gimp_display_shell_real_scrolled (GimpDisplayShell *shell)
+{
+ GimpContext *user_context;
+
+ if (! shell->display)
+ return;
+
+ gimp_display_shell_title_update (shell);
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (user_context))
+ {
+ gimp_display_shell_update_priority_rect (shell);
+
+ }
+}
+
+static void
+gimp_display_shell_real_rotated (GimpDisplayShell *shell)
+{
+ GimpContext *user_context;
+
+ if (! shell->display)
+ return;
+
+ gimp_display_shell_title_update (shell);
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (user_context))
+ {
+ gimp_display_shell_update_priority_rect (shell);
+
+ gimp_ui_manager_update (shell->popup_manager, shell->display);
+ }
+}
+
+static const guint8 *
+gimp_display_shell_get_icc_profile (GimpColorManaged *managed,
+ gsize *len)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (managed);
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ if (image)
+ return gimp_color_managed_get_icc_profile (GIMP_COLOR_MANAGED (image), len);
+
+ return NULL;
+}
+
+static GimpColorProfile *
+gimp_display_shell_get_color_profile (GimpColorManaged *managed)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (managed);
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ if (image)
+ return gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (image));
+
+ return NULL;
+}
+
+static void
+gimp_display_shell_profile_changed (GimpColorManaged *managed)
+{
+ GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (managed);
+
+ gimp_display_shell_profile_update (shell);
+ gimp_display_shell_expose_full (shell);
+}
+
+static void
+gimp_display_shell_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gpointer data)
+{
+ gimp_button_menu_position (GTK_WIDGET (data), menu, GTK_POS_RIGHT, x, y);
+}
+
+static void
+gimp_display_shell_zoom_button_callback (GimpDisplayShell *shell,
+ GtkWidget *zoom_button)
+{
+ shell->zoom_on_resize =
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (zoom_button));
+
+ if (shell->zoom_on_resize &&
+ gimp_display_shell_scale_image_is_within_viewport (shell, NULL, NULL))
+ {
+ /* Implicitly make a View -> Fit Image in Window */
+ gimp_display_shell_scale_fit_in (shell);
+ }
+}
+
+static void
+gimp_display_shell_sync_config (GimpDisplayShell *shell,
+ GimpDisplayConfig *config)
+{
+ gimp_config_sync (G_OBJECT (config->default_view),
+ G_OBJECT (shell->options), 0);
+ gimp_config_sync (G_OBJECT (config->default_fullscreen_view),
+ G_OBJECT (shell->fullscreen_options), 0);
+}
+
+static void
+gimp_display_shell_remove_overlay (GtkWidget *canvas,
+ GtkWidget *child,
+ GimpDisplayShell *shell)
+{
+ shell->children = g_list_remove (shell->children, child);
+}
+
+static void
+gimp_display_shell_transform_overlay (GimpDisplayShell *shell,
+ GtkWidget *child,
+ gdouble *x,
+ gdouble *y)
+{
+ GimpDisplayShellOverlay *overlay;
+ GtkRequisition requisition;
+
+ overlay = g_object_get_data (G_OBJECT (child), "image-coords-overlay");
+
+ gimp_display_shell_transform_xy_f (shell,
+ overlay->image_x,
+ overlay->image_y,
+ x, y);
+
+ gtk_widget_size_request (child, &requisition);
+
+ switch (overlay->anchor)
+ {
+ case GIMP_HANDLE_ANCHOR_CENTER:
+ *x -= requisition.width / 2;
+ *y -= requisition.height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH:
+ *x -= requisition.width / 2;
+ *y += overlay->spacing_y;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_WEST:
+ *x += overlay->spacing_x;
+ *y += overlay->spacing_y;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_EAST:
+ *x -= requisition.width + overlay->spacing_x;
+ *y += overlay->spacing_y;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH:
+ *x -= requisition.width / 2;
+ *y -= requisition.height + overlay->spacing_y;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
+ *x += overlay->spacing_x;
+ *y -= requisition.height + overlay->spacing_y;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
+ *x -= requisition.width + overlay->spacing_x;
+ *y -= requisition.height + overlay->spacing_y;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_WEST:
+ *x += overlay->spacing_x;
+ *y -= requisition.height / 2;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_EAST:
+ *x -= requisition.width + overlay->spacing_x;
+ *y -= requisition.height / 2;
+ break;
+ }
+}
+
+
+/* public functions */
+
+GtkWidget *
+gimp_display_shell_new (GimpDisplay *display,
+ GimpUnit unit,
+ gdouble scale,
+ GimpUIManager *popup_manager,
+ GdkScreen *screen,
+ gint monitor)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+ g_return_val_if_fail (GIMP_IS_UI_MANAGER (popup_manager), NULL);
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+ return g_object_new (GIMP_TYPE_DISPLAY_SHELL,
+ "popup-manager", popup_manager,
+ "initial-screen", screen,
+ "initial-monitor", monitor,
+ "display", display,
+ "unit", unit,
+ NULL);
+}
+
+void
+gimp_display_shell_add_overlay (GimpDisplayShell *shell,
+ GtkWidget *child,
+ gdouble image_x,
+ gdouble image_y,
+ GimpHandleAnchor anchor,
+ gint spacing_x,
+ gint spacing_y)
+{
+ GimpDisplayShellOverlay *overlay;
+ gdouble x, y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GTK_IS_WIDGET (shell));
+
+ overlay = g_new0 (GimpDisplayShellOverlay, 1);
+
+ overlay->image_x = image_x;
+ overlay->image_y = image_y;
+ overlay->anchor = anchor;
+ overlay->spacing_x = spacing_x;
+ overlay->spacing_y = spacing_y;
+
+ g_object_set_data_full (G_OBJECT (child), "image-coords-overlay", overlay,
+ (GDestroyNotify) g_free);
+
+ shell->children = g_list_prepend (shell->children, child);
+
+ gimp_display_shell_transform_overlay (shell, child, &x, &y);
+
+ gimp_overlay_box_add_child (GIMP_OVERLAY_BOX (shell->canvas), child, 0.0, 0.0);
+ gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas),
+ child, x, y);
+}
+
+void
+gimp_display_shell_move_overlay (GimpDisplayShell *shell,
+ GtkWidget *child,
+ gdouble image_x,
+ gdouble image_y,
+ GimpHandleAnchor anchor,
+ gint spacing_x,
+ gint spacing_y)
+{
+ GimpDisplayShellOverlay *overlay;
+ gdouble x, y;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GTK_IS_WIDGET (shell));
+
+ overlay = g_object_get_data (G_OBJECT (child), "image-coords-overlay");
+
+ g_return_if_fail (overlay != NULL);
+
+ overlay->image_x = image_x;
+ overlay->image_y = image_y;
+ overlay->anchor = anchor;
+ overlay->spacing_x = spacing_x;
+ overlay->spacing_y = spacing_y;
+
+ gimp_display_shell_transform_overlay (shell, child, &x, &y);
+
+ gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas),
+ child, x, y);
+}
+
+GimpImageWindow *
+gimp_display_shell_get_window (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return GIMP_IMAGE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (shell),
+ GIMP_TYPE_IMAGE_WINDOW));
+}
+
+GimpStatusbar *
+gimp_display_shell_get_statusbar (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return GIMP_STATUSBAR (shell->statusbar);
+}
+
+GimpColorConfig *
+gimp_display_shell_get_color_config (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return shell->color_config;
+}
+
+void
+gimp_display_shell_present (GimpDisplayShell *shell)
+{
+ GimpImageWindow *window;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ window = gimp_display_shell_get_window (shell);
+
+ if (window)
+ {
+ gimp_image_window_set_active_shell (window, shell);
+
+ gtk_window_present (GTK_WINDOW (window));
+ }
+}
+
+void
+gimp_display_shell_reconnect (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_DISPLAY (shell->display));
+ g_return_if_fail (gimp_display_get_image (shell->display) != NULL);
+
+ if (shell->fill_idle_id)
+ {
+ g_source_remove (shell->fill_idle_id);
+ shell->fill_idle_id = 0;
+ }
+
+ g_signal_emit (shell, display_shell_signals[RECONNECT], 0);
+
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell));
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+
+ gimp_display_shell_scaled (shell);
+
+ gimp_display_shell_expose_full (shell);
+}
+
+static gboolean
+gimp_display_shell_blink (GimpDisplayShell *shell)
+{
+ shell->blink_timeout_id = 0;
+
+ if (shell->blink)
+ {
+ shell->blink = FALSE;
+ }
+ else
+ {
+ shell->blink = TRUE;
+
+ shell->blink_timeout_id =
+ g_timeout_add (100, (GSourceFunc) gimp_display_shell_blink, shell);
+ }
+
+ gimp_display_shell_expose_full (shell);
+
+ return FALSE;
+}
+
+void
+gimp_display_shell_empty (GimpDisplayShell *shell)
+{
+ GimpContext *user_context;
+ GimpImageWindow *window;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_DISPLAY (shell->display));
+ g_return_if_fail (gimp_display_get_image (shell->display) == NULL);
+
+ window = gimp_display_shell_get_window (shell);
+
+ if (shell->fill_idle_id)
+ {
+ g_source_remove (shell->fill_idle_id);
+ shell->fill_idle_id = 0;
+ }
+
+ gimp_display_shell_selection_undraw (shell);
+
+ gimp_display_shell_unset_cursor (shell);
+
+ gimp_display_shell_filter_set (shell, NULL);
+
+ gimp_display_shell_sync_config (shell, shell->display->config);
+
+ gimp_display_shell_appearance_update (shell);
+ gimp_image_window_update_tabs (window);
+#if 0
+ gimp_help_set_help_data (shell->canvas,
+ _("Drop image files here to open them"), NULL);
+#endif
+
+ gimp_statusbar_empty (GIMP_STATUSBAR (shell->statusbar));
+
+ shell->flip_horizontally = FALSE;
+ shell->flip_vertically = FALSE;
+ shell->rotate_angle = 0.0;
+ gimp_display_shell_rotate_update_transform (shell);
+
+ gimp_display_shell_expose_full (shell);
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (user_context))
+ gimp_ui_manager_update (shell->popup_manager, shell->display);
+
+ shell->blink_timeout_id =
+ g_timeout_add (1403230, (GSourceFunc) gimp_display_shell_blink, shell);
+}
+
+static gboolean
+gimp_display_shell_fill_idle (GimpDisplayShell *shell)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
+
+ shell->fill_idle_id = 0;
+
+ if (GTK_IS_WINDOW (toplevel))
+ {
+ gimp_display_shell_scale_shrink_wrap (shell, TRUE);
+
+ gtk_window_present (GTK_WINDOW (toplevel));
+ }
+
+ return FALSE;
+}
+
+void
+gimp_display_shell_fill (GimpDisplayShell *shell,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale)
+{
+ GimpDisplayConfig *config;
+ GimpImageWindow *window;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GIMP_IS_DISPLAY (shell->display));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ config = shell->display->config;
+ window = gimp_display_shell_get_window (shell);
+
+ shell->show_image = TRUE;
+
+ shell->dot_for_dot = config->default_dot_for_dot;
+
+ gimp_display_shell_set_unit (shell, unit);
+ gimp_display_shell_set_initial_scale (shell, scale, NULL, NULL);
+ gimp_display_shell_scale_update (shell);
+
+ gimp_display_shell_sync_config (shell, config);
+
+ gimp_image_window_suspend_keep_pos (window);
+ gimp_display_shell_appearance_update (shell);
+ gimp_image_window_resume_keep_pos (window);
+
+ gimp_image_window_update_tabs (window);
+#if 0
+ gimp_help_set_help_data (shell->canvas, NULL, NULL);
+#endif
+
+ gimp_statusbar_fill (GIMP_STATUSBAR (shell->statusbar));
+
+ /* make sure a size-allocate always occurs, even when the rulers and
+ * scrollbars are hidden. see issue #4968.
+ */
+ shell->size_allocate_center_image = TRUE;
+ gtk_widget_queue_resize (GTK_WIDGET (shell->canvas));
+
+ if (shell->blink_timeout_id)
+ {
+ g_source_remove (shell->blink_timeout_id);
+ shell->blink_timeout_id = 0;
+ }
+
+ shell->fill_idle_id =
+ g_idle_add_full (GIMP_PRIORITY_DISPLAY_SHELL_FILL_IDLE,
+ (GSourceFunc) gimp_display_shell_fill_idle, shell,
+ NULL);
+
+ gimp_display_shell_set_show_all (shell, config->default_show_all);
+}
+
+void
+gimp_display_shell_scaled (GimpDisplayShell *shell)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_rotate_update_transform (shell);
+
+ for (list = shell->children; list; list = g_list_next (list))
+ {
+ GtkWidget *child = list->data;
+ gdouble x, y;
+
+ gimp_display_shell_transform_overlay (shell, child, &x, &y);
+
+ gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas),
+ child, x, y);
+ }
+
+ g_signal_emit (shell, display_shell_signals[SCALED], 0);
+}
+
+void
+gimp_display_shell_scrolled (GimpDisplayShell *shell)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_rotate_update_transform (shell);
+
+ for (list = shell->children; list; list = g_list_next (list))
+ {
+ GtkWidget *child = list->data;
+ gdouble x, y;
+
+ gimp_display_shell_transform_overlay (shell, child, &x, &y);
+
+ gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas),
+ child, x, y);
+ }
+
+ g_signal_emit (shell, display_shell_signals[SCROLLED], 0);
+}
+
+void
+gimp_display_shell_rotated (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ gimp_display_shell_rotate_update_transform (shell);
+
+ g_signal_emit (shell, display_shell_signals[ROTATED], 0);
+}
+
+void
+gimp_display_shell_set_unit (GimpDisplayShell *shell,
+ GimpUnit unit)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell->unit != unit)
+ {
+ shell->unit = unit;
+
+ gimp_display_shell_rulers_update (shell);
+
+ gimp_display_shell_scaled (shell);
+
+ g_object_notify (G_OBJECT (shell), "unit");
+ }
+}
+
+GimpUnit
+gimp_display_shell_get_unit (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), GIMP_UNIT_PIXEL);
+
+ return shell->unit;
+}
+
+gboolean
+gimp_display_shell_snap_coords (GimpDisplayShell *shell,
+ GimpCoords *coords,
+ gint snap_offset_x,
+ gint snap_offset_y,
+ gint snap_width,
+ gint snap_height)
+{
+ GimpImage *image;
+ gboolean snap_to_guides = FALSE;
+ gboolean snap_to_grid = FALSE;
+ gboolean snap_to_canvas = FALSE;
+ gboolean snap_to_vectors = FALSE;
+ gboolean snapped = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (gimp_display_shell_get_snap_to_guides (shell) &&
+ gimp_image_get_guides (image))
+ {
+ snap_to_guides = TRUE;
+ }
+
+ if (gimp_display_shell_get_snap_to_grid (shell) &&
+ gimp_image_get_grid (image))
+ {
+ snap_to_grid = TRUE;
+ }
+
+ snap_to_canvas = gimp_display_shell_get_snap_to_canvas (shell);
+
+ if (gimp_display_shell_get_snap_to_vectors (shell) &&
+ gimp_image_get_active_vectors (image))
+ {
+ snap_to_vectors = TRUE;
+ }
+
+ if (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors)
+ {
+ gint snap_distance;
+ gdouble tx, ty;
+
+ snap_distance = shell->display->config->snap_distance;
+
+ if (snap_width > 0 && snap_height > 0)
+ {
+ snapped = gimp_image_snap_rectangle (image,
+ coords->x + snap_offset_x,
+ coords->y + snap_offset_y,
+ coords->x + snap_offset_x +
+ snap_width,
+ coords->y + snap_offset_y +
+ snap_height,
+ &tx,
+ &ty,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas,
+ snap_to_vectors);
+ }
+ else
+ {
+ snapped = gimp_image_snap_point (image,
+ coords->x + snap_offset_x,
+ coords->y + snap_offset_y,
+ &tx,
+ &ty,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas,
+ snap_to_vectors);
+ }
+
+ if (snapped)
+ {
+ coords->x = tx - snap_offset_x;
+ coords->y = ty - snap_offset_y;
+ }
+ }
+
+ return snapped;
+}
+
+gboolean
+gimp_display_shell_mask_bounds (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ GimpImage *image;
+ GimpLayer *layer;
+ gint x1, y1;
+ gint x2, y2;
+ gdouble x1_f, y1_f;
+ gdouble x2_f, y2_f;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+ g_return_val_if_fail (x != NULL, FALSE);
+ g_return_val_if_fail (y != NULL, FALSE);
+ g_return_val_if_fail (width != NULL, FALSE);
+ g_return_val_if_fail (height != NULL, FALSE);
+
+ image = gimp_display_get_image (shell->display);
+
+ /* If there is a floating selection, handle things differently */
+ if ((layer = gimp_image_get_floating_selection (image)))
+ {
+ gint fs_x;
+ gint fs_y;
+ gint fs_width;
+ gint fs_height;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &fs_x, &fs_y);
+ fs_width = gimp_item_get_width (GIMP_ITEM (layer));
+ fs_height = gimp_item_get_height (GIMP_ITEM (layer));
+
+ if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ x, y, width, height))
+ {
+ *x = fs_x;
+ *y = fs_y;
+ *width = fs_width;
+ *height = fs_height;
+ }
+ else
+ {
+ gimp_rectangle_union (*x, *y, *width, *height,
+ fs_x, fs_y, fs_width, fs_height,
+ x, y, width, height);
+ }
+ }
+ else if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ x, y, width, height))
+ {
+ return FALSE;
+ }
+
+ x1 = *x;
+ y1 = *y;
+ x2 = *x + *width;
+ y2 = *y + *height;
+
+ gimp_display_shell_transform_bounds (shell,
+ x1, y1, x2, y2,
+ &x1_f, &y1_f, &x2_f, &y2_f);
+
+ /* Make sure the extents are within bounds */
+ x1 = CLAMP (floor (x1_f), 0, shell->disp_width);
+ y1 = CLAMP (floor (y1_f), 0, shell->disp_height);
+ x2 = CLAMP (ceil (x2_f), 0, shell->disp_width);
+ y2 = CLAMP (ceil (y2_f), 0, shell->disp_height);
+
+ *x = x1;
+ *y = y1;
+ *width = x2 - x1;
+ *height = y2 - y1;
+
+ return (*width > 0) && (*height > 0);
+}
+
+void
+gimp_display_shell_set_show_image (GimpDisplayShell *shell,
+ gboolean show_image)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (show_image != shell->show_image)
+ {
+ shell->show_image = show_image;
+
+ gimp_display_shell_expose_full (shell);
+ }
+}
+
+void
+gimp_display_shell_set_show_all (GimpDisplayShell *shell,
+ gboolean show_all)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (show_all != shell->show_all)
+ {
+ shell->show_all = show_all;
+
+ if (shell->display && gimp_display_get_image (shell->display))
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpContext *user_context;
+
+ if (show_all)
+ gimp_image_inc_show_all_count (image);
+ else
+ gimp_image_dec_show_all_count (image);
+
+ gimp_image_flush (image);
+
+ gimp_display_update_bounding_box (shell->display);
+
+ gimp_display_shell_update_show_canvas (shell);
+
+ gimp_display_shell_scroll_clamp_and_update (shell);
+ gimp_display_shell_scrollbars_update (shell);
+
+ gimp_display_shell_expose_full (shell);
+
+ user_context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (user_context))
+ {
+ gimp_display_shell_update_priority_rect (shell);
+
+ gimp_ui_manager_update (shell->popup_manager, shell->display);
+ }
+ }
+
+ g_object_notify (G_OBJECT (shell), "show-all");
+ g_object_notify (G_OBJECT (shell), "infinite-canvas");
+ }
+}
+
+
+GimpPickable *
+gimp_display_shell_get_pickable (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ if (! shell->show_all)
+ return GIMP_PICKABLE (image);
+ else
+ return GIMP_PICKABLE (gimp_image_get_projection (image));
+ }
+
+ return NULL;
+}
+
+GimpPickable *
+gimp_display_shell_get_canvas_pickable (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ if (! gimp_display_shell_get_infinite_canvas (shell))
+ return GIMP_PICKABLE (image);
+ else
+ return GIMP_PICKABLE (gimp_image_get_projection (image));
+ }
+
+ return NULL;
+}
+
+GeglRectangle
+gimp_display_shell_get_bounding_box (GimpDisplayShell *shell)
+{
+ GeglRectangle bounding_box = {};
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), bounding_box);
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ if (! shell->show_all)
+ {
+ bounding_box.width = gimp_image_get_width (image);
+ bounding_box.height = gimp_image_get_height (image);
+ }
+ else
+ {
+ bounding_box = gimp_projectable_get_bounding_box (
+ GIMP_PROJECTABLE (image));
+ }
+ }
+
+ return bounding_box;
+}
+
+gboolean
+gimp_display_shell_get_infinite_canvas (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
+
+ return shell->show_all &&
+ ! gimp_display_shell_get_padding_in_show_all (shell);
+}
+
+void
+gimp_display_shell_update_priority_rect (GimpDisplayShell *shell)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ image = gimp_display_get_image (shell->display);
+
+ if (image)
+ {
+ GimpProjection *projection = gimp_image_get_projection (image);
+ gint x, y;
+ gint width, height;
+
+ gimp_display_shell_untransform_viewport (shell, ! shell->show_all,
+ &x, &y, &width, &height);
+ gimp_projection_set_priority_rect (projection, x, y, width, height);
+ }
+}
+
+void
+gimp_display_shell_flush (GimpDisplayShell *shell,
+ gboolean now)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (now)
+ {
+ gdk_window_process_updates (gtk_widget_get_window (shell->canvas),
+ FALSE);
+ }
+ else
+ {
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+ GimpContext *context;
+
+ gimp_display_shell_title_update (shell);
+
+ gimp_canvas_layer_boundary_set_layer (GIMP_CANVAS_LAYER_BOUNDARY (shell->layer_boundary),
+ gimp_image_get_active_layer (gimp_display_get_image (shell->display)));
+
+ gimp_canvas_canvas_boundary_set_image (GIMP_CANVAS_CANVAS_BOUNDARY (shell->canvas_boundary),
+ gimp_display_get_image (shell->display));
+
+ if (window && gimp_image_window_get_active_shell (window) == shell)
+ {
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_ui_manager_update (manager, shell->display);
+ }
+
+ context = gimp_get_user_context (shell->display->gimp);
+
+ if (shell->display == gimp_context_get_display (context))
+ {
+ gimp_ui_manager_update (shell->popup_manager, shell->display);
+ }
+ }
+}
+
+/**
+ * gimp_display_shell_pause:
+ * @shell: a display shell
+ *
+ * This function increments the pause count or the display shell.
+ * If it was zero coming in, then the function pauses the active tool,
+ * so that operations on the display can take place without corrupting
+ * anything that the tool has drawn. It "undraws" the current tool
+ * drawing, and must be followed by gimp_display_shell_resume() after
+ * the operation in question is completed.
+ **/
+void
+gimp_display_shell_pause (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ shell->paused_count++;
+
+ if (shell->paused_count == 1)
+ {
+ /* pause the currently active tool */
+ tool_manager_control_active (shell->display->gimp,
+ GIMP_TOOL_ACTION_PAUSE,
+ shell->display);
+ }
+}
+
+/**
+ * gimp_display_shell_resume:
+ * @shell: a display shell
+ *
+ * This function decrements the pause count for the display shell.
+ * If this brings it to zero, then the current tool is resumed.
+ * It is an error to call this function without having previously
+ * called gimp_display_shell_pause().
+ **/
+void
+gimp_display_shell_resume (GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (shell->paused_count > 0);
+
+ shell->paused_count--;
+
+ if (shell->paused_count == 0)
+ {
+ /* start the currently active tool */
+ tool_manager_control_active (shell->display->gimp,
+ GIMP_TOOL_ACTION_RESUME,
+ shell->display);
+ }
+}
+
+/**
+ * gimp_display_shell_set_highlight:
+ * @shell: a #GimpDisplayShell
+ * @highlight: a rectangle in image coordinates that should be brought out
+ * @opacity: how much to hide the unselected area
+ *
+ * This function sets an area of the image that should be
+ * accentuated. The actual implementation is to dim all pixels outside
+ * this rectangle. Passing %NULL for @highlight unsets the rectangle.
+ **/
+void
+gimp_display_shell_set_highlight (GimpDisplayShell *shell,
+ const GdkRectangle *highlight,
+ gdouble opacity)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (highlight)
+ {
+ gimp_canvas_item_begin_change (shell->passe_partout);
+
+ gimp_canvas_rectangle_set (shell->passe_partout,
+ highlight->x,
+ highlight->y,
+ highlight->width,
+ highlight->height);
+ g_object_set (shell->passe_partout, "opacity", opacity, NULL);
+
+ gimp_canvas_item_set_visible (shell->passe_partout, TRUE);
+
+ gimp_canvas_item_end_change (shell->passe_partout);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (shell->passe_partout, FALSE);
+ }
+}
+
+/**
+ * gimp_display_shell_set_mask:
+ * @shell: a #GimpDisplayShell
+ * @mask: a #GimpDrawable (1 byte per pixel)
+ * @color: the color to use for drawing the mask
+ * @inverted: #TRUE if the mask should be drawn inverted
+ *
+ * Previews a mask originating at offset_x, offset_x. Depending on
+ * @inverted, pixels that are selected or not selected are tinted with
+ * the given color.
+ **/
+void
+gimp_display_shell_set_mask (GimpDisplayShell *shell,
+ GeglBuffer *mask,
+ gint offset_x,
+ gint offset_y,
+ const GimpRGB *color,
+ gboolean inverted)
+{
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (mask == NULL || GEGL_IS_BUFFER (mask));
+ g_return_if_fail (mask == NULL || color != NULL);
+
+ if (mask)
+ g_object_ref (mask);
+
+ if (shell->mask)
+ g_object_unref (shell->mask);
+
+ shell->mask = mask;
+
+ shell->mask_offset_x = offset_x;
+ shell->mask_offset_y = offset_y;
+
+ if (mask)
+ shell->mask_color = *color;
+
+ shell->mask_inverted = inverted;
+
+ gimp_display_shell_expose_full (shell);
+}
diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h
new file mode 100644
index 0000000..1da9d8f
--- /dev/null
+++ b/app/display/gimpdisplayshell.h
@@ -0,0 +1,340 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_SHELL_H__
+#define __GIMP_DISPLAY_SHELL_H__
+
+
+/* Apply to a float the same rounding mode used in the renderer */
+#define PROJ_ROUND(coord) ((gint) RINT (coord))
+#define PROJ_ROUND64(coord) ((gint64) RINT (coord))
+
+/* scale values */
+#define SCALEX(s,x) PROJ_ROUND ((x) * (s)->scale_x)
+#define SCALEY(s,y) PROJ_ROUND ((y) * (s)->scale_y)
+
+/* unscale values */
+#define UNSCALEX(s,x) ((gint) ((x) / (s)->scale_x))
+#define UNSCALEY(s,y) ((gint) ((y) / (s)->scale_y))
+/* (and float-returning versions) */
+#define FUNSCALEX(s,x) ((x) / (s)->scale_x)
+#define FUNSCALEY(s,y) ((y) / (s)->scale_y)
+
+
+#define GIMP_TYPE_DISPLAY_SHELL (gimp_display_shell_get_type ())
+#define GIMP_DISPLAY_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DISPLAY_SHELL, GimpDisplayShell))
+#define GIMP_DISPLAY_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DISPLAY_SHELL, GimpDisplayShellClass))
+#define GIMP_IS_DISPLAY_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DISPLAY_SHELL))
+#define GIMP_IS_DISPLAY_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DISPLAY_SHELL))
+#define GIMP_DISPLAY_SHELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DISPLAY_SHELL, GimpDisplayShellClass))
+
+
+typedef struct _GimpDisplayShellClass GimpDisplayShellClass;
+
+struct _GimpDisplayShell
+{
+ GtkEventBox parent_instance;
+
+ GimpDisplay *display;
+
+ GimpUIManager *popup_manager;
+ GdkScreen *initial_screen;
+ gint initial_monitor;
+
+ GimpDisplayOptions *options;
+ GimpDisplayOptions *fullscreen_options;
+ GimpDisplayOptions *no_image_options;
+
+ GimpUnit unit;
+
+ gint offset_x; /* offset of display image */
+ gint offset_y;
+
+ gdouble scale_x; /* horizontal scale factor */
+ gdouble scale_y; /* vertical scale factor */
+
+ gboolean flip_horizontally;
+ gboolean flip_vertically;
+ gdouble rotate_angle;
+ cairo_matrix_t *rotate_transform;
+ cairo_matrix_t *rotate_untransform;
+
+ gdouble monitor_xres;
+ gdouble monitor_yres;
+ gboolean dot_for_dot; /* ignore monitor resolution */
+
+ GimpZoomModel *zoom;
+
+ gdouble last_scale; /* scale used when reverting zoom */
+ guint last_scale_time; /* time when last_scale was set */
+ gint last_offset_x; /* offsets used when reverting zoom */
+ gint last_offset_y;
+
+ gdouble other_scale; /* scale factor entered in Zoom->Other*/
+
+ gint disp_width; /* width of drawing area */
+ gint disp_height; /* height of drawing area */
+
+ gboolean proximity; /* is a device in proximity */
+
+ gboolean show_image; /* whether to show the image */
+
+ gboolean show_all; /* show the entire image */
+
+ Selection *selection; /* Selection (marching ants) */
+
+ GList *children;
+
+ GtkWidget *canvas; /* GimpCanvas widget */
+
+ GtkAdjustment *hsbdata; /* adjustments */
+ GtkAdjustment *vsbdata;
+ GtkWidget *hsb; /* scroll bars */
+ GtkWidget *vsb;
+
+ GtkWidget *hrule; /* rulers */
+ GtkWidget *vrule;
+
+ GtkWidget *origin; /* NW: origin */
+ GtkWidget *quick_mask_button;/* SW: quick mask button */
+ GtkWidget *zoom_button; /* NE: zoom toggle button */
+ GtkWidget *nav_ebox; /* SE: navigation event box */
+
+ GtkWidget *statusbar; /* statusbar */
+
+ GimpCanvasItem *canvas_item; /* items drawn on the canvas */
+ GimpCanvasItem *unrotated_item; /* unrotated items for e.g. cursor */
+ GimpCanvasItem *passe_partout; /* item for the highlight */
+ GimpCanvasItem *preview_items; /* item for previews */
+ GimpCanvasItem *vectors; /* item proxy of vectors */
+ GimpCanvasItem *grid; /* item proxy of the grid */
+ GimpCanvasItem *guides; /* item proxies of guides */
+ GimpCanvasItem *sample_points; /* item proxies of sample points */
+ GimpCanvasItem *canvas_boundary; /* item for the cabvas boundary */
+ GimpCanvasItem *layer_boundary; /* item for the layer boundary */
+ GimpCanvasItem *tool_items; /* tools items, below the cursor */
+ GimpCanvasItem *cursor; /* item for the software cursor */
+
+ guint title_idle_id; /* title update idle ID */
+ gchar *title; /* current title */
+ gchar *status; /* current default statusbar content */
+
+ gint icon_size; /* size of the icon pixbuf */
+ gint icon_size_small; /* size of the icon's wilber pixbuf */
+ guint icon_idle_id; /* ID of the idle-function */
+ GdkPixbuf *icon; /* icon */
+
+ guint fill_idle_id; /* display_shell_fill() idle ID */
+
+ GimpHandedness cursor_handedness;/* Handedness for cursor display */
+ GimpCursorType current_cursor; /* Currently installed main cursor */
+ GimpToolCursorType tool_cursor; /* Current Tool cursor */
+ GimpCursorModifier cursor_modifier; /* Cursor modifier (plus, minus, ...) */
+
+ GimpCursorType override_cursor; /* Overriding cursor */
+ gboolean using_override_cursor;
+ gboolean draw_cursor; /* should we draw software cursor ? */
+
+ GtkWidget *close_dialog; /* close dialog */
+ GtkWidget *scale_dialog; /* scale (zoom) dialog */
+ GtkWidget *rotate_dialog; /* rotate dialog */
+ GtkWidget *nav_popup; /* navigation popup */
+
+ GimpColorConfig *color_config; /* color management settings */
+ gboolean color_config_set; /* settings changed from defaults */
+
+ GimpColorTransform *profile_transform;
+ GeglBuffer *profile_buffer; /* buffer for profile transform */
+ guchar *profile_data; /* profile_buffer's pixels */
+ gint profile_stride; /* profile_buffer's stride */
+
+ GimpColorDisplayStack *filter_stack; /* color display conversion stuff */
+ guint filter_idle_id;
+
+ GimpColorTransform *filter_transform;
+ const Babl *filter_format; /* filter_buffer's format */
+ GeglBuffer *filter_buffer; /* buffer for display filters */
+ guchar *filter_data; /* filter_buffer's pixels */
+ gint filter_stride; /* filter_buffer's stride */
+
+ GimpDisplayXfer *xfer; /* manages image buffer transfers */
+ cairo_surface_t *mask_surface; /* buffer for rendering the mask */
+ cairo_pattern_t *checkerboard; /* checkerboard pattern */
+
+ gint paused_count;
+
+ GimpTreeHandler *vectors_freeze_handler;
+ GimpTreeHandler *vectors_thaw_handler;
+ GimpTreeHandler *vectors_visible_handler;
+
+ gboolean zoom_on_resize;
+
+ gboolean size_allocate_from_configure_event;
+ gboolean size_allocate_center_image;
+
+ /* the state of gimp_display_shell_tool_events() */
+ gboolean pointer_grabbed;
+ guint32 pointer_grab_time;
+
+ gboolean keyboard_grabbed;
+ guint32 keyboard_grab_time;
+
+ gboolean inferior_ignore_mode;
+
+ /* Two states are possible when the shell is grabbed: it can be
+ * grabbed with space (or space+button1 which is the same state),
+ * then if space is released but button1 was still pressed, we wait
+ * for button1 to be released as well.
+ */
+ gboolean space_release_pending;
+ gboolean button1_release_pending;
+ const gchar *space_shaded_tool;
+
+ gboolean scrolling;
+ gint scroll_start_x;
+ gint scroll_start_y;
+ gint scroll_last_x;
+ gint scroll_last_y;
+ gboolean rotating;
+ gdouble rotate_drag_angle;
+ gboolean scaling;
+ gpointer scroll_info;
+ gboolean layer_picking;
+ GimpLayer *picked_layer;
+
+ GeglBuffer *mask;
+ gint mask_offset_x;
+ gint mask_offset_y;
+ GimpRGB mask_color;
+ gboolean mask_inverted;
+
+ GimpMotionBuffer *motion_buffer;
+
+ GQueue *zoom_focus_pointer_queue;
+
+ gboolean blink;
+ guint blink_timeout_id;
+};
+
+struct _GimpDisplayShellClass
+{
+ GtkEventBoxClass parent_class;
+
+ void (* scaled) (GimpDisplayShell *shell);
+ void (* scrolled) (GimpDisplayShell *shell);
+ void (* rotated) (GimpDisplayShell *shell);
+ void (* reconnect) (GimpDisplayShell *shell);
+};
+
+
+GType gimp_display_shell_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_display_shell_new (GimpDisplay *display,
+ GimpUnit unit,
+ gdouble scale,
+ GimpUIManager *popup_manager,
+ GdkScreen *screen,
+ gint monitor);
+
+void gimp_display_shell_add_overlay (GimpDisplayShell *shell,
+ GtkWidget *child,
+ gdouble image_x,
+ gdouble image_y,
+ GimpHandleAnchor anchor,
+ gint spacing_x,
+ gint spacing_y);
+void gimp_display_shell_move_overlay (GimpDisplayShell *shell,
+ GtkWidget *child,
+ gdouble image_x,
+ gdouble image_y,
+ GimpHandleAnchor anchor,
+ gint spacing_x,
+ gint spacing_y);
+
+GimpImageWindow * gimp_display_shell_get_window (GimpDisplayShell *shell);
+GimpStatusbar * gimp_display_shell_get_statusbar (GimpDisplayShell *shell);
+
+GimpColorConfig * gimp_display_shell_get_color_config
+ (GimpDisplayShell *shell);
+
+void gimp_display_shell_present (GimpDisplayShell *shell);
+
+void gimp_display_shell_reconnect (GimpDisplayShell *shell);
+
+void gimp_display_shell_empty (GimpDisplayShell *shell);
+void gimp_display_shell_fill (GimpDisplayShell *shell,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale);
+
+void gimp_display_shell_scaled (GimpDisplayShell *shell);
+void gimp_display_shell_scrolled (GimpDisplayShell *shell);
+void gimp_display_shell_rotated (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_unit (GimpDisplayShell *shell,
+ GimpUnit unit);
+GimpUnit gimp_display_shell_get_unit (GimpDisplayShell *shell);
+
+gboolean gimp_display_shell_snap_coords (GimpDisplayShell *shell,
+ GimpCoords *coords,
+ gint snap_offset_x,
+ gint snap_offset_y,
+ gint snap_width,
+ gint snap_height);
+
+gboolean gimp_display_shell_mask_bounds (GimpDisplayShell *shell,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+void gimp_display_shell_set_show_image
+ (GimpDisplayShell *shell,
+ gboolean show_image);
+
+void gimp_display_shell_set_show_all (GimpDisplayShell *shell,
+ gboolean show_all);
+
+GimpPickable * gimp_display_shell_get_pickable (GimpDisplayShell *shell);
+GimpPickable * gimp_display_shell_get_canvas_pickable
+ (GimpDisplayShell *shell);
+GeglRectangle gimp_display_shell_get_bounding_box
+ (GimpDisplayShell *shell);
+gboolean gimp_display_shell_get_infinite_canvas
+ (GimpDisplayShell *shell);
+
+void gimp_display_shell_update_priority_rect
+ (GimpDisplayShell *shell);
+
+void gimp_display_shell_flush (GimpDisplayShell *shell,
+ gboolean now);
+
+void gimp_display_shell_pause (GimpDisplayShell *shell);
+void gimp_display_shell_resume (GimpDisplayShell *shell);
+
+void gimp_display_shell_set_highlight (GimpDisplayShell *shell,
+ const GdkRectangle *highlight,
+ double opacity);
+void gimp_display_shell_set_mask (GimpDisplayShell *shell,
+ GeglBuffer *mask,
+ gint offset_x,
+ gint offset_y,
+ const GimpRGB *color,
+ gboolean inverted);
+
+
+#endif /* __GIMP_DISPLAY_SHELL_H__ */
diff --git a/app/display/gimpdisplayxfer.c b/app/display/gimpdisplayxfer.c
new file mode 100644
index 0000000..1dcd13c
--- /dev/null
+++ b/app/display/gimpdisplayxfer.c
@@ -0,0 +1,289 @@
+/* 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 <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "display-types.h"
+
+#include "gimpdisplayxfer.h"
+
+
+#define NUM_PAGES 2
+
+typedef struct _RTree RTree;
+typedef struct _RTreeNode RTreeNode;
+
+struct _RTreeNode
+{
+ RTreeNode *children[2];
+ RTreeNode *next;
+ gint x, y, w, h;
+};
+
+struct _RTree
+{
+ RTreeNode root;
+ RTreeNode *available;
+};
+
+struct _GimpDisplayXfer
+{
+ /* track subregions of render_surface for efficient uploads */
+ RTree rtree;
+ cairo_surface_t *render_surface[NUM_PAGES];
+ gint page;
+};
+
+
+gint GIMP_DISPLAY_RENDER_BUF_WIDTH = 256;
+gint GIMP_DISPLAY_RENDER_BUF_HEIGHT = 256;
+
+
+static RTreeNode *
+rtree_node_create (RTree *rtree,
+ RTreeNode **prev,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ RTreeNode *node;
+
+ gimp_assert (x >= 0 && x+w <= rtree->root.w);
+ gimp_assert (y >= 0 && y+h <= rtree->root.h);
+
+ if (w <= 0 || h <= 0)
+ return NULL;
+
+ node = g_slice_alloc (sizeof (*node));
+
+ node->children[0] = NULL;
+ node->children[1] = NULL;
+ node->x = x;
+ node->y = y;
+ node->w = w;
+ node->h = h;
+
+ node->next = *prev;
+ *prev = node;
+
+ return node;
+}
+
+static void
+rtree_node_destroy (RTree *rtree,
+ RTreeNode *node)
+{
+ gint i;
+
+ for (i = 0; i < 2; i++)
+ {
+ if (node->children[i])
+ rtree_node_destroy (rtree, node->children[i]);
+ }
+
+ g_slice_free (RTreeNode, node);
+}
+
+static RTreeNode *
+rtree_node_insert (RTree *rtree,
+ RTreeNode **prev,
+ RTreeNode *node,
+ gint w,
+ gint h)
+{
+ *prev = node->next;
+
+ if (((node->w - w) | (node->h - h)) > 1)
+ {
+ gint ww = node->w - w;
+ gint hh = node->h - h;
+
+ if (ww >= hh)
+ {
+ node->children[0] = rtree_node_create (rtree, prev,
+ node->x + w, node->y,
+ ww, node->h);
+ node->children[1] = rtree_node_create (rtree, prev,
+ node->x, node->y + h,
+ w, hh);
+ }
+ else
+ {
+ node->children[0] = rtree_node_create (rtree, prev,
+ node->x, node->y + h,
+ node->w, hh);
+ node->children[1] = rtree_node_create (rtree, prev,
+ node->x + w, node->y,
+ ww, h);
+ }
+ }
+
+ return node;
+}
+
+static RTreeNode *
+rtree_insert (RTree *rtree,
+ gint w,
+ gint h)
+{
+ RTreeNode *node, **prev;
+
+ for (prev = &rtree->available; (node = *prev); prev = &node->next)
+ if (node->w >= w && node->h >= h)
+ return rtree_node_insert (rtree, prev, node, w, h);
+
+ return NULL;
+}
+
+static void
+rtree_init (RTree *rtree,
+ gint w,
+ gint h)
+{
+ rtree->root.x = 0;
+ rtree->root.y = 0;
+ rtree->root.w = w;
+ rtree->root.h = h;
+ rtree->root.children[0] = NULL;
+ rtree->root.children[1] = NULL;
+ rtree->root.next = NULL;
+ rtree->available = &rtree->root;
+}
+
+static void
+rtree_reset (RTree *rtree)
+{
+ gint i;
+
+ for (i = 0; i < 2; i++)
+ {
+ if (rtree->root.children[i] == NULL)
+ continue;
+
+ rtree_node_destroy (rtree, rtree->root.children[i]);
+ rtree->root.children[i] = NULL;
+ }
+
+ rtree->root.next = NULL;
+ rtree->available = &rtree->root;
+}
+
+static void
+xfer_destroy (void *data)
+{
+ GimpDisplayXfer *xfer = data;
+ gint i;
+
+ for (i = 0; i < NUM_PAGES; i++)
+ cairo_surface_destroy (xfer->render_surface[i]);
+
+ rtree_reset (&xfer->rtree);
+ g_free (xfer);
+}
+
+GimpDisplayXfer *
+gimp_display_xfer_realize (GtkWidget *widget)
+{
+ GdkScreen *screen;
+ GimpDisplayXfer *xfer;
+ const gchar *env;
+
+ env = g_getenv ("GIMP_DISPLAY_RENDER_BUF_SIZE");
+ if (env)
+ {
+ gint width = atoi (env);
+ gint height = width;
+
+ env = strchr (env, 'x');
+ if (env)
+ height = atoi (env + 1);
+
+ if (width > 0 && width <= 8192 &&
+ height > 0 && height <= 8192)
+ {
+ GIMP_DISPLAY_RENDER_BUF_WIDTH = width;
+ GIMP_DISPLAY_RENDER_BUF_HEIGHT = height;
+ }
+ }
+
+ screen = gtk_widget_get_screen (widget);
+ xfer = g_object_get_data (G_OBJECT (screen), "gimp-display-xfer");
+
+ if (xfer == NULL)
+ {
+ cairo_t *cr;
+ gint w = GIMP_DISPLAY_RENDER_BUF_WIDTH;
+ gint h = GIMP_DISPLAY_RENDER_BUF_HEIGHT;
+ int n;
+
+ xfer = g_new (GimpDisplayXfer, 1);
+ rtree_init (&xfer->rtree, w, h);
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+ for (n = 0; n < NUM_PAGES; n++)
+ {
+ xfer->render_surface[n] =
+ cairo_surface_create_similar_image (cairo_get_target (cr),
+ CAIRO_FORMAT_ARGB32, w, h);
+ cairo_surface_mark_dirty (xfer->render_surface[n]);
+ }
+ cairo_destroy (cr);
+ xfer->page = 0;
+
+ g_object_set_data_full (G_OBJECT (screen),
+ "gimp-display-xfer",
+ xfer, xfer_destroy);
+ }
+
+ return xfer;
+}
+
+cairo_surface_t *
+gimp_display_xfer_get_surface (GimpDisplayXfer *xfer,
+ gint w,
+ gint h,
+ gint *src_x,
+ gint *src_y)
+{
+ RTreeNode *node;
+
+ gimp_assert (w <= GIMP_DISPLAY_RENDER_BUF_WIDTH &&
+ h <= GIMP_DISPLAY_RENDER_BUF_HEIGHT);
+
+ node = rtree_insert (&xfer->rtree, w, h);
+ if (node == NULL)
+ {
+ xfer->page = (xfer->page + 1) % NUM_PAGES;
+ cairo_surface_flush (xfer->render_surface[xfer->page]);
+ rtree_reset (&xfer->rtree);
+ cairo_surface_mark_dirty (xfer->render_surface[xfer->page]); /* XXX */
+ node = rtree_insert (&xfer->rtree, w, h);
+ gimp_assert (node != NULL);
+ }
+
+ *src_x = node->x;
+ *src_y = node->y;
+
+ return xfer->render_surface[xfer->page];
+}
diff --git a/app/display/gimpdisplayxfer.h b/app/display/gimpdisplayxfer.h
new file mode 100644
index 0000000..20656b9
--- /dev/null
+++ b/app/display/gimpdisplayxfer.h
@@ -0,0 +1,37 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_DISPLAY_XFER_H__
+#define __GIMP_DISPLAY_XFER_H__
+
+
+extern gint GIMP_DISPLAY_RENDER_BUF_WIDTH;
+extern gint GIMP_DISPLAY_RENDER_BUF_HEIGHT;
+
+#define GIMP_DISPLAY_RENDER_MAX_SCALE 4.0
+
+
+GimpDisplayXfer * gimp_display_xfer_realize (GtkWidget *widget);
+
+cairo_surface_t * gimp_display_xfer_get_surface (GimpDisplayXfer *xfer,
+ gint w,
+ gint h,
+ gint *src_x,
+ gint *src_y);
+
+
+#endif /* __GIMP_DISPLAY_XFER_H__ */
diff --git a/app/display/gimpimagewindow.c b/app/display/gimpimagewindow.c
new file mode 100644
index 0000000..fd5c501
--- /dev/null
+++ b/app/display/gimpimagewindow.c
@@ -0,0 +1,2428 @@
+/* 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 <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#ifdef G_OS_WIN32
+#include <windef.h>
+#include <winbase.h>
+#include <windows.h>
+#endif
+
+#ifdef GDK_WINDOWING_QUARTZ
+#import <AppKit/AppKit.h>
+#include <gdk/gdkquartz.h>
+#endif /* !GDK_WINDOWING_QUARTZ */
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpprogress.h"
+#include "core/gimpcontainer.h"
+
+#include "widgets/gimpactiongroup.h"
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpdock.h"
+#include "widgets/gimpdockbook.h"
+#include "widgets/gimpdockcolumns.h"
+#include "widgets/gimpdockcontainer.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpmenufactory.h"
+#include "widgets/gimpsessioninfo.h"
+#include "widgets/gimpsessioninfo-aux.h"
+#include "widgets/gimpsessionmanaged.h"
+#include "widgets/gimpsessioninfo-dock.h"
+#include "widgets/gimptoolbox.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpview.h"
+#include "widgets/gimpviewrenderer.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplay-foreach.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-close.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-tool-events.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpimagewindow.h"
+#include "gimpstatusbar.h"
+
+#include "gimp-log.h"
+#include "gimp-priorities.h"
+
+#include "gimp-intl.h"
+
+
+#define GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID "gimp-empty-image-window"
+#define GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID "gimp-single-image-window"
+
+/* The width of the left and right dock areas */
+#define GIMP_IMAGE_WINDOW_LEFT_DOCKS_WIDTH "left-docks-width"
+#define GIMP_IMAGE_WINDOW_RIGHT_DOCKS_WIDTH "right-docks-width"
+
+/* deprecated property: GtkPaned position of the right docks area */
+#define GIMP_IMAGE_WINDOW_RIGHT_DOCKS_POS "right-docks-position"
+
+/* Whether the window's maximized or not */
+#define GIMP_IMAGE_WINDOW_MAXIMIZED "maximized"
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
+#define NSWindowCollectionBehaviorFullScreenAuxiliary (1 << 8)
+#endif
+
+
+enum
+{
+ PROP_0,
+ PROP_GIMP,
+ PROP_DIALOG_FACTORY,
+ PROP_INITIAL_SCREEN,
+ PROP_INITIAL_MONITOR
+};
+
+
+typedef struct _GimpImageWindowPrivate GimpImageWindowPrivate;
+
+struct _GimpImageWindowPrivate
+{
+ Gimp *gimp;
+ GimpUIManager *menubar_manager;
+ GimpDialogFactory *dialog_factory;
+
+ GList *shells;
+ GimpDisplayShell *active_shell;
+
+ GtkWidget *main_vbox;
+ GtkWidget *menubar;
+ GtkWidget *hbox;
+ GtkWidget *left_hpane;
+ GtkWidget *left_docks;
+ GtkWidget *right_hpane;
+ GtkWidget *notebook;
+ GtkWidget *right_docks;
+
+ GdkWindowState window_state;
+
+ const gchar *entry_id;
+
+ GdkScreen *initial_screen;
+ gint initial_monitor;
+
+ gint suspend_keep_pos;
+
+ gint update_ui_manager_idle_id;
+};
+
+typedef struct
+{
+ gint canvas_x;
+ gint canvas_y;
+ gint window_x;
+ gint window_y;
+} PosCorrectionData;
+
+
+#define GIMP_IMAGE_WINDOW_GET_PRIVATE(window) \
+ ((GimpImageWindowPrivate *) gimp_image_window_get_instance_private ((GimpImageWindow *) (window)))
+
+
+/* local function prototypes */
+
+static void gimp_image_window_dock_container_iface_init
+ (GimpDockContainerInterface
+ *iface);
+static void gimp_image_window_session_managed_iface_init
+ (GimpSessionManagedInterface
+ *iface);
+static void gimp_image_window_constructed (GObject *object);
+static void gimp_image_window_dispose (GObject *object);
+static void gimp_image_window_finalize (GObject *object);
+static void gimp_image_window_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_image_window_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_image_window_map (GtkWidget *widget);
+static gboolean gimp_image_window_delete_event (GtkWidget *widget,
+ GdkEventAny *event);
+static gboolean gimp_image_window_configure_event (GtkWidget *widget,
+ GdkEventConfigure *event);
+static gboolean gimp_image_window_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event);
+static void gimp_image_window_style_set (GtkWidget *widget,
+ GtkStyle *prev_style);
+
+static void gimp_image_window_monitor_changed (GimpWindow *window,
+ GdkScreen *screen,
+ gint monitor);
+
+static GList * gimp_image_window_get_docks (GimpDockContainer *dock_container);
+static GimpDialogFactory *
+ gimp_image_window_dock_container_get_dialog_factory
+ (GimpDockContainer *dock_container);
+static GimpUIManager *
+ gimp_image_window_dock_container_get_ui_manager
+ (GimpDockContainer *dock_container);
+static void gimp_image_window_add_dock (GimpDockContainer *dock_container,
+ GimpDock *dock,
+ GimpSessionInfoDock *dock_info);
+static GimpAlignmentType
+ gimp_image_window_get_dock_side (GimpDockContainer *dock_container,
+ GimpDock *dock);
+static GList * gimp_image_window_get_aux_info (GimpSessionManaged *session_managed);
+static void gimp_image_window_set_aux_info (GimpSessionManaged *session_managed,
+ GList *aux_info);
+
+static void gimp_image_window_config_notify (GimpImageWindow *window,
+ GParamSpec *pspec,
+ GimpGuiConfig *config);
+static void gimp_image_window_session_clear (GimpImageWindow *window);
+static void gimp_image_window_session_apply (GimpImageWindow *window,
+ const gchar *entry_id,
+ GdkScreen *screen,
+ gint monitor);
+static void gimp_image_window_session_update (GimpImageWindow *window,
+ GimpDisplay *new_display,
+ const gchar *new_entry_id,
+ GdkScreen *screen,
+ gint monitor);
+static const gchar *
+ gimp_image_window_config_to_entry_id (GimpGuiConfig *config);
+static void gimp_image_window_show_tooltip (GimpUIManager *manager,
+ const gchar *tooltip,
+ GimpImageWindow *window);
+static void gimp_image_window_hide_tooltip (GimpUIManager *manager,
+ GimpImageWindow *window);
+static gboolean gimp_image_window_update_ui_manager_idle
+ (GimpImageWindow *window);
+static void gimp_image_window_update_ui_manager (GimpImageWindow *window);
+
+static void gimp_image_window_shell_size_allocate (GimpDisplayShell *shell,
+ GtkAllocation *allocation,
+ PosCorrectionData *data);
+static gboolean gimp_image_window_shell_events (GtkWidget *widget,
+ GdkEvent *event,
+ GimpImageWindow *window);
+
+static void gimp_image_window_switch_page (GtkNotebook *notebook,
+ gpointer page,
+ gint page_num,
+ GimpImageWindow *window);
+static void gimp_image_window_page_removed (GtkNotebook *notebook,
+ GtkWidget *widget,
+ gint page_num,
+ GimpImageWindow *window);
+static void gimp_image_window_page_reordered (GtkNotebook *notebook,
+ GtkWidget *widget,
+ gint page_num,
+ GimpImageWindow *window);
+static void gimp_image_window_disconnect_from_active_shell
+ (GimpImageWindow *window);
+
+static void gimp_image_window_image_notify (GimpDisplay *display,
+ const GParamSpec *pspec,
+ GimpImageWindow *window);
+static void gimp_image_window_shell_scaled (GimpDisplayShell *shell,
+ GimpImageWindow *window);
+static void gimp_image_window_shell_rotated (GimpDisplayShell *shell,
+ GimpImageWindow *window);
+static void gimp_image_window_shell_title_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpImageWindow *window);
+static void gimp_image_window_shell_icon_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpImageWindow *window);
+static GtkWidget *
+ gimp_image_window_create_tab_label (GimpImageWindow *window,
+ GimpDisplayShell *shell);
+static void gimp_image_window_update_tab_labels (GimpImageWindow *window);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpImageWindow, gimp_image_window, GIMP_TYPE_WINDOW,
+ G_ADD_PRIVATE (GimpImageWindow)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCK_CONTAINER,
+ gimp_image_window_dock_container_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_SESSION_MANAGED,
+ gimp_image_window_session_managed_iface_init))
+
+#define parent_class gimp_image_window_parent_class
+
+
+static const gchar image_window_rc_style[] =
+ "style \"fullscreen-menubar-style\"\n"
+ "{\n"
+ " GtkMenuBar::shadow-type = none\n"
+ " GtkMenuBar::internal-padding = 0\n"
+ "}\n"
+ "widget \"*.gimp-menubar-fullscreen\" style \"fullscreen-menubar-style\"\n";
+
+static void
+gimp_image_window_class_init (GimpImageWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GimpWindowClass *window_class = GIMP_WINDOW_CLASS (klass);
+
+ object_class->constructed = gimp_image_window_constructed;
+ object_class->dispose = gimp_image_window_dispose;
+ object_class->finalize = gimp_image_window_finalize;
+ object_class->set_property = gimp_image_window_set_property;
+ object_class->get_property = gimp_image_window_get_property;
+
+ widget_class->map = gimp_image_window_map;
+ widget_class->delete_event = gimp_image_window_delete_event;
+ widget_class->configure_event = gimp_image_window_configure_event;
+ widget_class->window_state_event = gimp_image_window_window_state_event;
+ widget_class->style_set = gimp_image_window_style_set;
+
+ window_class->monitor_changed = gimp_image_window_monitor_changed;
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp",
+ NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_DIALOG_FACTORY,
+ g_param_spec_object ("dialog-factory",
+ NULL, NULL,
+ GIMP_TYPE_DIALOG_FACTORY,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_INITIAL_SCREEN,
+ g_param_spec_object ("initial-screen",
+ NULL, NULL,
+ GDK_TYPE_SCREEN,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class, PROP_INITIAL_MONITOR,
+ g_param_spec_int ("initial-monitor",
+ NULL, NULL,
+ 0, 16, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ gtk_rc_parse_string (image_window_rc_style);
+}
+
+static void
+gimp_image_window_init (GimpImageWindow *window)
+{
+ static gint role_serial = 1;
+ gchar *role;
+
+ role = g_strdup_printf ("gimp-image-window-%d", role_serial++);
+ gtk_window_set_role (GTK_WINDOW (window), role);
+ g_free (role);
+
+ gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
+}
+
+static void
+gimp_image_window_dock_container_iface_init (GimpDockContainerInterface *iface)
+{
+ iface->get_docks = gimp_image_window_get_docks;
+ iface->get_dialog_factory = gimp_image_window_dock_container_get_dialog_factory;
+ iface->get_ui_manager = gimp_image_window_dock_container_get_ui_manager;
+ iface->add_dock = gimp_image_window_add_dock;
+ iface->get_dock_side = gimp_image_window_get_dock_side;
+}
+
+static void
+gimp_image_window_session_managed_iface_init (GimpSessionManagedInterface *iface)
+{
+ iface->get_aux_info = gimp_image_window_get_aux_info;
+ iface->set_aux_info = gimp_image_window_set_aux_info;
+}
+
+static void
+gimp_image_window_constructed (GObject *object)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (object);
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpMenuFactory *menu_factory;
+ GimpGuiConfig *config;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_GIMP (private->gimp));
+ gimp_assert (GIMP_IS_DIALOG_FACTORY (private->dialog_factory));
+
+ menu_factory = gimp_dialog_factory_get_menu_factory (private->dialog_factory);
+
+ private->menubar_manager = gimp_menu_factory_manager_new (menu_factory,
+ "<Image>",
+ window,
+ FALSE);
+
+ g_signal_connect_object (private->dialog_factory, "dock-window-added",
+ G_CALLBACK (gimp_image_window_update_ui_manager),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (private->dialog_factory, "dock-window-removed",
+ G_CALLBACK (gimp_image_window_update_ui_manager),
+ window, G_CONNECT_SWAPPED);
+
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gimp_ui_manager_get_accel_group (private->menubar_manager));
+
+ g_signal_connect (private->menubar_manager, "show-tooltip",
+ G_CALLBACK (gimp_image_window_show_tooltip),
+ window);
+ g_signal_connect (private->menubar_manager, "hide-tooltip",
+ G_CALLBACK (gimp_image_window_hide_tooltip),
+ window);
+
+ config = GIMP_GUI_CONFIG (private->gimp->config);
+
+ /* Create the window toplevel container */
+ private->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (window), private->main_vbox);
+ gtk_widget_show (private->main_vbox);
+
+ /* Create the menubar */
+#ifndef GDK_WINDOWING_QUARTZ
+ private->menubar = gimp_ui_manager_get_widget (private->menubar_manager,
+ "/image-menubar");
+#endif /* !GDK_WINDOWING_QUARTZ */
+ if (private->menubar)
+ {
+ gtk_box_pack_start (GTK_BOX (private->main_vbox),
+ private->menubar, FALSE, FALSE, 0);
+
+ /* make sure we can activate accels even if the menubar is invisible
+ * (see https://bugzilla.gnome.org/show_bug.cgi?id=137151)
+ */
+ g_signal_connect (private->menubar, "can-activate-accel",
+ G_CALLBACK (gtk_true),
+ NULL);
+
+ /* active display callback */
+ g_signal_connect (private->menubar, "button-press-event",
+ G_CALLBACK (gimp_image_window_shell_events),
+ window);
+ g_signal_connect (private->menubar, "button-release-event",
+ G_CALLBACK (gimp_image_window_shell_events),
+ window);
+ g_signal_connect (private->menubar, "key-press-event",
+ G_CALLBACK (gimp_image_window_shell_events),
+ window);
+ }
+
+ /* Create the hbox that contains docks and images */
+ private->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (private->main_vbox), private->hbox,
+ TRUE, TRUE, 0);
+ gtk_widget_show (private->hbox);
+
+ /* Create the left pane */
+ private->left_hpane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_pack_start (GTK_BOX (private->hbox), private->left_hpane,
+ TRUE, TRUE, 0);
+ gtk_widget_show (private->left_hpane);
+
+ /* Create the left dock columns widget */
+ private->left_docks =
+ gimp_dock_columns_new (gimp_get_user_context (private->gimp),
+ private->dialog_factory,
+ private->menubar_manager);
+ gtk_paned_pack1 (GTK_PANED (private->left_hpane), private->left_docks,
+ FALSE, FALSE);
+ gtk_widget_set_visible (private->left_docks, config->single_window_mode);
+
+ /* Create the right pane */
+ private->right_hpane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_paned_pack2 (GTK_PANED (private->left_hpane), private->right_hpane,
+ TRUE, FALSE);
+ gtk_widget_show (private->right_hpane);
+
+ /* Create notebook that contains images */
+ private->notebook = gtk_notebook_new ();
+ gtk_notebook_set_scrollable (GTK_NOTEBOOK (private->notebook), TRUE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (private->notebook), FALSE);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook), FALSE);
+ gtk_notebook_set_tab_pos (GTK_NOTEBOOK (private->notebook), GTK_POS_TOP);
+
+ gtk_paned_pack1 (GTK_PANED (private->right_hpane), private->notebook,
+ TRUE, TRUE);
+
+ g_signal_connect (private->notebook, "switch-page",
+ G_CALLBACK (gimp_image_window_switch_page),
+ window);
+ g_signal_connect (private->notebook, "page-removed",
+ G_CALLBACK (gimp_image_window_page_removed),
+ window);
+ g_signal_connect (private->notebook, "page-reordered",
+ G_CALLBACK (gimp_image_window_page_reordered),
+ window);
+ gtk_widget_show (private->notebook);
+
+ /* Create the right dock columns widget */
+ private->right_docks =
+ gimp_dock_columns_new (gimp_get_user_context (private->gimp),
+ private->dialog_factory,
+ private->menubar_manager);
+ gtk_paned_pack2 (GTK_PANED (private->right_hpane), private->right_docks,
+ FALSE, FALSE);
+ gtk_widget_set_visible (private->right_docks, config->single_window_mode);
+
+ g_signal_connect_object (config, "notify::single-window-mode",
+ G_CALLBACK (gimp_image_window_config_notify),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (config, "notify::show-tabs",
+ G_CALLBACK (gimp_image_window_config_notify),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (config, "notify::hide-docks",
+ G_CALLBACK (gimp_image_window_config_notify),
+ window, G_CONNECT_SWAPPED);
+ g_signal_connect_object (config, "notify::tabs-position",
+ G_CALLBACK (gimp_image_window_config_notify),
+ window, G_CONNECT_SWAPPED);
+
+ gimp_image_window_session_update (window,
+ NULL /*new_display*/,
+ gimp_image_window_config_to_entry_id (config),
+ private->initial_screen,
+ private->initial_monitor);
+}
+
+static void
+gimp_image_window_dispose (GObject *object)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (object);
+
+ if (private->dialog_factory)
+ {
+ g_signal_handlers_disconnect_by_func (private->dialog_factory,
+ gimp_image_window_update_ui_manager,
+ object);
+ private->dialog_factory = NULL;
+ }
+
+ g_clear_object (&private->menubar_manager);
+
+ if (private->update_ui_manager_idle_id)
+ {
+ g_source_remove (private->update_ui_manager_idle_id);
+ private->update_ui_manager_idle_id = 0;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_image_window_finalize (GObject *object)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (object);
+
+ if (private->shells)
+ {
+ g_list_free (private->shells);
+ private->shells = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_image_window_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (object);
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ private->gimp = g_value_get_object (value);
+ break;
+ case PROP_DIALOG_FACTORY:
+ private->dialog_factory = g_value_get_object (value);
+ break;
+ case PROP_INITIAL_SCREEN:
+ private->initial_screen = g_value_get_object (value);
+ break;
+ case PROP_INITIAL_MONITOR:
+ private->initial_monitor = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_image_window_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (object);
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ g_value_set_object (value, private->gimp);
+ break;
+ case PROP_DIALOG_FACTORY:
+ g_value_set_object (value, private->dialog_factory);
+ break;
+ case PROP_INITIAL_SCREEN:
+ g_value_set_object (value, private->initial_screen);
+ break;
+ case PROP_INITIAL_MONITOR:
+ g_value_set_int (value, private->initial_monitor);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_image_window_map (GtkWidget *widget)
+{
+#ifdef GDK_WINDOWING_QUARTZ
+ GdkWindow *gdk_window;
+ NSWindow *ns_window;
+#endif /* !GDK_WINDOWING_QUARTZ */
+
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+#ifdef GDK_WINDOWING_QUARTZ
+ gdk_window = gtk_widget_get_window (GTK_WIDGET (widget));
+ ns_window = gdk_quartz_window_get_nswindow (gdk_window);
+
+ /* Disable the new-style full screen mode. For now only the "old-style"
+ * full screen mode, via the "View" menu, is supported. In the future, and
+ * as soon as GTK+ has proper support for this, we will migrate to the
+ * new-style full screen mode.
+ */
+ ns_window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
+#endif /* !GDK_WINDOWING_QUARTZ */
+}
+
+static gboolean
+gimp_image_window_delete_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget);
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (private->gimp->config);
+
+ if (config->single_window_mode)
+ gimp_ui_manager_activate_action (gimp_image_window_get_ui_manager (window),
+ "file", "file-quit");
+ else if (shell)
+ gimp_display_shell_close (shell, FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_image_window_configure_event (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget);
+ GtkAllocation allocation;
+ gint current_width;
+ gint current_height;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ /* Grab the size before we run the parent implementation */
+ current_width = allocation.width;
+ current_height = allocation.height;
+
+ /* Run the parent implementation */
+ if (GTK_WIDGET_CLASS (parent_class)->configure_event)
+ GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
+
+ /* If the window size has changed, make sure additoinal logic is run
+ * in the display shell's size-allocate
+ */
+ if (event->width != current_width ||
+ event->height != current_height)
+ {
+ /* FIXME multiple shells */
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+
+ if (shell && gimp_display_get_image (shell->display))
+ shell->size_allocate_from_configure_event = TRUE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_image_window_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget);
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+
+ if (! shell)
+ return FALSE;
+
+ private->window_state = event->new_window_state;
+
+ if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
+ {
+ gboolean fullscreen = gimp_image_window_get_fullscreen (window);
+
+ GIMP_LOG (WM, "Image window '%s' [%p] set fullscreen %s",
+ gtk_window_get_title (GTK_WINDOW (widget)),
+ widget,
+ fullscreen ? "TRUE" : "FALSE");
+
+ if (private->menubar)
+ gtk_widget_set_name (private->menubar,
+ fullscreen ? "gimp-menubar-fullscreen" : NULL);
+
+ gimp_image_window_suspend_keep_pos (window);
+ gimp_display_shell_appearance_update (shell);
+ gimp_image_window_resume_keep_pos (window);
+ }
+
+ if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED)
+ {
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell);
+ gboolean iconified = gimp_image_window_is_iconified (window);
+
+ GIMP_LOG (WM, "Image window '%s' [%p] set %s",
+ gtk_window_get_title (GTK_WINDOW (widget)),
+ widget,
+ iconified ? "iconified" : "uniconified");
+
+ if (iconified)
+ {
+ if (gimp_displays_get_num_visible (private->gimp) == 0)
+ {
+ GIMP_LOG (WM, "No displays visible any longer");
+
+ gimp_dialog_factory_hide_with_display (private->dialog_factory);
+ }
+ }
+ else
+ {
+ gimp_dialog_factory_show_with_display (private->dialog_factory);
+ }
+
+ if (gimp_progress_is_active (GIMP_PROGRESS (statusbar)))
+ {
+ if (iconified)
+ gimp_statusbar_override_window_title (statusbar);
+ else
+ gtk_window_set_title (GTK_WINDOW (window), shell->title);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_image_window_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget);
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+ GimpStatusbar *statusbar = NULL;
+ GtkRequisition requisition = { 0, };
+ GdkGeometry geometry = { 0, };
+ GdkWindowHints geometry_mask = 0;
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
+
+ if (! shell)
+ return;
+
+ statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gtk_widget_size_request (GTK_WIDGET (statusbar), &requisition);
+
+ geometry.min_height = 23;
+
+ geometry.min_width = requisition.width;
+ geometry.min_height += requisition.height;
+
+ if (private->menubar)
+ {
+ gtk_widget_size_request (private->menubar, &requisition);
+
+ geometry.min_height += requisition.height;
+ }
+
+ geometry_mask = GDK_HINT_MIN_SIZE;
+
+ /* Only set user pos on the empty display because it gets a pos
+ * set by gimp. All other displays should be placed by the window
+ * manager. See https://bugzilla.gnome.org/show_bug.cgi?id=559580
+ */
+ if (! gimp_display_get_image (shell->display))
+ geometry_mask |= GDK_HINT_USER_POS;
+
+ gtk_window_set_geometry_hints (GTK_WINDOW (widget), NULL,
+ &geometry, geometry_mask);
+
+ gimp_dialog_factory_set_has_min_size (GTK_WINDOW (widget), TRUE);
+}
+
+static void
+gimp_image_window_monitor_changed (GimpWindow *window,
+ GdkScreen *screen,
+ gint monitor)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GList *list;
+
+ for (list = private->shells; list; list = g_list_next (list))
+ {
+ /* hack, this should live here, and screen_changed call
+ * monitor_changed
+ */
+ g_signal_emit_by_name (list->data, "screen-changed",
+ gtk_widget_get_screen (list->data));
+
+ /* make it fetch the new monitor's resolution */
+ gimp_display_shell_scale_update (GIMP_DISPLAY_SHELL (list->data));
+
+ /* make it fetch the right monitor profile */
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (list->data));
+ }
+}
+
+static GList *
+gimp_image_window_get_docks (GimpDockContainer *dock_container)
+{
+ GimpImageWindowPrivate *private;
+ GList *iter;
+ GList *all_docks = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (dock_container), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (dock_container);
+
+ for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->left_docks));
+ iter;
+ iter = g_list_next (iter))
+ {
+ all_docks = g_list_append (all_docks, GIMP_DOCK (iter->data));
+ }
+
+ for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->right_docks));
+ iter;
+ iter = g_list_next (iter))
+ {
+ all_docks = g_list_append (all_docks, GIMP_DOCK (iter->data));
+ }
+
+ return all_docks;
+}
+
+static GimpDialogFactory *
+gimp_image_window_dock_container_get_dialog_factory (GimpDockContainer *dock_container)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (dock_container);
+
+ return private->dialog_factory;
+}
+
+static GimpUIManager *
+gimp_image_window_dock_container_get_ui_manager (GimpDockContainer *dock_container)
+{
+ GimpImageWindow *window = GIMP_IMAGE_WINDOW (dock_container);
+
+ return gimp_image_window_get_ui_manager (window);
+}
+
+void
+gimp_image_window_add_dock (GimpDockContainer *dock_container,
+ GimpDock *dock,
+ GimpSessionInfoDock *dock_info)
+{
+ GimpImageWindow *window;
+ GimpDisplayShell *active_shell;
+ GimpImageWindowPrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (dock_container));
+
+ window = GIMP_IMAGE_WINDOW (dock_container);
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ if (dock_info->side == GIMP_ALIGN_LEFT)
+ {
+ gimp_dock_columns_add_dock (GIMP_DOCK_COLUMNS (private->left_docks),
+ dock,
+ -1 /*index*/);
+ }
+ else
+ {
+ gimp_dock_columns_add_dock (GIMP_DOCK_COLUMNS (private->right_docks),
+ dock,
+ -1 /*index*/);
+ }
+
+ active_shell = gimp_image_window_get_active_shell (window);
+ if (active_shell)
+ gimp_display_shell_appearance_update (active_shell);
+}
+
+static GimpAlignmentType
+gimp_image_window_get_dock_side (GimpDockContainer *dock_container,
+ GimpDock *dock)
+{
+ GimpAlignmentType side = -1;
+ GimpImageWindowPrivate *private;
+ GList *iter;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (dock_container), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (dock_container);
+
+ for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->left_docks));
+ iter && side == -1;
+ iter = g_list_next (iter))
+ {
+ GimpDock *dock_iter = GIMP_DOCK (iter->data);
+
+ if (dock_iter == dock)
+ side = GIMP_ALIGN_LEFT;
+ }
+
+ for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->right_docks));
+ iter && side == -1;
+ iter = g_list_next (iter))
+ {
+ GimpDock *dock_iter = GIMP_DOCK (iter->data);
+
+ if (dock_iter == dock)
+ side = GIMP_ALIGN_RIGHT;
+ }
+
+ return side;
+}
+
+static GList *
+gimp_image_window_get_aux_info (GimpSessionManaged *session_managed)
+{
+ GList *aux_info = NULL;
+ GimpImageWindowPrivate *private;
+ GimpGuiConfig *config;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (session_managed), NULL);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (session_managed);
+ config = GIMP_GUI_CONFIG (private->gimp->config);
+
+ if (config->single_window_mode)
+ {
+ GimpSessionInfoAux *aux;
+ GtkAllocation allocation;
+ gchar widthbuf[128];
+
+ g_snprintf (widthbuf, sizeof (widthbuf), "%d",
+ gtk_paned_get_position (GTK_PANED (private->left_hpane)));
+ aux = gimp_session_info_aux_new (GIMP_IMAGE_WINDOW_LEFT_DOCKS_WIDTH,
+ widthbuf);
+ aux_info = g_list_append (aux_info, aux);
+
+ gtk_widget_get_allocation (private->right_hpane, &allocation);
+
+ g_snprintf (widthbuf, sizeof (widthbuf), "%d",
+ allocation.width -
+ gtk_paned_get_position (GTK_PANED (private->right_hpane)));
+ aux = gimp_session_info_aux_new (GIMP_IMAGE_WINDOW_RIGHT_DOCKS_WIDTH,
+ widthbuf);
+ aux_info = g_list_append (aux_info, aux);
+
+ aux = gimp_session_info_aux_new (GIMP_IMAGE_WINDOW_MAXIMIZED,
+ gimp_image_window_is_maximized (GIMP_IMAGE_WINDOW (session_managed)) ?
+ "yes" : "no");
+ aux_info = g_list_append (aux_info, aux);
+ }
+
+ return aux_info;
+}
+
+static void
+gimp_image_window_set_right_docks_width (GtkPaned *paned,
+ GtkAllocation *allocation,
+ void *data)
+{
+ gint width = GPOINTER_TO_INT (data);
+
+ g_return_if_fail (GTK_IS_PANED (paned));
+
+ if (width > 0)
+ gtk_paned_set_position (paned, allocation->width - width);
+ else
+ gtk_paned_set_position (paned, - width);
+
+ g_signal_handlers_disconnect_by_func (paned,
+ gimp_image_window_set_right_docks_width,
+ data);
+}
+
+static void
+gimp_image_window_set_aux_info (GimpSessionManaged *session_managed,
+ GList *aux_info)
+{
+ GimpImageWindowPrivate *private;
+ GList *iter;
+ gint left_docks_width = G_MININT;
+ gint right_docks_width = G_MININT;
+ gboolean wait_with_right_docks = FALSE;
+ gboolean maximized = FALSE;
+#ifdef G_OS_WIN32
+ STARTUPINFO StartupInfo;
+
+ GetStartupInfo (&StartupInfo);
+#endif
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (session_managed));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (session_managed);
+
+ for (iter = aux_info; iter; iter = g_list_next (iter))
+ {
+ GimpSessionInfoAux *aux = iter->data;
+ gint *width = NULL;
+
+ if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_LEFT_DOCKS_WIDTH))
+ width = &left_docks_width;
+ else if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_RIGHT_DOCKS_WIDTH))
+ width = &right_docks_width;
+ else if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_RIGHT_DOCKS_POS))
+ width = &right_docks_width;
+ else if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_MAXIMIZED))
+ if (! g_ascii_strcasecmp (aux->value, "yes"))
+ maximized = TRUE;
+
+ if (width)
+ sscanf (aux->value, "%d", width);
+
+ /* compat handling for right docks */
+ if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_RIGHT_DOCKS_POS))
+ {
+ /* negate the value because negative docks pos means docks width,
+ * also use the negativenes of a real docks pos as condition below.
+ */
+ *width = - *width;
+ }
+ }
+
+ if (left_docks_width != G_MININT &&
+ gtk_paned_get_position (GTK_PANED (private->left_hpane)) !=
+ left_docks_width)
+ {
+ gtk_paned_set_position (GTK_PANED (private->left_hpane), left_docks_width);
+
+ /* We can't set the position of the right docks, because it will
+ * be undesirably adjusted when its get a new size
+ * allocation. We must wait until after the size allocation.
+ */
+ wait_with_right_docks = TRUE;
+ }
+
+ if (right_docks_width != G_MININT &&
+ gtk_paned_get_position (GTK_PANED (private->right_hpane)) !=
+ right_docks_width)
+ {
+ if (wait_with_right_docks || right_docks_width > 0)
+ {
+ /* We must wait for a size allocation before we can set the
+ * position
+ */
+ g_signal_connect_data (private->right_hpane, "size-allocate",
+ G_CALLBACK (gimp_image_window_set_right_docks_width),
+ GINT_TO_POINTER (right_docks_width), NULL,
+ G_CONNECT_AFTER);
+ }
+ else
+ {
+ /* We can set the position directly, because we didn't
+ * change the left hpane position, and we got the old compat
+ * dock pos property.
+ */
+ gtk_paned_set_position (GTK_PANED (private->right_hpane),
+ - right_docks_width);
+ }
+ }
+
+#ifdef G_OS_WIN32
+ /* On Windows, user can provide startup hints to have a program
+ * maximized/minimized on startup. This can be done through command
+ * line: `start /max gimp-2.9.exe` or with the shortcut's "run"
+ * property.
+ * When such a hint is given, we should follow it and bypass the
+ * session's information.
+ */
+ if (StartupInfo.wShowWindow == SW_SHOWMAXIMIZED)
+ gtk_window_maximize (GTK_WINDOW (session_managed));
+ else if (StartupInfo.wShowWindow == SW_SHOWMINIMIZED ||
+ StartupInfo.wShowWindow == SW_SHOWMINNOACTIVE ||
+ StartupInfo.wShowWindow == SW_MINIMIZE)
+ /* XXX Iconification does not seem to work. I see the
+ * window being iconified and immediately re-raised.
+ * I leave this piece of code for later improvement. */
+ gtk_window_iconify (GTK_WINDOW (session_managed));
+ else
+ /* Another show property not relevant to min/max.
+ * Defaults is: SW_SHOWNORMAL
+ */
+#endif
+ if (maximized)
+ gtk_window_maximize (GTK_WINDOW (session_managed));
+ else
+ gtk_window_unmaximize (GTK_WINDOW (session_managed));
+}
+
+
+/* public functions */
+
+GimpImageWindow *
+gimp_image_window_new (Gimp *gimp,
+ GimpImage *image,
+ GimpDialogFactory *dialog_factory,
+ GdkScreen *screen,
+ gint monitor)
+{
+ GimpImageWindow *window;
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL);
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+ window = g_object_new (GIMP_TYPE_IMAGE_WINDOW,
+ "gimp", gimp,
+ "dialog-factory", dialog_factory,
+ "initial-screen", screen,
+ "initial-monitor", monitor,
+ /* The window position will be overridden by the
+ * dialog factory, it is only really used on first
+ * startup.
+ */
+ image ? NULL : "window-position",
+ GTK_WIN_POS_CENTER,
+ NULL);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ gimp->image_windows = g_list_append (gimp->image_windows, window);
+
+ if (! GIMP_GUI_CONFIG (private->gimp->config)->single_window_mode)
+ {
+ GdkScreen *pointer_screen;
+ gint pointer_monitor;
+
+ pointer_monitor = gimp_get_monitor_at_pointer (&pointer_screen);
+
+ /* If we are supposed to go to a monitor other than where the
+ * pointer is, place the window on that monitor manually,
+ * otherwise simply let the window manager place the window on
+ * the poiner's monitor.
+ */
+ if (pointer_screen != screen ||
+ pointer_monitor != monitor)
+ {
+ GdkRectangle rect;
+ gchar geom[32];
+
+ gdk_screen_get_monitor_workarea (screen, monitor, &rect);
+
+ /* FIXME: image window placement
+ *
+ * This is ugly beyond description but better than showing
+ * the window on the wrong monitor
+ */
+ g_snprintf (geom, sizeof (geom), "%+d%+d",
+ rect.x + 300, rect.y + 30);
+ gtk_window_parse_geometry (GTK_WINDOW (window), geom);
+ }
+ }
+
+ return window;
+}
+
+void
+gimp_image_window_destroy (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ private->gimp->image_windows = g_list_remove (private->gimp->image_windows,
+ window);
+
+ gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+GimpUIManager *
+gimp_image_window_get_ui_manager (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return private->menubar_manager;
+}
+
+GimpDockColumns *
+gimp_image_window_get_left_docks (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return GIMP_DOCK_COLUMNS (private->left_docks);
+}
+
+GimpDockColumns *
+gimp_image_window_get_right_docks (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return GIMP_DOCK_COLUMNS (private->right_docks);
+}
+
+void
+gimp_image_window_add_shell (GimpImageWindow *window,
+ GimpDisplayShell *shell)
+{
+ GimpImageWindowPrivate *private;
+ GtkWidget *tab_label;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ g_return_if_fail (g_list_find (private->shells, shell) == NULL);
+
+ private->shells = g_list_append (private->shells, shell);
+
+ tab_label = gimp_image_window_create_tab_label (window, shell);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (private->notebook),
+ GTK_WIDGET (shell), tab_label);
+ gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (private->notebook),
+ GTK_WIDGET (shell), TRUE);
+
+ gtk_widget_show (GTK_WIDGET (shell));
+
+ /* make it fetch the right monitor profile */
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell));
+}
+
+GimpDisplayShell *
+gimp_image_window_get_shell (GimpImageWindow *window,
+ gint index)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), NULL);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return g_list_nth_data (private->shells, index);
+}
+
+void
+gimp_image_window_remove_shell (GimpImageWindow *window,
+ GimpDisplayShell *shell)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ g_return_if_fail (g_list_find (private->shells, shell) != NULL);
+
+ private->shells = g_list_remove (private->shells, shell);
+
+ gtk_container_remove (GTK_CONTAINER (private->notebook),
+ GTK_WIDGET (shell));
+}
+
+gint
+gimp_image_window_get_n_shells (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), 0);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return g_list_length (private->shells);
+}
+
+void
+gimp_image_window_set_active_shell (GimpImageWindow *window,
+ GimpDisplayShell *shell)
+{
+ GimpImageWindowPrivate *private;
+ gint page_num;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ g_return_if_fail (g_list_find (private->shells, shell));
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (private->notebook),
+ GTK_WIDGET (shell));
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (private->notebook), page_num);
+}
+
+GimpDisplayShell *
+gimp_image_window_get_active_shell (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), NULL);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return private->active_shell;
+}
+
+void
+gimp_image_window_set_fullscreen (GimpImageWindow *window,
+ gboolean fullscreen)
+{
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ if (fullscreen != gimp_image_window_get_fullscreen (window))
+ {
+ if (fullscreen)
+ gtk_window_fullscreen (GTK_WINDOW (window));
+ else
+ gtk_window_unfullscreen (GTK_WINDOW (window));
+ }
+}
+
+gboolean
+gimp_image_window_get_fullscreen (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return (private->window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0;
+}
+
+void
+gimp_image_window_set_show_menubar (GimpImageWindow *window,
+ gboolean show)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ if (private->menubar)
+ gtk_widget_set_visible (private->menubar, show);
+}
+
+gboolean
+gimp_image_window_get_show_menubar (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ if (private->menubar)
+ return gtk_widget_get_visible (private->menubar);
+
+ return FALSE;
+}
+
+gboolean
+gimp_image_window_is_iconified (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return (private->window_state & GDK_WINDOW_STATE_ICONIFIED) != 0;
+}
+
+gboolean
+gimp_image_window_is_maximized (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ return (private->window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
+}
+
+/**
+ * gimp_image_window_has_toolbox:
+ * @window:
+ *
+ * Returns: %TRUE if the image window contains a GimpToolbox.
+ **/
+gboolean
+gimp_image_window_has_toolbox (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+ GList *iter = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE);
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->left_docks));
+ iter;
+ iter = g_list_next (iter))
+ {
+ if (GIMP_IS_TOOLBOX (iter->data))
+ return TRUE;
+ }
+
+ for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->right_docks));
+ iter;
+ iter = g_list_next (iter))
+ {
+ if (GIMP_IS_TOOLBOX (iter->data))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_image_window_shrink_wrap (GimpImageWindow *window,
+ gboolean grow_only)
+{
+ GimpDisplayShell *active_shell;
+ GtkWidget *widget;
+ GtkAllocation allocation;
+ GdkScreen *screen;
+ GdkRectangle rect;
+ gint monitor;
+ gint disp_width, disp_height;
+ gint width, height;
+ gint max_auto_width, max_auto_height;
+ gint border_width, border_height;
+ gboolean resize = FALSE;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ if (! gtk_widget_get_realized (GTK_WIDGET (window)))
+ return;
+
+ /* FIXME this so needs cleanup and shell/window separation */
+
+ active_shell = gimp_image_window_get_active_shell (window);
+
+ if (!active_shell)
+ return;
+
+ widget = GTK_WIDGET (window);
+ screen = gtk_widget_get_screen (widget);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ monitor = gdk_screen_get_monitor_at_window (screen,
+ gtk_widget_get_window (widget));
+ gdk_screen_get_monitor_workarea (screen, monitor, &rect);
+
+ if (! gimp_display_shell_get_infinite_canvas (active_shell))
+ {
+ gimp_display_shell_scale_get_image_size (active_shell,
+ &width, &height);
+ }
+ else
+ {
+ gimp_display_shell_scale_get_image_bounding_box (active_shell,
+ NULL, NULL,
+ &width, &height);
+ }
+
+ disp_width = active_shell->disp_width;
+ disp_height = active_shell->disp_height;
+
+
+ /* As long as the disp_width/disp_height is larger than 1 we
+ * can reliably depend on it to calculate the
+ * border_width/border_height because that means there is enough
+ * room in the top-level for the canvas as well as the rulers and
+ * scrollbars. If it is 1 or smaller it is likely that the rulers
+ * and scrollbars are overlapping each other and thus we cannot use
+ * the normal approach to border size, so special case that.
+ */
+ if (disp_width > 1 || !active_shell->vsb)
+ {
+ border_width = allocation.width - disp_width;
+ }
+ else
+ {
+ GtkAllocation vsb_allocation;
+
+ gtk_widget_get_allocation (active_shell->vsb, &vsb_allocation);
+
+ border_width = allocation.width - disp_width + vsb_allocation.width;
+ }
+
+ if (disp_height > 1 || !active_shell->hsb)
+ {
+ border_height = allocation.height - disp_height;
+ }
+ else
+ {
+ GtkAllocation hsb_allocation;
+
+ gtk_widget_get_allocation (active_shell->hsb, &hsb_allocation);
+
+ border_height = allocation.height - disp_height + hsb_allocation.height;
+ }
+
+
+ max_auto_width = (rect.width - border_width) * 0.75;
+ max_auto_height = (rect.height - border_height) * 0.75;
+
+ /* If one of the display dimensions has changed and one of the
+ * dimensions fits inside the screen
+ */
+ if (((width + border_width) < rect.width ||
+ (height + border_height) < rect.height) &&
+ (width != disp_width ||
+ height != disp_height))
+ {
+ width = ((width + border_width) < rect.width) ? width : max_auto_width;
+ height = ((height + border_height) < rect.height) ? height : max_auto_height;
+
+ resize = TRUE;
+ }
+
+ /* If the projected dimension is greater than current, but less than
+ * 3/4 of the screen size, expand automagically
+ */
+ else if ((width > disp_width ||
+ height > disp_height) &&
+ (disp_width < max_auto_width ||
+ disp_height < max_auto_height))
+ {
+ width = MIN (max_auto_width, width);
+ height = MIN (max_auto_height, height);
+
+ resize = TRUE;
+ }
+
+ if (resize)
+ {
+ GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (active_shell);
+ gint statusbar_width;
+
+ gtk_widget_get_size_request (GTK_WIDGET (statusbar),
+ &statusbar_width, NULL);
+
+ if (width < statusbar_width)
+ width = statusbar_width;
+
+ width = width + border_width;
+ height = height + border_height;
+
+ if (grow_only)
+ {
+ if (width < allocation.width)
+ width = allocation.width;
+
+ if (height < allocation.height)
+ height = allocation.height;
+ }
+
+ gtk_window_resize (GTK_WINDOW (window), width, height);
+ }
+
+ /* A wrap always means that we should center the image too. If the
+ * window changes size another center will be done in
+ * GimpDisplayShell::configure_event().
+ */
+ /* FIXME multiple shells */
+ gimp_display_shell_scroll_center_content (active_shell, TRUE, TRUE);
+}
+
+static GtkWidget *
+gimp_image_window_get_first_dockbook (GimpDockColumns *columns)
+{
+ GList *dock_iter;
+
+ for (dock_iter = gimp_dock_columns_get_docks (columns);
+ dock_iter;
+ dock_iter = g_list_next (dock_iter))
+ {
+ GimpDock *dock = GIMP_DOCK (dock_iter->data);
+ GList *dockbooks = gimp_dock_get_dockbooks (dock);
+
+ if (dockbooks)
+ return GTK_WIDGET (dockbooks->data);
+ }
+
+ return NULL;
+}
+
+/**
+ * gimp_image_window_get_default_dockbook:
+ * @window:
+ *
+ * Gets the default dockbook, which is the dockbook in which new
+ * dockables should be put in single-window mode.
+ *
+ * Returns: The default dockbook for new dockables, or NULL if no
+ * dockbook were available.
+ **/
+GtkWidget *
+gimp_image_window_get_default_dockbook (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+ GimpDockColumns *dock_columns;
+ GtkWidget *dockbook = NULL;
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ /* First try the first dockbook in the right docks */
+ dock_columns = GIMP_DOCK_COLUMNS (private->right_docks);
+ dockbook = gimp_image_window_get_first_dockbook (dock_columns);
+
+ /* Then the left docks */
+ if (! dockbook)
+ {
+ dock_columns = GIMP_DOCK_COLUMNS (private->left_docks);
+ dockbook = gimp_image_window_get_first_dockbook (dock_columns);
+ }
+
+ return dockbook;
+}
+
+/**
+ * gimp_image_window_keep_canvas_pos:
+ * @window:
+ *
+ * Stores the coordinates of the current image canvas origin relatively
+ * its GtkWindow; and on the first size-allocate sets the offsets in
+ * the shell so that the image origin remains the same (even on another
+ * GtkWindow).
+ *
+ * Example use case: The user hides docks attached to the side of image
+ * windows. You want the image to remain fixed on the screen though,
+ * so you use this function to keep the image fixed after the docks
+ * have been hidden.
+ **/
+void
+gimp_image_window_keep_canvas_pos (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+ GimpDisplayShell *shell;
+ gint canvas_x;
+ gint canvas_y;
+ gint window_x;
+ gint window_y;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ if (private->suspend_keep_pos > 0)
+ return;
+
+ shell = gimp_image_window_get_active_shell (window);
+
+ gimp_display_shell_transform_xy (shell, 0.0, 0.0, &canvas_x, &canvas_y);
+
+ if (gtk_widget_translate_coordinates (GTK_WIDGET (shell->canvas),
+ GTK_WIDGET (window),
+ canvas_x, canvas_y,
+ &window_x, &window_y))
+ {
+ PosCorrectionData *data = g_new0 (PosCorrectionData, 1);
+
+ data->canvas_x = canvas_x;
+ data->canvas_y = canvas_y;
+ data->window_x = window_x;
+ data->window_y = window_y;
+
+ g_signal_connect_data (shell, "size-allocate",
+ G_CALLBACK (gimp_image_window_shell_size_allocate),
+ data, (GClosureNotify) g_free,
+ G_CONNECT_AFTER);
+ }
+}
+
+void
+gimp_image_window_suspend_keep_pos (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ private->suspend_keep_pos++;
+}
+
+void
+gimp_image_window_resume_keep_pos (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ g_return_if_fail (private->suspend_keep_pos > 0);
+
+ private->suspend_keep_pos--;
+}
+
+/**
+ * gimp_image_window_update_tabs:
+ * @window: the Image Window to update.
+ *
+ * Holds the logics of whether shell tabs are to be shown or not in the
+ * Image Window @window. This function should be called after every
+ * change to @window where one might expect tab visibility to change.
+ *
+ * No direct call to gtk_notebook_set_show_tabs() should ever be made.
+ * If we change the logics of tab hiding, we should only change this
+ * procedure instead.
+ **/
+void
+gimp_image_window_update_tabs (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private;
+ GimpGuiConfig *config;
+ GtkPositionType position;
+
+ g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window));
+
+ private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ config = GIMP_GUI_CONFIG (private->gimp->config);
+
+ /* Tab visibility. */
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook),
+ config->single_window_mode &&
+ config->show_tabs &&
+ ! config->hide_docks &&
+ ((private->active_shell &&
+ private->active_shell->display &&
+ gimp_display_get_image (private->active_shell->display)) ||
+ g_list_length (private->shells) > 1));
+
+ /* Tab position. */
+ switch (config->tabs_position)
+ {
+ case GIMP_POSITION_TOP:
+ position = GTK_POS_TOP;
+ break;
+ case GIMP_POSITION_BOTTOM:
+ position = GTK_POS_BOTTOM;
+ break;
+ case GIMP_POSITION_LEFT:
+ position = GTK_POS_LEFT;
+ break;
+ case GIMP_POSITION_RIGHT:
+ position = GTK_POS_RIGHT;
+ break;
+ default:
+ /* If we have any strange value, just reset to default. */
+ position = GTK_POS_TOP;
+ break;
+ }
+
+ gtk_notebook_set_tab_pos (GTK_NOTEBOOK (private->notebook), position);
+}
+
+
+/* private functions */
+
+static void
+gimp_image_window_show_tooltip (GimpUIManager *manager,
+ const gchar *tooltip,
+ GimpImageWindow *window)
+{
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+ GimpStatusbar *statusbar = NULL;
+
+ if (! shell)
+ return;
+
+ statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_statusbar_push (statusbar, "menu-tooltip",
+ NULL, "%s", tooltip);
+}
+
+static void
+gimp_image_window_config_notify (GimpImageWindow *window,
+ GParamSpec *pspec,
+ GimpGuiConfig *config)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ /* Dock column visibility */
+ if (strcmp (pspec->name, "single-window-mode") == 0 ||
+ strcmp (pspec->name, "hide-docks") == 0 ||
+ strcmp (pspec->name, "show-tabs") == 0 ||
+ strcmp (pspec->name, "tabs-position") == 0)
+ {
+ if (strcmp (pspec->name, "single-window-mode") == 0 ||
+ strcmp (pspec->name, "hide-docks") == 0)
+ {
+ gboolean show_docks = (config->single_window_mode &&
+ ! config->hide_docks);
+
+ gimp_image_window_keep_canvas_pos (window);
+ gtk_widget_set_visible (private->left_docks, show_docks);
+ gtk_widget_set_visible (private->right_docks, show_docks);
+
+ /* If docks are being shown, and we are in multi-window-mode,
+ * and this is the window of the active display, try to set
+ * the keyboard focus to this window because it might have
+ * been stolen by a dock. See bug #567333.
+ */
+ if (strcmp (pspec->name, "hide-docks") == 0 &&
+ ! config->single_window_mode &&
+ ! config->hide_docks)
+ {
+ GimpDisplayShell *shell;
+ GimpContext *user_context;
+
+ shell = gimp_image_window_get_active_shell (window);
+ user_context = gimp_get_user_context (private->gimp);
+
+ if (gimp_context_get_display (user_context) == shell->display)
+ {
+ GdkWindow *w = gtk_widget_get_window (GTK_WIDGET (window));
+
+ if (w)
+ gdk_window_focus (w, gtk_get_current_event_time ());
+ }
+ }
+ }
+
+ gimp_image_window_update_tabs (window);
+ }
+
+ /* Session management */
+ if (strcmp (pspec->name, "single-window-mode") == 0)
+ {
+ gimp_image_window_session_update (window,
+ NULL /*new_display*/,
+ gimp_image_window_config_to_entry_id (config),
+ gtk_widget_get_screen (GTK_WIDGET (window)),
+ gimp_widget_get_monitor (GTK_WIDGET (window)));
+ }
+}
+
+static void
+gimp_image_window_hide_tooltip (GimpUIManager *manager,
+ GimpImageWindow *window)
+{
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+ GimpStatusbar *statusbar = NULL;
+
+ if (! shell)
+ return;
+
+ statusbar = gimp_display_shell_get_statusbar (shell);
+
+ gimp_statusbar_pop (statusbar, "menu-tooltip");
+}
+
+static gboolean
+gimp_image_window_update_ui_manager_idle (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ gimp_assert (private->active_shell != NULL);
+
+ gimp_ui_manager_update (private->menubar_manager,
+ private->active_shell->display);
+
+ private->update_ui_manager_idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gimp_image_window_update_ui_manager (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ if (! private->update_ui_manager_idle_id)
+ {
+ private->update_ui_manager_idle_id =
+ g_idle_add_full (GIMP_PRIORITY_IMAGE_WINDOW_UPDATE_UI_MANAGER_IDLE,
+ (GSourceFunc) gimp_image_window_update_ui_manager_idle,
+ window,
+ NULL);
+ }
+}
+
+static void
+gimp_image_window_shell_size_allocate (GimpDisplayShell *shell,
+ GtkAllocation *allocation,
+ PosCorrectionData *data)
+{
+ GimpImageWindow *window = gimp_display_shell_get_window (shell);
+ gint new_window_x;
+ gint new_window_y;
+
+ if (gtk_widget_translate_coordinates (GTK_WIDGET (shell->canvas),
+ GTK_WIDGET (window),
+ data->canvas_x, data->canvas_y,
+ &new_window_x, &new_window_y))
+ {
+ gint off_x = new_window_x - data->window_x;
+ gint off_y = new_window_y - data->window_y;
+
+ if (off_x || off_y)
+ gimp_display_shell_scroll (shell, off_x, off_y);
+ }
+
+ g_signal_handlers_disconnect_by_func (shell,
+ gimp_image_window_shell_size_allocate,
+ data);
+}
+
+static gboolean
+gimp_image_window_shell_events (GtkWidget *widget,
+ GdkEvent *event,
+ GimpImageWindow *window)
+{
+ GimpDisplayShell *shell = gimp_image_window_get_active_shell (window);
+
+ if (! shell)
+ return FALSE;
+
+ return gimp_display_shell_events (widget, event, shell);
+}
+
+static void
+gimp_image_window_switch_page (GtkNotebook *notebook,
+ gpointer page,
+ gint page_num,
+ GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpDisplayShell *shell;
+ GimpDisplay *active_display;
+
+ shell = GIMP_DISPLAY_SHELL (gtk_notebook_get_nth_page (notebook, page_num));
+
+ if (shell == private->active_shell)
+ return;
+
+ gimp_image_window_disconnect_from_active_shell (window);
+
+ GIMP_LOG (WM, "GimpImageWindow %p, private->active_shell = %p; \n",
+ window, shell);
+ private->active_shell = shell;
+
+ gimp_window_set_primary_focus_widget (GIMP_WINDOW (window),
+ shell->canvas);
+
+ active_display = private->active_shell->display;
+
+ g_signal_connect (active_display, "notify::image",
+ G_CALLBACK (gimp_image_window_image_notify),
+ window);
+
+ g_signal_connect (private->active_shell, "scaled",
+ G_CALLBACK (gimp_image_window_shell_scaled),
+ window);
+ g_signal_connect (private->active_shell, "rotated",
+ G_CALLBACK (gimp_image_window_shell_rotated),
+ window);
+ g_signal_connect (private->active_shell, "notify::title",
+ G_CALLBACK (gimp_image_window_shell_title_notify),
+ window);
+ g_signal_connect (private->active_shell, "notify::icon",
+ G_CALLBACK (gimp_image_window_shell_icon_notify),
+ window);
+
+ gtk_window_set_title (GTK_WINDOW (window), shell->title);
+ gtk_window_set_icon (GTK_WINDOW (window), shell->icon);
+
+ gimp_display_shell_appearance_update (private->active_shell);
+
+ if (gtk_widget_get_window (GTK_WIDGET (window)))
+ {
+ /* we are fully initialized, use the window's current monitor
+ */
+ gimp_image_window_session_update (window,
+ active_display,
+ NULL /*new_entry_id*/,
+ gtk_widget_get_screen (GTK_WIDGET (window)),
+ gimp_widget_get_monitor (GTK_WIDGET (window)));
+ }
+ else
+ {
+ /* we are in construction, use the initial monitor; calling
+ * gimp_widget_get_monitor() would get us the monitor where the
+ * pointer is
+ */
+ gimp_image_window_session_update (window,
+ active_display,
+ NULL /*new_entry_id*/,
+ private->initial_screen,
+ private->initial_monitor);
+ }
+
+ gimp_context_set_display (gimp_get_user_context (private->gimp),
+ active_display);
+
+ gimp_image_window_update_ui_manager (window);
+
+ gimp_image_window_update_tab_labels (window);
+}
+
+static void
+gimp_image_window_page_removed (GtkNotebook *notebook,
+ GtkWidget *widget,
+ gint page_num,
+ GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ if (GTK_WIDGET (private->active_shell) == widget)
+ {
+ GIMP_LOG (WM, "GimpImageWindow %p, private->active_shell = %p; \n",
+ window, NULL);
+ gimp_image_window_disconnect_from_active_shell (window);
+ private->active_shell = NULL;
+ }
+}
+
+static void
+gimp_image_window_page_reordered (GtkNotebook *notebook,
+ GtkWidget *widget,
+ gint page_num,
+ GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpContainer *displays = private->gimp->displays;
+ gint index = g_list_index (private->shells, widget);
+
+ if (index != page_num)
+ {
+ private->shells = g_list_remove (private->shells, widget);
+ private->shells = g_list_insert (private->shells, widget, page_num);
+ }
+
+ /* We need to reorder the displays as well in order to update the
+ * numbered accelerators (alt-1, alt-2, etc.).
+ */
+ gimp_container_reorder (displays,
+ GIMP_OBJECT (GIMP_DISPLAY_SHELL (widget)->display),
+ page_num);
+
+ gtk_notebook_reorder_child (notebook, widget, page_num);
+}
+
+static void
+gimp_image_window_disconnect_from_active_shell (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpDisplay *active_display = NULL;
+
+ if (! private->active_shell)
+ return;
+
+ active_display = private->active_shell->display;
+
+ if (active_display)
+ g_signal_handlers_disconnect_by_func (active_display,
+ gimp_image_window_image_notify,
+ window);
+
+ g_signal_handlers_disconnect_by_func (private->active_shell,
+ gimp_image_window_shell_scaled,
+ window);
+ g_signal_handlers_disconnect_by_func (private->active_shell,
+ gimp_image_window_shell_rotated,
+ window);
+ g_signal_handlers_disconnect_by_func (private->active_shell,
+ gimp_image_window_shell_title_notify,
+ window);
+ g_signal_handlers_disconnect_by_func (private->active_shell,
+ gimp_image_window_shell_icon_notify,
+ window);
+
+ if (private->menubar_manager)
+ gimp_image_window_hide_tooltip (private->menubar_manager, window);
+
+ if (private->update_ui_manager_idle_id)
+ {
+ g_source_remove (private->update_ui_manager_idle_id);
+ private->update_ui_manager_idle_id = 0;
+ }
+}
+
+static void
+gimp_image_window_image_notify (GimpDisplay *display,
+ const GParamSpec *pspec,
+ GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GtkWidget *tab_label;
+ GList *children;
+ GtkWidget *view;
+
+ gimp_image_window_session_update (window,
+ display,
+ NULL /*new_entry_id*/,
+ gtk_widget_get_screen (GTK_WIDGET (window)),
+ gimp_widget_get_monitor (GTK_WIDGET (window)));
+
+ tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (private->notebook),
+ GTK_WIDGET (gimp_display_get_shell (display)));
+ children = gtk_container_get_children (GTK_CONTAINER (tab_label));
+ view = GTK_WIDGET (children->data);
+ g_list_free (children);
+
+ gimp_view_set_viewable (GIMP_VIEW (view),
+ GIMP_VIEWABLE (gimp_display_get_image (display)));
+
+ gimp_image_window_update_ui_manager (window);
+}
+
+static void
+gimp_image_window_session_clear (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GtkWidget *widget = GTK_WIDGET (window);
+
+ if (gimp_dialog_factory_from_widget (widget, NULL))
+ gimp_dialog_factory_remove_dialog (private->dialog_factory,
+ widget);
+}
+
+static void
+gimp_image_window_session_apply (GimpImageWindow *window,
+ const gchar *entry_id,
+ GdkScreen *screen,
+ gint monitor)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GimpSessionInfo *session_info = NULL;
+ gint width = -1;
+ gint height = -1;
+
+ gtk_window_unfullscreen (GTK_WINDOW (window));
+
+ /* get the NIW size before adding the display to the dialog
+ * factory so the window's current size doesn't affect the
+ * stored session info entry.
+ */
+ session_info =
+ gimp_dialog_factory_find_session_info (private->dialog_factory, entry_id);
+
+ if (session_info)
+ {
+ width = gimp_session_info_get_width (session_info);
+ height = gimp_session_info_get_height (session_info);
+ }
+ else
+ {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+ }
+
+ gimp_dialog_factory_add_foreign (private->dialog_factory,
+ entry_id,
+ GTK_WIDGET (window),
+ screen,
+ monitor);
+
+ gtk_window_unmaximize (GTK_WINDOW (window));
+ gtk_window_resize (GTK_WINDOW (window), width, height);
+}
+
+static void
+gimp_image_window_session_update (GimpImageWindow *window,
+ GimpDisplay *new_display,
+ const gchar *new_entry_id,
+ GdkScreen *screen,
+ gint monitor)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+
+ /* Handle changes to the entry id */
+ if (new_entry_id)
+ {
+ if (! private->entry_id)
+ {
+ /* We're initializing. If we're in single-window mode, this
+ * will be the only window, so start to session manage
+ * it. If we're in multi-window mode, we will find out if we
+ * should session manage ourselves when we get a display
+ */
+ if (strcmp (new_entry_id, GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID) == 0)
+ {
+ gimp_image_window_session_apply (window, new_entry_id,
+ screen, monitor);
+ }
+ }
+ else if (strcmp (private->entry_id, new_entry_id) != 0)
+ {
+ /* The entry id changed, immediately and always stop session
+ * managing the old entry
+ */
+ gimp_image_window_session_clear (window);
+
+ if (strcmp (new_entry_id, GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID) == 0)
+ {
+ /* If there is only one imageless display, we shall
+ * become the empty image window
+ */
+ if (private->active_shell &&
+ private->active_shell->display &&
+ ! gimp_display_get_image (private->active_shell->display) &&
+ g_list_length (private->shells) <= 1)
+ {
+ gimp_image_window_session_apply (window, new_entry_id,
+ screen, monitor);
+ }
+ }
+ else if (strcmp (new_entry_id, GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID) == 0)
+ {
+ /* As soon as we become the single image window, we
+ * shall session manage ourself until single-window mode
+ * is exited
+ */
+ gimp_image_window_session_apply (window, new_entry_id,
+ screen, monitor);
+ }
+ }
+
+ private->entry_id = new_entry_id;
+ }
+
+ /* Handle changes to the displays. When in single-window mode, we
+ * just keep session managing the single image window. We only need
+ * to care about the multi-window mode case here
+ */
+ if (new_display &&
+ strcmp (private->entry_id, GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID) == 0)
+ {
+ if (gimp_display_get_image (new_display))
+ {
+ /* As soon as we have an image we should not affect the size of the
+ * empty image window
+ */
+ gimp_image_window_session_clear (window);
+ }
+ else if (! gimp_display_get_image (new_display) &&
+ g_list_length (private->shells) <= 1)
+ {
+ /* As soon as we have no image (and no other shells that may
+ * contain images) we should become the empty image window
+ */
+ gimp_image_window_session_apply (window, private->entry_id,
+ screen, monitor);
+ }
+ }
+}
+
+static const gchar *
+gimp_image_window_config_to_entry_id (GimpGuiConfig *config)
+{
+ return (config->single_window_mode ?
+ GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID :
+ GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID);
+}
+
+static void
+gimp_image_window_shell_scaled (GimpDisplayShell *shell,
+ GimpImageWindow *window)
+{
+ /* update the <Image>/View/Zoom menu */
+ gimp_image_window_update_ui_manager (window);
+}
+
+static void
+gimp_image_window_shell_rotated (GimpDisplayShell *shell,
+ GimpImageWindow *window)
+{
+ /* update the <Image>/View/Rotate menu */
+ gimp_image_window_update_ui_manager (window);
+}
+
+static void
+gimp_image_window_shell_title_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpImageWindow *window)
+{
+ gtk_window_set_title (GTK_WINDOW (window), shell->title);
+}
+
+static void
+gimp_image_window_shell_icon_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpImageWindow *window)
+{
+ gtk_window_set_icon (GTK_WINDOW (window), shell->icon);
+}
+
+static void
+gimp_image_window_shell_close_button_callback (GimpDisplayShell *shell)
+{
+ if (shell)
+ gimp_display_shell_close (shell, FALSE);
+}
+
+static GtkWidget *
+gimp_image_window_create_tab_label (GimpImageWindow *window,
+ GimpDisplayShell *shell)
+{
+ GtkWidget *hbox;
+ GtkWidget *view;
+ GimpImage *image;
+ GtkWidget *button;
+ GtkWidget *gtk_image;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_widget_show (hbox);
+
+ view = gimp_view_new_by_types (gimp_get_user_context (shell->display->gimp),
+ GIMP_TYPE_VIEW, GIMP_TYPE_IMAGE,
+ GIMP_VIEW_SIZE_LARGE, 0, FALSE);
+ gtk_widget_set_size_request (view, GIMP_VIEW_SIZE_LARGE, -1);
+ gimp_view_renderer_set_color_config (GIMP_VIEW (view)->renderer,
+ gimp_display_shell_get_color_config (shell));
+ gtk_box_pack_start (GTK_BOX (hbox), view, FALSE, FALSE, 0);
+ gtk_widget_show (view);
+
+ image = gimp_display_get_image (shell->display);
+ if (image)
+ gimp_view_set_viewable (GIMP_VIEW (view), GIMP_VIEWABLE (image));
+
+ button = gtk_button_new ();
+ gtk_widget_set_can_focus (button, FALSE);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_CLOSE,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (button), gtk_image);
+ gtk_widget_show (gtk_image);
+
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (gimp_image_window_shell_close_button_callback),
+ shell);
+
+ g_object_set_data (G_OBJECT (hbox), "close-button", button);
+
+ return hbox;
+}
+
+static void
+gimp_image_window_update_tab_labels (GimpImageWindow *window)
+{
+ GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
+ GList *children;
+ GList *list;
+
+ children = gtk_container_get_children (GTK_CONTAINER (private->notebook));
+
+ for (list = children; list; list = g_list_next (list))
+ {
+ GtkWidget *shell = list->data;
+ GtkWidget *tab_widget;
+ GtkWidget *close_button;
+
+ tab_widget = gtk_notebook_get_tab_label (GTK_NOTEBOOK (private->notebook),
+ shell);
+
+ close_button = g_object_get_data (G_OBJECT (tab_widget), "close-button");
+
+ if (gimp_context_get_display (gimp_get_user_context (private->gimp)) ==
+ GIMP_DISPLAY_SHELL (shell)->display)
+ {
+ gtk_widget_show (close_button);
+ }
+ else
+ {
+ gtk_widget_hide (close_button);
+ }
+ }
+
+ g_list_free (children);
+}
diff --git a/app/display/gimpimagewindow.h b/app/display/gimpimagewindow.h
new file mode 100644
index 0000000..121dc71
--- /dev/null
+++ b/app/display/gimpimagewindow.h
@@ -0,0 +1,100 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_IMAGE_WINDOW_H__
+#define __GIMP_IMAGE_WINDOW_H__
+
+
+#include "widgets/gimpwindow.h"
+
+
+#define GIMP_TYPE_IMAGE_WINDOW (gimp_image_window_get_type ())
+#define GIMP_IMAGE_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_WINDOW, GimpImageWindow))
+#define GIMP_IMAGE_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_WINDOW, GimpImageWindowClass))
+#define GIMP_IS_IMAGE_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_WINDOW))
+#define GIMP_IS_IMAGE_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_WINDOW))
+#define GIMP_IMAGE_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_WINDOW, GimpImageWindowClass))
+
+
+typedef struct _GimpImageWindowClass GimpImageWindowClass;
+
+struct _GimpImageWindow
+{
+ GimpWindow parent_instance;
+};
+
+struct _GimpImageWindowClass
+{
+ GimpWindowClass parent_class;
+};
+
+
+GType gimp_image_window_get_type (void) G_GNUC_CONST;
+
+GimpImageWindow * gimp_image_window_new (Gimp *gimp,
+ GimpImage *image,
+ GimpDialogFactory *dialog_factory,
+ GdkScreen *screen,
+ gint monitor);
+void gimp_image_window_destroy (GimpImageWindow *window);
+
+GimpUIManager * gimp_image_window_get_ui_manager (GimpImageWindow *window);
+GimpDockColumns * gimp_image_window_get_left_docks (GimpImageWindow *window);
+GimpDockColumns * gimp_image_window_get_right_docks (GimpImageWindow *window);
+
+void gimp_image_window_add_shell (GimpImageWindow *window,
+ GimpDisplayShell *shell);
+GimpDisplayShell * gimp_image_window_get_shell (GimpImageWindow *window,
+ gint index);
+void gimp_image_window_remove_shell (GimpImageWindow *window,
+ GimpDisplayShell *shell);
+
+gint gimp_image_window_get_n_shells (GimpImageWindow *window);
+
+void gimp_image_window_set_active_shell (GimpImageWindow *window,
+ GimpDisplayShell *shell);
+GimpDisplayShell * gimp_image_window_get_active_shell (GimpImageWindow *window);
+
+void gimp_image_window_set_fullscreen (GimpImageWindow *window,
+ gboolean fullscreen);
+gboolean gimp_image_window_get_fullscreen (GimpImageWindow *window);
+
+void gimp_image_window_set_show_menubar (GimpImageWindow *window,
+ gboolean show);
+gboolean gimp_image_window_get_show_menubar (GimpImageWindow *window);
+
+void gimp_image_window_set_show_statusbar (GimpImageWindow *window,
+ gboolean show);
+gboolean gimp_image_window_get_show_statusbar (GimpImageWindow *window);
+
+gboolean gimp_image_window_is_iconified (GimpImageWindow *window);
+gboolean gimp_image_window_is_maximized (GimpImageWindow *window);
+
+gboolean gimp_image_window_has_toolbox (GimpImageWindow *window);
+
+void gimp_image_window_shrink_wrap (GimpImageWindow *window,
+ gboolean grow_only);
+
+GtkWidget * gimp_image_window_get_default_dockbook (GimpImageWindow *window);
+
+void gimp_image_window_keep_canvas_pos (GimpImageWindow *window);
+void gimp_image_window_suspend_keep_pos (GimpImageWindow *window);
+void gimp_image_window_resume_keep_pos (GimpImageWindow *window);
+
+void gimp_image_window_update_tabs (GimpImageWindow *window);
+
+#endif /* __GIMP_IMAGE_WINDOW_H__ */
diff --git a/app/display/gimpmotionbuffer.c b/app/display/gimpmotionbuffer.c
new file mode 100644
index 0000000..549f6f0
--- /dev/null
+++ b/app/display/gimpmotionbuffer.c
@@ -0,0 +1,594 @@
+/* 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpcoords.h"
+#include "core/gimpcoords-interpolate.h"
+#include "core/gimpmarshal.h"
+
+#include "gimpmotionbuffer.h"
+
+
+/* Velocity unit is screen pixels per millisecond we pass to tools as 1. */
+#define VELOCITY_UNIT 3.0
+#define EVENT_FILL_PRECISION 6.0
+#define DIRECTION_RADIUS (1.0 / MAX (scale_x, scale_y))
+#define SMOOTH_FACTOR 0.3
+
+
+enum
+{
+ PROP_0
+};
+
+enum
+{
+ STROKE,
+ HOVER,
+ LAST_SIGNAL
+};
+
+
+/* local function prototypes */
+
+static void gimp_motion_buffer_dispose (GObject *object);
+static void gimp_motion_buffer_finalize (GObject *object);
+static void gimp_motion_buffer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_motion_buffer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_motion_buffer_push_event_history (GimpMotionBuffer *buffer,
+ const GimpCoords *coords);
+static void gimp_motion_buffer_pop_event_queue (GimpMotionBuffer *buffer,
+ GimpCoords *coords);
+
+static void gimp_motion_buffer_interpolate_stroke (GimpMotionBuffer *buffer,
+ GimpCoords *coords);
+static gboolean gimp_motion_buffer_event_queue_timeout (GimpMotionBuffer *buffer);
+
+
+G_DEFINE_TYPE (GimpMotionBuffer, gimp_motion_buffer, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_motion_buffer_parent_class
+
+static guint motion_buffer_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_motion_buffer_class_init (GimpMotionBufferClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ motion_buffer_signals[STROKE] =
+ g_signal_new ("stroke",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpMotionBufferClass, stroke),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER_UINT_FLAGS,
+ G_TYPE_NONE, 3,
+ G_TYPE_POINTER,
+ G_TYPE_UINT,
+ GDK_TYPE_MODIFIER_TYPE);
+
+ motion_buffer_signals[HOVER] =
+ g_signal_new ("hover",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpMotionBufferClass, hover),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER_FLAGS_BOOLEAN,
+ G_TYPE_NONE, 3,
+ G_TYPE_POINTER,
+ GDK_TYPE_MODIFIER_TYPE,
+ G_TYPE_BOOLEAN);
+
+ object_class->dispose = gimp_motion_buffer_dispose;
+ object_class->finalize = gimp_motion_buffer_finalize;
+ object_class->set_property = gimp_motion_buffer_set_property;
+ object_class->get_property = gimp_motion_buffer_get_property;
+}
+
+static void
+gimp_motion_buffer_init (GimpMotionBuffer *buffer)
+{
+ buffer->event_history = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+ buffer->event_queue = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+}
+
+static void
+gimp_motion_buffer_dispose (GObject *object)
+{
+ GimpMotionBuffer *buffer = GIMP_MOTION_BUFFER (object);
+
+ if (buffer->event_delay_timeout)
+ {
+ g_source_remove (buffer->event_delay_timeout);
+ buffer->event_delay_timeout = 0;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_motion_buffer_finalize (GObject *object)
+{
+ GimpMotionBuffer *buffer = GIMP_MOTION_BUFFER (object);
+
+ if (buffer->event_history)
+ {
+ g_array_free (buffer->event_history, TRUE);
+ buffer->event_history = NULL;
+ }
+
+ if (buffer->event_queue)
+ {
+ g_array_free (buffer->event_queue, TRUE);
+ buffer->event_queue = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_motion_buffer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_motion_buffer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+GimpMotionBuffer *
+gimp_motion_buffer_new (void)
+{
+ return g_object_new (GIMP_TYPE_MOTION_BUFFER,
+ NULL);
+}
+
+void
+gimp_motion_buffer_begin_stroke (GimpMotionBuffer *buffer,
+ guint32 time,
+ GimpCoords *last_motion)
+{
+ g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
+ g_return_if_fail (last_motion != NULL);
+
+ buffer->last_read_motion_time = time;
+
+ *last_motion = buffer->last_coords;
+}
+
+void
+gimp_motion_buffer_end_stroke (GimpMotionBuffer *buffer)
+{
+ g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
+
+ if (buffer->event_delay_timeout)
+ {
+ g_source_remove (buffer->event_delay_timeout);
+ buffer->event_delay_timeout = 0;
+ }
+
+ gimp_motion_buffer_event_queue_timeout (buffer);
+}
+
+/**
+ * gimp_motion_buffer_motion_event:
+ * @buffer:
+ * @coords:
+ * @time:
+ * @event_fill:
+ *
+ * This function evaluates the event to decide if the change is big
+ * enough to need handling and returns FALSE, if change is less than
+ * set filter level taking a whole lot of load off any draw tools that
+ * have no use for these events anyway. If the event is seen fit at
+ * first look, it is evaluated for speed and smoothed. Due to lousy
+ * time resolution of events pretty strong smoothing is applied to
+ * timestamps for sensible speed result. This function is also ideal
+ * for other event adjustment like pressure curve or calculating other
+ * derived dynamics factors like angular velocity calculation from
+ * tilt values, to allow for even more dynamic brushes. Calculated
+ * distance to last event is stored in GimpCoords because its a
+ * sideproduct of velocity calculation and is currently calculated in
+ * each tool. If they were to use this distance, more resources on
+ * recalculating the same value would be saved.
+ *
+ * Return value: %TRUE if the motion was significant enough to be
+ * processed, %FALSE otherwise.
+ **/
+gboolean
+gimp_motion_buffer_motion_event (GimpMotionBuffer *buffer,
+ GimpCoords *coords,
+ guint32 time,
+ gboolean event_fill)
+{
+ gdouble delta_time = 0.001;
+ gdouble delta_x = 0.0;
+ gdouble delta_y = 0.0;
+ gdouble distance = 1.0;
+ gdouble scale_x = coords->xscale;
+ gdouble scale_y = coords->yscale;
+
+ g_return_val_if_fail (GIMP_IS_MOTION_BUFFER (buffer), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+
+ /* the last_read_motion_time most be set unconditionally, so set
+ * it early
+ */
+ buffer->last_read_motion_time = time;
+
+ delta_time = (buffer->last_motion_delta_time * (1 - SMOOTH_FACTOR) +
+ (time - buffer->last_motion_time) * SMOOTH_FACTOR);
+
+ if (buffer->last_motion_time == 0)
+ {
+ /* First pair is invalid to do any velocity calculation, so we
+ * apply a constant value.
+ */
+ coords->velocity = 1.0;
+ }
+ else
+ {
+ GimpCoords last_dir_event = buffer->last_coords;
+ gdouble filter;
+ gdouble dist;
+ gdouble delta_dir;
+ gdouble dir_delta_x = 0.0;
+ gdouble dir_delta_y = 0.0;
+
+ delta_x = last_dir_event.x - coords->x;
+ delta_y = last_dir_event.y - coords->y;
+
+ /* Events with distances less than the screen resolution are
+ * not worth handling.
+ */
+ filter = MIN (1.0 / scale_x, 1.0 / scale_y) / 2.0;
+
+ if (fabs (delta_x) < filter &&
+ fabs (delta_y) < filter)
+ {
+ return FALSE;
+ }
+
+ distance = dist = sqrt (SQR (delta_x) + SQR (delta_y));
+
+ /* If even smoothed time resolution does not allow to guess for
+ * speed, use last velocity.
+ */
+ if (delta_time == 0)
+ {
+ coords->velocity = buffer->last_coords.velocity;
+ }
+ else
+ {
+ /* We need to calculate the velocity in screen coordinates
+ * for human interaction
+ */
+ gdouble screen_distance = (distance * MIN (scale_x, scale_y));
+
+ /* Calculate raw valocity */
+ coords->velocity = ((screen_distance / delta_time) / VELOCITY_UNIT);
+
+ /* Adding velocity dependent smoothing, feels better in tools. */
+ coords->velocity = (buffer->last_coords.velocity *
+ (1 - MIN (SMOOTH_FACTOR, coords->velocity)) +
+ coords->velocity *
+ MIN (SMOOTH_FACTOR, coords->velocity));
+
+ /* Speed needs upper limit */
+ coords->velocity = MIN (coords->velocity, 1.0);
+ }
+
+ if (((fabs (delta_x) > DIRECTION_RADIUS) &&
+ (fabs (delta_y) > DIRECTION_RADIUS)) ||
+ (buffer->event_history->len < 4))
+ {
+ dir_delta_x = delta_x;
+ dir_delta_y = delta_y;
+ }
+ else
+ {
+ gint x = CLAMP ((buffer->event_history->len - 1), 3, 15);
+
+ while (((fabs (dir_delta_x) < DIRECTION_RADIUS) ||
+ (fabs (dir_delta_y) < DIRECTION_RADIUS)) &&
+ (x >= 0))
+ {
+ last_dir_event = g_array_index (buffer->event_history,
+ GimpCoords, x);
+
+ dir_delta_x = last_dir_event.x - coords->x;
+ dir_delta_y = last_dir_event.y - coords->y;
+
+ x--;
+ }
+ }
+
+ if ((fabs (dir_delta_x) < DIRECTION_RADIUS) ||
+ (fabs (dir_delta_y) < DIRECTION_RADIUS))
+ {
+ coords->direction = buffer->last_coords.direction;
+ }
+ else
+ {
+ coords->direction = gimp_coords_direction (&last_dir_event, coords);
+ }
+
+ coords->direction = coords->direction - floor (coords->direction);
+
+ delta_dir = coords->direction - buffer->last_coords.direction;
+
+ if (delta_dir < -0.5)
+ {
+ coords->direction = (0.5 * coords->direction +
+ 0.5 * (buffer->last_coords.direction - 1.0));
+ }
+ else if (delta_dir > 0.5)
+ {
+ coords->direction = (0.5 * coords->direction +
+ 0.5 * (buffer->last_coords.direction + 1.0));
+ }
+ else
+ {
+ coords->direction = (0.5 * coords->direction +
+ 0.5 * buffer->last_coords.direction);
+ }
+
+ coords->direction = coords->direction - floor (coords->direction);
+
+ /* do event fill for devices that do not provide enough events */
+ if (distance >= EVENT_FILL_PRECISION &&
+ event_fill &&
+ buffer->event_history->len >= 2)
+ {
+ if (buffer->event_delay)
+ {
+ gimp_motion_buffer_interpolate_stroke (buffer, coords);
+ }
+ else
+ {
+ buffer->event_delay = TRUE;
+ gimp_motion_buffer_push_event_history (buffer, coords);
+ }
+ }
+ else
+ {
+ if (buffer->event_delay)
+ buffer->event_delay = FALSE;
+
+ gimp_motion_buffer_push_event_history (buffer, coords);
+ }
+
+#ifdef EVENT_VERBOSE
+ g_printerr ("DIST: %f, DT:%f, Vel:%f, Press:%f,smooth_dd:%f, POS: (%f, %f)\n",
+ distance,
+ delta_time,
+ buffer->last_coords.velocity,
+ coords->pressure,
+ distance - dist,
+ coords->x,
+ coords->y);
+#endif
+ }
+
+ g_array_append_val (buffer->event_queue, *coords);
+
+ buffer->last_coords = *coords;
+ buffer->last_motion_time = time;
+ buffer->last_motion_delta_time = delta_time;
+ buffer->last_motion_delta_x = delta_x;
+ buffer->last_motion_delta_y = delta_y;
+ buffer->last_motion_distance = distance;
+
+ return TRUE;
+}
+
+guint32
+gimp_motion_buffer_get_last_motion_time (GimpMotionBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_MOTION_BUFFER (buffer), 0);
+
+ return buffer->last_read_motion_time;
+}
+
+void
+gimp_motion_buffer_request_stroke (GimpMotionBuffer *buffer,
+ GdkModifierType state,
+ guint32 time)
+{
+ GdkModifierType event_state;
+ gint keep = 0;
+
+ g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
+
+ if (buffer->event_delay)
+ {
+ /* If we are in delay we use LAST state, not current */
+ event_state = buffer->last_active_state;
+
+ keep = 1; /* Holding one event in buf */
+ }
+ else
+ {
+ /* Save the state */
+ event_state = state;
+ }
+
+ if (buffer->event_delay_timeout)
+ {
+ g_source_remove (buffer->event_delay_timeout);
+ buffer->event_delay_timeout = 0;
+ }
+
+ buffer->last_active_state = state;
+
+ while (buffer->event_queue->len > keep)
+ {
+ GimpCoords buf_coords;
+
+ gimp_motion_buffer_pop_event_queue (buffer, &buf_coords);
+
+ g_signal_emit (buffer, motion_buffer_signals[STROKE], 0,
+ &buf_coords, time, event_state);
+ }
+
+ if (buffer->event_delay)
+ {
+ buffer->event_delay_timeout =
+ g_timeout_add (50,
+ (GSourceFunc) gimp_motion_buffer_event_queue_timeout,
+ buffer);
+ }
+}
+
+void
+gimp_motion_buffer_request_hover (GimpMotionBuffer *buffer,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer));
+
+ if (buffer->event_queue->len > 0)
+ {
+ GimpCoords buf_coords = g_array_index (buffer->event_queue,
+ GimpCoords,
+ buffer->event_queue->len - 1);
+
+ g_signal_emit (buffer, motion_buffer_signals[HOVER], 0,
+ &buf_coords, state, proximity);
+
+ g_array_set_size (buffer->event_queue, 0);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_motion_buffer_push_event_history (GimpMotionBuffer *buffer,
+ const GimpCoords *coords)
+{
+ if (buffer->event_history->len == 4)
+ g_array_remove_index (buffer->event_history, 0);
+
+ g_array_append_val (buffer->event_history, *coords);
+}
+
+static void
+gimp_motion_buffer_pop_event_queue (GimpMotionBuffer *buffer,
+ GimpCoords *coords)
+{
+ *coords = g_array_index (buffer->event_queue, GimpCoords, 0);
+
+ g_array_remove_index (buffer->event_queue, 0);
+}
+
+static void
+gimp_motion_buffer_interpolate_stroke (GimpMotionBuffer *buffer,
+ GimpCoords *coords)
+{
+ GimpCoords catmull[4];
+ GArray *ret_coords;
+ gint i = buffer->event_history->len - 1;
+
+ /* Note that there must be exactly one event in buffer or bad things
+ * can happen. This must never get called under other circumstances.
+ */
+ ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ catmull[0] = g_array_index (buffer->event_history, GimpCoords, i - 1);
+ catmull[1] = g_array_index (buffer->event_history, GimpCoords, i);
+ catmull[2] = g_array_index (buffer->event_queue, GimpCoords, 0);
+ catmull[3] = *coords;
+
+ gimp_coords_interpolate_catmull (catmull, EVENT_FILL_PRECISION / 2,
+ ret_coords, NULL);
+
+ /* Push the last actual event in history */
+ gimp_motion_buffer_push_event_history (buffer,
+ &g_array_index (buffer->event_queue,
+ GimpCoords, 0));
+
+ g_array_set_size (buffer->event_queue, 0);
+
+ g_array_append_vals (buffer->event_queue,
+ &g_array_index (ret_coords, GimpCoords, 0),
+ ret_coords->len);
+
+ g_array_free (ret_coords, TRUE);
+}
+
+static gboolean
+gimp_motion_buffer_event_queue_timeout (GimpMotionBuffer *buffer)
+{
+ buffer->event_delay = FALSE;
+ buffer->event_delay_timeout = 0;
+
+ if (buffer->event_queue->len > 0)
+ {
+ GimpCoords last_coords = g_array_index (buffer->event_queue,
+ GimpCoords,
+ buffer->event_queue->len - 1);
+
+ gimp_motion_buffer_push_event_history (buffer, &last_coords);
+
+ gimp_motion_buffer_request_stroke (buffer,
+ buffer->last_active_state,
+ buffer->last_read_motion_time);
+ }
+
+ return FALSE;
+}
diff --git a/app/display/gimpmotionbuffer.h b/app/display/gimpmotionbuffer.h
new file mode 100644
index 0000000..c7aee64
--- /dev/null
+++ b/app/display/gimpmotionbuffer.h
@@ -0,0 +1,99 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmotionbuffer.h
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_MOTION_BUFFER_H__
+#define __GIMP_MOTION_BUFFER_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_MOTION_BUFFER (gimp_motion_buffer_get_type ())
+#define GIMP_MOTION_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MOTION_BUFFER, GimpMotionBuffer))
+#define GIMP_MOTION_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MOTION_BUFFER, GimpMotionBufferClass))
+#define GIMP_IS_MOTION_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MOTION_BUFFER))
+#define GIMP_IS_MOTION_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MOTION_BUFFER))
+#define GIMP_MOTION_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MOTION_BUFFER, GimpMotionBufferClass))
+
+
+typedef struct _GimpMotionBufferClass GimpMotionBufferClass;
+
+struct _GimpMotionBuffer
+{
+ GimpObject parent_instance;
+
+ guint32 last_read_motion_time;
+
+ guint32 last_motion_time; /* previous time of a forwarded motion event */
+ gdouble last_motion_delta_time;
+ gdouble last_motion_delta_x;
+ gdouble last_motion_delta_y;
+ gdouble last_motion_distance;
+
+ GimpCoords last_coords; /* last motion event */
+
+ GArray *event_history;
+ GArray *event_queue;
+ gboolean event_delay; /* TRUE if there's an unsent event in
+ * the history buffer
+ */
+
+ gint event_delay_timeout;
+ GdkModifierType last_active_state;
+};
+
+struct _GimpMotionBufferClass
+{
+ GimpObjectClass parent_class;
+
+ void (* stroke) (GimpMotionBuffer *buffer,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+ void (* hover) (GimpMotionBuffer *buffer,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+};
+
+
+GType gimp_motion_buffer_get_type (void) G_GNUC_CONST;
+
+GimpMotionBuffer * gimp_motion_buffer_new (void);
+
+void gimp_motion_buffer_begin_stroke (GimpMotionBuffer *buffer,
+ guint32 time,
+ GimpCoords *last_motion);
+void gimp_motion_buffer_end_stroke (GimpMotionBuffer *buffer);
+
+gboolean gimp_motion_buffer_motion_event (GimpMotionBuffer *buffer,
+ GimpCoords *coords,
+ guint32 time,
+ gboolean event_fill);
+guint32 gimp_motion_buffer_get_last_motion_time (GimpMotionBuffer *buffer);
+
+void gimp_motion_buffer_request_stroke (GimpMotionBuffer *buffer,
+ GdkModifierType state,
+ guint32 time);
+void gimp_motion_buffer_request_hover (GimpMotionBuffer *buffer,
+ GdkModifierType state,
+ gboolean proximity);
+
+
+#endif /* __GIMP_MOTION_BUFFER_H__ */
diff --git a/app/display/gimpmultiwindowstrategy.c b/app/display/gimpmultiwindowstrategy.c
new file mode 100644
index 0000000..ca5cb22
--- /dev/null
+++ b/app/display/gimpmultiwindowstrategy.c
@@ -0,0 +1,89 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmultiwindowstrategy.c
+ * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * 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 "display-types.h"
+
+#include "core/gimp.h"
+
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpwindowstrategy.h"
+
+#include "gimpmultiwindowstrategy.h"
+
+
+static void gimp_multi_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface);
+static GtkWidget * gimp_multi_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy,
+ Gimp *gimp,
+ GimpDialogFactory *factory,
+ GdkScreen *screen,
+ gint monitor,
+ const gchar *identifiers);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpMultiWindowStrategy, gimp_multi_window_strategy, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_WINDOW_STRATEGY,
+ gimp_multi_window_strategy_window_strategy_iface_init))
+
+#define parent_class gimp_multi_window_strategy_parent_class
+
+
+static void
+gimp_multi_window_strategy_class_init (GimpMultiWindowStrategyClass *klass)
+{
+}
+
+static void
+gimp_multi_window_strategy_init (GimpMultiWindowStrategy *strategy)
+{
+}
+
+static void
+gimp_multi_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface)
+{
+ iface->show_dockable_dialog = gimp_multi_window_strategy_show_dockable_dialog;
+}
+
+static GtkWidget *
+gimp_multi_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy,
+ Gimp *gimp,
+ GimpDialogFactory *factory,
+ GdkScreen *screen,
+ gint monitor,
+ const gchar *identifiers)
+{
+ return gimp_dialog_factory_dialog_raise (factory, screen, monitor,
+ identifiers, -1);
+}
+
+GimpObject *
+gimp_multi_window_strategy_get_singleton (void)
+{
+ static GimpObject *singleton = NULL;
+
+ if (! singleton)
+ singleton = g_object_new (GIMP_TYPE_MULTI_WINDOW_STRATEGY, NULL);
+
+ return singleton;
+}
diff --git a/app/display/gimpmultiwindowstrategy.h b/app/display/gimpmultiwindowstrategy.h
new file mode 100644
index 0000000..4df8873
--- /dev/null
+++ b/app/display/gimpmultiwindowstrategy.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmultiwindowstrategy.h
+ * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_MULTI_WINDOW_STRATEGY_H__
+#define __GIMP_MULTI_WINDOW_STRATEGY_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_MULTI_WINDOW_STRATEGY (gimp_multi_window_strategy_get_type ())
+#define GIMP_MULTI_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MULTI_WINDOW_STRATEGY, GimpMultiWindowStrategy))
+#define GIMP_MULTI_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MULTI_WINDOW_STRATEGY, GimpMultiWindowStrategyClass))
+#define GIMP_IS_MULTI_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MULTI_WINDOW_STRATEGY))
+#define GIMP_IS_MULTI_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MULTI_WINDOW_STRATEGY))
+#define GIMP_MULTI_WINDOW_STRATEGY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MULTI_WINDOW_STRATEGY, GimpMultiWindowStrategyClass))
+
+
+typedef struct _GimpMultiWindowStrategyClass GimpMultiWindowStrategyClass;
+
+struct _GimpMultiWindowStrategy
+{
+ GimpObject parent_instance;
+};
+
+struct _GimpMultiWindowStrategyClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_multi_window_strategy_get_type (void) G_GNUC_CONST;
+
+GimpObject * gimp_multi_window_strategy_get_singleton (void);
+
+
+#endif /* __GIMP_MULTI_WINDOW_STRATEGY_H__ */
diff --git a/app/display/gimpnavigationeditor.c b/app/display/gimpnavigationeditor.c
new file mode 100644
index 0000000..a518882
--- /dev/null
+++ b/app/display/gimpnavigationeditor.c
@@ -0,0 +1,890 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpnavigationeditor.c
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * partly based on app/nav_window
+ * Copyright (C) 1999 Andy Thomas <alt@gimp.org>
+ *
+ * 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 "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpimageproxy.h"
+
+#include "widgets/gimpdocked.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpmenufactory.h"
+#include "widgets/gimpnavigationview.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpviewrenderer.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpnavigationeditor.h"
+
+#include "gimp-intl.h"
+
+
+#define UPDATE_DELAY 300 /* From GtkRange in GTK+ 2.22 */
+
+
+static void gimp_navigation_editor_docked_iface_init (GimpDockedInterface *iface);
+
+static void gimp_navigation_editor_dispose (GObject *object);
+
+static void gimp_navigation_editor_set_context (GimpDocked *docked,
+ GimpContext *context);
+
+static GtkWidget * gimp_navigation_editor_new_private (GimpMenuFactory *menu_factory,
+ GimpDisplayShell *shell);
+
+static void gimp_navigation_editor_set_shell (GimpNavigationEditor *editor,
+ GimpDisplayShell *shell);
+static gboolean gimp_navigation_editor_button_release (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell);
+static void gimp_navigation_editor_marker_changed (GimpNavigationView *view,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble width,
+ gdouble height,
+ GimpNavigationEditor *editor);
+static void gimp_navigation_editor_zoom (GimpNavigationView *view,
+ GimpZoomType direction,
+ GimpNavigationEditor *editor);
+static void gimp_navigation_editor_scroll (GimpNavigationView *view,
+ GdkScrollDirection direction,
+ GimpNavigationEditor *editor);
+
+static void gimp_navigation_editor_zoom_adj_changed (GtkAdjustment *adj,
+ GimpNavigationEditor *editor);
+
+static void gimp_navigation_editor_shell_infinite_canvas_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpNavigationEditor *editor);
+static void gimp_navigation_editor_shell_scaled (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor);
+static void gimp_navigation_editor_shell_scrolled (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor);
+static void gimp_navigation_editor_shell_rotated (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor);
+static void gimp_navigation_editor_shell_reconnect (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor);
+
+static void gimp_navigation_editor_viewable_size_changed (GimpViewable *viewable,
+ GimpNavigationEditor *editor);
+
+static void gimp_navigation_editor_options_show_canvas_notify (GimpDisplayOptions *options,
+ const GParamSpec *pspec,
+ GimpNavigationEditor *editor);
+
+static void gimp_navigation_editor_update_marker (GimpNavigationEditor *editor);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpNavigationEditor, gimp_navigation_editor,
+ GIMP_TYPE_EDITOR,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
+ gimp_navigation_editor_docked_iface_init))
+
+#define parent_class gimp_navigation_editor_parent_class
+
+
+static void
+gimp_navigation_editor_class_init (GimpNavigationEditorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_navigation_editor_dispose;
+}
+
+static void
+gimp_navigation_editor_docked_iface_init (GimpDockedInterface *iface)
+{
+ iface->set_context = gimp_navigation_editor_set_context;
+}
+
+static void
+gimp_navigation_editor_init (GimpNavigationEditor *editor)
+{
+ GtkWidget *frame;
+
+ editor->context = NULL;
+ editor->shell = NULL;
+ editor->scale_timeout = 0;
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ editor->view = gimp_view_new_by_types (NULL,
+ GIMP_TYPE_NAVIGATION_VIEW,
+ GIMP_TYPE_IMAGE_PROXY,
+ GIMP_VIEW_SIZE_MEDIUM, 0, TRUE);
+ gtk_container_add (GTK_CONTAINER (frame), editor->view);
+ gtk_widget_show (editor->view);
+
+ g_signal_connect (editor->view, "marker-changed",
+ G_CALLBACK (gimp_navigation_editor_marker_changed),
+ editor);
+ g_signal_connect (editor->view, "zoom",
+ G_CALLBACK (gimp_navigation_editor_zoom),
+ editor);
+ g_signal_connect (editor->view, "scroll",
+ G_CALLBACK (gimp_navigation_editor_scroll),
+ editor);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
+}
+
+static void
+gimp_navigation_editor_dispose (GObject *object)
+{
+ GimpNavigationEditor *editor = GIMP_NAVIGATION_EDITOR (object);
+
+ if (editor->shell)
+ gimp_navigation_editor_set_shell (editor, NULL);
+
+ if (editor->scale_timeout)
+ {
+ g_source_remove (editor->scale_timeout);
+ editor->scale_timeout = 0;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_navigation_editor_display_changed (GimpContext *context,
+ GimpDisplay *display,
+ GimpNavigationEditor *editor)
+{
+ GimpDisplayShell *shell = NULL;
+
+ if (display && gimp_display_get_image (display))
+ shell = gimp_display_get_shell (display);
+
+ gimp_navigation_editor_set_shell (editor, shell);
+}
+
+static void
+gimp_navigation_editor_image_chaged (GimpContext *context,
+ GimpImage *image,
+ GimpNavigationEditor *editor)
+{
+ GimpDisplay *display = gimp_context_get_display (context);
+ GimpDisplayShell *shell = NULL;
+
+ if (display && image)
+ shell = gimp_display_get_shell (display);
+
+ gimp_navigation_editor_set_shell (editor, shell);
+}
+
+static void
+gimp_navigation_editor_set_context (GimpDocked *docked,
+ GimpContext *context)
+{
+ GimpNavigationEditor *editor = GIMP_NAVIGATION_EDITOR (docked);
+ GimpDisplay *display = NULL;
+
+ if (editor->context)
+ {
+ g_signal_handlers_disconnect_by_func (editor->context,
+ gimp_navigation_editor_display_changed,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->context,
+ gimp_navigation_editor_image_chaged,
+ editor);
+ }
+
+ editor->context = context;
+
+ if (editor->context)
+ {
+ g_signal_connect (context, "display-changed",
+ G_CALLBACK (gimp_navigation_editor_display_changed),
+ editor);
+ /* make sure to also call gimp_navigation_editor_set_shell() when the
+ * last image is closed, even though the display isn't changed, so that
+ * the editor is properly cleared.
+ */
+ g_signal_connect (context, "image-changed",
+ G_CALLBACK (gimp_navigation_editor_image_chaged),
+ editor);
+
+ display = gimp_context_get_display (context);
+ }
+
+ gimp_view_renderer_set_context (GIMP_VIEW (editor->view)->renderer,
+ context);
+
+ gimp_navigation_editor_display_changed (editor->context,
+ display,
+ editor);
+}
+
+
+/* public functions */
+
+GtkWidget *
+gimp_navigation_editor_new (GimpMenuFactory *menu_factory)
+{
+ return gimp_navigation_editor_new_private (menu_factory, NULL);
+}
+
+void
+gimp_navigation_editor_popup (GimpDisplayShell *shell,
+ GtkWidget *widget,
+ gint click_x,
+ gint click_y)
+{
+ GtkStyle *style = gtk_widget_get_style (widget);
+ GimpNavigationEditor *editor;
+ GimpNavigationView *view;
+ GdkScreen *screen;
+ gint x, y;
+ gint view_marker_center_x, view_marker_center_y;
+ gint view_marker_width, view_marker_height;
+
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (! shell->nav_popup)
+ {
+ GtkWidget *frame;
+
+ shell->nav_popup = gtk_window_new (GTK_WINDOW_POPUP);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (shell->nav_popup), frame);
+ gtk_widget_show (frame);
+
+ editor =
+ GIMP_NAVIGATION_EDITOR (gimp_navigation_editor_new_private (NULL,
+ shell));
+ gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (editor));
+ gtk_widget_show (GTK_WIDGET (editor));
+
+ g_signal_connect (editor->view, "button-release-event",
+ G_CALLBACK (gimp_navigation_editor_button_release),
+ shell);
+ }
+ else
+ {
+ GtkWidget *bin = gtk_bin_get_child (GTK_BIN (shell->nav_popup));
+
+ editor = GIMP_NAVIGATION_EDITOR (gtk_bin_get_child (GTK_BIN (bin)));
+ }
+
+ view = GIMP_NAVIGATION_VIEW (editor->view);
+
+ /* Set poup screen */
+ screen = gtk_widget_get_screen (widget);
+ gtk_window_set_screen (GTK_WINDOW (shell->nav_popup), screen);
+
+ gimp_navigation_view_get_local_marker (view,
+ &view_marker_center_x,
+ &view_marker_center_y,
+ &view_marker_width,
+ &view_marker_height);
+ /* Position the popup */
+ {
+ gint x_origin, y_origin;
+ gint popup_width, popup_height;
+ gint border_width, border_height;
+ gint screen_click_x, screen_click_y;
+
+ gdk_window_get_origin (gtk_widget_get_window (widget),
+ &x_origin, &y_origin);
+
+ screen_click_x = x_origin + click_x;
+ screen_click_y = y_origin + click_y;
+ border_width = 2 * style->xthickness;
+ border_height = 2 * style->ythickness;
+ popup_width = GIMP_VIEW (view)->renderer->width - 2 * border_width;
+ popup_height = GIMP_VIEW (view)->renderer->height - 2 * border_height;
+
+ x = screen_click_x -
+ border_width -
+ view_marker_center_x;
+
+ y = screen_click_y -
+ border_height -
+ view_marker_center_y;
+
+ /* When the image is zoomed out and overscrolled, the above
+ * calculation risks positioning the popup far far away from the
+ * click coordinate. We don't want that, so perform some clamping.
+ */
+ x = CLAMP (x, screen_click_x - popup_width, screen_click_x);
+ y = CLAMP (y, screen_click_y - popup_height, screen_click_y);
+
+ /* If the popup doesn't fit into the screen, we have a problem.
+ * We move the popup onscreen and risk that the pointer is not
+ * in the square representing the viewable area anymore. Moving
+ * the pointer will make the image scroll by a large amount,
+ * but then it works as usual. Probably better than a popup that
+ * is completely unusable in the lower right of the screen.
+ *
+ * Warping the pointer would be another solution ...
+ */
+ x = CLAMP (x, 0, gdk_screen_get_width (screen) - popup_width);
+ y = CLAMP (y, 0, gdk_screen_get_height (screen) - popup_height);
+
+ gtk_window_move (GTK_WINDOW (shell->nav_popup), x, y);
+ }
+
+ gtk_widget_show (shell->nav_popup);
+ gdk_flush ();
+
+ /* fill in then grab pointer */
+ gimp_navigation_view_set_motion_offset (view, 0, 0);
+ gimp_navigation_view_grab_pointer (view);
+}
+
+
+/* private functions */
+
+static GtkWidget *
+gimp_navigation_editor_new_private (GimpMenuFactory *menu_factory,
+ GimpDisplayShell *shell)
+{
+ GimpNavigationEditor *editor;
+
+ g_return_val_if_fail (menu_factory == NULL ||
+ GIMP_IS_MENU_FACTORY (menu_factory), NULL);
+ g_return_val_if_fail (shell == NULL || GIMP_IS_DISPLAY_SHELL (shell), NULL);
+ g_return_val_if_fail (menu_factory || shell, NULL);
+
+ if (shell)
+ {
+ Gimp *gimp = shell->display->gimp;
+ GimpDisplayConfig *config = shell->display->config;
+ GimpView *view;
+
+ editor = g_object_new (GIMP_TYPE_NAVIGATION_EDITOR, NULL);
+
+ view = GIMP_VIEW (editor->view);
+
+ gimp_view_renderer_set_size (view->renderer,
+ config->nav_preview_size * 3,
+ view->renderer->border_width);
+ gimp_view_renderer_set_context (view->renderer,
+ gimp_get_user_context (gimp));
+ gimp_view_renderer_set_color_config (view->renderer,
+ gimp_display_shell_get_color_config (shell));
+
+ gimp_navigation_editor_set_shell (editor, shell);
+
+ }
+ else
+ {
+ GtkWidget *hscale;
+ GtkWidget *hbox;
+
+ editor = g_object_new (GIMP_TYPE_NAVIGATION_EDITOR,
+ "menu-factory", menu_factory,
+ "menu-identifier", "<NavigationEditor>",
+ NULL);
+
+ gtk_widget_set_size_request (editor->view,
+ GIMP_VIEW_SIZE_HUGE,
+ GIMP_VIEW_SIZE_HUGE);
+ gimp_view_set_expand (GIMP_VIEW (editor->view), TRUE);
+
+ /* the editor buttons */
+
+ editor->zoom_out_button =
+ gimp_editor_add_action_button (GIMP_EDITOR (editor), "view",
+ "view-zoom-out", NULL);
+
+ editor->zoom_in_button =
+ gimp_editor_add_action_button (GIMP_EDITOR (editor), "view",
+ "view-zoom-in", NULL);
+
+ editor->zoom_100_button =
+ gimp_editor_add_action_button (GIMP_EDITOR (editor), "view",
+ "view-zoom-1-1", NULL);
+
+ editor->zoom_fit_in_button =
+ gimp_editor_add_action_button (GIMP_EDITOR (editor), "view",
+ "view-zoom-fit-in", NULL);
+
+ editor->zoom_fill_button =
+ gimp_editor_add_action_button (GIMP_EDITOR (editor), "view",
+ "view-zoom-fill", NULL);
+
+ editor->shrink_wrap_button =
+ gimp_editor_add_action_button (GIMP_EDITOR (editor), "view",
+ "view-shrink-wrap", NULL);
+
+ /* the zoom scale */
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_end (GTK_BOX (editor), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ editor->zoom_adjustment =
+ GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -8.0, 8.0, 0.5, 1.0, 0.0));
+
+ g_signal_connect (editor->zoom_adjustment, "value-changed",
+ G_CALLBACK (gimp_navigation_editor_zoom_adj_changed),
+ editor);
+
+ hscale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL,
+ editor->zoom_adjustment);
+ gtk_scale_set_draw_value (GTK_SCALE (hscale), FALSE);
+ gtk_box_pack_start (GTK_BOX (hbox), hscale, TRUE, TRUE, 0);
+ gtk_widget_show (hscale);
+
+ /* the zoom label */
+
+ editor->zoom_label = gtk_label_new ("100%");
+ gtk_label_set_width_chars (GTK_LABEL (editor->zoom_label), 7);
+ gtk_box_pack_start (GTK_BOX (hbox), editor->zoom_label, FALSE, FALSE, 0);
+ gtk_widget_show (editor->zoom_label);
+ }
+
+ gimp_view_renderer_set_background (GIMP_VIEW (editor->view)->renderer,
+ GIMP_ICON_TEXTURE);
+
+ return GTK_WIDGET (editor);
+}
+
+static void
+gimp_navigation_editor_set_shell (GimpNavigationEditor *editor,
+ GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_NAVIGATION_EDITOR (editor));
+ g_return_if_fail (! shell || GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell == editor->shell)
+ return;
+
+ if (editor->shell)
+ {
+ g_signal_handlers_disconnect_by_func (editor->shell,
+ gimp_navigation_editor_shell_infinite_canvas_notify,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->shell,
+ gimp_navigation_editor_shell_scaled,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->shell,
+ gimp_navigation_editor_shell_scrolled,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->shell,
+ gimp_navigation_editor_shell_rotated,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->shell,
+ gimp_navigation_editor_shell_reconnect,
+ editor);
+
+ g_signal_handlers_disconnect_by_func (editor->shell->options,
+ gimp_navigation_editor_options_show_canvas_notify,
+ editor);
+ g_signal_handlers_disconnect_by_func (editor->shell->fullscreen_options,
+ gimp_navigation_editor_options_show_canvas_notify,
+ editor);
+ }
+ else if (shell)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
+ }
+
+ editor->shell = shell;
+
+ if (editor->shell)
+ {
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ g_clear_object (&editor->image_proxy);
+
+ if (image)
+ {
+ editor->image_proxy = gimp_image_proxy_new (image);
+
+ g_signal_connect (
+ editor->image_proxy, "size-changed",
+ G_CALLBACK (gimp_navigation_editor_viewable_size_changed),
+ editor);
+ }
+
+ gimp_view_set_viewable (GIMP_VIEW (editor->view),
+ GIMP_VIEWABLE (editor->image_proxy));
+
+ g_signal_connect (editor->shell, "notify::infinite-canvas",
+ G_CALLBACK (gimp_navigation_editor_shell_infinite_canvas_notify),
+ editor);
+ g_signal_connect (editor->shell, "scaled",
+ G_CALLBACK (gimp_navigation_editor_shell_scaled),
+ editor);
+ g_signal_connect (editor->shell, "scrolled",
+ G_CALLBACK (gimp_navigation_editor_shell_scrolled),
+ editor);
+ g_signal_connect (editor->shell, "rotated",
+ G_CALLBACK (gimp_navigation_editor_shell_rotated),
+ editor);
+ g_signal_connect (editor->shell, "reconnect",
+ G_CALLBACK (gimp_navigation_editor_shell_reconnect),
+ editor);
+
+ g_signal_connect (editor->shell->options, "notify::show-canvas-boundary",
+ G_CALLBACK (gimp_navigation_editor_options_show_canvas_notify),
+ editor);
+ g_signal_connect (editor->shell->fullscreen_options, "notify::show-canvas-boundary",
+ G_CALLBACK (gimp_navigation_editor_options_show_canvas_notify),
+ editor);
+
+ gimp_navigation_editor_shell_scaled (editor->shell, editor);
+ }
+ else
+ {
+ gimp_view_set_viewable (GIMP_VIEW (editor->view), NULL);
+ gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
+
+ g_clear_object (&editor->image_proxy);
+ }
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static gboolean
+gimp_navigation_editor_button_release (GtkWidget *widget,
+ GdkEventButton *bevent,
+ GimpDisplayShell *shell)
+{
+ if (bevent->button == 1)
+ {
+ gtk_widget_hide (shell->nav_popup);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_navigation_editor_marker_changed (GimpNavigationView *view,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble width,
+ gdouble height,
+ GimpNavigationEditor *editor)
+{
+ GimpViewRenderer *renderer = GIMP_VIEW (editor->view)->renderer;
+
+ if (editor->shell)
+ {
+ if (gimp_display_get_image (editor->shell->display))
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_image_proxy_get_bounding_box (
+ GIMP_IMAGE_PROXY (renderer->viewable));
+
+ center_x += bounding_box.x;
+ center_y += bounding_box.y;
+
+ gimp_display_shell_scroll_center_image_xy (editor->shell,
+ center_x, center_y);
+ }
+ }
+}
+
+static void
+gimp_navigation_editor_zoom (GimpNavigationView *view,
+ GimpZoomType direction,
+ GimpNavigationEditor *editor)
+{
+ g_return_if_fail (direction != GIMP_ZOOM_TO);
+
+ if (editor->shell)
+ {
+ if (gimp_display_get_image (editor->shell->display))
+ gimp_display_shell_scale (editor->shell,
+ direction,
+ 0.0,
+ GIMP_ZOOM_FOCUS_BEST_GUESS);
+ }
+}
+
+static void
+gimp_navigation_editor_scroll (GimpNavigationView *view,
+ GdkScrollDirection direction,
+ GimpNavigationEditor *editor)
+{
+ if (editor->shell)
+ {
+ GtkAdjustment *adj = NULL;
+ gdouble value;
+
+ switch (direction)
+ {
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ adj = editor->shell->hsbdata;
+ break;
+
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_DOWN:
+ adj = editor->shell->vsbdata;
+ break;
+ }
+
+ gimp_assert (adj != NULL);
+
+ value = gtk_adjustment_get_value (adj);
+
+ switch (direction)
+ {
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_UP:
+ value -= gtk_adjustment_get_page_increment (adj) / 2;
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ case GDK_SCROLL_DOWN:
+ value += gtk_adjustment_get_page_increment (adj) / 2;
+ break;
+ }
+
+ value = CLAMP (value,
+ gtk_adjustment_get_lower (adj),
+ gtk_adjustment_get_upper (adj) -
+ gtk_adjustment_get_page_size (adj));
+
+ gtk_adjustment_set_value (adj, value);
+ }
+}
+
+static gboolean
+gimp_navigation_editor_zoom_adj_changed_timeout (gpointer data)
+{
+ GimpNavigationEditor *editor = GIMP_NAVIGATION_EDITOR (data);
+ GtkAdjustment *adj = editor->zoom_adjustment;
+
+ if (gimp_display_get_image (editor->shell->display))
+ gimp_display_shell_scale (editor->shell,
+ GIMP_ZOOM_TO,
+ pow (2.0, gtk_adjustment_get_value (adj)),
+ GIMP_ZOOM_FOCUS_BEST_GUESS);
+
+ editor->scale_timeout = 0;
+
+ return FALSE;
+}
+
+static void
+gimp_navigation_editor_zoom_adj_changed (GtkAdjustment *adj,
+ GimpNavigationEditor *editor)
+{
+ if (editor->scale_timeout)
+ g_source_remove (editor->scale_timeout);
+
+ editor->scale_timeout =
+ g_timeout_add (UPDATE_DELAY,
+ gimp_navigation_editor_zoom_adj_changed_timeout,
+ editor);
+}
+
+static void
+gimp_navigation_editor_shell_infinite_canvas_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpNavigationEditor *editor)
+{
+ gimp_navigation_editor_update_marker (editor);
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static void
+gimp_navigation_editor_shell_scaled (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor)
+{
+ if (editor->zoom_label)
+ {
+ gchar *str;
+
+ g_object_get (shell->zoom,
+ "percentage", &str,
+ NULL);
+ gtk_label_set_text (GTK_LABEL (editor->zoom_label), str);
+ g_free (str);
+ }
+
+ if (editor->zoom_adjustment)
+ {
+ gdouble val;
+
+ val = log (gimp_zoom_model_get_factor (shell->zoom)) / G_LN2;
+
+ g_signal_handlers_block_by_func (editor->zoom_adjustment,
+ gimp_navigation_editor_zoom_adj_changed,
+ editor);
+
+ gtk_adjustment_set_value (editor->zoom_adjustment, val);
+
+ g_signal_handlers_unblock_by_func (editor->zoom_adjustment,
+ gimp_navigation_editor_zoom_adj_changed,
+ editor);
+ }
+
+ gimp_navigation_editor_update_marker (editor);
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static void
+gimp_navigation_editor_shell_scrolled (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor)
+{
+ gimp_navigation_editor_update_marker (editor);
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static void
+gimp_navigation_editor_shell_rotated (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor)
+{
+ gimp_navigation_editor_update_marker (editor);
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static void
+gimp_navigation_editor_viewable_size_changed (GimpViewable *viewable,
+ GimpNavigationEditor *editor)
+{
+ gimp_navigation_editor_update_marker (editor);
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static void
+gimp_navigation_editor_options_show_canvas_notify (GimpDisplayOptions *options,
+ const GParamSpec *pspec,
+ GimpNavigationEditor *editor)
+{
+ gimp_navigation_editor_update_marker (editor);
+}
+
+static void
+gimp_navigation_editor_shell_reconnect (GimpDisplayShell *shell,
+ GimpNavigationEditor *editor)
+{
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ g_clear_object (&editor->image_proxy);
+
+ if (image)
+ {
+ editor->image_proxy = gimp_image_proxy_new (image);
+
+ g_signal_connect (
+ editor->image_proxy, "size-changed",
+ G_CALLBACK (gimp_navigation_editor_viewable_size_changed),
+ editor);
+ }
+
+ gimp_view_set_viewable (GIMP_VIEW (editor->view),
+ GIMP_VIEWABLE (editor->image_proxy));
+
+ if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)))
+ gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)),
+ gimp_editor_get_popup_data (GIMP_EDITOR (editor)));
+}
+
+static void
+gimp_navigation_editor_update_marker (GimpNavigationEditor *editor)
+{
+ GimpViewRenderer *renderer = GIMP_VIEW (editor->view)->renderer;
+ GimpDisplayShell *shell = editor->shell;
+
+ if (renderer->dot_for_dot != shell->dot_for_dot)
+ gimp_view_renderer_set_dot_for_dot (renderer, shell->dot_for_dot);
+
+ if (renderer->viewable)
+ {
+ GimpNavigationView *view = GIMP_NAVIGATION_VIEW (editor->view);
+ GimpImage *image;
+ GeglRectangle bounding_box;
+ gdouble x, y;
+ gdouble w, h;
+
+ image = gimp_image_proxy_get_image (
+ GIMP_IMAGE_PROXY (renderer->viewable));
+
+ gimp_image_proxy_set_show_all (
+ GIMP_IMAGE_PROXY (renderer->viewable),
+ gimp_display_shell_get_infinite_canvas (shell));
+
+ bounding_box = gimp_image_proxy_get_bounding_box (
+ GIMP_IMAGE_PROXY (renderer->viewable));
+
+ gimp_display_shell_scroll_get_viewport (shell, &x, &y, &w, &h);
+ gimp_display_shell_untransform_xy_f (shell,
+ shell->disp_width / 2,
+ shell->disp_height / 2,
+ &x, &y);
+
+ x -= bounding_box.x;
+ y -= bounding_box.y;
+
+ gimp_navigation_view_set_marker (view,
+ x, y, w, h,
+ shell->flip_horizontally,
+ shell->flip_vertically,
+ shell->rotate_angle);
+
+ gimp_navigation_view_set_canvas (
+ view,
+ gimp_display_shell_get_infinite_canvas (shell) &&
+ gimp_display_shell_get_show_canvas (shell),
+ -bounding_box.x, -bounding_box.y,
+ gimp_image_get_width (image), gimp_image_get_height (image));
+ }
+}
diff --git a/app/display/gimpnavigationeditor.h b/app/display/gimpnavigationeditor.h
new file mode 100644
index 0000000..8e876c2
--- /dev/null
+++ b/app/display/gimpnavigationeditor.h
@@ -0,0 +1,79 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpnavigationeditor.h
+ * Copyright (C) 2002 Michael Natterer <mitch@gimp.org>
+ *
+ * partly based on app/nav_window
+ * Copyright (C) 1999 Andy Thomas <alt@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_NAVIGATION_EDITOR_H__
+#define __GIMP_NAVIGATION_EDITOR_H__
+
+
+#include "widgets/gimpeditor.h"
+
+
+#define GIMP_TYPE_NAVIGATION_EDITOR (gimp_navigation_editor_get_type ())
+#define GIMP_NAVIGATION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_NAVIGATION_EDITOR, GimpNavigationEditor))
+#define GIMP_NAVIGATION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_NAVIGATION_EDITOR, GimpNavigationEditorClass))
+#define GIMP_IS_NAVIGATION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_NAVIGATION_EDITOR))
+#define GIMP_IS_NAVIGATION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_NAVIGATION_EDITOR))
+#define GIMP_NAVIGATION_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_NAVIGATION_EDITOR, GimpNavigationEditorClass))
+
+
+typedef struct _GimpNavigationEditorClass GimpNavigationEditorClass;
+
+struct _GimpNavigationEditor
+{
+ GimpEditor parent_instance;
+
+ GimpContext *context;
+ GimpDisplayShell *shell;
+
+ GimpImageProxy *image_proxy;
+
+ GtkWidget *view;
+ GtkWidget *zoom_label;
+ GtkAdjustment *zoom_adjustment;
+
+ GtkWidget *zoom_out_button;
+ GtkWidget *zoom_in_button;
+ GtkWidget *zoom_100_button;
+ GtkWidget *zoom_fit_in_button;
+ GtkWidget *zoom_fill_button;
+ GtkWidget *shrink_wrap_button;
+
+ guint scale_timeout;
+};
+
+struct _GimpNavigationEditorClass
+{
+ GimpEditorClass parent_class;
+};
+
+
+GType gimp_navigation_editor_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_navigation_editor_new (GimpMenuFactory *menu_factory);
+void gimp_navigation_editor_popup (GimpDisplayShell *shell,
+ GtkWidget *widget,
+ gint click_x,
+ gint click_y);
+
+
+#endif /* __GIMP_NAVIGATION_EDITOR_H__ */
diff --git a/app/display/gimpscalecombobox.c b/app/display/gimpscalecombobox.c
new file mode 100644
index 0000000..3a5bba0
--- /dev/null
+++ b/app/display/gimpscalecombobox.c
@@ -0,0 +1,562 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpscalecombobox.c
+ * Copyright (C) 2004, 2008 Sven Neumann <sven@gimp.org>
+ *
+ * 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 "stdlib.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include "gdk/gdkkeysyms.h"
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimpmarshal.h"
+
+#include "gimpscalecombobox.h"
+
+
+#define MAX_ITEMS 10
+
+enum
+{
+ COLUMN_SCALE,
+ COLUMN_LABEL,
+ COLUMN_PERSISTENT,
+ N_COLUMNS
+};
+
+enum
+{
+ ENTRY_ACTIVATED,
+ LAST_SIGNAL
+};
+
+
+static void gimp_scale_combo_box_constructed (GObject *object);
+static void gimp_scale_combo_box_finalize (GObject *object);
+
+static void gimp_scale_combo_box_style_set (GtkWidget *widget,
+ GtkStyle *prev_style);
+
+static void gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box);
+static void gimp_scale_combo_box_entry_activate (GtkWidget *entry,
+ GimpScaleComboBox *combo_box);
+static gboolean gimp_scale_combo_box_entry_key_press (GtkWidget *entry,
+ GdkEventKey *event,
+ GimpScaleComboBox *combo_box);
+
+static void gimp_scale_combo_box_scale_iter_set (GtkListStore *store,
+ GtkTreeIter *iter,
+ gdouble scale,
+ gboolean persistent);
+
+
+G_DEFINE_TYPE (GimpScaleComboBox, gimp_scale_combo_box,
+ GTK_TYPE_COMBO_BOX)
+
+#define parent_class gimp_scale_combo_box_parent_class
+
+static guint scale_combo_box_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_scale_combo_box_class_init (GimpScaleComboBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ scale_combo_box_signals[ENTRY_ACTIVATED] =
+ g_signal_new ("entry-activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpScaleComboBoxClass, entry_activated),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = gimp_scale_combo_box_constructed;
+ object_class->finalize = gimp_scale_combo_box_finalize;
+
+ widget_class->style_set = gimp_scale_combo_box_style_set;
+
+ klass->entry_activated = NULL;
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_double ("label-scale",
+ NULL, NULL,
+ 0.0,
+ G_MAXDOUBLE,
+ 1.0,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_scale_combo_box_init (GimpScaleComboBox *combo_box)
+{
+ combo_box->scale = 1.0;
+ combo_box->last_path = NULL;
+}
+
+static void
+gimp_scale_combo_box_constructed (GObject *object)
+{
+ GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object);
+ GtkWidget *entry;
+ GtkListStore *store;
+ GtkCellLayout *layout;
+ GtkCellRenderer *cell;
+ GtkTreeIter iter;
+ GtkBorder border = { 0, 0, 0, 0 };
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ store = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_DOUBLE, /* SCALE */
+ G_TYPE_STRING, /* LABEL */
+ G_TYPE_BOOLEAN); /* PERSISTENT */
+
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo_box),
+ COLUMN_LABEL);
+
+ entry = gtk_bin_get_child (GTK_BIN (combo_box));
+
+ g_object_set (entry,
+ "xalign", 1.0,
+ "width-chars", 5,
+ "truncate-multiline", TRUE,
+ "inner-border", &border,
+ NULL);
+
+ layout = GTK_CELL_LAYOUT (combo_box);
+
+ cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
+ "xalign", 1.0,
+ NULL);
+
+ gtk_cell_layout_clear (layout);
+ gtk_cell_layout_pack_start (layout, cell, TRUE);
+ gtk_cell_layout_set_attributes (layout, cell,
+ "text", COLUMN_LABEL,
+ NULL);
+
+ for (i = 8; i > 0; i /= 2)
+ {
+ gtk_list_store_append (store, &iter);
+ gimp_scale_combo_box_scale_iter_set (store, &iter, i, TRUE);
+ }
+
+ for (i = 2; i <= 8; i *= 2)
+ {
+ gtk_list_store_append (store, &iter);
+ gimp_scale_combo_box_scale_iter_set (store, &iter, 1.0 / i, TRUE);
+ }
+
+ g_signal_connect (combo_box, "changed",
+ G_CALLBACK (gimp_scale_combo_box_changed),
+ NULL);
+
+ g_signal_connect (entry, "activate",
+ G_CALLBACK (gimp_scale_combo_box_entry_activate),
+ combo_box);
+ g_signal_connect (entry, "key-press-event",
+ G_CALLBACK (gimp_scale_combo_box_entry_key_press),
+ combo_box);
+}
+
+static void
+gimp_scale_combo_box_finalize (GObject *object)
+{
+ GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object);
+
+ if (combo_box->last_path)
+ {
+ gtk_tree_path_free (combo_box->last_path);
+ combo_box->last_path = NULL;
+ }
+
+ if (combo_box->mru)
+ {
+ g_list_free_full (combo_box->mru,
+ (GDestroyNotify) gtk_tree_row_reference_free);
+ combo_box->mru = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_scale_combo_box_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ GtkWidget *entry;
+ GtkRcStyle *rc_style;
+ PangoContext *context;
+ PangoFontDescription *font_desc;
+ gint font_size;
+ gdouble label_scale;
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
+
+ gtk_widget_style_get (widget, "label-scale", &label_scale, NULL);
+
+ entry = gtk_bin_get_child (GTK_BIN (widget));
+
+ rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (entry));
+
+ if (rc_style->font_desc)
+ pango_font_description_free (rc_style->font_desc);
+
+ context = gtk_widget_get_pango_context (widget);
+ font_desc = pango_context_get_font_description (context);
+ rc_style->font_desc = pango_font_description_copy (font_desc);
+
+ font_size = pango_font_description_get_size (rc_style->font_desc);
+ pango_font_description_set_size (rc_style->font_desc, label_scale * font_size);
+
+ gtk_widget_modify_style (GTK_WIDGET (entry), rc_style);
+}
+
+static void
+gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box)
+{
+ GtkTreeIter iter;
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
+ {
+ GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+ gdouble scale;
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_SCALE, &scale,
+ -1);
+ if (scale > 0.0)
+ {
+ combo_box->scale = scale;
+
+ if (combo_box->last_path)
+ gtk_tree_path_free (combo_box->last_path);
+
+ combo_box->last_path = gtk_tree_model_get_path (model, &iter);
+ }
+ }
+}
+
+static gboolean
+gimp_scale_combo_box_parse_text (const gchar *text,
+ gdouble *scale)
+{
+ gchar *end;
+ gdouble left_number;
+ gdouble right_number;
+
+ /* try to parse a number */
+ left_number = strtod (text, &end);
+
+ if (end == text)
+ return FALSE;
+ else
+ text = end;
+
+ /* skip over whitespace */
+ while (g_unichar_isspace (g_utf8_get_char (text)))
+ text = g_utf8_next_char (text);
+
+ if (*text == '\0' || *text == '%')
+ {
+ *scale = left_number / 100.0;
+ return TRUE;
+ }
+
+ /* check for a valid separator */
+ if (*text != '/' && *text != ':')
+ {
+ *scale = left_number;
+ return TRUE;
+ }
+
+ text = g_utf8_next_char (text);
+
+ /* skip over whitespace */
+ while (g_unichar_isspace (g_utf8_get_char (text)))
+ text = g_utf8_next_char (text);
+
+ /* try to parse another number */
+ right_number = strtod (text, &end);
+
+ if (end == text)
+ return FALSE;
+
+ if (right_number == 0.0)
+ return FALSE;
+
+ *scale = left_number / right_number;
+ return TRUE;
+}
+
+static void
+gimp_scale_combo_box_entry_activate (GtkWidget *entry,
+ GimpScaleComboBox *combo_box)
+{
+ const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry));
+ gdouble scale;
+
+ if (gimp_scale_combo_box_parse_text (text, &scale) &&
+ scale >= 1.0 / 256.0 &&
+ scale <= 256.0)
+ {
+ gimp_scale_combo_box_set_scale (combo_box, scale);
+ }
+ else
+ {
+ gtk_widget_error_bell (entry);
+
+ gimp_scale_combo_box_set_scale (combo_box, combo_box->scale);
+ }
+
+ g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0);
+}
+
+static gboolean
+gimp_scale_combo_box_entry_key_press (GtkWidget *entry,
+ GdkEventKey *event,
+ GimpScaleComboBox *combo_box)
+{
+ if (event->keyval == GDK_KEY_Escape)
+ {
+ gimp_scale_combo_box_set_scale (combo_box, combo_box->scale);
+
+ g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0);
+
+ return TRUE;
+ }
+
+ if (event->keyval == GDK_KEY_Tab ||
+ event->keyval == GDK_KEY_KP_Tab ||
+ event->keyval == GDK_KEY_ISO_Left_Tab)
+ {
+ gimp_scale_combo_box_entry_activate (entry, combo_box);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_scale_combo_box_scale_iter_set (GtkListStore *store,
+ GtkTreeIter *iter,
+ gdouble scale,
+ gboolean persistent)
+{
+ gchar label[32];
+
+#ifdef G_OS_WIN32
+
+ /* use a normal space until pango's windows backend uses harfbuzz,
+ * see bug #735505
+ */
+#define PERCENT_SPACE " "
+
+#else
+
+ /* use U+2009 THIN SPACE to separate the percent sign from the number */
+#define PERCENT_SPACE "\342\200\211"
+
+#endif
+
+ if (scale > 1.0)
+ g_snprintf (label, sizeof (label),
+ "%d" PERCENT_SPACE "%%", (gint) ROUND (100.0 * scale));
+ else
+ g_snprintf (label, sizeof (label),
+ "%.3g" PERCENT_SPACE "%%", 100.0 * scale);
+
+ gtk_list_store_set (store, iter,
+ COLUMN_SCALE, scale,
+ COLUMN_LABEL, label,
+ COLUMN_PERSISTENT, persistent,
+ -1);
+}
+
+static void
+gimp_scale_combo_box_mru_add (GimpScaleComboBox *combo_box,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+ GtkTreePath *path = gtk_tree_model_get_path (model, iter);
+ GList *list;
+ gboolean found;
+
+ for (list = combo_box->mru, found = FALSE; list && !found; list = list->next)
+ {
+ GtkTreePath *this = gtk_tree_row_reference_get_path (list->data);
+
+ if (gtk_tree_path_compare (this, path) == 0)
+ {
+ if (list->prev)
+ {
+ combo_box->mru = g_list_remove_link (combo_box->mru, list);
+ combo_box->mru = g_list_concat (list, combo_box->mru);
+ }
+
+ found = TRUE;
+ }
+
+ gtk_tree_path_free (this);
+ }
+
+ if (! found)
+ combo_box->mru = g_list_prepend (combo_box->mru,
+ gtk_tree_row_reference_new (model, path));
+
+ gtk_tree_path_free (path);
+}
+
+static void
+gimp_scale_combo_box_mru_remove_last (GimpScaleComboBox *combo_box)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GList *last;
+ GtkTreeIter iter;
+
+ if (! combo_box->mru)
+ return;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+
+ last = g_list_last (combo_box->mru);
+ path = gtk_tree_row_reference_get_path (last->data);
+
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ {
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ gtk_tree_row_reference_free (last->data);
+ combo_box->mru = g_list_delete_link (combo_box->mru, last);
+ }
+
+ gtk_tree_path_free (path);
+}
+
+
+/**
+ * gimp_scale_combo_box_new:
+ *
+ * Return value: a new #GimpScaleComboBox.
+ **/
+GtkWidget *
+gimp_scale_combo_box_new (void)
+{
+ return g_object_new (GIMP_TYPE_SCALE_COMBO_BOX,
+ "has-entry", TRUE,
+ NULL);
+}
+
+void
+gimp_scale_combo_box_set_scale (GimpScaleComboBox *combo_box,
+ gdouble scale)
+{
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkWidget *entry;
+ GtkTreeIter iter;
+ gboolean iter_valid;
+ gboolean persistent;
+ gint n_digits;
+
+ g_return_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box));
+ g_return_if_fail (scale > 0.0);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+ store = GTK_LIST_STORE (model);
+
+ for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
+ iter_valid;
+ iter_valid = gtk_tree_model_iter_next (model, &iter))
+ {
+ gdouble this;
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_SCALE, &this,
+ -1);
+
+ if (fabs (this - scale) < 0.0001)
+ break;
+ }
+
+ if (! iter_valid)
+ {
+ GtkTreeIter sibling;
+
+ for (iter_valid = gtk_tree_model_get_iter_first (model, &sibling);
+ iter_valid;
+ iter_valid = gtk_tree_model_iter_next (model, &sibling))
+ {
+ gdouble this;
+
+ gtk_tree_model_get (model, &sibling,
+ COLUMN_SCALE, &this,
+ -1);
+
+ if (this < scale)
+ break;
+ }
+
+ gtk_list_store_insert_before (store, &iter, iter_valid ? &sibling : NULL);
+ gimp_scale_combo_box_scale_iter_set (store, &iter, scale, FALSE);
+ }
+
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_PERSISTENT, &persistent,
+ -1);
+ if (! persistent)
+ {
+ gimp_scale_combo_box_mru_add (combo_box, &iter);
+
+ if (gtk_tree_model_iter_n_children (model, NULL) > MAX_ITEMS)
+ gimp_scale_combo_box_mru_remove_last (combo_box);
+ }
+
+ /* Update entry size appropriately. */
+ entry = gtk_bin_get_child (GTK_BIN (combo_box));
+ n_digits = (gint) floor (log10 (scale) + 1);
+
+ g_object_set (entry,
+ "width-chars", MAX (5, n_digits + 4),
+ NULL);
+}
+
+gdouble
+gimp_scale_combo_box_get_scale (GimpScaleComboBox *combo_box)
+{
+ g_return_val_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box), 1.0);
+
+ return combo_box->scale;
+}
diff --git a/app/display/gimpscalecombobox.h b/app/display/gimpscalecombobox.h
new file mode 100644
index 0000000..1c35e2f
--- /dev/null
+++ b/app/display/gimpscalecombobox.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpscalecombobox.h
+ * Copyright (C) 2004, 2008 Sven Neumann <sven@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_SCALE_COMBO_BOX_H__
+#define __GIMP_SCALE_COMBO_BOX_H__
+
+
+#define GIMP_TYPE_SCALE_COMBO_BOX (gimp_scale_combo_box_get_type ())
+#define GIMP_SCALE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SCALE_COMBO_BOX, GimpScaleComboBox))
+#define GIMP_SCALE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SCALE_COMBO_BOX, GimpScaleComboBoxClass))
+#define GIMP_IS_SCALE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SCALE_COMBO_BOX))
+#define GIMP_IS_SCALE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SCALE_COMBO_BOX))
+#define GIMP_SCALE_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SCALE_COMBO_BOX, GimpScaleComboBoxClass))
+
+
+typedef struct _GimpScaleComboBoxClass GimpScaleComboBoxClass;
+
+struct _GimpScaleComboBoxClass
+{
+ GtkComboBoxClass parent_instance;
+
+ void (* entry_activated) (GimpScaleComboBox *combo_box);
+};
+
+struct _GimpScaleComboBox
+{
+ GtkComboBox parent_instance;
+
+ gdouble scale;
+ GtkTreePath *last_path;
+ GList *mru;
+};
+
+
+GType gimp_scale_combo_box_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_scale_combo_box_new (void);
+void gimp_scale_combo_box_set_scale (GimpScaleComboBox *combo_box,
+ gdouble scale);
+gdouble gimp_scale_combo_box_get_scale (GimpScaleComboBox *combo_box);
+
+
+#endif /* __GIMP_SCALE_COMBO_BOX_H__ */
diff --git a/app/display/gimpsinglewindowstrategy.c b/app/display/gimpsinglewindowstrategy.c
new file mode 100644
index 0000000..e840dfa
--- /dev/null
+++ b/app/display/gimpsinglewindowstrategy.c
@@ -0,0 +1,157 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsinglewindowstrategy.c
+ * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "display-types.h"
+
+#include "core/gimp.h"
+
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpdock.h"
+#include "widgets/gimpdockbook.h"
+#include "widgets/gimpdockcolumns.h"
+#include "widgets/gimpwindowstrategy.h"
+
+#include "gimpimagewindow.h"
+#include "gimpsinglewindowstrategy.h"
+
+
+static void gimp_single_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface);
+static GtkWidget * gimp_single_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy,
+ Gimp *gimp,
+ GimpDialogFactory *factory,
+ GdkScreen *screen,
+ gint monitor,
+ const gchar *identifiers);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpSingleWindowStrategy, gimp_single_window_strategy, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_WINDOW_STRATEGY,
+ gimp_single_window_strategy_window_strategy_iface_init))
+
+#define parent_class gimp_single_window_strategy_parent_class
+
+
+static void
+gimp_single_window_strategy_class_init (GimpSingleWindowStrategyClass *klass)
+{
+}
+
+static void
+gimp_single_window_strategy_init (GimpSingleWindowStrategy *strategy)
+{
+}
+
+static void
+gimp_single_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface)
+{
+ iface->show_dockable_dialog = gimp_single_window_strategy_show_dockable_dialog;
+}
+
+static GtkWidget *
+gimp_single_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy,
+ Gimp *gimp,
+ GimpDialogFactory *factory,
+ GdkScreen *screen,
+ gint monitor,
+ const gchar *identifiers)
+{
+ GList *windows = gimp_get_image_windows (gimp);
+ GtkWidget *widget = NULL;
+ GimpImageWindow *window;
+
+ g_return_val_if_fail (windows != NULL, NULL);
+
+ /* In single-window mode, there should only be one window... */
+ window = GIMP_IMAGE_WINDOW (windows->data);
+
+ if (strcmp ("gimp-toolbox", identifiers) == 0)
+ {
+ /* Only allow one toolbox... */
+ if (! gimp_image_window_has_toolbox (window))
+ {
+ GimpDockColumns *columns;
+ GimpUIManager *ui_manager = gimp_image_window_get_ui_manager (window);
+
+ widget = gimp_dialog_factory_dialog_new (factory,
+ screen,
+ monitor,
+ ui_manager,
+ "gimp-toolbox",
+ -1 /*view_size*/,
+ FALSE /*present*/);
+ gtk_widget_show (widget);
+
+ columns = gimp_image_window_get_left_docks (window);
+ gimp_dock_columns_add_dock (columns,
+ GIMP_DOCK (widget),
+ -1 /*index*/);
+ }
+ }
+ else if (gimp_dialog_factory_find_widget (factory, identifiers))
+ {
+ /* if the dialog is already open, simply raise it */
+ return gimp_dialog_factory_dialog_raise (factory, screen, monitor,
+ identifiers, -1);
+ }
+ else
+ {
+ GtkWidget *dockbook;
+
+ dockbook = gimp_image_window_get_default_dockbook (window);
+
+ if (! dockbook)
+ {
+ GimpDockColumns *dock_columns;
+
+ /* No dock, need to add one */
+ dock_columns = gimp_image_window_get_right_docks (window);
+ gimp_dock_columns_prepare_dockbook (dock_columns,
+ -1 /*index*/,
+ &dockbook);
+ }
+
+ widget = gimp_dockbook_add_from_dialog_factory (GIMP_DOCKBOOK (dockbook),
+ identifiers,
+ -1 /*index*/);
+ }
+
+
+ g_list_free (windows);
+
+ return widget;
+}
+
+GimpObject *
+gimp_single_window_strategy_get_singleton (void)
+{
+ static GimpObject *singleton = NULL;
+
+ if (! singleton)
+ singleton = g_object_new (GIMP_TYPE_SINGLE_WINDOW_STRATEGY, NULL);
+
+ return singleton;
+}
diff --git a/app/display/gimpsinglewindowstrategy.h b/app/display/gimpsinglewindowstrategy.h
new file mode 100644
index 0000000..5b145c7
--- /dev/null
+++ b/app/display/gimpsinglewindowstrategy.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsinglewindowstrategy.h
+ * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_SINGLE_WINDOW_STRATEGY_H__
+#define __GIMP_SINGLE_WINDOW_STRATEGY_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_SINGLE_WINDOW_STRATEGY (gimp_single_window_strategy_get_type ())
+#define GIMP_SINGLE_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SINGLE_WINDOW_STRATEGY, GimpSingleWindowStrategy))
+#define GIMP_SINGLE_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SINGLE_WINDOW_STRATEGY, GimpSingleWindowStrategyClass))
+#define GIMP_IS_SINGLE_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SINGLE_WINDOW_STRATEGY))
+#define GIMP_IS_SINGLE_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SINGLE_WINDOW_STRATEGY))
+#define GIMP_SINGLE_WINDOW_STRATEGY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SINGLE_WINDOW_STRATEGY, GimpSingleWindowStrategyClass))
+
+
+typedef struct _GimpSingleWindowStrategyClass GimpSingleWindowStrategyClass;
+
+struct _GimpSingleWindowStrategy
+{
+ GimpObject parent_instance;
+};
+
+struct _GimpSingleWindowStrategyClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_single_window_strategy_get_type (void) G_GNUC_CONST;
+
+GimpObject * gimp_single_window_strategy_get_singleton (void);
+
+
+#endif /* __GIMP_SINGLE_WINDOW_STRATEGY_H__ */
diff --git a/app/display/gimpstatusbar.c b/app/display/gimpstatusbar.c
new file mode 100644
index 0000000..0c0ca14
--- /dev/null
+++ b/app/display/gimpstatusbar.c
@@ -0,0 +1,1747 @@
+/* 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimpimage.h"
+#include "core/gimpprogress.h"
+
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scale.h"
+#include "gimpimagewindow.h"
+#include "gimpscalecombobox.h"
+#include "gimpstatusbar.h"
+
+#include "gimp-intl.h"
+
+
+/* maximal width of the string holding the cursor-coordinates */
+#define CURSOR_LEN 256
+
+/* the spacing of the hbox */
+#define HBOX_SPACING 1
+
+/* spacing between the icon and the statusbar label */
+#define ICON_SPACING 2
+
+/* timeout (in milliseconds) for temporary statusbar messages */
+#define MESSAGE_TIMEOUT 8000
+
+/* minimal interval (in microseconds) between progress updates */
+#define MIN_PROGRESS_UPDATE_INTERVAL 50000
+
+
+typedef struct _GimpStatusbarMsg GimpStatusbarMsg;
+
+struct _GimpStatusbarMsg
+{
+ guint context_id;
+ gchar *icon_name;
+ gchar *text;
+};
+
+
+static void gimp_statusbar_progress_iface_init (GimpProgressInterface *iface);
+
+static void gimp_statusbar_dispose (GObject *object);
+static void gimp_statusbar_finalize (GObject *object);
+
+static void gimp_statusbar_screen_changed (GtkWidget *widget,
+ GdkScreen *previous);
+static void gimp_statusbar_style_set (GtkWidget *widget,
+ GtkStyle *prev_style);
+
+static void gimp_statusbar_hbox_size_request (GtkWidget *widget,
+ GtkRequisition *requisition,
+ GimpStatusbar *statusbar);
+
+static GimpProgress *
+ gimp_statusbar_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+static void gimp_statusbar_progress_end (GimpProgress *progress);
+static gboolean gimp_statusbar_progress_is_active (GimpProgress *progress);
+static void gimp_statusbar_progress_set_text (GimpProgress *progress,
+ const gchar *message);
+static void gimp_statusbar_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+static gdouble gimp_statusbar_progress_get_value (GimpProgress *progress);
+static void gimp_statusbar_progress_pulse (GimpProgress *progress);
+static gboolean gimp_statusbar_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+static void gimp_statusbar_progress_canceled (GtkWidget *button,
+ GimpStatusbar *statusbar);
+
+static gboolean gimp_statusbar_label_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ GimpStatusbar *statusbar);
+
+static void gimp_statusbar_update (GimpStatusbar *statusbar);
+static void gimp_statusbar_unit_changed (GimpUnitComboBox *combo,
+ GimpStatusbar *statusbar);
+static void gimp_statusbar_scale_changed (GimpScaleComboBox *combo,
+ GimpStatusbar *statusbar);
+static void gimp_statusbar_scale_activated (GimpScaleComboBox *combo,
+ GimpStatusbar *statusbar);
+static gboolean gimp_statusbar_rotate_pressed (GtkWidget *event_box,
+ GdkEvent *event,
+ GimpStatusbar *statusbar);
+static gboolean gimp_statusbar_horiz_flip_pressed (GtkWidget *event_box,
+ GdkEvent *event,
+ GimpStatusbar *statusbar);
+static gboolean gimp_statusbar_vert_flip_pressed (GtkWidget *event_box,
+ GdkEvent *event,
+ GimpStatusbar *statusbar);
+static void gimp_statusbar_shell_scaled (GimpDisplayShell *shell,
+ GimpStatusbar *statusbar);
+static void gimp_statusbar_shell_rotated (GimpDisplayShell *shell,
+ GimpStatusbar *statusbar);
+static void gimp_statusbar_shell_status_notify(GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpStatusbar *statusbar);
+static guint gimp_statusbar_get_context_id (GimpStatusbar *statusbar,
+ const gchar *context);
+static gboolean gimp_statusbar_temp_timeout (GimpStatusbar *statusbar);
+
+static void gimp_statusbar_add_message (GimpStatusbar *statusbar,
+ guint context_id,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args,
+ gboolean move_to_front) G_GNUC_PRINTF (4, 0);
+static void gimp_statusbar_remove_message (GimpStatusbar *statusbar,
+ guint context_id);
+static void gimp_statusbar_msg_free (GimpStatusbarMsg *msg);
+
+static gchar * gimp_statusbar_vprintf (const gchar *format,
+ va_list args) G_GNUC_PRINTF (1, 0);
+
+static GdkPixbuf * gimp_statusbar_load_icon (GimpStatusbar *statusbar,
+ const gchar *icon_name);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpStatusbar, gimp_statusbar, GTK_TYPE_STATUSBAR,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
+ gimp_statusbar_progress_iface_init))
+
+#define parent_class gimp_statusbar_parent_class
+
+
+static void
+gimp_statusbar_class_init (GimpStatusbarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gimp_statusbar_dispose;
+ object_class->finalize = gimp_statusbar_finalize;
+
+ widget_class->screen_changed = gimp_statusbar_screen_changed;
+ widget_class->style_set = gimp_statusbar_style_set;
+}
+
+static void
+gimp_statusbar_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_statusbar_progress_start;
+ iface->end = gimp_statusbar_progress_end;
+ iface->is_active = gimp_statusbar_progress_is_active;
+ iface->set_text = gimp_statusbar_progress_set_text;
+ iface->set_value = gimp_statusbar_progress_set_value;
+ iface->get_value = gimp_statusbar_progress_get_value;
+ iface->pulse = gimp_statusbar_progress_pulse;
+ iface->message = gimp_statusbar_progress_message;
+}
+
+static void
+gimp_statusbar_init (GimpStatusbar *statusbar)
+{
+ GtkWidget *hbox;
+ GtkWidget *hbox2;
+ GtkWidget *image;
+ GtkWidget *label;
+ GimpUnitStore *store;
+ GList *children;
+
+ statusbar->shell = NULL;
+ statusbar->messages = NULL;
+ statusbar->context_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ statusbar->seq_context_id = 1;
+
+ statusbar->temp_context_id =
+ gimp_statusbar_get_context_id (statusbar, "gimp-statusbar-temp");
+
+ statusbar->cursor_format_str[0] = '\0';
+ statusbar->cursor_format_str_f[0] = '\0';
+ statusbar->length_format_str[0] = '\0';
+
+ statusbar->progress_active = FALSE;
+ statusbar->progress_shown = FALSE;
+
+ /* remove the message label from the message area */
+ hbox = gtk_statusbar_get_message_area (GTK_STATUSBAR (statusbar));
+ gtk_box_set_spacing (GTK_BOX (hbox), HBOX_SPACING);
+
+ children = gtk_container_get_children (GTK_CONTAINER (hbox));
+ statusbar->label = g_object_ref (children->data);
+ g_list_free (children);
+
+ gtk_container_remove (GTK_CONTAINER (hbox), statusbar->label);
+
+ g_signal_connect (hbox, "size-request",
+ G_CALLBACK (gimp_statusbar_hbox_size_request),
+ statusbar);
+
+ statusbar->cursor_label = gtk_label_new ("8888, 8888");
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->cursor_label, FALSE, FALSE, 0);
+ gtk_widget_show (statusbar->cursor_label);
+
+ store = gimp_unit_store_new (2);
+ statusbar->unit_combo = gimp_unit_combo_box_new_with_model (store);
+ g_object_unref (store);
+
+ /* see issue #2642 */
+ gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (statusbar->unit_combo), 1);
+
+ gtk_widget_set_can_focus (statusbar->unit_combo, FALSE);
+ g_object_set (statusbar->unit_combo, "focus-on-click", FALSE, NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->unit_combo, FALSE, FALSE, 0);
+ gtk_widget_show (statusbar->unit_combo);
+
+ g_signal_connect (statusbar->unit_combo, "changed",
+ G_CALLBACK (gimp_statusbar_unit_changed),
+ statusbar);
+
+ statusbar->scale_combo = gimp_scale_combo_box_new ();
+ gtk_widget_set_can_focus (statusbar->scale_combo, FALSE);
+ g_object_set (statusbar->scale_combo, "focus-on-click", FALSE, NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->scale_combo, FALSE, FALSE, 0);
+ gtk_widget_show (statusbar->scale_combo);
+
+ g_signal_connect (statusbar->scale_combo, "changed",
+ G_CALLBACK (gimp_statusbar_scale_changed),
+ statusbar);
+
+ g_signal_connect (statusbar->scale_combo, "entry-activated",
+ G_CALLBACK (gimp_statusbar_scale_activated),
+ statusbar);
+
+ /* Shell transform status */
+ statusbar->rotate_widget = gtk_event_box_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->rotate_widget,
+ FALSE, FALSE, 1);
+ gtk_widget_show (statusbar->rotate_widget);
+
+ statusbar->rotate_label = gtk_label_new (NULL);
+ gtk_container_add (GTK_CONTAINER (statusbar->rotate_widget),
+ statusbar->rotate_label);
+ gtk_widget_show (statusbar->rotate_label);
+
+ g_signal_connect (statusbar->rotate_widget, "button-press-event",
+ G_CALLBACK (gimp_statusbar_rotate_pressed),
+ statusbar);
+
+ statusbar->horizontal_flip_icon = gtk_event_box_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->horizontal_flip_icon,
+ FALSE, FALSE, 1);
+ gtk_widget_show (statusbar->horizontal_flip_icon);
+
+ image = gtk_image_new_from_icon_name ("gimp-flip-horizontal",
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (statusbar->horizontal_flip_icon), image);
+ gtk_widget_show (image);
+
+ g_signal_connect (statusbar->horizontal_flip_icon, "button-press-event",
+ G_CALLBACK (gimp_statusbar_horiz_flip_pressed),
+ statusbar);
+
+ statusbar->vertical_flip_icon = gtk_event_box_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->vertical_flip_icon,
+ FALSE, FALSE, 1);
+ gtk_widget_show (statusbar->vertical_flip_icon);
+
+ image = gtk_image_new_from_icon_name ("gimp-flip-vertical",
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (statusbar->vertical_flip_icon), image);
+ gtk_widget_show (image);
+
+ g_signal_connect (statusbar->vertical_flip_icon, "button-press-event",
+ G_CALLBACK (gimp_statusbar_vert_flip_pressed),
+ statusbar);
+
+ /* put the label back into the message area */
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->label, TRUE, TRUE, 1);
+
+ g_object_unref (statusbar->label);
+
+ g_signal_connect_after (statusbar->label, "expose-event",
+ G_CALLBACK (gimp_statusbar_label_expose),
+ statusbar);
+
+ statusbar->progressbar = g_object_new (GTK_TYPE_PROGRESS_BAR,
+ "text-xalign", 0.0,
+ "text-yalign", 0.5,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), statusbar->progressbar, TRUE, TRUE, 0);
+ /* don't show the progress bar */
+
+ /* construct the cancel button's contents manually because we
+ * always want image and label regardless of settings, and we want
+ * a menu size image.
+ */
+ statusbar->cancel_button = gtk_button_new ();
+ gtk_widget_set_can_focus (statusbar->cancel_button, FALSE);
+ gtk_button_set_relief (GTK_BUTTON (statusbar->cancel_button),
+ GTK_RELIEF_NONE);
+ gtk_widget_set_sensitive (statusbar->cancel_button, FALSE);
+ gtk_box_pack_end (GTK_BOX (hbox),
+ statusbar->cancel_button, FALSE, FALSE, 0);
+ /* don't show the cancel button */
+
+ hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (statusbar->cancel_button), hbox2);
+ gtk_widget_show (hbox2);
+
+ image = gtk_image_new_from_icon_name ("gtk-cancel", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (hbox2), image, FALSE, FALSE, 2);
+ gtk_widget_show (image);
+
+ label = gtk_label_new ("Cancel");
+ gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 2);
+ gtk_widget_show (label);
+
+ g_signal_connect (statusbar->cancel_button, "clicked",
+ G_CALLBACK (gimp_statusbar_progress_canceled),
+ statusbar);
+}
+
+static void
+gimp_statusbar_dispose (GObject *object)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (object);
+
+ if (statusbar->temp_timeout_id)
+ {
+ g_source_remove (statusbar->temp_timeout_id);
+ statusbar->temp_timeout_id = 0;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_statusbar_finalize (GObject *object)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (object);
+
+ g_clear_object (&statusbar->icon);
+ g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref);
+
+ g_slist_free_full (statusbar->messages,
+ (GDestroyNotify) gimp_statusbar_msg_free);
+ statusbar->messages = NULL;
+
+ g_clear_pointer (&statusbar->context_ids, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_statusbar_screen_changed (GtkWidget *widget,
+ GdkScreen *previous)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (widget);
+
+ if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
+ GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous);
+
+ g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref);
+}
+
+static void
+gimp_statusbar_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
+
+ g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref);
+}
+
+static void
+gimp_statusbar_hbox_size_request (GtkWidget *widget,
+ GtkRequisition *requisition,
+ GimpStatusbar *statusbar)
+{
+ GtkRequisition child_requisition;
+ gint width = 0;
+
+ /* also consider the children which can be invisible */
+
+ gtk_widget_size_request (statusbar->cursor_label, &child_requisition);
+ width += child_requisition.width;
+ requisition->height = MAX (requisition->height,
+ child_requisition.height);
+
+ gtk_widget_size_request (statusbar->unit_combo, &child_requisition);
+ width += child_requisition.width;
+ requisition->height = MAX (requisition->height,
+ child_requisition.height);
+
+ gtk_widget_size_request (statusbar->scale_combo, &child_requisition);
+ width += child_requisition.width;
+ requisition->height = MAX (requisition->height,
+ child_requisition.height);
+
+ gtk_widget_size_request (statusbar->progressbar, &child_requisition);
+ requisition->height = MAX (requisition->height,
+ child_requisition.height);
+
+ gtk_widget_size_request (statusbar->label, &child_requisition);
+ requisition->height = MAX (requisition->height,
+ child_requisition.height);
+
+ gtk_widget_size_request (statusbar->cancel_button, &child_requisition);
+ requisition->height = MAX (requisition->height,
+ child_requisition.height);
+
+ requisition->width = MAX (requisition->width, width + 32);
+}
+
+static GimpProgress *
+gimp_statusbar_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ if (! statusbar->progress_active)
+ {
+ GtkWidget *bar = statusbar->progressbar;
+ GtkAllocation allocation;
+
+ statusbar->progress_active = TRUE;
+ statusbar->progress_value = 0.0;
+ statusbar->progress_last_update_time = g_get_monotonic_time ();
+
+ gimp_statusbar_push (statusbar, "progress", NULL, "%s", message);
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0);
+ gtk_widget_set_sensitive (statusbar->cancel_button, cancellable);
+
+ if (cancellable)
+ {
+ if (message)
+ {
+ gchar *tooltip = g_strdup_printf (_("Cancel <i>%s</i>"), message);
+
+ gimp_help_set_help_data_with_markup (statusbar->cancel_button,
+ tooltip, NULL);
+ g_free (tooltip);
+ }
+
+ gtk_widget_show (statusbar->cancel_button);
+ }
+
+ gtk_widget_get_allocation (statusbar->label, &allocation);
+
+ gtk_widget_show (statusbar->progressbar);
+ gtk_widget_hide (statusbar->label);
+
+ /* This shit is needed so that the progress bar is drawn in the
+ * correct place in the cases where we suck completely and run
+ * an operation that blocks the GUI and doesn't let the main
+ * loop run.
+ */
+ gtk_container_resize_children (GTK_CONTAINER (statusbar));
+ gtk_widget_size_allocate (statusbar->progressbar, &allocation);
+
+ if (! gtk_widget_get_visible (GTK_WIDGET (statusbar)))
+ {
+ gtk_widget_show (GTK_WIDGET (statusbar));
+ statusbar->progress_shown = TRUE;
+ }
+
+ gimp_widget_flush_expose (bar);
+
+ gimp_statusbar_override_window_title (statusbar);
+
+ return progress;
+ }
+
+ return NULL;
+}
+
+static void
+gimp_statusbar_progress_end (GimpProgress *progress)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ if (statusbar->progress_active)
+ {
+ GtkWidget *bar = statusbar->progressbar;
+
+ if (statusbar->progress_shown)
+ {
+ gtk_widget_hide (GTK_WIDGET (statusbar));
+ statusbar->progress_shown = FALSE;
+ }
+
+ statusbar->progress_active = FALSE;
+ statusbar->progress_value = 0.0;
+
+ gtk_widget_hide (bar);
+ gtk_widget_show (statusbar->label);
+
+ gimp_statusbar_pop (statusbar, "progress");
+
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0);
+ gtk_widget_set_sensitive (statusbar->cancel_button, FALSE);
+ gtk_widget_hide (statusbar->cancel_button);
+
+ gimp_statusbar_restore_window_title (statusbar);
+ }
+}
+
+static gboolean
+gimp_statusbar_progress_is_active (GimpProgress *progress)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ return statusbar->progress_active;
+}
+
+static void
+gimp_statusbar_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ if (statusbar->progress_active)
+ {
+ GtkWidget *bar = statusbar->progressbar;
+
+ gimp_statusbar_replace (statusbar, "progress", NULL, "%s", message);
+
+ gimp_widget_flush_expose (bar);
+
+ gimp_statusbar_override_window_title (statusbar);
+ }
+}
+
+static void
+gimp_statusbar_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ if (statusbar->progress_active)
+ {
+ guint64 time = g_get_monotonic_time ();
+
+ if (time - statusbar->progress_last_update_time >=
+ MIN_PROGRESS_UPDATE_INTERVAL)
+ {
+ GtkWidget *bar = statusbar->progressbar;
+ GtkAllocation allocation;
+ gdouble diff;
+
+ gtk_widget_get_allocation (bar, &allocation);
+
+ statusbar->progress_value = percentage;
+
+ diff = fabs (percentage -
+ gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (bar)));
+
+ /* only update the progress bar if this causes a visible change */
+ if (allocation.width * diff >= 1.0)
+ {
+ statusbar->progress_last_update_time = time;
+
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar),
+ percentage);
+
+ gimp_widget_flush_expose (bar);
+ }
+ }
+ }
+}
+
+static gdouble
+gimp_statusbar_progress_get_value (GimpProgress *progress)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ if (statusbar->progress_active)
+ return statusbar->progress_value;
+
+ return 0.0;
+}
+
+static void
+gimp_statusbar_progress_pulse (GimpProgress *progress)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+
+ if (statusbar->progress_active)
+ {
+ guint64 time = g_get_monotonic_time ();
+
+ if (time - statusbar->progress_last_update_time >=
+ MIN_PROGRESS_UPDATE_INTERVAL)
+ {
+ GtkWidget *bar = statusbar->progressbar;
+
+ statusbar->progress_last_update_time = time;
+
+ gtk_progress_bar_pulse (GTK_PROGRESS_BAR (bar));
+
+ gimp_widget_flush_expose (bar);
+ }
+ }
+}
+
+static gboolean
+gimp_statusbar_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
+ PangoLayout *layout;
+ const gchar *icon_name;
+ gboolean handle_msg = FALSE;
+
+ /* don't accept a message if we are already displaying a more severe one */
+ if (statusbar->temp_timeout_id && statusbar->temp_severity > severity)
+ return FALSE;
+
+ /* we can only handle short one-liners */
+ layout = gtk_widget_create_pango_layout (statusbar->label, message);
+
+ icon_name = gimp_get_message_icon_name (severity);
+
+ if (pango_layout_get_line_count (layout) == 1)
+ {
+ GtkAllocation label_allocation;
+ gint width;
+
+ gtk_widget_get_allocation (statusbar->label, &label_allocation);
+
+ pango_layout_get_pixel_size (layout, &width, NULL);
+
+ if (width < label_allocation.width)
+ {
+ if (icon_name)
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gimp_statusbar_load_icon (statusbar, icon_name);
+
+ width += ICON_SPACING + gdk_pixbuf_get_width (pixbuf);
+
+ g_object_unref (pixbuf);
+
+ handle_msg = (width < label_allocation.width);
+ }
+ else
+ {
+ handle_msg = TRUE;
+ }
+ }
+ }
+
+ g_object_unref (layout);
+
+ if (handle_msg)
+ gimp_statusbar_push_temp (statusbar, severity, icon_name, "%s", message);
+
+ return handle_msg;
+}
+
+static void
+gimp_statusbar_progress_canceled (GtkWidget *button,
+ GimpStatusbar *statusbar)
+{
+ if (statusbar->progress_active)
+ gimp_progress_cancel (GIMP_PROGRESS (statusbar));
+}
+
+static void
+gimp_statusbar_set_text (GimpStatusbar *statusbar,
+ const gchar *icon_name,
+ const gchar *text)
+{
+ if (statusbar->progress_active)
+ {
+ gtk_progress_bar_set_text (GTK_PROGRESS_BAR (statusbar->progressbar),
+ text);
+ }
+ else
+ {
+ g_clear_object (&statusbar->icon);
+
+ if (icon_name)
+ statusbar->icon = gimp_statusbar_load_icon (statusbar, icon_name);
+
+ if (statusbar->icon)
+ {
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+ PangoRectangle rect;
+ gchar *tmp;
+
+ tmp = g_strconcat (" ", text, NULL);
+ gtk_label_set_text (GTK_LABEL (statusbar->label), tmp);
+ g_free (tmp);
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = PANGO_SCALE * (gdk_pixbuf_get_width (statusbar->icon) +
+ ICON_SPACING);
+ rect.height = 0;
+
+ attrs = pango_attr_list_new ();
+
+ attr = pango_attr_shape_new (&rect, &rect);
+ attr->start_index = 0;
+ attr->end_index = 1;
+ pango_attr_list_insert (attrs, attr);
+
+ gtk_label_set_attributes (GTK_LABEL (statusbar->label), attrs);
+ pango_attr_list_unref (attrs);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (statusbar->label), text);
+ gtk_label_set_attributes (GTK_LABEL (statusbar->label), NULL);
+ }
+ }
+}
+
+static void
+gimp_statusbar_update (GimpStatusbar *statusbar)
+{
+ GimpStatusbarMsg *msg = NULL;
+
+ if (statusbar->messages)
+ msg = statusbar->messages->data;
+
+ if (msg && msg->text)
+ {
+ gimp_statusbar_set_text (statusbar, msg->icon_name, msg->text);
+ }
+ else
+ {
+ gimp_statusbar_set_text (statusbar, NULL, "");
+ }
+}
+
+
+/* public functions */
+
+GtkWidget *
+gimp_statusbar_new (void)
+{
+ return g_object_new (GIMP_TYPE_STATUSBAR, NULL);
+}
+
+void
+gimp_statusbar_set_shell (GimpStatusbar *statusbar,
+ GimpDisplayShell *shell)
+{
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell == statusbar->shell)
+ return;
+
+ if (statusbar->shell)
+ {
+ g_signal_handlers_disconnect_by_func (statusbar->shell,
+ gimp_statusbar_shell_scaled,
+ statusbar);
+ g_signal_handlers_disconnect_by_func (statusbar->shell,
+ gimp_statusbar_shell_rotated,
+ statusbar);
+ g_signal_handlers_disconnect_by_func (statusbar->shell,
+ gimp_statusbar_shell_status_notify,
+ statusbar);
+ }
+
+ statusbar->shell = shell;
+
+ g_signal_connect_object (statusbar->shell, "scaled",
+ G_CALLBACK (gimp_statusbar_shell_scaled),
+ statusbar, 0);
+ g_signal_connect_object (statusbar->shell, "rotated",
+ G_CALLBACK (gimp_statusbar_shell_rotated),
+ statusbar, 0);
+ g_signal_connect_object (statusbar->shell, "notify::status",
+ G_CALLBACK (gimp_statusbar_shell_status_notify),
+ statusbar, 0);
+ gimp_statusbar_shell_rotated (shell, statusbar);
+}
+
+gboolean
+gimp_statusbar_get_visible (GimpStatusbar *statusbar)
+{
+ g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), FALSE);
+
+ if (statusbar->progress_shown)
+ return FALSE;
+
+ return gtk_widget_get_visible (GTK_WIDGET (statusbar));
+}
+
+void
+gimp_statusbar_set_visible (GimpStatusbar *statusbar,
+ gboolean visible)
+{
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ if (statusbar->progress_shown)
+ {
+ if (visible)
+ {
+ statusbar->progress_shown = FALSE;
+ return;
+ }
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (statusbar), visible);
+}
+
+void
+gimp_statusbar_empty (GimpStatusbar *statusbar)
+{
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ gtk_widget_hide (statusbar->cursor_label);
+ gtk_widget_hide (statusbar->unit_combo);
+ gtk_widget_hide (statusbar->scale_combo);
+ gtk_widget_hide (statusbar->rotate_widget);
+ gtk_widget_hide (statusbar->horizontal_flip_icon);
+ gtk_widget_hide (statusbar->vertical_flip_icon);
+}
+
+void
+gimp_statusbar_fill (GimpStatusbar *statusbar)
+{
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ gtk_widget_show (statusbar->cursor_label);
+ gtk_widget_show (statusbar->unit_combo);
+ gtk_widget_show (statusbar->scale_combo);
+ gtk_widget_show (statusbar->rotate_widget);
+ gimp_statusbar_shell_rotated (statusbar->shell, statusbar);
+}
+
+void
+gimp_statusbar_override_window_title (GimpStatusbar *statusbar)
+{
+ GtkWidget *toplevel;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar));
+
+ if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel)))
+ {
+ const gchar *message = gimp_statusbar_peek (statusbar, "progress");
+
+ if (message)
+ gtk_window_set_title (GTK_WINDOW (toplevel), message);
+ }
+}
+
+void
+gimp_statusbar_restore_window_title (GimpStatusbar *statusbar)
+{
+ GtkWidget *toplevel;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar));
+
+ if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel)))
+ {
+ g_object_notify (G_OBJECT (statusbar->shell), "title");
+ }
+}
+
+void
+gimp_statusbar_push (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (context != NULL);
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ gimp_statusbar_push_valist (statusbar, context, icon_name, format, args);
+ va_end (args);
+}
+
+void
+gimp_statusbar_push_valist (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args)
+{
+ guint context_id;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (context != NULL);
+ g_return_if_fail (format != NULL);
+
+ context_id = gimp_statusbar_get_context_id (statusbar, context);
+
+ gimp_statusbar_add_message (statusbar,
+ context_id,
+ icon_name, format, args,
+ /* move_to_front = */ TRUE);
+}
+
+void
+gimp_statusbar_push_coords (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ GimpCursorPrecision precision,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help)
+{
+ GimpDisplayShell *shell;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (title != NULL);
+ g_return_if_fail (separator != NULL);
+
+ if (help == NULL)
+ help = "";
+
+ shell = statusbar->shell;
+
+ switch (precision)
+ {
+ case GIMP_CURSOR_PRECISION_PIXEL_CENTER:
+ x = (gint) x;
+ y = (gint) y;
+ break;
+
+ case GIMP_CURSOR_PRECISION_PIXEL_BORDER:
+ x = RINT (x);
+ y = RINT (y);
+ break;
+
+ case GIMP_CURSOR_PRECISION_SUBPIXEL:
+ break;
+ }
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ {
+ if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL)
+ {
+ gimp_statusbar_push (statusbar, context,
+ icon_name,
+ statusbar->cursor_format_str_f,
+ title,
+ x,
+ separator,
+ y,
+ help);
+ }
+ else
+ {
+ gimp_statusbar_push (statusbar, context,
+ icon_name,
+ statusbar->cursor_format_str,
+ title,
+ (gint) RINT (x),
+ separator,
+ (gint) RINT (y),
+ help);
+ }
+ }
+ else /* show real world units */
+ {
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (gimp_display_get_image (shell->display),
+ &xres, &yres);
+
+ gimp_statusbar_push (statusbar, context,
+ icon_name,
+ statusbar->cursor_format_str,
+ title,
+ gimp_pixels_to_units (x, shell->unit, xres),
+ separator,
+ gimp_pixels_to_units (y, shell->unit, yres),
+ help);
+ }
+}
+
+void
+gimp_statusbar_push_length (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *title,
+ GimpOrientationType axis,
+ gdouble value,
+ const gchar *help)
+{
+ GimpDisplayShell *shell;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (title != NULL);
+
+ if (help == NULL)
+ help = "";
+
+ shell = statusbar->shell;
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ {
+ gimp_statusbar_push (statusbar, context,
+ icon_name,
+ statusbar->length_format_str,
+ title,
+ (gint) RINT (value),
+ help);
+ }
+ else /* show real world units */
+ {
+ gdouble xres;
+ gdouble yres;
+ gdouble resolution;
+
+ gimp_image_get_resolution (gimp_display_get_image (shell->display),
+ &xres, &yres);
+
+ switch (axis)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ resolution = xres;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ resolution = yres;
+ break;
+
+ default:
+ g_return_if_reached ();
+ break;
+ }
+
+ gimp_statusbar_push (statusbar, context,
+ icon_name,
+ statusbar->length_format_str,
+ title,
+ gimp_pixels_to_units (value, shell->unit, resolution),
+ help);
+ }
+}
+
+void
+gimp_statusbar_replace (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (context != NULL);
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ gimp_statusbar_replace_valist (statusbar, context, icon_name, format, args);
+ va_end (args);
+}
+
+void
+gimp_statusbar_replace_valist (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args)
+{
+ guint context_id;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (context != NULL);
+ g_return_if_fail (format != NULL);
+
+ context_id = gimp_statusbar_get_context_id (statusbar, context);
+
+ gimp_statusbar_add_message (statusbar,
+ context_id,
+ icon_name, format, args,
+ /* move_to_front = */ FALSE);
+}
+
+const gchar *
+gimp_statusbar_peek (GimpStatusbar *statusbar,
+ const gchar *context)
+{
+ GSList *list;
+ guint context_id;
+
+ g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), NULL);
+ g_return_val_if_fail (context != NULL, NULL);
+
+ context_id = gimp_statusbar_get_context_id (statusbar, context);
+
+ for (list = statusbar->messages; list; list = list->next)
+ {
+ GimpStatusbarMsg *msg = list->data;
+
+ if (msg->context_id == context_id)
+ {
+ return msg->text;
+ }
+ }
+
+ return NULL;
+}
+
+void
+gimp_statusbar_pop (GimpStatusbar *statusbar,
+ const gchar *context)
+{
+ guint context_id;
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (context != NULL);
+
+ context_id = gimp_statusbar_get_context_id (statusbar, context);
+
+ gimp_statusbar_remove_message (statusbar,
+ context_id);
+}
+
+void
+gimp_statusbar_push_temp (GimpStatusbar *statusbar,
+ GimpMessageSeverity severity,
+ const gchar *icon_name,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ gimp_statusbar_push_temp_valist (statusbar, severity, icon_name, format, args);
+ va_end (args);
+}
+
+void
+gimp_statusbar_push_temp_valist (GimpStatusbar *statusbar,
+ GimpMessageSeverity severity,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args)
+{
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+ g_return_if_fail (severity <= GIMP_MESSAGE_WARNING);
+ g_return_if_fail (format != NULL);
+
+ /* don't accept a message if we are already displaying a more severe one */
+ if (statusbar->temp_timeout_id && statusbar->temp_severity > severity)
+ return;
+
+ if (statusbar->temp_timeout_id)
+ g_source_remove (statusbar->temp_timeout_id);
+
+ statusbar->temp_timeout_id =
+ g_timeout_add (MESSAGE_TIMEOUT,
+ (GSourceFunc) gimp_statusbar_temp_timeout, statusbar);
+
+ statusbar->temp_severity = severity;
+
+ gimp_statusbar_add_message (statusbar,
+ statusbar->temp_context_id,
+ icon_name, format, args,
+ /* move_to_front = */ TRUE);
+
+ if (severity >= GIMP_MESSAGE_WARNING)
+ gimp_widget_blink (statusbar->label);
+}
+
+void
+gimp_statusbar_pop_temp (GimpStatusbar *statusbar)
+{
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ if (statusbar->temp_timeout_id)
+ {
+ g_source_remove (statusbar->temp_timeout_id);
+ statusbar->temp_timeout_id = 0;
+
+ gimp_statusbar_remove_message (statusbar,
+ statusbar->temp_context_id);
+ }
+}
+
+void
+gimp_statusbar_update_cursor (GimpStatusbar *statusbar,
+ GimpCursorPrecision precision,
+ gdouble x,
+ gdouble y)
+{
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ gchar buffer[CURSOR_LEN];
+
+ g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
+
+ shell = statusbar->shell;
+ image = gimp_display_get_image (shell->display);
+
+ if (! image ||
+ x < 0 ||
+ y < 0 ||
+ x >= gimp_image_get_width (image) ||
+ y >= gimp_image_get_height (image))
+ {
+ gtk_widget_set_sensitive (statusbar->cursor_label, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (statusbar->cursor_label, TRUE);
+ }
+
+ switch (precision)
+ {
+ case GIMP_CURSOR_PRECISION_PIXEL_CENTER:
+ x = (gint) x;
+ y = (gint) y;
+ break;
+
+ case GIMP_CURSOR_PRECISION_PIXEL_BORDER:
+ x = RINT (x);
+ y = RINT (y);
+ break;
+
+ case GIMP_CURSOR_PRECISION_SUBPIXEL:
+ break;
+ }
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ {
+ if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL)
+ {
+ g_snprintf (buffer, sizeof (buffer),
+ statusbar->cursor_format_str_f,
+ "", x, ", ", y, "");
+ }
+ else
+ {
+ g_snprintf (buffer, sizeof (buffer),
+ statusbar->cursor_format_str,
+ "", (gint) RINT (x), ", ", (gint) RINT (y), "");
+ }
+ }
+ else /* show real world units */
+ {
+ GtkTreeModel *model;
+ GimpUnitStore *store;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo));
+ store = GIMP_UNIT_STORE (model);
+
+ gimp_unit_store_set_pixel_values (store, x, y);
+ gimp_unit_store_get_values (store, shell->unit, &x, &y);
+
+ g_snprintf (buffer, sizeof (buffer),
+ statusbar->cursor_format_str,
+ "", x, ", ", y, "");
+ }
+
+ gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), buffer);
+}
+
+void
+gimp_statusbar_clear_cursor (GimpStatusbar *statusbar)
+{
+ gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), "");
+ gtk_widget_set_sensitive (statusbar->cursor_label, TRUE);
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_statusbar_label_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ GimpStatusbar *statusbar)
+{
+ if (statusbar->icon)
+ {
+ cairo_t *cr;
+ PangoRectangle rect;
+ gint x, y;
+
+ cr = gdk_cairo_create (event->window);
+
+ gdk_cairo_region (cr, event->region);
+ cairo_clip (cr);
+
+ gtk_label_get_layout_offsets (GTK_LABEL (widget), &x, &y);
+
+ pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (widget)), 0,
+ &rect);
+
+ /* the rectangle width is negative when rendering right-to-left */
+ x += PANGO_PIXELS (rect.x) + (rect.width < 0 ?
+ PANGO_PIXELS (rect.width) : 0);
+ y += PANGO_PIXELS (rect.y);
+
+ gdk_cairo_set_source_pixbuf (cr, statusbar->icon, x, y);
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_statusbar_shell_scaled (GimpDisplayShell *shell,
+ GimpStatusbar *statusbar)
+{
+ static PangoLayout *layout = NULL;
+
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GtkTreeModel *model;
+ const gchar *text;
+ gint image_width;
+ gint image_height;
+ gdouble image_xres;
+ gdouble image_yres;
+ gint width;
+
+ if (image)
+ {
+ image_width = gimp_image_get_width (image);
+ image_height = gimp_image_get_height (image);
+ gimp_image_get_resolution (image, &image_xres, &image_yres);
+ }
+ else
+ {
+ image_width = shell->disp_width;
+ image_height = shell->disp_height;
+ image_xres = shell->display->config->monitor_xres;
+ image_yres = shell->display->config->monitor_yres;
+ }
+
+ g_signal_handlers_block_by_func (statusbar->scale_combo,
+ gimp_statusbar_scale_changed, statusbar);
+ gimp_scale_combo_box_set_scale (GIMP_SCALE_COMBO_BOX (statusbar->scale_combo),
+ gimp_zoom_model_get_factor (shell->zoom));
+ g_signal_handlers_unblock_by_func (statusbar->scale_combo,
+ gimp_statusbar_scale_changed, statusbar);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo));
+ gimp_unit_store_set_resolutions (GIMP_UNIT_STORE (model),
+ image_xres, image_yres);
+
+ g_signal_handlers_block_by_func (statusbar->unit_combo,
+ gimp_statusbar_unit_changed, statusbar);
+ gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (statusbar->unit_combo),
+ shell->unit);
+ g_signal_handlers_unblock_by_func (statusbar->unit_combo,
+ gimp_statusbar_unit_changed, statusbar);
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ {
+ g_snprintf (statusbar->cursor_format_str,
+ sizeof (statusbar->cursor_format_str),
+ "%%s%%d%%s%%d%%s");
+ g_snprintf (statusbar->cursor_format_str_f,
+ sizeof (statusbar->cursor_format_str_f),
+ "%%s%%.1f%%s%%.1f%%s");
+ g_snprintf (statusbar->length_format_str,
+ sizeof (statusbar->length_format_str),
+ "%%s%%d%%s");
+ }
+ else /* show real world units */
+ {
+ gint w_digits;
+ gint h_digits;
+
+ w_digits = gimp_unit_get_scaled_digits (shell->unit, image_xres);
+ h_digits = gimp_unit_get_scaled_digits (shell->unit, image_yres);
+
+ g_snprintf (statusbar->cursor_format_str,
+ sizeof (statusbar->cursor_format_str),
+ "%%s%%.%df%%s%%.%df%%s",
+ w_digits, h_digits);
+ strcpy (statusbar->cursor_format_str_f, statusbar->cursor_format_str);
+ g_snprintf (statusbar->length_format_str,
+ sizeof (statusbar->length_format_str),
+ "%%s%%.%df%%s", MAX (w_digits, h_digits));
+ }
+
+ gimp_statusbar_update_cursor (statusbar, GIMP_CURSOR_PRECISION_SUBPIXEL,
+ -image_width, -image_height);
+
+ text = gtk_label_get_text (GTK_LABEL (statusbar->cursor_label));
+
+ /* one static layout for all displays should be fine */
+ if (! layout)
+ layout = gtk_widget_create_pango_layout (statusbar->cursor_label, NULL);
+
+ pango_layout_set_text (layout, text, -1);
+ pango_layout_get_pixel_size (layout, &width, NULL);
+
+ gtk_widget_set_size_request (statusbar->cursor_label, width, -1);
+
+ gimp_statusbar_clear_cursor (statusbar);
+}
+
+static void
+gimp_statusbar_shell_rotated (GimpDisplayShell *shell,
+ GimpStatusbar *statusbar)
+{
+ if (shell->rotate_angle != 0.0)
+ {
+ /* Degree symbol U+00B0. There are no spaces between the value and the
+ * unit for angular rotation.
+ */
+ gchar *text = g_strdup_printf (" %.2f\xC2\xB0", shell->rotate_angle);
+
+ gtk_label_set_text (GTK_LABEL (statusbar->rotate_label), text);
+ g_free (text);
+
+ gtk_widget_show (statusbar->rotate_widget);
+ }
+ else
+ {
+ gtk_widget_hide (statusbar->rotate_widget);
+ }
+
+ if (shell->flip_horizontally)
+ gtk_widget_show (statusbar->horizontal_flip_icon);
+ else
+ gtk_widget_hide (statusbar->horizontal_flip_icon);
+
+ if (shell->flip_vertically)
+ gtk_widget_show (statusbar->vertical_flip_icon);
+ else
+ gtk_widget_hide (statusbar->vertical_flip_icon);
+}
+
+static void
+gimp_statusbar_shell_status_notify (GimpDisplayShell *shell,
+ const GParamSpec *pspec,
+ GimpStatusbar *statusbar)
+{
+ gimp_statusbar_replace (statusbar, "title",
+ NULL, "%s", shell->status);
+}
+
+static void
+gimp_statusbar_unit_changed (GimpUnitComboBox *combo,
+ GimpStatusbar *statusbar)
+{
+ gimp_display_shell_set_unit (statusbar->shell,
+ gimp_unit_combo_box_get_active (combo));
+}
+
+static void
+gimp_statusbar_scale_changed (GimpScaleComboBox *combo,
+ GimpStatusbar *statusbar)
+{
+ gimp_display_shell_scale (statusbar->shell,
+ GIMP_ZOOM_TO,
+ gimp_scale_combo_box_get_scale (combo),
+ GIMP_ZOOM_FOCUS_BEST_GUESS);
+}
+
+static void
+gimp_statusbar_scale_activated (GimpScaleComboBox *combo,
+ GimpStatusbar *statusbar)
+{
+ gtk_widget_grab_focus (statusbar->shell->canvas);
+}
+
+static gboolean
+gimp_statusbar_rotate_pressed (GtkWidget *event_box,
+ GdkEvent *event,
+ GimpStatusbar *statusbar)
+{
+ GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell);
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_ui_manager_activate_action (manager, "view", "view-rotate-other");
+ return FALSE;
+}
+
+static gboolean
+gimp_statusbar_horiz_flip_pressed (GtkWidget *event_box,
+ GdkEvent *event,
+ GimpStatusbar *statusbar)
+{
+ GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell);
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_ui_manager_activate_action (manager, "view", "view-flip-horizontally");
+
+ return FALSE;
+}
+
+static gboolean
+gimp_statusbar_vert_flip_pressed (GtkWidget *event_box,
+ GdkEvent *event,
+ GimpStatusbar *statusbar)
+{
+ GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell);
+ GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
+
+ gimp_ui_manager_activate_action (manager, "view", "view-flip-vertically");
+
+ return FALSE;
+}
+
+static guint
+gimp_statusbar_get_context_id (GimpStatusbar *statusbar,
+ const gchar *context)
+{
+ guint id = GPOINTER_TO_UINT (g_hash_table_lookup (statusbar->context_ids,
+ context));
+
+ if (! id)
+ {
+ id = statusbar->seq_context_id++;
+
+ g_hash_table_insert (statusbar->context_ids,
+ g_strdup (context), GUINT_TO_POINTER (id));
+ }
+
+ return id;
+}
+
+static gboolean
+gimp_statusbar_temp_timeout (GimpStatusbar *statusbar)
+{
+ gimp_statusbar_pop_temp (statusbar);
+
+ return FALSE;
+}
+
+static void
+gimp_statusbar_add_message (GimpStatusbar *statusbar,
+ guint context_id,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args,
+ gboolean move_to_front)
+{
+ gchar *message;
+ GSList *list;
+ GimpStatusbarMsg *msg;
+ gint position;
+
+ message = gimp_statusbar_vprintf (format, args);
+
+ for (list = statusbar->messages; list; list = g_slist_next (list))
+ {
+ msg = list->data;
+
+ if (msg->context_id == context_id)
+ {
+ gboolean is_front_message = (list == statusbar->messages);
+
+ if ((is_front_message || ! move_to_front) &&
+ strcmp (msg->text, message) == 0 &&
+ g_strcmp0 (msg->icon_name, icon_name) == 0)
+ {
+ g_free (message);
+ return;
+ }
+
+ if (move_to_front)
+ {
+ statusbar->messages = g_slist_remove (statusbar->messages, msg);
+ gimp_statusbar_msg_free (msg);
+
+ break;
+ }
+ else
+ {
+ g_free (msg->icon_name);
+ msg->icon_name = g_strdup (icon_name);
+
+ g_free (msg->text);
+ msg->text = message;
+
+ if (is_front_message)
+ gimp_statusbar_update (statusbar);
+
+ return;
+ }
+ }
+ }
+
+ msg = g_slice_new (GimpStatusbarMsg);
+
+ msg->context_id = context_id;
+ msg->icon_name = g_strdup (icon_name);
+ msg->text = message;
+
+ /* find the position at which to insert the new message */
+ position = 0;
+ /* progress messages are always at the front of the list */
+ if (! (statusbar->progress_active &&
+ context_id == gimp_statusbar_get_context_id (statusbar, "progress")))
+ {
+ if (statusbar->progress_active)
+ position++;
+
+ /* temporary messages are in front of all other non-progress messages */
+ if (statusbar->temp_timeout_id &&
+ context_id != statusbar->temp_context_id)
+ position++;
+ }
+
+ statusbar->messages = g_slist_insert (statusbar->messages, msg, position);
+
+ if (position == 0)
+ gimp_statusbar_update (statusbar);
+}
+
+static void
+gimp_statusbar_remove_message (GimpStatusbar *statusbar,
+ guint context_id)
+{
+ GSList *list;
+ gboolean needs_update = FALSE;
+
+ for (list = statusbar->messages; list; list = g_slist_next (list))
+ {
+ GimpStatusbarMsg *msg = list->data;
+
+ if (msg->context_id == context_id)
+ {
+ needs_update = (list == statusbar->messages);
+
+ statusbar->messages = g_slist_remove (statusbar->messages, msg);
+ gimp_statusbar_msg_free (msg);
+
+ break;
+ }
+ }
+
+ if (needs_update)
+ gimp_statusbar_update (statusbar);
+}
+
+static void
+gimp_statusbar_msg_free (GimpStatusbarMsg *msg)
+{
+ g_free (msg->icon_name);
+ g_free (msg->text);
+
+ g_slice_free (GimpStatusbarMsg, msg);
+}
+
+static gchar *
+gimp_statusbar_vprintf (const gchar *format,
+ va_list args)
+{
+ gchar *message;
+ gchar *newline;
+
+ message = g_strdup_vprintf (format, args);
+
+ /* guard us from multi-line strings */
+ newline = strchr (message, '\r');
+ if (newline)
+ *newline = '\0';
+
+ newline = strchr (message, '\n');
+ if (newline)
+ *newline = '\0';
+
+ return message;
+}
+
+static GdkPixbuf *
+gimp_statusbar_load_icon (GimpStatusbar *statusbar,
+ const gchar *icon_name)
+{
+ GdkPixbuf *icon;
+
+ if (G_UNLIKELY (! statusbar->icon_hash))
+ {
+ statusbar->icon_hash =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+ }
+
+ icon = g_hash_table_lookup (statusbar->icon_hash, icon_name);
+
+ if (icon)
+ return g_object_ref (icon);
+
+ icon = gimp_widget_load_icon (statusbar->label, icon_name, 16);
+
+ /* this is not optimal but so what */
+ if (g_hash_table_size (statusbar->icon_hash) > 16)
+ g_hash_table_remove_all (statusbar->icon_hash);
+
+ g_hash_table_insert (statusbar->icon_hash,
+ g_strdup (icon_name), g_object_ref (icon));
+
+ return icon;
+}
diff --git a/app/display/gimpstatusbar.h b/app/display/gimpstatusbar.h
new file mode 100644
index 0000000..7d5f279
--- /dev/null
+++ b/app/display/gimpstatusbar.h
@@ -0,0 +1,158 @@
+/* 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/>.
+ */
+
+#ifndef __GIMP_STATUSBAR_H__
+#define __GIMP_STATUSBAR_H__
+
+G_BEGIN_DECLS
+
+
+/* maximal length of the format string for the cursor-coordinates */
+#define CURSOR_FORMAT_LENGTH 32
+
+
+#define GIMP_TYPE_STATUSBAR (gimp_statusbar_get_type ())
+#define GIMP_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STATUSBAR, GimpStatusbar))
+#define GIMP_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STATUSBAR, GimpStatusbarClass))
+#define GIMP_IS_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STATUSBAR))
+#define GIMP_IS_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STATUSBAR))
+#define GIMP_STATUSBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STATUSBAR, GimpStatusbarClass))
+
+typedef struct _GimpStatusbarClass GimpStatusbarClass;
+
+struct _GimpStatusbar
+{
+ GtkStatusbar parent_instance;
+
+ GimpDisplayShell *shell;
+
+ GSList *messages;
+ GHashTable *context_ids;
+ guint seq_context_id;
+
+ GdkPixbuf *icon;
+ GHashTable *icon_hash;
+
+ guint temp_context_id;
+ guint temp_timeout_id;
+ GimpMessageSeverity temp_severity;
+
+ gchar cursor_format_str[CURSOR_FORMAT_LENGTH];
+ gchar cursor_format_str_f[CURSOR_FORMAT_LENGTH];
+ gchar length_format_str[CURSOR_FORMAT_LENGTH];
+
+ GtkWidget *cursor_label;
+ GtkWidget *unit_combo;
+ GtkWidget *scale_combo;
+ GtkWidget *rotate_widget;
+ GtkWidget *rotate_label;
+ GtkWidget *horizontal_flip_icon;
+ GtkWidget *vertical_flip_icon;
+ GtkWidget *label; /* same as GtkStatusbar->label */
+
+ GtkWidget *progressbar;
+ GtkWidget *cancel_button;
+ gboolean progress_active;
+ gboolean progress_shown;
+ gdouble progress_value;
+ guint64 progress_last_update_time;
+};
+
+struct _GimpStatusbarClass
+{
+ GtkStatusbarClass parent_class;
+};
+
+
+GType gimp_statusbar_get_type (void) G_GNUC_CONST;
+GtkWidget * gimp_statusbar_new (void);
+
+void gimp_statusbar_set_shell (GimpStatusbar *statusbar,
+ GimpDisplayShell *shell);
+
+gboolean gimp_statusbar_get_visible (GimpStatusbar *statusbar);
+void gimp_statusbar_set_visible (GimpStatusbar *statusbar,
+ gboolean visible);
+void gimp_statusbar_empty (GimpStatusbar *statusbar);
+void gimp_statusbar_fill (GimpStatusbar *statusbar);
+
+void gimp_statusbar_override_window_title (GimpStatusbar *statusbar);
+void gimp_statusbar_restore_window_title (GimpStatusbar *statusbar);
+
+void gimp_statusbar_push (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (4, 5);
+void gimp_statusbar_push_valist (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args) G_GNUC_PRINTF (4, 0);
+void gimp_statusbar_push_coords (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ GimpCursorPrecision precision,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help);
+void gimp_statusbar_push_length (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *title,
+ GimpOrientationType axis,
+ gdouble value,
+ const gchar *help);
+void gimp_statusbar_replace (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (4, 5);
+void gimp_statusbar_replace_valist (GimpStatusbar *statusbar,
+ const gchar *context,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args) G_GNUC_PRINTF (4, 0);
+const gchar * gimp_statusbar_peek (GimpStatusbar *statusbar,
+ const gchar *context);
+void gimp_statusbar_pop (GimpStatusbar *statusbar,
+ const gchar *context);
+
+void gimp_statusbar_push_temp (GimpStatusbar *statusbar,
+ GimpMessageSeverity severity,
+ const gchar *icon_name,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (4, 5);
+void gimp_statusbar_push_temp_valist (GimpStatusbar *statusbar,
+ GimpMessageSeverity severity,
+ const gchar *icon_name,
+ const gchar *format,
+ va_list args) G_GNUC_PRINTF (4, 0);
+void gimp_statusbar_pop_temp (GimpStatusbar *statusbar);
+
+void gimp_statusbar_update_cursor (GimpStatusbar *statusbar,
+ GimpCursorPrecision precision,
+ gdouble x,
+ gdouble y);
+void gimp_statusbar_clear_cursor (GimpStatusbar *statusbar);
+
+
+G_END_DECLS
+
+#endif /* __GIMP_STATUSBAR_H__ */
diff --git a/app/display/gimptoolcompass.c b/app/display/gimptoolcompass.c
new file mode 100644
index 0000000..3d9045b
--- /dev/null
+++ b/app/display/gimptoolcompass.c
@@ -0,0 +1,1218 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolcompass.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Measure tool
+ * Copyright (C) 1999-2003 Sven Neumann <sven@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpimage.h"
+#include "core/gimpmarshal.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvasline.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-appearance.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolcompass.h"
+
+#include "gimp-intl.h"
+
+
+#define ARC_RADIUS 30
+#define ARC_GAP (ARC_RADIUS / 2)
+#define EPSILON 1e-6
+
+
+/* possible measure functions */
+typedef enum
+{
+ CREATING,
+ ADDING,
+ MOVING,
+ MOVING_ALL,
+ GUIDING,
+ FINISHED
+} CompassFunction;
+
+enum
+{
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_N_POINTS,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ PROP_X3,
+ PROP_Y3,
+ PROP_PIXEL_ANGLE,
+ PROP_UNIT_ANGLE,
+ PROP_EFFECTIVE_ORIENTATION
+};
+
+enum
+{
+ CREATE_GUIDES,
+ LAST_SIGNAL
+};
+
+struct _GimpToolCompassPrivate
+{
+ GimpCompassOrientation orientation;
+ gint n_points;
+ gint x[3];
+ gint y[3];
+
+ GimpVector2 radius1;
+ GimpVector2 radius2;
+ gdouble display_angle;
+ gdouble pixel_angle;
+ gdouble unit_angle;
+ GimpCompassOrientation effective_orientation;
+
+ CompassFunction function;
+ gdouble mouse_x;
+ gdouble mouse_y;
+ gint last_x;
+ gint last_y;
+ gint point;
+
+ GimpCanvasItem *line1;
+ GimpCanvasItem *line2;
+ GimpCanvasItem *arc;
+ GimpCanvasItem *arc_line;
+ GimpCanvasItem *handles[3];
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_compass_constructed (GObject *object);
+static void gimp_tool_compass_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_compass_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_compass_changed (GimpToolWidget *widget);
+static gint gimp_tool_compass_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_compass_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_compass_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_compass_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_compass_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_compass_leave_notify (GimpToolWidget *widget);
+static void gimp_tool_compass_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_compass_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static gint gimp_tool_compass_get_point (GimpToolCompass *compass,
+ const GimpCoords *coords);
+static void gimp_tool_compass_update_hilight (GimpToolCompass *compass);
+static void gimp_tool_compass_update_angle (GimpToolCompass *compass,
+ GimpCompassOrientation orientation,
+ gboolean flip);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolCompass, gimp_tool_compass,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_compass_parent_class
+
+static guint compass_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_tool_compass_class_init (GimpToolCompassClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_compass_constructed;
+ object_class->set_property = gimp_tool_compass_set_property;
+ object_class->get_property = gimp_tool_compass_get_property;
+
+ widget_class->changed = gimp_tool_compass_changed;
+ widget_class->button_press = gimp_tool_compass_button_press;
+ widget_class->button_release = gimp_tool_compass_button_release;
+ widget_class->motion = gimp_tool_compass_motion;
+ widget_class->hit = gimp_tool_compass_hit;
+ widget_class->hover = gimp_tool_compass_hover;
+ widget_class->leave_notify = gimp_tool_compass_leave_notify;
+ widget_class->motion_modifier = gimp_tool_compass_motion_modifier;
+ widget_class->get_cursor = gimp_tool_compass_get_cursor;
+ widget_class->update_on_scale = TRUE;
+ widget_class->update_on_rotate = TRUE;
+
+ compass_signals[CREATE_GUIDES] =
+ g_signal_new ("create-guides",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolCompassClass, create_guides),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_BOOLEAN_BOOLEAN,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN);
+
+ g_object_class_install_property (object_class, PROP_ORIENTATION,
+ g_param_spec_enum ("orientation", NULL, NULL,
+ GIMP_TYPE_COMPASS_ORIENTATION,
+ GIMP_COMPASS_ORIENTATION_AUTO,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_N_POINTS,
+ g_param_spec_int ("n-points", NULL, NULL,
+ 1, 3, 1,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_int ("x1", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_int ("y1", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_int ("x2", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_int ("y2", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X3,
+ g_param_spec_int ("x3", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y3,
+ g_param_spec_int ("y3", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIXEL_ANGLE,
+ g_param_spec_double ("pixel-angle", NULL, NULL,
+ -G_PI, G_PI, 0.0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_UNIT_ANGLE,
+ g_param_spec_double ("unit-angle", NULL, NULL,
+ -G_PI, G_PI, 0.0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_EFFECTIVE_ORIENTATION,
+ g_param_spec_enum ("effective-orientation", NULL, NULL,
+ GIMP_TYPE_COMPASS_ORIENTATION,
+ GIMP_COMPASS_ORIENTATION_AUTO,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_tool_compass_init (GimpToolCompass *compass)
+{
+ compass->private = gimp_tool_compass_get_instance_private (compass);
+
+ compass->private->point = -1;
+}
+
+static void
+gimp_tool_compass_constructed (GObject *object)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolCompassPrivate *private = compass->private;
+ GimpCanvasGroup *stroke_group;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ stroke_group = gimp_tool_widget_add_stroke_group (widget);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->line1 = gimp_tool_widget_add_line (widget,
+ private->x[0],
+ private->y[0],
+ private->x[1],
+ private->y[1]);
+
+ private->line2 = gimp_tool_widget_add_line (widget,
+ private->x[0],
+ private->y[0],
+ private->x[2],
+ private->y[2]);
+
+ private->arc = gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CIRCLE,
+ private->x[0],
+ private->y[0],
+ ARC_RADIUS * 2 + 1,
+ ARC_RADIUS * 2 + 1,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ private->arc_line = gimp_tool_widget_add_line (widget,
+ private->x[0],
+ private->y[0],
+ private->x[0] + 10,
+ private->y[0]);
+
+ gimp_tool_widget_pop_group (widget);
+
+ for (i = 0; i < 3; i++)
+ {
+ private->handles[i] =
+ gimp_tool_widget_add_handle (widget,
+ i == 0 ?
+ GIMP_HANDLE_CIRCLE : GIMP_HANDLE_CROSS,
+ private->x[i],
+ private->y[i],
+ GIMP_CANVAS_HANDLE_SIZE_CROSS,
+ GIMP_CANVAS_HANDLE_SIZE_CROSS,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+
+ gimp_tool_compass_changed (widget);
+}
+
+static void
+gimp_tool_compass_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (object);
+ GimpToolCompassPrivate *private = compass->private;
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ private->orientation = g_value_get_enum (value);
+ break;
+ case PROP_N_POINTS:
+ private->n_points = g_value_get_int (value);
+ break;
+ case PROP_X1:
+ private->x[0] = g_value_get_int (value);
+ break;
+ case PROP_Y1:
+ private->y[0] = g_value_get_int (value);
+ break;
+ case PROP_X2:
+ private->x[1] = g_value_get_int (value);
+ break;
+ case PROP_Y2:
+ private->y[1] = g_value_get_int (value);
+ break;
+ case PROP_X3:
+ private->x[2] = g_value_get_int (value);
+ break;
+ case PROP_Y3:
+ private->y[2] = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_compass_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (object);
+ GimpToolCompassPrivate *private = compass->private;
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, private->orientation);
+ break;
+ case PROP_N_POINTS:
+ g_value_set_int (value, private->n_points);
+ break;
+ case PROP_X1:
+ g_value_set_int (value, private->x[0]);
+ break;
+ case PROP_Y1:
+ g_value_set_int (value, private->y[0]);
+ break;
+ case PROP_X2:
+ g_value_set_int (value, private->x[1]);
+ break;
+ case PROP_Y2:
+ g_value_set_int (value, private->y[1]);
+ break;
+ case PROP_X3:
+ g_value_set_int (value, private->x[2]);
+ break;
+ case PROP_Y3:
+ g_value_set_int (value, private->y[2]);
+ break;
+ case PROP_PIXEL_ANGLE:
+ g_value_set_double (value, private->pixel_angle);
+ break;
+ case PROP_UNIT_ANGLE:
+ g_value_set_double (value, private->unit_angle);
+ break;
+ case PROP_EFFECTIVE_ORIENTATION:
+ g_value_set_enum (value, private->effective_orientation);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_compass_changed (GimpToolWidget *widget)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ gdouble angle1;
+ gdouble angle2;
+ gint draw_arc = 0;
+ gboolean draw_arc_line = FALSE;
+ gdouble arc_line_display_length;
+ gdouble arc_line_length;
+
+ gimp_tool_compass_update_angle (compass, private->orientation, FALSE);
+
+ angle1 = -atan2 (private->radius1.y * shell->scale_y,
+ private->radius1.x * shell->scale_x);
+ angle2 = -private->display_angle;
+
+ gimp_canvas_line_set (private->line1,
+ private->x[0],
+ private->y[0],
+ private->x[1],
+ private->y[1]);
+ gimp_canvas_item_set_visible (private->line1, private->n_points > 1);
+ if (private->n_points > 1 &&
+ gimp_canvas_item_transform_distance (private->line1,
+ private->x[0],
+ private->y[0],
+ private->x[1],
+ private->y[1]) > ARC_RADIUS)
+ {
+ draw_arc++;
+ }
+
+
+ arc_line_display_length = ARC_RADIUS +
+ (GIMP_CANVAS_HANDLE_SIZE_CROSS >> 1) +
+ ARC_GAP;
+ arc_line_length = arc_line_display_length /
+ hypot (private->radius2.x * shell->scale_x,
+ private->radius2.y * shell->scale_y);
+
+ if (private->n_points > 2)
+ {
+ gdouble length = gimp_canvas_item_transform_distance (private->line2,
+ private->x[0],
+ private->y[0],
+ private->x[2],
+ private->y[2]);
+
+ if (length > ARC_RADIUS)
+ {
+ draw_arc++;
+ draw_arc_line = TRUE;
+
+ if (length > arc_line_display_length)
+ {
+ gimp_canvas_line_set (
+ private->line2,
+ private->x[0] + private->radius2.x * arc_line_length,
+ private->y[0] + private->radius2.y * arc_line_length,
+ private->x[2],
+ private->y[2]);
+ gimp_canvas_item_set_visible (private->line2, TRUE);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (private->line2, FALSE);
+ }
+ }
+ else
+ {
+ gimp_canvas_line_set (private->line2,
+ private->x[0],
+ private->y[0],
+ private->x[2],
+ private->y[2]);
+ gimp_canvas_item_set_visible (private->line2, TRUE);
+ }
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (private->line2, FALSE);
+ }
+
+ gimp_canvas_handle_set_position (private->arc,
+ private->x[0], private->y[0]);
+ gimp_canvas_handle_set_angles (private->arc, angle1, angle2);
+ gimp_canvas_item_set_visible (private->arc,
+ private->n_points > 1 &&
+ draw_arc == private->n_points - 1 &&
+ fabs (angle2) > EPSILON);
+
+ arc_line_length = (ARC_RADIUS + (GIMP_CANVAS_HANDLE_SIZE_CROSS >> 1)) /
+ hypot (private->radius2.x * shell->scale_x,
+ private->radius2.y * shell->scale_y);
+
+ gimp_canvas_line_set (private->arc_line,
+ private->x[0],
+ private->y[0],
+ private->x[0] + private->radius2.x * arc_line_length,
+ private->y[0] + private->radius2.y * arc_line_length);
+ gimp_canvas_item_set_visible (private->arc_line,
+ (private->n_points == 2 || draw_arc_line) &&
+ fabs (angle2) > EPSILON);
+
+ gimp_canvas_handle_set_position (private->handles[0],
+ private->x[0], private->y[0]);
+ gimp_canvas_item_set_visible (private->handles[0],
+ private->n_points > 0);
+
+ gimp_canvas_handle_set_position (private->handles[1],
+ private->x[1], private->y[1]);
+ gimp_canvas_item_set_visible (private->handles[1],
+ private->n_points > 1);
+
+ gimp_canvas_handle_set_position (private->handles[2],
+ private->x[2], private->y[2]);
+ gimp_canvas_item_set_visible (private->handles[2],
+ private->n_points > 2);
+
+ gimp_tool_compass_update_hilight (compass);
+}
+
+gint
+gimp_tool_compass_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+
+ private->function = CREATING;
+
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ /* if the cursor is in one of the handles, the new function will be
+ * moving or adding a new point or guide
+ */
+ if (private->point != -1)
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if (state & (toggle_mask | GDK_MOD1_MASK))
+ {
+ gboolean create_hguide = (state & toggle_mask);
+ gboolean create_vguide = (state & GDK_MOD1_MASK);
+
+ g_signal_emit (compass, compass_signals[CREATE_GUIDES], 0,
+ private->x[private->point],
+ private->y[private->point],
+ create_hguide,
+ create_vguide);
+
+ private->function = GUIDING;
+ }
+ else
+ {
+ if (private->n_points == 1 || (state & extend_mask))
+ private->function = ADDING;
+ else
+ private->function = MOVING;
+ }
+ }
+
+ /* adding to the middle point makes no sense */
+ if (private->point == 0 &&
+ private->function == ADDING &&
+ private->n_points == 3)
+ {
+ private->function = MOVING;
+ }
+
+ /* if the function is still CREATING, we are outside the handles */
+ if (private->function == CREATING)
+ {
+ if (private->n_points > 1 && (state & GDK_MOD1_MASK))
+ {
+ private->function = MOVING_ALL;
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+ }
+ }
+
+ if (private->function == CREATING)
+ {
+ /* set the first point and go into ADDING mode */
+ g_object_set (compass,
+ "n-points", 1,
+ "x1", (gint) (coords->x + 0.5),
+ "y1", (gint) (coords->y + 0.5),
+ "x2", 0,
+ "y2", 0,
+ "x3", 0,
+ "y3", 0,
+ NULL);
+
+ private->point = 0;
+ private->function = ADDING;
+ }
+
+ return 1;
+}
+
+void
+gimp_tool_compass_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+
+ private->function = FINISHED;
+}
+
+void
+gimp_tool_compass_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+ gint new_n_points;
+ gint new_x[3];
+ gint new_y[3];
+ gint dx, dy;
+ gint tmp;
+
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ /* A few comments here, because this routine looks quite weird at first ...
+ *
+ * The goal is to keep point 0, called the start point, to be
+ * always the one in the middle or, if there are only two points,
+ * the one that is fixed. The angle is then always measured at
+ * this point.
+ */
+
+ new_n_points = private->n_points;
+ new_x[0] = private->x[0];
+ new_y[0] = private->y[0];
+ new_x[1] = private->x[1];
+ new_y[1] = private->y[1];
+ new_x[2] = private->x[2];
+ new_y[2] = private->y[2];
+
+ switch (private->function)
+ {
+ case ADDING:
+ switch (private->point)
+ {
+ case 0:
+ /* we are adding to the start point */
+ break;
+
+ case 1:
+ /* we are adding to the end point, make it the new start point */
+ new_x[0] = private->x[1];
+ new_y[0] = private->y[1];
+
+ new_x[1] = private->x[0];
+ new_y[1] = private->y[0];
+ break;
+
+ case 2:
+ /* we are adding to the third point, make it the new start point */
+ new_x[1] = private->x[0];
+ new_y[1] = private->y[0];
+ new_x[0] = private->x[2];
+ new_y[0] = private->y[2];
+ break;
+
+ default:
+ break;
+ }
+
+ new_n_points = MIN (new_n_points + 1, 3);
+
+ private->point = new_n_points - 1;
+ private->function = MOVING;
+ /* don't break here! */
+
+ case MOVING:
+ /* if we are moving the start point and only have two, make it
+ * the end point
+ */
+ if (new_n_points == 2 && private->point == 0)
+ {
+ tmp = new_x[0];
+ new_x[0] = new_x[1];
+ new_x[1] = tmp;
+
+ tmp = new_y[0];
+ new_y[0] = new_y[1];
+ new_y[1] = tmp;
+
+ private->point = 1;
+ }
+
+ new_x[private->point] = ROUND (coords->x);
+ new_y[private->point] = ROUND (coords->y);
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ {
+ gdouble x = new_x[private->point];
+ gdouble y = new_y[private->point];
+
+ gimp_display_shell_constrain_line (gimp_tool_widget_get_shell (widget),
+ new_x[0], new_y[0],
+ &x, &y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+
+ new_x[private->point] = ROUND (x);
+ new_y[private->point] = ROUND (y);
+ }
+
+ g_object_set (compass,
+ "n-points", new_n_points,
+ "x1", new_x[0],
+ "y1", new_y[0],
+ "x2", new_x[1],
+ "y2", new_y[1],
+ "x3", new_x[2],
+ "y3", new_y[2],
+ NULL);
+ break;
+
+ case MOVING_ALL:
+ dx = ROUND (coords->x) - private->last_x;
+ dy = ROUND (coords->y) - private->last_y;
+
+ g_object_set (compass,
+ "x1", new_x[0] + dx,
+ "y1", new_y[0] + dy,
+ "x2", new_x[1] + dx,
+ "y2", new_y[1] + dy,
+ "x3", new_x[2] + dx,
+ "y3", new_y[2] + dy,
+ NULL);
+
+ private->last_x = ROUND (coords->x);
+ private->last_y = ROUND (coords->y);
+ break;
+
+ default:
+ break;
+ }
+}
+
+GimpHit
+gimp_tool_compass_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+
+ if (gimp_tool_compass_get_point (compass, coords) >= 0)
+ return GIMP_HIT_DIRECT;
+ else
+ return GIMP_HIT_INDIRECT;
+}
+
+void
+gimp_tool_compass_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+ gint point;
+
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ point = gimp_tool_compass_get_point (compass, coords);
+
+ if (point >= 0)
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+ gchar *status;
+
+ if (state & toggle_mask)
+ {
+ if (state & GDK_MOD1_MASK)
+ {
+ status = gimp_suggest_modifiers (_("Click to place "
+ "vertical and "
+ "horizontal guides"),
+ 0,
+ NULL, NULL, NULL);
+ }
+ else
+ {
+ status = gimp_suggest_modifiers (_("Click to place a "
+ "horizontal guide"),
+ GDK_MOD1_MASK & ~state,
+ NULL, NULL, NULL);
+ }
+ }
+ else if (state & GDK_MOD1_MASK)
+ {
+ status = gimp_suggest_modifiers (_("Click to place a "
+ "vertical guide"),
+ toggle_mask & ~state,
+ NULL, NULL, NULL);
+ }
+ else if ((state & extend_mask) &&
+ ! ((point == 0) && (private->n_points == 3)))
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to add a "
+ "new point"),
+ (toggle_mask |
+ GDK_MOD1_MASK) & ~state,
+ NULL, NULL, NULL);
+ }
+ else
+ {
+ if ((point == 0) && (private->n_points == 3))
+ state |= extend_mask;
+
+ status = gimp_suggest_modifiers (_("Click-Drag to move this "
+ "point"),
+ (extend_mask |
+ toggle_mask |
+ GDK_MOD1_MASK) & ~state,
+ NULL, NULL, NULL);
+ }
+
+ gimp_tool_widget_set_status (widget, status);
+
+ g_free (status);
+ }
+ else
+ {
+ if ((private->n_points > 1) && (state & GDK_MOD1_MASK))
+ {
+ gimp_tool_widget_set_status (widget,
+ _("Click-Drag to move all points"));
+ }
+ else
+ {
+ gimp_tool_widget_set_status (widget, NULL);
+ }
+ }
+
+ if (point != private->point)
+ {
+ private->point = point;
+
+ gimp_tool_compass_update_hilight (compass);
+ }
+}
+
+void
+gimp_tool_compass_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+
+ if (private->point != -1)
+ {
+ private->point = -1;
+
+ gimp_tool_compass_update_hilight (compass);
+ }
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static void
+gimp_tool_compass_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+
+ if (key == gimp_get_constrain_behavior_mask () &&
+ private->function == MOVING)
+ {
+ gint new_x[3];
+ gint new_y[3];
+ gdouble x = private->mouse_x;
+ gdouble y = private->mouse_y;
+
+ new_x[0] = private->x[0];
+ new_y[0] = private->y[0];
+ new_x[1] = private->x[1];
+ new_y[1] = private->y[1];
+ new_x[2] = private->x[2];
+ new_y[2] = private->y[2];
+
+ if (press)
+ {
+ gimp_display_shell_constrain_line (gimp_tool_widget_get_shell (widget),
+ private->x[0], private->y[0],
+ &x, &y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+
+ new_x[private->point] = ROUND (x);
+ new_y[private->point] = ROUND (y);
+
+ g_object_set (compass,
+ "x1", new_x[0],
+ "y1", new_y[0],
+ "x2", new_x[1],
+ "y2", new_y[1],
+ "x3", new_x[2],
+ "y3", new_y[2],
+ NULL);
+ }
+}
+
+static gboolean
+gimp_tool_compass_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget);
+ GimpToolCompassPrivate *private = compass->private;
+
+ if (private->point != -1)
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if (state & toggle_mask)
+ {
+ if (state & GDK_MOD1_MASK)
+ {
+ *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
+ return TRUE;
+ }
+ else
+ {
+ *cursor = GIMP_CURSOR_SIDE_BOTTOM;
+ return TRUE;
+ }
+ }
+ else if (state & GDK_MOD1_MASK)
+ {
+ *cursor = GIMP_CURSOR_SIDE_RIGHT;
+ return TRUE;
+ }
+ else if ((state & extend_mask) &&
+ ! ((private->point == 0) &&
+ (private->n_points == 3)))
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ return TRUE;
+ }
+ else
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ return TRUE;
+ }
+ }
+ else
+ {
+ if ((private->n_points > 1) && (state & GDK_MOD1_MASK))
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_tool_compass_get_point (GimpToolCompass *compass,
+ const GimpCoords *coords)
+{
+ GimpToolCompassPrivate *private = compass->private;
+ gint i;
+
+ for (i = 0; i < private->n_points; i++)
+ {
+ if (gimp_canvas_item_hit (private->handles[i],
+ coords->x, coords->y))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static void
+gimp_tool_compass_update_hilight (GimpToolCompass *compass)
+{
+ GimpToolCompassPrivate *private = compass->private;
+ gint i;
+
+ for (i = 0; i < private->n_points; i++)
+ {
+ if (private->handles[i])
+ {
+ gimp_canvas_item_set_highlight (private->handles[i],
+ private->point == i);
+ }
+ }
+}
+
+static void
+gimp_tool_compass_update_angle (GimpToolCompass *compass,
+ GimpCompassOrientation orientation,
+ gboolean flip)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (compass);
+ GimpToolCompassPrivate *private = compass->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpVector2 radius1;
+ GimpVector2 radius2;
+ gdouble pixel_angle;
+ gdouble unit_angle;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ private->radius1.x = private->x[1] - private->x[0];
+ private->radius1.y = private->y[1] - private->y[0];
+
+ if (private->n_points == 3)
+ {
+ orientation = GIMP_COMPASS_ORIENTATION_AUTO;
+
+ private->radius2.x = private->x[2] - private->x[0];
+ private->radius2.y = private->y[2] - private->y[0];
+ }
+ else
+ {
+ gdouble angle = -shell->rotate_angle * G_PI / 180.0;
+
+ if (orientation == GIMP_COMPASS_ORIENTATION_VERTICAL)
+ angle -= G_PI / 2.0;
+
+ if (flip)
+ angle += G_PI;
+
+ if (shell->flip_horizontally)
+ angle = G_PI - angle;
+ if (shell->flip_vertically)
+ angle = -angle;
+
+ private->radius2.x = cos (angle);
+ private->radius2.y = sin (angle);
+
+ if (! shell->dot_for_dot)
+ {
+ private->radius2.x *= xres;
+ private->radius2.y *= yres;
+
+ gimp_vector2_normalize (&private->radius2);
+ }
+ }
+
+ radius1 = private->radius1;
+ radius2 = private->radius2;
+
+ pixel_angle = atan2 (gimp_vector2_cross_product (&radius1, &radius2).x,
+ gimp_vector2_inner_product (&radius1, &radius2));
+
+ radius1.x /= xres;
+ radius1.y /= yres;
+
+ radius2.x /= xres;
+ radius2.y /= yres;
+
+ unit_angle = atan2 (gimp_vector2_cross_product (&radius1, &radius2).x,
+ gimp_vector2_inner_product (&radius1, &radius2));
+
+ if (shell->dot_for_dot)
+ private->display_angle = pixel_angle;
+ else
+ private->display_angle = unit_angle;
+
+ if (private->n_points == 2)
+ {
+ if (! flip && fabs (private->display_angle) > G_PI / 2.0 + EPSILON)
+ {
+ gimp_tool_compass_update_angle (compass, orientation, TRUE);
+
+ return;
+ }
+ else if (orientation == GIMP_COMPASS_ORIENTATION_AUTO)
+ {
+ if (fabs (private->display_angle) <= G_PI / 4.0 + EPSILON)
+ {
+ orientation = GIMP_COMPASS_ORIENTATION_HORIZONTAL;
+ }
+ else
+ {
+ gimp_tool_compass_update_angle (compass,
+ GIMP_COMPASS_ORIENTATION_VERTICAL,
+ FALSE);
+
+ return;
+ }
+ }
+ }
+
+ if (fabs (pixel_angle - private->pixel_angle) > EPSILON)
+ {
+ private->pixel_angle = pixel_angle;
+
+ g_object_notify (G_OBJECT (compass), "pixel-angle");
+ }
+
+ if (fabs (unit_angle - private->unit_angle) > EPSILON)
+ {
+ private->unit_angle = unit_angle;
+
+ g_object_notify (G_OBJECT (compass), "unit-angle");
+ }
+
+ if (orientation != private->effective_orientation)
+ {
+ private->effective_orientation = orientation;
+
+ g_object_notify (G_OBJECT (compass), "effective-orientation");
+ }
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_compass_new (GimpDisplayShell *shell,
+ GimpCompassOrientation orientation,
+ gint n_points,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint x3,
+ gint y3)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_COMPASS,
+ "shell", shell,
+ "orientation", orientation,
+ "n-points", n_points,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+}
diff --git a/app/display/gimptoolcompass.h b/app/display/gimptoolcompass.h
new file mode 100644
index 0000000..8e6fdfe
--- /dev/null
+++ b/app/display/gimptoolcompass.h
@@ -0,0 +1,72 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolcompass.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_COMPASS_H__
+#define __GIMP_TOOL_COMPASS_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_COMPASS (gimp_tool_compass_get_type ())
+#define GIMP_TOOL_COMPASS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_COMPASS, GimpToolCompass))
+#define GIMP_TOOL_COMPASS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_COMPASS, GimpToolCompassClass))
+#define GIMP_IS_TOOL_COMPASS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_COMPASS))
+#define GIMP_IS_TOOL_COMPASS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_COMPASS))
+#define GIMP_TOOL_COMPASS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_COMPASS, GimpToolCompassClass))
+
+
+typedef struct _GimpToolCompass GimpToolCompass;
+typedef struct _GimpToolCompassPrivate GimpToolCompassPrivate;
+typedef struct _GimpToolCompassClass GimpToolCompassClass;
+
+struct _GimpToolCompass
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolCompassPrivate *private;
+};
+
+struct _GimpToolCompassClass
+{
+ GimpToolWidgetClass parent_class;
+
+ void (* create_guides) (GimpToolCompass *compass,
+ gint x,
+ gint y,
+ gboolean horizontal,
+ gboolean vertical);
+};
+
+
+GType gimp_tool_compass_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_compass_new (GimpDisplayShell *shell,
+ GimpCompassOrientation orinetation,
+ gint n_points,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint y3,
+ gint x3);
+
+
+#endif /* __GIMP_TOOL_COMPASS_H__ */
diff --git a/app/display/gimptooldialog.c b/app/display/gimptooldialog.c
new file mode 100644
index 0000000..e0bc082
--- /dev/null
+++ b/app/display/gimptooldialog.c
@@ -0,0 +1,208 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptooldialog.c
+ * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
+ *
+ * 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimpobject.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimpdialogfactory.h"
+
+#include "gimpdisplayshell.h"
+#include "gimptooldialog.h"
+
+
+typedef struct _GimpToolDialogPrivate GimpToolDialogPrivate;
+
+struct _GimpToolDialogPrivate
+{
+ GimpDisplayShell *shell;
+};
+
+#define GET_PRIVATE(dialog) ((GimpToolDialogPrivate *) gimp_tool_dialog_get_instance_private ((GimpToolDialog *) (dialog)))
+
+
+static void gimp_tool_dialog_dispose (GObject *object);
+
+static void gimp_tool_dialog_shell_unmap (GimpDisplayShell *shell,
+ GimpToolDialog *dialog);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolDialog, gimp_tool_dialog,
+ GIMP_TYPE_VIEWABLE_DIALOG)
+
+
+static void
+gimp_tool_dialog_class_init (GimpToolDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_tool_dialog_dispose;
+}
+
+static void
+gimp_tool_dialog_init (GimpToolDialog *dialog)
+{
+}
+
+static void
+gimp_tool_dialog_dispose (GObject *object)
+{
+ GimpToolDialogPrivate *private = GET_PRIVATE (object);
+
+ if (private->shell)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (private->shell),
+ (gpointer) &private->shell);
+ private->shell = NULL;
+ }
+
+ G_OBJECT_CLASS (gimp_tool_dialog_parent_class)->dispose (object);
+}
+
+
+/**
+ * gimp_tool_dialog_new:
+ * @tool_info: a #GimpToolInfo
+ * @desc: a string to use in the dialog header or %NULL to use the help
+ * field from #GimpToolInfo
+ * @...: a %NULL-terminated valist of button parameters as described in
+ * gtk_dialog_new_with_buttons().
+ *
+ * This function conveniently creates a #GimpViewableDialog using the
+ * information stored in @tool_info. It also registers the tool with
+ * the "toplevel" dialog factory.
+ *
+ * Return value: a new #GimpViewableDialog
+ **/
+GtkWidget *
+gimp_tool_dialog_new (GimpToolInfo *tool_info,
+ GdkScreen *screen,
+ gint monitor,
+ const gchar *title,
+ const gchar *description,
+ const gchar *icon_name,
+ const gchar *help_id,
+ ...)
+{
+ GtkWidget *dialog;
+ gchar *identifier;
+ va_list args;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL);
+
+ if (! title)
+ title = tool_info->label;
+
+ if (! description)
+ description = tool_info->tooltip;
+
+ if (! help_id)
+ help_id = tool_info->help_id;
+
+ if (! icon_name)
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info));
+
+ dialog = g_object_new (GIMP_TYPE_TOOL_DIALOG,
+ "title", title,
+ "role", gimp_object_get_name (tool_info),
+ "description", description,
+ "icon-name", icon_name,
+ "help-func", gimp_standard_help_func,
+ "help-id", help_id,
+ NULL);
+
+ va_start (args, help_id);
+ gimp_dialog_add_buttons_valist (GIMP_DIALOG (dialog), args);
+ va_end (args);
+
+ identifier = g_strconcat (gimp_object_get_name (tool_info), "-dialog", NULL);
+
+ gimp_dialog_factory_add_foreign (gimp_dialog_factory_get_singleton (),
+ identifier,
+ dialog,
+ screen,
+ monitor);
+
+ g_free (identifier);
+
+ return dialog;
+}
+
+void
+gimp_tool_dialog_set_shell (GimpToolDialog *tool_dialog,
+ GimpDisplayShell *shell)
+{
+ GimpToolDialogPrivate *private = GET_PRIVATE (tool_dialog);
+
+ g_return_if_fail (GIMP_IS_TOOL_DIALOG (tool_dialog));
+ g_return_if_fail (shell == NULL || GIMP_IS_DISPLAY_SHELL (shell));
+
+ if (shell == private->shell)
+ return;
+
+ if (private->shell)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (private->shell),
+ (gpointer) &private->shell);
+ g_signal_handlers_disconnect_by_func (private->shell,
+ gimp_tool_dialog_shell_unmap,
+ tool_dialog);
+
+ gtk_window_set_transient_for (GTK_WINDOW (tool_dialog), NULL);
+ }
+
+ private->shell = shell;
+
+ if (private->shell)
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
+
+ gtk_window_set_transient_for (GTK_WINDOW (tool_dialog),
+ GTK_WINDOW (toplevel));
+
+ g_signal_connect_object (private->shell, "unmap",
+ G_CALLBACK (gimp_tool_dialog_shell_unmap),
+ tool_dialog, 0);
+ g_object_add_weak_pointer (G_OBJECT (private->shell),
+ (gpointer) &private->shell);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_tool_dialog_shell_unmap (GimpDisplayShell *shell,
+ GimpToolDialog *dialog)
+{
+ /* the dialog being mapped while the shell is being unmapped
+ * happens when switching images in GimpImageWindow
+ */
+ if (gtk_widget_get_mapped (GTK_WIDGET (dialog)))
+ g_signal_emit_by_name (dialog, "close");
+}
diff --git a/app/display/gimptooldialog.h b/app/display/gimptooldialog.h
new file mode 100644
index 0000000..13f437d
--- /dev/null
+++ b/app/display/gimptooldialog.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptooldialog.h
+ * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_DIALOG_H__
+#define __GIMP_TOOL_DIALOG_H__
+
+#include "widgets/gimpviewabledialog.h"
+
+
+#define GIMP_TYPE_TOOL_DIALOG (gimp_tool_dialog_get_type ())
+#define GIMP_TOOL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_DIALOG, GimpToolDialog))
+#define GIMP_TOOL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_DIALOG, GimpToolDialogClass))
+#define GIMP_IS_TOOL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_DIALOG))
+#define GIMP_IS_TOOL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_DIALOG))
+#define GIMP_TOOL_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_DIALOG, GimpToolDialogClass))
+
+
+typedef struct _GimpViewableDialogClass GimpToolDialogClass;
+
+struct _GimpToolDialog
+{
+ GimpViewableDialog parent_instance;
+};
+
+
+GType gimp_tool_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_tool_dialog_new (GimpToolInfo *tool_info,
+ GdkScreen *screen,
+ gint monitor,
+ const gchar *title,
+ const gchar *description,
+ const gchar *icon_name,
+ const gchar *help_id,
+ ...) G_GNUC_NULL_TERMINATED;
+
+void gimp_tool_dialog_set_shell (GimpToolDialog *tool_dialog,
+ GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_TOOL_DIALOG_H__ */
diff --git a/app/display/gimptoolfocus.c b/app/display/gimptoolfocus.c
new file mode 100644
index 0000000..a607f06
--- /dev/null
+++ b/app/display/gimptoolfocus.c
@@ -0,0 +1,1209 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolfocus.c
+ * Copyright (C) 2020 Ell
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvasgroup.h"
+#include "gimpcanvashandle.h"
+#include "gimpcanvaslimit.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolfocus.h"
+
+#include "gimp-intl.h"
+
+
+#define HANDLE_SIZE 12.0
+#define SNAP_DISTANCE 12.0
+
+#define EPSILON 1e-6
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_X,
+ PROP_Y,
+ PROP_RADIUS,
+ PROP_ASPECT_RATIO,
+ PROP_ANGLE,
+ PROP_INNER_LIMIT,
+ PROP_MIDPOINT
+};
+
+enum
+{
+ LIMIT_OUTER,
+ LIMIT_INNER,
+ LIMIT_MIDDLE,
+
+ N_LIMITS
+};
+
+
+typedef enum
+{
+ HOVER_NONE,
+ HOVER_LIMIT,
+ HOVER_HANDLE,
+ HOVER_MOVE,
+ HOVER_ROTATE
+} Hover;
+
+
+typedef struct
+{
+ GimpCanvasItem *item;
+
+ GtkOrientation orientation;
+ GimpVector2 dir;
+} GimpToolFocusHandle;
+
+typedef struct
+{
+ GimpCanvasGroup *group;
+ GimpCanvasItem *item;
+
+ gint n_handles;
+ GimpToolFocusHandle handles[4];
+} GimpToolFocusLimit;
+
+struct _GimpToolFocusPrivate
+{
+ GimpLimitType type;
+
+ gdouble x;
+ gdouble y;
+ gdouble radius;
+ gdouble aspect_ratio;
+ gdouble angle;
+
+ gdouble inner_limit;
+ gdouble midpoint;
+
+ GimpToolFocusLimit limits[N_LIMITS];
+
+ Hover hover;
+ gint hover_limit;
+ gint hover_handle;
+ GimpCanvasItem *hover_item;
+
+ GimpCanvasItem *last_hover_item;
+
+ gdouble saved_x;
+ gdouble saved_y;
+ gdouble saved_radius;
+ gdouble saved_aspect_ratio;
+ gdouble saved_angle;
+
+ gdouble saved_inner_limit;
+ gdouble saved_midpoint;
+
+ gdouble orig_x;
+ gdouble orig_y;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_focus_constructed (GObject *object);
+static void gimp_tool_focus_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_focus_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_focus_changed (GimpToolWidget *widget);
+static gint gimp_tool_focus_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_focus_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_focus_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_focus_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_focus_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_focus_leave_notify (GimpToolWidget *widget);
+static void gimp_tool_focus_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static void gimp_tool_focus_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_focus_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_focus_update_hover (GimpToolFocus *focus,
+ const GimpCoords *coords,
+ gboolean proximity);
+
+static void gimp_tool_focus_update_highlight (GimpToolFocus *focus);
+static void gimp_tool_focus_update_status (GimpToolFocus *focus,
+ GdkModifierType state);
+
+static void gimp_tool_focus_save (GimpToolFocus *focus);
+static void gimp_tool_focus_restore (GimpToolFocus *focus);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolFocus, gimp_tool_focus,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_focus_parent_class
+
+
+/* private functions */
+
+static void
+gimp_tool_focus_class_init (GimpToolFocusClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_focus_constructed;
+ object_class->set_property = gimp_tool_focus_set_property;
+ object_class->get_property = gimp_tool_focus_get_property;
+
+ widget_class->changed = gimp_tool_focus_changed;
+ widget_class->button_press = gimp_tool_focus_button_press;
+ widget_class->button_release = gimp_tool_focus_button_release;
+ widget_class->motion = gimp_tool_focus_motion;
+ widget_class->hit = gimp_tool_focus_hit;
+ widget_class->hover = gimp_tool_focus_hover;
+ widget_class->leave_notify = gimp_tool_focus_leave_notify;
+ widget_class->motion_modifier = gimp_tool_focus_motion_modifier;
+ widget_class->hover_modifier = gimp_tool_focus_hover_modifier;
+ widget_class->get_cursor = gimp_tool_focus_get_cursor;
+ widget_class->update_on_scale = TRUE;
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL, NULL,
+ GIMP_TYPE_LIMIT_TYPE,
+ GIMP_LIMIT_CIRCLE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_RADIUS,
+ g_param_spec_double ("radius", NULL, NULL,
+ 0.0,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ASPECT_RATIO,
+ g_param_spec_double ("aspect-ratio", NULL, NULL,
+ -1.0,
+ +1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ANGLE,
+ g_param_spec_double ("angle", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_INNER_LIMIT,
+ g_param_spec_double ("inner-limit", NULL, NULL,
+ 0.0,
+ 1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_MIDPOINT,
+ g_param_spec_double ("midpoint", NULL, NULL,
+ 0.0,
+ 1.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_focus_init (GimpToolFocus *focus)
+{
+ GimpToolFocusPrivate *priv;
+
+ priv = gimp_tool_focus_get_instance_private (focus);
+
+ focus->priv = priv;
+
+ priv->hover = HOVER_NONE;
+}
+
+static void
+gimp_tool_focus_constructed (GObject *object)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolFocusPrivate *priv = focus->priv;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ for (i = N_LIMITS - 1; i >= 0; i--)
+ {
+ priv->limits[i].group = gimp_tool_widget_add_group (widget);
+
+ gimp_tool_widget_push_group (widget, priv->limits[i].group);
+
+ priv->limits[i].item = gimp_tool_widget_add_limit (
+ widget,
+ GIMP_LIMIT_CIRCLE,
+ 0.0, 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ /* dashed = */ i == LIMIT_MIDDLE);
+
+ if (i == LIMIT_OUTER || i == LIMIT_INNER)
+ {
+ gint j;
+
+ priv->limits[i].n_handles = 4;
+
+ for (j = priv->limits[i].n_handles - 1; j >= 0; j--)
+ {
+ priv->limits[i].handles[j].item = gimp_tool_widget_add_handle (
+ widget,
+ GIMP_HANDLE_FILLED_CIRCLE,
+ 0.0, 0.0, HANDLE_SIZE, HANDLE_SIZE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ priv->limits[i].handles[j].orientation =
+ j % 2 == 0 ? GTK_ORIENTATION_HORIZONTAL :
+ GTK_ORIENTATION_VERTICAL;
+
+ priv->limits[i].handles[j].dir.x = cos (j * G_PI / 2.0);
+ priv->limits[i].handles[j].dir.y = sin (j * G_PI / 2.0);
+ }
+ }
+
+ gimp_tool_widget_pop_group (widget);
+ }
+
+ gimp_tool_focus_changed (widget);
+}
+
+static void
+gimp_tool_focus_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (object);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ priv->type = g_value_get_enum (value);
+ break;
+
+ case PROP_X:
+ priv->x = g_value_get_double (value);
+ break;
+
+ case PROP_Y:
+ priv->y = g_value_get_double (value);
+ break;
+
+ case PROP_RADIUS:
+ priv->radius = g_value_get_double (value);
+ break;
+
+ case PROP_ASPECT_RATIO:
+ priv->aspect_ratio = g_value_get_double (value);
+ break;
+
+ case PROP_ANGLE:
+ priv->angle = g_value_get_double (value);
+ break;
+
+ case PROP_INNER_LIMIT:
+ priv->inner_limit = g_value_get_double (value);
+ break;
+
+ case PROP_MIDPOINT:
+ priv->midpoint = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_focus_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (object);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ g_value_set_enum (value, priv->type);
+ break;
+
+ case PROP_X:
+ g_value_set_double (value, priv->x);
+ break;
+
+ case PROP_Y:
+ g_value_set_double (value, priv->y);
+ break;
+
+ case PROP_RADIUS:
+ g_value_set_double (value, priv->radius);
+ break;
+
+ case PROP_ASPECT_RATIO:
+ g_value_set_double (value, priv->aspect_ratio);
+ break;
+
+ case PROP_ANGLE:
+ g_value_set_double (value, priv->angle);
+ break;
+
+ case PROP_INNER_LIMIT:
+ g_value_set_double (value, priv->inner_limit);
+ break;
+
+ case PROP_MIDPOINT:
+ g_value_set_double (value, priv->midpoint);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_focus_changed (GimpToolWidget *widget)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+ gint i;
+
+ for (i = 0; i < N_LIMITS; i++)
+ {
+ gimp_canvas_item_begin_change (priv->limits[i].item);
+
+ g_object_set (priv->limits[i].item,
+ "type", priv->type,
+ "x", priv->x,
+ "y", priv->y,
+ "aspect-ratio", priv->aspect_ratio,
+ "angle", priv->angle,
+ NULL);
+ }
+
+ g_object_set (priv->limits[LIMIT_OUTER].item,
+ "radius", priv->radius,
+ NULL);
+
+ g_object_set (priv->limits[LIMIT_INNER].item,
+ "radius", priv->radius * priv->inner_limit,
+ NULL);
+
+ g_object_set (priv->limits[LIMIT_MIDDLE].item,
+ "radius", priv->radius * (priv->inner_limit +
+ (1.0 - priv->inner_limit) *
+ priv->midpoint),
+ NULL);
+
+ for (i = 0; i < N_LIMITS; i++)
+ {
+ gdouble rx, ry;
+ gdouble max_r = 0.0;
+ gint j;
+
+ gimp_canvas_limit_get_radii (GIMP_CANVAS_LIMIT (priv->limits[i].item),
+ &rx, &ry);
+
+ for (j = 0; j < priv->limits[i].n_handles; j++)
+ {
+ GimpVector2 p = priv->limits[i].handles[j].dir;
+ gdouble r;
+
+ p.x *= rx;
+ p.y *= ry;
+
+ gimp_vector2_rotate (&p, -priv->angle);
+
+ p.x += priv->x;
+ p.y += priv->y;
+
+ gimp_canvas_handle_set_position (priv->limits[i].handles[j].item,
+ p.x, p.y);
+
+ r = gimp_canvas_item_transform_distance (
+ priv->limits[i].handles[j].item,
+ priv->x, priv->y,
+ p.x, p.y);
+
+ max_r = MAX (max_r, r);
+ }
+
+ for (j = 0; j < priv->limits[i].n_handles; j++)
+ {
+ gimp_canvas_item_set_visible (priv->limits[i].handles[j].item,
+ priv->type != GIMP_LIMIT_HORIZONTAL &&
+ priv->type != GIMP_LIMIT_VERTICAL &&
+ max_r >= 1.5 * HANDLE_SIZE);
+ }
+
+ gimp_canvas_item_end_change (priv->limits[i].item);
+ }
+}
+
+static gint
+gimp_tool_focus_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ gimp_tool_focus_save (focus);
+
+ priv->orig_x = coords->x;
+ priv->orig_y = coords->y;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_tool_focus_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_tool_focus_restore (focus);
+}
+
+static void
+gimp_tool_focus_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ gboolean extend;
+ gboolean constrain;
+
+ extend = state & gimp_get_extend_selection_mask ();
+ constrain = state & gimp_get_constrain_behavior_mask ();
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ break;
+
+ case HOVER_LIMIT:
+ {
+ GimpCanvasItem *limit = priv->limits[priv->hover_limit].item;
+ gdouble radius;
+ gdouble outer_radius;
+ gdouble inner_radius;
+ gdouble x, y;
+ gdouble cx, cy;
+
+ x = coords->x;
+ y = coords->y;
+
+ gimp_canvas_limit_center_point (GIMP_CANVAS_LIMIT (limit),
+ x, y,
+ &cx, &cy);
+
+ if (gimp_canvas_item_transform_distance (limit,
+ x, y,
+ cx, cy) <= SNAP_DISTANCE)
+ {
+ x = cx;
+ y = cy;
+ }
+
+ if (fabs (fabs (priv->aspect_ratio) - 1.0) <= EPSILON)
+ {
+ if (priv->radius <= EPSILON)
+ {
+ g_object_set (focus,
+ "aspect-ratio", 0.0,
+ NULL);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ radius = gimp_canvas_limit_boundary_radius (GIMP_CANVAS_LIMIT (limit),
+ x, y);
+
+ outer_radius = priv->radius;
+ inner_radius = priv->radius * priv->inner_limit;
+
+ switch (priv->hover_limit)
+ {
+ case LIMIT_OUTER:
+ {
+ outer_radius = radius;
+
+ if (extend)
+ inner_radius = priv->inner_limit * radius;
+ else
+ outer_radius = MAX (outer_radius, inner_radius);
+ }
+ break;
+
+ case LIMIT_INNER:
+ {
+ inner_radius = radius;
+
+ if (extend)
+ {
+ if (priv->inner_limit > EPSILON)
+ outer_radius = inner_radius / priv->inner_limit;
+ else
+ inner_radius = 0.0;
+ }
+ else
+ {
+ inner_radius = MIN (inner_radius, outer_radius);
+ }
+ }
+ break;
+
+ case LIMIT_MIDDLE:
+ {
+ if (extend)
+ {
+ if (priv->inner_limit > EPSILON || priv->midpoint > EPSILON)
+ {
+ outer_radius = radius / (priv->inner_limit +
+ (1.0 - priv->inner_limit) *
+ priv->midpoint);
+ inner_radius = priv->inner_limit * outer_radius;
+ }
+ else
+ {
+ radius = 0.0;
+ }
+ }
+ else
+ {
+ radius = CLAMP (radius, inner_radius, outer_radius);
+ }
+
+ if (fabs (outer_radius - inner_radius) > EPSILON)
+ {
+ g_object_set (focus,
+ "midpoint", MAX ((radius - inner_radius) /
+ (outer_radius - inner_radius),
+ 0.0),
+ NULL);
+ }
+ }
+ break;
+ }
+
+ g_object_set (focus,
+ "radius", outer_radius,
+ NULL);
+
+ if (outer_radius > EPSILON)
+ {
+ g_object_set (focus,
+ "inner-limit", inner_radius / outer_radius,
+ NULL);
+ }
+ }
+ break;
+
+ case HOVER_HANDLE:
+ {
+ GimpToolFocusHandle *handle;
+ GimpVector2 e;
+ GimpVector2 s;
+ GimpVector2 p;
+ gdouble rx, ry;
+ gdouble r;
+
+ handle = &priv->limits[priv->hover_limit].handles[priv->hover_handle];
+
+ e = handle->dir;
+
+ gimp_vector2_rotate (&e, -priv->angle);
+
+ s = e;
+
+ gimp_canvas_limit_get_radii (
+ GIMP_CANVAS_LIMIT (priv->limits[priv->hover_limit].item),
+ &rx, &ry);
+
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ gimp_vector2_mul (&s, ry);
+ else
+ gimp_vector2_mul (&s, rx);
+
+ p.x = coords->x - priv->x;
+ p.y = coords->y - priv->y;
+
+ r = gimp_vector2_inner_product (&p, &e);
+ r = MAX (r, 0.0);
+
+ p = e;
+
+ gimp_vector2_mul (&p, r);
+
+ if (extend)
+ {
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (rx <= EPSILON && ry > EPSILON)
+ break;
+
+ ry = r * (1.0 - priv->aspect_ratio);
+ }
+ else
+ {
+ if (ry <= EPSILON && rx > EPSILON)
+ break;
+
+ rx = r * (1.0 + priv->aspect_ratio);
+ }
+ }
+ else
+ {
+ if (gimp_canvas_item_transform_distance (
+ priv->limits[priv->hover_limit].item,
+ s.x, s.y,
+ p.x, p.y) <= SNAP_DISTANCE * 0.75)
+ {
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ r = ry;
+ else
+ r = rx;
+ }
+ }
+
+ if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+ rx = r;
+ else
+ ry = r;
+
+ r = MAX (rx, ry);
+
+ if (priv->hover_limit == LIMIT_INNER)
+ r /= priv->inner_limit;
+
+ g_object_set (focus,
+ "radius", r,
+ NULL);
+
+ if (! extend)
+ {
+ gdouble aspect_ratio;
+
+ if (fabs (rx - ry) <= EPSILON)
+ aspect_ratio = 0.0;
+ else if (rx > ry)
+ aspect_ratio = 1.0 - ry / rx;
+ else
+ aspect_ratio = rx / ry - 1.0;
+
+ g_object_set (focus,
+ "aspect-ratio", aspect_ratio,
+ NULL);
+ }
+ }
+ break;
+
+ case HOVER_MOVE:
+ g_object_set (focus,
+ "x", priv->saved_x + (coords->x - priv->orig_x),
+ "y", priv->saved_y + (coords->y - priv->orig_y),
+ NULL);
+ break;
+
+ case HOVER_ROTATE:
+ {
+ gdouble angle;
+ gdouble orig_angle;
+
+ angle = atan2 (coords->y - priv->y, coords->x - priv->x);
+ orig_angle = atan2 (priv->orig_y - priv->y, priv->orig_x - priv->x);
+
+ angle = priv->saved_angle + (angle - orig_angle);
+
+ if (constrain)
+ angle = gimp_display_shell_constrain_angle (shell, angle, 12);
+
+ g_object_set (focus,
+ "angle", angle,
+ NULL);
+ }
+ break;
+ }
+}
+
+static GimpHit
+gimp_tool_focus_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ gimp_tool_focus_update_hover (focus, coords, proximity);
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ return GIMP_HIT_NONE;
+
+ case HOVER_LIMIT:
+ case HOVER_HANDLE:
+ return GIMP_HIT_DIRECT;
+
+ case HOVER_MOVE:
+ case HOVER_ROTATE:
+ return GIMP_HIT_INDIRECT;
+ }
+
+ g_return_val_if_reached (GIMP_HIT_NONE);
+}
+
+static void
+gimp_tool_focus_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_hover (focus, coords, proximity);
+
+ gimp_tool_focus_update_highlight (focus);
+ gimp_tool_focus_update_status (focus, state);
+}
+
+static void
+gimp_tool_focus_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_hover (focus, NULL, FALSE);
+
+ gimp_tool_focus_update_highlight (focus);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static void
+gimp_tool_focus_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_status (focus, state);
+}
+
+static void
+gimp_tool_focus_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+ gimp_tool_focus_update_status (focus, state);
+}
+
+static gboolean
+gimp_tool_focus_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ return FALSE;
+
+ case HOVER_LIMIT:
+ case HOVER_HANDLE:
+ *modifier = GIMP_CURSOR_MODIFIER_RESIZE;
+ return TRUE;
+
+ case HOVER_MOVE:
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ return TRUE;
+
+ case HOVER_ROTATE:
+ *modifier = GIMP_CURSOR_MODIFIER_ROTATE;
+ return TRUE;
+ }
+
+ g_return_val_if_reached (FALSE);
+}
+
+static void
+gimp_tool_focus_update_hover (GimpToolFocus *focus,
+ const GimpCoords *coords,
+ gboolean proximity)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+ gdouble min_handle_dist = HANDLE_SIZE;
+ gint i;
+
+ priv->hover = HOVER_NONE;
+ priv->hover_item = NULL;
+
+ if (! proximity)
+ return;
+
+ for (i = 0; i < N_LIMITS; i++)
+ {
+ gint j;
+
+ for (j = 0; j < priv->limits[i].n_handles; j++)
+ {
+ GimpCanvasItem *handle = priv->limits[i].handles[j].item;
+
+ if (gimp_canvas_item_get_visible (handle))
+ {
+ gdouble x, y;
+ gdouble dist;
+
+ g_object_get (handle,
+ "x", &x,
+ "y", &y,
+ NULL);
+
+ dist = gimp_canvas_item_transform_distance (handle,
+ x, y,
+ coords->x, coords->y);
+
+ if (dist < min_handle_dist)
+ {
+ min_handle_dist = dist;
+
+ priv->hover = HOVER_HANDLE;
+ priv->hover_limit = i;
+ priv->hover_handle = j;
+ priv->hover_item = handle;
+ }
+ }
+ }
+ }
+
+ if (priv->hover != HOVER_NONE)
+ return;
+
+ if ( gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
+ coords->x, coords->y) &&
+ ! gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
+ coords->x, coords->y))
+ {
+ if (gimp_canvas_item_hit (priv->limits[LIMIT_MIDDLE].item,
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_LIMIT;
+ priv->hover_limit = LIMIT_MIDDLE;
+ priv->hover_item = priv->limits[LIMIT_MIDDLE].item;
+
+ return;
+ }
+ }
+
+ if (! gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
+ coords->x, coords->y))
+ {
+ if (gimp_canvas_item_hit (priv->limits[LIMIT_OUTER].item,
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_LIMIT;
+ priv->hover_limit = LIMIT_OUTER;
+ priv->hover_item = priv->limits[LIMIT_OUTER].item;
+
+ return;
+ }
+ }
+
+ if (gimp_canvas_item_hit (priv->limits[LIMIT_INNER].item,
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_LIMIT;
+ priv->hover_limit = LIMIT_INNER;
+ priv->hover_item = priv->limits[LIMIT_INNER].item;
+
+ return;
+ }
+
+ if (gimp_canvas_limit_is_inside (
+ GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
+ coords->x, coords->y))
+ {
+ priv->hover = HOVER_MOVE;
+ }
+ else
+ {
+ priv->hover = HOVER_ROTATE;
+ }
+}
+
+static void
+gimp_tool_focus_update_highlight (GimpToolFocus *focus)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (focus);
+ GimpToolFocusPrivate *priv = focus->priv;
+ gint i;
+
+ if (priv->hover_item == priv->last_hover_item)
+ return;
+
+ if (priv->last_hover_item)
+ gimp_canvas_item_set_highlight (priv->last_hover_item, FALSE);
+
+ #define RAISE_ITEM(item) \
+ G_STMT_START \
+ { \
+ g_object_ref (item); \
+ \
+ gimp_tool_widget_remove_item (widget, item); \
+ gimp_tool_widget_add_item (widget, item); \
+ \
+ g_object_unref (item); \
+ } \
+ G_STMT_END
+
+ for (i = N_LIMITS - 1; i >= 0; i--)
+ RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[i].group));
+
+ if (priv->hover_item)
+ {
+ gimp_canvas_item_set_highlight (priv->hover_item, TRUE);
+
+ RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[priv->hover_limit].group));
+ }
+
+ #undef RAISE_ITEM
+
+ priv->last_hover_item = priv->hover_item;
+}
+
+static void
+gimp_tool_focus_update_status (GimpToolFocus *focus,
+ GdkModifierType state)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+ GdkModifierType state_mask = 0;
+ const gchar *message = NULL;
+ const gchar *extend_selection_format = NULL;
+ const gchar *toggle_behavior_format = NULL;
+ gchar *status;
+
+ switch (priv->hover)
+ {
+ case HOVER_NONE:
+ break;
+
+ case HOVER_LIMIT:
+ if (! (state & gimp_get_extend_selection_mask ()))
+ {
+ if (priv->hover_limit == LIMIT_MIDDLE)
+ message = _("Click-Drag to change the midpoint");
+ else
+ message = _("Click-Drag to resize the limit");
+
+ extend_selection_format = _("%s to resize the focus");
+ state_mask |= gimp_get_extend_selection_mask ();
+ }
+ else
+ {
+ message = _("Click-Drag to resize the focus");
+ }
+ break;
+
+ case HOVER_HANDLE:
+ if (! (state & gimp_get_extend_selection_mask ()))
+ {
+ message = _("Click-Drag to change the aspect ratio");
+ extend_selection_format = _("%s to resize the focus");
+ state_mask |= gimp_get_extend_selection_mask ();
+ }
+ else
+ {
+ message = _("Click-Drag to resize the focus");
+ }
+ break;
+
+ case HOVER_MOVE:
+ message = _("Click-Drag to move the focus");
+ break;
+
+ case HOVER_ROTATE:
+ message = _("Click-Drag to rotate the focus");
+ toggle_behavior_format = _("%s for constrained angles");
+ state_mask |= gimp_get_constrain_behavior_mask ();
+ break;
+ }
+
+ status = gimp_suggest_modifiers (message,
+ ~state & state_mask,
+ extend_selection_format,
+ toggle_behavior_format,
+ NULL);
+
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (focus), status);
+
+ g_free (status);
+}
+
+static void
+gimp_tool_focus_save (GimpToolFocus *focus)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ priv->saved_x = priv->x;
+ priv->saved_y = priv->y;
+ priv->saved_radius = priv->radius;
+ priv->saved_aspect_ratio = priv->aspect_ratio;
+ priv->saved_angle = priv->angle;
+
+ priv->saved_inner_limit = priv->inner_limit;
+ priv->saved_midpoint = priv->midpoint;
+}
+
+static void
+gimp_tool_focus_restore (GimpToolFocus *focus)
+{
+ GimpToolFocusPrivate *priv = focus->priv;
+
+ g_object_set (focus,
+ "x", priv->saved_x,
+ "y", priv->saved_y,
+ "radius", priv->saved_radius,
+ "aspect-ratio", priv->saved_aspect_ratio,
+ "angle", priv->saved_angle,
+
+ "inner_limit", priv->saved_inner_limit,
+ "midpoint", priv->saved_midpoint,
+
+ NULL);
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_focus_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_FOCUS,
+ "shell", shell,
+ NULL);
+}
diff --git a/app/display/gimptoolfocus.h b/app/display/gimptoolfocus.h
new file mode 100644
index 0000000..e2fc5bd
--- /dev/null
+++ b/app/display/gimptoolfocus.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolfocus.h
+ * Copyright (C) 2020 Ell
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_FOCUS_H__
+#define __GIMP_TOOL_FOCUS_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_FOCUS (gimp_tool_focus_get_type ())
+#define GIMP_TOOL_FOCUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_FOCUS, GimpToolFocus))
+#define GIMP_TOOL_FOCUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_FOCUS, GimpToolFocusClass))
+#define GIMP_IS_TOOL_FOCUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_FOCUS))
+#define GIMP_IS_TOOL_FOCUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_FOCUS))
+#define GIMP_TOOL_FOCUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_FOCUS, GimpToolFocusClass))
+
+
+typedef struct _GimpToolFocus GimpToolFocus;
+typedef struct _GimpToolFocusPrivate GimpToolFocusPrivate;
+typedef struct _GimpToolFocusClass GimpToolFocusClass;
+
+struct _GimpToolFocus
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolFocusPrivate *priv;
+};
+
+struct _GimpToolFocusClass
+{
+ GimpToolWidgetClass parent_class;
+};
+
+
+GType gimp_tool_focus_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_focus_new (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_TOOL_FOCUS_H__ */
diff --git a/app/display/gimptoolgui.c b/app/display/gimptoolgui.c
new file mode 100644
index 0000000..28c40bf
--- /dev/null
+++ b/app/display/gimptoolgui.c
@@ -0,0 +1,1037 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgui.c
+ * Copyright (C) 2013 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "display-types.h"
+
+#include "core/gimpcontext.h"
+#include "core/gimpmarshal.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpoverlaybox.h"
+#include "widgets/gimpoverlaydialog.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimptooldialog.h"
+#include "gimptoolgui.h"
+
+
+enum
+{
+ RESPONSE,
+ LAST_SIGNAL
+};
+
+
+typedef struct _ResponseEntry ResponseEntry;
+
+struct _ResponseEntry
+{
+ gint response_id;
+ gchar *button_text;
+ gint alternative_position;
+ gboolean sensitive;
+};
+
+typedef struct _GimpToolGuiPrivate GimpToolGuiPrivate;
+
+struct _GimpToolGuiPrivate
+{
+ GimpToolInfo *tool_info;
+ gchar *title;
+ gchar *description;
+ gchar *icon_name;
+ gchar *help_id;
+ GList *response_entries;
+ gint default_response;
+ gboolean focus_on_map;
+
+ gboolean overlay;
+ gboolean auto_overlay;
+
+ GimpDisplayShell *shell;
+ GimpViewable *viewable;
+
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+};
+
+#define GET_PRIVATE(gui) ((GimpToolGuiPrivate *) gimp_tool_gui_get_instance_private ((GimpToolGui *) (gui)))
+
+
+static void gimp_tool_gui_dispose (GObject *object);
+static void gimp_tool_gui_finalize (GObject *object);
+
+static void gimp_tool_gui_create_dialog (GimpToolGui *gui,
+ GdkScreen *screen,
+ gint monitor);
+static void gimp_tool_gui_add_dialog_button (GimpToolGui *gui,
+ ResponseEntry *entry);
+static void gimp_tool_gui_update_buttons (GimpToolGui *gui);
+static void gimp_tool_gui_update_shell (GimpToolGui *gui);
+static void gimp_tool_gui_update_viewable (GimpToolGui *gui);
+
+static void gimp_tool_gui_dialog_response (GtkWidget *dialog,
+ gint response_id,
+ GimpToolGui *gui);
+static void gimp_tool_gui_canvas_resized (GtkWidget *canvas,
+ GtkAllocation *allocation,
+ GimpToolGui *gui);
+
+static ResponseEntry * response_entry_new (gint response_id,
+ const gchar *button_text);
+static void response_entry_free (ResponseEntry *entry);
+static ResponseEntry * response_entry_find (GList *entries,
+ gint response_id);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGui, gimp_tool_gui, GIMP_TYPE_OBJECT)
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+#define parent_class gimp_tool_gui_parent_class
+
+
+static void
+gimp_tool_gui_class_init (GimpToolGuiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_tool_gui_dispose;
+ object_class->finalize = gimp_tool_gui_finalize;
+
+ signals[RESPONSE] =
+ g_signal_new ("response",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpToolGuiClass, response),
+ NULL, NULL,
+ gimp_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+}
+
+static void
+gimp_tool_gui_init (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+
+ private->default_response = -1;
+ private->focus_on_map = TRUE;
+
+ private->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ g_object_ref_sink (private->vbox);
+}
+
+static void
+gimp_tool_gui_dispose (GObject *object)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->tool_info);
+
+ if (private->shell)
+ gimp_tool_gui_set_shell (GIMP_TOOL_GUI (object), NULL);
+
+ if (private->viewable)
+ gimp_tool_gui_set_viewable (GIMP_TOOL_GUI (object), NULL);
+
+ g_clear_object (&private->vbox);
+
+ if (private->dialog)
+ {
+ if (gtk_widget_get_visible (private->dialog))
+ gimp_tool_gui_hide (GIMP_TOOL_GUI (object));
+
+ if (private->overlay)
+ g_object_unref (private->dialog);
+ else
+ gtk_widget_destroy (private->dialog);
+
+ private->dialog = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_tool_gui_finalize (GObject *object)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (object);
+
+ g_clear_pointer (&private->title, g_free);
+ g_clear_pointer (&private->description, g_free);
+ g_clear_pointer (&private->icon_name, g_free);
+ g_clear_pointer (&private->help_id, g_free);
+
+ if (private->response_entries)
+ {
+ g_list_free_full (private->response_entries,
+ (GDestroyNotify) response_entry_free);
+ private->response_entries = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+/**
+ * gimp_tool_gui_new:
+ * @tool_info: a #GimpToolInfo
+ * @description: a string to use in the gui header or %NULL to use the help
+ * field from #GimpToolInfo
+ * @...: a %NULL-terminated valist of button parameters as described in
+ * gtk_gui_new_with_buttons().
+ *
+ * This function creates a #GimpToolGui using the information stored
+ * in @tool_info.
+ *
+ * Return value: a new #GimpToolGui
+ **/
+GimpToolGui *
+gimp_tool_gui_new (GimpToolInfo *tool_info,
+ const gchar *title,
+ const gchar *description,
+ const gchar *icon_name,
+ const gchar *help_id,
+ GdkScreen *screen,
+ gint monitor,
+ gboolean overlay,
+ ...)
+{
+ GimpToolGui *gui;
+ GimpToolGuiPrivate *private;
+ va_list args;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL);
+
+ gui = g_object_new (GIMP_TYPE_TOOL_GUI, NULL);
+
+ private = GET_PRIVATE (gui);
+
+ if (! title)
+ title = tool_info->label;
+
+ if (! description)
+ description = tool_info->label;
+
+ if (! icon_name)
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info));
+
+ if (! help_id)
+ help_id = tool_info->help_id;
+
+ private->tool_info = g_object_ref (tool_info);
+ private->title = g_strdup (title);
+ private->description = g_strdup (description);
+ private->icon_name = g_strdup (icon_name);
+ private->help_id = g_strdup (help_id);
+ private->overlay = overlay;
+
+ va_start (args, overlay);
+
+ gimp_tool_gui_add_buttons_valist (gui, args);
+
+ va_end (args);
+
+ gimp_tool_gui_create_dialog (gui, screen, monitor);
+
+ return gui;
+}
+
+void
+gimp_tool_gui_set_title (GimpToolGui *gui,
+ const gchar *title)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (title == private->title)
+ return;
+
+ g_free (private->title);
+ private->title = g_strdup (title);
+
+ if (! title)
+ title = private->tool_info->label;
+
+ g_object_set (private->dialog, "title", title, NULL);
+}
+
+void
+gimp_tool_gui_set_description (GimpToolGui *gui,
+ const gchar *description)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (description == private->description)
+ return;
+
+ g_free (private->description);
+ private->description = g_strdup (description);
+
+ if (! description)
+ description = private->tool_info->tooltip;
+
+ if (private->overlay)
+ {
+ /* TODO */
+ }
+ else
+ {
+ g_object_set (private->dialog, "description", description, NULL);
+ }
+}
+
+void
+gimp_tool_gui_set_icon_name (GimpToolGui *gui,
+ const gchar *icon_name)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (icon_name == private->icon_name)
+ return;
+
+ g_free (private->icon_name);
+ private->icon_name = g_strdup (icon_name);
+
+ if (! icon_name)
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (private->tool_info));
+
+ g_object_set (private->dialog, "icon-name", icon_name, NULL);
+}
+
+void
+gimp_tool_gui_set_help_id (GimpToolGui *gui,
+ const gchar *help_id)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (help_id == private->help_id)
+ return;
+
+ g_free (private->help_id);
+ private->help_id = g_strdup (help_id);
+
+ if (! help_id)
+ help_id = private->tool_info->help_id;
+
+ if (private->overlay)
+ {
+ /* TODO */
+ }
+ else
+ {
+ g_object_set (private->dialog, "help-id", help_id, NULL);
+ }
+}
+
+void
+gimp_tool_gui_set_shell (GimpToolGui *gui,
+ GimpDisplayShell *shell)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+ g_return_if_fail (shell == NULL || GIMP_IS_DISPLAY_SHELL (shell));
+
+ private = GET_PRIVATE (gui);
+
+ if (shell == private->shell)
+ return;
+
+ if (private->shell)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (private->shell),
+ (gpointer) &private->shell);
+ g_signal_handlers_disconnect_by_func (private->shell->canvas,
+ gimp_tool_gui_canvas_resized,
+ gui);
+ }
+
+ private->shell = shell;
+
+ if (private->shell)
+ {
+ g_signal_connect (private->shell->canvas, "size-allocate",
+ G_CALLBACK (gimp_tool_gui_canvas_resized),
+ gui);
+ g_object_add_weak_pointer (G_OBJECT (private->shell),
+ (gpointer) &private->shell);
+ }
+
+ gimp_tool_gui_update_shell (gui);
+}
+
+void
+gimp_tool_gui_set_viewable (GimpToolGui *gui,
+ GimpViewable *viewable)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+ g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable));
+
+ private = GET_PRIVATE (gui);
+
+ if (private->viewable == viewable)
+ return;
+
+ if (private->viewable)
+ g_object_remove_weak_pointer (G_OBJECT (private->viewable),
+ (gpointer) &private->viewable);
+
+ private->viewable = viewable;
+
+ if (private->viewable)
+ g_object_add_weak_pointer (G_OBJECT (private->viewable),
+ (gpointer) &private->viewable);
+
+ gimp_tool_gui_update_viewable (gui);
+}
+
+GtkWidget *
+gimp_tool_gui_get_dialog (GimpToolGui *gui)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), NULL);
+
+ return GET_PRIVATE (gui)->dialog;
+}
+
+GtkWidget *
+gimp_tool_gui_get_vbox (GimpToolGui *gui)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), NULL);
+
+ return GET_PRIVATE (gui)->vbox;
+}
+
+gboolean
+gimp_tool_gui_get_visible (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE);
+
+ private = GET_PRIVATE (gui);
+
+ if (private->overlay)
+ return gtk_widget_get_parent (private->dialog) != NULL;
+ else
+ return gtk_widget_get_visible (private->dialog);
+}
+
+void
+gimp_tool_gui_show (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ g_return_if_fail (private->shell != NULL);
+
+ if (private->overlay)
+ {
+ if (! gtk_widget_get_parent (private->dialog))
+ {
+ gimp_overlay_box_add_child (GIMP_OVERLAY_BOX (private->shell->canvas),
+ private->dialog, 1.0, 0.0);
+ gtk_widget_show (private->dialog);
+ }
+ }
+ else
+ {
+ if (gtk_widget_get_visible (private->dialog))
+ gdk_window_show (gtk_widget_get_window (private->dialog));
+ else
+ gtk_widget_show (private->dialog);
+ }
+}
+
+void
+gimp_tool_gui_hide (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (private->overlay)
+ {
+ if (gtk_widget_get_parent (private->dialog))
+ {
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (private->dialog)),
+ private->dialog);
+ gtk_widget_hide (private->dialog);
+ }
+ }
+ else
+ {
+ if (gimp_dialog_factory_from_widget (private->dialog, NULL))
+ {
+ gimp_dialog_factory_hide_dialog (private->dialog);
+ }
+ else
+ {
+ gtk_widget_hide (private->dialog);
+ }
+ }
+}
+
+void
+gimp_tool_gui_set_overlay (GimpToolGui *gui,
+ GdkScreen *screen,
+ gint monitor,
+ gboolean overlay)
+{
+ GimpToolGuiPrivate *private;
+ gboolean visible;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (private->overlay == overlay)
+ return;
+
+ if (! private->dialog)
+ {
+ private->overlay = overlay;
+ return;
+ }
+
+ visible = gtk_widget_get_visible (private->dialog);
+
+ if (visible)
+ gimp_tool_gui_hide (gui);
+
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (private->vbox)),
+ private->vbox);
+
+ if (private->overlay)
+ g_object_unref (private->dialog);
+ else
+ gtk_widget_destroy (private->dialog);
+
+ private->overlay = overlay;
+
+ gimp_tool_gui_create_dialog (gui, screen, monitor);
+
+ if (visible)
+ gimp_tool_gui_show (gui);
+}
+
+gboolean
+gimp_tool_gui_get_overlay (GimpToolGui *gui)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE);
+
+ return GET_PRIVATE (gui)->overlay;
+}
+
+void
+gimp_tool_gui_set_auto_overlay (GimpToolGui *gui,
+ gboolean auto_overlay)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (private->auto_overlay != auto_overlay)
+ {
+ private->auto_overlay = auto_overlay;
+
+ if (private->shell)
+ gimp_tool_gui_canvas_resized (private->shell->canvas, NULL, gui);
+ }
+}
+
+gboolean
+gimp_tool_gui_get_auto_overlay (GimpToolGui *gui)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE);
+
+ return GET_PRIVATE (gui)->auto_overlay;
+}
+
+void
+gimp_tool_gui_set_focus_on_map (GimpToolGui *gui,
+ gboolean focus_on_map)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ if (private->focus_on_map == focus_on_map)
+ return;
+
+ private->focus_on_map = focus_on_map ? TRUE : FALSE;
+
+ if (! private->overlay)
+ {
+ gtk_window_set_focus_on_map (GTK_WINDOW (private->dialog),
+ private->focus_on_map);
+ }
+}
+
+gboolean
+gimp_tool_gui_get_focus_on_map (GimpToolGui *gui)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE);
+
+ return GET_PRIVATE (gui)->focus_on_map;
+}
+
+void
+gimp_tool_gui_add_buttons_valist (GimpToolGui *gui,
+ va_list args)
+{
+ const gchar *button_text;
+ gint response_id;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ while ((button_text = va_arg (args, const gchar *)))
+ {
+ response_id = va_arg (args, gint);
+
+ gimp_tool_gui_add_button (gui, button_text, response_id);
+ }
+}
+
+void
+gimp_tool_gui_add_button (GimpToolGui *gui,
+ const gchar *button_text,
+ gint response_id)
+{
+ GimpToolGuiPrivate *private;
+ ResponseEntry *entry;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+ g_return_if_fail (button_text != NULL);
+
+ private = GET_PRIVATE (gui);
+
+ entry = response_entry_new (response_id, button_text);
+
+ private->response_entries = g_list_append (private->response_entries,
+ entry);
+
+ if (private->dialog)
+ gimp_tool_gui_add_dialog_button (gui, entry);
+}
+
+void
+gimp_tool_gui_set_default_response (GimpToolGui *gui,
+ gint response_id)
+{
+ GimpToolGuiPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ g_return_if_fail (response_entry_find (private->response_entries,
+ response_id) != NULL);
+
+ private->default_response = response_id;
+
+ if (private->overlay)
+ {
+ gimp_overlay_dialog_set_default_response (GIMP_OVERLAY_DIALOG (private->dialog),
+ response_id);
+ }
+ else
+ {
+ gtk_dialog_set_default_response (GTK_DIALOG (private->dialog),
+ response_id);
+ }
+}
+
+void
+gimp_tool_gui_set_response_sensitive (GimpToolGui *gui,
+ gint response_id,
+ gboolean sensitive)
+{
+ GimpToolGuiPrivate *private;
+ ResponseEntry *entry;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ entry = response_entry_find (private->response_entries, response_id);
+
+ if (! entry)
+ return;
+
+ entry->sensitive = sensitive;
+
+ if (private->overlay)
+ {
+ gimp_overlay_dialog_set_response_sensitive (GIMP_OVERLAY_DIALOG (private->dialog),
+ response_id, sensitive);
+ }
+ else
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (private->dialog),
+ response_id, sensitive);
+ }
+}
+
+void
+gimp_tool_gui_set_alternative_button_order (GimpToolGui *gui,
+ ...)
+{
+ GimpToolGuiPrivate *private;
+ va_list args;
+ gint response_id;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_TOOL_GUI (gui));
+
+ private = GET_PRIVATE (gui);
+
+ va_start (args, gui);
+
+ for (response_id = va_arg (args, gint), i = 0;
+ response_id != -1;
+ response_id = va_arg (args, gint), i++)
+ {
+ ResponseEntry *entry = response_entry_find (private->response_entries,
+ response_id);
+
+ if (entry)
+ entry->alternative_position = i;
+ }
+
+ va_end (args);
+
+ gimp_tool_gui_update_buttons (gui);
+}
+
+
+/* private functions */
+
+static void
+gimp_tool_gui_create_dialog (GimpToolGui *gui,
+ GdkScreen *screen,
+ gint monitor)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+ GList *list;
+
+ if (private->overlay)
+ {
+ private->dialog = gimp_overlay_dialog_new (private->tool_info,
+ private->description,
+ NULL);
+ g_object_ref_sink (private->dialog);
+
+ for (list = private->response_entries; list; list = g_list_next (list))
+ {
+ ResponseEntry *entry = list->data;
+
+ gimp_tool_gui_add_dialog_button (gui, entry);
+ }
+
+ if (private->default_response != -1)
+ gimp_overlay_dialog_set_default_response (GIMP_OVERLAY_DIALOG (private->dialog),
+ private->default_response);
+
+ gtk_container_set_border_width (GTK_CONTAINER (private->dialog), 6);
+
+ gtk_container_set_border_width (GTK_CONTAINER (private->vbox), 0);
+ gtk_container_add (GTK_CONTAINER (private->dialog), private->vbox);
+ gtk_widget_show (private->vbox);
+ }
+ else
+ {
+ private->dialog = gimp_tool_dialog_new (private->tool_info,
+ screen, monitor,
+ private->title,
+ private->description,
+ private->icon_name,
+ private->help_id,
+ NULL);
+
+ for (list = private->response_entries; list; list = g_list_next (list))
+ {
+ ResponseEntry *entry = list->data;
+
+ gimp_tool_gui_add_dialog_button (gui, entry);
+ }
+
+ if (private->default_response != -1)
+ gtk_dialog_set_default_response (GTK_DIALOG (private->dialog),
+ private->default_response);
+
+ gtk_window_set_focus_on_map (GTK_WINDOW (private->dialog),
+ private->focus_on_map);
+
+ gtk_container_set_border_width (GTK_CONTAINER (private->vbox), 6);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (private->dialog))),
+ private->vbox, TRUE, TRUE, 0);
+ gtk_widget_show (private->vbox);
+ }
+
+ gimp_tool_gui_update_buttons (gui);
+
+ if (private->shell)
+ gimp_tool_gui_update_shell (gui);
+
+ if (private->viewable)
+ gimp_tool_gui_update_viewable (gui);
+
+ g_signal_connect_object (private->dialog, "response",
+ G_CALLBACK (gimp_tool_gui_dialog_response),
+ G_OBJECT (gui), 0);
+}
+
+static void
+gimp_tool_gui_add_dialog_button (GimpToolGui *gui,
+ ResponseEntry *entry)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+
+ if (private->overlay)
+ {
+ gimp_overlay_dialog_add_button (GIMP_OVERLAY_DIALOG (private->dialog),
+ entry->button_text,
+ entry->response_id);
+
+ if (! entry->sensitive)
+ {
+ gimp_overlay_dialog_set_response_sensitive (
+ GIMP_OVERLAY_DIALOG (private->dialog),
+ entry->response_id, FALSE);
+ }
+ }
+ else
+ {
+ gimp_dialog_add_button (GIMP_DIALOG (private->dialog),
+ entry->button_text,
+ entry->response_id);
+
+ if (! entry->sensitive)
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (private->dialog),
+ entry->response_id,
+ FALSE);
+ }
+}
+
+static void
+gimp_tool_gui_update_buttons (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+ GList *list;
+ gint *ids;
+ gint n_ids;
+ gint n_alternatives = 0;
+ gint i;
+
+ n_ids = g_list_length (private->response_entries);
+ ids = g_new0 (gint, n_ids);
+
+ for (list = private->response_entries, i = 0;
+ list;
+ list = g_list_next (list), i++)
+ {
+ ResponseEntry *entry = list->data;
+
+ if (entry->alternative_position >= 0 &&
+ entry->alternative_position < n_ids)
+ {
+ ids[entry->alternative_position] = entry->response_id;
+ n_alternatives++;
+ }
+ }
+
+ if (n_ids == n_alternatives)
+ {
+ if (private->overlay)
+ {
+ gimp_overlay_dialog_set_alternative_button_order (GIMP_OVERLAY_DIALOG (private->dialog),
+ n_ids, ids);
+ }
+ else
+ {
+ gtk_dialog_set_alternative_button_order_from_array (GTK_DIALOG (private->dialog),
+ n_ids, ids);
+ }
+ }
+
+ g_free (ids);
+}
+
+static void
+gimp_tool_gui_update_shell (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+
+ if (private->overlay)
+ {
+ if (gtk_widget_get_parent (private->dialog))
+ {
+ gimp_tool_gui_hide (gui);
+
+ if (private->shell)
+ gimp_tool_gui_show (gui);
+ }
+ }
+ else
+ {
+ gimp_tool_dialog_set_shell (GIMP_TOOL_DIALOG (private->dialog),
+ private->shell);
+ }
+}
+
+static void
+gimp_tool_gui_update_viewable (GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+
+ if (! private->overlay)
+ {
+ GimpContext *context = NULL;
+
+ if (private->tool_info)
+ context = GIMP_CONTEXT (private->tool_info->tool_options);
+
+ gimp_viewable_dialog_set_viewable (GIMP_VIEWABLE_DIALOG (private->dialog),
+ private->viewable, context);
+ }
+}
+
+static void
+gimp_tool_gui_dialog_response (GtkWidget *dialog,
+ gint response_id,
+ GimpToolGui *gui)
+{
+ if (response_id == GIMP_RESPONSE_DETACH)
+ {
+ gimp_tool_gui_set_auto_overlay (gui, FALSE);
+ gimp_tool_gui_set_overlay (gui,
+ gtk_widget_get_screen (dialog),
+ gimp_widget_get_monitor (dialog),
+ FALSE);
+ }
+ else
+ {
+ g_signal_emit (gui, signals[RESPONSE], 0,
+ response_id);
+ }
+}
+
+static void
+gimp_tool_gui_canvas_resized (GtkWidget *canvas,
+ GtkAllocation *unused,
+ GimpToolGui *gui)
+{
+ GimpToolGuiPrivate *private = GET_PRIVATE (gui);
+
+ if (private->auto_overlay)
+ {
+ GtkRequisition requisition;
+ GtkAllocation allocation;
+ gboolean overlay = FALSE;
+
+ gtk_widget_size_request (private->vbox, &requisition);
+ gtk_widget_get_allocation (canvas, &allocation);
+
+ if (allocation.width > 2 * requisition.width &&
+ allocation.height > 3 * requisition.height)
+ {
+ overlay = TRUE;
+ }
+
+ gimp_tool_gui_set_overlay (gui,
+ gtk_widget_get_screen (private->dialog),
+ gimp_widget_get_monitor (private->dialog),
+ overlay);
+ }
+}
+
+static ResponseEntry *
+response_entry_new (gint response_id,
+ const gchar *button_text)
+{
+ ResponseEntry *entry = g_slice_new0 (ResponseEntry);
+
+ entry->response_id = response_id;
+ entry->button_text = g_strdup (button_text);
+ entry->alternative_position = -1;
+ entry->sensitive = TRUE;
+
+ return entry;
+}
+
+static void
+response_entry_free (ResponseEntry *entry)
+{
+ g_free (entry->button_text);
+
+ g_slice_free (ResponseEntry, entry);
+}
+
+static ResponseEntry *
+response_entry_find (GList *entries,
+ gint response_id)
+{
+ for (; entries; entries = g_list_next (entries))
+ {
+ ResponseEntry *entry = entries->data;
+
+ if (entry->response_id == response_id)
+ return entry;
+ }
+
+ return NULL;
+}
diff --git a/app/display/gimptoolgui.h b/app/display/gimptoolgui.h
new file mode 100644
index 0000000..7b099a5
--- /dev/null
+++ b/app/display/gimptoolgui.h
@@ -0,0 +1,115 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgui.h
+ * Copyright (C) 2013 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_GUI_H__
+#define __GIMP_TOOL_GUI_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_TOOL_GUI (gimp_tool_gui_get_type ())
+#define GIMP_TOOL_GUI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_GUI, GimpToolGui))
+#define GIMP_TOOL_GUI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_GUI, GimpToolGuiClass))
+#define GIMP_IS_TOOL_GUI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_GUI))
+#define GIMP_IS_TOOL_GUI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_GUI))
+#define GIMP_TOOL_GUI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_GUI, GimpToolGuiClass))
+
+
+typedef struct _GimpToolGuiClass GimpToolGuiClass;
+
+struct _GimpToolGui
+{
+ GimpObject parent_instance;
+};
+
+struct _GimpToolGuiClass
+{
+ GimpObjectClass parent_instance;
+
+ void (* response) (GimpToolGui *gui,
+ gint response_id);
+
+};
+
+
+GType gimp_tool_gui_get_type (void) G_GNUC_CONST;
+
+GimpToolGui * gimp_tool_gui_new (GimpToolInfo *tool_info,
+ const gchar *title,
+ const gchar *description,
+ const gchar *icon_name,
+ const gchar *help_id,
+ GdkScreen *screen,
+ gint monitor,
+ gboolean overlay,
+ ...) G_GNUC_NULL_TERMINATED;
+
+void gimp_tool_gui_set_title (GimpToolGui *gui,
+ const gchar *title);
+void gimp_tool_gui_set_description (GimpToolGui *gui,
+ const gchar *description);
+void gimp_tool_gui_set_icon_name (GimpToolGui *gui,
+ const gchar *icon_name);
+void gimp_tool_gui_set_help_id (GimpToolGui *gui,
+ const gchar *help_id);
+
+void gimp_tool_gui_set_shell (GimpToolGui *gui,
+ GimpDisplayShell *shell);
+void gimp_tool_gui_set_viewable (GimpToolGui *gui,
+ GimpViewable *viewable);
+
+GtkWidget * gimp_tool_gui_get_dialog (GimpToolGui *gui);
+GtkWidget * gimp_tool_gui_get_vbox (GimpToolGui *gui);
+
+gboolean gimp_tool_gui_get_visible (GimpToolGui *gui);
+void gimp_tool_gui_show (GimpToolGui *gui);
+void gimp_tool_gui_hide (GimpToolGui *gui);
+
+void gimp_tool_gui_set_overlay (GimpToolGui *gui,
+ GdkScreen *screen,
+ gint monitor,
+ gboolean overlay);
+gboolean gimp_tool_gui_get_overlay (GimpToolGui *gui);
+
+void gimp_tool_gui_set_auto_overlay (GimpToolGui *gui,
+ gboolean auto_overlay);
+gboolean gimp_tool_gui_get_auto_overlay (GimpToolGui *gui);
+
+void gimp_tool_gui_set_focus_on_map (GimpToolGui *gui,
+ gboolean focus_on_map);
+gboolean gimp_tool_gui_get_focus_on_map (GimpToolGui *gui);
+
+
+void gimp_tool_gui_add_buttons_valist (GimpToolGui *gui,
+ va_list args);
+void gimp_tool_gui_add_button (GimpToolGui *gui,
+ const gchar *button_text,
+ gint response_id);
+void gimp_tool_gui_set_default_response (GimpToolGui *gui,
+ gint response_id);
+void gimp_tool_gui_set_response_sensitive (GimpToolGui *gui,
+ gint response_id,
+ gboolean sensitive);
+void gimp_tool_gui_set_alternative_button_order (GimpToolGui *gui,
+ ...);
+
+
+#endif /* __GIMP_TOOL_GUI_H__ */
diff --git a/app/display/gimptoolgyroscope.c b/app/display/gimptoolgyroscope.c
new file mode 100644
index 0000000..4d6b175
--- /dev/null
+++ b/app/display/gimptoolgyroscope.c
@@ -0,0 +1,879 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgyroscope.c
+ * Copyright (C) 2018 Ell
+ *
+ * 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimptoolgyroscope.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 1e-6
+#define DEG_TO_RAD (G_PI / 180.0)
+
+
+typedef enum
+{
+ MODE_NONE,
+ MODE_PAN,
+ MODE_ROTATE,
+ MODE_ZOOM
+} Mode;
+
+typedef enum
+{
+ CONSTRAINT_NONE,
+ CONSTRAINT_UNKNOWN,
+ CONSTRAINT_HORIZONTAL,
+ CONSTRAINT_VERTICAL
+} Constraint;
+
+enum
+{
+ PROP_0,
+ PROP_YAW,
+ PROP_PITCH,
+ PROP_ROLL,
+ PROP_ZOOM,
+ PROP_INVERT,
+ PROP_SPEED,
+ PROP_PIVOT_X,
+ PROP_PIVOT_Y
+};
+
+struct _GimpToolGyroscopePrivate
+{
+ gdouble yaw;
+ gdouble pitch;
+ gdouble roll;
+ gdouble zoom;
+
+ gdouble orig_yaw;
+ gdouble orig_pitch;
+ gdouble orig_roll;
+ gdouble orig_zoom;
+
+ gboolean invert;
+
+ gdouble speed;
+
+ gdouble pivot_x;
+ gdouble pivot_y;
+
+ Mode mode;
+ Constraint constraint;
+
+ gdouble last_x;
+ gdouble last_y;
+
+ gdouble last_angle;
+ gdouble curr_angle;
+
+ gdouble last_zoom;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_gyroscope_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_gyroscope_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint gimp_tool_gyroscope_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_gyroscope_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_gyroscope_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_gyroscope_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_gyroscope_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static gboolean gimp_tool_gyroscope_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope,
+ GdkModifierType state);
+
+static void gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope);
+static void gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope);
+
+static void gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope,
+ const GimpVector3 *axis);
+static void gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector,
+ const GimpVector3 *axis);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGyroscope, gimp_tool_gyroscope,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_gyroscope_parent_class
+
+
+static void
+gimp_tool_gyroscope_class_init (GimpToolGyroscopeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->set_property = gimp_tool_gyroscope_set_property;
+ object_class->get_property = gimp_tool_gyroscope_get_property;
+
+ widget_class->button_press = gimp_tool_gyroscope_button_press;
+ widget_class->button_release = gimp_tool_gyroscope_button_release;
+ widget_class->motion = gimp_tool_gyroscope_motion;
+ widget_class->hit = gimp_tool_gyroscope_hit;
+ widget_class->hover = gimp_tool_gyroscope_hover;
+ widget_class->key_press = gimp_tool_gyroscope_key_press;
+ widget_class->motion_modifier = gimp_tool_gyroscope_motion_modifier;
+ widget_class->get_cursor = gimp_tool_gyroscope_get_cursor;
+
+ g_object_class_install_property (object_class, PROP_YAW,
+ g_param_spec_double ("yaw", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PITCH,
+ g_param_spec_double ("pitch", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ROLL,
+ g_param_spec_double ("roll", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ZOOM,
+ g_param_spec_double ("zoom", NULL, NULL,
+ 0.0,
+ +G_MAXDOUBLE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_INVERT,
+ g_param_spec_boolean ("invert", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SPEED,
+ g_param_spec_double ("speed", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_X,
+ g_param_spec_double ("pivot-x", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_Y,
+ g_param_spec_double ("pivot-y", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_gyroscope_init (GimpToolGyroscope *gyroscope)
+{
+ gyroscope->private = gimp_tool_gyroscope_get_instance_private (gyroscope);
+}
+
+static void
+gimp_tool_gyroscope_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ switch (property_id)
+ {
+ case PROP_YAW:
+ private->yaw = g_value_get_double (value);
+ break;
+ case PROP_PITCH:
+ private->pitch = g_value_get_double (value);
+ break;
+ case PROP_ROLL:
+ private->roll = g_value_get_double (value);
+ break;
+ case PROP_ZOOM:
+ private->zoom = g_value_get_double (value);
+ break;
+ case PROP_INVERT:
+ private->invert = g_value_get_boolean (value);
+ break;
+ case PROP_SPEED:
+ private->speed = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_X:
+ private->pivot_x = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_Y:
+ private->pivot_y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_gyroscope_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ switch (property_id)
+ {
+ case PROP_YAW:
+ g_value_set_double (value, private->yaw);
+ break;
+ case PROP_PITCH:
+ g_value_set_double (value, private->pitch);
+ break;
+ case PROP_ROLL:
+ g_value_set_double (value, private->roll);
+ break;
+ case PROP_ZOOM:
+ g_value_set_double (value, private->zoom);
+ break;
+ case PROP_INVERT:
+ g_value_set_boolean (value, private->invert);
+ break;
+ case PROP_SPEED:
+ g_value_set_double (value, private->speed);
+ break;
+ case PROP_PIVOT_X:
+ g_value_set_double (value, private->pivot_x);
+ break;
+ case PROP_PIVOT_Y:
+ g_value_set_double (value, private->pivot_y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint
+gimp_tool_gyroscope_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ gimp_tool_gyroscope_save (gyroscope);
+
+ if (state & GDK_MOD1_MASK)
+ {
+ private->mode = MODE_ZOOM;
+
+ private->last_zoom = private->zoom;
+ }
+ else if (state & gimp_get_extend_selection_mask ())
+ {
+ private->mode = MODE_ROTATE;
+
+ private->last_angle = atan2 (coords->y - private->pivot_y,
+ coords->x - private->pivot_x);
+ private->curr_angle = private->last_angle;
+ }
+ else
+ {
+ private->mode = MODE_PAN;
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ private->constraint = CONSTRAINT_UNKNOWN;
+ else
+ private->constraint = CONSTRAINT_NONE;
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ gimp_tool_gyroscope_update_status (gyroscope, state);
+
+ return 1;
+}
+
+static void
+gimp_tool_gyroscope_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_tool_gyroscope_restore (gyroscope);
+
+ private->mode = MODE_NONE;
+
+ gimp_tool_gyroscope_update_status (gyroscope, state);
+}
+
+static void
+gimp_tool_gyroscope_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GimpVector3 axis = {};
+
+ switch (private->mode)
+ {
+ case MODE_PAN:
+ {
+ gdouble x1 = private->last_x;
+ gdouble y1 = private->last_y;
+ gdouble x2 = coords->x;
+ gdouble y2 = coords->y;
+ gdouble factor = 1.0 / private->zoom;
+
+ if (private->constraint != CONSTRAINT_NONE)
+ {
+ gimp_display_shell_rotate_xy_f (shell, x1, y1, &x1, &y1);
+ gimp_display_shell_rotate_xy_f (shell, x2, y2, &x2, &y2);
+
+ if (private->constraint == CONSTRAINT_UNKNOWN)
+ {
+ if (fabs (x2 - x1) > fabs (y2 - y1))
+ private->constraint = CONSTRAINT_HORIZONTAL;
+ else if (fabs (y2 - y1) > fabs (x2 - x1))
+ private->constraint = CONSTRAINT_VERTICAL;
+ }
+
+ if (private->constraint == CONSTRAINT_HORIZONTAL)
+ y2 = y1;
+ else if (private->constraint == CONSTRAINT_VERTICAL)
+ x2 = x1;
+
+ gimp_display_shell_unrotate_xy_f (shell, x1, y1, &x1, &y1);
+ gimp_display_shell_unrotate_xy_f (shell, x2, y2, &x2, &y2);
+ }
+
+ if (private->invert)
+ factor = 1.0 / factor;
+
+ gimp_vector3_set (&axis, y2 - y1, x2 - x1, 0.0);
+ gimp_vector3_mul (&axis, factor * private->speed);
+ }
+ break;
+
+ case MODE_ROTATE:
+ {
+ gdouble angle;
+
+ angle = atan2 (coords->y - private->pivot_y,
+ coords->x - private->pivot_x);
+
+ private->curr_angle = angle;
+
+ angle -= private->last_angle;
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ angle = RINT (angle / (G_PI / 6.0)) * (G_PI / 6.0);
+
+ gimp_vector3_set (&axis, 0.0, 0.0, angle);
+
+ private->last_angle += angle;
+ }
+ break;
+
+ case MODE_ZOOM:
+ {
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble zoom;
+
+ gimp_display_shell_transform_xy_f (shell,
+ private->last_x, private->last_y,
+ &x1, &y1);
+ gimp_display_shell_transform_xy_f (shell,
+ coords->x, coords->y,
+ &x2, &y2);
+
+ zoom = (y1 - y2) * shell->scale_y / 128.0;
+
+ if (private->invert)
+ zoom = -zoom;
+
+ private->last_zoom *= pow (2.0, zoom);
+
+ zoom = log (private->last_zoom / private->zoom) / G_LN2;
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ zoom = RINT (zoom * 2.0) / 2.0;
+
+ g_object_set (gyroscope,
+ "zoom", private->zoom * pow (2.0, zoom),
+ NULL);
+ }
+ break;
+
+ case MODE_NONE:
+ g_return_if_reached ();
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ gimp_tool_gyroscope_rotate (gyroscope, &axis);
+}
+
+static GimpHit
+gimp_tool_gyroscope_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ return GIMP_HIT_INDIRECT;
+}
+
+static void
+gimp_tool_gyroscope_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ gimp_tool_gyroscope_update_status (GIMP_TOOL_GYROSCOPE (widget), state);
+}
+
+static gboolean
+gimp_tool_gyroscope_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GimpVector3 axis = {};
+ gboolean fast;
+ gboolean result = FALSE;
+
+ fast = (kevent->state & gimp_get_constrain_behavior_mask ());
+
+ if (kevent->state & GDK_MOD1_MASK)
+ {
+ /* zoom */
+ gdouble zoom = 0.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ zoom = fast ? +1.0 : +1.0 / 8.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Down:
+ zoom = fast ? -1.0 : -1.0 / 8.0;
+ result = TRUE;
+ break;
+ }
+
+ if (private->invert)
+ zoom = -zoom;
+
+ if (zoom)
+ {
+ g_object_set (gyroscope,
+ "zoom", private->zoom * pow (2.0, zoom),
+ NULL);
+ }
+ }
+ else if (kevent->state & gimp_get_extend_selection_mask ())
+ {
+ /* rotate */
+ gdouble angle = 0.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ angle = fast ? +15.0 : +1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Right:
+ angle = fast ? -15.0 : -1.0;
+ result = TRUE;
+ break;
+ }
+
+ if (shell->flip_horizontally ^ shell->flip_vertically)
+ angle = -angle;
+
+ gimp_vector3_set (&axis, 0.0, 0.0, angle * DEG_TO_RAD);
+ }
+ else
+ {
+ /* pan */
+ gdouble x0 = 0.0;
+ gdouble y0 = 0.0;
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+ gdouble factor = 1.0 / private->zoom;
+
+ if (private->invert)
+ factor = 1.0 / factor;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ x = fast ? +15.0 : +1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Right:
+ x = fast ? -15.0 : -1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Up:
+ y = fast ? +15.0 : +1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Down:
+ y = fast ? -15.0 : -1.0;
+ result = TRUE;
+ break;
+ }
+
+ gimp_display_shell_unrotate_xy_f (shell, x0, y0, &x0, &y0);
+ gimp_display_shell_unrotate_xy_f (shell, x, y, &x, &y);
+
+ gimp_vector3_set (&axis,
+ (y - y0) * DEG_TO_RAD,
+ (x - x0) * DEG_TO_RAD,
+ 0.0);
+ gimp_vector3_mul (&axis, factor);
+ }
+
+ gimp_tool_gyroscope_rotate (gyroscope, &axis);
+
+ return result;
+}
+
+static void
+gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ gimp_tool_gyroscope_update_status (gyroscope, state);
+
+ if (key == gimp_get_constrain_behavior_mask ())
+ {
+ switch (private->mode)
+ {
+ case MODE_PAN:
+ if (state & gimp_get_constrain_behavior_mask ())
+ private->constraint = CONSTRAINT_UNKNOWN;
+ else
+ private->constraint = CONSTRAINT_NONE;
+ break;
+
+ case MODE_ROTATE:
+ if (! (state & gimp_get_constrain_behavior_mask ()))
+ private->last_angle = private->curr_angle;
+ break;
+
+ case MODE_ZOOM:
+ if (! (state & gimp_get_constrain_behavior_mask ()))
+ private->last_zoom = private->zoom;
+ break;
+
+ case MODE_NONE:
+ break;
+ }
+ }
+}
+
+static gboolean
+gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ if (state & GDK_MOD1_MASK)
+ *modifier = GIMP_CURSOR_MODIFIER_ZOOM;
+ else if (state & gimp_get_extend_selection_mask ())
+ *modifier = GIMP_CURSOR_MODIFIER_ROTATE;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ return TRUE;
+}
+
+static void
+gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope,
+ GdkModifierType state)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ gchar *status;
+
+ if (private->mode == MODE_ZOOM ||
+ (private->mode == MODE_NONE &&
+ state & GDK_MOD1_MASK))
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to zoom"),
+ gimp_get_toggle_behavior_mask () &
+ ~state,
+ NULL,
+ _("%s for constrained steps"),
+ NULL);
+ }
+ else if (private->mode == MODE_ROTATE ||
+ (private->mode == MODE_NONE &&
+ state & gimp_get_extend_selection_mask ()))
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to rotate"),
+ gimp_get_toggle_behavior_mask () &
+ ~state,
+ NULL,
+ _("%s for constrained angles"),
+ NULL);
+ }
+ else
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to pan"),
+ (((gimp_get_extend_selection_mask () |
+ GDK_MOD1_MASK) *
+ (private->mode == MODE_NONE)) |
+ gimp_get_toggle_behavior_mask ()) &
+ ~state,
+ _("%s to rotate"),
+ _("%s for a constrained axis"),
+ _("%s to zoom"));
+ }
+
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (gyroscope), status);
+
+ g_free (status);
+}
+
+static void
+gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ private->orig_yaw = private->yaw;
+ private->orig_pitch = private->pitch;
+ private->orig_roll = private->roll;
+ private->orig_zoom = private->zoom;
+}
+
+static void
+gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ g_object_set (gyroscope,
+ "yaw", private->orig_yaw,
+ "pitch", private->orig_pitch,
+ "roll", private->orig_roll,
+ "zoom", private->orig_zoom,
+ NULL);
+}
+
+static void
+gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope,
+ const GimpVector3 *axis)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ GimpVector3 real_axis;
+ GimpVector3 basis[2];
+ gdouble yaw;
+ gdouble pitch;
+ gdouble roll;
+ gint i;
+
+ if (gimp_vector3_length (axis) < EPSILON)
+ return;
+
+ real_axis = *axis;
+
+ if (private->invert)
+ gimp_vector3_neg (&real_axis);
+
+ for (i = 0; i < 2; i++)
+ {
+ gimp_vector3_set (&basis[i], i == 0, i == 1, 0.0);
+
+ if (private->invert)
+ gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis);
+
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {0.0, private->yaw * DEG_TO_RAD, 0.0});
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {private->pitch * DEG_TO_RAD, 0.0, 0.0});
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {0.0, 0.0, private->roll * DEG_TO_RAD});
+
+ if (! private->invert)
+ gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis);
+ }
+
+ roll = atan2 (basis[1].x, basis[1].y);
+
+ for (i = 0; i < 2; i++)
+ {
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {0.0, 0.0, -roll});
+ }
+
+ pitch = atan2 (-basis[1].z, basis[1].y);
+
+ for (i = 0; i < 1; i++)
+ {
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {-pitch, 0.0, 0.0});
+ }
+
+ yaw = atan2 (basis[0].z, basis[0].x);
+
+ g_object_set (gyroscope,
+ "yaw", yaw / DEG_TO_RAD,
+ "pitch", pitch / DEG_TO_RAD,
+ "roll", roll / DEG_TO_RAD,
+ NULL);
+}
+
+static void
+gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector,
+ const GimpVector3 *axis)
+{
+ GimpVector3 normalized_axis;
+ GimpVector3 projection;
+ GimpVector3 u;
+ GimpVector3 v;
+ gdouble angle;
+
+ angle = gimp_vector3_length (axis);
+
+ if (angle < EPSILON)
+ return;
+
+ normalized_axis = gimp_vector3_mul_val (*axis, 1.0 / angle);
+
+ projection = gimp_vector3_mul_val (
+ normalized_axis,
+ gimp_vector3_inner_product (vector, &normalized_axis));
+
+ u = gimp_vector3_sub_val (*vector, projection);
+ v = gimp_vector3_cross_product (&u, &normalized_axis);
+
+ gimp_vector3_mul (&u, cos (angle));
+ gimp_vector3_mul (&v, sin (angle));
+
+ gimp_vector3_add (vector, &u, &v);
+ gimp_vector3_add (vector, vector, &projection);
+}
+
+
+/* public functions */
+
+
+GimpToolWidget *
+gimp_tool_gyroscope_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_GYROSCOPE,
+ "shell", shell,
+ NULL);
+}
diff --git a/app/display/gimptoolgyroscope.h b/app/display/gimptoolgyroscope.h
new file mode 100644
index 0000000..3d89648
--- /dev/null
+++ b/app/display/gimptoolgyroscope.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgyroscope.h
+ * Copyright (C) 2018 Ell
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_GYROSCOPE_H__
+#define __GIMP_TOOL_GYROSCOPE_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_GYROSCOPE (gimp_tool_gyroscope_get_type ())
+#define GIMP_TOOL_GYROSCOPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_GYROSCOPE, GimpToolGyroscope))
+#define GIMP_TOOL_GYROSCOPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_GYROSCOPE, GimpToolGyroscopeClass))
+#define GIMP_IS_TOOL_GYROSCOPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_GYROSCOPE))
+#define GIMP_IS_TOOL_GYROSCOPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_GYROSCOPE))
+#define GIMP_TOOL_GYROSCOPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_GYROSCOPE, GimpToolGyroscopeClass))
+
+
+typedef struct _GimpToolGyroscope GimpToolGyroscope;
+typedef struct _GimpToolGyroscopePrivate GimpToolGyroscopePrivate;
+typedef struct _GimpToolGyroscopeClass GimpToolGyroscopeClass;
+
+struct _GimpToolGyroscope
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolGyroscopePrivate *private;
+};
+
+struct _GimpToolGyroscopeClass
+{
+ GimpToolWidgetClass parent_class;
+};
+
+
+GType gimp_tool_gyroscope_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_gyroscope_new (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_TOOL_GYROSCOPE_H__ */
diff --git a/app/display/gimptoolhandlegrid.c b/app/display/gimptoolhandlegrid.c
new file mode 100644
index 0000000..cb5b53d
--- /dev/null
+++ b/app/display/gimptoolhandlegrid.c
@@ -0,0 +1,1186 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolhandlegrid.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpHandleTransformTool
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpdisplayshell.h"
+#include "gimptoolhandlegrid.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_HANDLE_MODE,
+ PROP_N_HANDLES,
+ PROP_ORIG_X1,
+ PROP_ORIG_Y1,
+ PROP_ORIG_X2,
+ PROP_ORIG_Y2,
+ PROP_ORIG_X3,
+ PROP_ORIG_Y3,
+ PROP_ORIG_X4,
+ PROP_ORIG_Y4,
+ PROP_TRANS_X1,
+ PROP_TRANS_Y1,
+ PROP_TRANS_X2,
+ PROP_TRANS_Y2,
+ PROP_TRANS_X3,
+ PROP_TRANS_Y3,
+ PROP_TRANS_X4,
+ PROP_TRANS_Y4
+};
+
+
+struct _GimpToolHandleGridPrivate
+{
+ GimpTransformHandleMode handle_mode; /* enum to be renamed */
+
+ gint n_handles;
+ GimpVector2 orig[4];
+ GimpVector2 trans[4];
+
+ gint handle;
+ gdouble last_x;
+ gdouble last_y;
+
+ gboolean hover;
+ gdouble mouse_x;
+ gdouble mouse_y;
+
+ GimpCanvasItem *handles[5];
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_handle_grid_constructed (GObject *object);
+static void gimp_tool_handle_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_handle_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_handle_grid_changed (GimpToolWidget *widget);
+static gint gimp_tool_handle_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_handle_grid_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_handle_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_handle_grid_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_handle_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_handle_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static gint gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid,
+ const GimpCoords *coords);
+static void gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid);
+static void gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid);
+
+static gboolean is_handle_position_valid (GimpToolHandleGrid *grid,
+ gint handle);
+static void handle_micro_move (GimpToolHandleGrid *grid,
+ gint handle);
+
+static inline gdouble calc_angle (gdouble ax,
+ gdouble ay,
+ gdouble bx,
+ gdouble by);
+static inline gdouble calc_len (gdouble a,
+ gdouble b);
+static inline gdouble calc_lineintersect_ratio (gdouble p1x,
+ gdouble p1y,
+ gdouble p2x,
+ gdouble p2y,
+ gdouble q1x,
+ gdouble q1y,
+ gdouble q2x,
+ gdouble q2y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolHandleGrid, gimp_tool_handle_grid,
+ GIMP_TYPE_TOOL_TRANSFORM_GRID)
+
+#define parent_class gimp_tool_handle_grid_parent_class
+
+
+static void
+gimp_tool_handle_grid_class_init (GimpToolHandleGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_handle_grid_constructed;
+ object_class->set_property = gimp_tool_handle_grid_set_property;
+ object_class->get_property = gimp_tool_handle_grid_get_property;
+
+ widget_class->changed = gimp_tool_handle_grid_changed;
+ widget_class->button_press = gimp_tool_handle_grid_button_press;
+ widget_class->button_release = gimp_tool_handle_grid_button_release;
+ widget_class->motion = gimp_tool_handle_grid_motion;
+ widget_class->hit = gimp_tool_handle_grid_hit;
+ widget_class->hover = gimp_tool_handle_grid_hover;
+ widget_class->leave_notify = gimp_tool_handle_grid_leave_notify;
+ widget_class->get_cursor = gimp_tool_handle_grid_get_cursor;
+
+ g_object_class_install_property (object_class, PROP_HANDLE_MODE,
+ g_param_spec_enum ("handle-mode",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_HANDLE_MODE,
+ GIMP_HANDLE_MODE_ADD_TRANSFORM,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_N_HANDLES,
+ g_param_spec_int ("n-handles",
+ NULL, NULL,
+ 0, 4, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X1,
+ g_param_spec_double ("orig-x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y1,
+ g_param_spec_double ("orig-y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X2,
+ g_param_spec_double ("orig-x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y2,
+ g_param_spec_double ("orig-y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X3,
+ g_param_spec_double ("orig-x3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y3,
+ g_param_spec_double ("orig-y3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X4,
+ g_param_spec_double ("orig-x4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y4,
+ g_param_spec_double ("orig-y4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X1,
+ g_param_spec_double ("trans-x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y1,
+ g_param_spec_double ("trans-y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X2,
+ g_param_spec_double ("trans-x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y2,
+ g_param_spec_double ("trans-y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X3,
+ g_param_spec_double ("trans-x3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y3,
+ g_param_spec_double ("trans-y3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X4,
+ g_param_spec_double ("trans-x4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y4,
+ g_param_spec_double ("trans-y4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_handle_grid_init (GimpToolHandleGrid *grid)
+{
+ grid->private = gimp_tool_handle_grid_get_instance_private (grid);
+}
+
+static void
+gimp_tool_handle_grid_constructed (GObject *object)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ for (i = 0; i < 4; i++)
+ {
+ private->handles[i + 1] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CIRCLE,
+ 0, 0,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+
+ gimp_tool_handle_grid_changed (widget);
+}
+
+static void
+gimp_tool_handle_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_HANDLE_MODE:
+ private->handle_mode = g_value_get_enum (value);
+ break;
+
+ case PROP_N_HANDLES:
+ private->n_handles = g_value_get_int (value);
+ break;
+
+ case PROP_ORIG_X1:
+ private->orig[0].x = g_value_get_double (value);
+ break;
+ case PROP_ORIG_Y1:
+ private->orig[0].y = g_value_get_double (value);
+ break;
+ case PROP_ORIG_X2:
+ private->orig[1].x = g_value_get_double (value);
+ break;
+ case PROP_ORIG_Y2:
+ private->orig[1].y = g_value_get_double (value);
+ break;
+ case PROP_ORIG_X3:
+ private->orig[2].x = g_value_get_double (value);
+ break;
+ case PROP_ORIG_Y3:
+ private->orig[2].y = g_value_get_double (value);
+ break;
+ case PROP_ORIG_X4:
+ private->orig[3].x = g_value_get_double (value);
+ break;
+ case PROP_ORIG_Y4:
+ private->orig[3].y = g_value_get_double (value);
+ break;
+
+ case PROP_TRANS_X1:
+ private->trans[0].x = g_value_get_double (value);
+ break;
+ case PROP_TRANS_Y1:
+ private->trans[0].y = g_value_get_double (value);
+ break;
+ case PROP_TRANS_X2:
+ private->trans[1].x = g_value_get_double (value);
+ break;
+ case PROP_TRANS_Y2:
+ private->trans[1].y = g_value_get_double (value);
+ break;
+ case PROP_TRANS_X3:
+ private->trans[2].x = g_value_get_double (value);
+ break;
+ case PROP_TRANS_Y3:
+ private->trans[2].y = g_value_get_double (value);
+ break;
+ case PROP_TRANS_X4:
+ private->trans[3].x = g_value_get_double (value);
+ break;
+ case PROP_TRANS_Y4:
+ private->trans[3].y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_handle_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_N_HANDLES:
+ g_value_set_int (value, private->n_handles);
+ break;
+
+ case PROP_HANDLE_MODE:
+ g_value_set_enum (value, private->handle_mode);
+ break;
+
+ case PROP_ORIG_X1:
+ g_value_set_double (value, private->orig[0].x);
+ break;
+ case PROP_ORIG_Y1:
+ g_value_set_double (value, private->orig[0].y);
+ break;
+ case PROP_ORIG_X2:
+ g_value_set_double (value, private->orig[1].x);
+ break;
+ case PROP_ORIG_Y2:
+ g_value_set_double (value, private->orig[1].y);
+ break;
+ case PROP_ORIG_X3:
+ g_value_set_double (value, private->orig[2].x);
+ break;
+ case PROP_ORIG_Y3:
+ g_value_set_double (value, private->orig[2].y);
+ break;
+ case PROP_ORIG_X4:
+ g_value_set_double (value, private->orig[3].x);
+ break;
+ case PROP_ORIG_Y4:
+ g_value_set_double (value, private->orig[3].y);
+ break;
+
+ case PROP_TRANS_X1:
+ g_value_set_double (value, private->trans[0].x);
+ break;
+ case PROP_TRANS_Y1:
+ g_value_set_double (value, private->trans[0].y);
+ break;
+ case PROP_TRANS_X2:
+ g_value_set_double (value, private->trans[1].x);
+ break;
+ case PROP_TRANS_Y2:
+ g_value_set_double (value, private->trans[1].y);
+ break;
+ case PROP_TRANS_X3:
+ g_value_set_double (value, private->trans[2].x);
+ break;
+ case PROP_TRANS_Y3:
+ g_value_set_double (value, private->trans[2].y);
+ break;
+ case PROP_TRANS_X4:
+ g_value_set_double (value, private->trans[3].x);
+ break;
+ case PROP_TRANS_Y4:
+ g_value_set_double (value, private->trans[3].y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_handle_grid_changed (GimpToolWidget *widget)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->changed (widget);
+
+ for (i = 0; i < 4; i++)
+ {
+ gimp_canvas_handle_set_position (private->handles[i + 1],
+ private->trans[i].x,
+ private->trans[i].y);
+ gimp_canvas_item_set_visible (private->handles[i + 1],
+ i < private->n_handles);
+ }
+
+ gimp_tool_handle_grid_update_hilight (grid);
+}
+
+static gint
+gimp_tool_handle_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint n_handles = private->n_handles;
+ gint active_handle = private->handle - 1;
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (n_handles < 4 && active_handle == -1)
+ {
+ /* add handle */
+
+ GimpMatrix3 *matrix;
+
+ active_handle = n_handles;
+
+ private->trans[active_handle].x = coords->x;
+ private->trans[active_handle].y = coords->y;
+ private->n_handles++;
+
+ if (! is_handle_position_valid (grid, active_handle))
+ {
+ handle_micro_move (grid, active_handle);
+ }
+
+ /* handle was added, calculate new original position */
+ g_object_get (grid,
+ "transform", &matrix,
+ NULL);
+
+ gimp_matrix3_invert (matrix);
+ gimp_matrix3_transform_point (matrix,
+ private->trans[active_handle].x,
+ private->trans[active_handle].y,
+ &private->orig[active_handle].x,
+ &private->orig[active_handle].y);
+
+ g_free (matrix);
+
+ private->handle = active_handle + 1;
+
+ g_object_notify (G_OBJECT (grid), "n-handles");
+ }
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ /* check for valid position and calculating of OX0...OY3 is
+ * done on button release
+ */
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (n_handles > 0 &&
+ active_handle >= 0 &&
+ active_handle < 4)
+ {
+ /* remove handle */
+
+ GimpVector2 temp = private->trans[active_handle];
+ GimpVector2 tempo = private->orig[active_handle];
+ gint i;
+
+ n_handles--;
+ private->n_handles--;
+
+ for (i = active_handle; i < n_handles; i++)
+ {
+ private->trans[i] = private->trans[i + 1];
+ private->orig[i] = private->orig[i + 1];
+ }
+
+ private->trans[n_handles] = temp;
+ private->orig[n_handles] = tempo;
+
+ g_object_notify (G_OBJECT (grid), "n-handles");
+ }
+ break;
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ return private->handle;
+}
+
+static void
+gimp_tool_handle_grid_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint active_handle = private->handle - 1;
+
+ if (private->handle_mode == GIMP_HANDLE_MODE_MOVE &&
+ active_handle >= 0 &&
+ active_handle < 4)
+ {
+ GimpMatrix3 *matrix;
+
+ if (! is_handle_position_valid (grid, active_handle))
+ {
+ handle_micro_move (grid, active_handle);
+ }
+
+ /* handle was moved, calculate new original position */
+ g_object_get (grid,
+ "transform", &matrix,
+ NULL);
+
+ gimp_matrix3_invert (matrix);
+ gimp_matrix3_transform_point (matrix,
+ private->trans[active_handle].x,
+ private->trans[active_handle].y,
+ &private->orig[active_handle].x,
+ &private->orig[active_handle].y);
+
+ g_free (matrix);
+
+ gimp_tool_handle_grid_update_matrix (grid);
+ }
+}
+
+void
+gimp_tool_handle_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint n_handles = private->n_handles;
+ gint active_handle = private->handle - 1;
+ gdouble diff_x = coords->x - private->last_x;
+ gdouble diff_y = coords->y - private->last_y;
+
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ if (active_handle >= 0 && active_handle < 4)
+ {
+ if (private->handle_mode == GIMP_HANDLE_MODE_MOVE)
+ {
+ private->trans[active_handle].x += diff_x;
+ private->trans[active_handle].y += diff_y;
+
+ /* check for valid position and calculating of OX0...OY3 is
+ * done on button release hopefully this makes the code run
+ * faster Moving could be even faster if there was caching
+ * for the image preview
+ */
+ gimp_canvas_handle_set_position (private->handles[active_handle + 1],
+ private->trans[active_handle].x,
+ private->trans[active_handle].y);
+ }
+ else if (private->handle_mode == GIMP_HANDLE_MODE_ADD_TRANSFORM)
+ {
+ gdouble angle, angle_sin, angle_cos, scale;
+ GimpVector2 fixed_handles[3];
+ GimpVector2 oldpos[4];
+ GimpVector2 newpos[4];
+ gint i, j;
+
+ for (i = 0, j = 0; i < 4; i++)
+ {
+ /* Find all visible handles that are not being moved */
+ if (i < n_handles && i != active_handle)
+ {
+ fixed_handles[j] = private->trans[i];
+ j++;
+ }
+
+ newpos[i] = oldpos[i] = private->trans[i];
+ }
+
+ newpos[active_handle].x = oldpos[active_handle].x + diff_x;
+ newpos[active_handle].y = oldpos[active_handle].y + diff_y;
+
+ switch (n_handles)
+ {
+ case 1:
+ /* move */
+ for (i = 0; i < 4; i++)
+ {
+ newpos[i].x = oldpos[i].x + diff_x;
+ newpos[i].y = oldpos[i].y + diff_y;
+ }
+ break;
+
+ case 2:
+ /* rotate and keep-aspect-scale */
+ scale =
+ calc_len (newpos[active_handle].x - fixed_handles[0].x,
+ newpos[active_handle].y - fixed_handles[0].y) /
+ calc_len (oldpos[active_handle].x - fixed_handles[0].x,
+ oldpos[active_handle].y - fixed_handles[0].y);
+
+ angle = calc_angle (oldpos[active_handle].x - fixed_handles[0].x,
+ oldpos[active_handle].y - fixed_handles[0].y,
+ newpos[active_handle].x - fixed_handles[0].x,
+ newpos[active_handle].y - fixed_handles[0].y);
+
+ angle_sin = sin (angle);
+ angle_cos = cos (angle);
+
+ for (i = 2; i < 4; i++)
+ {
+ newpos[i].x =
+ fixed_handles[0].x +
+ scale * (angle_cos * (oldpos[i].x - fixed_handles[0].x) +
+ angle_sin * (oldpos[i].y - fixed_handles[0].y));
+
+ newpos[i].y =
+ fixed_handles[0].y +
+ scale * (-angle_sin * (oldpos[i].x - fixed_handles[0].x) +
+ angle_cos * (oldpos[i].y - fixed_handles[0].y));
+ }
+ break;
+
+ case 3:
+ /* shear and non-aspect-scale */
+ scale = calc_lineintersect_ratio (oldpos[3].x,
+ oldpos[3].y,
+ oldpos[active_handle].x,
+ oldpos[active_handle].y,
+ fixed_handles[0].x,
+ fixed_handles[0].y,
+ fixed_handles[1].x,
+ fixed_handles[1].y);
+
+ newpos[3].x = oldpos[3].x + scale * diff_x;
+ newpos[3].y = oldpos[3].y + scale * diff_y;
+ break;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ private->trans[i] = newpos[i];
+ }
+
+ gimp_tool_handle_grid_update_matrix (grid);
+ }
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+}
+
+static GimpHit
+gimp_tool_handle_grid_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ if (proximity)
+ {
+ gint handle = gimp_tool_handle_grid_get_handle (grid, coords);
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (handle > 0)
+ return GIMP_HIT_DIRECT;
+ else
+ return GIMP_HIT_INDIRECT;
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (private->handle > 0)
+ return GIMP_HIT_DIRECT;
+ break;
+ }
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+static void
+gimp_tool_handle_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gchar *status = NULL;
+
+ private->hover = TRUE;
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ private->handle = gimp_tool_handle_grid_get_handle (grid, coords);
+
+ if (proximity)
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (private->handle > 0)
+ {
+ const gchar *s = NULL;
+
+ switch (private->n_handles)
+ {
+ case 1:
+ s = _("Click-Drag to move");
+ break;
+ case 2:
+ s = _("Click-Drag to rotate and scale");
+ break;
+ case 3:
+ s = _("Click-Drag to shear and scale");
+ break;
+ case 4:
+ s = _("Click-Drag to change perspective");
+ break;
+ }
+
+ status = gimp_suggest_modifiers (s,
+ extend_mask | toggle_mask,
+ NULL, NULL, NULL);
+ }
+ else
+ {
+ if (private->n_handles < 4)
+ status = g_strdup (_("Click to add a handle"));
+ }
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ if (private->handle > 0)
+ status = g_strdup (_("Click-Drag to move this handle"));
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (private->handle > 0)
+ status = g_strdup (_("Click-Drag to remove this handle"));
+ break;
+ }
+ }
+
+ gimp_tool_widget_set_status (widget, status);
+ g_free (status);
+
+ gimp_tool_handle_grid_update_hilight (grid);
+}
+
+static void
+gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ private->hover = FALSE;
+ private->handle = 0;
+
+ gimp_tool_handle_grid_update_hilight (grid);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static gboolean
+gimp_tool_handle_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ *cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ *tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ *modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (private->handle > 0)
+ {
+ switch (private->n_handles)
+ {
+ case 1:
+ *tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ break;
+ case 2:
+ *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
+ break;
+ case 3:
+ *tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
+ break;
+ case 4:
+ *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
+ break;
+ }
+ }
+ else
+ {
+ if (private->n_handles < 4)
+ *modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ if (private->handle > 0)
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (private->handle > 0)
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ break;
+ }
+
+ return TRUE;
+}
+
+static gint
+gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid,
+ const GimpCoords *coords)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (private->handles[i + 1] &&
+ gimp_canvas_item_hit (private->handles[i + 1],
+ coords->x, coords->y))
+ {
+ return i + 1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ GimpCanvasItem *item = private->handles[i + 1];
+
+ if (item)
+ {
+ gdouble diameter = GIMP_CANVAS_HANDLE_SIZE_CIRCLE;
+
+ if (private->hover)
+ {
+ diameter = gimp_canvas_handle_calc_size (
+ item,
+ private->mouse_x,
+ private->mouse_y,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE);
+ }
+
+ gimp_canvas_handle_set_size (item, diameter, diameter);
+ gimp_canvas_item_set_highlight (item, (i + 1) == private->handle);
+ }
+ }
+}
+
+static void
+gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ GimpMatrix3 transform;
+ gboolean transform_valid;
+
+ gimp_matrix3_identity (&transform);
+ transform_valid = gimp_transform_matrix_generic (&transform,
+ private->orig,
+ private->trans);
+
+ g_object_set (grid,
+ "transform", &transform,
+ "show-guides", transform_valid,
+ NULL);
+}
+
+/* check if a handle is not on the connection line of two other handles */
+static gboolean
+is_handle_position_valid (GimpToolHandleGrid *grid,
+ gint handle)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i, j, k;
+
+ for (i = 0; i < 2; i++)
+ {
+ for (j = i + 1; j < 3; j++)
+ {
+ for (k = j + 1; i < 4; i++)
+ {
+ if (handle == i ||
+ handle == j ||
+ handle == k)
+ {
+ if ((private->trans[i].x -
+ private->trans[j].x) *
+ (private->trans[j].y -
+ private->trans[k].y) ==
+
+ (private->trans[j].x -
+ private->trans[k].x) *
+ (private->trans[i].y -
+ private->trans[j].y))
+ {
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* three handles on a line causes problems.
+ * Let's move the new handle around a bit to find a better position */
+static void
+handle_micro_move (GimpToolHandleGrid *grid,
+ gint handle)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ gdouble posx = private->trans[handle].x;
+ gdouble posy = private->trans[handle].y;
+ gdouble dx, dy;
+
+ for (dx = -0.1; dx < 0.11; dx += 0.1)
+ {
+ private->trans[handle].x = posx + dx;
+
+ for (dy = -0.1; dy < 0.11; dy += 0.1)
+ {
+ private->trans[handle].y = posy + dy;
+
+ if (is_handle_position_valid (grid, handle))
+ {
+ return;
+ }
+ }
+ }
+}
+
+/* finds the clockwise angle between the vectors given, 0-2Ï€ */
+static inline gdouble
+calc_angle (gdouble ax,
+ gdouble ay,
+ gdouble bx,
+ gdouble by)
+{
+ gdouble angle;
+ gdouble direction;
+ gdouble length = sqrt ((ax * ax + ay * ay) * (bx * bx + by * by));
+
+ angle = acos ((ax * bx + ay * by) / length);
+ direction = ax * by - ay * bx;
+
+ return ((direction < 0) ? angle : 2 * G_PI - angle);
+}
+
+static inline gdouble
+calc_len (gdouble a,
+ gdouble b)
+{
+ return sqrt (a * a + b * b);
+}
+
+/* imagine two lines, one through the points p1 and p2, the other one
+ * through the points q1 and q2. Find the intersection point r.
+ * Calculate (distance p1 to r)/(distance p2 to r)
+ */
+static inline gdouble
+calc_lineintersect_ratio (gdouble p1x, gdouble p1y,
+ gdouble p2x, gdouble p2y,
+ gdouble q1x, gdouble q1y,
+ gdouble q2x, gdouble q2y)
+{
+ gdouble denom, u;
+
+ denom = (q2y - q1y) * (p2x - p1x) - (q2x - q1x) * (p2y - p1y);
+ if (denom == 0.0)
+ {
+ /* u is infinite, so u/(u-1) is 1 */
+ return 1.0;
+ }
+
+ u = (q2y - q1y) * (q1x - p1x) - (q1y - p1y) * (q2x - q1x);
+ u /= denom;
+
+ return u / (u - 1);
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_handle_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_HANDLE_GRID,
+ "shell", shell,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "clip-guides", TRUE,
+ NULL);
+}
diff --git a/app/display/gimptoolhandlegrid.h b/app/display/gimptoolhandlegrid.h
new file mode 100644
index 0000000..77d31dc
--- /dev/null
+++ b/app/display/gimptoolhandlegrid.h
@@ -0,0 +1,62 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolhandlegrid.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_HANDLE_GRID_H__
+#define __GIMP_TOOL_HANDLE_GRID_H__
+
+
+#include "gimptooltransformgrid.h"
+
+
+#define GIMP_TYPE_TOOL_HANDLE_GRID (gimp_tool_handle_grid_get_type ())
+#define GIMP_TOOL_HANDLE_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_HANDLE_GRID, GimpToolHandleGrid))
+#define GIMP_TOOL_HANDLE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_HANDLE_GRID, GimpToolHandleGridClass))
+#define GIMP_IS_TOOL_HANDLE_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_HANDLE_GRID))
+#define GIMP_IS_TOOL_HANDLE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_HANDLE_GRID))
+#define GIMP_TOOL_HANDLE_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_HANDLE_GRID, GimpToolHandleGridClass))
+
+
+typedef struct _GimpToolHandleGrid GimpToolHandleGrid;
+typedef struct _GimpToolHandleGridPrivate GimpToolHandleGridPrivate;
+typedef struct _GimpToolHandleGridClass GimpToolHandleGridClass;
+
+struct _GimpToolHandleGrid
+{
+ GimpToolTransformGrid parent_instance;
+
+ GimpToolHandleGridPrivate *private;
+};
+
+struct _GimpToolHandleGridClass
+{
+ GimpToolTransformGridClass parent_class;
+};
+
+
+GType gimp_tool_handle_grid_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_handle_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+
+#endif /* __GIMP_TOOL_HANDLE_GRID_H__ */
diff --git a/app/display/gimptoolline.c b/app/display/gimptoolline.c
new file mode 100644
index 0000000..271f926
--- /dev/null
+++ b/app/display/gimptoolline.c
@@ -0,0 +1,1796 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolline.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Major improvements for interactivity
+ * Copyright (C) 2014 Michael Henning <drawoc@darkrefraction.com>
+ *
+ * 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpmarshal.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvasgroup.h"
+#include "gimpcanvashandle.h"
+#include "gimpcanvasline.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-cursor.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolline.h"
+
+#include "gimp-intl.h"
+
+
+#define SHOW_LINE TRUE
+#define GRAB_LINE_MASK GDK_MOD1_MASK
+#define ENDPOINT_HANDLE_TYPE GIMP_HANDLE_CROSS
+#define ENDPOINT_HANDLE_SIZE GIMP_CANVAS_HANDLE_SIZE_CROSS
+#define SLIDER_HANDLE_TYPE GIMP_HANDLE_FILLED_DIAMOND
+#define SLIDER_HANDLE_SIZE (ENDPOINT_HANDLE_SIZE * 2 / 3)
+#define HANDLE_CIRCLE_SCALE 1.8
+#define LINE_VICINITY ((gint) (SLIDER_HANDLE_SIZE * HANDLE_CIRCLE_SCALE) / 2)
+#define SLIDER_TEAR_DISTANCE (5 * LINE_VICINITY)
+
+
+/* hover-only "handles" */
+#define HOVER_NEW_SLIDER (GIMP_TOOL_LINE_HANDLE_NONE - 1)
+
+
+typedef enum
+{
+ GRAB_NONE,
+ GRAB_SELECTION,
+ GRAB_LINE
+} GimpToolLineGrab;
+
+enum
+{
+ PROP_0,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ PROP_SLIDERS,
+ PROP_SELECTION,
+ PROP_STATUS_TITLE,
+};
+
+enum
+{
+ CAN_ADD_SLIDER,
+ ADD_SLIDER,
+ PREPARE_TO_REMOVE_SLIDER,
+ REMOVE_SLIDER,
+ SELECTION_CHANGED,
+ HANDLE_CLICKED,
+ LAST_SIGNAL
+};
+
+struct _GimpToolLinePrivate
+{
+ gdouble x1;
+ gdouble y1;
+ gdouble x2;
+ gdouble y2;
+ GArray *sliders;
+ gint selection;
+ gchar *status_title;
+
+ gdouble saved_x1;
+ gdouble saved_y1;
+ gdouble saved_x2;
+ gdouble saved_y2;
+ gdouble saved_slider_value;
+
+ gdouble mouse_x;
+ gdouble mouse_y;
+ gint hover;
+ gdouble new_slider_value;
+ gboolean remove_slider;
+ GimpToolLineGrab grab;
+
+ GimpCanvasItem *line;
+ GimpCanvasItem *start_handle;
+ GimpCanvasItem *end_handle;
+ GimpCanvasItem *slider_group;
+ GArray *slider_handles;
+ GimpCanvasItem *handle_circle;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_line_constructed (GObject *object);
+static void gimp_tool_line_finalize (GObject *object);
+static void gimp_tool_line_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_line_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_line_changed (GimpToolWidget *widget);
+static void gimp_tool_line_focus_changed (GimpToolWidget *widget);
+static gint gimp_tool_line_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_line_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_line_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_line_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_line_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_line_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_line_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_line_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_line_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static gint gimp_tool_line_get_hover (GimpToolLine *line,
+ const GimpCoords *coords,
+ GdkModifierType state);
+static GimpControllerSlider *
+ gimp_tool_line_get_slider (GimpToolLine *line,
+ gint slider);
+static GimpCanvasItem *
+ gimp_tool_line_get_handle (GimpToolLine *line,
+ gint handle);
+static gdouble gimp_tool_line_project_point (GimpToolLine *line,
+ gdouble x,
+ gdouble y,
+ gboolean constrain,
+ gdouble *dist);
+
+static gboolean
+ gimp_tool_line_selection_motion (GimpToolLine *line,
+ gboolean constrain);
+
+static void gimp_tool_line_update_handles (GimpToolLine *line);
+static void gimp_tool_line_update_circle (GimpToolLine *line);
+static void gimp_tool_line_update_hilight (GimpToolLine *line);
+static void gimp_tool_line_update_status (GimpToolLine *line,
+ GdkModifierType state,
+ gboolean proximity);
+
+static gboolean gimp_tool_line_handle_hit (GimpCanvasItem *handle,
+ gdouble x,
+ gdouble y,
+ gdouble *min_dist);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolLine, gimp_tool_line, GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_line_parent_class
+
+static guint line_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_tool_line_class_init (GimpToolLineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_line_constructed;
+ object_class->finalize = gimp_tool_line_finalize;
+ object_class->set_property = gimp_tool_line_set_property;
+ object_class->get_property = gimp_tool_line_get_property;
+
+ widget_class->changed = gimp_tool_line_changed;
+ widget_class->focus_changed = gimp_tool_line_focus_changed;
+ widget_class->button_press = gimp_tool_line_button_press;
+ widget_class->button_release = gimp_tool_line_button_release;
+ widget_class->motion = gimp_tool_line_motion;
+ widget_class->hit = gimp_tool_line_hit;
+ widget_class->hover = gimp_tool_line_hover;
+ widget_class->leave_notify = gimp_tool_line_leave_notify;
+ widget_class->key_press = gimp_tool_line_key_press;
+ widget_class->motion_modifier = gimp_tool_line_motion_modifier;
+ widget_class->get_cursor = gimp_tool_line_get_cursor;
+
+ line_signals[CAN_ADD_SLIDER] =
+ g_signal_new ("can-add-slider",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpToolLineClass, can_add_slider),
+ NULL, NULL,
+ gimp_marshal_BOOLEAN__DOUBLE,
+ G_TYPE_BOOLEAN, 1,
+ G_TYPE_DOUBLE);
+
+ line_signals[ADD_SLIDER] =
+ g_signal_new ("add-slider",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpToolLineClass, add_slider),
+ NULL, NULL,
+ gimp_marshal_INT__DOUBLE,
+ G_TYPE_INT, 1,
+ G_TYPE_DOUBLE);
+
+ line_signals[PREPARE_TO_REMOVE_SLIDER] =
+ g_signal_new ("prepare-to-remove-slider",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolLineClass, prepare_to_remove_slider),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_BOOLEAN,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_BOOLEAN);
+
+ line_signals[REMOVE_SLIDER] =
+ g_signal_new ("remove-slider",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolLineClass, remove_slider),
+ NULL, NULL,
+ gimp_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ line_signals[SELECTION_CHANGED] =
+ g_signal_new ("selection-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolLineClass, selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ line_signals[HANDLE_CLICKED] =
+ g_signal_new ("handle-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpToolLineClass, handle_clicked),
+ NULL, NULL,
+ gimp_marshal_BOOLEAN__INT_UINT_ENUM,
+ G_TYPE_BOOLEAN, 3,
+ G_TYPE_INT,
+ G_TYPE_UINT,
+ GIMP_TYPE_BUTTON_PRESS_TYPE);
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_double ("x1", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_double ("y1", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_double ("x2", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_double ("y2", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SLIDERS,
+ g_param_spec_boxed ("sliders", NULL, NULL,
+ G_TYPE_ARRAY,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SELECTION,
+ g_param_spec_int ("selection", NULL, NULL,
+ GIMP_TOOL_LINE_HANDLE_NONE,
+ G_MAXINT,
+ GIMP_TOOL_LINE_HANDLE_NONE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_STATUS_TITLE,
+ g_param_spec_string ("status-title",
+ NULL, NULL,
+ _("Line: "),
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_line_init (GimpToolLine *line)
+{
+ GimpToolLinePrivate *private;
+
+ private = line->private = gimp_tool_line_get_instance_private (line);
+
+ private->sliders = g_array_new (FALSE, FALSE, sizeof (GimpControllerSlider));
+
+ private->selection = GIMP_TOOL_LINE_HANDLE_NONE;
+ private->hover = GIMP_TOOL_LINE_HANDLE_NONE;
+ private->grab = GRAB_NONE;
+
+ private->slider_handles = g_array_new (FALSE, TRUE,
+ sizeof (GimpCanvasItem *));
+}
+
+static void
+gimp_tool_line_constructed (GObject *object)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolLinePrivate *private = line->private;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ private->line = gimp_tool_widget_add_line (widget,
+ private->x1,
+ private->y1,
+ private->x2,
+ private->y2);
+
+ gimp_canvas_item_set_visible (private->line, SHOW_LINE);
+
+ private->start_handle =
+ gimp_tool_widget_add_handle (widget,
+ ENDPOINT_HANDLE_TYPE,
+ private->x1,
+ private->y1,
+ ENDPOINT_HANDLE_SIZE,
+ ENDPOINT_HANDLE_SIZE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ private->end_handle =
+ gimp_tool_widget_add_handle (widget,
+ ENDPOINT_HANDLE_TYPE,
+ private->x2,
+ private->y2,
+ ENDPOINT_HANDLE_SIZE,
+ ENDPOINT_HANDLE_SIZE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ private->slider_group =
+ gimp_canvas_group_new (gimp_tool_widget_get_shell (widget));
+ gimp_tool_widget_add_item (widget, private->slider_group);
+ g_object_unref (private->slider_group);
+
+ private->handle_circle =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CIRCLE,
+ private->x1,
+ private->y1,
+ ENDPOINT_HANDLE_SIZE * HANDLE_CIRCLE_SCALE,
+ ENDPOINT_HANDLE_SIZE * HANDLE_CIRCLE_SCALE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ gimp_tool_line_changed (widget);
+}
+
+static void
+gimp_tool_line_finalize (GObject *object)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (object);
+ GimpToolLinePrivate *private = line->private;
+
+ g_clear_pointer (&private->sliders, g_array_unref);
+ g_clear_pointer (&private->status_title, g_free);
+ g_clear_pointer (&private->slider_handles, g_array_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_line_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (object);
+ GimpToolLinePrivate *private = line->private;
+
+ switch (property_id)
+ {
+ case PROP_X1:
+ private->x1 = g_value_get_double (value);
+ break;
+ case PROP_Y1:
+ private->y1 = g_value_get_double (value);
+ break;
+ case PROP_X2:
+ private->x2 = g_value_get_double (value);
+ break;
+ case PROP_Y2:
+ private->y2 = g_value_get_double (value);
+ break;
+
+ case PROP_SLIDERS:
+ {
+ GArray *sliders = g_value_dup_boxed (value);
+ gboolean deselect;
+
+ g_return_if_fail (sliders != NULL);
+
+ deselect =
+ GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection) &&
+ (sliders->len != private->sliders->len ||
+ ! gimp_tool_line_get_slider (line, private->selection)->selectable);
+
+ g_array_unref (private->sliders);
+ private->sliders = sliders;
+
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->hover))
+ private->hover = GIMP_TOOL_LINE_HANDLE_NONE;
+
+ if (deselect)
+ gimp_tool_line_set_selection (line, GIMP_TOOL_LINE_HANDLE_NONE);
+ }
+ break;
+
+ case PROP_SELECTION:
+ {
+ gint selection = g_value_get_int (value);
+
+ g_return_if_fail (selection < (gint) private->sliders->len);
+ g_return_if_fail (selection < 0 ||
+ gimp_tool_line_get_slider (line,
+ selection)->selectable);
+
+ if (selection != private->selection)
+ {
+ private->selection = selection;
+
+ if (private->grab == GRAB_SELECTION)
+ private->grab = GRAB_NONE;
+
+ g_signal_emit (line, line_signals[SELECTION_CHANGED], 0);
+ }
+ }
+ break;
+
+ case PROP_STATUS_TITLE:
+ g_free (private->status_title);
+ private->status_title = g_value_dup_string (value);
+ if (! private->status_title)
+ private->status_title = g_strdup (_("Line: "));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_line_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (object);
+ GimpToolLinePrivate *private = line->private;
+
+ switch (property_id)
+ {
+ case PROP_X1:
+ g_value_set_double (value, private->x1);
+ break;
+ case PROP_Y1:
+ g_value_set_double (value, private->y1);
+ break;
+ case PROP_X2:
+ g_value_set_double (value, private->x2);
+ break;
+ case PROP_Y2:
+ g_value_set_double (value, private->y2);
+ break;
+
+ case PROP_SLIDERS:
+ g_value_set_boxed (value, private->sliders);
+ break;
+
+ case PROP_SELECTION:
+ g_value_set_int (value, private->selection);
+ break;
+
+ case PROP_STATUS_TITLE:
+ g_value_set_string (value, private->status_title);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_line_changed (GimpToolWidget *widget)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+ gint i;
+
+ gimp_canvas_line_set (private->line,
+ private->x1,
+ private->y1,
+ private->x2,
+ private->y2);
+
+ gimp_canvas_handle_set_position (private->start_handle,
+ private->x1,
+ private->y1);
+
+ gimp_canvas_handle_set_position (private->end_handle,
+ private->x2,
+ private->y2);
+
+ /* remove excessive slider handles */
+ for (i = private->sliders->len; i < private->slider_handles->len; i++)
+ {
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (private->slider_group),
+ gimp_tool_line_get_handle (line, i));
+ }
+
+ g_array_set_size (private->slider_handles, private->sliders->len);
+
+ for (i = 0; i < private->sliders->len; i++)
+ {
+ gdouble value;
+ gdouble x;
+ gdouble y;
+ GimpCanvasItem **handle;
+
+ value = gimp_tool_line_get_slider (line, i)->value;
+
+ x = private->x1 + (private->x2 - private->x1) * value;
+ y = private->y1 + (private->y2 - private->y1) * value;
+
+ handle = &g_array_index (private->slider_handles, GimpCanvasItem *, i);
+
+ if (*handle)
+ {
+ gimp_canvas_handle_set_position (*handle, x, y);
+ }
+ else
+ {
+ *handle = gimp_canvas_handle_new (gimp_tool_widget_get_shell (widget),
+ SLIDER_HANDLE_TYPE,
+ GIMP_HANDLE_ANCHOR_CENTER,
+ x,
+ y,
+ SLIDER_HANDLE_SIZE,
+ SLIDER_HANDLE_SIZE);
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (private->slider_group),
+ *handle);
+ g_object_unref (*handle);
+ }
+ }
+
+ gimp_tool_line_update_handles (line);
+ gimp_tool_line_update_circle (line);
+ gimp_tool_line_update_hilight (line);
+}
+
+static void
+gimp_tool_line_focus_changed (GimpToolWidget *widget)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+
+ gimp_tool_line_update_hilight (line);
+}
+
+gboolean
+gimp_tool_line_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+ gboolean result = FALSE;
+
+ private->grab = GRAB_NONE;
+ private->remove_slider = FALSE;
+
+ private->saved_x1 = private->x1;
+ private->saved_y1 = private->y1;
+ private->saved_x2 = private->x2;
+ private->saved_y2 = private->y2;
+
+ if (press_type != GIMP_BUTTON_PRESS_NORMAL &&
+ private->hover > GIMP_TOOL_LINE_HANDLE_NONE &&
+ private->selection > GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ g_signal_emit (line, line_signals[HANDLE_CLICKED], 0,
+ private->selection, state, press_type, &result);
+
+ if (! result)
+ gimp_tool_widget_hover (widget, coords, state, TRUE);
+ }
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL || ! result)
+ {
+ private->saved_x1 = private->x1;
+ private->saved_y1 = private->y1;
+ private->saved_x2 = private->x2;
+ private->saved_y2 = private->y2;
+
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->hover))
+ {
+ private->saved_slider_value =
+ gimp_tool_line_get_slider (line, private->hover)->value;
+ }
+
+ if (private->hover > GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ gimp_tool_line_set_selection (line, private->hover);
+
+ private->grab = GRAB_SELECTION;
+ }
+ else if (private->hover == HOVER_NEW_SLIDER)
+ {
+ gint slider;
+
+ g_signal_emit (line, line_signals[ADD_SLIDER], 0,
+ private->new_slider_value, &slider);
+
+ g_return_val_if_fail (slider < (gint) private->sliders->len, FALSE);
+
+ if (slider >= 0)
+ {
+ gimp_tool_line_set_selection (line, slider);
+
+ private->saved_slider_value =
+ gimp_tool_line_get_slider (line, private->selection)->value;
+
+ private->grab = GRAB_SELECTION;
+ }
+ }
+ else if (state & GRAB_LINE_MASK)
+ {
+ private->grab = GRAB_LINE;
+ }
+
+ result = (private->grab != GRAB_NONE);
+ }
+
+ if (! result)
+ {
+ private->hover = GIMP_TOOL_LINE_HANDLE_NONE;
+
+ gimp_tool_line_set_selection (line, GIMP_TOOL_LINE_HANDLE_NONE);
+ }
+
+ gimp_tool_line_update_handles (line);
+ gimp_tool_line_update_circle (line);
+ gimp_tool_line_update_status (line, state, TRUE);
+
+ return result;
+}
+
+void
+gimp_tool_line_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+ GimpToolLineGrab grab = private->grab;
+
+ private->grab = GRAB_NONE;
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ if (grab != GRAB_NONE)
+ {
+ if (grab == GRAB_SELECTION &&
+ GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection))
+ {
+ gimp_tool_line_get_slider (line, private->selection)->value =
+ private->saved_slider_value;
+
+ if (private->remove_slider)
+ {
+ private->remove_slider = FALSE;
+
+ g_signal_emit (line, line_signals[PREPARE_TO_REMOVE_SLIDER], 0,
+ private->selection, FALSE);
+ }
+ }
+
+ g_object_set (line,
+ "x1", private->saved_x1,
+ "y1", private->saved_y1,
+ "x2", private->saved_x2,
+ "y2", private->saved_y2,
+ NULL);
+ }
+ }
+ else if (grab == GRAB_SELECTION)
+ {
+ if (private->remove_slider)
+ {
+ private->remove_slider = FALSE;
+
+ g_signal_emit (line, line_signals[REMOVE_SLIDER], 0,
+ private->selection);
+ }
+ else if (release_type == GIMP_BUTTON_RELEASE_CLICK)
+ {
+ gboolean result;
+
+ g_signal_emit (line, line_signals[HANDLE_CLICKED], 0,
+ private->selection, state, GIMP_BUTTON_PRESS_NORMAL,
+ &result);
+ }
+ }
+}
+
+void
+gimp_tool_line_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+ gdouble diff_x = coords->x - private->mouse_x;
+ gdouble diff_y = coords->y - private->mouse_y;
+
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ if (private->grab == GRAB_LINE)
+ {
+ g_object_set (line,
+ "x1", private->x1 + diff_x,
+ "y1", private->y1 + diff_y,
+ "x2", private->x2 + diff_x,
+ "y2", private->y2 + diff_y,
+ NULL);
+ }
+ else
+ {
+ gboolean constrain = (state & gimp_get_constrain_behavior_mask ()) != 0;
+
+ gimp_tool_line_selection_motion (line, constrain);
+ }
+
+ gimp_tool_line_update_status (line, state, TRUE);
+}
+
+GimpHit
+gimp_tool_line_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+
+ if (! (state & GRAB_LINE_MASK))
+ {
+ gint hover = gimp_tool_line_get_hover (line, coords, state);
+
+ if (hover != GIMP_TOOL_LINE_HANDLE_NONE)
+ return GIMP_HIT_DIRECT;
+ }
+ else
+ {
+ return GIMP_HIT_INDIRECT;
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+void
+gimp_tool_line_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ if (! (state & GRAB_LINE_MASK))
+ private->hover = gimp_tool_line_get_hover (line, coords, state);
+ else
+ private->hover = GIMP_TOOL_LINE_HANDLE_NONE;
+
+ gimp_tool_line_update_handles (line);
+ gimp_tool_line_update_circle (line);
+ gimp_tool_line_update_status (line, state, proximity);
+}
+
+static void
+gimp_tool_line_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+
+ private->hover = GIMP_TOOL_LINE_HANDLE_NONE;
+
+ gimp_tool_line_update_handles (line);
+ gimp_tool_line_update_circle (line);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static gboolean
+gimp_tool_line_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+ GimpDisplayShell *shell;
+ gdouble pixels = 1.0;
+ gboolean move_line;
+
+ move_line = kevent->state & GRAB_LINE_MASK;
+
+ if (private->selection == GIMP_TOOL_LINE_HANDLE_NONE && ! move_line)
+ return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
+
+ shell = gimp_tool_widget_get_shell (widget);
+
+ if (kevent->state & gimp_get_extend_selection_mask ())
+ pixels = 10.0;
+
+ if (kevent->state & gimp_get_toggle_behavior_mask ())
+ pixels = 50.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ case GDK_KEY_Right:
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ /* move an endpoint (or both endpoints) */
+ if (private->selection < 0 || move_line)
+ {
+ gdouble xdist, ydist;
+ gdouble dx, dy;
+
+ xdist = FUNSCALEX (shell, pixels);
+ ydist = FUNSCALEY (shell, pixels);
+
+ dx = 0.0;
+ dy = 0.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left: dx = -xdist; break;
+ case GDK_KEY_Right: dx = +xdist; break;
+ case GDK_KEY_Up: dy = -ydist; break;
+ case GDK_KEY_Down: dy = +ydist; break;
+ }
+
+ if (private->selection == GIMP_TOOL_LINE_HANDLE_START || move_line)
+ {
+ g_object_set (line,
+ "x1", private->x1 + dx,
+ "y1", private->y1 + dy,
+ NULL);
+ }
+
+ if (private->selection == GIMP_TOOL_LINE_HANDLE_END || move_line)
+ {
+ g_object_set (line,
+ "x2", private->x2 + dx,
+ "y2", private->y2 + dy,
+ NULL);
+ }
+ }
+ /* move a slider */
+ else
+ {
+ GimpControllerSlider *slider;
+ gdouble dist;
+ gdouble dvalue;
+
+ slider = gimp_tool_line_get_slider (line, private->selection);
+
+ if (! slider->movable)
+ break;
+
+ dist = gimp_canvas_item_transform_distance (private->line,
+ private->x1, private->y1,
+ private->x2, private->y2);
+
+ if (dist > 0.0)
+ dist = pixels / dist;
+
+ dvalue = 0.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ if (private->x1 < private->x2) dvalue = -dist;
+ else if (private->x1 > private->x2) dvalue = +dist;
+ break;
+
+ case GDK_KEY_Right:
+ if (private->x1 < private->x2) dvalue = +dist;
+ else if (private->x1 > private->x2) dvalue = -dist;
+ break;
+
+ case GDK_KEY_Up:
+ if (private->y1 < private->y2) dvalue = -dist;
+ else if (private->y1 > private->y2) dvalue = +dist;
+ break;
+
+ case GDK_KEY_Down:
+ if (private->y1 < private->y2) dvalue = +dist;
+ else if (private->y1 > private->y2) dvalue = -dist;
+ break;
+ }
+
+ if (dvalue != 0.0)
+ {
+ slider->value += dvalue;
+ slider->value = CLAMP (slider->value, slider->min, slider->max);
+ slider->value = CLAMP (slider->value, 0.0, 1.0);
+
+ g_object_set (line,
+ "sliders", private->sliders,
+ NULL);
+ }
+ }
+ return TRUE;
+
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Delete:
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection))
+ {
+ if (gimp_tool_line_get_slider (line, private->selection)->removable)
+ {
+ g_signal_emit (line, line_signals[REMOVE_SLIDER], 0,
+ private->selection);
+ }
+ }
+ return TRUE;
+ }
+
+ return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
+}
+
+static void
+gimp_tool_line_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+
+ if (key == gimp_get_constrain_behavior_mask ())
+ {
+ gimp_tool_line_selection_motion (line, press);
+
+ gimp_tool_line_update_status (line, state, TRUE);
+ }
+}
+
+static gboolean
+gimp_tool_line_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolLine *line = GIMP_TOOL_LINE (widget);
+ GimpToolLinePrivate *private = line->private;
+
+ if (private->grab ==GRAB_LINE || (state & GRAB_LINE_MASK))
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ return TRUE;
+ }
+ else if (private->grab == GRAB_SELECTION ||
+ private->hover > GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ const GimpControllerSlider *slider = NULL;
+
+ if (private->grab == GRAB_SELECTION)
+ {
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection))
+ slider = gimp_tool_line_get_slider (line, private->selection);
+ }
+ else if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->hover))
+ {
+ slider = gimp_tool_line_get_slider (line, private->hover);
+ }
+
+ if (private->grab == GRAB_SELECTION && slider && private->remove_slider)
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+
+ return TRUE;
+ }
+ else if (! slider || slider->movable)
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ return TRUE;
+ }
+ }
+ else if (private->hover == HOVER_NEW_SLIDER)
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_PLUS;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_tool_line_get_hover (GimpToolLine *line,
+ const GimpCoords *coords,
+ GdkModifierType state)
+{
+ GimpToolLinePrivate *private = line->private;
+ gint hover = GIMP_TOOL_LINE_HANDLE_NONE;
+ gdouble min_dist;
+ gint first_handle;
+ gint i;
+
+ /* find the closest handle to the cursor */
+ min_dist = G_MAXDOUBLE;
+ first_handle = private->sliders->len - 1;
+
+ /* skip the sliders if the two endpoints are the same, in particular so
+ * that if the line is created during a button-press event (as in the
+ * blend tool), the end endpoint is dragged, instead of a slider.
+ */
+ if (private->x1 == private->x2 && private->y1 == private->y2)
+ first_handle = -1;
+
+ for (i = first_handle; i > GIMP_TOOL_LINE_HANDLE_NONE; i--)
+ {
+ GimpCanvasItem *handle;
+
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (i))
+ {
+ const GimpControllerSlider *slider;
+
+ slider = gimp_tool_line_get_slider (line, i);
+
+ if (! slider->visible || ! slider->selectable)
+ continue;
+ }
+
+ handle = gimp_tool_line_get_handle (line, i);
+
+ if (gimp_tool_line_handle_hit (handle,
+ private->mouse_x,
+ private->mouse_y,
+ &min_dist))
+ {
+ hover = i;
+ }
+ }
+
+ if (hover == GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ gboolean constrain;
+ gdouble value;
+ gdouble dist;
+
+ constrain = (state & gimp_get_constrain_behavior_mask ()) != 0;
+
+ value = gimp_tool_line_project_point (line,
+ private->mouse_x,
+ private->mouse_y,
+ constrain,
+ &dist);
+
+ if (value >= 0.0 && value <= 1.0 && dist <= LINE_VICINITY)
+ {
+ gboolean can_add;
+
+ g_signal_emit (line, line_signals[CAN_ADD_SLIDER], 0,
+ value, &can_add);
+
+ if (can_add)
+ {
+ hover = HOVER_NEW_SLIDER;
+ private->new_slider_value = value;
+ }
+ }
+ }
+
+ return hover;
+}
+
+static GimpControllerSlider *
+gimp_tool_line_get_slider (GimpToolLine *line,
+ gint slider)
+{
+ GimpToolLinePrivate *private = line->private;
+
+ gimp_assert (slider >= 0 && slider < private->sliders->len);
+
+ return &g_array_index (private->sliders, GimpControllerSlider, slider);
+}
+
+static GimpCanvasItem *
+gimp_tool_line_get_handle (GimpToolLine *line,
+ gint handle)
+{
+ GimpToolLinePrivate *private = line->private;
+
+ switch (handle)
+ {
+ case GIMP_TOOL_LINE_HANDLE_NONE:
+ return NULL;
+
+ case GIMP_TOOL_LINE_HANDLE_START:
+ return private->start_handle;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ return private->end_handle;
+
+ default:
+ gimp_assert (handle >= 0 &&
+ handle < (gint) private->slider_handles->len);
+
+ return g_array_index (private->slider_handles,
+ GimpCanvasItem *, handle);
+ }
+}
+
+static gdouble
+gimp_tool_line_project_point (GimpToolLine *line,
+ gdouble x,
+ gdouble y,
+ gboolean constrain,
+ gdouble *dist)
+{
+ GimpToolLinePrivate *private = line->private;
+ gdouble length_sqr;
+ gdouble value = 0.0;
+
+ length_sqr = SQR (private->x2 - private->x1) +
+ SQR (private->y2 - private->y1);
+
+ /* don't calculate the projection for 0-length lines, since we'll just get
+ * NaN.
+ */
+ if (length_sqr > 0.0)
+ {
+ value = (private->x2 - private->x1) * (x - private->x1) +
+ (private->y2 - private->y1) * (y - private->y1);
+ value /= length_sqr;
+
+ if (dist)
+ {
+ gdouble px;
+ gdouble py;
+
+ px = private->x1 + (private->x2 - private->x1) * value;
+ py = private->y1 + (private->y2 - private->y1) * value;
+
+ *dist = gimp_canvas_item_transform_distance (private->line,
+ x, y,
+ px, py);
+ }
+
+ if (constrain)
+ value = RINT (12.0 * value) / 12.0;
+ }
+ else
+ {
+ if (dist)
+ {
+ *dist = gimp_canvas_item_transform_distance (private->line,
+ x, y,
+ private->x1, private->y1);
+ }
+ }
+
+ return value;
+}
+
+static gboolean
+gimp_tool_line_selection_motion (GimpToolLine *line,
+ gboolean constrain)
+{
+ GimpToolLinePrivate *private = line->private;
+ gdouble x = private->mouse_x;
+ gdouble y = private->mouse_y;
+
+ if (private->grab != GRAB_SELECTION)
+ return FALSE;
+
+ switch (private->selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_NONE:
+ gimp_assert_not_reached ();
+
+ case GIMP_TOOL_LINE_HANDLE_START:
+ if (constrain)
+ {
+ gimp_display_shell_constrain_line (
+ gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line)),
+ private->x2, private->y2,
+ &x, &y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+
+ g_object_set (line,
+ "x1", x,
+ "y1", y,
+ NULL);
+ return TRUE;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ if (constrain)
+ {
+ gimp_display_shell_constrain_line (
+ gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line)),
+ private->x1, private->y1,
+ &x, &y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+
+ g_object_set (line,
+ "x2", x,
+ "y2", y,
+ NULL);
+ return TRUE;
+
+ default:
+ {
+ GimpDisplayShell *shell;
+ GimpControllerSlider *slider;
+ gdouble value;
+ gdouble dist;
+ gboolean remove_slider;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line));
+
+ slider = gimp_tool_line_get_slider (line, private->selection);
+
+ /* project the cursor position onto the line */
+ value = gimp_tool_line_project_point (line, x, y, constrain, &dist);
+
+ /* slider dragging */
+ if (slider->movable)
+ {
+ value = CLAMP (value, slider->min, slider->max);
+ value = CLAMP (value, 0.0, 1.0);
+
+ value = fabs (value); /* avoid negative zero */
+
+ slider->value = value;
+
+ g_object_set (line,
+ "sliders", private->sliders,
+ NULL);
+ }
+
+ /* slider tearing */
+ remove_slider = slider->removable && dist > SLIDER_TEAR_DISTANCE;
+
+ if (remove_slider != private->remove_slider)
+ {
+ private->remove_slider = remove_slider;
+
+ g_signal_emit (line, line_signals[PREPARE_TO_REMOVE_SLIDER], 0,
+ private->selection, remove_slider);
+
+ /* set the cursor modifier to a minus by talking to the shell
+ * directly -- eek!
+ */
+ {
+ GimpCursorType cursor;
+ GimpToolCursorType tool_cursor;
+ GimpCursorModifier modifier;
+
+ cursor = shell->current_cursor;
+ tool_cursor = shell->tool_cursor;
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ gimp_tool_line_get_cursor (GIMP_TOOL_WIDGET (line), NULL, 0,
+ &cursor, &tool_cursor, &modifier);
+
+ gimp_display_shell_set_cursor (shell, cursor, tool_cursor, modifier);
+ }
+
+ gimp_tool_line_update_handles (line);
+ gimp_tool_line_update_circle (line);
+ gimp_tool_line_update_status (line,
+ constrain ?
+ gimp_get_constrain_behavior_mask () :
+ 0,
+ TRUE);
+ }
+
+ return TRUE;
+ }
+ }
+}
+
+static void
+gimp_tool_line_update_handles (GimpToolLine *line)
+{
+ GimpToolLinePrivate *private = line->private;
+ gdouble value;
+ gdouble dist;
+ gint i;
+
+ value = gimp_tool_line_project_point (line,
+ private->mouse_x,
+ private->mouse_y,
+ FALSE,
+ &dist);
+
+ for (i = 0; i < private->sliders->len; i++)
+ {
+ const GimpControllerSlider *slider;
+ GimpCanvasItem *handle;
+ gint size;
+ gint hit_radius;
+ gboolean show_autohidden;
+ gboolean visible;
+
+ slider = gimp_tool_line_get_slider (line, i);
+ handle = gimp_tool_line_get_handle (line, i);
+
+ size = slider->size * SLIDER_HANDLE_SIZE;
+ size = MAX (size, 1);
+
+ hit_radius = (MAX (size, SLIDER_HANDLE_SIZE) * HANDLE_CIRCLE_SCALE) / 2;
+
+ /* show a autohidden slider if it's selected, or if no other handle is
+ * grabbed or hovered-over, and the cursor is close enough to the line,
+ * between the slider's min and max values.
+ */
+ show_autohidden = private->selection == i ||
+ (private->grab == GRAB_NONE &&
+ (private->hover <= GIMP_TOOL_LINE_HANDLE_NONE ||
+ private->hover == i) &&
+ dist <= hit_radius &&
+ value >= slider->min &&
+ value <= slider->max);
+
+ visible = slider->visible &&
+ (! slider->autohide || show_autohidden) &&
+ ! (private->selection == i && private->remove_slider);
+
+ handle = gimp_tool_line_get_handle (line, i);
+
+ if (visible)
+ {
+ g_object_set (handle,
+ "type", slider->type,
+ "width", size,
+ "height", size,
+ NULL);
+ }
+
+ gimp_canvas_item_set_visible (handle, visible);
+ }
+}
+
+static void
+gimp_tool_line_update_circle (GimpToolLine *line)
+{
+ GimpToolLinePrivate *private = line->private;
+ gboolean visible;
+
+ visible = (private->grab == GRAB_NONE &&
+ private->hover != GIMP_TOOL_LINE_HANDLE_NONE) ||
+ (private->grab == GRAB_SELECTION &&
+ private->remove_slider);
+
+ if (visible)
+ {
+ gdouble x;
+ gdouble y;
+ gint width;
+ gint height;
+ gboolean dashed;
+
+ if (private->grab == GRAB_NONE && private->hover == HOVER_NEW_SLIDER)
+ {
+ /* new slider */
+ x = private->x1 +
+ (private->x2 - private->x1) * private->new_slider_value;
+ y = private->y1 +
+ (private->y2 - private->y1) * private->new_slider_value;
+
+ width = height = SLIDER_HANDLE_SIZE;
+
+ dashed = TRUE;
+ }
+ else
+ {
+ GimpCanvasItem *handle;
+
+ if (private->grab == GRAB_SELECTION)
+ {
+ /* tear slider */
+ handle = gimp_tool_line_get_handle (line, private->selection);
+ dashed = TRUE;
+ }
+ else
+ {
+ /* hover over handle */
+ handle = gimp_tool_line_get_handle (line, private->hover);
+ dashed = FALSE;
+ }
+
+ gimp_canvas_handle_get_position (handle, &x, &y);
+ gimp_canvas_handle_get_size (handle, &width, &height);
+ }
+
+ width = MAX (width, SLIDER_HANDLE_SIZE);
+ height = MAX (height, SLIDER_HANDLE_SIZE);
+
+ width *= HANDLE_CIRCLE_SCALE;
+ height *= HANDLE_CIRCLE_SCALE;
+
+ gimp_canvas_handle_set_position (private->handle_circle, x, y);
+ gimp_canvas_handle_set_size (private->handle_circle, width, height);
+
+ g_object_set (private->handle_circle,
+ "type", dashed ? GIMP_HANDLE_DASHED_CIRCLE :
+ GIMP_HANDLE_CIRCLE,
+ NULL);
+ }
+
+ gimp_canvas_item_set_visible (private->handle_circle, visible);
+}
+
+static void
+gimp_tool_line_update_hilight (GimpToolLine *line)
+{
+ GimpToolLinePrivate *private = line->private;
+ gboolean focus;
+ gint i;
+
+ focus = gimp_tool_widget_get_focus (GIMP_TOOL_WIDGET (line));
+
+ for (i = GIMP_TOOL_LINE_HANDLE_NONE + 1;
+ i < (gint) private->sliders->len;
+ i++)
+ {
+ GimpCanvasItem *handle;
+
+ handle = gimp_tool_line_get_handle (line, i);
+
+ gimp_canvas_item_set_highlight (handle, focus && i == private->selection);
+ }
+}
+
+static void
+gimp_tool_line_update_status (GimpToolLine *line,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolLinePrivate *private = line->private;
+
+ if (proximity)
+ {
+ GimpDisplayShell *shell;
+ const gchar *toggle_behavior_format = NULL;
+ const gchar *message = NULL;
+ gchar *line_status = NULL;
+ gchar *status;
+ gint handle;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line));
+
+ if (private->grab == GRAB_SELECTION)
+ handle = private->selection;
+ else
+ handle = private->hover;
+
+ if (handle == GIMP_TOOL_LINE_HANDLE_START ||
+ handle == GIMP_TOOL_LINE_HANDLE_END)
+ {
+ line_status = gimp_display_shell_get_line_status (shell,
+ _("Click-Drag to move the endpoint"),
+ ". ",
+ private->x1,
+ private->y1,
+ private->x2,
+ private->y2);
+ toggle_behavior_format = _("%s for constrained angles");
+ }
+ else if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (handle) ||
+ handle == HOVER_NEW_SLIDER)
+ {
+ if (private->grab == GRAB_SELECTION && private->remove_slider)
+ {
+ message = _("Release to remove the slider");
+ }
+ else
+ {
+ toggle_behavior_format = _("%s for constrained values");
+
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (handle))
+ {
+ if (gimp_tool_line_get_slider (line, handle)->movable)
+ {
+ if (gimp_tool_line_get_slider (line, handle)->removable)
+ {
+ if (private->grab == GRAB_SELECTION)
+ {
+ message = _("Click-Drag to move the slider; "
+ "drag away to remove the slider");
+ }
+ else
+ {
+ message = _("Click-Drag to move or remove the slider");
+ }
+ }
+ else
+ {
+ message = _("Click-Drag to move the slider");
+ }
+ }
+ else
+ {
+ toggle_behavior_format = NULL;
+
+ if (gimp_tool_line_get_slider (line, handle)->removable)
+ {
+ if (private->grab == GRAB_SELECTION)
+ {
+ message = _("Click-Drag away to remove the slider");
+ }
+ else
+ {
+ message = _("Click-Drag to remove the slider");
+ }
+ }
+ else
+ {
+ message = NULL;
+ }
+ }
+ }
+ else
+ {
+ message = _("Click or Click-Drag to add a new slider");
+ }
+ }
+ }
+ else if (state & GDK_MOD1_MASK)
+ {
+ message = _("Click-Drag to move the line");
+ }
+
+ status =
+ gimp_suggest_modifiers (message ? message : (line_status ? line_status : ""),
+ ((toggle_behavior_format ?
+ gimp_get_constrain_behavior_mask () : 0) |
+ (private->grab == GRAB_NONE ?
+ GDK_MOD1_MASK : 0)) &
+ ~state,
+ NULL,
+ toggle_behavior_format,
+ _("%s to move the whole line"));
+
+ if (message || line_status)
+ {
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (line), status);
+ }
+ else
+ {
+ line_status = gimp_display_shell_get_line_status (shell,
+ private->status_title,
+ ". ",
+ private->x1,
+ private->y1,
+ private->x2,
+ private->y2);
+ gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (line),
+ line_status,
+ private->x2 - private->x1,
+ ", ",
+ private->y2 - private->y1,
+ status);
+ }
+
+ g_free (status);
+ if (line_status)
+ g_free (line_status);
+ }
+ else
+ {
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (line), NULL);
+ }
+}
+
+static gboolean
+gimp_tool_line_handle_hit (GimpCanvasItem *handle,
+ gdouble x,
+ gdouble y,
+ gdouble *min_dist)
+{
+ gdouble handle_x;
+ gdouble handle_y;
+ gint handle_width;
+ gint handle_height;
+ gint radius;
+ gdouble dist;
+
+ gimp_canvas_handle_get_position (handle, &handle_x, &handle_y);
+ gimp_canvas_handle_get_size (handle, &handle_width, &handle_height);
+
+ handle_width = MAX (handle_width, SLIDER_HANDLE_SIZE);
+ handle_height = MAX (handle_height, SLIDER_HANDLE_SIZE);
+
+ radius = ((gint) (handle_width * HANDLE_CIRCLE_SCALE)) / 2;
+ radius = MAX (radius, LINE_VICINITY);
+
+ dist = gimp_canvas_item_transform_distance (handle,
+ x, y, handle_x, handle_y);
+
+ if (dist <= radius && dist < *min_dist)
+ {
+ *min_dist = dist;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_line_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_LINE,
+ "shell", shell,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+}
+
+void
+gimp_tool_line_set_sliders (GimpToolLine *line,
+ const GimpControllerSlider *sliders,
+ gint n_sliders)
+{
+ GimpToolLinePrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_LINE (line));
+ g_return_if_fail (n_sliders == 0 || (n_sliders > 0 && sliders != NULL));
+
+ private = line->private;
+
+ if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection) &&
+ private->sliders->len != n_sliders)
+ {
+ gimp_tool_line_set_selection (line, GIMP_TOOL_LINE_HANDLE_NONE);
+ }
+
+ g_array_set_size (private->sliders, n_sliders);
+
+ memcpy (private->sliders->data, sliders,
+ n_sliders * sizeof (GimpControllerSlider));
+
+ g_object_set (line,
+ "sliders", private->sliders,
+ NULL);
+}
+
+const GimpControllerSlider *
+gimp_tool_line_get_sliders (GimpToolLine *line,
+ gint *n_sliders)
+{
+ GimpToolLinePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_LINE (line), NULL);
+
+ private = line->private;
+
+ if (n_sliders) *n_sliders = private->sliders->len;
+
+ return (const GimpControllerSlider *) private->sliders->data;
+}
+
+void
+gimp_tool_line_set_selection (GimpToolLine *line,
+ gint handle)
+{
+ GimpToolLinePrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_LINE (line));
+
+ private = line->private;
+
+ g_return_if_fail (handle >= GIMP_TOOL_LINE_HANDLE_NONE &&
+ handle < (gint) private->sliders->len);
+
+ g_object_set (line,
+ "selection", handle,
+ NULL);
+}
+
+gint
+gimp_tool_line_get_selection (GimpToolLine *line)
+{
+ GimpToolLinePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_LINE (line), GIMP_TOOL_LINE_HANDLE_NONE);
+
+ private = line->private;
+
+ return private->selection;
+}
diff --git a/app/display/gimptoolline.h b/app/display/gimptoolline.h
new file mode 100644
index 0000000..80d046d
--- /dev/null
+++ b/app/display/gimptoolline.h
@@ -0,0 +1,99 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolline.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_LINE_H__
+#define __GIMP_TOOL_LINE_H__
+
+
+#include "gimptoolwidget.h"
+
+
+/* in the context of GimpToolLine, "handle" is a collective term for either an
+ * endpoint or a slider. a handle value may be either a (nonnegative) slider
+ * index, or one of the values below:
+ */
+#define GIMP_TOOL_LINE_HANDLE_NONE (-3)
+#define GIMP_TOOL_LINE_HANDLE_START (-2)
+#define GIMP_TOOL_LINE_HANDLE_END (-1)
+
+#define GIMP_TOOL_LINE_HANDLE_IS_SLIDER(handle) ((handle) >= 0)
+
+
+#define GIMP_TYPE_TOOL_LINE (gimp_tool_line_get_type ())
+#define GIMP_TOOL_LINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_LINE, GimpToolLine))
+#define GIMP_TOOL_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_LINE, GimpToolLineClass))
+#define GIMP_IS_TOOL_LINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_LINE))
+#define GIMP_IS_TOOL_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_LINE))
+#define GIMP_TOOL_LINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_LINE, GimpToolLineClass))
+
+
+typedef struct _GimpToolLine GimpToolLine;
+typedef struct _GimpToolLinePrivate GimpToolLinePrivate;
+typedef struct _GimpToolLineClass GimpToolLineClass;
+
+struct _GimpToolLine
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolLinePrivate *private;
+};
+
+struct _GimpToolLineClass
+{
+ GimpToolWidgetClass parent_class;
+
+ /* signals */
+ gboolean (* can_add_slider) (GimpToolLine *line,
+ gdouble value);
+ gint (* add_slider) (GimpToolLine *line,
+ gdouble value);
+ void (* prepare_to_remove_slider) (GimpToolLine *line,
+ gint slider,
+ gboolean remove);
+ void (* remove_slider) (GimpToolLine *line,
+ gint slider);
+ void (* selection_changed) (GimpToolLine *line);
+ gboolean (* handle_clicked) (GimpToolLine *line,
+ gint handle,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+};
+
+
+GType gimp_tool_line_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_line_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+void gimp_tool_line_set_sliders (GimpToolLine *line,
+ const GimpControllerSlider *sliders,
+ gint n_sliders);
+const GimpControllerSlider * gimp_tool_line_get_sliders (GimpToolLine *line,
+ gint *n_sliders);
+
+void gimp_tool_line_set_selection (GimpToolLine *line,
+ gint handle);
+gint gimp_tool_line_get_selection (GimpToolLine *line);
+
+
+#endif /* __GIMP_TOOL_LINE_H__ */
diff --git a/app/display/gimptoolpath.c b/app/display/gimptoolpath.c
new file mode 100644
index 0000000..9e2cf54
--- /dev/null
+++ b/app/display/gimptoolpath.c
@@ -0,0 +1,1904 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpath.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Vector tool
+ * Copyright (C) 2003 Simon Budig <simon@gimp.org>
+ *
+ * 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "display-types.h"
+
+#include "vectors/gimpanchor.h"
+#include "vectors/gimpbezierstroke.h"
+#include "vectors/gimpvectors.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "tools/gimptools-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpcanvasline.h"
+#include "gimpcanvaspath.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimptoolpath.h"
+
+#include "gimp-intl.h"
+
+
+#define TOGGLE_MASK gimp_get_extend_selection_mask ()
+#define MOVE_MASK GDK_MOD1_MASK
+#define INSDEL_MASK gimp_get_toggle_behavior_mask ()
+
+
+/* possible vector functions */
+typedef enum
+{
+ VECTORS_SELECT_VECTOR,
+ VECTORS_CREATE_VECTOR,
+ VECTORS_CREATE_STROKE,
+ VECTORS_ADD_ANCHOR,
+ VECTORS_MOVE_ANCHOR,
+ VECTORS_MOVE_ANCHORSET,
+ VECTORS_MOVE_HANDLE,
+ VECTORS_MOVE_CURVE,
+ VECTORS_MOVE_STROKE,
+ VECTORS_MOVE_VECTORS,
+ VECTORS_INSERT_ANCHOR,
+ VECTORS_DELETE_ANCHOR,
+ VECTORS_CONNECT_STROKES,
+ VECTORS_DELETE_SEGMENT,
+ VECTORS_CONVERT_EDGE,
+ VECTORS_FINISHED
+} GimpVectorFunction;
+
+enum
+{
+ PROP_0,
+ PROP_VECTORS,
+ PROP_EDIT_MODE,
+ PROP_POLYGONAL
+};
+
+enum
+{
+ BEGIN_CHANGE,
+ END_CHANGE,
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+struct _GimpToolPathPrivate
+{
+ GimpVectors *vectors; /* the current Vector data */
+ GimpVectorMode edit_mode;
+ gboolean polygonal;
+
+ GimpVectorFunction function; /* function we're performing */
+ GimpAnchorFeatureType restriction; /* movement restriction */
+ gboolean modifier_lock; /* can we toggle the Shift key? */
+ GdkModifierType saved_state; /* modifier state at button_press */
+ gdouble last_x; /* last x coordinate */
+ gdouble last_y; /* last y coordinate */
+ gboolean undo_motion; /* we need a motion to have an undo */
+ gboolean have_undo; /* did we push an undo at */
+ /* ..._button_press? */
+
+ GimpAnchor *cur_anchor; /* the current Anchor */
+ GimpAnchor *cur_anchor2; /* secondary Anchor (end on_curve) */
+ GimpStroke *cur_stroke; /* the current Stroke */
+ gdouble cur_position; /* the current Position on a segment */
+
+ gint sel_count; /* number of selected anchors */
+ GimpAnchor *sel_anchor; /* currently selected anchor, NULL */
+ /* if multiple anchors are selected */
+ GimpStroke *sel_stroke; /* selected stroke */
+
+ GimpVectorMode saved_mode; /* used by modifier_key() */
+
+ GimpCanvasItem *path;
+ GList *items;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_path_constructed (GObject *object);
+static void gimp_tool_path_dispose (GObject *object);
+static void gimp_tool_path_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_path_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_path_changed (GimpToolWidget *widget);
+static gint gimp_tool_path_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_path_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_path_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_path_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_path_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static gboolean gimp_tool_path_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static gboolean gimp_tool_path_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static GimpVectorFunction
+ gimp_tool_path_get_function (GimpToolPath *path,
+ const GimpCoords *coords,
+ GdkModifierType state);
+
+static void gimp_tool_path_update_status (GimpToolPath *path,
+ GdkModifierType state,
+ gboolean proximity);
+
+static void gimp_tool_path_begin_change (GimpToolPath *path,
+ const gchar *desc);
+static void gimp_tool_path_end_change (GimpToolPath *path,
+ gboolean success);
+
+static void gimp_tool_path_vectors_visible (GimpVectors *vectors,
+ GimpToolPath *path);
+static void gimp_tool_path_vectors_freeze (GimpVectors *vectors,
+ GimpToolPath *path);
+static void gimp_tool_path_vectors_thaw (GimpVectors *vectors,
+ GimpToolPath *path);
+static void gimp_tool_path_verify_state (GimpToolPath *path);
+
+static void gimp_tool_path_move_selected_anchors
+ (GimpToolPath *path,
+ gdouble x,
+ gdouble y);
+static void gimp_tool_path_delete_selected_anchors
+ (GimpToolPath *path);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolPath, gimp_tool_path, GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_path_parent_class
+
+static guint path_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_tool_path_class_init (GimpToolPathClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_path_constructed;
+ object_class->dispose = gimp_tool_path_dispose;
+ object_class->set_property = gimp_tool_path_set_property;
+ object_class->get_property = gimp_tool_path_get_property;
+
+ widget_class->changed = gimp_tool_path_changed;
+ widget_class->focus_changed = gimp_tool_path_changed;
+ widget_class->button_press = gimp_tool_path_button_press;
+ widget_class->button_release = gimp_tool_path_button_release;
+ widget_class->motion = gimp_tool_path_motion;
+ widget_class->hit = gimp_tool_path_hit;
+ widget_class->hover = gimp_tool_path_hover;
+ widget_class->key_press = gimp_tool_path_key_press;
+ widget_class->get_cursor = gimp_tool_path_get_cursor;
+
+ path_signals[BEGIN_CHANGE] =
+ g_signal_new ("begin-change",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolPathClass, begin_change),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ path_signals[END_CHANGE] =
+ g_signal_new ("end-change",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolPathClass, end_change),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+
+ path_signals[ACTIVATE] =
+ g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolPathClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__FLAGS,
+ G_TYPE_NONE, 1,
+ GDK_TYPE_MODIFIER_TYPE);
+
+ g_object_class_install_property (object_class, PROP_VECTORS,
+ g_param_spec_object ("vectors", NULL, NULL,
+ GIMP_TYPE_VECTORS,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_EDIT_MODE,
+ g_param_spec_enum ("edit-mode",
+ _("Edit Mode"),
+ NULL,
+ GIMP_TYPE_VECTOR_MODE,
+ GIMP_VECTOR_MODE_DESIGN,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_POLYGONAL,
+ g_param_spec_boolean ("polygonal",
+ _("Polygonal"),
+ _("Restrict editing to polygons"),
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_path_init (GimpToolPath *path)
+{
+ path->private = gimp_tool_path_get_instance_private (path);
+}
+
+static void
+gimp_tool_path_constructed (GObject *object)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolPathPrivate *private = path->private;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ private->path = gimp_tool_widget_add_path (widget, NULL);
+
+ gimp_tool_path_changed (widget);
+}
+
+static void
+gimp_tool_path_dispose (GObject *object)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (object);
+
+ gimp_tool_path_set_vectors (path, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_tool_path_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (object);
+ GimpToolPathPrivate *private = path->private;
+
+ switch (property_id)
+ {
+ case PROP_VECTORS:
+ gimp_tool_path_set_vectors (path, g_value_get_object (value));
+ break;
+ case PROP_EDIT_MODE:
+ private->edit_mode = g_value_get_enum (value);
+ break;
+ case PROP_POLYGONAL:
+ private->polygonal = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_path_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (object);
+ GimpToolPathPrivate *private = path->private;
+
+ switch (property_id)
+ {
+ case PROP_VECTORS:
+ g_value_set_object (value, private->vectors);
+ break;
+ case PROP_EDIT_MODE:
+ g_value_set_enum (value, private->edit_mode);
+ break;
+ case PROP_POLYGONAL:
+ g_value_set_boolean (value, private->polygonal);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+item_remove_func (GimpCanvasItem *item,
+ GimpToolWidget *widget)
+{
+ gimp_tool_widget_remove_item (widget, item);
+}
+
+static void
+gimp_tool_path_changed (GimpToolWidget *widget)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+ GimpVectors *vectors = private->vectors;
+
+ if (private->items)
+ {
+ g_list_foreach (private->items, (GFunc) item_remove_func, widget);
+ g_list_free (private->items);
+ private->items = NULL;
+ }
+
+ if (vectors && gimp_vectors_get_bezier (vectors))
+ {
+ GimpStroke *cur_stroke;
+
+ gimp_canvas_path_set (private->path,
+ gimp_vectors_get_bezier (vectors));
+ gimp_canvas_item_set_visible (private->path,
+ ! gimp_item_get_visible (GIMP_ITEM (vectors)));
+
+ for (cur_stroke = gimp_vectors_stroke_get_next (vectors, NULL);
+ cur_stroke;
+ cur_stroke = gimp_vectors_stroke_get_next (vectors, cur_stroke))
+ {
+ GimpCanvasItem *item;
+ GArray *coords;
+ GList *draw_anchors;
+ GList *list;
+
+ /* anchor handles */
+ draw_anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+ for (list = draw_anchors; list; list = g_list_next (list))
+ {
+ GimpAnchor *cur_anchor = GIMP_ANCHOR (list->data);
+
+ if (cur_anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ item =
+ gimp_tool_widget_add_handle (widget,
+ cur_anchor->selected ?
+ GIMP_HANDLE_CIRCLE :
+ GIMP_HANDLE_FILLED_CIRCLE,
+ cur_anchor->position.x,
+ cur_anchor->position.y,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ private->items = g_list_prepend (private->items, item);
+ }
+ }
+
+ g_list_free (draw_anchors);
+
+ if (private->sel_count <= 2)
+ {
+ /* the lines to the control handles */
+ coords = gimp_stroke_get_draw_lines (cur_stroke);
+
+ if (coords)
+ {
+ if (coords->len % 2 == 0)
+ {
+ gint i;
+
+ for (i = 0; i < coords->len; i += 2)
+ {
+ item = gimp_tool_widget_add_line
+ (widget,
+ g_array_index (coords, GimpCoords, i).x,
+ g_array_index (coords, GimpCoords, i).y,
+ g_array_index (coords, GimpCoords, i + 1).x,
+ g_array_index (coords, GimpCoords, i + 1).y);
+
+ if (gimp_tool_widget_get_focus (widget))
+ gimp_canvas_item_set_highlight (item, TRUE);
+
+ private->items = g_list_prepend (private->items, item);
+ }
+ }
+
+ g_array_free (coords, TRUE);
+ }
+
+ /* control handles */
+ draw_anchors = gimp_stroke_get_draw_controls (cur_stroke);
+
+ for (list = draw_anchors; list; list = g_list_next (list))
+ {
+ GimpAnchor *cur_anchor = GIMP_ANCHOR (list->data);
+
+ item =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_SQUARE,
+ cur_anchor->position.x,
+ cur_anchor->position.y,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE - 3,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE - 3,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ private->items = g_list_prepend (private->items, item);
+ }
+
+ g_list_free (draw_anchors);
+ }
+ }
+ }
+ else
+ {
+ gimp_canvas_path_set (private->path, NULL);
+ }
+}
+
+static gboolean
+gimp_tool_path_check_writable (GimpToolPath *path)
+{
+ GimpToolPathPrivate *private = path->private;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (path);
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (private->vectors)) ||
+ gimp_item_is_position_locked (GIMP_ITEM (private->vectors)))
+ {
+ gimp_tool_widget_message_literal (GIMP_TOOL_WIDGET (path),
+ _("The active path is locked."));
+
+ /* FIXME: this should really be done by the tool */
+ gimp_tools_blink_lock_box (shell->display->gimp,
+ GIMP_ITEM (private->vectors));
+
+ private->function = VECTORS_FINISHED;
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_tool_path_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+
+ /* do nothing if we are in a FINISHED state */
+ if (private->function == VECTORS_FINISHED)
+ return 0;
+
+ g_return_val_if_fail (private->vectors != NULL ||
+ private->function == VECTORS_SELECT_VECTOR ||
+ private->function == VECTORS_CREATE_VECTOR, 0);
+
+ private->undo_motion = FALSE;
+
+ /* save the current modifier state */
+
+ private->saved_state = state;
+
+
+ /* select a vectors object */
+
+ if (private->function == VECTORS_SELECT_VECTOR)
+ {
+ GimpVectors *vectors;
+
+ if (gimp_canvas_item_on_vectors (private->path,
+ coords,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ NULL, NULL, NULL, NULL, NULL, &vectors))
+ {
+ gimp_tool_path_set_vectors (path, vectors);
+ }
+
+ private->function = VECTORS_FINISHED;
+ }
+
+
+ /* create a new vector from scratch */
+
+ if (private->function == VECTORS_CREATE_VECTOR)
+ {
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpVectors *vectors;
+
+ vectors = gimp_vectors_new (image, _("Unnamed"));
+ g_object_ref_sink (vectors);
+
+ /* Undo step gets added implicitly */
+ private->have_undo = TRUE;
+
+ private->undo_motion = TRUE;
+
+ gimp_tool_path_set_vectors (path, vectors);
+ g_object_unref (vectors);
+
+ private->function = VECTORS_CREATE_STROKE;
+ }
+
+
+ gimp_vectors_freeze (private->vectors);
+
+ /* create a new stroke */
+
+ if (private->function == VECTORS_CREATE_STROKE &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Add Stroke"));
+ private->undo_motion = TRUE;
+
+ private->cur_stroke = gimp_bezier_stroke_new ();
+ gimp_vectors_stroke_add (private->vectors, private->cur_stroke);
+ g_object_unref (private->cur_stroke);
+
+ private->sel_stroke = private->cur_stroke;
+ private->cur_anchor = NULL;
+ private->sel_anchor = NULL;
+ private->function = VECTORS_ADD_ANCHOR;
+ }
+
+
+ /* add an anchor to an existing stroke */
+
+ if (private->function == VECTORS_ADD_ANCHOR &&
+ gimp_tool_path_check_writable (path))
+ {
+ GimpCoords position = GIMP_COORDS_DEFAULT_VALUES;
+
+ position.x = coords->x;
+ position.y = coords->y;
+
+ gimp_tool_path_begin_change (path, _("Add Anchor"));
+ private->undo_motion = TRUE;
+
+ private->cur_anchor = gimp_bezier_stroke_extend (private->sel_stroke,
+ &position,
+ private->sel_anchor,
+ EXTEND_EDITABLE);
+
+ private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+
+ if (! private->polygonal)
+ private->function = VECTORS_MOVE_HANDLE;
+ else
+ private->function = VECTORS_MOVE_ANCHOR;
+
+ private->cur_stroke = private->sel_stroke;
+ }
+
+
+ /* insertion of an anchor in a curve segment */
+
+ if (private->function == VECTORS_INSERT_ANCHOR &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Insert Anchor"));
+ private->undo_motion = TRUE;
+
+ private->cur_anchor = gimp_stroke_anchor_insert (private->cur_stroke,
+ private->cur_anchor,
+ private->cur_position);
+ if (private->cur_anchor)
+ {
+ if (private->polygonal)
+ {
+ gimp_stroke_anchor_convert (private->cur_stroke,
+ private->cur_anchor,
+ GIMP_ANCHOR_FEATURE_EDGE);
+ }
+
+ private->function = VECTORS_MOVE_ANCHOR;
+ }
+ else
+ {
+ private->function = VECTORS_FINISHED;
+ }
+ }
+
+
+ /* move a handle */
+
+ if (private->function == VECTORS_MOVE_HANDLE &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Drag Handle"));
+
+ if (private->cur_anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (! private->cur_anchor->selected)
+ {
+ gimp_vectors_anchor_select (private->vectors,
+ private->cur_stroke,
+ private->cur_anchor,
+ TRUE, TRUE);
+ private->undo_motion = TRUE;
+ }
+
+ gimp_canvas_item_on_vectors_handle (private->path,
+ private->vectors, coords,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_ANCHOR_CONTROL, TRUE,
+ &private->cur_anchor,
+ &private->cur_stroke);
+ if (! private->cur_anchor)
+ private->function = VECTORS_FINISHED;
+ }
+ }
+
+
+ /* move an anchor */
+
+ if (private->function == VECTORS_MOVE_ANCHOR &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Drag Anchor"));
+
+ if (! private->cur_anchor->selected)
+ {
+ gimp_vectors_anchor_select (private->vectors,
+ private->cur_stroke,
+ private->cur_anchor,
+ TRUE, TRUE);
+ private->undo_motion = TRUE;
+ }
+ }
+
+
+ /* move multiple anchors */
+
+ if (private->function == VECTORS_MOVE_ANCHORSET &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Drag Anchors"));
+
+ if (state & TOGGLE_MASK)
+ {
+ gimp_vectors_anchor_select (private->vectors,
+ private->cur_stroke,
+ private->cur_anchor,
+ !private->cur_anchor->selected,
+ FALSE);
+ private->undo_motion = TRUE;
+
+ if (private->cur_anchor->selected == FALSE)
+ private->function = VECTORS_FINISHED;
+ }
+ }
+
+
+ /* move a curve segment directly */
+
+ if (private->function == VECTORS_MOVE_CURVE &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Drag Curve"));
+
+ /* the magic numbers are taken from the "feel good" parameter
+ * from gimp_bezier_stroke_point_move_relative in gimpbezierstroke.c. */
+ if (private->cur_position < 5.0 / 6.0)
+ {
+ gimp_vectors_anchor_select (private->vectors,
+ private->cur_stroke,
+ private->cur_anchor, TRUE, TRUE);
+ private->undo_motion = TRUE;
+ }
+
+ if (private->cur_position > 1.0 / 6.0)
+ {
+ gimp_vectors_anchor_select (private->vectors,
+ private->cur_stroke,
+ private->cur_anchor2, TRUE,
+ (private->cur_position >= 5.0 / 6.0));
+ private->undo_motion = TRUE;
+ }
+
+ }
+
+
+ /* connect two strokes */
+
+ if (private->function == VECTORS_CONNECT_STROKES &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Connect Strokes"));
+ private->undo_motion = TRUE;
+
+ gimp_stroke_connect_stroke (private->sel_stroke,
+ private->sel_anchor,
+ private->cur_stroke,
+ private->cur_anchor);
+
+ if (private->cur_stroke != private->sel_stroke &&
+ gimp_stroke_is_empty (private->cur_stroke))
+ {
+ gimp_vectors_stroke_remove (private->vectors,
+ private->cur_stroke);
+ }
+
+ private->sel_anchor = private->cur_anchor;
+ private->cur_stroke = private->sel_stroke;
+
+ gimp_vectors_anchor_select (private->vectors,
+ private->sel_stroke,
+ private->sel_anchor, TRUE, TRUE);
+
+ private->function = VECTORS_FINISHED;
+ }
+
+
+ /* move a stroke or all strokes of a vectors object */
+
+ if ((private->function == VECTORS_MOVE_STROKE ||
+ private->function == VECTORS_MOVE_VECTORS) &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Drag Path"));
+
+ /* Work is being done in gimp_tool_path_motion()... */
+ }
+
+
+ /* convert an anchor to something that looks like an edge */
+
+ if (private->function == VECTORS_CONVERT_EDGE &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Convert Edge"));
+ private->undo_motion = TRUE;
+
+ gimp_stroke_anchor_convert (private->cur_stroke,
+ private->cur_anchor,
+ GIMP_ANCHOR_FEATURE_EDGE);
+
+ if (private->cur_anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ gimp_vectors_anchor_select (private->vectors,
+ private->cur_stroke,
+ private->cur_anchor, TRUE, TRUE);
+
+ private->function = VECTORS_MOVE_ANCHOR;
+ }
+ else
+ {
+ private->cur_stroke = NULL;
+ private->cur_anchor = NULL;
+
+ /* avoid doing anything stupid */
+ private->function = VECTORS_FINISHED;
+ }
+ }
+
+
+ /* removal of a node in a stroke */
+
+ if (private->function == VECTORS_DELETE_ANCHOR &&
+ gimp_tool_path_check_writable (path))
+ {
+ gimp_tool_path_begin_change (path, _("Delete Anchor"));
+ private->undo_motion = TRUE;
+
+ gimp_stroke_anchor_delete (private->cur_stroke,
+ private->cur_anchor);
+
+ if (gimp_stroke_is_empty (private->cur_stroke))
+ gimp_vectors_stroke_remove (private->vectors,
+ private->cur_stroke);
+
+ private->cur_stroke = NULL;
+ private->cur_anchor = NULL;
+ private->function = VECTORS_FINISHED;
+ }
+
+
+ /* deleting a segment (opening up a stroke) */
+
+ if (private->function == VECTORS_DELETE_SEGMENT &&
+ gimp_tool_path_check_writable (path))
+ {
+ GimpStroke *new_stroke;
+
+ gimp_tool_path_begin_change (path, _("Delete Segment"));
+ private->undo_motion = TRUE;
+
+ new_stroke = gimp_stroke_open (private->cur_stroke,
+ private->cur_anchor);
+ if (new_stroke)
+ {
+ gimp_vectors_stroke_add (private->vectors, new_stroke);
+ g_object_unref (new_stroke);
+ }
+
+ private->cur_stroke = NULL;
+ private->cur_anchor = NULL;
+ private->function = VECTORS_FINISHED;
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ gimp_vectors_thaw (private->vectors);
+
+ return 1;
+}
+
+void
+gimp_tool_path_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+
+ private->function = VECTORS_FINISHED;
+
+ if (private->have_undo)
+ {
+ if (! private->undo_motion ||
+ release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ gimp_tool_path_end_change (path, FALSE);
+ }
+ else
+ {
+ gimp_tool_path_end_change (path, TRUE);
+ }
+ }
+}
+
+void
+gimp_tool_path_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+ GimpCoords position = GIMP_COORDS_DEFAULT_VALUES;
+ GimpAnchor *anchor;
+
+ if (private->function == VECTORS_FINISHED)
+ return;
+
+ position.x = coords->x;
+ position.y = coords->y;
+
+ gimp_vectors_freeze (private->vectors);
+
+ if ((private->saved_state & TOGGLE_MASK) != (state & TOGGLE_MASK))
+ private->modifier_lock = FALSE;
+
+ if (! private->modifier_lock)
+ {
+ if (state & TOGGLE_MASK)
+ {
+ private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+ }
+ else
+ {
+ private->restriction = GIMP_ANCHOR_FEATURE_NONE;
+ }
+ }
+
+ switch (private->function)
+ {
+ case VECTORS_MOVE_ANCHOR:
+ case VECTORS_MOVE_HANDLE:
+ anchor = private->cur_anchor;
+
+ if (anchor)
+ {
+ gimp_stroke_anchor_move_absolute (private->cur_stroke,
+ private->cur_anchor,
+ &position,
+ private->restriction);
+ private->undo_motion = TRUE;
+ }
+ break;
+
+ case VECTORS_MOVE_CURVE:
+ if (private->polygonal)
+ {
+ gimp_tool_path_move_selected_anchors (path,
+ coords->x - private->last_x,
+ coords->y - private->last_y);
+ private->undo_motion = TRUE;
+ }
+ else
+ {
+ gimp_stroke_point_move_absolute (private->cur_stroke,
+ private->cur_anchor,
+ private->cur_position,
+ &position,
+ private->restriction);
+ private->undo_motion = TRUE;
+ }
+ break;
+
+ case VECTORS_MOVE_ANCHORSET:
+ gimp_tool_path_move_selected_anchors (path,
+ coords->x - private->last_x,
+ coords->y - private->last_y);
+ private->undo_motion = TRUE;
+ break;
+
+ case VECTORS_MOVE_STROKE:
+ if (private->cur_stroke)
+ {
+ gimp_stroke_translate (private->cur_stroke,
+ coords->x - private->last_x,
+ coords->y - private->last_y);
+ private->undo_motion = TRUE;
+ }
+ else if (private->sel_stroke)
+ {
+ gimp_stroke_translate (private->sel_stroke,
+ coords->x - private->last_x,
+ coords->y - private->last_y);
+ private->undo_motion = TRUE;
+ }
+ break;
+
+ case VECTORS_MOVE_VECTORS:
+ gimp_item_translate (GIMP_ITEM (private->vectors),
+ coords->x - private->last_x,
+ coords->y - private->last_y, FALSE);
+ private->undo_motion = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ gimp_vectors_thaw (private->vectors);
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+}
+
+GimpHit
+gimp_tool_path_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+
+ switch (gimp_tool_path_get_function (path, coords, state))
+ {
+ case VECTORS_SELECT_VECTOR:
+ case VECTORS_MOVE_ANCHOR:
+ case VECTORS_MOVE_ANCHORSET:
+ case VECTORS_MOVE_HANDLE:
+ case VECTORS_MOVE_CURVE:
+ case VECTORS_MOVE_STROKE:
+ case VECTORS_DELETE_ANCHOR:
+ case VECTORS_DELETE_SEGMENT:
+ case VECTORS_INSERT_ANCHOR:
+ case VECTORS_CONNECT_STROKES:
+ case VECTORS_CONVERT_EDGE:
+ return GIMP_HIT_DIRECT;
+
+ case VECTORS_CREATE_VECTOR:
+ case VECTORS_CREATE_STROKE:
+ case VECTORS_ADD_ANCHOR:
+ case VECTORS_MOVE_VECTORS:
+ return GIMP_HIT_INDIRECT;
+
+ case VECTORS_FINISHED:
+ return GIMP_HIT_NONE;
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+void
+gimp_tool_path_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+
+ private->function = gimp_tool_path_get_function (path, coords, state);
+
+ gimp_tool_path_update_status (path, state, proximity);
+}
+
+static gboolean
+gimp_tool_path_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+ GimpDisplayShell *shell;
+ gdouble xdist, ydist;
+ gdouble pixels = 1.0;
+
+ if (! private->vectors)
+ return FALSE;
+
+ shell = gimp_tool_widget_get_shell (widget);
+
+ if (kevent->state & gimp_get_extend_selection_mask ())
+ pixels = 10.0;
+
+ if (kevent->state & gimp_get_toggle_behavior_mask ())
+ pixels = 50.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ g_signal_emit (path, path_signals[ACTIVATE], 0,
+ kevent->state);
+ break;
+
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Delete:
+ gimp_tool_path_delete_selected_anchors (path);
+ break;
+
+ case GDK_KEY_Left:
+ case GDK_KEY_Right:
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ xdist = FUNSCALEX (shell, pixels);
+ ydist = FUNSCALEY (shell, pixels);
+
+ gimp_tool_path_begin_change (path, _("Move Anchors"));
+ gimp_vectors_freeze (private->vectors);
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ gimp_tool_path_move_selected_anchors (path, -xdist, 0);
+ break;
+
+ case GDK_KEY_Right:
+ gimp_tool_path_move_selected_anchors (path, xdist, 0);
+ break;
+
+ case GDK_KEY_Up:
+ gimp_tool_path_move_selected_anchors (path, 0, -ydist);
+ break;
+
+ case GDK_KEY_Down:
+ gimp_tool_path_move_selected_anchors (path, 0, ydist);
+ break;
+
+ default:
+ break;
+ }
+
+ gimp_vectors_thaw (private->vectors);
+ gimp_tool_path_end_change (path, TRUE);
+ break;
+
+ case GDK_KEY_Escape:
+ if (private->edit_mode != GIMP_VECTOR_MODE_DESIGN)
+ g_object_set (private,
+ "vectors-edit-mode", GIMP_VECTOR_MODE_DESIGN,
+ NULL);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_tool_path_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolPath *path = GIMP_TOOL_PATH (widget);
+ GimpToolPathPrivate *private = path->private;
+
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS;
+ *modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ switch (private->function)
+ {
+ case VECTORS_SELECT_VECTOR:
+ *tool_cursor = GIMP_TOOL_CURSOR_HAND;
+ break;
+
+ case VECTORS_CREATE_VECTOR:
+ case VECTORS_CREATE_STROKE:
+ *modifier = GIMP_CURSOR_MODIFIER_CONTROL;
+ break;
+
+ case VECTORS_ADD_ANCHOR:
+ case VECTORS_INSERT_ANCHOR:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+ *modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ break;
+
+ case VECTORS_DELETE_ANCHOR:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ break;
+
+ case VECTORS_DELETE_SEGMENT:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT;
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ break;
+
+ case VECTORS_MOVE_HANDLE:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_CONTROL;
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case VECTORS_CONVERT_EDGE:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_CONTROL;
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ break;
+
+ case VECTORS_MOVE_ANCHOR:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case VECTORS_MOVE_CURVE:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT;
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case VECTORS_MOVE_STROKE:
+ case VECTORS_MOVE_VECTORS:
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case VECTORS_MOVE_ANCHORSET:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case VECTORS_CONNECT_STROKES:
+ *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT;
+ *modifier = GIMP_CURSOR_MODIFIER_JOIN;
+ break;
+
+ default:
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ break;
+ }
+
+ return TRUE;
+}
+
+static GimpVectorFunction
+gimp_tool_path_get_function (GimpToolPath *path,
+ const GimpCoords *coords,
+ GdkModifierType state)
+{
+ GimpToolPathPrivate *private = path->private;
+ GimpAnchor *anchor = NULL;
+ GimpAnchor *anchor2 = NULL;
+ GimpStroke *stroke = NULL;
+ gdouble position = -1;
+ gboolean on_handle = FALSE;
+ gboolean on_curve = FALSE;
+ gboolean on_vectors = FALSE;
+ GimpVectorFunction function = VECTORS_FINISHED;
+
+ private->modifier_lock = FALSE;
+
+ /* are we hovering the current vectors on the current display? */
+ if (private->vectors)
+ {
+ on_handle = gimp_canvas_item_on_vectors_handle (private->path,
+ private->vectors,
+ coords,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_ANCHOR_ANCHOR,
+ private->sel_count > 2,
+ &anchor, &stroke);
+
+ if (! on_handle)
+ on_curve = gimp_canvas_item_on_vectors_curve (private->path,
+ private->vectors,
+ coords,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ NULL,
+ &position, &anchor,
+ &anchor2, &stroke);
+ }
+
+ if (! on_handle && ! on_curve)
+ {
+ on_vectors = gimp_canvas_item_on_vectors (private->path,
+ coords,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ }
+
+ private->cur_position = position;
+ private->cur_anchor = anchor;
+ private->cur_anchor2 = anchor2;
+ private->cur_stroke = stroke;
+
+ switch (private->edit_mode)
+ {
+ case GIMP_VECTOR_MODE_DESIGN:
+ if (! private->vectors)
+ {
+ if (on_vectors)
+ {
+ function = VECTORS_SELECT_VECTOR;
+ }
+ else
+ {
+ function = VECTORS_CREATE_VECTOR;
+ private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+ private->modifier_lock = TRUE;
+ }
+ }
+ else if (on_handle)
+ {
+ if (anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (state & TOGGLE_MASK)
+ {
+ function = VECTORS_MOVE_ANCHORSET;
+ }
+ else
+ {
+ if (private->sel_count >= 2 && anchor->selected)
+ function = VECTORS_MOVE_ANCHORSET;
+ else
+ function = VECTORS_MOVE_ANCHOR;
+ }
+ }
+ else
+ {
+ function = VECTORS_MOVE_HANDLE;
+
+ if (state & TOGGLE_MASK)
+ private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+ else
+ private->restriction = GIMP_ANCHOR_FEATURE_NONE;
+ }
+ }
+ else if (on_curve)
+ {
+ if (gimp_stroke_point_is_movable (stroke, anchor, position))
+ {
+ function = VECTORS_MOVE_CURVE;
+
+ if (state & TOGGLE_MASK)
+ private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+ else
+ private->restriction = GIMP_ANCHOR_FEATURE_NONE;
+ }
+ else
+ {
+ function = VECTORS_FINISHED;
+ }
+ }
+ else
+ {
+ if (private->sel_stroke &&
+ private->sel_anchor &&
+ gimp_stroke_is_extendable (private->sel_stroke,
+ private->sel_anchor) &&
+ ! (state & TOGGLE_MASK))
+ function = VECTORS_ADD_ANCHOR;
+ else
+ function = VECTORS_CREATE_STROKE;
+
+ private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+ private->modifier_lock = TRUE;
+ }
+
+ break;
+
+ case GIMP_VECTOR_MODE_EDIT:
+ if (! private->vectors)
+ {
+ if (on_vectors)
+ {
+ function = VECTORS_SELECT_VECTOR;
+ }
+ else
+ {
+ function = VECTORS_FINISHED;
+ }
+ }
+ else if (on_handle)
+ {
+ if (anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (! (state & TOGGLE_MASK) &&
+ private->sel_anchor &&
+ private->sel_anchor != anchor &&
+ gimp_stroke_is_extendable (private->sel_stroke,
+ private->sel_anchor) &&
+ gimp_stroke_is_extendable (stroke, anchor))
+ {
+ function = VECTORS_CONNECT_STROKES;
+ }
+ else
+ {
+ if (state & TOGGLE_MASK)
+ {
+ function = VECTORS_DELETE_ANCHOR;
+ }
+ else
+ {
+ if (private->polygonal)
+ function = VECTORS_MOVE_ANCHOR;
+ else
+ function = VECTORS_MOVE_HANDLE;
+ }
+ }
+ }
+ else
+ {
+ if (state & TOGGLE_MASK)
+ function = VECTORS_CONVERT_EDGE;
+ else
+ function = VECTORS_MOVE_HANDLE;
+ }
+ }
+ else if (on_curve)
+ {
+ if (state & TOGGLE_MASK)
+ {
+ function = VECTORS_DELETE_SEGMENT;
+ }
+ else if (gimp_stroke_anchor_is_insertable (stroke, anchor, position))
+ {
+ function = VECTORS_INSERT_ANCHOR;
+ }
+ else
+ {
+ function = VECTORS_FINISHED;
+ }
+ }
+ else
+ {
+ function = VECTORS_FINISHED;
+ }
+
+ break;
+
+ case GIMP_VECTOR_MODE_MOVE:
+ if (! private->vectors)
+ {
+ if (on_vectors)
+ {
+ function = VECTORS_SELECT_VECTOR;
+ }
+ else
+ {
+ function = VECTORS_FINISHED;
+ }
+ }
+ else if (on_handle || on_curve)
+ {
+ if (state & TOGGLE_MASK)
+ {
+ function = VECTORS_MOVE_VECTORS;
+ }
+ else
+ {
+ function = VECTORS_MOVE_STROKE;
+ }
+ }
+ else
+ {
+ if (on_vectors)
+ {
+ function = VECTORS_SELECT_VECTOR;
+ }
+ else
+ {
+ function = VECTORS_MOVE_VECTORS;
+ }
+ }
+ break;
+ }
+
+ return function;
+}
+
+static void
+gimp_tool_path_update_status (GimpToolPath *path,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPathPrivate *private = path->private;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+ const gchar *status = NULL;
+ gboolean free_status = FALSE;
+
+ if (! proximity)
+ {
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (path), NULL);
+ return;
+ }
+
+ switch (private->function)
+ {
+ case VECTORS_SELECT_VECTOR:
+ status = _("Click to pick path to edit");
+ break;
+
+ case VECTORS_CREATE_VECTOR:
+ status = _("Click to create a new path");
+ break;
+
+ case VECTORS_CREATE_STROKE:
+ status = _("Click to create a new component of the path");
+ break;
+
+ case VECTORS_ADD_ANCHOR:
+ status = gimp_suggest_modifiers (_("Click or Click-Drag to create "
+ "a new anchor"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case VECTORS_MOVE_ANCHOR:
+ if (private->edit_mode != GIMP_VECTOR_MODE_EDIT)
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to move the "
+ "anchor around"),
+ toggle_mask & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ }
+ else
+ status = _("Click-Drag to move the anchor around");
+ break;
+
+ case VECTORS_MOVE_ANCHORSET:
+ status = _("Click-Drag to move the anchors around");
+ break;
+
+ case VECTORS_MOVE_HANDLE:
+ if (private->restriction != GIMP_ANCHOR_FEATURE_SYMMETRIC)
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to move the "
+ "handle around"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ }
+ else
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to move the "
+ "handles around symmetrically"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ }
+ free_status = TRUE;
+ break;
+
+ case VECTORS_MOVE_CURVE:
+ if (private->polygonal)
+ status = gimp_suggest_modifiers (_("Click-Drag to move the "
+ "anchors around"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ else
+ status = gimp_suggest_modifiers (_("Click-Drag to change the "
+ "shape of the curve"),
+ extend_mask & ~state,
+ _("%s: symmetrical"), NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case VECTORS_MOVE_STROKE:
+ status = gimp_suggest_modifiers (_("Click-Drag to move the "
+ "component around"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case VECTORS_MOVE_VECTORS:
+ status = _("Click-Drag to move the path around");
+ break;
+
+ case VECTORS_INSERT_ANCHOR:
+ status = gimp_suggest_modifiers (_("Click-Drag to insert an anchor "
+ "on the path"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case VECTORS_DELETE_ANCHOR:
+ status = _("Click to delete this anchor");
+ break;
+
+ case VECTORS_CONNECT_STROKES:
+ status = _("Click to connect this anchor "
+ "with the selected endpoint");
+ break;
+
+ case VECTORS_DELETE_SEGMENT:
+ status = _("Click to open up the path");
+ break;
+
+ case VECTORS_CONVERT_EDGE:
+ status = _("Click to make this node angular");
+ break;
+
+ case VECTORS_FINISHED:
+ status = _("Clicking here does nothing, try clicking on path elements.");
+ break;
+ }
+
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (path), status);
+
+ if (free_status)
+ g_free ((gchar *) status);
+}
+
+static void
+gimp_tool_path_begin_change (GimpToolPath *path,
+ const gchar *desc)
+{
+ GimpToolPathPrivate *private = path->private;
+
+ g_return_if_fail (private->vectors != NULL);
+
+ /* don't push two undos */
+ if (private->have_undo)
+ return;
+
+ g_signal_emit (path, path_signals[BEGIN_CHANGE], 0,
+ desc);
+
+ private->have_undo = TRUE;
+}
+
+static void
+gimp_tool_path_end_change (GimpToolPath *path,
+ gboolean success)
+{
+ GimpToolPathPrivate *private = path->private;
+
+ private->have_undo = FALSE;
+ private->undo_motion = FALSE;
+
+ g_signal_emit (path, path_signals[END_CHANGE], 0,
+ success);
+}
+
+static void
+gimp_tool_path_vectors_visible (GimpVectors *vectors,
+ GimpToolPath *path)
+{
+ GimpToolPathPrivate *private = path->private;
+
+ gimp_canvas_item_set_visible (private->path,
+ ! gimp_item_get_visible (GIMP_ITEM (vectors)));
+}
+
+static void
+gimp_tool_path_vectors_freeze (GimpVectors *vectors,
+ GimpToolPath *path)
+{
+}
+
+static void
+gimp_tool_path_vectors_thaw (GimpVectors *vectors,
+ GimpToolPath *path)
+{
+ /* Ok, the vector might have changed externally (e.g. Undo) we need
+ * to validate our internal state.
+ */
+ gimp_tool_path_verify_state (path);
+ gimp_tool_path_changed (GIMP_TOOL_WIDGET (path));
+}
+
+static void
+gimp_tool_path_verify_state (GimpToolPath *path)
+{
+ GimpToolPathPrivate *private = path->private;
+ GimpStroke *cur_stroke = NULL;
+ gboolean cur_anchor_valid = FALSE;
+ gboolean cur_stroke_valid = FALSE;
+
+ private->sel_count = 0;
+ private->sel_anchor = NULL;
+ private->sel_stroke = NULL;
+
+ if (! private->vectors)
+ {
+ private->cur_position = -1;
+ private->cur_anchor = NULL;
+ private->cur_stroke = NULL;
+ return;
+ }
+
+ while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors,
+ cur_stroke)))
+ {
+ GList *anchors;
+ GList *list;
+
+ /* anchor handles */
+ anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+ if (cur_stroke == private->cur_stroke)
+ cur_stroke_valid = TRUE;
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ GimpAnchor *cur_anchor = list->data;
+
+ if (cur_anchor == private->cur_anchor)
+ cur_anchor_valid = TRUE;
+
+ if (cur_anchor->type == GIMP_ANCHOR_ANCHOR &&
+ cur_anchor->selected)
+ {
+ private->sel_count++;
+ if (private->sel_count == 1)
+ {
+ private->sel_anchor = cur_anchor;
+ private->sel_stroke = cur_stroke;
+ }
+ else
+ {
+ private->sel_anchor = NULL;
+ private->sel_stroke = NULL;
+ }
+ }
+ }
+
+ g_list_free (anchors);
+
+ anchors = gimp_stroke_get_draw_controls (cur_stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ GimpAnchor *cur_anchor = list->data;
+
+ if (cur_anchor == private->cur_anchor)
+ cur_anchor_valid = TRUE;
+ }
+
+ g_list_free (anchors);
+ }
+
+ if (! cur_stroke_valid)
+ private->cur_stroke = NULL;
+
+ if (! cur_anchor_valid)
+ private->cur_anchor = NULL;
+}
+
+static void
+gimp_tool_path_move_selected_anchors (GimpToolPath *path,
+ gdouble x,
+ gdouble y)
+{
+ GimpToolPathPrivate *private = path->private;
+ GimpAnchor *cur_anchor;
+ GimpStroke *cur_stroke = NULL;
+ GList *anchors;
+ GList *list;
+ GimpCoords offset = { 0.0, };
+
+ offset.x = x;
+ offset.y = y;
+
+ while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors,
+ cur_stroke)))
+ {
+ /* anchors */
+ anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ cur_anchor = GIMP_ANCHOR (list->data);
+
+ if (cur_anchor->selected)
+ gimp_stroke_anchor_move_relative (cur_stroke,
+ cur_anchor,
+ &offset,
+ GIMP_ANCHOR_FEATURE_NONE);
+ }
+
+ g_list_free (anchors);
+ }
+}
+
+static void
+gimp_tool_path_delete_selected_anchors (GimpToolPath *path)
+{
+ GimpToolPathPrivate *private = path->private;
+ GimpAnchor *cur_anchor;
+ GimpStroke *cur_stroke = NULL;
+ GList *anchors;
+ GList *list;
+ gboolean have_undo = FALSE;
+
+ gimp_vectors_freeze (private->vectors);
+
+ while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors,
+ cur_stroke)))
+ {
+ /* anchors */
+ anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ cur_anchor = GIMP_ANCHOR (list->data);
+
+ if (cur_anchor->selected)
+ {
+ if (! have_undo)
+ {
+ gimp_tool_path_begin_change (path, _("Delete Anchors"));
+ have_undo = TRUE;
+ }
+
+ gimp_stroke_anchor_delete (cur_stroke, cur_anchor);
+
+ if (gimp_stroke_is_empty (cur_stroke))
+ {
+ gimp_vectors_stroke_remove (private->vectors, cur_stroke);
+ cur_stroke = NULL;
+ }
+ }
+ }
+
+ g_list_free (anchors);
+ }
+
+ if (have_undo)
+ gimp_tool_path_end_change (path, TRUE);
+
+ gimp_vectors_thaw (private->vectors);
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_path_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_PATH,
+ "shell", shell,
+ NULL);
+}
+
+void
+gimp_tool_path_set_vectors (GimpToolPath *path,
+ GimpVectors *vectors)
+{
+ GimpToolPathPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_PATH (path));
+ g_return_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors));
+
+ private = path->private;
+
+ if (vectors == private->vectors)
+ return;
+
+ if (private->vectors)
+ {
+ g_signal_handlers_disconnect_by_func (private->vectors,
+ gimp_tool_path_vectors_visible,
+ path);
+ g_signal_handlers_disconnect_by_func (private->vectors,
+ gimp_tool_path_vectors_freeze,
+ path);
+ g_signal_handlers_disconnect_by_func (private->vectors,
+ gimp_tool_path_vectors_thaw,
+ path);
+
+ g_object_unref (private->vectors);
+ }
+
+ private->vectors = vectors;
+ private->function = VECTORS_FINISHED;
+ gimp_tool_path_verify_state (path);
+
+ if (private->vectors)
+ {
+ g_object_ref (private->vectors);
+
+ g_signal_connect_object (private->vectors, "visibility-changed",
+ G_CALLBACK (gimp_tool_path_vectors_visible),
+ path, 0);
+ g_signal_connect_object (private->vectors, "freeze",
+ G_CALLBACK (gimp_tool_path_vectors_freeze),
+ path, 0);
+ g_signal_connect_object (private->vectors, "thaw",
+ G_CALLBACK (gimp_tool_path_vectors_thaw),
+ path, 0);
+ }
+
+ g_object_notify (G_OBJECT (path), "vectors");
+}
diff --git a/app/display/gimptoolpath.h b/app/display/gimptoolpath.h
new file mode 100644
index 0000000..861d5e8
--- /dev/null
+++ b/app/display/gimptoolpath.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpath.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_PATH_H__
+#define __GIMP_TOOL_PATH_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_PATH (gimp_tool_path_get_type ())
+#define GIMP_TOOL_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PATH, GimpToolPath))
+#define GIMP_TOOL_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PATH, GimpToolPathClass))
+#define GIMP_IS_TOOL_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PATH))
+#define GIMP_IS_TOOL_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PATH))
+#define GIMP_TOOL_PATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PATH, GimpToolPathClass))
+
+
+typedef struct _GimpToolPath GimpToolPath;
+typedef struct _GimpToolPathPrivate GimpToolPathPrivate;
+typedef struct _GimpToolPathClass GimpToolPathClass;
+
+struct _GimpToolPath
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolPathPrivate *private;
+};
+
+struct _GimpToolPathClass
+{
+ GimpToolWidgetClass parent_class;
+
+ void (* begin_change) (GimpToolPath *path,
+ const gchar *desc);
+ void (* end_change) (GimpToolPath *path,
+ gboolean success);
+ void (* activate) (GimpToolPath *path,
+ GdkModifierType state);
+};
+
+
+GType gimp_tool_path_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_path_new (GimpDisplayShell *shell);
+
+void gimp_tool_path_set_vectors (GimpToolPath *path,
+ GimpVectors *vectors);
+
+
+#endif /* __GIMP_TOOL_PATH_H__ */
diff --git a/app/display/gimptoolpolygon.c b/app/display/gimptoolpolygon.c
new file mode 100644
index 0000000..4f2c20b
--- /dev/null
+++ b/app/display/gimptoolpolygon.c
@@ -0,0 +1,1495 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpolygon.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpFreeSelectTool
+ *
+ * Major improvement to support polygonal segments
+ * Copyright (C) 2008 Martin Nordholts
+ *
+ * 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpmarshal.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvasline.h"
+#include "gimpcanvaspolygon.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolpolygon.h"
+
+#include "gimp-intl.h"
+
+
+#define POINT_GRAB_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE / 2)
+#define POINT_SHOW_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE * 7)
+#define N_ITEMS_PER_ALLOC 1024
+#define INVALID_INDEX (-1)
+#define NO_CLICK_TIME_AVAILABLE 0
+
+
+enum
+{
+ CHANGE_COMPLETE,
+ LAST_SIGNAL
+};
+
+
+struct _GimpToolPolygonPrivate
+{
+ /* Index of grabbed segment index. */
+ gint grabbed_segment_index;
+
+ /* We need to keep track of a number of points when we move a
+ * segment vertex
+ */
+ GimpVector2 *saved_points_lower_segment;
+ GimpVector2 *saved_points_higher_segment;
+ gint max_n_saved_points_lower_segment;
+ gint max_n_saved_points_higher_segment;
+
+ /* Keeps track whether or not a modification of the polygon has been
+ * made between _button_press and _button_release
+ */
+ gboolean polygon_modified;
+
+ /* Point which is used to draw the polygon but which is not part of
+ * it yet
+ */
+ GimpVector2 pending_point;
+ gboolean show_pending_point;
+
+ /* The points of the polygon */
+ GimpVector2 *points;
+ gint max_n_points;
+
+ /* The number of points actually in use */
+ gint n_points;
+
+
+ /* Any int array containing the indices for the points in the
+ * polygon that connects different segments together
+ */
+ gint *segment_indices;
+ gint max_n_segment_indices;
+
+ /* The number of segment indices actually in use */
+ gint n_segment_indices;
+
+ /* Is the polygon closed? */
+ gboolean polygon_closed;
+
+ /* Whether or not to constrain the angle for newly created polygonal
+ * segments.
+ */
+ gboolean constrain_angle;
+
+ /* Whether or not to suppress handles (so that new segments can be
+ * created immediately after an existing segment vertex.
+ */
+ gboolean suppress_handles;
+
+ /* Last _oper_update or _motion coords */
+ gboolean hover;
+ GimpVector2 last_coords;
+
+ /* A double-click commits the selection, keep track of last
+ * click-time and click-position.
+ */
+ guint32 last_click_time;
+ GimpCoords last_click_coord;
+
+ /* Are we in a click-drag-release? */
+ gboolean button_down;
+
+ GimpCanvasItem *polygon;
+ GimpCanvasItem *pending_line;
+ GimpCanvasItem *closing_line;
+ GPtrArray *handles;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_polygon_constructed (GObject *object);
+static void gimp_tool_polygon_finalize (GObject *object);
+static void gimp_tool_polygon_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_polygon_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_polygon_changed (GimpToolWidget *widget);
+static gint gimp_tool_polygon_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_polygon_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_polygon_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_polygon_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_polygon_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_polygon_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_polygon_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_polygon_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static void gimp_tool_polygon_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_polygon_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_polygon_change_complete (GimpToolPolygon *polygon);
+
+static gint gimp_tool_polygon_get_segment_index (GimpToolPolygon *polygon,
+ const GimpCoords *coords);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolPolygon, gimp_tool_polygon,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_polygon_parent_class
+
+static guint polygon_signals[LAST_SIGNAL] = { 0, };
+
+static const GimpVector2 vector2_zero = { 0.0, 0.0 };
+
+
+static void
+gimp_tool_polygon_class_init (GimpToolPolygonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_polygon_constructed;
+ object_class->finalize = gimp_tool_polygon_finalize;
+ object_class->set_property = gimp_tool_polygon_set_property;
+ object_class->get_property = gimp_tool_polygon_get_property;
+
+ widget_class->changed = gimp_tool_polygon_changed;
+ widget_class->button_press = gimp_tool_polygon_button_press;
+ widget_class->button_release = gimp_tool_polygon_button_release;
+ widget_class->motion = gimp_tool_polygon_motion;
+ widget_class->hit = gimp_tool_polygon_hit;
+ widget_class->hover = gimp_tool_polygon_hover;
+ widget_class->leave_notify = gimp_tool_polygon_leave_notify;
+ widget_class->key_press = gimp_tool_polygon_key_press;
+ widget_class->motion_modifier = gimp_tool_polygon_motion_modifier;
+ widget_class->hover_modifier = gimp_tool_polygon_hover_modifier;
+ widget_class->get_cursor = gimp_tool_polygon_get_cursor;
+
+ polygon_signals[CHANGE_COMPLETE] =
+ g_signal_new ("change-complete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolPolygonClass, change_complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gimp_tool_polygon_init (GimpToolPolygon *polygon)
+{
+ polygon->private = gimp_tool_polygon_get_instance_private (polygon);
+
+ polygon->private->grabbed_segment_index = INVALID_INDEX;
+ polygon->private->last_click_time = NO_CLICK_TIME_AVAILABLE;
+}
+
+static void
+gimp_tool_polygon_constructed (GObject *object)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolPolygonPrivate *private = polygon->private;
+ GimpCanvasGroup *stroke_group;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ stroke_group = gimp_tool_widget_add_stroke_group (widget);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->polygon = gimp_tool_widget_add_polygon (widget, NULL,
+ NULL, 0, FALSE);
+
+ private->pending_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0);
+ private->closing_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0);
+
+ gimp_tool_widget_pop_group (widget);
+
+ private->handles = g_ptr_array_new ();
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static void
+gimp_tool_polygon_finalize (GObject *object)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (object);
+ GimpToolPolygonPrivate *private = polygon->private;
+
+ g_free (private->points);
+ g_free (private->segment_indices);
+ g_free (private->saved_points_lower_segment);
+ g_free (private->saved_points_higher_segment);
+
+ g_clear_pointer (&private->handles, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_polygon_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_polygon_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_polygon_get_segment (GimpToolPolygon *polygon,
+ GimpVector2 **points,
+ gint *n_points,
+ gint segment_start,
+ gint segment_end)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ *points = &priv->points[priv->segment_indices[segment_start]];
+ *n_points = priv->segment_indices[segment_end] -
+ priv->segment_indices[segment_start] +
+ 1;
+}
+
+static void
+gimp_tool_polygon_get_segment_point (GimpToolPolygon *polygon,
+ gdouble *start_point_x,
+ gdouble *start_point_y,
+ gint segment_index)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ *start_point_x = priv->points[priv->segment_indices[segment_index]].x;
+ *start_point_y = priv->points[priv->segment_indices[segment_index]].y;
+}
+
+static gboolean
+gimp_tool_polygon_should_close (GimpToolPolygon *polygon,
+ guint32 time,
+ const GimpCoords *coords)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (polygon);
+ GimpToolPolygonPrivate *priv = polygon->private;
+ gboolean double_click = FALSE;
+ gdouble dist;
+
+ if (priv->polygon_modified ||
+ priv->n_segment_indices < 1 ||
+ priv->n_points < 3 ||
+ priv->polygon_closed)
+ return FALSE;
+
+ dist = gimp_canvas_item_transform_distance_square (priv->polygon,
+ coords->x,
+ coords->y,
+ priv->points[0].x,
+ priv->points[0].y);
+
+ /* Handle double-click. It must be within GTK+ global double-click
+ * time since last click, and it must be within GTK+ global
+ * double-click distance away from the last point
+ */
+ if (time != NO_CLICK_TIME_AVAILABLE)
+ {
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (shell));
+ gint double_click_time;
+ gint double_click_distance;
+ gint click_time_passed;
+ gdouble dist_from_last_point;
+
+ click_time_passed = time - priv->last_click_time;
+
+ dist_from_last_point =
+ gimp_canvas_item_transform_distance_square (priv->polygon,
+ coords->x,
+ coords->y,
+ priv->last_click_coord.x,
+ priv->last_click_coord.y);
+
+ g_object_get (settings,
+ "gtk-double-click-time", &double_click_time,
+ "gtk-double-click-distance", &double_click_distance,
+ NULL);
+
+ double_click = click_time_passed < double_click_time &&
+ dist_from_last_point < double_click_distance;
+ }
+
+ return ((! priv->suppress_handles && dist < POINT_GRAB_THRESHOLD_SQ) ||
+ double_click);
+}
+
+static void
+gimp_tool_polygon_revert_to_last_segment (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->n_points = priv->segment_indices[priv->n_segment_indices - 1] + 1;
+}
+
+static void
+gimp_tool_polygon_remove_last_segment (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (priv->polygon_closed)
+ {
+ priv->polygon_closed = FALSE;
+
+ gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon));
+
+ return;
+ }
+
+ if (priv->n_segment_indices > 0)
+ {
+ GimpCanvasItem *handle;
+
+ priv->n_segment_indices--;
+
+ handle = g_ptr_array_index (priv->handles,
+ priv->n_segment_indices);
+
+ gimp_tool_widget_remove_item (GIMP_TOOL_WIDGET (polygon), handle);
+ g_ptr_array_remove (priv->handles, handle);
+ }
+
+ if (priv->n_segment_indices <= 0)
+ {
+ priv->grabbed_segment_index = INVALID_INDEX;
+ priv->show_pending_point = FALSE;
+ priv->n_points = 0;
+ priv->n_segment_indices = 0;
+
+ gimp_tool_widget_response (GIMP_TOOL_WIDGET (polygon),
+ GIMP_TOOL_WIDGET_RESPONSE_CANCEL);
+ }
+ else
+ {
+ gimp_tool_polygon_revert_to_last_segment (polygon);
+
+ gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon));
+ }
+}
+
+static void
+gimp_tool_polygon_add_point (GimpToolPolygon *polygon,
+ gdouble x,
+ gdouble y)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (priv->n_points >= priv->max_n_points)
+ {
+ priv->max_n_points += N_ITEMS_PER_ALLOC;
+
+ priv->points = g_realloc (priv->points,
+ sizeof (GimpVector2) * priv->max_n_points);
+ }
+
+ priv->points[priv->n_points].x = x;
+ priv->points[priv->n_points].y = y;
+
+ priv->n_points++;
+}
+
+static void
+gimp_tool_polygon_add_segment_index (GimpToolPolygon *polygon,
+ gint index)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (priv->n_segment_indices >= priv->max_n_segment_indices)
+ {
+ priv->max_n_segment_indices += N_ITEMS_PER_ALLOC;
+
+ priv->segment_indices = g_realloc (priv->segment_indices,
+ sizeof (GimpVector2) *
+ priv->max_n_segment_indices);
+ }
+
+ priv->segment_indices[priv->n_segment_indices] = index;
+
+ g_ptr_array_add (priv->handles,
+ gimp_tool_widget_add_handle (GIMP_TOOL_WIDGET (polygon),
+ GIMP_HANDLE_CROSS,
+ 0, 0,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER));
+
+ priv->n_segment_indices++;
+}
+
+static gboolean
+gimp_tool_polygon_is_point_grabbed (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ return priv->grabbed_segment_index != INVALID_INDEX;
+}
+
+static void
+gimp_tool_polygon_fit_segment (GimpToolPolygon *polygon,
+ GimpVector2 *dest_points,
+ GimpVector2 dest_start_target,
+ GimpVector2 dest_end_target,
+ const GimpVector2 *source_points,
+ gint n_points)
+{
+ GimpVector2 origo_translation_offset;
+ GimpVector2 untranslation_offset;
+ gdouble rotation;
+ gdouble scale;
+
+ /* Handle some quick special cases */
+ if (n_points <= 0)
+ {
+ return;
+ }
+ else if (n_points == 1)
+ {
+ dest_points[0] = dest_end_target;
+ return;
+ }
+ else if (n_points == 2)
+ {
+ dest_points[0] = dest_start_target;
+ dest_points[1] = dest_end_target;
+ return;
+ }
+
+ /* Copy from source to dest; we work on the dest data */
+ memcpy (dest_points, source_points, sizeof (GimpVector2) * n_points);
+
+ /* Transform the destination end point */
+ {
+ GimpVector2 *dest_end;
+ GimpVector2 origo_translated_end_target;
+ gdouble target_rotation;
+ gdouble current_rotation;
+ gdouble target_length;
+ gdouble current_length;
+
+ dest_end = &dest_points[n_points - 1];
+
+ /* Transate to origin */
+ gimp_vector2_sub (&origo_translation_offset,
+ &vector2_zero,
+ &dest_points[0]);
+ gimp_vector2_add (dest_end,
+ dest_end,
+ &origo_translation_offset);
+
+ /* Calculate origo_translated_end_target */
+ gimp_vector2_sub (&origo_translated_end_target,
+ &dest_end_target,
+ &dest_start_target);
+
+ /* Rotate */
+ target_rotation = atan2 (vector2_zero.y - origo_translated_end_target.y,
+ vector2_zero.x - origo_translated_end_target.x);
+ current_rotation = atan2 (vector2_zero.y - dest_end->y,
+ vector2_zero.x - dest_end->x);
+ rotation = current_rotation - target_rotation;
+
+ gimp_vector2_rotate (dest_end, rotation);
+
+
+ /* Scale */
+ target_length = gimp_vector2_length (&origo_translated_end_target);
+ current_length = gimp_vector2_length (dest_end);
+ scale = target_length / current_length;
+
+ gimp_vector2_mul (dest_end, scale);
+
+
+ /* Untranslate */
+ gimp_vector2_sub (&untranslation_offset,
+ &dest_end_target,
+ dest_end);
+ gimp_vector2_add (dest_end,
+ dest_end,
+ &untranslation_offset);
+ }
+
+ /* Do the same transformation for the rest of the points */
+ {
+ gint i;
+
+ for (i = 0; i < n_points - 1; i++)
+ {
+ /* Translate */
+ gimp_vector2_add (&dest_points[i],
+ &origo_translation_offset,
+ &dest_points[i]);
+
+ /* Rotate */
+ gimp_vector2_rotate (&dest_points[i],
+ rotation);
+
+ /* Scale */
+ gimp_vector2_mul (&dest_points[i],
+ scale);
+
+ /* Untranslate */
+ gimp_vector2_add (&dest_points[i],
+ &dest_points[i],
+ &untranslation_offset);
+ }
+ }
+}
+
+static void
+gimp_tool_polygon_move_segment_vertex_to (GimpToolPolygon *polygon,
+ gint segment_index,
+ gdouble new_x,
+ gdouble new_y)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ GimpVector2 cursor_point = { new_x, new_y };
+ GimpVector2 *dest;
+ GimpVector2 *dest_start_target;
+ GimpVector2 *dest_end_target;
+ gint n_points;
+
+ /* Handle the segment before the grabbed point */
+ if (segment_index > 0)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index - 1,
+ priv->grabbed_segment_index);
+
+ dest_start_target = &dest[0];
+ dest_end_target = &cursor_point;
+
+ gimp_tool_polygon_fit_segment (polygon,
+ dest,
+ *dest_start_target,
+ *dest_end_target,
+ priv->saved_points_lower_segment,
+ n_points);
+ }
+
+ /* Handle the segment after the grabbed point */
+ if (segment_index < priv->n_segment_indices - 1)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index,
+ priv->grabbed_segment_index + 1);
+
+ dest_start_target = &cursor_point;
+ dest_end_target = &dest[n_points - 1];
+
+ gimp_tool_polygon_fit_segment (polygon,
+ dest,
+ *dest_start_target,
+ *dest_end_target,
+ priv->saved_points_higher_segment,
+ n_points);
+ }
+
+ /* Handle when there only is one point */
+ if (segment_index == 0 &&
+ priv->n_segment_indices == 1)
+ {
+ priv->points[0].x = new_x;
+ priv->points[0].y = new_y;
+ }
+}
+
+static void
+gimp_tool_polygon_revert_to_saved_state (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ GimpVector2 *dest;
+ gint n_points;
+
+ /* Without a point grab we have no sensible information to fall back
+ * on, bail out
+ */
+ if (! gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ return;
+ }
+
+ if (priv->grabbed_segment_index > 0)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index - 1,
+ priv->grabbed_segment_index);
+
+ memcpy (dest,
+ priv->saved_points_lower_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ if (priv->grabbed_segment_index < priv->n_segment_indices - 1)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index,
+ priv->grabbed_segment_index + 1);
+
+ memcpy (dest,
+ priv->saved_points_higher_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ if (priv->grabbed_segment_index == 0 &&
+ priv->n_segment_indices == 1)
+ {
+ priv->points[0] = *priv->saved_points_lower_segment;
+ }
+}
+
+static void
+gimp_tool_polygon_prepare_for_move (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ GimpVector2 *source;
+ gint n_points;
+
+ if (priv->grabbed_segment_index > 0)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &source,
+ &n_points,
+ priv->grabbed_segment_index - 1,
+ priv->grabbed_segment_index);
+
+ if (n_points > priv->max_n_saved_points_lower_segment)
+ {
+ priv->max_n_saved_points_lower_segment = n_points;
+
+ priv->saved_points_lower_segment =
+ g_realloc (priv->saved_points_lower_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ memcpy (priv->saved_points_lower_segment,
+ source,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ if (priv->grabbed_segment_index < priv->n_segment_indices - 1)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &source,
+ &n_points,
+ priv->grabbed_segment_index,
+ priv->grabbed_segment_index + 1);
+
+ if (n_points > priv->max_n_saved_points_higher_segment)
+ {
+ priv->max_n_saved_points_higher_segment = n_points;
+
+ priv->saved_points_higher_segment =
+ g_realloc (priv->saved_points_higher_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ memcpy (priv->saved_points_higher_segment,
+ source,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ /* A special-case when there only is one point */
+ if (priv->grabbed_segment_index == 0 &&
+ priv->n_segment_indices == 1)
+ {
+ if (priv->max_n_saved_points_lower_segment == 0)
+ {
+ priv->max_n_saved_points_lower_segment = 1;
+
+ priv->saved_points_lower_segment = g_new0 (GimpVector2, 1);
+ }
+
+ *priv->saved_points_lower_segment = priv->points[0];
+ }
+}
+
+static void
+gimp_tool_polygon_update_motion (GimpToolPolygon *polygon,
+ gdouble new_x,
+ gdouble new_y)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ priv->polygon_modified = TRUE;
+
+ if (priv->constrain_angle &&
+ priv->n_segment_indices > 1)
+ {
+ gdouble start_point_x;
+ gdouble start_point_y;
+ gint segment_index;
+
+ /* Base constraints on the last segment vertex if we move
+ * the first one, otherwise base on the previous segment
+ * vertex
+ */
+ if (priv->grabbed_segment_index == 0)
+ {
+ segment_index = priv->n_segment_indices - 1;
+ }
+ else
+ {
+ segment_index = priv->grabbed_segment_index - 1;
+ }
+
+ gimp_tool_polygon_get_segment_point (polygon,
+ &start_point_x,
+ &start_point_y,
+ segment_index);
+
+ gimp_display_shell_constrain_line (
+ gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (polygon)),
+ start_point_x,
+ start_point_y,
+ &new_x,
+ &new_y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+
+ gimp_tool_polygon_move_segment_vertex_to (polygon,
+ priv->grabbed_segment_index,
+ new_x,
+ new_y);
+
+ /* We also must update the pending point if we are moving the
+ * first point
+ */
+ if (priv->grabbed_segment_index == 0)
+ {
+ priv->pending_point.x = new_x;
+ priv->pending_point.y = new_y;
+ }
+ }
+ else
+ {
+ /* Don't show the pending point while we are adding points */
+ priv->show_pending_point = FALSE;
+
+ gimp_tool_polygon_add_point (polygon, new_x, new_y);
+ }
+}
+
+static void
+gimp_tool_polygon_status_update (GimpToolPolygon *polygon,
+ const GimpCoords *coords,
+ gboolean proximity)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (polygon);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (proximity)
+ {
+ const gchar *status_text = NULL;
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ if (gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords))
+ {
+ status_text = _("Click to close shape");
+ }
+ else
+ {
+ status_text = _("Click-Drag to move segment vertex");
+ }
+ }
+ else if (priv->polygon_closed)
+ {
+ status_text = _("Return commits, Escape cancels, Backspace re-opens shape");
+ }
+ else if (priv->n_segment_indices >= 3)
+ {
+ status_text = _("Return commits, Escape cancels, Backspace removes last segment");
+ }
+ else
+ {
+ status_text = _("Click-Drag adds a free segment, Click adds a polygonal segment");
+ }
+
+ if (status_text)
+ {
+ gimp_tool_widget_set_status (widget, status_text);
+ }
+ }
+ else
+ {
+ gimp_tool_widget_set_status (widget, NULL);
+ }
+}
+
+static void
+gimp_tool_polygon_changed (GimpToolWidget *widget)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *private = polygon->private;
+ gboolean hovering_first_point = FALSE;
+ gboolean handles_wants_to_show = FALSE;
+ GimpCoords coords = { private->last_coords.x,
+ private->last_coords.y,
+ /* pad with 0 */ };
+ gint i;
+
+ gimp_canvas_polygon_set_points (private->polygon,
+ private->points, private->n_points);
+
+ if (private->show_pending_point)
+ {
+ GimpVector2 last = private->points[private->n_points - 1];
+
+ gimp_canvas_line_set (private->pending_line,
+ last.x,
+ last.y,
+ private->pending_point.x,
+ private->pending_point.y);
+ }
+
+ gimp_canvas_item_set_visible (private->pending_line,
+ private->show_pending_point);
+
+ if (private->polygon_closed)
+ {
+ GimpVector2 first = private->points[0];
+ GimpVector2 last = private->points[private->n_points - 1];
+
+ gimp_canvas_line_set (private->closing_line,
+ first.x, first.y,
+ last.x, last.y);
+ }
+
+ gimp_canvas_item_set_visible (private->closing_line,
+ private->polygon_closed);
+
+ hovering_first_point =
+ gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ &coords);
+
+ /* We always show the handle for the first point, even with button1
+ * down, since releasing the button on the first point will close
+ * the polygon, so it's a significant state which we must give
+ * feedback for
+ */
+ handles_wants_to_show = (hovering_first_point || ! private->button_down);
+
+ for (i = 0; i < private->n_segment_indices; i++)
+ {
+ GimpCanvasItem *handle;
+ GimpVector2 *point;
+
+ handle = g_ptr_array_index (private->handles, i);
+ point = &private->points[private->segment_indices[i]];
+
+ if (private->hover &&
+ handles_wants_to_show &&
+ ! private->suppress_handles &&
+
+ /* If the first point is hovered while button1 is held down,
+ * only draw the first handle, the other handles are not
+ * relevant (see comment a few lines up)
+ */
+ (i == 0 ||
+ ! (private->button_down || hovering_first_point)))
+ {
+ gdouble dist;
+ GimpHandleType handle_type = -1;
+
+ dist =
+ gimp_canvas_item_transform_distance_square (handle,
+ private->last_coords.x,
+ private->last_coords.y,
+ point->x,
+ point->y);
+
+ /* If the cursor is over the point, fill, if it's just
+ * close, draw an outline
+ */
+ if (dist < POINT_GRAB_THRESHOLD_SQ)
+ handle_type = GIMP_HANDLE_FILLED_CIRCLE;
+ else if (dist < POINT_SHOW_THRESHOLD_SQ)
+ handle_type = GIMP_HANDLE_CIRCLE;
+
+ if (handle_type != -1)
+ {
+ gint size;
+
+ gimp_canvas_item_begin_change (handle);
+
+ gimp_canvas_handle_set_position (handle, point->x, point->y);
+
+ g_object_set (handle,
+ "type", handle_type,
+ NULL);
+
+ size = gimp_canvas_handle_calc_size (handle,
+ private->last_coords.x,
+ private->last_coords.y,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE);
+ if (FALSE)
+ gimp_canvas_handle_set_size (handle, size, size);
+
+ if (dist < POINT_GRAB_THRESHOLD_SQ)
+ gimp_canvas_item_set_highlight (handle, TRUE);
+ else
+ gimp_canvas_item_set_highlight (handle, FALSE);
+
+ gimp_canvas_item_set_visible (handle, TRUE);
+
+ gimp_canvas_item_end_change (handle);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (handle, FALSE);
+ }
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (handle, FALSE);
+ }
+ }
+}
+
+static gboolean
+gimp_tool_polygon_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ gimp_tool_polygon_prepare_for_move (polygon);
+ }
+ else if (priv->polygon_closed)
+ {
+ if (press_type == GIMP_BUTTON_PRESS_DOUBLE &&
+ gimp_canvas_item_hit (priv->polygon, coords->x, coords->y))
+ {
+ gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CONFIRM);
+ }
+
+ return 0;
+ }
+ else
+ {
+ GimpVector2 point_to_add;
+
+ /* Note that we add the pending point (unless it is the first
+ * point we add) because the pending point is setup correctly
+ * with regards to angle constraints.
+ */
+ if (priv->n_points > 0)
+ {
+ point_to_add = priv->pending_point;
+ }
+ else
+ {
+ point_to_add.x = coords->x;
+ point_to_add.y = coords->y;
+ }
+
+ /* No point was grabbed, add a new point and mark this as a
+ * segment divider. For a line segment, this will be the only
+ * new point. For a free segment, this will be the first point
+ * of the free segment.
+ */
+ gimp_tool_polygon_add_point (polygon,
+ point_to_add.x,
+ point_to_add.y);
+ gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1);
+ }
+
+ priv->button_down = TRUE;
+
+ gimp_tool_polygon_changed (widget);
+
+ return 1;
+}
+
+static void
+gimp_tool_polygon_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ g_object_ref (widget);
+
+ priv->button_down = FALSE;
+
+ switch (release_type)
+ {
+ case GIMP_BUTTON_RELEASE_CLICK:
+ case GIMP_BUTTON_RELEASE_NO_MOTION:
+ /* If a click was made, we don't consider the polygon modified */
+ priv->polygon_modified = FALSE;
+
+ /* First finish of the line segment if no point was grabbed */
+ if (! gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ /* Revert any free segment points that might have been added */
+ gimp_tool_polygon_revert_to_last_segment (polygon);
+ }
+
+ /* After the segments are up to date and we have handled
+ * double-click, see if it's committing time
+ */
+ if (gimp_tool_polygon_should_close (polygon, time, coords))
+ {
+ /* We can get a click notification even though the end point
+ * has been moved a few pixels. Since a move will change the
+ * free selection, revert it before doing the commit.
+ */
+ gimp_tool_polygon_revert_to_saved_state (polygon);
+
+ priv->polygon_closed = TRUE;
+
+ gimp_tool_polygon_change_complete (polygon);
+ }
+
+ priv->last_click_time = time;
+ priv->last_click_coord = *coords;
+ break;
+
+ case GIMP_BUTTON_RELEASE_NORMAL:
+ /* First finish of the free segment if no point was grabbed */
+ if (! gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ /* The points are all setup, just make a segment */
+ gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1);
+ }
+
+ /* After the segments are up to date, see if it's committing time */
+ if (gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords))
+ {
+ priv->polygon_closed = TRUE;
+ }
+
+ gimp_tool_polygon_change_complete (polygon);
+ break;
+
+ case GIMP_BUTTON_RELEASE_CANCEL:
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ gimp_tool_polygon_revert_to_saved_state (polygon);
+ else
+ gimp_tool_polygon_remove_last_segment (polygon);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Reset */
+ priv->polygon_modified = FALSE;
+
+ gimp_tool_polygon_changed (widget);
+
+ g_object_unref (widget);
+}
+
+static void
+gimp_tool_polygon_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->last_coords.x = coords->x;
+ priv->last_coords.y = coords->y;
+
+ gimp_tool_polygon_update_motion (polygon,
+ coords->x,
+ coords->y);
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static GimpHit
+gimp_tool_polygon_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if ((priv->n_points > 0 && ! priv->polygon_closed) ||
+ gimp_tool_polygon_get_segment_index (polygon, coords) != INVALID_INDEX)
+ {
+ return GIMP_HIT_DIRECT;
+ }
+ else if (priv->polygon_closed &&
+ gimp_canvas_item_hit (priv->polygon, coords->x, coords->y))
+ {
+ return GIMP_HIT_INDIRECT;
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+static void
+gimp_tool_polygon_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+ gboolean hovering_first_point;
+
+ priv->grabbed_segment_index = gimp_tool_polygon_get_segment_index (polygon,
+ coords);
+ priv->hover = TRUE;
+
+ hovering_first_point =
+ gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords);
+
+ priv->last_coords.x = coords->x;
+ priv->last_coords.y = coords->y;
+
+ if (priv->n_points == 0 ||
+ priv->polygon_closed ||
+ (gimp_tool_polygon_is_point_grabbed (polygon) &&
+ ! hovering_first_point) ||
+ ! proximity)
+ {
+ priv->show_pending_point = FALSE;
+ }
+ else
+ {
+ priv->show_pending_point = TRUE;
+
+ if (hovering_first_point)
+ {
+ priv->pending_point = priv->points[0];
+ }
+ else
+ {
+ priv->pending_point.x = coords->x;
+ priv->pending_point.y = coords->y;
+
+ if (priv->constrain_angle && priv->n_points > 0)
+ {
+ gdouble start_point_x;
+ gdouble start_point_y;
+
+ /* the last point is the line's start point */
+ gimp_tool_polygon_get_segment_point (polygon,
+ &start_point_x,
+ &start_point_y,
+ priv->n_segment_indices - 1);
+
+ gimp_display_shell_constrain_line (
+ gimp_tool_widget_get_shell (widget),
+ start_point_x, start_point_y,
+ &priv->pending_point.x,
+ &priv->pending_point.y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+ }
+ }
+
+ gimp_tool_polygon_status_update (polygon, coords, proximity);
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static void
+gimp_tool_polygon_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->grabbed_segment_index = INVALID_INDEX;
+ priv->hover = FALSE;
+ priv->show_pending_point = FALSE;
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static gboolean
+gimp_tool_polygon_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ gimp_tool_polygon_remove_last_segment (polygon);
+
+ if (priv->n_segment_indices > 0)
+ gimp_tool_polygon_change_complete (polygon);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
+}
+
+static void
+gimp_tool_polygon_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ?
+ TRUE : FALSE);
+
+ /* If we didn't came here due to a mouse release, immediately update
+ * the position of the thing we move.
+ */
+ if (state & GDK_BUTTON1_MASK)
+ {
+ gimp_tool_polygon_update_motion (polygon,
+ priv->last_coords.x,
+ priv->last_coords.y);
+ }
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static void
+gimp_tool_polygon_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ?
+ TRUE : FALSE);
+
+ priv->suppress_handles = ((state & gimp_get_extend_selection_mask ()) ?
+ TRUE : FALSE);
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static gboolean
+gimp_tool_polygon_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon) &&
+ ! gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords))
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_tool_polygon_change_complete (GimpToolPolygon *polygon)
+{
+ g_signal_emit (polygon, polygon_signals[CHANGE_COMPLETE], 0);
+}
+
+static gint
+gimp_tool_polygon_get_segment_index (GimpToolPolygon *polygon,
+ const GimpCoords *coords)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ gint segment_index = INVALID_INDEX;
+
+ if (! priv->suppress_handles)
+ {
+ gdouble shortest_dist = POINT_GRAB_THRESHOLD_SQ;
+ gint i;
+
+ for (i = 0; i < priv->n_segment_indices; i++)
+ {
+ gdouble dist;
+ GimpVector2 *point;
+
+ point = &priv->points[priv->segment_indices[i]];
+
+ dist = gimp_canvas_item_transform_distance_square (priv->polygon,
+ coords->x,
+ coords->y,
+ point->x,
+ point->y);
+
+ if (dist < shortest_dist)
+ {
+ shortest_dist = dist;
+
+ segment_index = i;
+ }
+ }
+ }
+
+ return segment_index;
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_polygon_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_POLYGON,
+ "shell", shell,
+ NULL);
+}
+
+gboolean
+gimp_tool_polygon_is_closed (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_POLYGON (polygon), FALSE);
+
+ private = polygon->private;
+
+ return private->polygon_closed;
+}
+
+void
+gimp_tool_polygon_get_points (GimpToolPolygon *polygon,
+ const GimpVector2 **points,
+ gint *n_points)
+{
+ GimpToolPolygonPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_POLYGON (polygon));
+
+ private = polygon->private;
+
+ if (points) *points = private->points;
+ if (n_points) *n_points = private->n_points;
+}
diff --git a/app/display/gimptoolpolygon.h b/app/display/gimptoolpolygon.h
new file mode 100644
index 0000000..fa3560a
--- /dev/null
+++ b/app/display/gimptoolpolygon.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpolygon.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_POLYGON_H__
+#define __GIMP_TOOL_POLYGON_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_POLYGON (gimp_tool_polygon_get_type ())
+#define GIMP_TOOL_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_POLYGON, GimpToolPolygon))
+#define GIMP_TOOL_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_POLYGON, GimpToolPolygonClass))
+#define GIMP_IS_TOOL_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_POLYGON))
+#define GIMP_IS_TOOL_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_POLYGON))
+#define GIMP_TOOL_POLYGON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_POLYGON, GimpToolPolygonClass))
+
+
+typedef struct _GimpToolPolygon GimpToolPolygon;
+typedef struct _GimpToolPolygonPrivate GimpToolPolygonPrivate;
+typedef struct _GimpToolPolygonClass GimpToolPolygonClass;
+
+struct _GimpToolPolygon
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolPolygonPrivate *private;
+};
+
+struct _GimpToolPolygonClass
+{
+ GimpToolWidgetClass parent_class;
+
+ /* signals */
+ void (* change_complete) (GimpToolPolygon *polygon);
+};
+
+
+GType gimp_tool_polygon_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_polygon_new (GimpDisplayShell *shell);
+
+gboolean gimp_tool_polygon_is_closed (GimpToolPolygon *polygon);
+void gimp_tool_polygon_get_points (GimpToolPolygon *polygon,
+ const GimpVector2 **points,
+ gint *n_points);
+
+
+#endif /* __GIMP_TOOL_POLYGON_H__ */
diff --git a/app/display/gimptoolrectangle.c b/app/display/gimptoolrectangle.c
new file mode 100644
index 0000000..c1192db
--- /dev/null
+++ b/app/display/gimptoolrectangle.c
@@ -0,0 +1,4256 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolrectangle.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpRectangleTool
+ * Copyright (C) 2007 Martin Nordholts
+ *
+ * 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimpmarshal.h"
+#include "core/gimppickable.h"
+#include "core/gimppickable-auto-shrink.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvasarc.h"
+#include "gimpcanvascorner.h"
+#include "gimpcanvashandle.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpcanvasrectangle.h"
+#include "gimpcanvasrectangleguides.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-scroll.h"
+#include "gimptoolrectangle.h"
+
+#include "gimp-intl.h"
+
+
+/* speed of key movement */
+#define ARROW_VELOCITY 25
+
+#define MAX_HANDLE_SIZE 50
+#define MIN_HANDLE_SIZE 15
+#define NARROW_MODE_HANDLE_SIZE 15
+#define NARROW_MODE_THRESHOLD 45
+
+
+enum
+{
+ PROP_0,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ PROP_CONSTRAINT,
+ PROP_PRECISION,
+ PROP_NARROW_MODE,
+ PROP_FORCE_NARROW_MODE,
+ PROP_DRAW_ELLIPSE,
+ PROP_ROUND_CORNERS,
+ PROP_CORNER_RADIUS,
+ PROP_STATUS_TITLE,
+
+ PROP_HIGHLIGHT,
+ PROP_HIGHLIGHT_OPACITY,
+ PROP_GUIDE,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_FIXED_RULE_ACTIVE,
+ PROP_FIXED_RULE,
+ PROP_DESIRED_FIXED_WIDTH,
+ PROP_DESIRED_FIXED_HEIGHT,
+ PROP_DESIRED_FIXED_SIZE_WIDTH,
+ PROP_DESIRED_FIXED_SIZE_HEIGHT,
+ PROP_ASPECT_NUMERATOR,
+ PROP_ASPECT_DENOMINATOR,
+ PROP_FIXED_CENTER
+};
+
+enum
+{
+ CHANGE_COMPLETE,
+ LAST_SIGNAL
+};
+
+typedef enum
+{
+ CLAMPED_NONE = 0,
+ CLAMPED_LEFT = 1 << 0,
+ CLAMPED_RIGHT = 1 << 1,
+ CLAMPED_TOP = 1 << 2,
+ CLAMPED_BOTTOM = 1 << 3
+} ClampedSide;
+
+typedef enum
+{
+ SIDE_TO_RESIZE_NONE,
+ SIDE_TO_RESIZE_LEFT,
+ SIDE_TO_RESIZE_RIGHT,
+ SIDE_TO_RESIZE_TOP,
+ SIDE_TO_RESIZE_BOTTOM,
+ SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY,
+ SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY,
+} SideToResize;
+
+
+#define FEQUAL(a,b) (fabs ((a) - (b)) < 0.0001)
+#define PIXEL_FEQUAL(a,b) (fabs ((a) - (b)) < 0.5)
+
+
+struct _GimpToolRectanglePrivate
+{
+ /* The following members are "constants", that is, variables that are setup
+ * during gimp_tool_rectangle_button_press and then only read.
+ */
+
+ /* Whether or not the rectangle currently being rubber-banded is the
+ * first one created with this instance, this determines if we can
+ * undo it on button_release.
+ */
+ gboolean is_first;
+
+ /* Whether or not the rectangle currently being rubber-banded was
+ * created from scratch.
+ */
+ gboolean is_new;
+
+ /* Holds the coordinate that should be used as the "other side" when
+ * fixed-center is turned off.
+ */
+ gdouble other_side_x;
+ gdouble other_side_y;
+
+ /* Holds the coordinate to be used as center when fixed-center is used. */
+ gdouble center_x_on_fixed_center;
+ gdouble center_y_on_fixed_center;
+
+ /* True when the rectangle is being adjusted (moved or
+ * rubber-banded).
+ */
+ gboolean rect_adjusting;
+
+
+ /* The rest of the members are internal state variables, that is, variables
+ * that might change during the manipulation session of the rectangle. Make
+ * sure these variables are in consistent states.
+ */
+
+ /* Coordinates of upper left and lower right rectangle corners. */
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ /* Integer coordinats of upper left corner and size. We must
+ * calculate this separately from the gdouble ones because sometimes
+ * we don't want to affect the integer size (e.g. when moving the
+ * rectangle), but that will be the case if we always calculate the
+ * integer coordinates based on rounded values of the gdouble
+ * coordinates even if the gdouble width remains constant.
+ *
+ * TODO: Change the internal double-representation of the rectangle
+ * to x,y width,height instead of x1,y1 x2,y2. That way we don't
+ * need to keep a separate representation of the integer version of
+ * the rectangle; rounding width an height will yield consistent
+ * results and not depend on position of the rectangle.
+ */
+ gint x1_int, y1_int;
+ gint width_int, height_int;
+
+ /* How to constrain the rectangle. */
+ GimpRectangleConstraint constraint;
+
+ /* What precision the rectangle will appear to have externally (it
+ * will always be double internally)
+ */
+ GimpRectanglePrecision precision;
+
+ /* Previous coordinate applied to the rectangle. */
+ gdouble lastx;
+ gdouble lasty;
+
+ /* Width and height of corner handles. */
+ gint corner_handle_w;
+ gint corner_handle_h;
+
+ /* Width and height of side handles. */
+ gint top_and_bottom_handle_w;
+ gint left_and_right_handle_h;
+
+ /* Whether or not the rectangle is in a 'narrow situation' i.e. it is
+ * too small for reasonable sized handle to be inside. In this case
+ * we put handles on the outside.
+ */
+ gboolean narrow_mode;
+
+ /* This boolean allows to always set narrow mode */
+ gboolean force_narrow_mode;
+
+ /* Whether or not to draw an ellipse inside the rectangle */
+ gboolean draw_ellipse;
+
+ /* Whether to draw round corners */
+ gboolean round_corners;
+ gdouble corner_radius;
+
+ /* The title for the statusbar coords */
+ gchar *status_title;
+
+ /* For saving in case of cancellation. */
+ gdouble saved_x1;
+ gdouble saved_y1;
+ gdouble saved_x2;
+ gdouble saved_y2;
+
+ gint suppress_updates;
+
+ GimpRectangleFunction function;
+
+ /* The following values are externally synced with GimpRectangleOptions */
+
+ gboolean highlight;
+ gdouble highlight_opacity;
+ GimpGuidesType guide;
+
+ gdouble x;
+ gdouble y;
+ gdouble width;
+ gdouble height;
+
+ gboolean fixed_rule_active;
+ GimpRectangleFixedRule fixed_rule;
+ gdouble desired_fixed_width;
+ gdouble desired_fixed_height;
+ gdouble desired_fixed_size_width;
+ gdouble desired_fixed_size_height;
+ gdouble aspect_numerator;
+ gdouble aspect_denominator;
+ gboolean fixed_center;
+
+ /* Canvas items for drawing the GUI */
+
+ GimpCanvasItem *guides;
+ GimpCanvasItem *rectangle;
+ GimpCanvasItem *ellipse;
+ GimpCanvasItem *corners[4];
+ GimpCanvasItem *center;
+ GimpCanvasItem *creating_corners[4];
+ GimpCanvasItem *handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS];
+ GimpCanvasItem *highlight_handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS];
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_rectangle_constructed (GObject *object);
+static void gimp_tool_rectangle_finalize (GObject *object);
+static void gimp_tool_rectangle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_rectangle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_rectangle_notify (GObject *object,
+ GParamSpec *pspec);
+
+static void gimp_tool_rectangle_changed (GimpToolWidget *widget);
+static gint gimp_tool_rectangle_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_rectangle_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_rectangle_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_rectangle_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_rectangle_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_rectangle_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_rectangle_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_rectangle_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_rectangle_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle);
+
+static void gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle);
+static void gimp_tool_rectangle_update_handle_sizes
+ (GimpToolRectangle *rectangle);
+static void gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle);
+
+static void gimp_tool_rectangle_synthesize_motion
+ (GimpToolRectangle *rectangle,
+ gint function,
+ gdouble new_x,
+ gdouble new_y);
+
+static GimpRectangleFunction
+ gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle,
+ const GimpCoords *coords,
+ gboolean proximity);
+static void gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle);
+
+static gboolean gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle,
+ const GimpCoords *coords);
+
+static gboolean gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle,
+ const GimpCoords *coords,
+ GimpHandleAnchor anchor);
+
+static GimpHandleAnchor gimp_tool_rectangle_get_anchor
+ (GimpRectangleFunction function);
+static gboolean gimp_tool_rectangle_rect_rubber_banding_func
+ (GimpToolRectangle *rectangle);
+static gboolean gimp_tool_rectangle_rect_adjusting_func
+ (GimpToolRectangle *rectangle);
+
+static void gimp_tool_rectangle_get_other_side (GimpToolRectangle *rectangle,
+ gdouble **other_x,
+ gdouble **other_y);
+static void gimp_tool_rectangle_get_other_side_coord
+ (GimpToolRectangle *rectangle,
+ gdouble *other_side_x,
+ gdouble *other_side_y);
+static void gimp_tool_rectangle_set_other_side_coord
+ (GimpToolRectangle *rectangle,
+ gdouble other_side_x,
+ gdouble other_side_y);
+
+static void gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle,
+ gdouble coord_x,
+ gdouble coord_y);
+static void gimp_tool_rectangle_setup_snap_offsets
+ (GimpToolRectangle *rectangle,
+ const GimpCoords *coords);
+
+static void gimp_tool_rectangle_clamp (GimpToolRectangle *rectangle,
+ ClampedSide *clamped_sides,
+ GimpRectangleConstraint constraint,
+ gboolean symmetrically);
+static void gimp_tool_rectangle_clamp_width (GimpToolRectangle *rectangle,
+ ClampedSide *clamped_sides,
+ GimpRectangleConstraint constraint,
+ gboolean symmetrically);
+static void gimp_tool_rectangle_clamp_height (GimpToolRectangle *rectangle,
+ ClampedSide *clamped_sides,
+ GimpRectangleConstraint constraint,
+ gboolean symmetrically);
+
+static void gimp_tool_rectangle_keep_inside (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint);
+static void gimp_tool_rectangle_keep_inside_horizontally
+ (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint);
+static void gimp_tool_rectangle_keep_inside_vertically
+ (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint);
+
+static void gimp_tool_rectangle_apply_fixed_width
+ (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint,
+ gdouble width);
+static void gimp_tool_rectangle_apply_fixed_height
+ (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint,
+ gdouble height);
+
+static void gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle,
+ gdouble aspect,
+ gint clamped_sides);
+
+static void gimp_tool_rectangle_update_with_coord
+ (GimpToolRectangle *rectangle,
+ gdouble new_x,
+ gdouble new_y);
+static void gimp_tool_rectangle_apply_fixed_rule(GimpToolRectangle *rectangle);
+
+static void gimp_tool_rectangle_get_constraints (GimpToolRectangle *rectangle,
+ gint *min_x,
+ gint *min_y,
+ gint *max_x,
+ gint *max_y,
+ GimpRectangleConstraint constraint);
+
+static void gimp_tool_rectangle_handle_general_clamping
+ (GimpToolRectangle *rectangle);
+static void gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle);
+static void gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle,
+ gdouble coord_x_input,
+ gdouble coord_y_input,
+ gdouble *coord_x_output,
+ gdouble *coord_y_output);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolRectangle, gimp_tool_rectangle,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_rectangle_parent_class
+
+static guint rectangle_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_tool_rectangle_class_init (GimpToolRectangleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_rectangle_constructed;
+ object_class->finalize = gimp_tool_rectangle_finalize;
+ object_class->set_property = gimp_tool_rectangle_set_property;
+ object_class->get_property = gimp_tool_rectangle_get_property;
+ object_class->notify = gimp_tool_rectangle_notify;
+
+ widget_class->changed = gimp_tool_rectangle_changed;
+ widget_class->button_press = gimp_tool_rectangle_button_press;
+ widget_class->button_release = gimp_tool_rectangle_button_release;
+ widget_class->motion = gimp_tool_rectangle_motion;
+ widget_class->hit = gimp_tool_rectangle_hit;
+ widget_class->hover = gimp_tool_rectangle_hover;
+ widget_class->leave_notify = gimp_tool_rectangle_leave_notify;
+ widget_class->key_press = gimp_tool_rectangle_key_press;
+ widget_class->motion_modifier = gimp_tool_rectangle_motion_modifier;
+ widget_class->get_cursor = gimp_tool_rectangle_get_cursor;
+ widget_class->update_on_scale = TRUE;
+
+ rectangle_signals[CHANGE_COMPLETE] =
+ g_signal_new ("change-complete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolRectangleClass, change_complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_double ("x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_double ("y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_double ("x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_double ("y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAINT,
+ g_param_spec_enum ("constraint",
+ NULL, NULL,
+ GIMP_TYPE_RECTANGLE_CONSTRAINT,
+ GIMP_RECTANGLE_CONSTRAIN_NONE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PRECISION,
+ g_param_spec_enum ("precision",
+ NULL, NULL,
+ GIMP_TYPE_RECTANGLE_PRECISION,
+ GIMP_RECTANGLE_PRECISION_INT,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_NARROW_MODE,
+ g_param_spec_boolean ("narrow-mode",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FORCE_NARROW_MODE,
+ g_param_spec_boolean ("force-narrow-mode",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_DRAW_ELLIPSE,
+ g_param_spec_boolean ("draw-ellipse",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ROUND_CORNERS,
+ g_param_spec_boolean ("round-corners",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CORNER_RADIUS,
+ g_param_spec_double ("corner-radius",
+ NULL, NULL,
+ 0.0, 10000.0, 10.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_STATUS_TITLE,
+ g_param_spec_string ("status-title",
+ NULL, NULL,
+ _("Rectangle: "),
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_HIGHLIGHT,
+ g_param_spec_boolean ("highlight",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_HIGHLIGHT_OPACITY,
+ g_param_spec_double ("highlight-opacity",
+ NULL, NULL,
+ 0.0, 1.0, 0.5,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_GUIDE,
+ g_param_spec_enum ("guide",
+ NULL, NULL,
+ GIMP_TYPE_GUIDES_TYPE,
+ GIMP_GUIDES_NONE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_double ("x",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_double ("y",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_double ("width",
+ NULL, NULL,
+ 0.0,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_double ("height",
+ NULL, NULL,
+ 0.0,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FIXED_RULE_ACTIVE,
+ g_param_spec_boolean ("fixed-rule-active",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FIXED_RULE,
+ g_param_spec_enum ("fixed-rule",
+ NULL, NULL,
+ GIMP_TYPE_RECTANGLE_FIXED_RULE,
+ GIMP_RECTANGLE_FIXED_ASPECT,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_DESIRED_FIXED_WIDTH,
+ g_param_spec_double ("desired-fixed-width",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_DESIRED_FIXED_HEIGHT,
+ g_param_spec_double ("desired-fixed-height",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_WIDTH,
+ g_param_spec_double ("desired-fixed-size-width",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_HEIGHT,
+ g_param_spec_double ("desired-fixed-size-height",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ASPECT_NUMERATOR,
+ g_param_spec_double ("aspect-numerator",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ASPECT_DENOMINATOR,
+ g_param_spec_double ("aspect-denominator",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FIXED_CENTER,
+ g_param_spec_boolean ("fixed-center",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_rectangle_init (GimpToolRectangle *rectangle)
+{
+ rectangle->private = gimp_tool_rectangle_get_instance_private (rectangle);
+
+ rectangle->private->function = GIMP_TOOL_RECTANGLE_CREATING;
+ rectangle->private->is_first = TRUE;
+}
+
+static void
+gimp_tool_rectangle_constructed (GObject *object)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpCanvasGroup *stroke_group;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ stroke_group = gimp_tool_widget_add_stroke_group (widget);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->guides = gimp_tool_widget_add_rectangle_guides (widget,
+ 0, 0, 10, 10,
+ GIMP_GUIDES_NONE);
+
+ private->rectangle = gimp_tool_widget_add_rectangle (widget,
+ 0, 0, 10, 10,
+ FALSE);
+
+ private->ellipse = gimp_tool_widget_add_arc (widget,
+ 0, 0, 10, 10,
+ 0.0, 2 * G_PI,
+ FALSE);
+
+ for (i = 0; i < 4; i++)
+ private->corners[i] = gimp_tool_widget_add_arc (widget,
+ 0, 0, 10, 10,
+ 0.0, 2 * G_PI,
+ FALSE);
+
+ gimp_tool_widget_pop_group (widget);
+
+ private->center = gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CROSS,
+ 0, 0,
+ GIMP_CANVAS_HANDLE_SIZE_SMALL,
+ GIMP_CANVAS_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->creating_corners[0] =
+ gimp_tool_widget_add_corner (widget,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_NORTH_WEST,
+ 10, 10,
+ FALSE);
+
+ private->creating_corners[1] =
+ gimp_tool_widget_add_corner (widget,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_NORTH_EAST,
+ 10, 10,
+ FALSE);
+
+ private->creating_corners[2] =
+ gimp_tool_widget_add_corner (widget,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_SOUTH_WEST,
+ 10, 10,
+ FALSE);
+
+ private->creating_corners[3] =
+ gimp_tool_widget_add_corner (widget,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_SOUTH_EAST,
+ 10, 10,
+ FALSE);
+
+ gimp_tool_widget_pop_group (widget);
+
+ for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
+ i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
+ i++)
+ {
+ GimpHandleAnchor anchor;
+
+ anchor = gimp_tool_rectangle_get_anchor (i);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->handles[i] = gimp_tool_widget_add_corner (widget,
+ 0, 0, 10, 10,
+ anchor,
+ 10, 10,
+ FALSE);
+
+ gimp_tool_widget_pop_group (widget);
+
+ private->highlight_handles[i] = gimp_tool_widget_add_corner (widget,
+ 0, 0, 10, 10,
+ anchor,
+ 10, 10,
+ FALSE);
+ gimp_canvas_item_set_highlight (private->highlight_handles[i], TRUE);
+ }
+
+ gimp_tool_rectangle_changed (widget);
+}
+
+static void
+gimp_tool_rectangle_finalize (GObject *object)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ g_clear_pointer (&private->status_title, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_rectangle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (property_id)
+ {
+ case PROP_X1:
+ private->x1 = g_value_get_double (value);
+ break;
+ case PROP_Y1:
+ private->y1 = g_value_get_double (value);
+ break;
+ case PROP_X2:
+ private->x2 = g_value_get_double (value);
+ break;
+ case PROP_Y2:
+ private->y2 = g_value_get_double (value);
+ break;
+
+ case PROP_CONSTRAINT:
+ private->constraint = g_value_get_enum (value);
+ break;
+ case PROP_PRECISION:
+ private->precision = g_value_get_enum (value);
+ break;
+
+ case PROP_NARROW_MODE:
+ private->narrow_mode = g_value_get_boolean (value);
+ break;
+ case PROP_FORCE_NARROW_MODE:
+ private->force_narrow_mode = g_value_get_boolean (value);
+ break;
+ case PROP_DRAW_ELLIPSE:
+ private->draw_ellipse = g_value_get_boolean (value);
+ break;
+ case PROP_ROUND_CORNERS:
+ private->round_corners = g_value_get_boolean (value);
+ break;
+ case PROP_CORNER_RADIUS:
+ private->corner_radius = g_value_get_double (value);
+ break;
+
+ case PROP_STATUS_TITLE:
+ g_free (private->status_title);
+ private->status_title = g_value_dup_string (value);
+ if (! private->status_title)
+ private->status_title = g_strdup (_("Rectangle: "));
+ break;
+
+ case PROP_HIGHLIGHT:
+ private->highlight = g_value_get_boolean (value);
+ break;
+ case PROP_HIGHLIGHT_OPACITY:
+ private->highlight_opacity = g_value_get_double (value);
+ break;
+ case PROP_GUIDE:
+ private->guide = g_value_get_enum (value);
+ break;
+
+ case PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case PROP_WIDTH:
+ private->width = g_value_get_double (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_double (value);
+ break;
+
+ case PROP_FIXED_RULE_ACTIVE:
+ private->fixed_rule_active = g_value_get_boolean (value);
+ break;
+ case PROP_FIXED_RULE:
+ private->fixed_rule = g_value_get_enum (value);
+ break;
+ case PROP_DESIRED_FIXED_WIDTH:
+ private->desired_fixed_width = g_value_get_double (value);
+ break;
+ case PROP_DESIRED_FIXED_HEIGHT:
+ private->desired_fixed_height = g_value_get_double (value);
+ break;
+ case PROP_DESIRED_FIXED_SIZE_WIDTH:
+ private->desired_fixed_size_width = g_value_get_double (value);
+ break;
+ case PROP_DESIRED_FIXED_SIZE_HEIGHT:
+ private->desired_fixed_size_height = g_value_get_double (value);
+ break;
+ case PROP_ASPECT_NUMERATOR:
+ private->aspect_numerator = g_value_get_double (value);
+ break;
+ case PROP_ASPECT_DENOMINATOR:
+ private->aspect_denominator = g_value_get_double (value);
+ break;
+
+ case PROP_FIXED_CENTER:
+ private->fixed_center = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_rectangle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (property_id)
+ {
+ case PROP_X1:
+ g_value_set_double (value, private->x1);
+ break;
+ case PROP_Y1:
+ g_value_set_double (value, private->y1);
+ break;
+ case PROP_X2:
+ g_value_set_double (value, private->x2);
+ break;
+ case PROP_Y2:
+ g_value_set_double (value, private->y2);
+ break;
+
+ case PROP_CONSTRAINT:
+ g_value_set_enum (value, private->constraint);
+ break;
+ case PROP_PRECISION:
+ g_value_set_enum (value, private->precision);
+ break;
+
+ case PROP_NARROW_MODE:
+ g_value_set_boolean (value, private->narrow_mode);
+ break;
+ case PROP_FORCE_NARROW_MODE:
+ g_value_set_boolean (value, private->force_narrow_mode);
+ break;
+ case PROP_DRAW_ELLIPSE:
+ g_value_set_boolean (value, private->draw_ellipse);
+ break;
+ case PROP_ROUND_CORNERS:
+ g_value_set_boolean (value, private->round_corners);
+ break;
+ case PROP_CORNER_RADIUS:
+ g_value_set_double (value, private->corner_radius);
+ break;
+
+ case PROP_STATUS_TITLE:
+ g_value_set_string (value, private->status_title);
+ break;
+
+ case PROP_HIGHLIGHT:
+ g_value_set_boolean (value, private->highlight);
+ break;
+ case PROP_HIGHLIGHT_OPACITY:
+ g_value_set_double (value, private->highlight_opacity);
+ break;
+ case PROP_GUIDE:
+ g_value_set_enum (value, private->guide);
+ break;
+
+ case PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case PROP_WIDTH:
+ g_value_set_double (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_double (value, private->height);
+ break;
+
+ case PROP_FIXED_RULE_ACTIVE:
+ g_value_set_boolean (value, private->fixed_rule_active);
+ break;
+ case PROP_FIXED_RULE:
+ g_value_set_enum (value, private->fixed_rule);
+ break;
+ case PROP_DESIRED_FIXED_WIDTH:
+ g_value_set_double (value, private->desired_fixed_width);
+ break;
+ case PROP_DESIRED_FIXED_HEIGHT:
+ g_value_set_double (value, private->desired_fixed_height);
+ break;
+ case PROP_DESIRED_FIXED_SIZE_WIDTH:
+ g_value_set_double (value, private->desired_fixed_size_width);
+ break;
+ case PROP_DESIRED_FIXED_SIZE_HEIGHT:
+ g_value_set_double (value, private->desired_fixed_size_height);
+ break;
+ case PROP_ASPECT_NUMERATOR:
+ g_value_set_double (value, private->aspect_numerator);
+ break;
+ case PROP_ASPECT_DENOMINATOR:
+ g_value_set_double (value, private->aspect_denominator);
+ break;
+
+ case PROP_FIXED_CENTER:
+ g_value_set_boolean (value, private->fixed_center);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_rectangle_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object);
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ if (G_OBJECT_CLASS (parent_class)->notify)
+ G_OBJECT_CLASS (parent_class)->notify (object, pspec);
+
+ if (! strcmp (pspec->name, "x1") ||
+ ! strcmp (pspec->name, "y1") ||
+ ! strcmp (pspec->name, "x2") ||
+ ! strcmp (pspec->name, "y2"))
+ {
+ gimp_tool_rectangle_update_int_rect (rectangle);
+
+ gimp_tool_rectangle_update_options (rectangle);
+ }
+ else if (! strcmp (pspec->name, "x") &&
+ ! PIXEL_FEQUAL (private->x1, private->x))
+ {
+ gimp_tool_rectangle_synthesize_motion (rectangle,
+ GIMP_TOOL_RECTANGLE_MOVING,
+ private->x,
+ private->y1);
+ }
+ else if (! strcmp (pspec->name, "y") &&
+ ! PIXEL_FEQUAL (private->y1, private->y))
+ {
+ gimp_tool_rectangle_synthesize_motion (rectangle,
+ GIMP_TOOL_RECTANGLE_MOVING,
+ private->x1,
+ private->y);
+ }
+ else if (! strcmp (pspec->name, "width") &&
+ ! PIXEL_FEQUAL (private->x2 - private->x1, private->width))
+ {
+ /* Calculate x2, y2 that will create a rectangle of given width,
+ * for the current options.
+ */
+ gdouble x2;
+
+ if (private->fixed_center)
+ {
+ x2 = private->center_x_on_fixed_center +
+ private->width / 2;
+ }
+ else
+ {
+ x2 = private->x1 + private->width;
+ }
+
+ gimp_tool_rectangle_synthesize_motion (rectangle,
+ GIMP_TOOL_RECTANGLE_RESIZING_RIGHT,
+ x2,
+ private->y2);
+ }
+ else if (! strcmp (pspec->name, "height") &&
+ ! PIXEL_FEQUAL (private->y2 - private->y1, private->height))
+ {
+ /* Calculate x2, y2 that will create a rectangle of given
+ * height, for the current options.
+ */
+ gdouble y2;
+
+ if (private->fixed_center)
+ {
+ y2 = private->center_y_on_fixed_center +
+ private->height / 2;
+ }
+ else
+ {
+ y2 = private->y1 + private->height;
+ }
+
+ gimp_tool_rectangle_synthesize_motion (rectangle,
+ GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM,
+ private->x2,
+ y2);
+ }
+ else if (! strcmp (pspec->name, "desired-fixed-size-width"))
+ {
+ /* We are only interested in when width and height swaps, so
+ * it's enough to only check e.g. for width.
+ */
+
+ gdouble width = private->x2 - private->x1;
+ gdouble height = private->y2 - private->y1;
+
+ /* Depending on a bunch of conditions, we might want to
+ * immedieately switch width and height of the pending
+ * rectangle.
+ */
+ if (private->fixed_rule_active &&
+#if 0
+ tool->button_press_state == 0 &&
+ tool->active_modifier_state == 0 &&
+#endif
+ FEQUAL (private->desired_fixed_size_width, height) &&
+ FEQUAL (private->desired_fixed_size_height, width))
+ {
+ gdouble x = private->x1;
+ gdouble y = private->y1;
+
+ gimp_tool_rectangle_synthesize_motion (rectangle,
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
+ private->x2,
+ private->y2);
+
+ /* For some reason these needs to be set separately... */
+ g_object_set (rectangle,
+ "x", x,
+ NULL);
+ g_object_set (rectangle,
+ "y", y,
+ NULL);
+ }
+ }
+ else if (! strcmp (pspec->name, "aspect-numerator"))
+ {
+ /* We are only interested in when numerator and denominator
+ * swaps, so it's enough to only check e.g. for numerator.
+ */
+
+ double width = private->x2 - private->x1;
+ double height = private->y2 - private->y1;
+ gdouble new_inverse_ratio = private->aspect_denominator /
+ private->aspect_numerator;
+ gdouble lower_ratio;
+ gdouble higher_ratio;
+
+ /* The ratio of the Fixed: Aspect ratio rule and the pending
+ * rectangle is very rarely exactly the same so use an
+ * interval. For small rectangles the below code will
+ * automatically yield a more generous accepted ratio interval
+ * which is exactly what we want.
+ */
+ if (width > height && height > 1.0)
+ {
+ lower_ratio = width / (height + 1.0);
+ higher_ratio = width / (height - 1.0);
+ }
+ else
+ {
+ lower_ratio = (width - 1.0) / height;
+ higher_ratio = (width + 1.0) / height;
+ }
+
+ /* Depending on a bunch of conditions, we might want to
+ * immedieately switch width and height of the pending
+ * rectangle.
+ */
+ if (private->fixed_rule_active &&
+#if 0
+ tool->button_press_state == 0 &&
+ tool->active_modifier_state == 0 &&
+#endif
+ lower_ratio < new_inverse_ratio &&
+ higher_ratio > new_inverse_ratio)
+ {
+ gdouble new_x2 = private->x1 + private->y2 - private->y1;
+ gdouble new_y2 = private->y1 + private->x2 - private->x1;
+
+ gimp_tool_rectangle_synthesize_motion (rectangle,
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
+ new_x2,
+ new_y2);
+ }
+ }
+}
+
+static void
+gimp_tool_rectangle_changed (GimpToolWidget *widget)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ gdouble x1, y1, x2, y2;
+ gint handle_width;
+ gint handle_height;
+ gint i;
+
+ gimp_tool_rectangle_update_handle_sizes (rectangle);
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+
+ gimp_canvas_rectangle_guides_set (private->guides,
+ x1, y1,
+ x2 - x1,
+ y2 - y1,
+ private->guide, 4);
+
+ gimp_canvas_rectangle_set (private->rectangle,
+ x1, y1,
+ x2 - x1,
+ y2 - y1);
+
+ if (private->draw_ellipse)
+ {
+ gimp_canvas_arc_set (private->ellipse,
+ (x1 + x2) / 2.0,
+ (y1 + y2) / 2.0,
+ (x2 - x1) / 2.0,
+ (y2 - y1) / 2.0,
+ 0.0, 2 * G_PI);
+ gimp_canvas_item_set_visible (private->ellipse, TRUE);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (private->ellipse, FALSE);
+ }
+
+ if (private->round_corners && private->corner_radius > 0.0)
+ {
+ gdouble radius;
+
+ radius = MIN (private->corner_radius,
+ MIN ((x2 - x1) / 2.0, (y2 - y1) / 2.0));
+
+ gimp_canvas_arc_set (private->corners[0],
+ x1 + radius,
+ y1 + radius,
+ radius, radius,
+ G_PI / 2.0, G_PI / 2.0);
+
+ gimp_canvas_arc_set (private->corners[1],
+ x2 - radius,
+ y1 + radius,
+ radius, radius,
+ 0.0, G_PI / 2.0);
+
+ gimp_canvas_arc_set (private->corners[2],
+ x1 + radius,
+ y2 - radius,
+ radius, radius,
+ G_PI, G_PI / 2.0);
+
+ gimp_canvas_arc_set (private->corners[3],
+ x2 - radius,
+ y2 - radius,
+ radius, radius,
+ G_PI * 1.5, G_PI / 2.0);
+
+ for (i = 0; i < 4; i++)
+ gimp_canvas_item_set_visible (private->corners[i], TRUE);
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ gimp_canvas_item_set_visible (private->corners[i], FALSE);
+ }
+
+ gimp_canvas_item_set_visible (private->center, FALSE);
+
+ for (i = 0; i < 4; i++)
+ {
+ gimp_canvas_item_set_visible (private->creating_corners[i], FALSE);
+ gimp_canvas_item_set_highlight (private->creating_corners[i], FALSE);
+ }
+
+ for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
+ i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
+ i++)
+ {
+ gimp_canvas_item_set_visible (private->handles[i], FALSE);
+ gimp_canvas_item_set_visible (private->highlight_handles[i], FALSE);
+ }
+
+ handle_width = private->corner_handle_w;
+ handle_height = private->corner_handle_h;
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ if (private->rect_adjusting)
+ {
+ /* Mark the center because we snap to it */
+ gimp_canvas_handle_set_position (private->center,
+ (x1 + x2) / 2.0,
+ (y1 + y2) / 2.0);
+ gimp_canvas_item_set_visible (private->center, TRUE);
+ break;
+ }
+
+ /* else fallthrough */
+
+ case GIMP_TOOL_RECTANGLE_DEAD:
+ case GIMP_TOOL_RECTANGLE_CREATING:
+ case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
+ for (i = 0; i < 4; i++)
+ {
+ gimp_canvas_corner_set (private->creating_corners[i],
+ x1, y1, x2 - x1, y2 - y1,
+ handle_width, handle_height,
+ private->narrow_mode);
+ gimp_canvas_item_set_visible (private->creating_corners[i], TRUE);
+ }
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ handle_width = private->top_and_bottom_handle_w;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ handle_height = private->left_and_right_handle_h;
+ break;
+
+ default:
+ break;
+ }
+
+ if (handle_width >= 3 &&
+ handle_height >= 3 &&
+ private->function >= GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT &&
+ private->function <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM)
+ {
+ GimpCanvasItem *corner;
+
+ if (private->rect_adjusting)
+ corner = private->handles[private->function];
+ else
+ corner = private->highlight_handles[private->function];
+
+ gimp_canvas_corner_set (corner,
+ x1, y1, x2 - x1, y2 - y1,
+ handle_width, handle_height,
+ private->narrow_mode);
+ gimp_canvas_item_set_visible (corner, TRUE);
+ }
+
+ if (private->highlight && ! private->rect_adjusting)
+ {
+ GdkRectangle rect;
+
+ rect.x = x1;
+ rect.y = y1;
+ rect.width = x2 - x1;
+ rect.height = y2 - y1;
+
+ gimp_display_shell_set_highlight (shell, &rect, private->highlight_opacity);
+ }
+ else
+ {
+ gimp_display_shell_set_highlight (shell, NULL, 0.0);
+ }
+}
+
+gint
+gimp_tool_rectangle_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gdouble snapped_x, snapped_y;
+ gint snap_x, snap_y;
+
+ /* save existing shape in case of cancellation */
+ private->saved_x1 = private->x1;
+ private->saved_y1 = private->y1;
+ private->saved_x2 = private->x2;
+ private->saved_y2 = private->y2;
+
+ gimp_tool_rectangle_setup_snap_offsets (rectangle, coords);
+ gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL);
+
+ snapped_x = coords->x + snap_x;
+ snapped_y = coords->y + snap_y;
+
+ private->lastx = snapped_x;
+ private->lasty = snapped_y;
+
+ if (private->function == GIMP_TOOL_RECTANGLE_CREATING)
+ {
+ /* Remember that this rectangle was created from scratch. */
+ private->is_new = TRUE;
+
+ private->x1 = private->x2 = snapped_x;
+ private->y1 = private->y2 = snapped_y;
+
+ /* Unless forced, created rectangles should not be started in
+ * narrow-mode
+ */
+ if (private->force_narrow_mode)
+ private->narrow_mode = TRUE;
+ else
+ private->narrow_mode = FALSE;
+
+ /* If the rectangle is being modified we want the center on
+ * fixed_center to be at the center of the currently existing
+ * rectangle, otherwise we want the point where the user clicked
+ * to be the center on fixed_center.
+ */
+ private->center_x_on_fixed_center = snapped_x;
+ private->center_y_on_fixed_center = snapped_y;
+
+ /* When the user toggles modifier keys, we want to keep track of
+ * what coordinates the "other side" should have. If we are
+ * creating a rectangle, use the current mouse coordinates as
+ * the coordinate of the "other side", otherwise use the
+ * immediate "other side" for that.
+ */
+ private->other_side_x = snapped_x;
+ private->other_side_y = snapped_y;
+ }
+ else
+ {
+ /* This rectangle was not created from scratch. */
+ private->is_new = FALSE;
+
+ private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
+ private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
+
+ gimp_tool_rectangle_get_other_side_coord (rectangle,
+ &private->other_side_x,
+ &private->other_side_y);
+ }
+
+ gimp_tool_rectangle_update_int_rect (rectangle);
+
+ /* Is the rectangle being rubber-banded? */
+ private->rect_adjusting = gimp_tool_rectangle_rect_adjusting_func (rectangle);
+
+ gimp_tool_rectangle_changed (widget);
+
+ gimp_tool_rectangle_update_status (rectangle);
+
+ return 1;
+}
+
+void
+gimp_tool_rectangle_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gint response = 0;
+
+ gimp_tool_widget_set_status (widget, NULL);
+
+ /* On button release, we are not rubber-banding the rectangle any longer. */
+ private->rect_adjusting = FALSE;
+
+ gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
+
+ switch (release_type)
+ {
+ case GIMP_BUTTON_RELEASE_NO_MOTION:
+ /* If the first created rectangle was not expanded, halt the
+ * tool...
+ */
+ if (gimp_tool_rectangle_rectangle_is_first (rectangle))
+ {
+ response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL;
+ break;
+ }
+
+ /* ...else fallthrough and treat a long click without movement
+ * like a normal change
+ */
+
+ case GIMP_BUTTON_RELEASE_NORMAL:
+ /* If a normal click-drag-release actually created a rectangle
+ * with content...
+ */
+ if (private->x1 != private->x2 &&
+ private->y1 != private->y2)
+ {
+ gimp_tool_rectangle_change_complete (rectangle);
+ break;
+ }
+
+ /* ...else fallthrough and undo the operation, we can't have
+ * zero-extent rectangles
+ */
+
+ case GIMP_BUTTON_RELEASE_CANCEL:
+ private->x1 = private->saved_x1;
+ private->y1 = private->saved_y1;
+ private->x2 = private->saved_x2;
+ private->y2 = private->saved_y2;
+
+ gimp_tool_rectangle_update_int_rect (rectangle);
+
+ /* If the first created rectangle was canceled, halt the tool */
+ if (gimp_tool_rectangle_rectangle_is_first (rectangle))
+ response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL;
+ break;
+
+ case GIMP_BUTTON_RELEASE_CLICK:
+ /* When a dead area is clicked, don't execute. */
+ if (private->function != GIMP_TOOL_RECTANGLE_DEAD)
+ response = GIMP_TOOL_WIDGET_RESPONSE_CONFIRM;
+ break;
+ }
+
+ /* We must update this. */
+ private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
+ private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
+
+ gimp_tool_rectangle_update_options (rectangle);
+
+ gimp_tool_rectangle_changed (widget);
+
+ private->is_first = FALSE;
+
+ /* emit response at the end, so everything is up to date even if
+ * a signal handler decides hot to shut down the rectangle
+ */
+ if (response != 0)
+ gimp_tool_widget_response (widget, response);
+}
+
+void
+gimp_tool_rectangle_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gdouble snapped_x;
+ gdouble snapped_y;
+ gint snap_x, snap_y;
+
+ /* Motion events should be ignored when we're just waiting for the
+ * button release event to execute or if the user has grabbed a dead
+ * area of the rectangle.
+ */
+ if (private->function == GIMP_TOOL_RECTANGLE_EXECUTING ||
+ private->function == GIMP_TOOL_RECTANGLE_DEAD)
+ return;
+
+ /* Handle snapping. */
+ gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL);
+
+ snapped_x = coords->x + snap_x;
+ snapped_y = coords->y + snap_y;
+
+ /* This is the core rectangle shape updating function: */
+ gimp_tool_rectangle_update_with_coord (rectangle, snapped_x, snapped_y);
+
+ gimp_tool_rectangle_update_status (rectangle);
+
+ if (private->function == GIMP_TOOL_RECTANGLE_CREATING)
+ {
+ GimpRectangleFunction function = GIMP_TOOL_RECTANGLE_CREATING;
+ gdouble dx = snapped_x - private->lastx;
+ gdouble dy = snapped_y - private->lasty;
+
+ /* When the user starts to move the cursor, set the current
+ * function to one of the corner-grabbed functions, depending on
+ * in what direction the user starts dragging the rectangle.
+ */
+ if (dx < 0)
+ {
+ function = (dy < 0 ?
+ GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT);
+ }
+ else if (dx > 0)
+ {
+ function = (dy < 0 ?
+ GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT :
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
+ }
+ else if (dy < 0)
+ {
+ function = (dx < 0 ?
+ GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
+ GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT);
+ }
+ else if (dy > 0)
+ {
+ function = (dx < 0 ?
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT :
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
+ }
+
+ gimp_tool_rectangle_set_function (rectangle, function);
+
+ if (private->fixed_rule_active &&
+ private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE)
+ {
+ /* For fixed size, set the function to moving immediately since the
+ * rectangle can not be resized anyway.
+ */
+
+ /* We fake a coord update to get the right size. */
+ gimp_tool_rectangle_update_with_coord (rectangle,
+ snapped_x,
+ snapped_y);
+
+ gimp_tool_widget_set_snap_offsets (widget,
+ -(private->x2 - private->x1) / 2,
+ -(private->y2 - private->y1) / 2,
+ private->x2 - private->x1,
+ private->y2 - private->y1);
+
+ gimp_tool_rectangle_set_function (rectangle,
+ GIMP_TOOL_RECTANGLE_MOVING);
+ }
+ }
+
+ gimp_tool_rectangle_update_options (rectangle);
+
+ private->lastx = snapped_x;
+ private->lasty = snapped_y;
+}
+
+GimpHit
+gimp_tool_rectangle_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpRectangleFunction function;
+
+ if (private->suppress_updates)
+ {
+ function = gimp_tool_rectangle_get_function (rectangle);
+ }
+ else
+ {
+ function = gimp_tool_rectangle_calc_function (rectangle,
+ coords, proximity);
+ }
+
+ switch (function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ return GIMP_HIT_DIRECT;
+
+ case GIMP_TOOL_RECTANGLE_CREATING:
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ return GIMP_HIT_INDIRECT;
+
+ case GIMP_TOOL_RECTANGLE_DEAD:
+ case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
+ case GIMP_TOOL_RECTANGLE_EXECUTING:
+ default:
+ return GIMP_HIT_NONE;
+ }
+}
+
+void
+gimp_tool_rectangle_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpRectangleFunction function;
+
+ if (private->suppress_updates)
+ {
+ private->suppress_updates--;
+ return;
+ }
+
+ function = gimp_tool_rectangle_calc_function (rectangle, coords, proximity);
+
+ gimp_tool_rectangle_set_function (rectangle, function);
+}
+
+static void
+gimp_tool_rectangle_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+
+ gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_DEAD);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static gboolean
+gimp_tool_rectangle_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gint dx = 0;
+ gint dy = 0;
+ gdouble new_x = 0;
+ gdouble new_y = 0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ dy = -1;
+ break;
+ case GDK_KEY_Left:
+ dx = -1;
+ break;
+ case GDK_KEY_Right:
+ dx = 1;
+ break;
+ case GDK_KEY_Down:
+ dy = 1;
+ break;
+
+ default:
+ return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
+ }
+
+ /* If the shift key is down, move by an accelerated increment */
+ if (kevent->state & gimp_get_extend_selection_mask ())
+ {
+ dx *= ARROW_VELOCITY;
+ dy *= ARROW_VELOCITY;
+ }
+
+ gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
+
+ /* Resize the rectangle if the mouse is over a handle, otherwise move it */
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ new_x = private->x1 + dx;
+ new_y = private->y1 + dy;
+ private->lastx = new_x;
+ private->lasty = new_y;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ new_x = private->x2 + dx;
+ new_y = private->y1 + dy;
+ private->lastx = new_x;
+ private->lasty = new_y;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ new_x = private->x1 + dx;
+ new_y = private->y2 + dy;
+ private->lastx = new_x;
+ private->lasty = new_y;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ new_x = private->x2 + dx;
+ new_y = private->y2 + dy;
+ private->lastx = new_x;
+ private->lasty = new_y;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ new_x = private->x1 + dx;
+ private->lastx = new_x;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ new_x = private->x2 + dx;
+ private->lastx = new_x;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ new_y = private->y1 + dy;
+ private->lasty = new_y;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ new_y = private->y2 + dy;
+ private->lasty = new_y;
+ break;
+
+ default:
+ return TRUE;
+ }
+
+ gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y);
+
+ private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
+ private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
+
+ gimp_tool_rectangle_update_options (rectangle);
+
+ gimp_tool_rectangle_change_complete (rectangle);
+
+ /* Evil hack to suppress oper updates. We do this because we don't
+ * want the rectangle tool to change function while the rectangle
+ * is being resized or moved using the keyboard.
+ */
+ private->suppress_updates = 2;
+
+ return TRUE;
+}
+
+static void
+gimp_tool_rectangle_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gboolean button1_down;
+
+ button1_down = (state & GDK_BUTTON1_MASK);
+
+ if (key == gimp_get_extend_selection_mask ())
+ {
+#if 0
+ /* Here we want to handle manually when to update the rectangle, so we
+ * don't want gimp_tool_rectangle_options_notify to do anything.
+ */
+ g_signal_handlers_block_by_func (options,
+ gimp_tool_rectangle_options_notify,
+ rectangle);
+#endif
+
+ g_object_set (rectangle,
+ "fixed-rule-active", ! private->fixed_rule_active,
+ NULL);
+
+#if 0
+ g_signal_handlers_unblock_by_func (options,
+ gimp_tool_rectangle_options_notify,
+ rectangle);
+#endif
+
+ /* Only change the shape if the mouse is still down (i.e. the user is
+ * still editing the rectangle.
+ */
+ if (button1_down)
+ {
+ if (! private->fixed_rule_active)
+ {
+ /* Reset anchor point */
+ gimp_tool_rectangle_set_other_side_coord (rectangle,
+ private->other_side_x,
+ private->other_side_y);
+ }
+
+ gimp_tool_rectangle_update_with_coord (rectangle,
+ private->lastx,
+ private->lasty);
+ }
+ }
+
+ if (key == gimp_get_toggle_behavior_mask ())
+ {
+ g_object_set (rectangle,
+ "fixed-center", ! private->fixed_center,
+ NULL);
+
+ if (private->fixed_center)
+ {
+ gimp_tool_rectangle_update_with_coord (rectangle,
+ private->lastx,
+ private->lasty);
+
+ /* Only emit the rectangle-changed signal if the button is
+ * not down. If it is down, the signal will and shall be
+ * emitted on _button_release instead.
+ */
+ if (! button1_down)
+ {
+ gimp_tool_rectangle_change_complete (rectangle);
+ }
+ }
+ else if (button1_down)
+ {
+ /* If we are leaving fixed_center mode we want to set the
+ * "other side" where it should be. Don't do anything if we
+ * came here by a mouse-click though, since then the user
+ * has confirmed the shape and we don't want to modify it
+ * afterwards.
+ */
+ gimp_tool_rectangle_set_other_side_coord (rectangle,
+ private->other_side_x,
+ private->other_side_y);
+ }
+ }
+
+ gimp_tool_rectangle_update_options (rectangle);
+}
+
+static gboolean
+gimp_tool_rectangle_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_CREATING:
+ *cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ break;
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ *cursor = GIMP_CURSOR_MOVE;
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ *cursor = GIMP_CURSOR_CORNER_TOP_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ *cursor = GIMP_CURSOR_CORNER_TOP_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ *cursor = GIMP_CURSOR_CORNER_BOTTOM_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ *cursor = GIMP_CURSOR_SIDE_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ *cursor = GIMP_CURSOR_SIDE_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ *cursor = GIMP_CURSOR_SIDE_TOP;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ *cursor = GIMP_CURSOR_SIDE_BOTTOM;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle)
+{
+ g_signal_emit (rectangle, rectangle_signals[CHANGE_COMPLETE], 0);
+}
+
+static void
+gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+
+#if 0
+ g_signal_handlers_block_by_func (options,
+ gimp_tool_rectangle_options_notify,
+ rect_tool);
+#endif
+
+ g_object_freeze_notify (G_OBJECT (rectangle));
+
+ if (! FEQUAL (private->x, x1))
+ g_object_set (rectangle, "x", x1, NULL);
+
+ if (! FEQUAL (private->y, y1))
+ g_object_set (rectangle, "y", y1, NULL);
+
+ if (! FEQUAL (private->width, x2 - x1))
+ g_object_set (rectangle, "width", x2 - x1, NULL);
+
+ if (! FEQUAL (private->height, y2 - y1))
+ g_object_set (rectangle, "height", y2 - y1, NULL);
+
+ g_object_thaw_notify (G_OBJECT (rectangle));
+
+#if 0
+ g_signal_handlers_unblock_by_func (options,
+ gimp_tool_rectangle_options_notify,
+ rect_tool);
+#endif
+}
+
+static void
+gimp_tool_rectangle_update_handle_sizes (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpDisplayShell *shell;
+ gint visible_rectangle_width;
+ gint visible_rectangle_height;
+ gint rectangle_width;
+ gint rectangle_height;
+ gdouble pub_x1, pub_y1;
+ gdouble pub_x2, pub_y2;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+
+ gimp_tool_rectangle_get_public_rect (rectangle,
+ &pub_x1, &pub_y1, &pub_x2, &pub_y2);
+ {
+ /* Calculate rectangles of the selection rectangle and the display
+ * shell, with origin at (0, 0) of image, and in screen coordinate
+ * scale.
+ */
+ gint x1 = pub_x1 * shell->scale_x;
+ gint y1 = pub_y1 * shell->scale_y;
+ gint w1 = (pub_x2 - pub_x1) * shell->scale_x;
+ gint h1 = (pub_y2 - pub_y1) * shell->scale_y;
+
+ gint x2, y2, w2, h2;
+
+ gimp_display_shell_scroll_get_scaled_viewport (shell, &x2, &y2, &w2, &h2);
+
+ rectangle_width = w1;
+ rectangle_height = h1;
+
+ /* Handle size calculations shall be based on the visible part of
+ * the rectangle, so calculate the size for the visible rectangle
+ * by intersecting with the viewport rectangle.
+ */
+ gimp_rectangle_intersect (x1, y1,
+ w1, h1,
+ x2, y2,
+ w2, h2,
+ NULL, NULL,
+ &visible_rectangle_width,
+ &visible_rectangle_height);
+
+ /* Determine if we are in narrow-mode or not. */
+ if (private->force_narrow_mode)
+ private->narrow_mode = TRUE;
+ else
+ private->narrow_mode = (visible_rectangle_width < NARROW_MODE_THRESHOLD ||
+ visible_rectangle_height < NARROW_MODE_THRESHOLD);
+ }
+
+ if (private->narrow_mode)
+ {
+ /* Corner handles always have the same (on-screen) size in
+ * narrow-mode.
+ */
+ private->corner_handle_w = NARROW_MODE_HANDLE_SIZE;
+ private->corner_handle_h = NARROW_MODE_HANDLE_SIZE;
+
+ private->top_and_bottom_handle_w = CLAMP (rectangle_width,
+ MIN (rectangle_width - 2,
+ NARROW_MODE_HANDLE_SIZE),
+ G_MAXINT);
+ private->left_and_right_handle_h = CLAMP (rectangle_height,
+ MIN (rectangle_height - 2,
+ NARROW_MODE_HANDLE_SIZE),
+ G_MAXINT);
+ }
+ else
+ {
+ /* Calculate and clamp corner handle size. */
+
+ private->corner_handle_w = visible_rectangle_width / 4;
+ private->corner_handle_h = visible_rectangle_height / 4;
+
+ private->corner_handle_w = CLAMP (private->corner_handle_w,
+ MIN_HANDLE_SIZE,
+ MAX_HANDLE_SIZE);
+ private->corner_handle_h = CLAMP (private->corner_handle_h,
+ MIN_HANDLE_SIZE,
+ MAX_HANDLE_SIZE);
+
+ /* Calculate and clamp side handle size. */
+
+ private->top_and_bottom_handle_w = rectangle_width - 3 * private->corner_handle_w;
+ private->left_and_right_handle_h = rectangle_height - 3 * private->corner_handle_h;
+
+ private->top_and_bottom_handle_w = CLAMP (private->top_and_bottom_handle_w,
+ MIN_HANDLE_SIZE,
+ G_MAXINT);
+ private->left_and_right_handle_h = CLAMP (private->left_and_right_handle_h,
+ MIN_HANDLE_SIZE,
+ G_MAXINT);
+ }
+}
+
+static void
+gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gdouble x1, y1, x2, y2;
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+
+ if (private->function == GIMP_TOOL_RECTANGLE_MOVING)
+ {
+ gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle),
+ _("Position: "),
+ x1, ", ", y1,
+ NULL);
+ }
+ else
+ {
+ gchar *aspect_text = NULL;
+ gint width = x2 - x1;
+ gint height = y2 - y1;
+
+ if (width > 0.0 && height > 0.0)
+ {
+ aspect_text = g_strdup_printf (" (%.2f:1)",
+ (gdouble) width / (gdouble) height);
+ }
+
+ gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle),
+ private->status_title,
+ width, " × ", height,
+ aspect_text);
+ g_free (aspect_text);
+ }
+}
+
+static void
+gimp_tool_rectangle_synthesize_motion (GimpToolRectangle *rectangle,
+ gint function,
+ gdouble new_x,
+ gdouble new_y)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpRectangleFunction old_function;
+
+ /* We don't want to synthesize motions if the tool control is active
+ * since that means the mouse button is down and the rectangle will
+ * get updated in _motion anyway. The reason we want to prevent this
+ * function from executing is that is emits the
+ * rectangle-changed-complete signal which we don't want in the
+ * middle of a rectangle change.
+ *
+ * In addition to that, we don't want to synthesize a motion if
+ * there is no pending rectangle because that doesn't make any
+ * sense.
+ */
+ if (private->rect_adjusting)
+ return;
+
+ old_function = private->function;
+
+ gimp_tool_rectangle_set_function (rectangle, function);
+
+ gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y);
+
+ /* We must update this. */
+ private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
+ private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
+
+ gimp_tool_rectangle_update_options (rectangle);
+
+ gimp_tool_rectangle_set_function (rectangle, old_function);
+
+ gimp_tool_rectangle_change_complete (rectangle);
+}
+
+static void
+swap_doubles (gdouble *i,
+ gdouble *j)
+{
+ gdouble tmp;
+
+ tmp = *i;
+ *i = *j;
+ *j = tmp;
+}
+
+static GimpRectangleFunction
+gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle,
+ const GimpCoords *coords,
+ gboolean proximity)
+{
+ if (! proximity)
+ {
+ return GIMP_TOOL_RECTANGLE_DEAD;
+ }
+ else if (gimp_tool_rectangle_coord_outside (rectangle, coords))
+ {
+ /* The cursor is outside of the rectangle, clicking should
+ * create a new rectangle.
+ */
+ return GIMP_TOOL_RECTANGLE_CREATING;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_NORTH_WEST))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_SOUTH_EAST))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_NORTH_EAST))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_SOUTH_WEST))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_WEST))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_LEFT;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_EAST))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_RIGHT;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_NORTH))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_TOP;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_SOUTH))
+ {
+ return GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
+ }
+ else if (gimp_tool_rectangle_coord_on_handle (rectangle,
+ coords,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ return GIMP_TOOL_RECTANGLE_MOVING;
+ }
+ else
+ {
+ return GIMP_TOOL_RECTANGLE_DEAD;
+ }
+}
+
+/* gimp_tool_rectangle_check_function() is needed to deal with
+ * situations where the user drags a corner or edge across one of the
+ * existing edges, thereby changing its function. Ugh.
+ */
+static void
+gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle)
+
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpRectangleFunction function = private->function;
+
+ if (private->x2 < private->x1)
+ {
+ swap_doubles (&private->x1, &private->x2);
+
+ switch (function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_LEFT;
+ break;
+ /* avoid annoying warnings about unhandled enums */
+ default:
+ break;
+ }
+ }
+
+ if (private->y2 < private->y1)
+ {
+ swap_doubles (&private->y1, &private->y2);
+
+ switch (function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
+ break;
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ function = GIMP_TOOL_RECTANGLE_RESIZING_TOP;
+ break;
+ default:
+ break;
+ }
+ }
+
+ gimp_tool_rectangle_set_function (rectangle, function);
+}
+
+/**
+ * gimp_tool_rectangle_coord_outside:
+ *
+ * Returns: %TRUE if the coord is outside the rectangle bounds
+ * including any outside handles.
+ */
+static gboolean
+gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle,
+ const GimpCoords *coord)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpDisplayShell *shell;
+ gboolean narrow_mode = private->narrow_mode;
+ gdouble x1, y1, x2, y2;
+ gdouble x1_b, y1_b, x2_b, y2_b;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+
+ x1_b = x1 - (narrow_mode ? private->corner_handle_w / shell->scale_x : 0);
+ x2_b = x2 + (narrow_mode ? private->corner_handle_w / shell->scale_x : 0);
+ y1_b = y1 - (narrow_mode ? private->corner_handle_h / shell->scale_y : 0);
+ y2_b = y2 + (narrow_mode ? private->corner_handle_h / shell->scale_y : 0);
+
+ return (coord->x < x1_b ||
+ coord->x > x2_b ||
+ coord->y < y1_b ||
+ coord->y > y2_b);
+}
+
+/**
+ * gimp_tool_rectangle_coord_on_handle:
+ *
+ * Returns: %TRUE if the coord is on the handle that corresponds to
+ * @anchor.
+ */
+static gboolean
+gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle,
+ const GimpCoords *coords,
+ GimpHandleAnchor anchor)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpDisplayShell *shell;
+ gdouble x1, y1, x2, y2;
+ gdouble rect_w, rect_h;
+ gdouble handle_x = 0;
+ gdouble handle_y = 0;
+ gdouble handle_width = 0;
+ gdouble handle_height = 0;
+ gint narrow_mode_x_dir = 0;
+ gint narrow_mode_y_dir = 0;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+
+ rect_w = x2 - x1;
+ rect_h = y2 - y1;
+
+ switch (anchor)
+ {
+ case GIMP_HANDLE_ANCHOR_NORTH_WEST:
+ handle_x = x1;
+ handle_y = y1;
+ handle_width = private->corner_handle_w;
+ handle_height = private->corner_handle_h;
+
+ narrow_mode_x_dir = -1;
+ narrow_mode_y_dir = -1;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
+ handle_x = x2;
+ handle_y = y2;
+ handle_width = private->corner_handle_w;
+ handle_height = private->corner_handle_h;
+
+ narrow_mode_x_dir = 1;
+ narrow_mode_y_dir = 1;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH_EAST:
+ handle_x = x2;
+ handle_y = y1;
+ handle_width = private->corner_handle_w;
+ handle_height = private->corner_handle_h;
+
+ narrow_mode_x_dir = 1;
+ narrow_mode_y_dir = -1;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
+ handle_x = x1;
+ handle_y = y2;
+ handle_width = private->corner_handle_w;
+ handle_height = private->corner_handle_h;
+
+ narrow_mode_x_dir = -1;
+ narrow_mode_y_dir = 1;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_WEST:
+ handle_x = x1;
+ handle_y = y1 + rect_h / 2;
+ handle_width = private->corner_handle_w;
+ handle_height = private->left_and_right_handle_h;
+
+ narrow_mode_x_dir = -1;
+ narrow_mode_y_dir = 0;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_EAST:
+ handle_x = x2;
+ handle_y = y1 + rect_h / 2;
+ handle_width = private->corner_handle_w;
+ handle_height = private->left_and_right_handle_h;
+
+ narrow_mode_x_dir = 1;
+ narrow_mode_y_dir = 0;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_NORTH:
+ handle_x = x1 + rect_w / 2;
+ handle_y = y1;
+ handle_width = private->top_and_bottom_handle_w;
+ handle_height = private->corner_handle_h;
+
+ narrow_mode_x_dir = 0;
+ narrow_mode_y_dir = -1;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_SOUTH:
+ handle_x = x1 + rect_w / 2;
+ handle_y = y2;
+ handle_width = private->top_and_bottom_handle_w;
+ handle_height = private->corner_handle_h;
+
+ narrow_mode_x_dir = 0;
+ narrow_mode_y_dir = 1;
+ break;
+
+ case GIMP_HANDLE_ANCHOR_CENTER:
+ handle_x = x1 + rect_w / 2;
+ handle_y = y1 + rect_h / 2;
+
+ if (private->narrow_mode)
+ {
+ handle_width = rect_w * shell->scale_x;
+ handle_height = rect_h * shell->scale_y;
+ }
+ else
+ {
+ handle_width = rect_w * shell->scale_x - private->corner_handle_w * 2;
+ handle_height = rect_h * shell->scale_y - private->corner_handle_h * 2;
+ }
+
+ narrow_mode_x_dir = 0;
+ narrow_mode_y_dir = 0;
+ break;
+ }
+
+ if (private->narrow_mode)
+ {
+ handle_x += narrow_mode_x_dir * handle_width / shell->scale_x;
+ handle_y += narrow_mode_y_dir * handle_height / shell->scale_y;
+ }
+
+ return gimp_canvas_item_on_handle (private->rectangle,
+ coords->x, coords->y,
+ GIMP_HANDLE_SQUARE,
+ handle_x, handle_y,
+ handle_width, handle_height,
+ anchor);
+}
+
+static GimpHandleAnchor
+gimp_tool_rectangle_get_anchor (GimpRectangleFunction function)
+{
+ switch (function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ return GIMP_HANDLE_ANCHOR_NORTH_WEST;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ return GIMP_HANDLE_ANCHOR_NORTH_EAST;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ return GIMP_HANDLE_ANCHOR_SOUTH_WEST;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ return GIMP_HANDLE_ANCHOR_SOUTH_EAST;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ return GIMP_HANDLE_ANCHOR_WEST;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ return GIMP_HANDLE_ANCHOR_EAST;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ return GIMP_HANDLE_ANCHOR_NORTH;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ return GIMP_HANDLE_ANCHOR_SOUTH;
+
+ default:
+ return GIMP_HANDLE_ANCHOR_CENTER;
+ }
+}
+
+static gboolean
+gimp_tool_rectangle_rect_rubber_banding_func (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_CREATING:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
+ return TRUE;
+
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ case GIMP_TOOL_RECTANGLE_DEAD:
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_tool_rectangle_rect_adjusting_func:
+ * @rectangle:
+ *
+ * Returns: %TRUE if the current function is a rectangle adjusting
+ * function.
+ */
+static gboolean
+gimp_tool_rectangle_rect_adjusting_func (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ return (gimp_tool_rectangle_rect_rubber_banding_func (rectangle) ||
+ private->function == GIMP_TOOL_RECTANGLE_MOVING);
+}
+
+/**
+ * gimp_tool_rectangle_get_other_side:
+ * @rectangle: A #GimpToolRectangle.
+ * @other_x: Pointer to double of the other-x double.
+ * @other_y: Pointer to double of the other-y double.
+ *
+ * Calculates pointers to member variables that hold the coordinates
+ * of the opposite side (either the opposite corner or literally the
+ * opposite side), based on the current function. The opposite of a
+ * corner needs two coordinates, the opposite of a side only needs
+ * one.
+ */
+static void
+gimp_tool_rectangle_get_other_side (GimpToolRectangle *rectangle,
+ gdouble **other_x,
+ gdouble **other_y)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ *other_x = &private->x1;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ *other_x = &private->x2;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ default:
+ *other_x = NULL;
+ break;
+ }
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ *other_y = &private->y1;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ *other_y = &private->y2;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ default:
+ *other_y = NULL;
+ break;
+ }
+}
+
+static void
+gimp_tool_rectangle_get_other_side_coord (GimpToolRectangle *rectangle,
+ gdouble *other_side_x,
+ gdouble *other_side_y)
+{
+ gdouble *other_x = NULL;
+ gdouble *other_y = NULL;
+
+ gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y);
+
+ if (other_x)
+ *other_side_x = *other_x;
+ if (other_y)
+ *other_side_y = *other_y;
+}
+
+static void
+gimp_tool_rectangle_set_other_side_coord (GimpToolRectangle *rectangle,
+ gdouble other_side_x,
+ gdouble other_side_y)
+{
+ gdouble *other_x = NULL;
+ gdouble *other_y = NULL;
+
+ gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y);
+
+ if (other_x)
+ *other_x = other_side_x;
+ if (other_y)
+ *other_y = other_side_y;
+
+ gimp_tool_rectangle_check_function (rectangle);
+
+ gimp_tool_rectangle_update_int_rect (rectangle);
+}
+
+/**
+ * gimp_tool_rectangle_apply_coord:
+ * @param: A #GimpToolRectangle.
+ * @coord_x: X of coord.
+ * @coord_y: Y of coord.
+ *
+ * Adjust the rectangle to the new position specified by passed
+ * coordinate, taking fixed_center into account, which means it
+ * expands the rectangle around the center point.
+ */
+static void
+gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle,
+ gdouble coord_x,
+ gdouble coord_y)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ if (private->function == GIMP_TOOL_RECTANGLE_MOVING)
+ {
+ /* Preserve width and height while moving the grab-point to where the
+ * cursor is.
+ */
+ gdouble w = private->x2 - private->x1;
+ gdouble h = private->y2 - private->y1;
+
+ private->x1 = coord_x;
+ private->y1 = coord_y;
+
+ private->x2 = private->x1 + w;
+ private->y2 = private->y1 + h;
+
+ /* We are done already. */
+ return;
+ }
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ private->x1 = coord_x;
+
+ if (private->fixed_center)
+ private->x2 = 2 * private->center_x_on_fixed_center - private->x1;
+
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ private->x2 = coord_x;
+
+ if (private->fixed_center)
+ private->x1 = 2 * private->center_x_on_fixed_center - private->x2;
+
+ break;
+
+ default:
+ break;
+ }
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ private->y1 = coord_y;
+
+ if (private->fixed_center)
+ private->y2 = 2 * private->center_y_on_fixed_center - private->y1;
+
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ private->y2 = coord_y;
+
+ if (private->fixed_center)
+ private->y1 = 2 * private->center_y_on_fixed_center - private->y2;
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gimp_tool_rectangle_setup_snap_offsets (GimpToolRectangle *rectangle,
+ const GimpCoords *coords)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (rectangle);
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gdouble x1, y1, x2, y2;
+ gdouble coord_x, coord_y;
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+ gimp_tool_rectangle_adjust_coord (rectangle,
+ coords->x, coords->y,
+ &coord_x, &coord_y);
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_CREATING:
+ gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x1 - coord_x,
+ y1 - coord_y,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x2 - coord_x,
+ y1 - coord_y,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x1 - coord_x,
+ y2 - coord_y,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x2 - coord_x,
+ y2 - coord_y,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x1 - coord_x, 0,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x2 - coord_x, 0,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ gimp_tool_widget_set_snap_offsets (widget,
+ 0, y1 - coord_y,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ gimp_tool_widget_set_snap_offsets (widget,
+ 0, y2 - coord_y,
+ 0, 0);
+ break;
+
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ gimp_tool_widget_set_snap_offsets (widget,
+ x1 - coord_x,
+ y1 - coord_y,
+ x2 - x1,
+ y2 - y1);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_clamp:
+ * @rectangle: A #GimpToolRectangle.
+ * @clamped_sides: Where to put contrainment information.
+ * @constraint: Constraint to use.
+ * @symmetrically: Whether or not to clamp symmetrically.
+ *
+ * Clamps rectangle inside specified bounds, providing information of
+ * where clamping was done. Can also clamp symmetrically.
+ */
+static void
+gimp_tool_rectangle_clamp (GimpToolRectangle *rectangle,
+ ClampedSide *clamped_sides,
+ GimpRectangleConstraint constraint,
+ gboolean symmetrically)
+{
+ gimp_tool_rectangle_clamp_width (rectangle,
+ clamped_sides,
+ constraint,
+ symmetrically);
+
+ gimp_tool_rectangle_clamp_height (rectangle,
+ clamped_sides,
+ constraint,
+ symmetrically);
+}
+
+/**
+ * gimp_tool_rectangle_clamp_width:
+ * @rectangle: A #GimpToolRectangle.
+ * @clamped_sides: Where to put contrainment information.
+ * @constraint: Constraint to use.
+ * @symmetrically: Whether or not to clamp symmetrically.
+ *
+ * Clamps height of rectangle. Set symmetrically to true when using
+ * for fixed_center:ed rectangles, since that will clamp symmetrically
+ * which is just what is needed.
+ *
+ * When this function constrains, it puts what it constrains in
+ * @constraint. This information is essential when an aspect ratio is
+ * to be applied.
+ */
+static void
+gimp_tool_rectangle_clamp_width (GimpToolRectangle *rectangle,
+ ClampedSide *clamped_sides,
+ GimpRectangleConstraint constraint,
+ gboolean symmetrically)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gint min_x;
+ gint max_x;
+
+ if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
+ return;
+
+ gimp_tool_rectangle_get_constraints (rectangle,
+ &min_x, NULL,
+ &max_x, NULL,
+ constraint);
+ if (private->x1 < min_x)
+ {
+ gdouble dx = min_x - private->x1;
+
+ private->x1 += dx;
+
+ if (symmetrically)
+ private->x2 -= dx;
+
+ if (private->x2 < min_x)
+ private->x2 = min_x;
+
+ if (clamped_sides)
+ *clamped_sides |= CLAMPED_LEFT;
+ }
+
+ if (private->x2 > max_x)
+ {
+ gdouble dx = max_x - private->x2;
+
+ private->x2 += dx;
+
+ if (symmetrically)
+ private->x1 -= dx;
+
+ if (private->x1 > max_x)
+ private->x1 = max_x;
+
+ if (clamped_sides)
+ *clamped_sides |= CLAMPED_RIGHT;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_clamp_height:
+ * @rectangle: A #GimpToolRectangle.
+ * @clamped_sides: Where to put contrainment information.
+ * @constraint: Constraint to use.
+ * @symmetrically: Whether or not to clamp symmetrically.
+ *
+ * Clamps height of rectangle. Set symmetrically to true when using for
+ * fixed_center:ed rectangles, since that will clamp symmetrically which is just
+ * what is needed.
+ *
+ * When this function constrains, it puts what it constrains in
+ * @constraint. This information is essential when an aspect ratio is to be
+ * applied.
+ */
+static void
+gimp_tool_rectangle_clamp_height (GimpToolRectangle *rectangle,
+ ClampedSide *clamped_sides,
+ GimpRectangleConstraint constraint,
+ gboolean symmetrically)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gint min_y;
+ gint max_y;
+
+ if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
+ return;
+
+ gimp_tool_rectangle_get_constraints (rectangle,
+ NULL, &min_y,
+ NULL, &max_y,
+ constraint);
+ if (private->y1 < min_y)
+ {
+ gdouble dy = min_y - private->y1;
+
+ private->y1 += dy;
+
+ if (symmetrically)
+ private->y2 -= dy;
+
+ if (private->y2 < min_y)
+ private->y2 = min_y;
+
+ if (clamped_sides)
+ *clamped_sides |= CLAMPED_TOP;
+ }
+
+ if (private->y2 > max_y)
+ {
+ gdouble dy = max_y - private->y2;
+
+ private->y2 += dy;
+
+ if (symmetrically)
+ private->y1 -= dy;
+
+ if (private->y1 > max_y)
+ private->y1 = max_y;
+
+ if (clamped_sides)
+ *clamped_sides |= CLAMPED_BOTTOM;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_keep_inside:
+ * @rectangle: A #GimpToolRectangle.
+ *
+ * If the rectangle is outside of the canvas, move it into it. If the rectangle is
+ * larger than the canvas in any direction, make it fill the canvas in that direction.
+ */
+static void
+gimp_tool_rectangle_keep_inside (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint)
+{
+ gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
+ gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint);
+}
+
+/**
+ * gimp_tool_rectangle_keep_inside_horizontally:
+ * @rectangle: A #GimpToolRectangle.
+ * @constraint: Constraint to use.
+ *
+ * If the rectangle is outside of the given constraint horizontally, move it
+ * inside. If it is too big to fit inside, make it just as big as the width
+ * limit.
+ */
+static void
+gimp_tool_rectangle_keep_inside_horizontally (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gint min_x;
+ gint max_x;
+
+ if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
+ return;
+
+ gimp_tool_rectangle_get_constraints (rectangle,
+ &min_x, NULL,
+ &max_x, NULL,
+ constraint);
+
+ if (max_x - min_x < private->x2 - private->x1)
+ {
+ private->x1 = min_x;
+ private->x2 = max_x;
+ }
+ else
+ {
+ if (private->x1 < min_x)
+ {
+ gdouble dx = min_x - private->x1;
+
+ private->x1 += dx;
+ private->x2 += dx;
+ }
+ if (private->x2 > max_x)
+ {
+ gdouble dx = max_x - private->x2;
+
+ private->x1 += dx;
+ private->x2 += dx;
+ }
+ }
+}
+
+/**
+ * gimp_tool_rectangle_keep_inside_vertically:
+ * @rectangle: A #GimpToolRectangle.
+ * @constraint: Constraint to use.
+ *
+ * If the rectangle is outside of the given constraint vertically,
+ * move it inside. If it is too big to fit inside, make it just as big
+ * as the width limit.
+ */
+static void
+gimp_tool_rectangle_keep_inside_vertically (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gint min_y;
+ gint max_y;
+
+ if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
+ return;
+
+ gimp_tool_rectangle_get_constraints (rectangle,
+ NULL, &min_y,
+ NULL, &max_y,
+ constraint);
+
+ if (max_y - min_y < private->y2 - private->y1)
+ {
+ private->y1 = min_y;
+ private->y2 = max_y;
+ }
+ else
+ {
+ if (private->y1 < min_y)
+ {
+ gdouble dy = min_y - private->y1;
+
+ private->y1 += dy;
+ private->y2 += dy;
+ }
+ if (private->y2 > max_y)
+ {
+ gdouble dy = max_y - private->y2;
+
+ private->y1 += dy;
+ private->y2 += dy;
+ }
+ }
+}
+
+/**
+ * gimp_tool_rectangle_apply_fixed_width:
+ * @rectangle: A #GimpToolRectangle.
+ * @constraint: Constraint to use.
+ * @width:
+ *
+ * Makes the rectangle have a fixed_width, following the constrainment
+ * rules of fixed widths as well. Please refer to the rectangle tools
+ * spec.
+ */
+static void
+gimp_tool_rectangle_apply_fixed_width (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint,
+ gdouble width)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ /* We always want to center around fixed_center here, since we want the
+ * anchor point to be directly on the opposite side.
+ */
+ private->x1 = private->center_x_on_fixed_center -
+ width / 2;
+ private->x2 = private->x1 + width;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ /* We always want to center around fixed_center here, since we want the
+ * anchor point to be directly on the opposite side.
+ */
+ private->x1 = private->center_x_on_fixed_center -
+ width / 2;
+ private->x2 = private->x1 + width;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Width shall be kept even after constraints, so we move the
+ * rectangle sideways rather than adjusting a side.
+ */
+ gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
+}
+
+/**
+ * gimp_tool_rectangle_apply_fixed_height:
+ * @rectangle: A #GimpToolRectangle.
+ * @constraint: Constraint to use.
+ * @height:
+ *
+ * Makes the rectangle have a fixed_height, following the
+ * constrainment rules of fixed heights as well. Please refer to the
+ * rectangle tools spec.
+ */
+static void
+gimp_tool_rectangle_apply_fixed_height (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint,
+ gdouble height)
+
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ /* We always want to center around fixed_center here, since we
+ * want the anchor point to be directly on the opposite side.
+ */
+ private->y1 = private->center_y_on_fixed_center -
+ height / 2;
+ private->y2 = private->y1 + height;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ /* We always want to center around fixed_center here, since we
+ * want the anchor point to be directly on the opposite side.
+ */
+ private->y1 = private->center_y_on_fixed_center -
+ height / 2;
+ private->y2 = private->y1 + height;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Width shall be kept even after constraints, so we move the
+ * rectangle sideways rather than adjusting a side.
+ */
+ gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint);
+}
+
+/**
+ * gimp_tool_rectangle_apply_aspect:
+ * @rectangle: A #GimpToolRectangle.
+ * @aspect: The desired aspect.
+ * @clamped_sides: Bitfield of sides that have been clamped.
+ *
+ * Adjust the rectangle to the desired aspect.
+ *
+ * Sometimes, a side must not be moved outwards, for example if a the
+ * RIGHT side has been clamped previously, we must not move the RIGHT
+ * side to the right, since that would violate the constraint
+ * again. The clamped_sides bitfield keeps track of sides that have
+ * previously been clamped.
+ *
+ * If fixed_center is used, the function adjusts the aspect by
+ * symmetrically adjusting the left and right, or top and bottom side.
+ */
+static void
+gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle,
+ gdouble aspect,
+ gint clamped_sides)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ gdouble current_w;
+ gdouble current_h;
+ gdouble current_aspect;
+ SideToResize side_to_resize = SIDE_TO_RESIZE_NONE;
+
+ current_w = private->x2 - private->x1;
+ current_h = private->y2 - private->y1;
+
+ current_aspect = (gdouble) current_w / (gdouble) current_h;
+
+ /* Do we have to do anything? */
+ if (current_aspect == aspect)
+ return;
+
+ if (private->fixed_center)
+ {
+ /* We may only adjust the sides symmetrically to get desired aspect. */
+ if (current_aspect > aspect)
+ {
+ /* We prefer to use top and bottom (since that will make the
+ * cursor remain on the rectangle edge), unless that is what
+ * the user has grabbed.
+ */
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ if (! (clamped_sides & CLAMPED_TOP) &&
+ ! (clamped_sides & CLAMPED_BOTTOM))
+ {
+ side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
+ }
+ else
+ {
+ side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
+ }
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ default:
+ side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
+ break;
+ }
+ }
+ else /* (current_aspect < aspect) */
+ {
+ /* We prefer to use left and right (since that will make the
+ * cursor remain on the rectangle edge), unless that is what
+ * the user has grabbed.
+ */
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ if (! (clamped_sides & CLAMPED_LEFT) &&
+ ! (clamped_sides & CLAMPED_RIGHT))
+ {
+ side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
+ }
+ else
+ {
+ side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
+ }
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ default:
+ side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
+ break;
+ }
+ }
+ }
+ else if (current_aspect > aspect)
+ {
+ /* We can safely pick LEFT or RIGHT, since using those sides
+ * will make the rectangle smaller, so we don't need to check
+ * for clamped_sides. We may only use TOP and BOTTOM if not
+ * those sides have been clamped, since using them will make the
+ * rectangle bigger.
+ */
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ if (! (clamped_sides & CLAMPED_TOP))
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ else
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ if (! (clamped_sides & CLAMPED_TOP))
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ else
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ if (! (clamped_sides & CLAMPED_BOTTOM))
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ else
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ if (! (clamped_sides & CLAMPED_BOTTOM))
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ else
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ if (! (clamped_sides & CLAMPED_TOP) &&
+ ! (clamped_sides & CLAMPED_BOTTOM))
+ side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
+ else
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ if (! (clamped_sides & CLAMPED_TOP) &&
+ ! (clamped_sides & CLAMPED_BOTTOM))
+ side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
+ else
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ default:
+ if (! (clamped_sides & CLAMPED_BOTTOM))
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ else if (! (clamped_sides & CLAMPED_RIGHT))
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ else if (! (clamped_sides & CLAMPED_TOP))
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ else if (! (clamped_sides & CLAMPED_LEFT))
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ break;
+ }
+ }
+ else /* (current_aspect < aspect) */
+ {
+ /* We can safely pick TOP or BOTTOM, since using those sides
+ * will make the rectangle smaller, so we don't need to check
+ * for clamped_sides. We may only use LEFT and RIGHT if not
+ * those sides have been clamped, since using them will make the
+ * rectangle bigger.
+ */
+ switch (private->function)
+ {
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
+ if (! (clamped_sides & CLAMPED_LEFT))
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ else
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
+ if (! (clamped_sides & CLAMPED_RIGHT))
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ else
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
+ if (! (clamped_sides & CLAMPED_LEFT))
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ else
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
+ if (! (clamped_sides & CLAMPED_RIGHT))
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ else
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
+ if (! (clamped_sides & CLAMPED_LEFT) &&
+ ! (clamped_sides & CLAMPED_RIGHT))
+ side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
+ else
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
+ if (! (clamped_sides & CLAMPED_LEFT) &&
+ ! (clamped_sides & CLAMPED_RIGHT))
+ side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
+ else
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
+ case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
+ side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
+ break;
+
+ case GIMP_TOOL_RECTANGLE_MOVING:
+ default:
+ if (! (clamped_sides & CLAMPED_BOTTOM))
+ side_to_resize = SIDE_TO_RESIZE_BOTTOM;
+ else if (! (clamped_sides & CLAMPED_RIGHT))
+ side_to_resize = SIDE_TO_RESIZE_RIGHT;
+ else if (! (clamped_sides & CLAMPED_TOP))
+ side_to_resize = SIDE_TO_RESIZE_TOP;
+ else if (! (clamped_sides & CLAMPED_LEFT))
+ side_to_resize = SIDE_TO_RESIZE_LEFT;
+ break;
+ }
+ }
+
+ /* We now know what side(s) we should resize, so now we just solve
+ * the aspect equation for that side(s).
+ */
+ switch (side_to_resize)
+ {
+ case SIDE_TO_RESIZE_NONE:
+ return;
+
+ case SIDE_TO_RESIZE_LEFT:
+ private->x1 = private->x2 - aspect * current_h;
+ break;
+
+ case SIDE_TO_RESIZE_RIGHT:
+ private->x2 = private->x1 + aspect * current_h;
+ break;
+
+ case SIDE_TO_RESIZE_TOP:
+ private->y1 = private->y2 - current_w / aspect;
+ break;
+
+ case SIDE_TO_RESIZE_BOTTOM:
+ private->y2 = private->y1 + current_w / aspect;
+ break;
+
+ case SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY:
+ {
+ gdouble correct_h = current_w / aspect;
+
+ private->y1 = private->center_y_on_fixed_center - correct_h / 2;
+ private->y2 = private->y1 + correct_h;
+ }
+ break;
+
+ case SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY:
+ {
+ gdouble correct_w = current_h * aspect;
+
+ private->x1 = private->center_x_on_fixed_center - correct_w / 2;
+ private->x2 = private->x1 + correct_w;
+ }
+ break;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_update_with_coord:
+ * @rectangle: A #GimpToolRectangle.
+ * @new_x: New X-coordinate in the context of the current function.
+ * @new_y: New Y-coordinate in the context of the current function.
+ *
+ * The core rectangle adjustment function. It updates the rectangle
+ * for the passed cursor coordinate, taking current function and tool
+ * options into account. It also updates the current
+ * private->function if necessary.
+ */
+static void
+gimp_tool_rectangle_update_with_coord (GimpToolRectangle *rectangle,
+ gdouble new_x,
+ gdouble new_y)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ /* Move the corner or edge the user currently has grabbed. */
+ gimp_tool_rectangle_apply_coord (rectangle, new_x, new_y);
+
+ /* Update private->function. The function changes if the user
+ * "flips" the rectangle.
+ */
+ gimp_tool_rectangle_check_function (rectangle);
+
+ /* Clamp the rectangle if necessary */
+ gimp_tool_rectangle_handle_general_clamping (rectangle);
+
+ /* If the rectangle is being moved, do not run through any further
+ * rectangle adjusting functions since it's shape should not change
+ * then.
+ */
+ if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
+ {
+ gimp_tool_rectangle_apply_fixed_rule (rectangle);
+ }
+
+ gimp_tool_rectangle_update_int_rect (rectangle);
+}
+
+static void
+gimp_tool_rectangle_apply_fixed_rule (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpRectangleConstraint constraint_to_use;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+ image = gimp_display_get_image (shell->display);
+
+ /* Calculate what constraint to use when needed. */
+ constraint_to_use = gimp_tool_rectangle_get_constraint (rectangle);
+
+ if (private->fixed_rule_active &&
+ private->fixed_rule == GIMP_RECTANGLE_FIXED_ASPECT)
+ {
+ gdouble aspect;
+
+ aspect = CLAMP (private->aspect_numerator /
+ private->aspect_denominator,
+ 1.0 / gimp_image_get_height (image),
+ gimp_image_get_width (image));
+
+ if (constraint_to_use == GIMP_RECTANGLE_CONSTRAIN_NONE)
+ {
+ gimp_tool_rectangle_apply_aspect (rectangle,
+ aspect,
+ CLAMPED_NONE);
+ }
+ else
+ {
+ if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
+ {
+ ClampedSide clamped_sides = CLAMPED_NONE;
+
+ gimp_tool_rectangle_apply_aspect (rectangle,
+ aspect,
+ clamped_sides);
+
+ /* After we have applied aspect, we might have taken the
+ * rectangle outside of constraint, so clamp and apply
+ * aspect again. We will get the right result this time,
+ * since 'clamped_sides' will be setup correctly now.
+ */
+ gimp_tool_rectangle_clamp (rectangle,
+ &clamped_sides,
+ constraint_to_use,
+ private->fixed_center);
+
+ gimp_tool_rectangle_apply_aspect (rectangle,
+ aspect,
+ clamped_sides);
+ }
+ else
+ {
+ gimp_tool_rectangle_apply_aspect (rectangle,
+ aspect,
+ CLAMPED_NONE);
+
+ gimp_tool_rectangle_keep_inside (rectangle,
+ constraint_to_use);
+ }
+ }
+ }
+ else if (private->fixed_rule_active &&
+ private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE)
+ {
+ gimp_tool_rectangle_apply_fixed_width (rectangle,
+ constraint_to_use,
+ private->desired_fixed_size_width);
+ gimp_tool_rectangle_apply_fixed_height (rectangle,
+ constraint_to_use,
+ private->desired_fixed_size_height);
+ }
+ else if (private->fixed_rule_active &&
+ private->fixed_rule == GIMP_RECTANGLE_FIXED_WIDTH)
+ {
+ gimp_tool_rectangle_apply_fixed_width (rectangle,
+ constraint_to_use,
+ private->desired_fixed_width);
+ }
+ else if (private->fixed_rule_active &&
+ private->fixed_rule == GIMP_RECTANGLE_FIXED_HEIGHT)
+ {
+ gimp_tool_rectangle_apply_fixed_height (rectangle,
+ constraint_to_use,
+ private->desired_fixed_height);
+ }
+}
+
+/**
+ * gimp_tool_rectangle_get_constraints:
+ * @rectangle: A #GimpToolRectangle.
+ * @min_x:
+ * @min_y:
+ * @max_x:
+ * @max_y: Pointers of where to put constraints. NULL allowed.
+ * @constraint: Whether to return image or layer constraints.
+ *
+ * Calculates constraint coordinates for image or layer.
+ */
+static void
+gimp_tool_rectangle_get_constraints (GimpToolRectangle *rectangle,
+ gint *min_x,
+ gint *min_y,
+ gint *max_x,
+ gint *max_y,
+ GimpRectangleConstraint constraint)
+{
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ gint min_x_dummy;
+ gint min_y_dummy;
+ gint max_x_dummy;
+ gint max_y_dummy;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+ image = gimp_display_get_image (shell->display);
+
+ if (! min_x) min_x = &min_x_dummy;
+ if (! min_y) min_y = &min_y_dummy;
+ if (! max_x) max_x = &max_x_dummy;
+ if (! max_y) max_y = &max_y_dummy;
+
+ *min_x = 0;
+ *min_y = 0;
+ *max_x = 0;
+ *max_y = 0;
+
+ switch (constraint)
+ {
+ case GIMP_RECTANGLE_CONSTRAIN_IMAGE:
+ if (image)
+ {
+ *min_x = 0;
+ *min_y = 0;
+ *max_x = gimp_image_get_width (image);
+ *max_y = gimp_image_get_height (image);
+ }
+ break;
+
+ case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE:
+ if (image)
+ {
+ GimpItem *item = GIMP_ITEM (gimp_image_get_active_drawable (image));
+
+ if (item)
+ {
+ gimp_item_get_offset (item, min_x, min_y);
+ *max_x = *min_x + gimp_item_get_width (item);
+ *max_y = *min_y + gimp_item_get_height (item);
+ }
+ }
+ break;
+
+ default:
+ g_warning ("Invalid rectangle constraint.\n");
+ return;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_handle_general_clamping:
+ * @rectangle: A #GimpToolRectangle.
+ *
+ * Make sure that constraints are applied to the rectangle, either by
+ * manually doing it, or by looking at the rectangle tool options and
+ * concluding it will be done later.
+ */
+static void
+gimp_tool_rectangle_handle_general_clamping (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+ GimpRectangleConstraint constraint;
+
+ constraint = gimp_tool_rectangle_get_constraint (rectangle);
+
+ /* fixed_aspect takes care of clamping by it self, so just return in
+ * case that is in use. Also return if no constraints should be
+ * enforced.
+ */
+ if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
+ return;
+
+ if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
+ {
+ gimp_tool_rectangle_clamp (rectangle,
+ NULL,
+ constraint,
+ private->fixed_center);
+ }
+ else
+ {
+ gimp_tool_rectangle_keep_inside (rectangle, constraint);
+ }
+}
+
+/**
+ * gimp_tool_rectangle_update_int_rect:
+ * @rectangle:
+ *
+ * Update integer representation of rectangle.
+ **/
+static void
+gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle)
+{
+ GimpToolRectanglePrivate *private = rectangle->private;
+
+ private->x1_int = SIGNED_ROUND (private->x1);
+ private->y1_int = SIGNED_ROUND (private->y1);
+
+ if (gimp_tool_rectangle_rect_rubber_banding_func (rectangle))
+ {
+ private->width_int = (gint) SIGNED_ROUND (private->x2) - private->x1_int;
+ private->height_int = (gint) SIGNED_ROUND (private->y2) - private->y1_int;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_adjust_coord:
+ * @rectangle:
+ * @ccoord_x_input:
+ * @ccoord_x_input:
+ * @ccoord_x_output:
+ * @ccoord_x_output:
+ *
+ * Transforms a coordinate to better fit the public behaviour of the
+ * rectangle.
+ */
+static void
+gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle,
+ gdouble coord_x_input,
+ gdouble coord_y_input,
+ gdouble *coord_x_output,
+ gdouble *coord_y_output)
+{
+ GimpToolRectanglePrivate *priv = rectangle->private;
+
+ switch (priv->precision)
+ {
+ case GIMP_RECTANGLE_PRECISION_INT:
+ *coord_x_output = RINT (coord_x_input);
+ *coord_y_output = RINT (coord_y_input);
+ break;
+
+ case GIMP_RECTANGLE_PRECISION_DOUBLE:
+ default:
+ *coord_x_output = coord_x_input;
+ *coord_y_output = coord_y_input;
+ break;
+ }
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_rectangle_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_RECTANGLE,
+ "shell", shell,
+ NULL);
+}
+
+GimpRectangleFunction
+gimp_tool_rectangle_get_function (GimpToolRectangle *rectangle)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle),
+ GIMP_TOOL_RECTANGLE_DEAD);
+
+ return rectangle->private->function;
+}
+
+void
+gimp_tool_rectangle_set_function (GimpToolRectangle *rectangle,
+ GimpRectangleFunction function)
+{
+ GimpToolRectanglePrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+
+ private = rectangle->private;
+
+ if (private->function != function)
+ {
+ private->function = function;
+
+ gimp_tool_rectangle_changed (GIMP_TOOL_WIDGET (rectangle));
+ }
+}
+
+void
+gimp_tool_rectangle_set_constraint (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint)
+{
+ GimpToolRectanglePrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+
+ private = rectangle->private;
+
+ if (constraint != private->constraint)
+ {
+ g_object_freeze_notify (G_OBJECT (rectangle));
+
+ private->constraint = constraint;
+ g_object_notify (G_OBJECT (rectangle), "constraint");
+
+ gimp_tool_rectangle_clamp (rectangle, NULL, constraint, FALSE);
+
+ g_object_thaw_notify (G_OBJECT (rectangle));
+
+ gimp_tool_rectangle_change_complete (rectangle);
+ }
+}
+
+GimpRectangleConstraint
+gimp_tool_rectangle_get_constraint (GimpToolRectangle *rectangle)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), 0);
+
+ return rectangle->private->constraint;
+}
+
+/**
+ * gimp_tool_rectangle_get_public_rect:
+ * @rectangle:
+ * @x1:
+ * @y1:
+ * @x2:
+ * @y2:
+ *
+ * This function returns the rectangle as it appears to be publicly
+ * (based on integer or double precision-mode).
+ **/
+void
+gimp_tool_rectangle_get_public_rect (GimpToolRectangle *rectangle,
+ gdouble *x1,
+ gdouble *y1,
+ gdouble *x2,
+ gdouble *y2)
+{
+ GimpToolRectanglePrivate *priv;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+ g_return_if_fail (x1 != NULL);
+ g_return_if_fail (y1 != NULL);
+ g_return_if_fail (x2 != NULL);
+ g_return_if_fail (y2 != NULL);
+
+ priv = rectangle->private;
+
+ switch (priv->precision)
+ {
+ case GIMP_RECTANGLE_PRECISION_INT:
+ *x1 = priv->x1_int;
+ *y1 = priv->y1_int;
+ *x2 = priv->x1_int + priv->width_int;
+ *y2 = priv->y1_int + priv->height_int;
+ break;
+
+ case GIMP_RECTANGLE_PRECISION_DOUBLE:
+ default:
+ *x1 = priv->x1;
+ *y1 = priv->y1;
+ *x2 = priv->x2;
+ *y2 = priv->y2;
+ break;
+ }
+}
+
+/**
+ * gimp_tool_rectangle_pending_size_set:
+ * @width_property: Option property to set to pending rectangle width.
+ * @height_property: Option property to set to pending rectangle height.
+ *
+ * Sets specified rectangle tool options properties to the width and
+ * height of the current pending rectangle.
+ */
+void
+gimp_tool_rectangle_pending_size_set (GimpToolRectangle *rectangle,
+ GObject *object,
+ const gchar *width_property,
+ const gchar *height_property)
+{
+ GimpToolRectanglePrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+ g_return_if_fail (width_property != NULL);
+ g_return_if_fail (height_property != NULL);
+
+ private = rectangle->private;
+
+ g_object_set (object,
+ width_property, MAX (private->x2 - private->x1, 1.0),
+ height_property, MAX (private->y2 - private->y1, 1.0),
+ NULL);
+}
+
+/**
+ * gimp_tool_rectangle_constraint_size_set:
+ * @width_property: Option property to set to current constraint width.
+ * @height_property: Option property to set to current constraint height.
+ *
+ * Sets specified rectangle tool options properties to the width and
+ * height of the current constraint size.
+ */
+void
+gimp_tool_rectangle_constraint_size_set (GimpToolRectangle *rectangle,
+ GObject *object,
+ const gchar *width_property,
+ const gchar *height_property)
+{
+ GimpDisplayShell *shell;
+ GimpContext *context;
+ GimpImage *image;
+ gdouble width;
+ gdouble height;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+ context = gimp_get_user_context (shell->display->gimp);
+ image = gimp_context_get_image (context);
+
+ if (! image)
+ {
+ width = 1.0;
+ height = 1.0;
+ }
+ else
+ {
+ GimpRectangleConstraint constraint;
+
+ constraint = gimp_tool_rectangle_get_constraint (rectangle);
+
+ switch (constraint)
+ {
+ case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE:
+ {
+ GimpItem *item = GIMP_ITEM (gimp_image_get_active_layer (image));
+
+ if (! item)
+ {
+ width = 1.0;
+ height = 1.0;
+ }
+ else
+ {
+ width = gimp_item_get_width (item);
+ height = gimp_item_get_height (item);
+ }
+ }
+ break;
+
+ case GIMP_RECTANGLE_CONSTRAIN_IMAGE:
+ default:
+ {
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+ }
+ break;
+ }
+ }
+
+ g_object_set (object,
+ width_property, width,
+ height_property, height,
+ NULL);
+}
+
+/**
+ * gimp_tool_rectangle_rectangle_is_first:
+ * @rectangle:
+ *
+ * Returns: %TRUE if the user is creating the first rectangle with
+ * this instance from scratch, %FALSE if modifying an existing
+ * rectangle, or creating a new rectangle, discarding the existing
+ * one. This function is only meaningful in _motion and
+ * _button_release.
+ */
+gboolean
+gimp_tool_rectangle_rectangle_is_first (GimpToolRectangle *rectangle)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);
+
+ return rectangle->private->is_first;
+}
+
+/**
+ * gimp_tool_rectangle_rectangle_is_new:
+ * @rectangle:
+ *
+ * Returns: %TRUE if the user is creating a new rectangle from
+ * scratch, %FALSE if modifying n previously existing rectangle. This
+ * function is only meaningful in _motion and _button_release.
+ */
+gboolean
+gimp_tool_rectangle_rectangle_is_new (GimpToolRectangle *rectangle)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);
+
+ return rectangle->private->is_new;
+}
+
+/**
+ * gimp_tool_rectangle_point_in_rectangle:
+ * @rectangle:
+ * @x: X-coord of point to test (in image coordinates)
+ * @y: Y-coord of point to test (in image coordinates)
+ *
+ * Returns: %TRUE if the passed point was within the rectangle
+ **/
+gboolean
+gimp_tool_rectangle_point_in_rectangle (GimpToolRectangle *rectangle,
+ gdouble x,
+ gdouble y)
+{
+ gdouble x1, y1, x2, y2;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);
+
+ gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
+
+ return (x >= x1 && x <= x2 &&
+ y >= y1 && y <= y2);
+}
+
+/**
+ * gimp_tool_rectangle_frame_item:
+ * @rectangle: a #GimpToolRectangle interface
+ * @item: a #GimpItem attached to the image on which a
+ * rectangle is being shown.
+ *
+ * Convenience function to set the corners of the rectangle to
+ * match the bounds of the specified item. The rectangle interface
+ * must be active (i.e., showing a rectangle), and the item must be
+ * attached to the image on which the rectangle is active.
+ **/
+void
+gimp_tool_rectangle_frame_item (GimpToolRectangle *rectangle,
+ GimpItem *item)
+{
+ GimpDisplayShell *shell;
+ gint offset_x;
+ gint offset_y;
+ gint width;
+ gint height;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+
+ g_return_if_fail (gimp_display_get_image (shell->display) ==
+ gimp_item_get_image (item));
+
+ width = gimp_item_get_width (item);
+ height = gimp_item_get_height (item);
+
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_CREATING);
+
+ g_object_set (rectangle,
+ "x1", (gdouble) offset_x,
+ "y1", (gdouble) offset_y,
+ "x2", (gdouble) (offset_x + width),
+ "y2", (gdouble) (offset_y + height),
+ NULL);
+
+ /* kludge to force handle sizes to update. This call may be harmful
+ * if this function is ever moved out of the text tool code.
+ */
+ gimp_tool_rectangle_set_constraint (rectangle, GIMP_RECTANGLE_CONSTRAIN_NONE);
+}
+
+void
+gimp_tool_rectangle_auto_shrink (GimpToolRectangle *rectangle,
+ gboolean shrink_merged)
+{
+ GimpToolRectanglePrivate *private;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpPickable *pickable;
+ gint offset_x = 0;
+ gint offset_y = 0;
+ gint x1, y1;
+ gint x2, y2;
+ gint shrunk_x;
+ gint shrunk_y;
+ gint shrunk_width;
+ gint shrunk_height;
+
+ g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
+
+ private = rectangle->private;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
+ image = gimp_display_get_image (shell->display);
+
+ if (shrink_merged)
+ {
+ pickable = GIMP_PICKABLE (image);
+
+ x1 = private->x1;
+ y1 = private->y1;
+ x2 = private->x2;
+ y2 = private->y2;
+ }
+ else
+ {
+ pickable = GIMP_PICKABLE (gimp_image_get_active_drawable (image));
+
+ if (! pickable)
+ return;
+
+ gimp_item_get_offset (GIMP_ITEM (pickable), &offset_x, &offset_y);
+
+ x1 = private->x1 - offset_x;
+ y1 = private->y1 - offset_y;
+ x2 = private->x2 - offset_x;
+ y2 = private->y2 - offset_y;
+ }
+
+ switch (gimp_pickable_auto_shrink (pickable,
+ x1, y1, x2 - x1, y2 - y1,
+ &shrunk_x,
+ &shrunk_y,
+ &shrunk_width,
+ &shrunk_height))
+ {
+ case GIMP_AUTO_SHRINK_SHRINK:
+ {
+ GimpRectangleFunction original_function = private->function;
+
+ private->function = GIMP_TOOL_RECTANGLE_AUTO_SHRINK;
+
+ private->x1 = offset_x + shrunk_x;
+ private->y1 = offset_y + shrunk_y;
+ private->x2 = offset_x + shrunk_x + shrunk_width;
+ private->y2 = offset_y + shrunk_y + shrunk_height;
+
+ gimp_tool_rectangle_update_int_rect (rectangle);
+
+ gimp_tool_rectangle_change_complete (rectangle);
+
+ private->function = original_function;
+
+ gimp_tool_rectangle_update_options (rectangle);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/app/display/gimptoolrectangle.h b/app/display/gimptoolrectangle.h
new file mode 100644
index 0000000..361839a
--- /dev/null
+++ b/app/display/gimptoolrectangle.h
@@ -0,0 +1,124 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolrectangle.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpRectangleTool
+ * Copyright (C) 2007 Martin Nordholts
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_RECTANGLE_H__
+#define __GIMP_TOOL_RECTANGLE_H__
+
+
+#include "gimptoolwidget.h"
+
+
+typedef enum
+{
+ GIMP_TOOL_RECTANGLE_DEAD,
+ GIMP_TOOL_RECTANGLE_CREATING,
+ GIMP_TOOL_RECTANGLE_MOVING,
+ GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT,
+ GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT,
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT,
+ GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
+ GIMP_TOOL_RECTANGLE_RESIZING_LEFT,
+ GIMP_TOOL_RECTANGLE_RESIZING_RIGHT,
+ GIMP_TOOL_RECTANGLE_RESIZING_TOP,
+ GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM,
+ GIMP_TOOL_RECTANGLE_AUTO_SHRINK,
+ GIMP_TOOL_RECTANGLE_EXECUTING,
+ GIMP_N_TOOL_RECTANGLE_FUNCTIONS
+} GimpRectangleFunction;
+
+
+#define GIMP_TYPE_TOOL_RECTANGLE (gimp_tool_rectangle_get_type ())
+#define GIMP_TOOL_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_RECTANGLE, GimpToolRectangle))
+#define GIMP_TOOL_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_RECTANGLE, GimpToolRectangleClass))
+#define GIMP_IS_TOOL_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_RECTANGLE))
+#define GIMP_IS_TOOL_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_RECTANGLE))
+#define GIMP_TOOL_RECTANGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_RECTANGLE, GimpToolRectangleClass))
+
+
+typedef struct _GimpToolRectangle GimpToolRectangle;
+typedef struct _GimpToolRectanglePrivate GimpToolRectanglePrivate;
+typedef struct _GimpToolRectangleClass GimpToolRectangleClass;
+
+struct _GimpToolRectangle
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolRectanglePrivate *private;
+};
+
+struct _GimpToolRectangleClass
+{
+ GimpToolWidgetClass parent_class;
+
+ /* signals */
+
+ gboolean (* change_complete) (GimpToolRectangle *rectangle);
+};
+
+
+GType gimp_tool_rectangle_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_rectangle_new (GimpDisplayShell *shell);
+
+GimpRectangleFunction
+ gimp_tool_rectangle_get_function (GimpToolRectangle *rectangle);
+void gimp_tool_rectangle_set_function (GimpToolRectangle *rectangle,
+ GimpRectangleFunction function);
+
+void gimp_tool_rectangle_set_constraint (GimpToolRectangle *rectangle,
+ GimpRectangleConstraint constraint);
+GimpRectangleConstraint
+ gimp_tool_rectangle_get_constraint (GimpToolRectangle *rectangle);
+
+void gimp_tool_rectangle_get_public_rect (GimpToolRectangle *rectangle,
+ gdouble *pub_x1,
+ gdouble *pub_y1,
+ gdouble *pub_x2,
+ gdouble *pub_y2);
+
+void gimp_tool_rectangle_pending_size_set (GimpToolRectangle *rectangle,
+ GObject *object,
+ const gchar *width_property,
+ const gchar *height_property);
+
+void gimp_tool_rectangle_constraint_size_set
+ (GimpToolRectangle *rectangle,
+ GObject *object,
+ const gchar *width_property,
+ const gchar *height_property);
+
+gboolean gimp_tool_rectangle_rectangle_is_first
+ (GimpToolRectangle *rectangle);
+gboolean gimp_tool_rectangle_rectangle_is_new (GimpToolRectangle *rectangle);
+gboolean gimp_tool_rectangle_point_in_rectangle
+ (GimpToolRectangle *rectangle,
+ gdouble x,
+ gdouble y);
+
+void gimp_tool_rectangle_frame_item (GimpToolRectangle *rectangle,
+ GimpItem *item);
+void gimp_tool_rectangle_auto_shrink (GimpToolRectangle *rectrectangle,
+ gboolean shrink_merged);
+
+
+#endif /* __GIMP_TOOL_RECTANGLE_H__ */
diff --git a/app/display/gimptoolrotategrid.c b/app/display/gimptoolrotategrid.c
new file mode 100644
index 0000000..c583490
--- /dev/null
+++ b/app/display/gimptoolrotategrid.c
@@ -0,0 +1,330 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolrotategrid.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimptoolrotategrid.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ANGLE
+};
+
+
+struct _GimpToolRotateGridPrivate
+{
+ gdouble angle;
+
+ gboolean rotate_grab;
+ gdouble real_angle;
+ gdouble last_x;
+ gdouble last_y;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_rotate_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_rotate_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint gimp_tool_rotate_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_rotate_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolRotateGrid, gimp_tool_rotate_grid,
+ GIMP_TYPE_TOOL_TRANSFORM_GRID)
+
+#define parent_class gimp_tool_rotate_grid_parent_class
+
+
+static void
+gimp_tool_rotate_grid_class_init (GimpToolRotateGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->set_property = gimp_tool_rotate_grid_set_property;
+ object_class->get_property = gimp_tool_rotate_grid_get_property;
+
+ widget_class->button_press = gimp_tool_rotate_grid_button_press;
+ widget_class->motion = gimp_tool_rotate_grid_motion;
+
+ g_object_class_install_property (object_class, PROP_ANGLE,
+ g_param_spec_double ("angle",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_rotate_grid_init (GimpToolRotateGrid *grid)
+{
+ grid->private = gimp_tool_rotate_grid_get_instance_private (grid);
+}
+
+static void
+gimp_tool_rotate_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (object);
+ GimpToolRotateGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_ANGLE:
+ private->angle = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_rotate_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (object);
+ GimpToolRotateGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_ANGLE:
+ g_value_set_double (value, private->angle);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint
+gimp_tool_rotate_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (widget);
+ GimpToolRotateGridPrivate *private = grid->private;
+ GimpTransformHandle handle;
+
+ handle = GIMP_TOOL_WIDGET_CLASS (parent_class)->button_press (widget,
+ coords, time,
+ state,
+ press_type);
+
+ if (handle == GIMP_TRANSFORM_HANDLE_ROTATION)
+ {
+ private->rotate_grab = TRUE;
+ private->real_angle = private->angle;
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+ }
+ else
+ {
+ private->rotate_grab = FALSE;
+ }
+
+ return handle;
+}
+
+void
+gimp_tool_rotate_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (widget);
+ GimpToolRotateGridPrivate *private = grid->private;
+ gdouble angle1, angle2, angle;
+ gdouble pivot_x, pivot_y;
+ gdouble x1, y1, x2, y2;
+ gboolean constrain;
+ GimpMatrix3 transform;
+
+ if (! private->rotate_grab)
+ {
+ gdouble old_pivot_x;
+ gdouble old_pivot_y;
+
+ g_object_get (widget,
+ "pivot-x", &old_pivot_x,
+ "pivot-y", &old_pivot_y,
+ NULL);
+
+ g_object_freeze_notify (G_OBJECT (widget));
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->motion (widget,
+ coords, time, state);
+
+ g_object_get (widget,
+ "pivot-x", &pivot_x,
+ "pivot-y", &pivot_y,
+ NULL);
+
+ if (old_pivot_x != pivot_x ||
+ old_pivot_y != pivot_y)
+ {
+ gimp_matrix3_identity (&transform);
+ gimp_transform_matrix_rotate_center (&transform,
+ pivot_x, pivot_y,
+ private->angle);
+
+ g_object_set (widget,
+ "transform", &transform,
+ NULL);
+ }
+
+ g_object_thaw_notify (G_OBJECT (widget));
+
+ return;
+ }
+
+ g_object_get (widget,
+ "pivot-x", &pivot_x,
+ "pivot-y", &pivot_y,
+ "constrain-rotate", &constrain,
+ NULL);
+
+ x1 = coords->x - pivot_x;
+ x2 = private->last_x - pivot_x;
+ y1 = pivot_y - coords->y;
+ y2 = pivot_y - private->last_y;
+
+ /* find the first angle */
+ angle1 = atan2 (y1, x1);
+
+ /* find the angle */
+ angle2 = atan2 (y2, x2);
+
+ angle = angle2 - angle1;
+
+ if (angle > G_PI || angle < -G_PI)
+ angle = angle2 - ((angle1 < 0) ? 2.0 * G_PI + angle1 : angle1 - 2.0 * G_PI);
+
+ /* increment the transform tool's angle */
+ private->real_angle += angle;
+
+ /* limit the angle to between -180 and 180 degrees */
+ if (private->real_angle < - G_PI)
+ {
+ private->real_angle += 2.0 * G_PI;
+ }
+ else if (private->real_angle > G_PI)
+ {
+ private->real_angle -= 2.0 * G_PI;
+ }
+
+ /* constrain the angle to 15-degree multiples if ctrl is held down */
+#define FIFTEEN_DEG (G_PI / 12.0)
+
+ if (constrain)
+ {
+ angle = FIFTEEN_DEG * (gint) ((private->real_angle +
+ FIFTEEN_DEG / 2.0) / FIFTEEN_DEG);
+ }
+ else
+ {
+ angle = private->real_angle;
+ }
+
+ gimp_matrix3_identity (&transform);
+ gimp_transform_matrix_rotate_center (&transform, pivot_x, pivot_y, angle);
+
+ g_object_set (widget,
+ "transform", &transform,
+ "angle", angle,
+ NULL);
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_rotate_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble pivot_x,
+ gdouble pivot_y,
+ gdouble angle)
+{
+ GimpMatrix3 transform;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ gimp_matrix3_identity (&transform);
+ gimp_transform_matrix_rotate_center (&transform, pivot_x, pivot_y, angle);
+
+ return g_object_new (GIMP_TYPE_TOOL_ROTATE_GRID,
+ "shell", shell,
+ "transform", &transform,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "pivot-x", pivot_x,
+ "pivot-y", pivot_y,
+ "angle", angle,
+ NULL);
+}
diff --git a/app/display/gimptoolrotategrid.h b/app/display/gimptoolrotategrid.h
new file mode 100644
index 0000000..2254c8d
--- /dev/null
+++ b/app/display/gimptoolrotategrid.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolrotategrid.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_ROTATE_GRID_H__
+#define __GIMP_TOOL_ROTATE_GRID_H__
+
+
+#include "gimptooltransformgrid.h"
+
+
+#define GIMP_TYPE_TOOL_ROTATE_GRID (gimp_tool_rotate_grid_get_type ())
+#define GIMP_TOOL_ROTATE_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGrid))
+#define GIMP_TOOL_ROTATE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGridClass))
+#define GIMP_IS_TOOL_ROTATE_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_ROTATE_GRID))
+#define GIMP_IS_TOOL_ROTATE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_ROTATE_GRID))
+#define GIMP_TOOL_ROTATE_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGridClass))
+
+
+typedef struct _GimpToolRotateGrid GimpToolRotateGrid;
+typedef struct _GimpToolRotateGridPrivate GimpToolRotateGridPrivate;
+typedef struct _GimpToolRotateGridClass GimpToolRotateGridClass;
+
+struct _GimpToolRotateGrid
+{
+ GimpToolTransformGrid parent_instance;
+
+ GimpToolRotateGridPrivate *private;
+};
+
+struct _GimpToolRotateGridClass
+{
+ GimpToolTransformGridClass parent_class;
+};
+
+
+GType gimp_tool_rotate_grid_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_rotate_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble pivot_x,
+ gdouble pivot_y,
+ gdouble angle);
+
+
+#endif /* __GIMP_TOOL_ROTATE_GRID_H__ */
diff --git a/app/display/gimptoolsheargrid.c b/app/display/gimptoolsheargrid.c
new file mode 100644
index 0000000..878c549
--- /dev/null
+++ b/app/display/gimptoolsheargrid.c
@@ -0,0 +1,360 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolsheargrid.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimptoolsheargrid.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_SHEAR_X,
+ PROP_SHEAR_Y
+};
+
+
+struct _GimpToolShearGridPrivate
+{
+ GimpOrientationType orientation;
+ gdouble shear_x;
+ gdouble shear_y;
+
+ gdouble last_x;
+ gdouble last_y;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_shear_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_shear_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint gimp_tool_shear_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_shear_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolShearGrid, gimp_tool_shear_grid,
+ GIMP_TYPE_TOOL_TRANSFORM_GRID)
+
+#define parent_class gimp_tool_shear_grid_parent_class
+
+
+static void
+gimp_tool_shear_grid_class_init (GimpToolShearGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->set_property = gimp_tool_shear_grid_set_property;
+ object_class->get_property = gimp_tool_shear_grid_get_property;
+
+ widget_class->button_press = gimp_tool_shear_grid_button_press;
+ widget_class->motion = gimp_tool_shear_grid_motion;
+
+ g_object_class_install_property (object_class, PROP_ORIENTATION,
+ g_param_spec_enum ("orientation",
+ NULL, NULL,
+ GIMP_TYPE_ORIENTATION_TYPE,
+ GIMP_ORIENTATION_UNKNOWN,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SHEAR_X,
+ g_param_spec_double ("shear-x",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SHEAR_Y,
+ g_param_spec_double ("shear-y",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_shear_grid_init (GimpToolShearGrid *grid)
+{
+ grid->private = gimp_tool_shear_grid_get_instance_private (grid);
+
+ g_object_set (grid,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_SHEAR,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_SHEAR,
+ "use-corner-handles", FALSE,
+ "use-perspective-handles", FALSE,
+ "use-side-handles", FALSE,
+ "use-shear-handles", FALSE,
+ "use-center-handle", FALSE,
+ "use-pivot-handle", FALSE,
+ NULL);
+}
+
+static void
+gimp_tool_shear_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (object);
+ GimpToolShearGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ private->orientation = g_value_get_enum (value);
+ break;
+ case PROP_SHEAR_X:
+ private->shear_x = g_value_get_double (value);
+ break;
+ case PROP_SHEAR_Y:
+ private->shear_y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_shear_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (object);
+ GimpToolShearGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, private->orientation);
+ break;
+ case PROP_SHEAR_X:
+ g_value_set_double (value, private->shear_x);
+ break;
+ case PROP_SHEAR_Y:
+ g_value_set_double (value, private->shear_y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint
+gimp_tool_shear_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (widget);
+ GimpToolShearGridPrivate *private = grid->private;
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ return 1;
+}
+
+void
+gimp_tool_shear_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (widget);
+ GimpToolShearGridPrivate *private = grid->private;
+ gdouble diffx = coords->x - private->last_x;
+ gdouble diffy = coords->y - private->last_y;
+ gdouble amount = 0.0;
+ GimpMatrix3 transform;
+ GimpMatrix3 *t;
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+ gdouble tx3, ty3;
+ gdouble tx4, ty4;
+ gdouble current_x;
+ gdouble current_y;
+
+ g_object_get (widget,
+ "transform", &t,
+ "x1", &x1,
+ "y1", &y1,
+ "x2", &x2,
+ "y2", &y2,
+ NULL);
+
+ gimp_matrix3_transform_point (t, x1, y1, &tx1, &ty1);
+ gimp_matrix3_transform_point (t, x2, y1, &tx2, &ty2);
+ gimp_matrix3_transform_point (t, x1, y2, &tx3, &ty3);
+ gimp_matrix3_transform_point (t, x2, y2, &tx4, &ty4);
+
+ g_free (t);
+
+ current_x = coords->x;
+ current_y = coords->y;
+
+ diffx = current_x - private->last_x;
+ diffy = current_y - private->last_y;
+
+ /* If we haven't yet decided on which way to control shearing
+ * decide using the maximum differential
+ */
+ if (private->orientation == GIMP_ORIENTATION_UNKNOWN)
+ {
+#define MIN_MOVE 5
+
+ if (ABS (diffx) > MIN_MOVE || ABS (diffy) > MIN_MOVE)
+ {
+ if (ABS (diffx) > ABS (diffy))
+ {
+ private->orientation = GIMP_ORIENTATION_HORIZONTAL;
+ private->shear_x = 0.0;
+ }
+ else
+ {
+ private->orientation = GIMP_ORIENTATION_VERTICAL;
+ private->shear_y = 0.0;
+ }
+ }
+ /* set the current coords to the last ones */
+ else
+ {
+ current_x = private->last_x;
+ current_y = private->last_y;
+ }
+ }
+
+ /* if the direction is known, keep track of the magnitude */
+ if (private->orientation == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ if (current_y > (ty1 + ty3) / 2)
+ private->shear_x += diffx;
+ else
+ private->shear_x -= diffx;
+
+ amount = private->shear_x;
+ }
+ else if (private->orientation == GIMP_ORIENTATION_VERTICAL)
+ {
+ if (current_x > (tx1 + tx2) / 2)
+ private->shear_y += diffy;
+ else
+ private->shear_y -= diffy;
+
+ amount = private->shear_y;
+ }
+
+ gimp_matrix3_identity (&transform);
+ gimp_transform_matrix_shear (&transform,
+ x1, y1, x2 - x1, y2 - y1,
+ private->orientation, amount);
+
+ g_object_set (widget,
+ "transform", &transform,
+ "orientation", private->orientation,
+ "shear-x", private->shear_x,
+ "shear_y", private->shear_y,
+ NULL);
+
+ private->last_x = current_x;
+ private->last_y = current_y;
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_shear_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpOrientationType orientation,
+ gdouble shear_x,
+ gdouble shear_y)
+{
+ GimpMatrix3 transform;
+ gdouble amount;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ amount = shear_x;
+ else
+ amount = shear_y;
+
+ gimp_matrix3_identity (&transform);
+ gimp_transform_matrix_shear (&transform,
+ x1, y1, x2 - x1, y2 - y1,
+ orientation, amount);
+
+ return g_object_new (GIMP_TYPE_TOOL_SHEAR_GRID,
+ "shell", shell,
+ "transform", &transform,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "orientation", orientation,
+ "shear-x", shear_x,
+ "shear-y", shear_y,
+ NULL);
+}
diff --git a/app/display/gimptoolsheargrid.h b/app/display/gimptoolsheargrid.h
new file mode 100644
index 0000000..dab96d5
--- /dev/null
+++ b/app/display/gimptoolsheargrid.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolsheargrid.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_SHEAR_GRID_H__
+#define __GIMP_TOOL_SHEAR_GRID_H__
+
+
+#include "gimptooltransformgrid.h"
+
+
+#define GIMP_TYPE_TOOL_SHEAR_GRID (gimp_tool_shear_grid_get_type ())
+#define GIMP_TOOL_SHEAR_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGrid))
+#define GIMP_TOOL_SHEAR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGridClass))
+#define GIMP_IS_TOOL_SHEAR_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_SHEAR_GRID))
+#define GIMP_IS_TOOL_SHEAR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_SHEAR_GRID))
+#define GIMP_TOOL_SHEAR_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGridClass))
+
+
+typedef struct _GimpToolShearGrid GimpToolShearGrid;
+typedef struct _GimpToolShearGridPrivate GimpToolShearGridPrivate;
+typedef struct _GimpToolShearGridClass GimpToolShearGridClass;
+
+struct _GimpToolShearGrid
+{
+ GimpToolTransformGrid parent_instance;
+
+ GimpToolShearGridPrivate *private;
+};
+
+struct _GimpToolShearGridClass
+{
+ GimpToolTransformGridClass parent_class;
+};
+
+
+GType gimp_tool_shear_grid_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_shear_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpOrientationType orientation,
+ gdouble shear_x,
+ gdouble shear_y);
+
+
+#endif /* __GIMP_TOOL_SHEAR_GRID_H__ */
diff --git a/app/display/gimptooltransform3dgrid.c b/app/display/gimptooltransform3dgrid.c
new file mode 100644
index 0000000..06cd955
--- /dev/null
+++ b/app/display/gimptooltransform3dgrid.c
@@ -0,0 +1,1162 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptool3dtransformgrid.c
+ * Copyright (C) 2019 Ell
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "core/gimp-transform-3d-utils.h"
+#include "core/gimp-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimptooltransform3dgrid.h"
+
+#include "gimp-intl.h"
+
+
+#define CONSTRAINT_MIN_DIST 8.0
+#define PIXELS_PER_REVOLUTION 1000
+
+
+enum
+{
+ PROP_0,
+ PROP_MODE,
+ PROP_UNIFIED,
+ PROP_CONSTRAIN_AXIS,
+ PROP_Z_AXIS,
+ PROP_LOCAL_FRAME,
+ PROP_CAMERA_X,
+ PROP_CAMERA_Y,
+ PROP_CAMERA_Z,
+ PROP_OFFSET_X,
+ PROP_OFFSET_Y,
+ PROP_OFFSET_Z,
+ PROP_ROTATION_ORDER,
+ PROP_ANGLE_X,
+ PROP_ANGLE_Y,
+ PROP_ANGLE_Z,
+ PROP_PIVOT_3D_X,
+ PROP_PIVOT_3D_Y,
+ PROP_PIVOT_3D_Z
+};
+
+typedef enum
+{
+ AXIS_NONE,
+ AXIS_X,
+ AXIS_Y
+} Axis;
+
+struct _GimpToolTransform3DGridPrivate
+{
+ GimpTransform3DMode mode;
+ gboolean unified;
+
+ gboolean constrain_axis;
+ gboolean z_axis;
+ gboolean local_frame;
+
+ gdouble camera_x;
+ gdouble camera_y;
+ gdouble camera_z;
+
+ gdouble offset_x;
+ gdouble offset_y;
+ gdouble offset_z;
+
+ gint rotation_order;
+ gdouble angle_x;
+ gdouble angle_y;
+ gdouble angle_z;
+
+ gdouble pivot_x;
+ gdouble pivot_y;
+ gdouble pivot_z;
+
+ GimpTransformHandle handle;
+
+ gdouble orig_x;
+ gdouble orig_y;
+ gdouble orig_offset_x;
+ gdouble orig_offset_y;
+ gdouble orig_offset_z;
+ GimpMatrix3 orig_transform;
+
+ gdouble last_x;
+ gdouble last_y;
+
+ Axis constrained_axis;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_transform_3d_grid_constructed (GObject *object);
+static void gimp_tool_transform_3d_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_transform_3d_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint gimp_tool_transform_3d_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_transform_3d_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static void gimp_tool_transform_3d_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_transform_3d_grid_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_transform_3d_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_transform_3d_grid_update_mode (GimpToolTransform3DGrid *grid);
+static void gimp_tool_transform_3d_grid_reset_motion (GimpToolTransform3DGrid *grid);
+static gboolean gimp_tool_transform_3d_grid_constrain (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y,
+ gdouble ox,
+ gdouble oy,
+ gdouble *tx,
+ gdouble *ty);
+
+static gboolean gimp_tool_transform_3d_grid_motion_vanishing_point (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y);
+static gboolean gimp_tool_transform_3d_grid_motion_move (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y);
+static gboolean gimp_tool_transform_3d_grid_motion_rotate (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolTransform3DGrid, gimp_tool_transform_3d_grid,
+ GIMP_TYPE_TOOL_TRANSFORM_GRID)
+
+#define parent_class gimp_tool_transform_3d_grid_parent_class
+
+
+static void
+gimp_tool_transform_3d_grid_class_init (GimpToolTransform3DGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_transform_3d_grid_constructed;
+ object_class->set_property = gimp_tool_transform_3d_grid_set_property;
+ object_class->get_property = gimp_tool_transform_3d_grid_get_property;
+
+ widget_class->button_press = gimp_tool_transform_3d_grid_button_press;
+ widget_class->motion = gimp_tool_transform_3d_grid_motion;
+ widget_class->hover = gimp_tool_transform_3d_grid_hover;
+ widget_class->hover_modifier = gimp_tool_transform_3d_grid_hover_modifier;
+ widget_class->get_cursor = gimp_tool_transform_3d_grid_get_cursor;
+
+ g_object_class_install_property (object_class, PROP_MODE,
+ g_param_spec_enum ("mode",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_3D_MODE,
+ GIMP_TRANSFORM_3D_MODE_CAMERA,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_UNIFIED,
+ g_param_spec_boolean ("unified",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAIN_AXIS,
+ g_param_spec_boolean ("constrain-axis",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Z_AXIS,
+ g_param_spec_boolean ("z-axis",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_LOCAL_FRAME,
+ g_param_spec_boolean ("local-frame",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CAMERA_X,
+ g_param_spec_double ("camera-x",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CAMERA_Y,
+ g_param_spec_double ("camera-y",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CAMERA_Z,
+ g_param_spec_double ("camera-z",
+ NULL, NULL,
+ -(1.0 / 0.0),
+ 1.0 / 0.0,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_X,
+ g_param_spec_double ("offset-x",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_Y,
+ g_param_spec_double ("offset-y",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_Z,
+ g_param_spec_double ("offset-z",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ROTATION_ORDER,
+ g_param_spec_int ("rotation-order",
+ NULL, NULL,
+ 0, 6, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ANGLE_X,
+ g_param_spec_double ("angle-x",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ANGLE_Y,
+ g_param_spec_double ("angle-y",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ANGLE_Z,
+ g_param_spec_double ("angle-z",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_3D_X,
+ g_param_spec_double ("pivot-3d-x",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_3D_Y,
+ g_param_spec_double ("pivot-3d-y",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_3D_Z,
+ g_param_spec_double ("pivot-3d-z",
+ NULL, NULL,
+ -G_MAXDOUBLE,
+ G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_transform_3d_grid_init (GimpToolTransform3DGrid *grid)
+{
+ grid->priv = gimp_tool_transform_3d_grid_get_instance_private (grid);
+}
+
+static void
+gimp_tool_transform_3d_grid_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ g_object_set (object,
+ "clip-guides", TRUE,
+ "dynamic-handle-size", FALSE,
+ NULL);
+}
+
+static void
+gimp_tool_transform_3d_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (object);
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+ switch (property_id)
+ {
+ case PROP_MODE:
+ priv->mode = g_value_get_enum (value);
+ gimp_tool_transform_3d_grid_update_mode (grid);
+ break;
+
+ case PROP_UNIFIED:
+ priv->unified = g_value_get_boolean (value);
+ gimp_tool_transform_3d_grid_update_mode (grid);
+ break;
+
+ case PROP_CONSTRAIN_AXIS:
+ priv->constrain_axis = g_value_get_boolean (value);
+ gimp_tool_transform_3d_grid_reset_motion (grid);
+ break;
+ case PROP_Z_AXIS:
+ priv->z_axis = g_value_get_boolean (value);
+ gimp_tool_transform_3d_grid_reset_motion (grid);
+ break;
+ case PROP_LOCAL_FRAME:
+ priv->local_frame = g_value_get_boolean (value);
+ gimp_tool_transform_3d_grid_reset_motion (grid);
+ break;
+
+ case PROP_CAMERA_X:
+ priv->camera_x = g_value_get_double (value);
+ g_object_set (grid,
+ "pivot-x", priv->camera_x,
+ NULL);
+ break;
+ case PROP_CAMERA_Y:
+ priv->camera_y = g_value_get_double (value);
+ g_object_set (grid,
+ "pivot-y", priv->camera_y,
+ NULL);
+ break;
+ case PROP_CAMERA_Z:
+ priv->camera_z = g_value_get_double (value);
+ break;
+
+ case PROP_OFFSET_X:
+ priv->offset_x = g_value_get_double (value);
+ break;
+ case PROP_OFFSET_Y:
+ priv->offset_y = g_value_get_double (value);
+ break;
+ case PROP_OFFSET_Z:
+ priv->offset_z = g_value_get_double (value);
+ break;
+
+ case PROP_ROTATION_ORDER:
+ priv->rotation_order = g_value_get_int (value);
+ break;
+ case PROP_ANGLE_X:
+ priv->angle_x = g_value_get_double (value);
+ break;
+ case PROP_ANGLE_Y:
+ priv->angle_y = g_value_get_double (value);
+ break;
+ case PROP_ANGLE_Z:
+ priv->angle_z = g_value_get_double (value);
+ break;
+
+ case PROP_PIVOT_3D_X:
+ priv->pivot_x = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_3D_Y:
+ priv->pivot_y = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_3D_Z:
+ priv->pivot_z = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_transform_3d_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (object);
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+ switch (property_id)
+ {
+ case PROP_MODE:
+ g_value_set_enum (value, priv->mode);
+ break;
+ case PROP_UNIFIED:
+ g_value_set_boolean (value, priv->unified);
+ break;
+
+ case PROP_CONSTRAIN_AXIS:
+ g_value_set_boolean (value, priv->constrain_axis);
+ break;
+ case PROP_Z_AXIS:
+ g_value_set_boolean (value, priv->z_axis);
+ break;
+ case PROP_LOCAL_FRAME:
+ g_value_set_boolean (value, priv->local_frame);
+ break;
+
+ case PROP_CAMERA_X:
+ g_value_set_double (value, priv->camera_x);
+ break;
+ case PROP_CAMERA_Y:
+ g_value_set_double (value, priv->camera_y);
+ break;
+ case PROP_CAMERA_Z:
+ g_value_set_double (value, priv->camera_z);
+ break;
+
+ case PROP_OFFSET_X:
+ g_value_set_double (value, priv->offset_x);
+ break;
+ case PROP_OFFSET_Y:
+ g_value_set_double (value, priv->offset_y);
+ break;
+ case PROP_OFFSET_Z:
+ g_value_set_double (value, priv->offset_z);
+ break;
+
+ case PROP_ROTATION_ORDER:
+ g_value_set_int (value, priv->rotation_order);
+ break;
+ case PROP_ANGLE_X:
+ g_value_set_double (value, priv->angle_x);
+ break;
+ case PROP_ANGLE_Y:
+ g_value_set_double (value, priv->angle_y);
+ break;
+ case PROP_ANGLE_Z:
+ g_value_set_double (value, priv->angle_z);
+ break;
+
+ case PROP_PIVOT_3D_X:
+ g_value_set_double (value, priv->pivot_x);
+ break;
+ case PROP_PIVOT_3D_Y:
+ g_value_set_double (value, priv->pivot_y);
+ break;
+ case PROP_PIVOT_3D_Z:
+ g_value_set_double (value, priv->pivot_z);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint
+gimp_tool_transform_3d_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget);
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+ priv->handle = GIMP_TOOL_WIDGET_CLASS (parent_class)->button_press (
+ widget, coords, time, state, press_type);
+
+ priv->orig_x = coords->x;
+ priv->orig_y = coords->y;
+ priv->orig_offset_x = priv->offset_x;
+ priv->orig_offset_y = priv->offset_y;
+ priv->orig_offset_z = priv->offset_z;
+ priv->last_x = coords->x;
+ priv->last_y = coords->y;
+
+ gimp_tool_transform_3d_grid_reset_motion (grid);
+
+ return priv->handle;
+}
+
+void
+gimp_tool_transform_3d_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget);
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+ GimpMatrix3 transform;
+ gboolean update = TRUE;
+
+ switch (priv->handle)
+ {
+ case GIMP_TRANSFORM_HANDLE_PIVOT:
+ update = gimp_tool_transform_3d_grid_motion_vanishing_point (
+ grid, coords->x, coords->y);
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_CENTER:
+ update = gimp_tool_transform_3d_grid_motion_move (
+ grid, coords->x, coords->y);
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_ROTATION:
+ update = gimp_tool_transform_3d_grid_motion_rotate (
+ grid, coords->x, coords->y);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ if (update)
+ {
+ gimp_transform_3d_matrix (&transform,
+
+ priv->camera_x,
+ priv->camera_y,
+ priv->camera_z,
+
+ priv->offset_x,
+ priv->offset_y,
+ priv->offset_z,
+
+ priv->rotation_order,
+ priv->angle_x,
+ priv->angle_y,
+ priv->angle_z,
+
+ priv->pivot_x,
+ priv->pivot_y,
+ priv->pivot_z);
+
+ g_object_set (widget,
+ "transform", &transform,
+ NULL);
+ }
+}
+
+static void
+gimp_tool_transform_3d_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->hover (widget,
+ coords, state, proximity);
+
+ if (proximity &&
+ gimp_tool_transform_grid_get_handle (GIMP_TOOL_TRANSFORM_GRID (widget)) ==
+ GIMP_TRANSFORM_HANDLE_PIVOT)
+ {
+ gimp_tool_widget_set_status (widget,
+ _("Click-Drag to move the vanishing point"));
+ }
+}
+
+static void
+gimp_tool_transform_3d_grid_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget);
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->hover_modifier (widget,
+ key, press, state);
+
+ priv->local_frame = (state & gimp_get_extend_selection_mask ()) != 0;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ if (! GIMP_TOOL_WIDGET_CLASS (parent_class)->get_cursor (widget,
+ coords,
+ state,
+ cursor,
+ tool_cursor,
+ modifier))
+ {
+ return FALSE;
+ }
+
+ if (gimp_tool_transform_grid_get_handle (GIMP_TOOL_TRANSFORM_GRID (widget)) ==
+ GIMP_TRANSFORM_HANDLE_PIVOT)
+ {
+ *tool_cursor = GIMP_TOOL_CURSOR_TRANSFORM_3D_CAMERA;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_tool_transform_3d_grid_update_mode (GimpToolTransform3DGrid *grid)
+{
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+ if (priv->unified)
+ {
+ g_object_set (grid,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-pivot-handle", TRUE,
+ NULL);
+ }
+ else
+ {
+ switch (priv->mode)
+ {
+ case GIMP_TRANSFORM_3D_MODE_CAMERA:
+ g_object_set (grid,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_NONE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_NONE,
+ "use-pivot-handle", TRUE,
+ NULL);
+ break;
+
+ case GIMP_TRANSFORM_3D_MODE_MOVE:
+ g_object_set (grid,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "use-pivot-handle", FALSE,
+ NULL);
+ break;
+
+ case GIMP_TRANSFORM_3D_MODE_ROTATE:
+ g_object_set (grid,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-pivot-handle", FALSE,
+ NULL);
+ break;
+ }
+ }
+}
+
+static void
+gimp_tool_transform_3d_grid_reset_motion (GimpToolTransform3DGrid *grid)
+{
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+ GimpMatrix3 *transform;
+
+ priv->constrained_axis = AXIS_NONE;
+
+ g_object_get (grid,
+ "transform", &transform,
+ NULL);
+
+ priv->orig_transform = *transform;
+
+ g_free (transform);
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_constrain (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y,
+ gdouble ox,
+ gdouble oy,
+ gdouble *tx,
+ gdouble *ty)
+{
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+ if (! priv->constrain_axis)
+ return TRUE;
+
+ if (priv->constrained_axis == AXIS_NONE)
+ {
+ GimpDisplayShell *shell;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (grid));
+
+ gimp_display_shell_transform_xy_f (shell,
+ priv->last_x, priv->last_y,
+ &x1, &y1);
+ gimp_display_shell_transform_xy_f (shell,
+ x, y,
+ &x2, &y2);
+
+ if (hypot (x2 - x1, y2 - y1) < CONSTRAINT_MIN_DIST)
+ return FALSE;
+
+ if (fabs (*tx - ox) >= fabs (*ty - oy))
+ priv->constrained_axis = AXIS_X;
+ else
+ priv->constrained_axis = AXIS_Y;
+ }
+
+ if (priv->constrained_axis == AXIS_X)
+ *ty = oy;
+ else
+ *tx = ox;
+
+ return TRUE;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_motion_vanishing_point (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y)
+{
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+ GimpCoords c = {};
+ gdouble pivot_x;
+ gdouble pivot_y;
+
+ if (! gimp_tool_transform_3d_grid_constrain (grid,
+ x, y,
+ priv->last_x, priv->last_y,
+ &x, &y))
+ {
+ return FALSE;
+ }
+
+ c.x = x;
+ c.y = y;
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->motion (GIMP_TOOL_WIDGET (grid),
+ &c, 0, 0);
+
+ g_object_get (grid,
+ "pivot-x", &pivot_x,
+ "pivot-y", &pivot_y,
+ NULL);
+
+ g_object_set (grid,
+ "camera-x", pivot_x,
+ "camera-y", pivot_y,
+ NULL);
+
+ priv->last_x = c.x;
+ priv->last_y = c.y;
+
+ return TRUE;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_motion_move (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y)
+{
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+ GimpMatrix4 matrix;
+
+ if (! priv->z_axis)
+ {
+ gdouble x1, y1, z1, w1;
+ gdouble x2, y2, z2, w2;
+
+ if (! priv->local_frame)
+ {
+ gimp_matrix4_identity (&matrix);
+ }
+ else
+ {
+ GimpMatrix3 transform_inv = priv->orig_transform;;
+
+ gimp_matrix3_invert (&transform_inv);
+
+ gimp_transform_3d_matrix3_to_matrix4 (&transform_inv, &matrix, 2);
+ }
+
+ w1 = gimp_matrix4_transform_point (&matrix,
+ priv->last_x, priv->last_y, 0.0,
+ &x1, &y1, &z1);
+ w2 = gimp_matrix4_transform_point (&matrix,
+ x, y, 0.0,
+ &x2, &y2, &z2);
+
+ if (w1 <= 0.0)
+ return FALSE;
+
+ if (! gimp_tool_transform_3d_grid_constrain (grid,
+ x, y,
+ x1, y1,
+ &x2, &y2))
+ {
+ return FALSE;
+ }
+
+ if (priv->local_frame)
+ {
+ gimp_matrix4_identity (&matrix);
+
+ gimp_transform_3d_matrix4_rotate_euler (&matrix,
+ priv->rotation_order,
+ priv->angle_x,
+ priv->angle_y,
+ priv->angle_z,
+ 0.0, 0.0, 0.0);
+
+ gimp_matrix4_transform_point (&matrix,
+ x1, y1, z1,
+ &x1, &y1, &z1);
+ gimp_matrix4_transform_point (&matrix,
+ x2, y2, z2,
+ &x2, &y2, &z2);
+ }
+
+ if (w2 > 0.0)
+ {
+ g_object_set (grid,
+ "offset-x", priv->offset_x + (x2 - x1),
+ "offset-y", priv->offset_y + (y2 - y1),
+ "offset-z", priv->offset_z + (z2 - z1),
+ NULL);
+
+ priv->last_x = x;
+ priv->last_y = y;
+ }
+ else
+ {
+ g_object_set (grid,
+ "offset-x", priv->orig_offset_x,
+ "offset-y", priv->orig_offset_y,
+ "offset-z", priv->orig_offset_z,
+ NULL);
+
+ priv->last_x = priv->orig_x;
+ priv->last_y = priv->orig_y;
+ }
+ }
+ else
+ {
+ GimpVector3 axis;
+ gdouble amount;
+
+ if (! priv->local_frame)
+ {
+ axis.x = 0.0;
+ axis.y = 0.0;
+ axis.z = 1.0;
+ }
+ else
+ {
+ gimp_matrix4_identity (&matrix);
+
+ gimp_transform_3d_matrix4_rotate_euler (&matrix,
+ priv->rotation_order,
+ priv->angle_x,
+ priv->angle_y,
+ priv->angle_z,
+ 0.0, 0.0, 0.0);
+
+ axis.x = matrix.coeff[0][2];
+ axis.y = matrix.coeff[1][2];
+ axis.z = matrix.coeff[2][2];
+
+ if (axis.x < 0.0)
+ gimp_vector3_neg (&axis);
+ }
+
+ amount = x - priv->last_x;
+
+ g_object_set (grid,
+ "offset-x", priv->offset_x + axis.x * amount,
+ "offset-y", priv->offset_y + axis.y * amount,
+ "offset-z", priv->offset_z + axis.z * amount,
+ NULL);
+
+ priv->last_x = x;
+ priv->last_y = y;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_motion_rotate (GimpToolTransform3DGrid *grid,
+ gdouble x,
+ gdouble y)
+{
+ GimpToolTransform3DGridPrivate *priv = grid->priv;
+ GimpDisplayShell *shell;
+ GimpMatrix4 matrix;
+ GimpMatrix2 basis_inv;
+ GimpVector3 omega;
+ gdouble z_sign;
+ gboolean local_frame;
+
+ shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (grid));
+
+ local_frame = priv->local_frame && (priv->constrain_axis || priv->z_axis);
+
+ if (! local_frame)
+ {
+ gimp_matrix2_identity (&basis_inv);
+ z_sign = 1.0;
+ }
+ else
+ {
+ {
+ GimpVector3 o, n, c;
+
+ gimp_matrix4_identity (&matrix);
+
+ gimp_transform_3d_matrix4_rotate_euler (&matrix,
+ priv->rotation_order,
+ priv->angle_x,
+ priv->angle_y,
+ priv->angle_z,
+ priv->pivot_x,
+ priv->pivot_y,
+ priv->pivot_z);
+
+ gimp_transform_3d_matrix4_translate (&matrix,
+ priv->offset_x,
+ priv->offset_y,
+ priv->offset_z);
+
+ gimp_matrix4_transform_point (&matrix,
+ 0.0, 0.0, 0.0,
+ &o.x, &o.y, &o.z);
+ gimp_matrix4_transform_point (&matrix,
+ 0.0, 0.0, 1.0,
+ &n.x, &n.y, &n.z);
+
+ c.x = priv->camera_x;
+ c.y = priv->camera_y;
+ c.z = priv->camera_z;
+
+ gimp_vector3_sub (&n, &n, &o);
+ gimp_vector3_sub (&c, &c, &o);
+
+ z_sign = gimp_vector3_inner_product (&c, &n) <= 0.0 ? +1.0 : -1.0;
+ }
+
+ {
+ GimpVector2 o, u, v;
+
+ gimp_matrix3_transform_point (&priv->orig_transform,
+ priv->pivot_x, priv->pivot_y,
+ &o.x, &o.y);
+ gimp_matrix3_transform_point (&priv->orig_transform,
+ priv->pivot_x + 1.0, priv->pivot_y,
+ &u.x, &u.y);
+ gimp_matrix3_transform_point (&priv->orig_transform,
+ priv->pivot_x, priv->pivot_y + 1.0,
+ &v.x, &v.y);
+
+ gimp_vector2_sub (&u, &u, &o);
+ gimp_vector2_sub (&v, &v, &o);
+
+ gimp_vector2_normalize (&u);
+ gimp_vector2_normalize (&v);
+
+ basis_inv.coeff[0][0] = u.x;
+ basis_inv.coeff[1][0] = u.y;
+ basis_inv.coeff[0][1] = v.x;
+ basis_inv.coeff[1][1] = v.y;
+
+ gimp_matrix2_invert (&basis_inv);
+ }
+ }
+
+ if (! priv->z_axis)
+ {
+ GimpVector2 scale;
+ gdouble norm;
+
+ gimp_matrix2_transform_point (&basis_inv,
+ -(y - priv->last_y),
+ x - priv->last_x,
+ &omega.x, &omega.y);
+
+ omega.z = 0.0;
+
+ if (! gimp_tool_transform_3d_grid_constrain (grid,
+ x, y,
+ 0.0, 0.0,
+ &omega.x, &omega.y))
+ {
+ return FALSE;
+ }
+
+ norm = gimp_vector3_length (&omega);
+
+ if (norm > 0.0)
+ {
+ scale.x = shell->scale_x * omega.y / norm;
+ scale.y = shell->scale_y * omega.x / norm;
+
+ gimp_vector3_mul (&omega, gimp_vector2_length (&scale));
+ gimp_vector3_mul (&omega, 2.0 * G_PI / PIXELS_PER_REVOLUTION);
+ }
+ }
+ else
+ {
+ GimpVector2 o;
+ GimpVector2 v1 = {priv->last_x, priv->last_y};
+ GimpVector2 v2 = {x, y};
+
+ g_warn_if_fail (priv->pivot_z == 0.0);
+
+ gimp_matrix3_transform_point (&priv->orig_transform,
+ priv->pivot_x, priv->pivot_y,
+ &o.x, &o.y);
+
+ gimp_vector2_sub (&v1, &v1, &o);
+ gimp_vector2_sub (&v2, &v2, &o);
+
+ gimp_vector2_normalize (&v1);
+ gimp_vector2_normalize (&v2);
+
+ omega.x = 0.0;
+ omega.y = 0.0;
+ omega.z = atan2 (gimp_vector2_cross_product (&v1, &v2).y,
+ gimp_vector2_inner_product (&v1, &v2));
+
+ omega.z *= z_sign;
+ }
+
+ gimp_matrix4_identity (&matrix);
+
+ if (local_frame)
+ gimp_transform_3d_matrix4_rotate (&matrix, &omega);
+
+ gimp_transform_3d_matrix4_rotate_euler (&matrix,
+ priv->rotation_order,
+ priv->angle_x,
+ priv->angle_y,
+ priv->angle_z,
+ 0.0, 0.0, 0.0);
+
+ if (! local_frame)
+ gimp_transform_3d_matrix4_rotate (&matrix, &omega);
+
+ gimp_transform_3d_matrix4_rotate_euler_decompose (&matrix,
+ priv->rotation_order,
+ &priv->angle_x,
+ &priv->angle_y,
+ &priv->angle_z);
+
+ priv->last_x = x;
+ priv->last_y = y;
+
+ g_object_set (grid,
+ "angle-x", priv->angle_x,
+ "angle-y", priv->angle_y,
+ "angle-z", priv->angle_z,
+ NULL);
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_transform_3d_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble camera_x,
+ gdouble camera_y,
+ gdouble camera_z)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_3D_GRID,
+ "shell", shell,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "camera-x", camera_x,
+ "camera-y", camera_y,
+ "camera-z", camera_z,
+ "pivot-3d-x", (x1 + x2) / 2.0,
+ "pivot-3d-y", (y1 + y2) / 2.0,
+ "pivot-3d-z", 0.0,
+ NULL);
+}
diff --git a/app/display/gimptooltransform3dgrid.h b/app/display/gimptooltransform3dgrid.h
new file mode 100644
index 0000000..42ac3ea
--- /dev/null
+++ b/app/display/gimptooltransform3dgrid.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptool3dtransformgrid.h
+ * Copyright (C) 2019 Ell
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_TRANSFORM_3D_GRID_H__
+#define __GIMP_TOOL_TRANSFORM_3D_GRID_H__
+
+
+#include "gimptooltransformgrid.h"
+
+
+#define GIMP_TYPE_TOOL_TRANSFORM_3D_GRID (gimp_tool_transform_3d_grid_get_type ())
+#define GIMP_TOOL_TRANSFORM_3D_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGrid))
+#define GIMP_TOOL_TRANSFORM_3D_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGridClass))
+#define GIMP_IS_TOOL_TRANSFORM_3D_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID))
+#define GIMP_IS_TOOL_TRANSFORM_3D_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID))
+#define GIMP_TOOL_TRANSFORM_3D_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGridClass))
+
+
+typedef struct _GimpToolTransform3DGrid GimpToolTransform3DGrid;
+typedef struct _GimpToolTransform3DGridPrivate GimpToolTransform3DGridPrivate;
+typedef struct _GimpToolTransform3DGridClass GimpToolTransform3DGridClass;
+
+struct _GimpToolTransform3DGrid
+{
+ GimpToolTransformGrid parent_instance;
+
+ GimpToolTransform3DGridPrivate *priv;
+};
+
+struct _GimpToolTransform3DGridClass
+{
+ GimpToolTransformGridClass parent_class;
+};
+
+
+GType gimp_tool_transform_3d_grid_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_transform_3d_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble camera_x,
+ gdouble camera_y,
+ gdouble camera_z);
+
+
+#endif /* __GIMP_TOOL_TRANSFORM_3D_GRID_H__ */
diff --git a/app/display/gimptooltransformgrid.c b/app/display/gimptooltransformgrid.c
new file mode 100644
index 0000000..b186b91
--- /dev/null
+++ b/app/display/gimptooltransformgrid.c
@@ -0,0 +1,2494 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptooltransformgrid.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpUnifiedTransformTool
+ * Copyright (C) 2011 Mikael Magnusson <mikachu@src.gnome.org>
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvastransformguides.h"
+#include "gimpdisplayshell.h"
+#include "gimptooltransformgrid.h"
+
+#include "gimp-intl.h"
+
+
+#define MIN_HANDLE_SIZE 6
+
+
+enum
+{
+ PROP_0,
+ PROP_TRANSFORM,
+ PROP_X1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ PROP_PIVOT_X,
+ PROP_PIVOT_Y,
+ PROP_GUIDE_TYPE,
+ PROP_N_GUIDES,
+ PROP_CLIP_GUIDES,
+ PROP_SHOW_GUIDES,
+ PROP_INSIDE_FUNCTION,
+ PROP_OUTSIDE_FUNCTION,
+ PROP_USE_CORNER_HANDLES,
+ PROP_USE_PERSPECTIVE_HANDLES,
+ PROP_USE_SIDE_HANDLES,
+ PROP_USE_SHEAR_HANDLES,
+ PROP_USE_CENTER_HANDLE,
+ PROP_USE_PIVOT_HANDLE,
+ PROP_DYNAMIC_HANDLE_SIZE,
+ PROP_CONSTRAIN_MOVE,
+ PROP_CONSTRAIN_SCALE,
+ PROP_CONSTRAIN_ROTATE,
+ PROP_CONSTRAIN_SHEAR,
+ PROP_CONSTRAIN_PERSPECTIVE,
+ PROP_FROMPIVOT_SCALE,
+ PROP_FROMPIVOT_SHEAR,
+ PROP_FROMPIVOT_PERSPECTIVE,
+ PROP_CORNERSNAP,
+ PROP_FIXEDPIVOT
+};
+
+
+struct _GimpToolTransformGridPrivate
+{
+ GimpMatrix3 transform;
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble pivot_x;
+ gdouble pivot_y;
+ GimpGuidesType guide_type;
+ gint n_guides;
+ gboolean clip_guides;
+ gboolean show_guides;
+ GimpTransformFunction inside_function;
+ GimpTransformFunction outside_function;
+ gboolean use_corner_handles;
+ gboolean use_perspective_handles;
+ gboolean use_side_handles;
+ gboolean use_shear_handles;
+ gboolean use_center_handle;
+ gboolean use_pivot_handle;
+ gboolean dynamic_handle_size;
+ gboolean constrain_move;
+ gboolean constrain_scale;
+ gboolean constrain_rotate;
+ gboolean constrain_shear;
+ gboolean constrain_perspective;
+ gboolean frompivot_scale;
+ gboolean frompivot_shear;
+ gboolean frompivot_perspective;
+ gboolean cornersnap;
+ gboolean fixedpivot;
+
+ gdouble curx; /* current x coord */
+ gdouble cury; /* current y coord */
+
+ gdouble button_down; /* is the mouse button pressed */
+ gdouble mousex; /* x coord where mouse was clicked */
+ gdouble mousey; /* y coord where mouse was clicked */
+
+ gdouble cx, cy; /* center point (for moving) */
+
+ /* transformed handle coords */
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+ gdouble tx3, ty3;
+ gdouble tx4, ty4;
+ gdouble tcx, tcy;
+ gdouble tpx, tpy;
+
+ /* previous transformed handle coords */
+ gdouble prev_tx1, prev_ty1;
+ gdouble prev_tx2, prev_ty2;
+ gdouble prev_tx3, prev_ty3;
+ gdouble prev_tx4, prev_ty4;
+ gdouble prev_tcx, prev_tcy;
+ gdouble prev_tpx, prev_tpy;
+
+ GimpTransformHandle handle; /* current tool activity */
+
+ GimpCanvasItem *guides;
+ GimpCanvasItem *handles[GIMP_N_TRANSFORM_HANDLES];
+ GimpCanvasItem *center_items[2];
+ GimpCanvasItem *pivot_items[2];
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_transform_grid_constructed (GObject *object);
+static void gimp_tool_transform_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_transform_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_transform_grid_changed (GimpToolWidget *widget);
+static gint gimp_tool_transform_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_transform_grid_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_transform_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_transform_grid_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_transform_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_transform_grid_leave_notify (GimpToolWidget *widget);
+static void gimp_tool_transform_grid_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static GimpTransformHandle
+ gimp_tool_transform_grid_get_handle_for_coords
+ (GimpToolTransformGrid *grid,
+ const GimpCoords *coords);
+static void gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid);
+static void gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid);
+static void gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid);
+static void gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid,
+ gint *handle_w,
+ gint *handle_h);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolTransformGrid, gimp_tool_transform_grid,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_transform_grid_parent_class
+
+
+static void
+gimp_tool_transform_grid_class_init (GimpToolTransformGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_transform_grid_constructed;
+ object_class->set_property = gimp_tool_transform_grid_set_property;
+ object_class->get_property = gimp_tool_transform_grid_get_property;
+
+ widget_class->changed = gimp_tool_transform_grid_changed;
+ widget_class->button_press = gimp_tool_transform_grid_button_press;
+ widget_class->button_release = gimp_tool_transform_grid_button_release;
+ widget_class->motion = gimp_tool_transform_grid_motion;
+ widget_class->hit = gimp_tool_transform_grid_hit;
+ widget_class->hover = gimp_tool_transform_grid_hover;
+ widget_class->leave_notify = gimp_tool_transform_grid_leave_notify;
+ widget_class->hover_modifier = gimp_tool_transform_grid_hover_modifier;
+ widget_class->get_cursor = gimp_tool_transform_grid_get_cursor;
+ widget_class->update_on_scale = TRUE;
+
+ g_object_class_install_property (object_class, PROP_TRANSFORM,
+ gimp_param_spec_matrix3 ("transform",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X1,
+ g_param_spec_double ("x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y1,
+ g_param_spec_double ("y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_X2,
+ g_param_spec_double ("x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_Y2,
+ g_param_spec_double ("y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_X,
+ g_param_spec_double ("pivot-x",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_Y,
+ g_param_spec_double ("pivot-y",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_GUIDE_TYPE,
+ g_param_spec_enum ("guide-type", NULL, NULL,
+ GIMP_TYPE_GUIDES_TYPE,
+ GIMP_GUIDES_NONE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_N_GUIDES,
+ g_param_spec_int ("n-guides", NULL, NULL,
+ 1, 128, 4,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CLIP_GUIDES,
+ g_param_spec_boolean ("clip-guides", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SHOW_GUIDES,
+ g_param_spec_boolean ("show-guides", NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_INSIDE_FUNCTION,
+ g_param_spec_enum ("inside-function",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_FUNCTION,
+ GIMP_TRANSFORM_FUNCTION_MOVE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_OUTSIDE_FUNCTION,
+ g_param_spec_enum ("outside-function",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_FUNCTION,
+ GIMP_TRANSFORM_FUNCTION_ROTATE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_USE_CORNER_HANDLES,
+ g_param_spec_boolean ("use-corner-handles",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_USE_PERSPECTIVE_HANDLES,
+ g_param_spec_boolean ("use-perspective-handles",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_USE_SIDE_HANDLES,
+ g_param_spec_boolean ("use-side-handles",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_USE_SHEAR_HANDLES,
+ g_param_spec_boolean ("use-shear-handles",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_USE_CENTER_HANDLE,
+ g_param_spec_boolean ("use-center-handle",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_USE_PIVOT_HANDLE,
+ g_param_spec_boolean ("use-pivot-handle",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_DYNAMIC_HANDLE_SIZE,
+ g_param_spec_boolean ("dynamic-handle-size",
+ NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAIN_MOVE,
+ g_param_spec_boolean ("constrain-move",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAIN_SCALE,
+ g_param_spec_boolean ("constrain-scale",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAIN_ROTATE,
+ g_param_spec_boolean ("constrain-rotate",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAIN_SHEAR,
+ g_param_spec_boolean ("constrain-shear",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONSTRAIN_PERSPECTIVE,
+ g_param_spec_boolean ("constrain-perspective",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FROMPIVOT_SCALE,
+ g_param_spec_boolean ("frompivot-scale",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FROMPIVOT_SHEAR,
+ g_param_spec_boolean ("frompivot-shear",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FROMPIVOT_PERSPECTIVE,
+ g_param_spec_boolean ("frompivot-perspective",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CORNERSNAP,
+ g_param_spec_boolean ("cornersnap",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_FIXEDPIVOT,
+ g_param_spec_boolean ("fixedpivot",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_transform_grid_init (GimpToolTransformGrid *grid)
+{
+ grid->private = gimp_tool_transform_grid_get_instance_private (grid);
+}
+
+static void
+gimp_tool_transform_grid_constructed (GObject *object)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpCanvasGroup *stroke_group;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ private->guides = gimp_tool_widget_add_transform_guides (widget,
+ &private->transform,
+ private->x1,
+ private->y1,
+ private->x2,
+ private->y2,
+ private->guide_type,
+ private->n_guides,
+ private->clip_guides);
+
+ for (i = 0; i < 4; i++)
+ {
+ /* draw the scale handles */
+ private->handles[GIMP_TRANSFORM_HANDLE_NW + i] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_SQUARE,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ /* draw the perspective handles */
+ private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_DIAMOND,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ /* draw the side handles */
+ private->handles[GIMP_TRANSFORM_HANDLE_N + i] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_SQUARE,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ /* draw the shear handles */
+ private->handles[GIMP_TRANSFORM_HANDLE_N_S + i] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_FILLED_DIAMOND,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+
+ /* draw the rotation center axis handle */
+ stroke_group = gimp_tool_widget_add_stroke_group (widget);
+
+ private->handles[GIMP_TRANSFORM_HANDLE_PIVOT] =
+ GIMP_CANVAS_ITEM (stroke_group);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->pivot_items[0] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CIRCLE,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ private->pivot_items[1] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CROSS,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ gimp_tool_widget_pop_group (widget);
+
+ /* draw the center handle */
+ stroke_group = gimp_tool_widget_add_stroke_group (widget);
+
+ private->handles[GIMP_TRANSFORM_HANDLE_CENTER] =
+ GIMP_CANVAS_ITEM (stroke_group);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->center_items[0] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_SQUARE,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ private->center_items[1] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CROSS,
+ 0, 0, 10, 10,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ gimp_tool_widget_pop_group (widget);
+
+ gimp_tool_transform_grid_changed (widget);
+}
+
+static void
+gimp_tool_transform_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object);
+ GimpToolTransformGridPrivate *private = grid->private;
+ gboolean box = FALSE;
+
+ switch (property_id)
+ {
+ case PROP_TRANSFORM:
+ {
+ GimpMatrix3 *transform = g_value_get_boxed (value);
+
+ if (transform)
+ private->transform = *transform;
+ else
+ gimp_matrix3_identity (&private->transform);
+ }
+ break;
+
+ case PROP_X1:
+ private->x1 = g_value_get_double (value);
+ box = TRUE;
+ break;
+ case PROP_Y1:
+ private->y1 = g_value_get_double (value);
+ box = TRUE;
+ break;
+ case PROP_X2:
+ private->x2 = g_value_get_double (value);
+ box = TRUE;
+ break;
+ case PROP_Y2:
+ private->y2 = g_value_get_double (value);
+ box = TRUE;
+ break;
+
+ case PROP_PIVOT_X:
+ private->pivot_x = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_Y:
+ private->pivot_y = g_value_get_double (value);
+ break;
+
+ case PROP_GUIDE_TYPE:
+ private->guide_type = g_value_get_enum (value);
+ break;
+ case PROP_N_GUIDES:
+ private->n_guides = g_value_get_int (value);
+ break;
+ case PROP_CLIP_GUIDES:
+ private->clip_guides = g_value_get_boolean (value);
+ break;
+ case PROP_SHOW_GUIDES:
+ private->show_guides = g_value_get_boolean (value);
+ break;
+
+ case PROP_INSIDE_FUNCTION:
+ private->inside_function = g_value_get_enum (value);
+ break;
+ case PROP_OUTSIDE_FUNCTION:
+ private->outside_function = g_value_get_enum (value);
+ break;
+
+ case PROP_USE_CORNER_HANDLES:
+ private->use_corner_handles = g_value_get_boolean (value);
+ break;
+ case PROP_USE_PERSPECTIVE_HANDLES:
+ private->use_perspective_handles = g_value_get_boolean (value);
+ break;
+ case PROP_USE_SIDE_HANDLES:
+ private->use_side_handles = g_value_get_boolean (value);
+ break;
+ case PROP_USE_SHEAR_HANDLES:
+ private->use_shear_handles = g_value_get_boolean (value);
+ break;
+ case PROP_USE_CENTER_HANDLE:
+ private->use_center_handle = g_value_get_boolean (value);
+ break;
+ case PROP_USE_PIVOT_HANDLE:
+ private->use_pivot_handle = g_value_get_boolean (value);
+ break;
+
+ case PROP_DYNAMIC_HANDLE_SIZE:
+ private->dynamic_handle_size = g_value_get_boolean (value);
+ break;
+
+ case PROP_CONSTRAIN_MOVE:
+ private->constrain_move = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_SCALE:
+ private->constrain_scale = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_ROTATE:
+ private->constrain_rotate = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_SHEAR:
+ private->constrain_shear = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_PERSPECTIVE:
+ private->constrain_perspective = g_value_get_boolean (value);
+ break;
+
+ case PROP_FROMPIVOT_SCALE:
+ private->frompivot_scale = g_value_get_boolean (value);
+ break;
+ case PROP_FROMPIVOT_SHEAR:
+ private->frompivot_shear = g_value_get_boolean (value);
+ break;
+ case PROP_FROMPIVOT_PERSPECTIVE:
+ private->frompivot_perspective = g_value_get_boolean (value);
+ break;
+
+ case PROP_CORNERSNAP:
+ private->cornersnap = g_value_get_boolean (value);
+ break;
+ case PROP_FIXEDPIVOT:
+ private->fixedpivot = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+
+ if (box)
+ {
+ private->cx = (private->x1 + private->x2) / 2.0;
+ private->cy = (private->y1 + private->y2) / 2.0;
+ }
+}
+
+static void
+gimp_tool_transform_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object);
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ switch (property_id)
+ {
+ case PROP_TRANSFORM:
+ g_value_set_boxed (value, &private->transform);
+ break;
+
+ case PROP_X1:
+ g_value_set_double (value, private->x1);
+ break;
+ case PROP_Y1:
+ g_value_set_double (value, private->y1);
+ break;
+ case PROP_X2:
+ g_value_set_double (value, private->x2);
+ break;
+ case PROP_Y2:
+ g_value_set_double (value, private->y2);
+ break;
+
+ case PROP_PIVOT_X:
+ g_value_set_double (value, private->pivot_x);
+ break;
+ case PROP_PIVOT_Y:
+ g_value_set_double (value, private->pivot_y);
+ break;
+
+ case PROP_GUIDE_TYPE:
+ g_value_set_enum (value, private->guide_type);
+ break;
+ case PROP_N_GUIDES:
+ g_value_set_int (value, private->n_guides);
+ break;
+ case PROP_CLIP_GUIDES:
+ g_value_set_boolean (value, private->clip_guides);
+ break;
+ case PROP_SHOW_GUIDES:
+ g_value_set_boolean (value, private->show_guides);
+ break;
+
+ case PROP_INSIDE_FUNCTION:
+ g_value_set_enum (value, private->inside_function);
+ break;
+ case PROP_OUTSIDE_FUNCTION:
+ g_value_set_enum (value, private->outside_function);
+ break;
+
+ case PROP_USE_CORNER_HANDLES:
+ g_value_set_boolean (value, private->use_corner_handles);
+ break;
+ case PROP_USE_PERSPECTIVE_HANDLES:
+ g_value_set_boolean (value, private->use_perspective_handles);
+ break;
+ case PROP_USE_SIDE_HANDLES:
+ g_value_set_boolean (value, private->use_side_handles);
+ break;
+ case PROP_USE_SHEAR_HANDLES:
+ g_value_set_boolean (value, private->use_shear_handles);
+ break;
+ case PROP_USE_CENTER_HANDLE:
+ g_value_set_boolean (value, private->use_center_handle);
+ break;
+ case PROP_USE_PIVOT_HANDLE:
+ g_value_set_boolean (value, private->use_pivot_handle);
+ break;
+
+ case PROP_DYNAMIC_HANDLE_SIZE:
+ g_value_set_boolean (value, private->dynamic_handle_size);
+ break;
+
+ case PROP_CONSTRAIN_MOVE:
+ g_value_set_boolean (value, private->constrain_move);
+ break;
+ case PROP_CONSTRAIN_SCALE:
+ g_value_set_boolean (value, private->constrain_scale);
+ break;
+ case PROP_CONSTRAIN_ROTATE:
+ g_value_set_boolean (value, private->constrain_rotate);
+ break;
+ case PROP_CONSTRAIN_SHEAR:
+ g_value_set_boolean (value, private->constrain_shear);
+ break;
+ case PROP_CONSTRAIN_PERSPECTIVE:
+ g_value_set_boolean (value, private->constrain_perspective);
+ break;
+
+ case PROP_FROMPIVOT_SCALE:
+ g_value_set_boolean (value, private->frompivot_scale);
+ break;
+ case PROP_FROMPIVOT_SHEAR:
+ g_value_set_boolean (value, private->frompivot_shear);
+ break;
+ case PROP_FROMPIVOT_PERSPECTIVE:
+ g_value_set_boolean (value, private->frompivot_perspective);
+ break;
+
+ case PROP_CORNERSNAP:
+ g_value_set_boolean (value, private->cornersnap);
+ break;
+ case PROP_FIXEDPIVOT:
+ g_value_set_boolean (value, private->fixedpivot);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+transform_is_convex (GimpVector2 *pos)
+{
+ return gimp_transform_polygon_is_convex (pos[0].x, pos[0].y,
+ pos[1].x, pos[1].y,
+ pos[2].x, pos[2].y,
+ pos[3].x, pos[3].y);
+}
+
+static gboolean
+transform_grid_is_convex (GimpToolTransformGrid *grid)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ return gimp_transform_polygon_is_convex (private->tx1, private->ty1,
+ private->tx2, private->ty2,
+ private->tx3, private->ty3,
+ private->tx4, private->ty4);
+}
+
+static inline gboolean
+vectorisnull (GimpVector2 v)
+{
+ return ((v.x == 0.0) && (v.y == 0.0));
+}
+
+static inline gdouble
+dotprod (GimpVector2 a,
+ GimpVector2 b)
+{
+ return a.x * b.x + a.y * b.y;
+}
+
+static inline gdouble
+norm (GimpVector2 a)
+{
+ return sqrt (dotprod (a, a));
+}
+
+static inline GimpVector2
+vectorsubtract (GimpVector2 a,
+ GimpVector2 b)
+{
+ GimpVector2 c;
+
+ c.x = a.x - b.x;
+ c.y = a.y - b.y;
+
+ return c;
+}
+
+static inline GimpVector2
+vectoradd (GimpVector2 a,
+ GimpVector2 b)
+{
+ GimpVector2 c;
+
+ c.x = a.x + b.x;
+ c.y = a.y + b.y;
+
+ return c;
+}
+
+static inline GimpVector2
+scalemult (GimpVector2 a,
+ gdouble b)
+{
+ GimpVector2 c;
+
+ c.x = a.x * b;
+ c.y = a.y * b;
+
+ return c;
+}
+
+static inline GimpVector2
+vectorproject (GimpVector2 a,
+ GimpVector2 b)
+{
+ return scalemult (b, dotprod (a, b) / dotprod (b, b));
+}
+
+/* finds the clockwise angle between the vectors given, 0-2Ï€ */
+static inline gdouble
+calcangle (GimpVector2 a,
+ GimpVector2 b)
+{
+ gdouble angle, angle2;
+ gdouble length;
+
+ if (vectorisnull (a) || vectorisnull (b))
+ return 0.0;
+
+ length = norm (a) * norm (b);
+
+ angle = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0));
+ angle2 = b.y;
+ b.y = -b.x;
+ b.x = angle2;
+ angle2 = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0));
+
+ return ((angle2 > G_PI / 2.0) ? angle : 2.0 * G_PI - angle);
+}
+
+static inline GimpVector2
+rotate2d (GimpVector2 p,
+ gdouble angle)
+{
+ GimpVector2 ret;
+
+ ret.x = cos (angle) * p.x-sin (angle) * p.y;
+ ret.y = sin (angle) * p.x+cos (angle) * p.y;
+
+ return ret;
+}
+
+static inline GimpVector2
+lineintersect (GimpVector2 p1, GimpVector2 p2,
+ GimpVector2 q1, GimpVector2 q2)
+{
+ gdouble denom, u;
+ GimpVector2 p;
+
+ denom = (q2.y - q1.y) * (p2.x - p1.x) - (q2.x - q1.x) * (p2.y - p1.y);
+ if (denom == 0.0)
+ {
+ p.x = (p1.x + p2.x + q1.x + q2.x) / 4;
+ p.y = (p1.y + p2.y + q1.y + q2.y) / 4;
+ }
+ else
+ {
+ u = (q2.x - q1.x) * (p1.y - q1.y) - (q2.y - q1.y) * (p1.x - q1.x);
+ u /= denom;
+
+ p.x = p1.x + u * (p2.x - p1.x);
+ p.y = p1.y + u * (p2.y - p1.y);
+ }
+
+ return p;
+}
+
+static inline GimpVector2
+get_pivot_delta (GimpToolTransformGrid *grid,
+ GimpVector2 *oldpos,
+ GimpVector2 *newpos,
+ GimpVector2 pivot)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpMatrix3 transform_before;
+ GimpMatrix3 transform_after;
+ GimpVector2 delta;
+
+ gimp_matrix3_identity (&transform_before);
+ gimp_matrix3_identity (&transform_after);
+
+ gimp_transform_matrix_perspective (&transform_before,
+ private->x1,
+ private->y1,
+ private->x2 - private->x1,
+ private->y2 - private->y1,
+ oldpos[0].x, oldpos[0].y,
+ oldpos[1].x, oldpos[1].y,
+ oldpos[2].x, oldpos[2].y,
+ oldpos[3].x, oldpos[3].y);
+ gimp_transform_matrix_perspective (&transform_after,
+ private->x1,
+ private->y1,
+ private->x2 - private->x1,
+ private->y2 - private->y1,
+ newpos[0].x, newpos[0].y,
+ newpos[1].x, newpos[1].y,
+ newpos[2].x, newpos[2].y,
+ newpos[3].x, newpos[3].y);
+ gimp_matrix3_invert (&transform_before);
+ gimp_matrix3_mult (&transform_after, &transform_before);
+ gimp_matrix3_transform_point (&transform_before,
+ pivot.x, pivot.y, &delta.x, &delta.y);
+
+ delta = vectorsubtract (delta, pivot);
+
+ return delta;
+}
+
+static gboolean
+point_is_inside_polygon (gint n,
+ gdouble *x,
+ gdouble *y,
+ gdouble px,
+ gdouble py)
+{
+ gint i, j;
+ gboolean odd = FALSE;
+
+ for (i = 0, j = n - 1; i < n; j = i++)
+ {
+ if ((y[i] < py && y[j] >= py) ||
+ (y[j] < py && y[i] >= py))
+ {
+ if (x[i] + (py - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < px)
+ odd = !odd;
+ }
+ }
+
+ return odd;
+}
+
+static gboolean
+point_is_inside_polygon_pos (GimpVector2 *pos,
+ GimpVector2 point)
+{
+ return point_is_inside_polygon (4,
+ (gdouble[4]){ pos[0].x, pos[1].x,
+ pos[3].x, pos[2].x },
+ (gdouble[4]){ pos[0].y, pos[1].y,
+ pos[3].y, pos[2].y },
+ point.x, point.y);
+}
+
+static void
+get_handle_geometry (GimpToolTransformGrid *grid,
+ GimpVector2 *position,
+ gdouble *angle)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ GimpVector2 o[] = { { .x = private->tx1, .y = private->ty1 },
+ { .x = private->tx2, .y = private->ty2 },
+ { .x = private->tx3, .y = private->ty3 },
+ { .x = private->tx4, .y = private->ty4 } };
+ GimpVector2 right = { .x = 1.0, .y = 0.0 };
+ GimpVector2 up = { .x = 0.0, .y = 1.0 };
+
+ if (position)
+ {
+ position[0] = o[0];
+ position[1] = o[1];
+ position[2] = o[2];
+ position[3] = o[3];
+ }
+
+ angle[0] = calcangle (vectorsubtract (o[1], o[0]), right);
+ angle[1] = calcangle (vectorsubtract (o[3], o[2]), right);
+ angle[2] = calcangle (vectorsubtract (o[3], o[1]), up);
+ angle[3] = calcangle (vectorsubtract (o[2], o[0]), up);
+
+ angle[4] = (angle[0] + angle[3]) / 2.0;
+ angle[5] = (angle[0] + angle[2]) / 2.0;
+ angle[6] = (angle[1] + angle[3]) / 2.0;
+ angle[7] = (angle[1] + angle[2]) / 2.0;
+
+ angle[8] = (angle[0] + angle[1] + angle[2] + angle[3]) / 4.0;
+}
+
+static void
+gimp_tool_transform_grid_changed (GimpToolWidget *widget)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+ gdouble angle[9];
+ GimpVector2 o[4], t[4];
+ gint handle_w;
+ gint handle_h;
+ gint d, i;
+
+ gimp_tool_transform_grid_update_box (grid);
+
+ gimp_canvas_transform_guides_set (private->guides,
+ &private->transform,
+ private->x1,
+ private->y1,
+ private->x2,
+ private->y2,
+ private->guide_type,
+ private->n_guides,
+ private->clip_guides);
+ gimp_canvas_item_set_visible (private->guides, private->show_guides);
+
+ get_handle_geometry (grid, o, angle);
+ gimp_tool_transform_grid_calc_handles (grid, &handle_w, &handle_h);
+
+ for (i = 0; i < 4; i++)
+ {
+ GimpCanvasItem *h;
+ gdouble factor;
+
+ /* the scale handles */
+ factor = 1.0;
+ if (private->use_perspective_handles)
+ factor = 1.5;
+
+ h = private->handles[GIMP_TRANSFORM_HANDLE_NW + i];
+ gimp_canvas_item_set_visible (h, private->use_corner_handles);
+
+ if (private->use_corner_handles)
+ {
+ gimp_canvas_handle_set_position (h, o[i].x, o[i].y);
+ gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor);
+ gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0);
+ }
+
+ /* the perspective handles */
+ factor = 1.0;
+ if (private->use_corner_handles)
+ factor = 0.8;
+
+ h = private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i];
+ gimp_canvas_item_set_visible (h, private->use_perspective_handles);
+
+ if (private->use_perspective_handles)
+ {
+ gimp_canvas_handle_set_position (h, o[i].x, o[i].y);
+ gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor);
+ gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0);
+ }
+ }
+
+ /* draw the side handles */
+ t[0] = scalemult (vectoradd (o[0], o[1]), 0.5);
+ t[1] = scalemult (vectoradd (o[2], o[3]), 0.5);
+ t[2] = scalemult (vectoradd (o[1], o[3]), 0.5);
+ t[3] = scalemult (vectoradd (o[2], o[0]), 0.5);
+
+ for (i = 0; i < 4; i++)
+ {
+ GimpCanvasItem *h;
+
+ h = private->handles[GIMP_TRANSFORM_HANDLE_N + i];
+ gimp_canvas_item_set_visible (h, private->use_side_handles);
+
+ if (private->use_side_handles)
+ {
+ gimp_canvas_handle_set_position (h, t[i].x, t[i].y);
+ gimp_canvas_handle_set_size (h, handle_w, handle_h);
+ gimp_canvas_handle_set_angles (h, angle[i], 0.0);
+ }
+ }
+
+ /* draw the shear handles */
+ t[0] = scalemult (vectoradd ( o[0] , scalemult (o[1], 3.0)),
+ 0.25);
+ t[1] = scalemult (vectoradd (scalemult (o[2], 3.0), o[3] ),
+ 0.25);
+ t[2] = scalemult (vectoradd ( o[1] , scalemult (o[3], 3.0)),
+ 0.25);
+ t[3] = scalemult (vectoradd (scalemult (o[0], 3.0), o[2] ),
+ 0.25);
+
+ for (i = 0; i < 4; i++)
+ {
+ GimpCanvasItem *h;
+
+ h = private->handles[GIMP_TRANSFORM_HANDLE_N_S + i];
+ gimp_canvas_item_set_visible (h, private->use_shear_handles);
+
+ if (private->use_shear_handles)
+ {
+ gimp_canvas_handle_set_position (h, t[i].x, t[i].y);
+ gimp_canvas_handle_set_size (h, handle_w, handle_h);
+ gimp_canvas_handle_set_angles (h, angle[i], 0.0);
+ }
+ }
+
+ d = MIN (handle_w, handle_h);
+ if (private->use_center_handle)
+ d *= 2; /* so you can grab it from under the center handle */
+
+ gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_PIVOT],
+ private->use_pivot_handle);
+
+ if (private->use_pivot_handle)
+ {
+ gimp_canvas_handle_set_position (private->pivot_items[0],
+ private->tpx, private->tpy);
+ gimp_canvas_handle_set_size (private->pivot_items[0], d, d);
+
+ gimp_canvas_handle_set_position (private->pivot_items[1],
+ private->tpx, private->tpy);
+ gimp_canvas_handle_set_size (private->pivot_items[1], d, d);
+ }
+
+ d = MIN (handle_w, handle_h);
+
+ gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_CENTER],
+ private->use_center_handle);
+
+ if (private->use_center_handle)
+ {
+ gimp_canvas_handle_set_position (private->center_items[0],
+ private->tcx, private->tcy);
+ gimp_canvas_handle_set_size (private->center_items[0], d, d);
+ gimp_canvas_handle_set_angles (private->center_items[0], angle[8], 0.0);
+
+ gimp_canvas_handle_set_position (private->center_items[1],
+ private->tcx, private->tcy);
+ gimp_canvas_handle_set_size (private->center_items[1], d, d);
+ gimp_canvas_handle_set_angles (private->center_items[1], angle[8], 0.0);
+ }
+
+ gimp_tool_transform_grid_update_hilight (grid);
+}
+
+gint
+gimp_tool_transform_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ private->button_down = TRUE;
+ private->mousex = coords->x;
+ private->mousey = coords->y;
+
+ if (private->handle != GIMP_TRANSFORM_HANDLE_NONE)
+ {
+ if (private->handles[private->handle])
+ {
+ GimpCanvasItem *handle;
+ gdouble x, y;
+
+ switch (private->handle)
+ {
+ case GIMP_TRANSFORM_HANDLE_CENTER:
+ handle = private->center_items[0];
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_PIVOT:
+ handle = private->pivot_items[0];
+ break;
+
+ default:
+ handle = private->handles[private->handle];
+ break;
+ }
+
+ gimp_canvas_handle_get_position (handle, &x, &y);
+
+ gimp_tool_widget_set_snap_offsets (widget,
+ SIGNED_ROUND (x - coords->x),
+ SIGNED_ROUND (y - coords->y),
+ 0, 0);
+ }
+ else
+ {
+ gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
+ }
+
+ private->prev_tx1 = private->tx1;
+ private->prev_ty1 = private->ty1;
+ private->prev_tx2 = private->tx2;
+ private->prev_ty2 = private->ty2;
+ private->prev_tx3 = private->tx3;
+ private->prev_ty3 = private->ty3;
+ private->prev_tx4 = private->tx4;
+ private->prev_ty4 = private->ty4;
+ private->prev_tpx = private->tpx;
+ private->prev_tpy = private->tpy;
+ private->prev_tcx = private->tcx;
+ private->prev_tcy = private->tcy;
+
+ return private->handle;
+ }
+
+ gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
+
+ return 0;
+}
+
+void
+gimp_tool_transform_grid_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ private->button_down = FALSE;
+}
+
+void
+gimp_tool_transform_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+ gdouble *x[4], *y[4];
+ gdouble *newpivot_x, *newpivot_y;
+
+ GimpVector2 oldpos[5], newpos[4];
+ GimpVector2 cur = { .x = coords->x,
+ .y = coords->y };
+ GimpVector2 mouse = { .x = private->mousex,
+ .y = private->mousey };
+ GimpVector2 d;
+ GimpVector2 pivot;
+
+ gboolean fixedpivot = private->fixedpivot;
+ GimpTransformHandle handle = private->handle;
+ gint i;
+
+ private->curx = coords->x;
+ private->cury = coords->y;
+
+ x[0] = &private->tx1;
+ y[0] = &private->ty1;
+ x[1] = &private->tx2;
+ y[1] = &private->ty2;
+ x[2] = &private->tx3;
+ y[2] = &private->ty3;
+ x[3] = &private->tx4;
+ y[3] = &private->ty4;
+
+ newpos[0].x = oldpos[0].x = private->prev_tx1;
+ newpos[0].y = oldpos[0].y = private->prev_ty1;
+ newpos[1].x = oldpos[1].x = private->prev_tx2;
+ newpos[1].y = oldpos[1].y = private->prev_ty2;
+ newpos[2].x = oldpos[2].x = private->prev_tx3;
+ newpos[2].y = oldpos[2].y = private->prev_ty3;
+ newpos[3].x = oldpos[3].x = private->prev_tx4;
+ newpos[3].y = oldpos[3].y = private->prev_ty4;
+
+ /* put center point in this array too */
+ oldpos[4].x = private->prev_tcx;
+ oldpos[4].y = private->prev_tcy;
+
+ d = vectorsubtract (cur, mouse);
+
+ newpivot_x = &private->tpx;
+ newpivot_y = &private->tpy;
+
+ if (private->use_pivot_handle)
+ {
+ pivot.x = private->prev_tpx;
+ pivot.y = private->prev_tpy;
+ }
+ else
+ {
+ /* when the transform grid doesn't use a pivot handle, use the center
+ * point as the pivot instead.
+ */
+ pivot.x = private->prev_tcx;
+ pivot.y = private->prev_tcy;
+
+ fixedpivot = TRUE;
+ }
+
+ /* move */
+ if (handle == GIMP_TRANSFORM_HANDLE_CENTER)
+ {
+ if (private->constrain_move)
+ {
+ /* snap to 45 degree vectors from starting point */
+ gdouble angle = 16.0 * calcangle ((GimpVector2) { 1.0, 0.0 },
+ d) / (2.0 * G_PI);
+ gdouble dist = norm (d) / sqrt (2);
+
+ if (angle < 1.0 || angle >= 15.0)
+ d.y = 0;
+ else if (angle < 3.0)
+ d.y = -(d.x = dist);
+ else if (angle < 5.0)
+ d.x = 0;
+ else if (angle < 7.0)
+ d.x = d.y = -dist;
+ else if (angle < 9.0)
+ d.y = 0;
+ else if (angle < 11.0)
+ d.x = -(d.y = dist);
+ else if (angle < 13.0)
+ d.x = 0;
+ else if (angle < 15.0)
+ d.x = d.y = dist;
+ }
+
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectoradd (oldpos[i], d);
+ }
+
+ /* rotate */
+ if (handle == GIMP_TRANSFORM_HANDLE_ROTATION)
+ {
+ gdouble angle = calcangle (vectorsubtract (cur, pivot),
+ vectorsubtract (mouse, pivot));
+
+ if (private->constrain_rotate)
+ {
+ /* round to 15 degree multiple */
+ angle /= 2 * G_PI / 24.0;
+ angle = round (angle);
+ angle *= 2 * G_PI / 24.0;
+ }
+
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectoradd (pivot,
+ rotate2d (vectorsubtract (oldpos[i], pivot),
+ angle));
+
+ fixedpivot = TRUE;
+ }
+
+ /* move rotation axis */
+ if (handle == GIMP_TRANSFORM_HANDLE_PIVOT)
+ {
+ pivot = vectoradd (pivot, d);
+
+ if (private->cornersnap)
+ {
+ /* snap to corner points and center */
+ gint closest = 0;
+ gdouble closest_dist = G_MAXDOUBLE, dist;
+
+ for (i = 0; i < 5; i++)
+ {
+ dist = norm (vectorsubtract (pivot, oldpos[i]));
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ closest = i;
+ }
+ }
+
+ if (closest_dist *
+ gimp_tool_widget_get_shell (widget)->scale_x < 50)
+ {
+ pivot = oldpos[closest];
+ }
+ }
+
+ fixedpivot = TRUE;
+ }
+
+ /* scaling via corner */
+ if (handle == GIMP_TRANSFORM_HANDLE_NW ||
+ handle == GIMP_TRANSFORM_HANDLE_NE ||
+ handle == GIMP_TRANSFORM_HANDLE_SE ||
+ handle == GIMP_TRANSFORM_HANDLE_SW)
+ {
+ /* Scaling through scale handles means translating one corner point,
+ * with all sides at constant angles.
+ */
+
+ gint this, left, right, opposite;
+
+ /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
+ if (handle == GIMP_TRANSFORM_HANDLE_NW)
+ {
+ this = 0; left = 1; right = 2; opposite = 3;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_NE)
+ {
+ this = 1; left = 3; right = 0; opposite = 2;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_SW)
+ {
+ this = 2; left = 0; right = 3; opposite = 1;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_SE)
+ {
+ this = 3; left = 2; right = 1; opposite = 0;
+ }
+ else
+ gimp_assert_not_reached ();
+
+ /* when the keep aspect transformation constraint is enabled,
+ * the translation shall only be along the diagonal that runs
+ * trough this corner point.
+ */
+ if (private->constrain_scale)
+ {
+ /* restrict to movement along the diagonal */
+ GimpVector2 diag = vectorsubtract (oldpos[this], oldpos[opposite]);
+
+ d = vectorproject (d, diag);
+ }
+
+ /* Move the corner being interacted with */
+ /* rp---------tp
+ * / /\ <- d, the interaction vector
+ * / / tp
+ * op----------/
+ *
+ */
+ newpos[this] = vectoradd (oldpos[this], d);
+
+ /* Where the corner to the right and left would go, need these to form
+ * lines to intersect with the sides */
+ /* rp----------/
+ * /\ /\
+ * / nr / nt
+ * op----------lp
+ * \
+ * nl
+ */
+
+ newpos[right] = vectoradd (oldpos[right], d);
+ newpos[left] = vectoradd (oldpos[left], d);
+
+ /* Now we just need to find the intersection of op-rp and nr-nt.
+ * rp----------/
+ * / /
+ * / nr==========nt
+ * op----------/
+ *
+ */
+ newpos[right] = lineintersect (newpos[right], newpos[this],
+ oldpos[opposite], oldpos[right]);
+ newpos[left] = lineintersect (newpos[left], newpos[this],
+ oldpos[opposite], oldpos[left]);
+ /* /-----------/
+ * / /
+ * rp============nt
+ * op----------/
+ *
+ */
+
+ /*
+ *
+ * /--------------/
+ * /--------------/
+ *
+ */
+
+ if (private->frompivot_scale &&
+ transform_is_convex (newpos) &&
+ transform_is_convex (oldpos))
+ {
+ /* transform the pivot point before the interaction and
+ * after, and move everything by this difference
+ */
+ //TODO the handle doesn't actually end up where the mouse cursor is
+ GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectorsubtract (newpos[i], delta);
+
+ fixedpivot = TRUE;
+ }
+ }
+
+ /* scaling via sides */
+ if (handle == GIMP_TRANSFORM_HANDLE_N ||
+ handle == GIMP_TRANSFORM_HANDLE_E ||
+ handle == GIMP_TRANSFORM_HANDLE_S ||
+ handle == GIMP_TRANSFORM_HANDLE_W)
+ {
+ gint this_l, this_r, opp_l, opp_r;
+ GimpVector2 side_l, side_r, midline;
+
+ /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
+ if (handle == GIMP_TRANSFORM_HANDLE_N)
+ {
+ this_l = 1; this_r = 0;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_E)
+ {
+ this_l = 3; this_r = 1;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_S)
+ {
+ this_l = 2; this_r = 3;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_W)
+ {
+ this_l = 0; this_r = 2;
+ }
+ else
+ gimp_assert_not_reached ();
+
+ opp_l = 3 - this_r; opp_r = 3 - this_l;
+
+ side_l = vectorsubtract (oldpos[opp_l], oldpos[this_l]);
+ side_r = vectorsubtract (oldpos[opp_r], oldpos[this_r]);
+ midline = vectoradd (side_l, side_r);
+
+ /* restrict to movement along the midline */
+ d = vectorproject (d, midline);
+
+ if (private->constrain_scale)
+ {
+ GimpVector2 before, after, effective_pivot = pivot;
+ gdouble distance;
+
+ if (! private->frompivot_scale)
+ {
+ /* center of the opposite side is pivot */
+ effective_pivot = scalemult (vectoradd (oldpos[opp_l],
+ oldpos[opp_r]), 0.5);
+ }
+
+ /* get the difference between the distance from the pivot to
+ * where interaction started and the distance from the pivot
+ * to where cursor is now, and scale all corners distance
+ * from the pivot with this factor
+ */
+ before = vectorsubtract (effective_pivot, mouse);
+ after = vectorsubtract (effective_pivot, cur);
+ after = vectorproject (after, before);
+
+ distance = 0.5 * (after.x / before.x + after.y / before.y);
+
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectoradd (effective_pivot,
+ scalemult (vectorsubtract (oldpos[i],
+ effective_pivot),
+ distance));
+ }
+ else
+ {
+ /* just move the side */
+ newpos[this_l] = vectoradd (oldpos[this_l], d);
+ newpos[this_r] = vectoradd (oldpos[this_r], d);
+ }
+
+ if (! private->constrain_scale &&
+ private->frompivot_scale &&
+ transform_is_convex (newpos) &&
+ transform_is_convex (oldpos))
+ {
+ GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectorsubtract (newpos[i], delta);
+
+ fixedpivot = TRUE;
+ }
+ }
+
+ /* shear */
+ if (handle == GIMP_TRANSFORM_HANDLE_N_S ||
+ handle == GIMP_TRANSFORM_HANDLE_E_S ||
+ handle == GIMP_TRANSFORM_HANDLE_S_S ||
+ handle == GIMP_TRANSFORM_HANDLE_W_S)
+ {
+ gint this_l, this_r;
+
+ /* set up indices for this edge and the opposite edge */
+ if (handle == GIMP_TRANSFORM_HANDLE_N_S)
+ {
+ this_l = 1; this_r = 0;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_W_S)
+ {
+ this_l = 0; this_r = 2;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_S_S)
+ {
+ this_l = 2; this_r = 3;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_E_S)
+ {
+ this_l = 3; this_r = 1;
+ }
+ else
+ gimp_assert_not_reached ();
+
+ if (private->constrain_shear)
+ {
+ /* restrict to movement along the side */
+ GimpVector2 side = vectorsubtract (oldpos[this_r], oldpos[this_l]);
+
+ d = vectorproject (d, side);
+ }
+
+ newpos[this_l] = vectoradd (oldpos[this_l], d);
+ newpos[this_r] = vectoradd (oldpos[this_r], d);
+
+ if (private->frompivot_shear &&
+ transform_is_convex (newpos) &&
+ transform_is_convex (oldpos))
+ {
+ GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectorsubtract (newpos[i], delta);
+
+ fixedpivot = TRUE;
+ }
+ }
+
+ /* perspective transform */
+ if (handle == GIMP_TRANSFORM_HANDLE_NW_P ||
+ handle == GIMP_TRANSFORM_HANDLE_NE_P ||
+ handle == GIMP_TRANSFORM_HANDLE_SE_P ||
+ handle == GIMP_TRANSFORM_HANDLE_SW_P)
+ {
+ gint this, left, right, opposite;
+
+ /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
+ if (handle == GIMP_TRANSFORM_HANDLE_NW_P)
+ {
+ this = 0; left = 1; right = 2; opposite = 3;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_NE_P)
+ {
+ this = 1; left = 3; right = 0; opposite = 2;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_SW_P)
+ {
+ this = 2; left = 0; right = 3; opposite = 1;
+ }
+ else if (handle == GIMP_TRANSFORM_HANDLE_SE_P)
+ {
+ this = 3; left = 2; right = 1; opposite = 0;
+ }
+ else
+ gimp_assert_not_reached ();
+
+ if (private->constrain_perspective)
+ {
+ /* when the constrain transformation constraint is enabled,
+ * the translation shall only be either along the side
+ * angles of the two sides that run to this corner point, or
+ * along the diagonal that runs trough this corner point.
+ */
+ GimpVector2 proj[4];
+ gdouble rej[4];
+
+ for (i = 0; i < 4; i++)
+ {
+ if (i == this)
+ continue;
+
+ /* get the vectors along the sides and the diagonal */
+ proj[i] = vectorsubtract (oldpos[this], oldpos[i]);
+
+ /* project d on each candidate vector and see which has
+ * the shortest rejection
+ */
+ proj[i] = vectorproject (d, proj[i]);
+ rej[i] = norm (vectorsubtract (d, proj[i]));
+ }
+
+ if (rej[left] < rej[right] && rej[left] < rej[opposite])
+ d = proj[left];
+ else if (rej[right] < rej[opposite])
+ d = proj[right];
+ else
+ d = proj[opposite];
+ }
+
+ newpos[this] = vectoradd (oldpos[this], d);
+
+ if (private->frompivot_perspective &&
+ transform_is_convex (newpos) &&
+ transform_is_convex (oldpos))
+ {
+ GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
+
+ for (i = 0; i < 4; i++)
+ newpos[i] = vectorsubtract (newpos[i], delta);
+
+ fixedpivot = TRUE;
+ }
+ }
+
+ /* this will have been set to TRUE if an operation used the pivot in
+ * addition to being a user option
+ */
+ if (! fixedpivot &&
+ transform_is_convex (newpos) &&
+ transform_is_convex (oldpos) &&
+ point_is_inside_polygon_pos (oldpos, pivot))
+ {
+ GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
+ pivot = vectoradd (pivot, delta);
+ }
+
+ /* make sure the new coordinates are valid */
+ for (i = 0; i < 4; i++)
+ {
+ if (! isfinite (newpos[i].x) || ! isfinite (newpos[i].y))
+ return;
+ }
+
+ if (! isfinite (pivot.x) || ! isfinite (pivot.y))
+ return;
+
+ for (i = 0; i < 4; i++)
+ {
+ *x[i] = newpos[i].x;
+ *y[i] = newpos[i].y;
+ }
+
+ /* set unconditionally: if options get toggled during operation, we
+ * have to move pivot back
+ */
+ *newpivot_x = pivot.x;
+ *newpivot_y = pivot.y;
+
+ gimp_tool_transform_grid_update_matrix (grid);
+}
+
+static const gchar *
+get_friendly_operation_name (GimpTransformHandle handle)
+{
+ switch (handle)
+ {
+ case GIMP_TRANSFORM_HANDLE_NONE:
+ return "";
+ case GIMP_TRANSFORM_HANDLE_NW_P:
+ case GIMP_TRANSFORM_HANDLE_NE_P:
+ case GIMP_TRANSFORM_HANDLE_SW_P:
+ case GIMP_TRANSFORM_HANDLE_SE_P:
+ return _("Click-Drag to change perspective");
+ case GIMP_TRANSFORM_HANDLE_NW:
+ case GIMP_TRANSFORM_HANDLE_NE:
+ case GIMP_TRANSFORM_HANDLE_SW:
+ case GIMP_TRANSFORM_HANDLE_SE:
+ return _("Click-Drag to scale");
+ case GIMP_TRANSFORM_HANDLE_N:
+ case GIMP_TRANSFORM_HANDLE_S:
+ case GIMP_TRANSFORM_HANDLE_E:
+ case GIMP_TRANSFORM_HANDLE_W:
+ return _("Click-Drag to scale");
+ case GIMP_TRANSFORM_HANDLE_CENTER:
+ return _("Click-Drag to move");
+ case GIMP_TRANSFORM_HANDLE_PIVOT:
+ return _("Click-Drag to move the pivot point");
+ case GIMP_TRANSFORM_HANDLE_N_S:
+ case GIMP_TRANSFORM_HANDLE_S_S:
+ case GIMP_TRANSFORM_HANDLE_E_S:
+ case GIMP_TRANSFORM_HANDLE_W_S:
+ return _("Click-Drag to shear");
+ case GIMP_TRANSFORM_HANDLE_ROTATION:
+ return _("Click-Drag to rotate");
+ default:
+ gimp_assert_not_reached ();
+ }
+}
+
+static GimpTransformHandle
+gimp_tool_transform_get_area_handle (GimpToolTransformGrid *grid,
+ const GimpCoords *coords,
+ GimpTransformFunction function)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpTransformHandle handle = GIMP_TRANSFORM_HANDLE_NONE;
+
+ switch (function)
+ {
+ case GIMP_TRANSFORM_FUNCTION_NONE:
+ break;
+
+ case GIMP_TRANSFORM_FUNCTION_MOVE:
+ handle = GIMP_TRANSFORM_HANDLE_CENTER;
+ break;
+
+ case GIMP_TRANSFORM_FUNCTION_ROTATE:
+ handle = GIMP_TRANSFORM_HANDLE_ROTATION;
+ break;
+
+ case GIMP_TRANSFORM_FUNCTION_SCALE:
+ case GIMP_TRANSFORM_FUNCTION_PERSPECTIVE:
+ {
+ gdouble closest_dist;
+ gdouble dist;
+
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ private->tx1,
+ private->ty1);
+ closest_dist = dist;
+ if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
+ handle = GIMP_TRANSFORM_HANDLE_NW_P;
+ else
+ handle = GIMP_TRANSFORM_HANDLE_NW;
+
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ private->tx2,
+ private->ty2);
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
+ handle = GIMP_TRANSFORM_HANDLE_NE_P;
+ else
+ handle = GIMP_TRANSFORM_HANDLE_NE;
+ }
+
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ private->tx3,
+ private->ty3);
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
+ handle = GIMP_TRANSFORM_HANDLE_SW_P;
+ else
+ handle = GIMP_TRANSFORM_HANDLE_SW;
+ }
+
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ private->tx4,
+ private->ty4);
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE)
+ handle = GIMP_TRANSFORM_HANDLE_SE_P;
+ else
+ handle = GIMP_TRANSFORM_HANDLE_SE;
+ }
+ }
+ break;
+
+ case GIMP_TRANSFORM_FUNCTION_SHEAR:
+ {
+ gdouble handle_x;
+ gdouble handle_y;
+ gdouble closest_dist;
+ gdouble dist;
+
+ gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_N],
+ &handle_x, &handle_y);
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ handle_x, handle_y);
+ closest_dist = dist;
+ handle = GIMP_TRANSFORM_HANDLE_N_S;
+
+ gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_W],
+ &handle_x, &handle_y);
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ handle_x, handle_y);
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ handle = GIMP_TRANSFORM_HANDLE_W_S;
+ }
+
+ gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_E],
+ &handle_x, &handle_y);
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ handle_x, handle_y);
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ handle = GIMP_TRANSFORM_HANDLE_E_S;
+ }
+
+ gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_S],
+ &handle_x, &handle_y);
+ dist = gimp_canvas_item_transform_distance_square (private->guides,
+ coords->x, coords->y,
+ handle_x, handle_y);
+ if (dist < closest_dist)
+ {
+ closest_dist = dist;
+ handle = GIMP_TRANSFORM_HANDLE_S_S;
+ }
+ }
+ break;
+ }
+
+ return handle;
+}
+
+GimpHit
+gimp_tool_transform_grid_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpTransformHandle handle;
+
+ handle = gimp_tool_transform_grid_get_handle_for_coords (grid, coords);
+
+ if (handle != GIMP_TRANSFORM_HANDLE_NONE)
+ return GIMP_HIT_DIRECT;
+
+ return GIMP_HIT_INDIRECT;
+}
+
+void
+gimp_tool_transform_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpTransformHandle handle;
+
+ handle = gimp_tool_transform_grid_get_handle_for_coords (grid, coords);
+
+ if (handle == GIMP_TRANSFORM_HANDLE_NONE)
+ {
+ /* points passed in clockwise order */
+ if (point_is_inside_polygon (4,
+ (gdouble[4]){ private->tx1, private->tx2,
+ private->tx4, private->tx3 },
+ (gdouble[4]){ private->ty1, private->ty2,
+ private->ty4, private->ty3 },
+ coords->x, coords->y))
+ {
+ handle = gimp_tool_transform_get_area_handle (grid, coords,
+ private->inside_function);
+ }
+ else
+ {
+ handle = gimp_tool_transform_get_area_handle (grid, coords,
+ private->outside_function);
+ }
+ }
+
+ if (handle != GIMP_TRANSFORM_HANDLE_NONE && proximity)
+ {
+ gimp_tool_widget_set_status (widget,
+ get_friendly_operation_name (handle));
+ }
+ else
+ {
+ gimp_tool_widget_set_status (widget, NULL);
+ }
+
+ private->handle = handle;
+
+ gimp_tool_transform_grid_update_hilight (grid);
+}
+
+void
+gimp_tool_transform_grid_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ private->handle = GIMP_TRANSFORM_HANDLE_NONE;
+
+ gimp_tool_transform_grid_update_hilight (grid);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static void
+gimp_tool_transform_grid_modifier (GimpToolWidget *widget,
+ GdkModifierType key)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ if (key == gimp_get_constrain_behavior_mask ())
+ {
+ g_object_set (widget,
+ "frompivot-scale", ! private->frompivot_scale,
+ "frompivot-shear", ! private->frompivot_shear,
+ "frompivot-perspective", ! private->frompivot_perspective,
+ NULL);
+ }
+ else if (key == gimp_get_extend_selection_mask ())
+ {
+ g_object_set (widget,
+ "cornersnap", ! private->cornersnap,
+ "constrain-move", ! private->constrain_move,
+ "constrain-scale", ! private->constrain_scale,
+ "constrain-rotate", ! private->constrain_rotate,
+ "constrain-shear", ! private->constrain_shear,
+ "constrain-perspective", ! private->constrain_perspective,
+ NULL);
+ }
+}
+
+static void
+gimp_tool_transform_grid_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpCoords coords = { 0.0, };
+
+ gimp_tool_transform_grid_modifier (widget, key);
+
+ if (private->button_down)
+ {
+ /* send a non-motion to update the grid with the new constraints */
+ coords.x = private->curx;
+ coords.y = private->cury;
+ gimp_tool_transform_grid_motion (widget, &coords, 0, state);
+ }
+}
+
+static gboolean
+gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget);
+ GimpToolTransformGridPrivate *private = grid->private;
+ gdouble angle[9];
+ gint i;
+ GimpCursorType map[8];
+ GimpVector2 pos[4], this, that;
+ gboolean flip = FALSE;
+ gboolean side = FALSE;
+ gboolean set_cursor = TRUE;
+
+ map[0] = GIMP_CURSOR_CORNER_TOP_LEFT;
+ map[1] = GIMP_CURSOR_CORNER_TOP;
+ map[2] = GIMP_CURSOR_CORNER_TOP_RIGHT;
+ map[3] = GIMP_CURSOR_CORNER_RIGHT;
+ map[4] = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
+ map[5] = GIMP_CURSOR_CORNER_BOTTOM;
+ map[6] = GIMP_CURSOR_CORNER_BOTTOM_LEFT;
+ map[7] = GIMP_CURSOR_CORNER_LEFT;
+
+ get_handle_geometry (grid, pos, angle);
+
+ for (i = 0; i < 8; i++)
+ angle[i] = round (angle[i] * 180.0 / G_PI / 45.0);
+
+ switch (private->handle)
+ {
+ case GIMP_TRANSFORM_HANDLE_NW_P:
+ case GIMP_TRANSFORM_HANDLE_NW:
+ i = (gint) angle[4] + 0;
+ this = pos[0];
+ that = pos[3];
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_NE_P:
+ case GIMP_TRANSFORM_HANDLE_NE:
+ i = (gint) angle[5] + 2;
+ this = pos[1];
+ that = pos[2];
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_SW_P:
+ case GIMP_TRANSFORM_HANDLE_SW:
+ i = (gint) angle[6] + 6;
+ this = pos[2];
+ that = pos[1];
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_SE_P:
+ case GIMP_TRANSFORM_HANDLE_SE:
+ i = (gint) angle[7] + 4;
+ this = pos[3];
+ that = pos[0];
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_N:
+ case GIMP_TRANSFORM_HANDLE_N_S:
+ i = (gint) angle[0] + 1;
+ this = vectoradd (pos[0], pos[1]);
+ that = vectoradd (pos[2], pos[3]);
+ side = TRUE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_S:
+ case GIMP_TRANSFORM_HANDLE_S_S:
+ i = (gint) angle[1] + 5;
+ this = vectoradd (pos[2], pos[3]);
+ that = vectoradd (pos[0], pos[1]);
+ side = TRUE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_E:
+ case GIMP_TRANSFORM_HANDLE_E_S:
+ i = (gint) angle[2] + 3;
+ this = vectoradd (pos[1], pos[3]);
+ that = vectoradd (pos[0], pos[2]);
+ side = TRUE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_W:
+ case GIMP_TRANSFORM_HANDLE_W_S:
+ i = (gint) angle[3] + 7;
+ this = vectoradd (pos[0], pos[2]);
+ that = vectoradd (pos[1], pos[3]);
+ side = TRUE;
+ break;
+
+ default:
+ set_cursor = FALSE;
+ break;
+ }
+
+ if (set_cursor)
+ {
+ i %= 8;
+
+ switch (map[i])
+ {
+ case GIMP_CURSOR_CORNER_TOP_LEFT:
+ if (this.x + this.y > that.x + that.y)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_TOP:
+ if (this.y > that.y)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_TOP_RIGHT:
+ if (this.x - this.y < that.x - that.y)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_RIGHT:
+ if (this.x < that.x)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_BOTTOM_RIGHT:
+ if (this.x + this.y < that.x + that.y)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_BOTTOM:
+ if (this.y < that.y)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_BOTTOM_LEFT:
+ if (this.x - this.y > that.x - that.y)
+ flip = TRUE;
+ break;
+ case GIMP_CURSOR_CORNER_LEFT:
+ if (this.x > that.x)
+ flip = TRUE;
+ break;
+ default:
+ gimp_assert_not_reached ();
+ }
+
+ if (flip)
+ *cursor = map[(i + 4) % 8];
+ else
+ *cursor = map[i];
+
+ if (side)
+ *cursor += 8;
+ }
+
+ /* parent class handles *cursor and *modifier for most handles */
+ switch (private->handle)
+ {
+ case GIMP_TRANSFORM_HANDLE_NONE:
+ *tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_NW_P:
+ case GIMP_TRANSFORM_HANDLE_NE_P:
+ case GIMP_TRANSFORM_HANDLE_SW_P:
+ case GIMP_TRANSFORM_HANDLE_SE_P:
+ *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_NW:
+ case GIMP_TRANSFORM_HANDLE_NE:
+ case GIMP_TRANSFORM_HANDLE_SW:
+ case GIMP_TRANSFORM_HANDLE_SE:
+ case GIMP_TRANSFORM_HANDLE_N:
+ case GIMP_TRANSFORM_HANDLE_S:
+ case GIMP_TRANSFORM_HANDLE_E:
+ case GIMP_TRANSFORM_HANDLE_W:
+ *tool_cursor = GIMP_TOOL_CURSOR_RESIZE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_CENTER:
+ *tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_PIVOT:
+ *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_N_S:
+ case GIMP_TRANSFORM_HANDLE_S_S:
+ case GIMP_TRANSFORM_HANDLE_E_S:
+ case GIMP_TRANSFORM_HANDLE_W_S:
+ *tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
+ break;
+
+ case GIMP_TRANSFORM_HANDLE_ROTATION:
+ *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
+ break;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ return TRUE;
+}
+
+static GimpTransformHandle
+gimp_tool_transform_grid_get_handle_for_coords (GimpToolTransformGrid *grid,
+ const GimpCoords *coords)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpTransformHandle i;
+
+ for (i = GIMP_TRANSFORM_HANDLE_NONE + 1; i < GIMP_N_TRANSFORM_HANDLES; i++)
+ {
+ if (private->handles[i] &&
+ gimp_canvas_item_hit (private->handles[i], coords->x, coords->y))
+ {
+ return i;
+ }
+ }
+
+ return GIMP_TRANSFORM_HANDLE_NONE;
+}
+
+static void
+gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+ GimpTransformHandle handle;
+
+ for (handle = GIMP_TRANSFORM_HANDLE_NONE;
+ handle < GIMP_N_TRANSFORM_HANDLES;
+ handle++)
+ {
+ if (private->handles[handle])
+ {
+ gimp_canvas_item_set_highlight (private->handles[handle],
+ handle == private->handle);
+ }
+ }
+}
+
+static void
+gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ gimp_matrix3_transform_point (&private->transform,
+ private->x1, private->y1,
+ &private->tx1, &private->ty1);
+ gimp_matrix3_transform_point (&private->transform,
+ private->x2, private->y1,
+ &private->tx2, &private->ty2);
+ gimp_matrix3_transform_point (&private->transform,
+ private->x1, private->y2,
+ &private->tx3, &private->ty3);
+ gimp_matrix3_transform_point (&private->transform,
+ private->x2, private->y2,
+ &private->tx4, &private->ty4);
+
+ /* don't transform pivot */
+ private->tpx = private->pivot_x;
+ private->tpy = private->pivot_y;
+
+ if (transform_grid_is_convex (grid))
+ {
+ gimp_matrix3_transform_point (&private->transform,
+ (private->x1 + private->x2) / 2.0,
+ (private->y1 + private->y2) / 2.0,
+ &private->tcx, &private->tcy);
+ }
+ else
+ {
+ private->tcx = (private->tx1 +
+ private->tx2 +
+ private->tx3 +
+ private->tx4) / 4.0;
+ private->tcy = (private->ty1 +
+ private->ty2 +
+ private->ty3 +
+ private->ty4) / 4.0;
+ }
+}
+
+static void
+gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+
+ gimp_matrix3_identity (&private->transform);
+ gimp_transform_matrix_perspective (&private->transform,
+ private->x1,
+ private->y1,
+ private->x2 - private->x1,
+ private->y2 - private->y1,
+ private->tx1,
+ private->ty1,
+ private->tx2,
+ private->ty2,
+ private->tx3,
+ private->ty3,
+ private->tx4,
+ private->ty4);
+
+ private->pivot_x = private->tpx;
+ private->pivot_y = private->tpy;
+
+ g_object_freeze_notify (G_OBJECT (grid));
+ g_object_notify (G_OBJECT (grid), "transform");
+ g_object_notify (G_OBJECT (grid), "pivot-x");
+ g_object_notify (G_OBJECT (grid), "pivot-x");
+ g_object_thaw_notify (G_OBJECT (grid));
+}
+
+static void
+gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid,
+ gint *handle_w,
+ gint *handle_h)
+{
+ GimpToolTransformGridPrivate *private = grid->private;
+ gint dx1, dy1;
+ gint dx2, dy2;
+ gint dx3, dy3;
+ gint dx4, dy4;
+ gint x1, y1;
+ gint x2, y2;
+
+ if (! private->dynamic_handle_size)
+ {
+ *handle_w = GIMP_CANVAS_HANDLE_SIZE_LARGE;
+ *handle_h = GIMP_CANVAS_HANDLE_SIZE_LARGE;
+
+ return;
+ }
+
+ gimp_canvas_item_transform_xy (private->guides,
+ private->tx1, private->ty1,
+ &dx1, &dy1);
+ gimp_canvas_item_transform_xy (private->guides,
+ private->tx2, private->ty2,
+ &dx2, &dy2);
+ gimp_canvas_item_transform_xy (private->guides,
+ private->tx3, private->ty3,
+ &dx3, &dy3);
+ gimp_canvas_item_transform_xy (private->guides,
+ private->tx4, private->ty4,
+ &dx4, &dy4);
+
+ x1 = MIN4 (dx1, dx2, dx3, dx4);
+ y1 = MIN4 (dy1, dy2, dy3, dy4);
+ x2 = MAX4 (dx1, dx2, dx3, dx4);
+ y2 = MAX4 (dy1, dy2, dy3, dy4);
+
+ *handle_w = CLAMP ((x2 - x1) / 3,
+ MIN_HANDLE_SIZE, GIMP_CANVAS_HANDLE_SIZE_LARGE);
+ *handle_h = CLAMP ((y2 - y1) / 3,
+ MIN_HANDLE_SIZE, GIMP_CANVAS_HANDLE_SIZE_LARGE);
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_transform_grid_new (GimpDisplayShell *shell,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_GRID,
+ "shell", shell,
+ "transform", transform,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+}
+
+
+/* protected functions */
+
+GimpTransformHandle
+gimp_tool_transform_grid_get_handle (GimpToolTransformGrid *grid)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_TRANSFORM_GRID (grid),
+ GIMP_TRANSFORM_HANDLE_NONE);
+
+ return grid->private->handle;
+}
diff --git a/app/display/gimptooltransformgrid.h b/app/display/gimptooltransformgrid.h
new file mode 100644
index 0000000..949beb2
--- /dev/null
+++ b/app/display/gimptooltransformgrid.h
@@ -0,0 +1,99 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptooltransformgrid.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_TRANSFORM_GRID_H__
+#define __GIMP_TOOL_TRANSFORM_GRID_H__
+
+
+#include "gimpcanvashandle.h"
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE \
+ (1.5 * GIMP_CANVAS_HANDLE_SIZE_LARGE)
+
+
+typedef enum
+{
+ GIMP_TRANSFORM_HANDLE_NONE,
+ GIMP_TRANSFORM_HANDLE_NW_P, /* north west perspective */
+ GIMP_TRANSFORM_HANDLE_NE_P, /* north east perspective */
+ GIMP_TRANSFORM_HANDLE_SW_P, /* south west perspective */
+ GIMP_TRANSFORM_HANDLE_SE_P, /* south east perspective */
+ GIMP_TRANSFORM_HANDLE_NW, /* north west */
+ GIMP_TRANSFORM_HANDLE_NE, /* north east */
+ GIMP_TRANSFORM_HANDLE_SW, /* south west */
+ GIMP_TRANSFORM_HANDLE_SE, /* south east */
+ GIMP_TRANSFORM_HANDLE_N, /* north */
+ GIMP_TRANSFORM_HANDLE_S, /* south */
+ GIMP_TRANSFORM_HANDLE_E, /* east */
+ GIMP_TRANSFORM_HANDLE_W, /* west */
+ GIMP_TRANSFORM_HANDLE_CENTER, /* center for moving */
+ GIMP_TRANSFORM_HANDLE_PIVOT, /* pivot for rotation and scaling */
+ GIMP_TRANSFORM_HANDLE_N_S, /* north shearing */
+ GIMP_TRANSFORM_HANDLE_S_S, /* south shearing */
+ GIMP_TRANSFORM_HANDLE_E_S, /* east shearing */
+ GIMP_TRANSFORM_HANDLE_W_S, /* west shearing */
+ GIMP_TRANSFORM_HANDLE_ROTATION, /* rotation */
+
+ GIMP_N_TRANSFORM_HANDLES /* keep this last so *handles[] is the right size */
+} GimpTransformHandle;
+
+
+#define GIMP_TYPE_TOOL_TRANSFORM_GRID (gimp_tool_transform_grid_get_type ())
+#define GIMP_TOOL_TRANSFORM_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGrid))
+#define GIMP_TOOL_TRANSFORM_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGridClass))
+#define GIMP_IS_TOOL_TRANSFORM_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID))
+#define GIMP_IS_TOOL_TRANSFORM_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_TRANSFORM_GRID))
+#define GIMP_TOOL_TRANSFORM_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGridClass))
+
+
+typedef struct _GimpToolTransformGrid GimpToolTransformGrid;
+typedef struct _GimpToolTransformGridPrivate GimpToolTransformGridPrivate;
+typedef struct _GimpToolTransformGridClass GimpToolTransformGridClass;
+
+struct _GimpToolTransformGrid
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolTransformGridPrivate *private;
+};
+
+struct _GimpToolTransformGridClass
+{
+ GimpToolWidgetClass parent_class;
+};
+
+
+GType gimp_tool_transform_grid_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_transform_grid_new (GimpDisplayShell *shell,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+/* protected functions */
+
+GimpTransformHandle gimp_tool_transform_grid_get_handle (GimpToolTransformGrid *grid);
+
+
+#endif /* __GIMP_TOOL_TRANSFORM_GRID_H__ */
diff --git a/app/display/gimptoolwidget.c b/app/display/gimptoolwidget.c
new file mode 100644
index 0000000..516f366
--- /dev/null
+++ b/app/display/gimptoolwidget.c
@@ -0,0 +1,1117 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolwidget.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 <stdarg.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "display-types.h"
+
+#include "core/gimpmarshal.h"
+
+#include "gimpcanvasarc.h"
+#include "gimpcanvascorner.h"
+#include "gimpcanvasgroup.h"
+#include "gimpcanvashandle.h"
+#include "gimpcanvaslimit.h"
+#include "gimpcanvasline.h"
+#include "gimpcanvaspath.h"
+#include "gimpcanvaspolygon.h"
+#include "gimpcanvasrectangle.h"
+#include "gimpcanvasrectangleguides.h"
+#include "gimpcanvastransformguides.h"
+#include "gimpdisplayshell.h"
+#include "gimptoolwidget.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SHELL,
+ PROP_ITEM
+};
+
+enum
+{
+ CHANGED,
+ RESPONSE,
+ SNAP_OFFSETS,
+ STATUS,
+ STATUS_COORDS,
+ MESSAGE,
+ FOCUS_CHANGED,
+ LAST_SIGNAL
+};
+
+struct _GimpToolWidgetPrivate
+{
+ GimpDisplayShell *shell;
+ GimpCanvasItem *item;
+ GList *group_stack;
+
+ gint snap_offset_x;
+ gint snap_offset_y;
+ gint snap_width;
+ gint snap_height;
+
+ gboolean visible;
+ gboolean focus;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_widget_finalize (GObject *object);
+static void gimp_tool_widget_constructed (GObject *object);
+static void gimp_tool_widget_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_widget_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_widget_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs);
+
+static void gimp_tool_widget_real_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_widget_real_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolWidget, gimp_tool_widget, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_tool_widget_parent_class
+
+static guint widget_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_tool_widget_class_init (GimpToolWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_tool_widget_finalize;
+ object_class->constructed = gimp_tool_widget_constructed;
+ object_class->set_property = gimp_tool_widget_set_property;
+ object_class->get_property = gimp_tool_widget_get_property;
+ object_class->dispatch_properties_changed = gimp_tool_widget_properties_changed;
+
+ klass->leave_notify = gimp_tool_widget_real_leave_notify;
+ klass->key_press = gimp_tool_widget_real_key_press;
+
+ widget_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ widget_signals[RESPONSE] =
+ g_signal_new ("response",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, response),
+ NULL, NULL,
+ gimp_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ widget_signals[SNAP_OFFSETS] =
+ g_signal_new ("snap-offsets",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, snap_offsets),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ widget_signals[STATUS] =
+ g_signal_new ("status",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, status),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ widget_signals[STATUS_COORDS] =
+ g_signal_new ("status-coords",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, status_coords),
+ NULL, NULL,
+ gimp_marshal_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING,
+ G_TYPE_NONE, 5,
+ G_TYPE_STRING,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING);
+
+ widget_signals[MESSAGE] =
+ g_signal_new ("message",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, message),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ widget_signals[FOCUS_CHANGED] =
+ g_signal_new ("focus-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolWidgetClass, focus_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_SHELL,
+ g_param_spec_object ("shell",
+ NULL, NULL,
+ GIMP_TYPE_DISPLAY_SHELL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_ITEM,
+ g_param_spec_object ("item",
+ NULL, NULL,
+ GIMP_TYPE_CANVAS_ITEM,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_tool_widget_init (GimpToolWidget *widget)
+{
+ widget->private = gimp_tool_widget_get_instance_private (widget);
+
+ widget->private->visible = TRUE;
+}
+
+static void
+gimp_tool_widget_constructed (GObject *object)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolWidgetPrivate *private = widget->private;
+ GimpToolWidgetClass *klass = GIMP_TOOL_WIDGET_GET_CLASS (widget);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_DISPLAY_SHELL (private->shell));
+
+ private->item = gimp_canvas_group_new (private->shell);
+
+ gimp_canvas_item_set_visible (private->item, private->visible);
+
+ if (klass->changed)
+ {
+ if (klass->update_on_scale)
+ {
+ g_signal_connect_object (private->shell, "scaled",
+ G_CALLBACK (klass->changed),
+ widget,
+ G_CONNECT_SWAPPED);
+ }
+
+ if (klass->update_on_scroll)
+ {
+ g_signal_connect_object (private->shell, "scrolled",
+ G_CALLBACK (klass->changed),
+ widget,
+ G_CONNECT_SWAPPED);
+ }
+
+ if (klass->update_on_rotate)
+ {
+ g_signal_connect_object (private->shell, "rotated",
+ G_CALLBACK (klass->changed),
+ widget,
+ G_CONNECT_SWAPPED);
+ }
+ }
+}
+
+static void
+gimp_tool_widget_finalize (GObject *object)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolWidgetPrivate *private = widget->private;
+
+ g_clear_object (&private->item);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_widget_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolWidgetPrivate *private = widget->private;
+
+ switch (property_id)
+ {
+ case PROP_SHELL:
+ private->shell = g_value_get_object (value); /* don't ref */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_widget_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolWidgetPrivate *private = widget->private;
+
+ switch (property_id)
+ {
+ case PROP_SHELL:
+ g_value_set_object (value, private->shell);
+ break;
+
+ case PROP_ITEM:
+ g_value_set_object (value, private->item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_widget_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+
+ G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object,
+ n_pspecs,
+ pspecs);
+
+ gimp_tool_widget_changed (widget);
+}
+
+static void
+gimp_tool_widget_real_leave_notify (GimpToolWidget *widget)
+{
+ gimp_tool_widget_set_status (widget, NULL);
+}
+
+static gboolean
+gimp_tool_widget_real_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CONFIRM);
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CANCEL);
+ return TRUE;
+
+ case GDK_KEY_BackSpace:
+ gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_RESET);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+
+/* public functions */
+
+GimpDisplayShell *
+gimp_tool_widget_get_shell (GimpToolWidget *widget)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ return widget->private->shell;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_get_item (GimpToolWidget *widget)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ return widget->private->item;
+}
+
+void
+gimp_tool_widget_set_visible (GimpToolWidget *widget,
+ gboolean visible)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ if (visible != widget->private->visible)
+ {
+ widget->private->visible = visible;
+
+ if (widget->private->item)
+ gimp_canvas_item_set_visible (widget->private->item, visible);
+
+ if (! visible)
+ gimp_tool_widget_set_status (widget, NULL);
+ }
+}
+
+gboolean
+gimp_tool_widget_get_visible (GimpToolWidget *widget)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE);
+
+ return widget->private->visible;
+}
+
+void
+gimp_tool_widget_set_focus (GimpToolWidget *widget,
+ gboolean focus)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ if (focus != widget->private->focus)
+ {
+ widget->private->focus = focus;
+
+ g_signal_emit (widget, widget_signals[FOCUS_CHANGED], 0);
+ }
+}
+
+gboolean
+gimp_tool_widget_get_focus (GimpToolWidget *widget)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE);
+
+ return widget->private->focus;
+}
+
+void
+gimp_tool_widget_changed (GimpToolWidget *widget)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ g_signal_emit (widget, widget_signals[CHANGED], 0);
+}
+
+void
+gimp_tool_widget_response (GimpToolWidget *widget,
+ gint response_id)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ g_signal_emit (widget, widget_signals[RESPONSE], 0,
+ response_id);
+}
+
+void
+gimp_tool_widget_set_snap_offsets (GimpToolWidget *widget,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height)
+{
+ GimpToolWidgetPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ private = widget->private;
+
+ if (offset_x != private->snap_offset_x ||
+ offset_y != private->snap_offset_y ||
+ width != private->snap_width ||
+ height != private->snap_height)
+ {
+ private->snap_offset_x = offset_x;
+ private->snap_offset_y = offset_y;
+ private->snap_width = width;
+ private->snap_height = height;
+
+ g_signal_emit (widget, widget_signals[SNAP_OFFSETS], 0,
+ offset_x, offset_y, width, height);
+ }
+}
+
+void
+gimp_tool_widget_get_snap_offsets (GimpToolWidget *widget,
+ gint *offset_x,
+ gint *offset_y,
+ gint *width,
+ gint *height)
+{
+ GimpToolWidgetPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ private = widget->private;
+
+ if (offset_x) *offset_x = private->snap_offset_x;
+ if (offset_y) *offset_y = private->snap_offset_y;
+ if (width) *width = private->snap_width;
+ if (height) *height = private->snap_height;
+}
+
+void
+gimp_tool_widget_set_status (GimpToolWidget *widget,
+ const gchar *status)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ g_signal_emit (widget, widget_signals[STATUS], 0,
+ status);
+}
+
+void
+gimp_tool_widget_set_status_coords (GimpToolWidget *widget,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ g_signal_emit (widget, widget_signals[STATUS_COORDS], 0,
+ title, x, separator, y, help);
+}
+
+void
+gimp_tool_widget_message (GimpToolWidget *widget,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *message;
+
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+
+ message = g_strdup_vprintf (format, args);
+
+ va_end (args);
+
+ gimp_tool_widget_message_literal (widget, message);
+
+ g_free (message);
+}
+
+void
+gimp_tool_widget_message_literal (GimpToolWidget *widget,
+ const gchar *message)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (message != NULL);
+
+ g_signal_emit (widget, widget_signals[MESSAGE], 0,
+ message);
+}
+
+void
+gimp_tool_widget_add_item (GimpToolWidget *widget,
+ GimpCanvasItem *item)
+{
+ GimpCanvasGroup *group;
+
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ group = GIMP_CANVAS_GROUP (widget->private->item);
+
+ if (widget->private->group_stack)
+ group = widget->private->group_stack->data;
+
+ gimp_canvas_group_add_item (group, item);
+}
+
+void
+gimp_tool_widget_remove_item (GimpToolWidget *widget,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (widget->private->item),
+ item);
+}
+
+GimpCanvasGroup *
+gimp_tool_widget_add_group (GimpToolWidget *widget)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_group_new (widget->private->shell);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return GIMP_CANVAS_GROUP (item);
+}
+
+GimpCanvasGroup *
+gimp_tool_widget_add_stroke_group (GimpToolWidget *widget)
+{
+ GimpCanvasGroup *group;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ group = gimp_tool_widget_add_group (widget);
+ gimp_canvas_group_set_group_stroking (group, TRUE);
+
+ return group;
+}
+
+GimpCanvasGroup *
+gimp_tool_widget_add_fill_group (GimpToolWidget *widget)
+{
+ GimpCanvasGroup *group;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ group = gimp_tool_widget_add_group (widget);
+ gimp_canvas_group_set_group_filling (group, TRUE);
+
+ return group;
+}
+
+void
+gimp_tool_widget_push_group (GimpToolWidget *widget,
+ GimpCanvasGroup *group)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+
+ widget->private->group_stack = g_list_prepend (widget->private->group_stack,
+ group);
+}
+
+void
+gimp_tool_widget_pop_group (GimpToolWidget *widget)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (widget->private->group_stack != NULL);
+
+ widget->private->group_stack = g_list_remove (widget->private->group_stack,
+ widget->private->group_stack->data);
+}
+
+/**
+ * gimp_tool_widget_add_line:
+ * @widget: the #GimpToolWidget
+ * @x1: start point X in image coordinates
+ * @y1: start point Y in image coordinates
+ * @x2: end point X in image coordinates
+ * @y2: end point Y in image coordinates
+ *
+ * This function adds a #GimpCanvasLine to @widget.
+ **/
+GimpCanvasItem *
+gimp_tool_widget_add_line (GimpToolWidget *widget,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_line_new (widget->private->shell,
+ x1, y1, x2, y2);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_rectangle (GimpToolWidget *widget,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_rectangle_new (widget->private->shell,
+ x, y, width, height, filled);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_arc (GimpToolWidget *widget,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble start_angle,
+ gdouble slice_angle,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_arc_new (widget->private->shell,
+ center_x, center_y,
+ radius_x, radius_y,
+ start_angle, slice_angle,
+ filled);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_limit (GimpToolWidget *widget,
+ GimpLimitType type,
+ gdouble x,
+ gdouble y,
+ gdouble radius,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean dashed)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_limit_new (widget->private->shell,
+ type,
+ x, y,
+ radius,
+ aspect_ratio,
+ angle,
+ dashed);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_polygon (GimpToolWidget *widget,
+ GimpMatrix3 *transform,
+ const GimpVector2 *points,
+ gint n_points,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+ g_return_val_if_fail (points == NULL || n_points > 0, NULL);
+
+ item = gimp_canvas_polygon_new (widget->private->shell,
+ points, n_points,
+ transform, filled);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_polygon_from_coords (GimpToolWidget *widget,
+ GimpMatrix3 *transform,
+ const GimpCoords *points,
+ gint n_points,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+ g_return_val_if_fail (points == NULL || n_points > 0, NULL);
+
+ item = gimp_canvas_polygon_new_from_coords (widget->private->shell,
+ points, n_points,
+ transform, filled);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_path (GimpToolWidget *widget,
+ const GimpBezierDesc *desc)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_path_new (widget->private->shell,
+ desc, 0, 0, FALSE, GIMP_PATH_STYLE_DEFAULT);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_handle (GimpToolWidget *widget,
+ GimpHandleType type,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_handle_new (widget->private->shell,
+ type, anchor, x, y, width, height);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_corner (GimpToolWidget *widget,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpHandleAnchor anchor,
+ gint corner_width,
+ gint corner_height,
+ gboolean outside)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_corner_new (widget->private->shell,
+ x, y, width, height,
+ anchor, corner_width, corner_height,
+ outside);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_rectangle_guides (GimpToolWidget *widget,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpGuidesType type)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_rectangle_guides_new (widget->private->shell,
+ x, y, width, height, type, 4);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_tool_widget_add_transform_guides (GimpToolWidget *widget,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpGuidesType type,
+ gint n_guides,
+ gboolean clip)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL);
+
+ item = gimp_canvas_transform_guides_new (widget->private->shell,
+ transform, x1, y1, x2, y2,
+ type, n_guides, clip);
+
+ gimp_tool_widget_add_item (widget, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+gint
+gimp_tool_widget_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), 0);
+ g_return_val_if_fail (coords != NULL, 0);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_press)
+ {
+ return GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_press (widget,
+ coords, time,
+ state,
+ press_type);
+ }
+
+ return 0;
+}
+
+void
+gimp_tool_widget_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (coords != NULL);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_release)
+ {
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_release (widget,
+ coords, time, state,
+ release_type);
+ }
+}
+
+void
+gimp_tool_widget_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (coords != NULL);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion)
+ {
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion (widget,
+ coords, time, state);
+ }
+}
+
+GimpHit
+gimp_tool_widget_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), GIMP_HIT_NONE);
+ g_return_val_if_fail (coords != NULL, GIMP_HIT_NONE);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->hit)
+ {
+ return GIMP_TOOL_WIDGET_GET_CLASS (widget)->hit (widget,
+ coords, state,
+ proximity);
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+void
+gimp_tool_widget_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (coords != NULL);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover)
+ {
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover (widget,
+ coords, state, proximity);
+ }
+}
+
+void
+gimp_tool_widget_leave_notify (GimpToolWidget *widget)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->leave_notify)
+ {
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->leave_notify (widget);
+ }
+}
+
+gboolean
+gimp_tool_widget_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE);
+ g_return_val_if_fail (kevent != NULL, FALSE);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_press)
+ {
+ return GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_press (widget, kevent);
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_tool_widget_key_release (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE);
+ g_return_val_if_fail (kevent != NULL, FALSE);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_release)
+ {
+ return GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_release (widget, kevent);
+ }
+
+ return FALSE;
+}
+
+void
+gimp_tool_widget_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion_modifier)
+ {
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion_modifier (widget,
+ key, press, state);
+ }
+}
+
+void
+gimp_tool_widget_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover_modifier)
+ {
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover_modifier (widget,
+ key, press, state);
+ }
+}
+
+gboolean
+gimp_tool_widget_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+
+ if (widget->private->visible &&
+ GIMP_TOOL_WIDGET_GET_CLASS (widget)->get_cursor)
+ {
+ GimpCursorType my_cursor;
+ GimpToolCursorType my_tool_cursor;
+ GimpCursorModifier my_modifier;
+
+ if (cursor) my_cursor = *cursor;
+ if (tool_cursor) my_tool_cursor = *tool_cursor;
+ if (modifier) my_modifier = *modifier;
+
+ if (GIMP_TOOL_WIDGET_GET_CLASS (widget)->get_cursor (widget, coords,
+ state,
+ &my_cursor,
+ &my_tool_cursor,
+ &my_modifier))
+ {
+ if (cursor) *cursor = my_cursor;
+ if (tool_cursor) *tool_cursor = my_tool_cursor;
+ if (modifier) *modifier = my_modifier;
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/app/display/gimptoolwidget.h b/app/display/gimptoolwidget.h
new file mode 100644
index 0000000..742aa2a
--- /dev/null
+++ b/app/display/gimptoolwidget.h
@@ -0,0 +1,318 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolwidget.h
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_WIDGET_H__
+#define __GIMP_TOOL_WIDGET_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TOOL_WIDGET_RESPONSE_CONFIRM -1
+#define GIMP_TOOL_WIDGET_RESPONSE_CANCEL -2
+#define GIMP_TOOL_WIDGET_RESPONSE_RESET -3
+
+
+#define GIMP_TYPE_TOOL_WIDGET (gimp_tool_widget_get_type ())
+#define GIMP_TOOL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_WIDGET, GimpToolWidget))
+#define GIMP_TOOL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_WIDGET, GimpToolWidgetClass))
+#define GIMP_IS_TOOL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_WIDGET))
+#define GIMP_IS_TOOL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_WIDGET))
+#define GIMP_TOOL_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_WIDGET, GimpToolWidgetClass))
+
+
+typedef struct _GimpToolWidgetPrivate GimpToolWidgetPrivate;
+typedef struct _GimpToolWidgetClass GimpToolWidgetClass;
+
+struct _GimpToolWidget
+{
+ GimpObject parent_instance;
+
+ GimpToolWidgetPrivate *private;
+};
+
+struct _GimpToolWidgetClass
+{
+ GimpObjectClass parent_class;
+
+ /* signals */
+ void (* changed) (GimpToolWidget *widget);
+ void (* response) (GimpToolWidget *widget,
+ gint response_id);
+ void (* snap_offsets) (GimpToolWidget *widget,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height);
+ void (* status) (GimpToolWidget *widget,
+ const gchar *status);
+ void (* status_coords) (GimpToolWidget *widget,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help);
+ void (* message) (GimpToolWidget *widget,
+ const gchar *message);
+ void (* focus_changed) (GimpToolWidget *widget);
+
+ /* virtual functions */
+ gint (* button_press) (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+ void (* button_release) (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+ void (* motion) (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+
+ GimpHit (* hit) (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+ void (* hover) (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+ void (* leave_notify) (GimpToolWidget *widget);
+
+ gboolean (* key_press) (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+ gboolean (* key_release) (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+
+ void (* motion_modifier) (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+ void (* hover_modifier) (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+
+ gboolean (* get_cursor) (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+ gboolean update_on_scale;
+ gboolean update_on_scroll;
+ gboolean update_on_rotate;
+};
+
+
+GType gimp_tool_widget_get_type (void) G_GNUC_CONST;
+
+GimpDisplayShell * gimp_tool_widget_get_shell (GimpToolWidget *widget);
+GimpCanvasItem * gimp_tool_widget_get_item (GimpToolWidget *widget);
+
+void gimp_tool_widget_set_visible (GimpToolWidget *widget,
+ gboolean visible);
+gboolean gimp_tool_widget_get_visible (GimpToolWidget *widget);
+
+void gimp_tool_widget_set_focus (GimpToolWidget *widget,
+ gboolean focus);
+gboolean gimp_tool_widget_get_focus (GimpToolWidget *widget);
+
+/* for subclasses, to notify the handling tool
+ */
+void gimp_tool_widget_changed (GimpToolWidget *widget);
+
+void gimp_tool_widget_response (GimpToolWidget *widget,
+ gint response_id);
+
+void gimp_tool_widget_set_snap_offsets (GimpToolWidget *widget,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height);
+void gimp_tool_widget_get_snap_offsets (GimpToolWidget *widget,
+ gint *offset_x,
+ gint *offset_y,
+ gint *width,
+ gint *height);
+
+void gimp_tool_widget_set_status (GimpToolWidget *widget,
+ const gchar *status);
+void gimp_tool_widget_set_status_coords (GimpToolWidget *widget,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help);
+
+void gimp_tool_widget_message (GimpToolWidget *widget,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+void gimp_tool_widget_message_literal (GimpToolWidget *widget,
+ const gchar *message);
+
+/* for subclasses, to add and manage their items
+ */
+void gimp_tool_widget_add_item (GimpToolWidget *widget,
+ GimpCanvasItem *item);
+void gimp_tool_widget_remove_item (GimpToolWidget *widget,
+ GimpCanvasItem *item);
+
+GimpCanvasGroup * gimp_tool_widget_add_group (GimpToolWidget *widget);
+GimpCanvasGroup * gimp_tool_widget_add_stroke_group (GimpToolWidget *widget);
+GimpCanvasGroup * gimp_tool_widget_add_fill_group (GimpToolWidget *widget);
+
+void gimp_tool_widget_push_group (GimpToolWidget *widget,
+ GimpCanvasGroup *group);
+void gimp_tool_widget_pop_group (GimpToolWidget *widget);
+
+/* for subclasses, convenience functions to add specific items
+ */
+GimpCanvasItem * gimp_tool_widget_add_line (GimpToolWidget *widget,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+GimpCanvasItem * gimp_tool_widget_add_rectangle (GimpToolWidget *widget,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gboolean filled);
+GimpCanvasItem * gimp_tool_widget_add_arc (GimpToolWidget *widget,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble start_angle,
+ gdouble slice_angle,
+ gboolean filled);
+GimpCanvasItem * gimp_tool_widget_add_limit (GimpToolWidget *widget,
+ GimpLimitType type,
+ gdouble x,
+ gdouble y,
+ gdouble radius,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean dashed);
+GimpCanvasItem * gimp_tool_widget_add_polygon (GimpToolWidget *widget,
+ GimpMatrix3 *transform,
+ const GimpVector2 *points,
+ gint n_points,
+ gboolean filled);
+GimpCanvasItem * gimp_tool_widget_add_polygon_from_coords
+ (GimpToolWidget *widget,
+ GimpMatrix3 *transform,
+ const GimpCoords *points,
+ gint n_points,
+ gboolean filled);
+GimpCanvasItem * gimp_tool_widget_add_path (GimpToolWidget *widget,
+ const GimpBezierDesc *desc);
+
+GimpCanvasItem * gimp_tool_widget_add_handle (GimpToolWidget *widget,
+ GimpHandleType type,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor);
+GimpCanvasItem * gimp_tool_widget_add_corner (GimpToolWidget *widget,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpHandleAnchor anchor,
+ gint corner_width,
+ gint corner_height,
+ gboolean outside);
+
+GimpCanvasItem * gimp_tool_widget_add_rectangle_guides
+ (GimpToolWidget *widget,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ GimpGuidesType type);
+GimpCanvasItem * gimp_tool_widget_add_transform_guides
+ (GimpToolWidget *widget,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ GimpGuidesType type,
+ gint n_guides,
+ gboolean clip);
+
+/* for tools, to be called from the respective GimpTool method
+ * implementations
+ */
+gint gimp_tool_widget_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+void gimp_tool_widget_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+void gimp_tool_widget_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+
+GimpHit gimp_tool_widget_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+void gimp_tool_widget_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+void gimp_tool_widget_leave_notify (GimpToolWidget *widget);
+
+gboolean gimp_tool_widget_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+gboolean gimp_tool_widget_key_release (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+
+void gimp_tool_widget_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+void gimp_tool_widget_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+
+gboolean gimp_tool_widget_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+
+#endif /* __GIMP_TOOL_WIDGET_H__ */
diff --git a/app/display/gimptoolwidgetgroup.c b/app/display/gimptoolwidgetgroup.c
new file mode 100644
index 0000000..d62f4c9
--- /dev/null
+++ b/app/display/gimptoolwidgetgroup.c
@@ -0,0 +1,731 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolwidgetgroup.c
+ * Copyright (C) 2018 Ell
+ *
+ * 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 "display-types.h"
+
+#include "core/gimpcontainer.h"
+#include "core/gimplist.h"
+
+#include "gimpcanvasgroup.h"
+#include "gimpdisplayshell.h"
+#include "gimptoolwidgetgroup.h"
+
+
+struct _GimpToolWidgetGroupPrivate
+{
+ GimpContainer *children;
+
+ GimpToolWidget *focus_widget;
+ GimpToolWidget *hover_widget;
+
+ gboolean auto_raise;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_widget_group_finalize (GObject *object);
+
+static void gimp_tool_widget_group_focus_changed (GimpToolWidget *widget);
+static gint gimp_tool_widget_group_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_widget_group_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_widget_group_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_widget_group_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_widget_group_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_widget_group_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_widget_group_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static gboolean gimp_tool_widget_group_key_release (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_widget_group_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static void gimp_tool_widget_group_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_widget_group_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_widget_group_children_add (GimpContainer *container,
+ GimpToolWidget *child,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_children_remove (GimpContainer *container,
+ GimpToolWidget *child,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_children_reorder (GimpContainer *container,
+ GimpToolWidget *child,
+ gint new_index,
+ GimpToolWidgetGroup *group);
+
+static void gimp_tool_widget_group_child_changed (GimpToolWidget *child,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_child_response (GimpToolWidget *child,
+ gint response_id,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_child_snap_offsets (GimpToolWidget *child,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_child_status (GimpToolWidget *child,
+ const gchar *status,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_child_status_coords (GimpToolWidget *child,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_child_message (GimpToolWidget *child,
+ const gchar *message,
+ GimpToolWidgetGroup *group);
+static void gimp_tool_widget_group_child_focus_changed (GimpToolWidget *child,
+ GimpToolWidgetGroup *group);
+
+static GimpToolWidget * gimp_tool_widget_group_get_hover_widget (GimpToolWidgetGroup *group,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpHit *hit);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolWidgetGroup, gimp_tool_widget_group,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_widget_group_parent_class
+
+
+/* priv functions */
+
+
+static void
+gimp_tool_widget_group_class_init (GimpToolWidgetGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->finalize = gimp_tool_widget_group_finalize;
+
+ widget_class->focus_changed = gimp_tool_widget_group_focus_changed;
+ widget_class->button_press = gimp_tool_widget_group_button_press;
+ widget_class->button_release = gimp_tool_widget_group_button_release;
+ widget_class->motion = gimp_tool_widget_group_motion;
+ widget_class->hit = gimp_tool_widget_group_hit;
+ widget_class->hover = gimp_tool_widget_group_hover;
+ widget_class->leave_notify = gimp_tool_widget_group_leave_notify;
+ widget_class->key_press = gimp_tool_widget_group_key_press;
+ widget_class->key_release = gimp_tool_widget_group_key_release;
+ widget_class->motion_modifier = gimp_tool_widget_group_motion_modifier;
+ widget_class->hover_modifier = gimp_tool_widget_group_hover_modifier;
+ widget_class->get_cursor = gimp_tool_widget_group_get_cursor;
+}
+
+static void
+gimp_tool_widget_group_init (GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv;
+
+ priv = group->priv = gimp_tool_widget_group_get_instance_private (group);
+
+ priv->children = g_object_new (GIMP_TYPE_LIST,
+ "children-type", GIMP_TYPE_TOOL_WIDGET,
+ "append", TRUE,
+ NULL);
+
+ g_signal_connect (priv->children, "add",
+ G_CALLBACK (gimp_tool_widget_group_children_add),
+ group);
+ g_signal_connect (priv->children, "remove",
+ G_CALLBACK (gimp_tool_widget_group_children_remove),
+ group);
+ g_signal_connect (priv->children, "reorder",
+ G_CALLBACK (gimp_tool_widget_group_children_reorder),
+ group);
+
+ gimp_container_add_handler (priv->children, "changed",
+ G_CALLBACK (gimp_tool_widget_group_child_changed),
+ group);
+ gimp_container_add_handler (priv->children, "response",
+ G_CALLBACK (gimp_tool_widget_group_child_response),
+ group);
+ gimp_container_add_handler (priv->children, "snap-offsets",
+ G_CALLBACK (gimp_tool_widget_group_child_snap_offsets),
+ group);
+ gimp_container_add_handler (priv->children, "status",
+ G_CALLBACK (gimp_tool_widget_group_child_status),
+ group);
+ gimp_container_add_handler (priv->children, "status-coords",
+ G_CALLBACK (gimp_tool_widget_group_child_status_coords),
+ group);
+ gimp_container_add_handler (priv->children, "message",
+ G_CALLBACK (gimp_tool_widget_group_child_message),
+ group);
+ gimp_container_add_handler (priv->children, "focus-changed",
+ G_CALLBACK (gimp_tool_widget_group_child_focus_changed),
+ group);
+}
+
+static void
+gimp_tool_widget_group_finalize (GObject *object)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (object);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ g_clear_object (&priv->children);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint
+gimp_tool_widget_group_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ gimp_tool_widget_group_hover (widget, coords, state, TRUE);
+
+ if (priv->focus_widget != priv->hover_widget)
+ {
+ if (priv->hover_widget)
+ gimp_tool_widget_set_focus (priv->hover_widget, TRUE);
+ else if (priv->focus_widget)
+ gimp_tool_widget_set_focus (priv->focus_widget, FALSE);
+ }
+
+ if (priv->hover_widget)
+ {
+ if (priv->auto_raise)
+ {
+ gimp_container_reorder (priv->children,
+ GIMP_OBJECT (priv->hover_widget), -1);
+ }
+
+ return gimp_tool_widget_button_press (priv->hover_widget,
+ coords, time, state, press_type);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_tool_widget_group_focus_changed (GimpToolWidget *widget)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->focus_widget)
+ {
+ GimpToolWidget *focus_widget = priv->focus_widget;
+
+ gimp_tool_widget_set_focus (priv->focus_widget,
+ gimp_tool_widget_get_focus (widget));
+
+ priv->focus_widget = focus_widget;
+ }
+}
+
+static void
+gimp_tool_widget_group_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->hover_widget)
+ {
+ gimp_tool_widget_button_release (priv->hover_widget,
+ coords, time, state, release_type);
+ }
+}
+
+static void
+gimp_tool_widget_group_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->hover_widget)
+ gimp_tool_widget_motion (priv->hover_widget, coords, time, state);
+}
+
+static GimpHit
+gimp_tool_widget_group_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpHit hit;
+
+ gimp_tool_widget_group_get_hover_widget (group,
+ coords, state, proximity,
+ &hit);
+
+ return hit;
+}
+
+static void
+gimp_tool_widget_group_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *hover_widget;
+
+ hover_widget =
+ gimp_tool_widget_group_get_hover_widget (group,
+ coords, state, proximity,
+ NULL);
+
+ if (priv->hover_widget && priv->hover_widget != hover_widget)
+ gimp_tool_widget_leave_notify (priv->hover_widget);
+
+ priv->hover_widget = hover_widget;
+
+ if (priv->hover_widget)
+ gimp_tool_widget_hover (priv->hover_widget, coords, state, proximity);
+}
+
+static void
+gimp_tool_widget_group_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->hover_widget)
+ {
+ gimp_tool_widget_leave_notify (priv->hover_widget);
+
+ priv->hover_widget = NULL;
+ }
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static gboolean
+gimp_tool_widget_group_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->focus_widget)
+ return gimp_tool_widget_key_press (priv->focus_widget, kevent);
+
+ return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
+}
+
+static gboolean
+gimp_tool_widget_group_key_release (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->focus_widget)
+ return gimp_tool_widget_key_release (priv->focus_widget, kevent);
+
+ return FALSE;
+}
+
+static void
+gimp_tool_widget_group_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->hover_widget)
+ gimp_tool_widget_motion_modifier (priv->hover_widget, key, press, state);
+}
+
+static void
+gimp_tool_widget_group_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GList *iter;
+
+ for (iter = g_queue_peek_head_link (GIMP_LIST (priv->children)->queue);
+ iter;
+ iter = g_list_next (iter))
+ {
+ gimp_tool_widget_hover_modifier (iter->data, key, press, state);
+ }
+}
+
+static gboolean
+gimp_tool_widget_group_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget);
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+
+ if (priv->hover_widget)
+ {
+ return gimp_tool_widget_get_cursor (priv->hover_widget,
+ coords, state,
+ cursor, tool_cursor, modifier);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_tool_widget_group_children_add (GimpContainer *container,
+ GimpToolWidget *child,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+ GimpCanvasGroup *canvas_group;
+
+ canvas_group = GIMP_CANVAS_GROUP (gimp_tool_widget_get_item (widget));
+
+ gimp_canvas_group_add_item (canvas_group,
+ gimp_tool_widget_get_item (child));
+
+ if (gimp_tool_widget_get_focus (child) && priv->focus_widget)
+ {
+ gimp_tool_widget_set_focus (priv->focus_widget, FALSE);
+
+ priv->focus_widget = NULL;
+ }
+
+ if (! priv->focus_widget)
+ {
+ priv->focus_widget = child;
+
+ gimp_tool_widget_set_focus (child, gimp_tool_widget_get_focus (widget));
+ }
+
+ gimp_tool_widget_changed (widget);
+}
+
+static void
+gimp_tool_widget_group_children_remove (GimpContainer *container,
+ GimpToolWidget *child,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+ GimpCanvasGroup *canvas_group;
+
+ canvas_group = GIMP_CANVAS_GROUP (gimp_tool_widget_get_item (widget));
+
+ if (priv->focus_widget == child)
+ {
+ gimp_tool_widget_set_focus (child, FALSE);
+
+ priv->focus_widget = NULL;
+ }
+
+ if (priv->hover_widget == child)
+ {
+ gimp_tool_widget_leave_notify (child);
+
+ priv->hover_widget = NULL;
+ }
+
+ if (! priv->focus_widget)
+ {
+ priv->focus_widget =
+ GIMP_TOOL_WIDGET (gimp_container_get_last_child (container));
+
+ if (priv->focus_widget)
+ gimp_tool_widget_set_focus (priv->focus_widget, TRUE);
+ }
+
+ gimp_canvas_group_remove_item (canvas_group,
+ gimp_tool_widget_get_item (child));
+
+ gimp_tool_widget_changed (widget);
+}
+
+static void
+gimp_tool_widget_group_children_reorder (GimpContainer *container,
+ GimpToolWidget *child,
+ gint new_index,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+ GimpCanvasGroup *canvas_group;
+ GList *iter;
+
+ canvas_group = GIMP_CANVAS_GROUP (gimp_tool_widget_get_item (widget));
+
+ for (iter = g_queue_peek_head_link (GIMP_LIST (container)->queue);
+ iter;
+ iter = g_list_next (iter))
+ {
+ GimpCanvasItem *item = gimp_tool_widget_get_item (iter->data);
+
+ gimp_canvas_group_remove_item (canvas_group, item);
+ gimp_canvas_group_add_item (canvas_group, item);
+ }
+
+ gimp_tool_widget_changed (widget);
+}
+
+static void
+gimp_tool_widget_group_child_changed (GimpToolWidget *child,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ gimp_tool_widget_changed (widget);
+}
+
+static void
+gimp_tool_widget_group_child_response (GimpToolWidget *child,
+ gint response_id,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ if (priv->focus_widget == child)
+ gimp_tool_widget_response (widget, response_id);
+}
+
+static void
+gimp_tool_widget_group_child_snap_offsets (GimpToolWidget *child,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ if (priv->hover_widget == child)
+ {
+ gimp_tool_widget_set_snap_offsets (widget,
+ offset_x, offset_y, width, height);
+ }
+}
+
+static void
+gimp_tool_widget_group_child_status (GimpToolWidget *child,
+ const gchar *status,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ if (priv->hover_widget == child)
+ gimp_tool_widget_set_status (widget, status);
+}
+
+static void
+gimp_tool_widget_group_child_status_coords (GimpToolWidget *child,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ if (priv->hover_widget == child)
+ gimp_tool_widget_set_status_coords (widget, title, x, separator, y, help);
+}
+
+static void
+gimp_tool_widget_group_child_message (GimpToolWidget *child,
+ const gchar *message,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ if (priv->focus_widget == child)
+ gimp_tool_widget_message_literal (widget, message);
+}
+
+static void
+gimp_tool_widget_group_child_focus_changed (GimpToolWidget *child,
+ GimpToolWidgetGroup *group)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (group);
+
+ if (gimp_tool_widget_get_focus (child))
+ {
+ if (priv->focus_widget && priv->focus_widget != child)
+ gimp_tool_widget_set_focus (priv->focus_widget, FALSE);
+
+ priv->focus_widget = child;
+
+ gimp_tool_widget_set_focus (widget, TRUE);
+ }
+ else
+ {
+ if (priv->focus_widget == child)
+ priv->focus_widget = NULL;
+ }
+}
+
+static GimpToolWidget *
+gimp_tool_widget_group_get_hover_widget (GimpToolWidgetGroup *group,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpHit *hit)
+{
+ GimpToolWidgetGroupPrivate *priv = group->priv;
+ GimpToolWidget *indirect_child = NULL;
+ gboolean indirect = FALSE;
+ GList *iter;
+
+ for (iter = g_queue_peek_tail_link (GIMP_LIST (priv->children)->queue);
+ iter;
+ iter = g_list_previous (iter))
+ {
+ GimpToolWidget *child = iter->data;
+
+ switch (gimp_tool_widget_hit (child, coords, state, proximity))
+ {
+ case GIMP_HIT_DIRECT:
+ if (hit) *hit = GIMP_HIT_DIRECT;
+
+ return child;
+
+ case GIMP_HIT_INDIRECT:
+ if (! indirect || child == priv->focus_widget)
+ indirect_child = child;
+ else if (indirect_child != priv->focus_widget)
+ indirect_child = NULL;
+
+ indirect = TRUE;
+
+ break;
+
+ case GIMP_HIT_NONE:
+ break;
+ }
+ }
+
+ if (hit) *hit = indirect_child ? GIMP_HIT_INDIRECT : GIMP_HIT_NONE;
+
+ return indirect_child;
+}
+
+
+/* public functions */
+
+
+GimpToolWidget *
+gimp_tool_widget_group_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_WIDGET_GROUP,
+ "shell", shell,
+ NULL);
+}
+
+GimpContainer *
+gimp_tool_widget_group_get_children (GimpToolWidgetGroup *group)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group), NULL);
+
+ return group->priv->children;
+}
+
+GimpToolWidget *
+gimp_tool_widget_group_get_focus_widget (GimpToolWidgetGroup *group)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group), NULL);
+
+ return group->priv->focus_widget;
+}
+
+void
+gimp_tool_widget_group_set_auto_raise (GimpToolWidgetGroup *group,
+ gboolean auto_raise)
+{
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group));
+
+ group->priv->auto_raise = auto_raise;
+}
+
+gboolean
+gimp_tool_widget_group_get_auto_raise (GimpToolWidgetGroup *group)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group), FALSE);
+
+ return group->priv->auto_raise;
+}
+
diff --git a/app/display/gimptoolwidgetgroup.h b/app/display/gimptoolwidgetgroup.h
new file mode 100644
index 0000000..0a8331f
--- /dev/null
+++ b/app/display/gimptoolwidgetgroup.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolwidgetgroup.h
+ * Copyright (C) 2018 Ell
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_WIDGET_GROUP_H__
+#define __GIMP_TOOL_WIDGET_GROUP_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_WIDGET_GROUP (gimp_tool_widget_group_get_type ())
+#define GIMP_TOOL_WIDGET_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_WIDGET_GROUP, GimpToolWidgetGroup))
+#define GIMP_TOOL_WIDGET_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_WIDGET_GROUP, GimpToolWidgetGroupClass))
+#define GIMP_IS_TOOL_WIDGET_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_WIDGET_GROUP))
+#define GIMP_IS_TOOL_WIDGET_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_WIDGET_GROUP))
+#define GIMP_TOOL_WIDGET_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_WIDGET_GROUP, GimpToolWidgetGroupClass))
+
+
+typedef struct _GimpToolWidgetGroupPrivate GimpToolWidgetGroupPrivate;
+typedef struct _GimpToolWidgetGroupClass GimpToolWidgetGroupClass;
+
+struct _GimpToolWidgetGroup
+{
+ GimpToolWidget parent_instance;
+
+ GimpToolWidgetGroupPrivate *priv;
+};
+
+struct _GimpToolWidgetGroupClass
+{
+ GimpToolWidgetClass parent_class;
+};
+
+
+GType gimp_tool_widget_group_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_widget_group_new (GimpDisplayShell *shell);
+
+GimpContainer * gimp_tool_widget_group_get_children (GimpToolWidgetGroup *group);
+
+GimpToolWidget * gimp_tool_widget_group_get_focus_widget (GimpToolWidgetGroup *group);
+
+void gimp_tool_widget_group_set_auto_raise (GimpToolWidgetGroup *group,
+ gboolean auto_raise);
+gboolean gimp_tool_widget_group_get_auto_raise (GimpToolWidgetGroup *group);
+
+
+#endif /* __GIMP_TOOL_WIDGET_GROUP_H__ */