summaryrefslogtreecommitdiffstats
path: root/app/tools
diff options
context:
space:
mode:
Diffstat (limited to 'app/tools')
-rw-r--r--app/tools/Makefile.am275
-rw-r--r--app/tools/Makefile.in1656
-rw-r--r--app/tools/gimp-tool-options-manager.c462
-rw-r--r--app/tools/gimp-tool-options-manager.h29
-rw-r--r--app/tools/gimp-tools.c831
-rw-r--r--app/tools/gimp-tools.h45
-rw-r--r--app/tools/gimpairbrushtool.c150
-rw-r--r--app/tools/gimpairbrushtool.h53
-rw-r--r--app/tools/gimpalignoptions.c403
-rw-r--r--app/tools/gimpalignoptions.h64
-rw-r--r--app/tools/gimpaligntool.c824
-rw-r--r--app/tools/gimpaligntool.h76
-rw-r--r--app/tools/gimpbrightnesscontrasttool.c314
-rw-r--r--app/tools/gimpbrightnesscontrasttool.h61
-rw-r--r--app/tools/gimpbrushtool.c451
-rw-r--r--app/tools/gimpbrushtool.h63
-rw-r--r--app/tools/gimpbucketfilloptions.c544
-rw-r--r--app/tools/gimpbucketfilloptions.h68
-rw-r--r--app/tools/gimpbucketfilltool.c1052
-rw-r--r--app/tools/gimpbucketfilltool.h58
-rw-r--r--app/tools/gimpbycolorselecttool.c143
-rw-r--r--app/tools/gimpbycolorselecttool.h55
-rw-r--r--app/tools/gimpcageoptions.c152
-rw-r--r--app/tools/gimpcageoptions.h57
-rw-r--r--app/tools/gimpcagetool.c1301
-rw-r--r--app/tools/gimpcagetool.h85
-rw-r--r--app/tools/gimpcloneoptions-gui.c108
-rw-r--r--app/tools/gimpcloneoptions-gui.h25
-rw-r--r--app/tools/gimpclonetool.c108
-rw-r--r--app/tools/gimpclonetool.h53
-rw-r--r--app/tools/gimpcoloroptions.c170
-rw-r--r--app/tools/gimpcoloroptions.h55
-rw-r--r--app/tools/gimpcolorpickeroptions.c208
-rw-r--r--app/tools/gimpcolorpickeroptions.h52
-rw-r--r--app/tools/gimpcolorpickertool.c455
-rw-r--r--app/tools/gimpcolorpickertool.h60
-rw-r--r--app/tools/gimpcolortool.c702
-rw-r--r--app/tools/gimpcolortool.h87
-rw-r--r--app/tools/gimpconvolvetool.c230
-rw-r--r--app/tools/gimpconvolvetool.h57
-rw-r--r--app/tools/gimpcropoptions.c240
-rw-r--r--app/tools/gimpcropoptions.h61
-rw-r--r--app/tools/gimpcroptool.c721
-rw-r--r--app/tools/gimpcroptool.h62
-rw-r--r--app/tools/gimpcurvestool.c1156
-rw-r--r--app/tools/gimpcurvestool.h69
-rw-r--r--app/tools/gimpdodgeburntool.c243
-rw-r--r--app/tools/gimpdodgeburntool.h56
-rw-r--r--app/tools/gimpdrawtool.c1281
-rw-r--r--app/tools/gimpdrawtool.h206
-rw-r--r--app/tools/gimpeditselectiontool.c1287
-rw-r--r--app/tools/gimpeditselectiontool.h50
-rw-r--r--app/tools/gimpellipseselecttool.c112
-rw-r--r--app/tools/gimpellipseselecttool.h53
-rw-r--r--app/tools/gimperasertool.c176
-rw-r--r--app/tools/gimperasertool.h55
-rw-r--r--app/tools/gimpfilteroptions.c269
-rw-r--r--app/tools/gimpfilteroptions.h63
-rw-r--r--app/tools/gimpfiltertool-settings.c252
-rw-r--r--app/tools/gimpfiltertool-settings.h37
-rw-r--r--app/tools/gimpfiltertool-widgets.c964
-rw-r--r--app/tools/gimpfiltertool-widgets.h36
-rw-r--r--app/tools/gimpfiltertool.c2027
-rw-r--r--app/tools/gimpfiltertool.h149
-rw-r--r--app/tools/gimpflipoptions.c164
-rw-r--r--app/tools/gimpflipoptions.h52
-rw-r--r--app/tools/gimpfliptool.c460
-rw-r--r--app/tools/gimpfliptool.h57
-rw-r--r--app/tools/gimpforegroundselectoptions.c395
-rw-r--r--app/tools/gimpforegroundselectoptions.h64
-rw-r--r--app/tools/gimpforegroundselecttool.c1396
-rw-r--r--app/tools/gimpforegroundselecttool.h78
-rw-r--r--app/tools/gimpforegroundselecttoolundo.c168
-rw-r--r--app/tools/gimpforegroundselecttoolundo.h56
-rw-r--r--app/tools/gimpfreeselecttool.c355
-rw-r--r--app/tools/gimpfreeselecttool.h56
-rw-r--r--app/tools/gimpfuzzyselecttool.c132
-rw-r--r--app/tools/gimpfuzzyselecttool.h55
-rw-r--r--app/tools/gimpgegltool.c559
-rw-r--r--app/tools/gimpgegltool.h57
-rw-r--r--app/tools/gimpgenerictransformtool.c194
-rw-r--r--app/tools/gimpgenerictransformtool.h59
-rw-r--r--app/tools/gimpgradientoptions.c414
-rw-r--r--app/tools/gimpgradientoptions.h65
-rw-r--r--app/tools/gimpgradienttool-editor.c2520
-rw-r--r--app/tools/gimpgradienttool-editor.h48
-rw-r--r--app/tools/gimpgradienttool.c1106
-rw-r--r--app/tools/gimpgradienttool.h111
-rw-r--r--app/tools/gimpguidetool.c546
-rw-r--r--app/tools/gimpguidetool.h74
-rw-r--r--app/tools/gimphandletransformoptions.c202
-rw-r--r--app/tools/gimphandletransformoptions.h54
-rw-r--r--app/tools/gimphandletransformtool.c384
-rw-r--r--app/tools/gimphandletransformtool.h57
-rw-r--r--app/tools/gimphealtool.c111
-rw-r--r--app/tools/gimphealtool.h53
-rw-r--r--app/tools/gimphistogramoptions.c113
-rw-r--r--app/tools/gimphistogramoptions.h52
-rw-r--r--app/tools/gimpinkoptions-gui.c143
-rw-r--r--app/tools/gimpinkoptions-gui.h25
-rw-r--r--app/tools/gimpinktool.c122
-rw-r--r--app/tools/gimpinktool.h55
-rw-r--r--app/tools/gimpiscissorsoptions.c133
-rw-r--r--app/tools/gimpiscissorsoptions.h49
-rw-r--r--app/tools/gimpiscissorstool.c2188
-rw-r--r--app/tools/gimpiscissorstool.h97
-rw-r--r--app/tools/gimplevelstool.c1055
-rw-r--r--app/tools/gimplevelstool.h76
-rw-r--r--app/tools/gimpmagnifyoptions.c202
-rw-r--r--app/tools/gimpmagnifyoptions.h50
-rw-r--r--app/tools/gimpmagnifytool.c293
-rw-r--r--app/tools/gimpmagnifytool.h60
-rw-r--r--app/tools/gimpmeasureoptions.c183
-rw-r--r--app/tools/gimpmeasureoptions.h58
-rw-r--r--app/tools/gimpmeasuretool.c890
-rw-r--r--app/tools/gimpmeasuretool.h71
-rw-r--r--app/tools/gimpmoveoptions.c227
-rw-r--r--app/tools/gimpmoveoptions.h53
-rw-r--r--app/tools/gimpmovetool.c665
-rw-r--r--app/tools/gimpmovetool.h63
-rw-r--r--app/tools/gimpmybrushoptions-gui.c88
-rw-r--r--app/tools/gimpmybrushoptions-gui.h25
-rw-r--r--app/tools/gimpmybrushtool.c169
-rw-r--r--app/tools/gimpmybrushtool.h60
-rw-r--r--app/tools/gimpnpointdeformationoptions.c264
-rw-r--r--app/tools/gimpnpointdeformationoptions.h67
-rw-r--r--app/tools/gimpnpointdeformationtool.c1020
-rw-r--r--app/tools/gimpnpointdeformationtool.h95
-rw-r--r--app/tools/gimpoffsettool.c787
-rw-r--r--app/tools/gimpoffsettool.h63
-rw-r--r--app/tools/gimpoperationtool.c914
-rw-r--r--app/tools/gimpoperationtool.h71
-rw-r--r--app/tools/gimppaintbrushtool.c94
-rw-r--r--app/tools/gimppaintbrushtool.h53
-rw-r--r--app/tools/gimppaintoptions-gui.c582
-rw-r--r--app/tools/gimppaintoptions-gui.h27
-rw-r--r--app/tools/gimppainttool-paint.c540
-rw-r--r--app/tools/gimppainttool-paint.h48
-rw-r--r--app/tools/gimppainttool.c991
-rw-r--r--app/tools/gimppainttool.h109
-rw-r--r--app/tools/gimppenciltool.c70
-rw-r--r--app/tools/gimppenciltool.h53
-rw-r--r--app/tools/gimpperspectiveclonetool.c913
-rw-r--r--app/tools/gimpperspectiveclonetool.h72
-rw-r--r--app/tools/gimpperspectivetool.c292
-rw-r--r--app/tools/gimpperspectivetool.h53
-rw-r--r--app/tools/gimppolygonselecttool.c555
-rw-r--r--app/tools/gimppolygonselecttool.h69
-rw-r--r--app/tools/gimprectangleoptions.c1266
-rw-r--r--app/tools/gimprectangleoptions.h181
-rw-r--r--app/tools/gimprectangleselectoptions.c203
-rw-r--r--app/tools/gimprectangleselectoptions.h50
-rw-r--r--app/tools/gimprectangleselecttool.c918
-rw-r--r--app/tools/gimprectangleselecttool.h67
-rw-r--r--app/tools/gimpregionselectoptions.c290
-rw-r--r--app/tools/gimpregionselectoptions.h54
-rw-r--r--app/tools/gimpregionselecttool.c386
-rw-r--r--app/tools/gimpregionselecttool.h66
-rw-r--r--app/tools/gimprotatetool.c537
-rw-r--r--app/tools/gimprotatetool.h58
-rw-r--r--app/tools/gimpsamplepointtool.c373
-rw-r--r--app/tools/gimpsamplepointtool.h62
-rw-r--r--app/tools/gimpscaletool.c474
-rw-r--r--app/tools/gimpscaletool.h54
-rw-r--r--app/tools/gimpseamlesscloneoptions.c138
-rw-r--r--app/tools/gimpseamlesscloneoptions.h57
-rw-r--r--app/tools/gimpseamlessclonetool.c845
-rw-r--r--app/tools/gimpseamlessclonetool.h86
-rw-r--r--app/tools/gimpselectionoptions.c292
-rw-r--r--app/tools/gimpselectionoptions.h56
-rw-r--r--app/tools/gimpselectiontool.c830
-rw-r--r--app/tools/gimpselectiontool.h78
-rw-r--r--app/tools/gimpsheartool.c331
-rw-r--r--app/tools/gimpsheartool.h56
-rw-r--r--app/tools/gimpsmudgetool.c115
-rw-r--r--app/tools/gimpsmudgetool.h53
-rw-r--r--app/tools/gimpsourcetool.c519
-rw-r--r--app/tools/gimpsourcetool.h66
-rw-r--r--app/tools/gimptextoptions.c743
-rw-r--r--app/tools/gimptextoptions.h80
-rw-r--r--app/tools/gimptexttool-editor.c1864
-rw-r--r--app/tools/gimptexttool-editor.h56
-rw-r--r--app/tools/gimptexttool.c2388
-rw-r--r--app/tools/gimptexttool.h132
-rw-r--r--app/tools/gimpthresholdtool.c436
-rw-r--r--app/tools/gimpthresholdtool.h59
-rw-r--r--app/tools/gimptilehandleriscissors.c331
-rw-r--r--app/tools/gimptilehandleriscissors.h59
-rw-r--r--app/tools/gimptool-progress.c260
-rw-r--r--app/tools/gimptool-progress.h28
-rw-r--r--app/tools/gimptool.c1512
-rw-r--r--app/tools/gimptool.h299
-rw-r--r--app/tools/gimptoolcontrol.c727
-rw-r--r--app/tools/gimptoolcontrol.h254
-rw-r--r--app/tools/gimptooloptions-gui.c63
-rw-r--r--app/tools/gimptooloptions-gui.h26
-rw-r--r--app/tools/gimptools-utils.c80
-rw-r--r--app/tools/gimptools-utils.h26
-rw-r--r--app/tools/gimptransform3doptions.c225
-rw-r--r--app/tools/gimptransform3doptions.h59
-rw-r--r--app/tools/gimptransform3dtool.c1036
-rw-r--r--app/tools/gimptransform3dtool.h67
-rw-r--r--app/tools/gimptransformgridoptions.c712
-rw-r--r--app/tools/gimptransformgridoptions.h76
-rw-r--r--app/tools/gimptransformgridtool.c2209
-rw-r--r--app/tools/gimptransformgridtool.h118
-rw-r--r--app/tools/gimptransformgridtoolundo.c220
-rw-r--r--app/tools/gimptransformgridtoolundo.h56
-rw-r--r--app/tools/gimptransformoptions.c271
-rw-r--r--app/tools/gimptransformoptions.h64
-rw-r--r--app/tools/gimptransformtool.c925
-rw-r--r--app/tools/gimptransformtool.h104
-rw-r--r--app/tools/gimpunifiedtransformtool.c355
-rw-r--r--app/tools/gimpunifiedtransformtool.h53
-rw-r--r--app/tools/gimpvectoroptions.c219
-rw-r--r--app/tools/gimpvectoroptions.h55
-rw-r--r--app/tools/gimpvectortool.c852
-rw-r--r--app/tools/gimpvectortool.h67
-rw-r--r--app/tools/gimpwarpoptions.c407
-rw-r--r--app/tools/gimpwarpoptions.h75
-rw-r--r--app/tools/gimpwarptool.c1484
-rw-r--r--app/tools/gimpwarptool.h78
-rw-r--r--app/tools/tool_manager.c966
-rw-r--r--app/tools/tool_manager.h96
-rw-r--r--app/tools/tools-enums.c342
-rw-r--r--app/tools/tools-enums.h203
-rw-r--r--app/tools/tools-types.h68
227 files changed, 75882 insertions, 0 deletions
diff --git a/app/tools/Makefile.am b/app/tools/Makefile.am
new file mode 100644
index 0000000..bbe4584
--- /dev/null
+++ b/app/tools/Makefile.am
@@ -0,0 +1,275 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Tools\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(GEGL_CFLAGS) \
+ $(GTK_CFLAGS) \
+ -I$(includedir)
+
+noinst_LIBRARIES = libapptools.a
+
+libapptools_a_sources = \
+ tools-enums.h \
+ tools-types.h \
+ gimp-tool-options-manager.c \
+ gimp-tool-options-manager.h \
+ gimp-tools.c \
+ gimp-tools.h \
+ tool_manager.c \
+ tool_manager.h \
+ \
+ gimpairbrushtool.c \
+ gimpairbrushtool.h \
+ gimpalignoptions.c \
+ gimpalignoptions.h \
+ gimpaligntool.c \
+ gimpaligntool.h \
+ gimpbrightnesscontrasttool.c \
+ gimpbrightnesscontrasttool.h \
+ gimpbrushtool.c \
+ gimpbrushtool.h \
+ gimpbucketfilloptions.c \
+ gimpbucketfilloptions.h \
+ gimpbucketfilltool.c \
+ gimpbucketfilltool.h \
+ gimpbycolorselecttool.c \
+ gimpbycolorselecttool.h \
+ gimpcageoptions.c \
+ gimpcageoptions.h \
+ gimpcagetool.c \
+ gimpcagetool.h \
+ gimpcloneoptions-gui.c \
+ gimpcloneoptions-gui.h \
+ gimpclonetool.c \
+ gimpclonetool.h \
+ gimpcoloroptions.c \
+ gimpcoloroptions.h \
+ gimpcolortool.c \
+ gimpcolortool.h \
+ gimpcolorpickeroptions.c \
+ gimpcolorpickeroptions.h \
+ gimpcolorpickertool.c \
+ gimpcolorpickertool.h \
+ gimpconvolvetool.c \
+ gimpconvolvetool.h \
+ gimpcropoptions.c \
+ gimpcropoptions.h \
+ gimpcroptool.c \
+ gimpcroptool.h \
+ gimpcurvestool.c \
+ gimpcurvestool.h \
+ gimpdodgeburntool.c \
+ gimpdodgeburntool.h \
+ gimpdrawtool.c \
+ gimpdrawtool.h \
+ gimpeditselectiontool.c \
+ gimpeditselectiontool.h \
+ gimpellipseselecttool.c \
+ gimpellipseselecttool.h \
+ gimperasertool.c \
+ gimperasertool.h \
+ gimpfilteroptions.c \
+ gimpfilteroptions.h \
+ gimpfiltertool.c \
+ gimpfiltertool.h \
+ gimpfiltertool-settings.c \
+ gimpfiltertool-settings.h \
+ gimpfiltertool-widgets.c \
+ gimpfiltertool-widgets.h \
+ gimpflipoptions.c \
+ gimpflipoptions.h \
+ gimpfliptool.c \
+ gimpfliptool.h \
+ gimpforegroundselectoptions.c \
+ gimpforegroundselectoptions.h \
+ gimpforegroundselecttool.c \
+ gimpforegroundselecttool.h \
+ gimpforegroundselecttoolundo.c \
+ gimpforegroundselecttoolundo.h \
+ gimpfreeselecttool.c \
+ gimpfreeselecttool.h \
+ gimpfuzzyselecttool.c \
+ gimpfuzzyselecttool.h \
+ gimpgegltool.c \
+ gimpgegltool.h \
+ gimpgenerictransformtool.c \
+ gimpgenerictransformtool.h \
+ gimpgradientoptions.c \
+ gimpgradientoptions.h \
+ gimpgradienttool.c \
+ gimpgradienttool.h \
+ gimpgradienttool-editor.c \
+ gimpgradienttool-editor.h \
+ gimpguidetool.c \
+ gimpguidetool.h \
+ gimphandletransformoptions.c \
+ gimphandletransformoptions.h \
+ gimphandletransformtool.c \
+ gimphandletransformtool.h \
+ gimphealtool.c \
+ gimphealtool.h \
+ gimphistogramoptions.c \
+ gimphistogramoptions.h \
+ gimpinkoptions-gui.c \
+ gimpinkoptions-gui.h \
+ gimpinktool.c \
+ gimpinktool.h \
+ gimpiscissorsoptions.c \
+ gimpiscissorsoptions.h \
+ gimpiscissorstool.c \
+ gimpiscissorstool.h \
+ gimplevelstool.c \
+ gimplevelstool.h \
+ gimpoffsettool.c \
+ gimpoffsettool.h \
+ gimpoperationtool.c \
+ gimpoperationtool.h \
+ gimpmagnifyoptions.c \
+ gimpmagnifyoptions.h \
+ gimpmagnifytool.c \
+ gimpmagnifytool.h \
+ gimpmeasureoptions.c \
+ gimpmeasureoptions.h \
+ gimpmeasuretool.c \
+ gimpmeasuretool.h \
+ gimpmoveoptions.c \
+ gimpmoveoptions.h \
+ gimpmovetool.c \
+ gimpmovetool.h \
+ gimpmybrushoptions-gui.c \
+ gimpmybrushoptions-gui.h \
+ gimpmybrushtool.c \
+ gimpmybrushtool.h \
+ gimpnpointdeformationoptions.c \
+ gimpnpointdeformationoptions.h \
+ gimpnpointdeformationtool.c \
+ gimpnpointdeformationtool.h \
+ gimppaintbrushtool.c \
+ gimppaintbrushtool.h \
+ gimppaintoptions-gui.c \
+ gimppaintoptions-gui.h \
+ gimppainttool.c \
+ gimppainttool.h \
+ gimppainttool-paint.c \
+ gimppainttool-paint.h \
+ gimppenciltool.c \
+ gimppenciltool.h \
+ gimpperspectiveclonetool.c \
+ gimpperspectiveclonetool.h \
+ gimpperspectivetool.c \
+ gimpperspectivetool.h \
+ gimppolygonselecttool.c \
+ gimppolygonselecttool.h \
+ gimprectangleselecttool.c \
+ gimprectangleselecttool.h \
+ gimprectangleselectoptions.c \
+ gimprectangleselectoptions.h \
+ gimprectangleoptions.c \
+ gimprectangleoptions.h \
+ gimpregionselectoptions.c \
+ gimpregionselectoptions.h \
+ gimpregionselecttool.c \
+ gimpregionselecttool.h \
+ gimprotatetool.c \
+ gimprotatetool.h \
+ gimpsamplepointtool.c \
+ gimpsamplepointtool.h \
+ gimpscaletool.c \
+ gimpscaletool.h \
+ gimpseamlesscloneoptions.c \
+ gimpseamlesscloneoptions.h \
+ gimpseamlessclonetool.c \
+ gimpseamlessclonetool.h \
+ gimpselectionoptions.c \
+ gimpselectionoptions.h \
+ gimpselectiontool.c \
+ gimpselectiontool.h \
+ gimpsheartool.c \
+ gimpsheartool.h \
+ gimpsmudgetool.c \
+ gimpsmudgetool.h \
+ gimpsourcetool.c \
+ gimpsourcetool.h \
+ gimptextoptions.c \
+ gimptextoptions.h \
+ gimptexttool.c \
+ gimptexttool.h \
+ gimptexttool-editor.c \
+ gimptexttool-editor.h \
+ gimpthresholdtool.c \
+ gimpthresholdtool.h \
+ gimptilehandleriscissors.c \
+ gimptilehandleriscissors.h \
+ gimptool.c \
+ gimptool.h \
+ gimptool-progress.c \
+ gimptool-progress.h \
+ gimptoolcontrol.c \
+ gimptoolcontrol.h \
+ gimptooloptions-gui.c \
+ gimptooloptions-gui.h \
+ gimptools-utils.c \
+ gimptools-utils.h \
+ gimptransform3doptions.c \
+ gimptransform3doptions.h \
+ gimptransform3dtool.c \
+ gimptransform3dtool.h \
+ gimptransformgridoptions.c \
+ gimptransformgridoptions.h \
+ gimptransformgridtool.c \
+ gimptransformgridtool.h \
+ gimptransformgridtoolundo.c \
+ gimptransformgridtoolundo.h \
+ gimptransformoptions.c \
+ gimptransformoptions.h \
+ gimptransformtool.c \
+ gimptransformtool.h \
+ gimpunifiedtransformtool.c \
+ gimpunifiedtransformtool.h \
+ gimpvectoroptions.c \
+ gimpvectoroptions.h \
+ gimpvectortool.c \
+ gimpvectortool.h \
+ gimpwarpoptions.c \
+ gimpwarpoptions.h \
+ gimpwarptool.c \
+ gimpwarptool.h
+
+libapptools_a_built_sources = tools-enums.c
+
+libapptools_a_SOURCES = $(libapptools_a_built_sources) $(libapptools_a_sources)
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-tec
+CLEANFILES = $(gen_sources)
+
+xgen-tec: $(srcdir)/tools-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 \"core/core-enums.h\"\n#include \"tools-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)/tools-enums.c: xgen-tec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
diff --git a/app/tools/Makefile.in b/app/tools/Makefile.in
new file mode 100644
index 0000000..89225e6
--- /dev/null
+++ b/app/tools/Makefile.in
@@ -0,0 +1,1656 @@
+# 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/tools
+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 =
+libapptools_a_AR = $(AR) $(ARFLAGS)
+libapptools_a_LIBADD =
+am__objects_1 = tools-enums.$(OBJEXT)
+am__objects_2 = gimp-tool-options-manager.$(OBJEXT) \
+ gimp-tools.$(OBJEXT) tool_manager.$(OBJEXT) \
+ gimpairbrushtool.$(OBJEXT) gimpalignoptions.$(OBJEXT) \
+ gimpaligntool.$(OBJEXT) gimpbrightnesscontrasttool.$(OBJEXT) \
+ gimpbrushtool.$(OBJEXT) gimpbucketfilloptions.$(OBJEXT) \
+ gimpbucketfilltool.$(OBJEXT) gimpbycolorselecttool.$(OBJEXT) \
+ gimpcageoptions.$(OBJEXT) gimpcagetool.$(OBJEXT) \
+ gimpcloneoptions-gui.$(OBJEXT) gimpclonetool.$(OBJEXT) \
+ gimpcoloroptions.$(OBJEXT) gimpcolortool.$(OBJEXT) \
+ gimpcolorpickeroptions.$(OBJEXT) gimpcolorpickertool.$(OBJEXT) \
+ gimpconvolvetool.$(OBJEXT) gimpcropoptions.$(OBJEXT) \
+ gimpcroptool.$(OBJEXT) gimpcurvestool.$(OBJEXT) \
+ gimpdodgeburntool.$(OBJEXT) gimpdrawtool.$(OBJEXT) \
+ gimpeditselectiontool.$(OBJEXT) \
+ gimpellipseselecttool.$(OBJEXT) gimperasertool.$(OBJEXT) \
+ gimpfilteroptions.$(OBJEXT) gimpfiltertool.$(OBJEXT) \
+ gimpfiltertool-settings.$(OBJEXT) \
+ gimpfiltertool-widgets.$(OBJEXT) gimpflipoptions.$(OBJEXT) \
+ gimpfliptool.$(OBJEXT) gimpforegroundselectoptions.$(OBJEXT) \
+ gimpforegroundselecttool.$(OBJEXT) \
+ gimpforegroundselecttoolundo.$(OBJEXT) \
+ gimpfreeselecttool.$(OBJEXT) gimpfuzzyselecttool.$(OBJEXT) \
+ gimpgegltool.$(OBJEXT) gimpgenerictransformtool.$(OBJEXT) \
+ gimpgradientoptions.$(OBJEXT) gimpgradienttool.$(OBJEXT) \
+ gimpgradienttool-editor.$(OBJEXT) gimpguidetool.$(OBJEXT) \
+ gimphandletransformoptions.$(OBJEXT) \
+ gimphandletransformtool.$(OBJEXT) gimphealtool.$(OBJEXT) \
+ gimphistogramoptions.$(OBJEXT) gimpinkoptions-gui.$(OBJEXT) \
+ gimpinktool.$(OBJEXT) gimpiscissorsoptions.$(OBJEXT) \
+ gimpiscissorstool.$(OBJEXT) gimplevelstool.$(OBJEXT) \
+ gimpoffsettool.$(OBJEXT) gimpoperationtool.$(OBJEXT) \
+ gimpmagnifyoptions.$(OBJEXT) gimpmagnifytool.$(OBJEXT) \
+ gimpmeasureoptions.$(OBJEXT) gimpmeasuretool.$(OBJEXT) \
+ gimpmoveoptions.$(OBJEXT) gimpmovetool.$(OBJEXT) \
+ gimpmybrushoptions-gui.$(OBJEXT) gimpmybrushtool.$(OBJEXT) \
+ gimpnpointdeformationoptions.$(OBJEXT) \
+ gimpnpointdeformationtool.$(OBJEXT) \
+ gimppaintbrushtool.$(OBJEXT) gimppaintoptions-gui.$(OBJEXT) \
+ gimppainttool.$(OBJEXT) gimppainttool-paint.$(OBJEXT) \
+ gimppenciltool.$(OBJEXT) gimpperspectiveclonetool.$(OBJEXT) \
+ gimpperspectivetool.$(OBJEXT) gimppolygonselecttool.$(OBJEXT) \
+ gimprectangleselecttool.$(OBJEXT) \
+ gimprectangleselectoptions.$(OBJEXT) \
+ gimprectangleoptions.$(OBJEXT) \
+ gimpregionselectoptions.$(OBJEXT) \
+ gimpregionselecttool.$(OBJEXT) gimprotatetool.$(OBJEXT) \
+ gimpsamplepointtool.$(OBJEXT) gimpscaletool.$(OBJEXT) \
+ gimpseamlesscloneoptions.$(OBJEXT) \
+ gimpseamlessclonetool.$(OBJEXT) gimpselectionoptions.$(OBJEXT) \
+ gimpselectiontool.$(OBJEXT) gimpsheartool.$(OBJEXT) \
+ gimpsmudgetool.$(OBJEXT) gimpsourcetool.$(OBJEXT) \
+ gimptextoptions.$(OBJEXT) gimptexttool.$(OBJEXT) \
+ gimptexttool-editor.$(OBJEXT) gimpthresholdtool.$(OBJEXT) \
+ gimptilehandleriscissors.$(OBJEXT) gimptool.$(OBJEXT) \
+ gimptool-progress.$(OBJEXT) gimptoolcontrol.$(OBJEXT) \
+ gimptooloptions-gui.$(OBJEXT) gimptools-utils.$(OBJEXT) \
+ gimptransform3doptions.$(OBJEXT) gimptransform3dtool.$(OBJEXT) \
+ gimptransformgridoptions.$(OBJEXT) \
+ gimptransformgridtool.$(OBJEXT) \
+ gimptransformgridtoolundo.$(OBJEXT) \
+ gimptransformoptions.$(OBJEXT) gimptransformtool.$(OBJEXT) \
+ gimpunifiedtransformtool.$(OBJEXT) gimpvectoroptions.$(OBJEXT) \
+ gimpvectortool.$(OBJEXT) gimpwarpoptions.$(OBJEXT) \
+ gimpwarptool.$(OBJEXT)
+am_libapptools_a_OBJECTS = $(am__objects_1) $(am__objects_2)
+libapptools_a_OBJECTS = $(am_libapptools_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)/gimp-tool-options-manager.Po \
+ ./$(DEPDIR)/gimp-tools.Po ./$(DEPDIR)/gimpairbrushtool.Po \
+ ./$(DEPDIR)/gimpalignoptions.Po ./$(DEPDIR)/gimpaligntool.Po \
+ ./$(DEPDIR)/gimpbrightnesscontrasttool.Po \
+ ./$(DEPDIR)/gimpbrushtool.Po \
+ ./$(DEPDIR)/gimpbucketfilloptions.Po \
+ ./$(DEPDIR)/gimpbucketfilltool.Po \
+ ./$(DEPDIR)/gimpbycolorselecttool.Po \
+ ./$(DEPDIR)/gimpcageoptions.Po ./$(DEPDIR)/gimpcagetool.Po \
+ ./$(DEPDIR)/gimpcloneoptions-gui.Po \
+ ./$(DEPDIR)/gimpclonetool.Po ./$(DEPDIR)/gimpcoloroptions.Po \
+ ./$(DEPDIR)/gimpcolorpickeroptions.Po \
+ ./$(DEPDIR)/gimpcolorpickertool.Po \
+ ./$(DEPDIR)/gimpcolortool.Po ./$(DEPDIR)/gimpconvolvetool.Po \
+ ./$(DEPDIR)/gimpcropoptions.Po ./$(DEPDIR)/gimpcroptool.Po \
+ ./$(DEPDIR)/gimpcurvestool.Po ./$(DEPDIR)/gimpdodgeburntool.Po \
+ ./$(DEPDIR)/gimpdrawtool.Po \
+ ./$(DEPDIR)/gimpeditselectiontool.Po \
+ ./$(DEPDIR)/gimpellipseselecttool.Po \
+ ./$(DEPDIR)/gimperasertool.Po ./$(DEPDIR)/gimpfilteroptions.Po \
+ ./$(DEPDIR)/gimpfiltertool-settings.Po \
+ ./$(DEPDIR)/gimpfiltertool-widgets.Po \
+ ./$(DEPDIR)/gimpfiltertool.Po ./$(DEPDIR)/gimpflipoptions.Po \
+ ./$(DEPDIR)/gimpfliptool.Po \
+ ./$(DEPDIR)/gimpforegroundselectoptions.Po \
+ ./$(DEPDIR)/gimpforegroundselecttool.Po \
+ ./$(DEPDIR)/gimpforegroundselecttoolundo.Po \
+ ./$(DEPDIR)/gimpfreeselecttool.Po \
+ ./$(DEPDIR)/gimpfuzzyselecttool.Po ./$(DEPDIR)/gimpgegltool.Po \
+ ./$(DEPDIR)/gimpgenerictransformtool.Po \
+ ./$(DEPDIR)/gimpgradientoptions.Po \
+ ./$(DEPDIR)/gimpgradienttool-editor.Po \
+ ./$(DEPDIR)/gimpgradienttool.Po ./$(DEPDIR)/gimpguidetool.Po \
+ ./$(DEPDIR)/gimphandletransformoptions.Po \
+ ./$(DEPDIR)/gimphandletransformtool.Po \
+ ./$(DEPDIR)/gimphealtool.Po \
+ ./$(DEPDIR)/gimphistogramoptions.Po \
+ ./$(DEPDIR)/gimpinkoptions-gui.Po ./$(DEPDIR)/gimpinktool.Po \
+ ./$(DEPDIR)/gimpiscissorsoptions.Po \
+ ./$(DEPDIR)/gimpiscissorstool.Po ./$(DEPDIR)/gimplevelstool.Po \
+ ./$(DEPDIR)/gimpmagnifyoptions.Po \
+ ./$(DEPDIR)/gimpmagnifytool.Po \
+ ./$(DEPDIR)/gimpmeasureoptions.Po \
+ ./$(DEPDIR)/gimpmeasuretool.Po ./$(DEPDIR)/gimpmoveoptions.Po \
+ ./$(DEPDIR)/gimpmovetool.Po \
+ ./$(DEPDIR)/gimpmybrushoptions-gui.Po \
+ ./$(DEPDIR)/gimpmybrushtool.Po \
+ ./$(DEPDIR)/gimpnpointdeformationoptions.Po \
+ ./$(DEPDIR)/gimpnpointdeformationtool.Po \
+ ./$(DEPDIR)/gimpoffsettool.Po ./$(DEPDIR)/gimpoperationtool.Po \
+ ./$(DEPDIR)/gimppaintbrushtool.Po \
+ ./$(DEPDIR)/gimppaintoptions-gui.Po \
+ ./$(DEPDIR)/gimppainttool-paint.Po \
+ ./$(DEPDIR)/gimppainttool.Po ./$(DEPDIR)/gimppenciltool.Po \
+ ./$(DEPDIR)/gimpperspectiveclonetool.Po \
+ ./$(DEPDIR)/gimpperspectivetool.Po \
+ ./$(DEPDIR)/gimppolygonselecttool.Po \
+ ./$(DEPDIR)/gimprectangleoptions.Po \
+ ./$(DEPDIR)/gimprectangleselectoptions.Po \
+ ./$(DEPDIR)/gimprectangleselecttool.Po \
+ ./$(DEPDIR)/gimpregionselectoptions.Po \
+ ./$(DEPDIR)/gimpregionselecttool.Po \
+ ./$(DEPDIR)/gimprotatetool.Po \
+ ./$(DEPDIR)/gimpsamplepointtool.Po \
+ ./$(DEPDIR)/gimpscaletool.Po \
+ ./$(DEPDIR)/gimpseamlesscloneoptions.Po \
+ ./$(DEPDIR)/gimpseamlessclonetool.Po \
+ ./$(DEPDIR)/gimpselectionoptions.Po \
+ ./$(DEPDIR)/gimpselectiontool.Po ./$(DEPDIR)/gimpsheartool.Po \
+ ./$(DEPDIR)/gimpsmudgetool.Po ./$(DEPDIR)/gimpsourcetool.Po \
+ ./$(DEPDIR)/gimptextoptions.Po \
+ ./$(DEPDIR)/gimptexttool-editor.Po ./$(DEPDIR)/gimptexttool.Po \
+ ./$(DEPDIR)/gimpthresholdtool.Po \
+ ./$(DEPDIR)/gimptilehandleriscissors.Po \
+ ./$(DEPDIR)/gimptool-progress.Po ./$(DEPDIR)/gimptool.Po \
+ ./$(DEPDIR)/gimptoolcontrol.Po \
+ ./$(DEPDIR)/gimptooloptions-gui.Po \
+ ./$(DEPDIR)/gimptools-utils.Po \
+ ./$(DEPDIR)/gimptransform3doptions.Po \
+ ./$(DEPDIR)/gimptransform3dtool.Po \
+ ./$(DEPDIR)/gimptransformgridoptions.Po \
+ ./$(DEPDIR)/gimptransformgridtool.Po \
+ ./$(DEPDIR)/gimptransformgridtoolundo.Po \
+ ./$(DEPDIR)/gimptransformoptions.Po \
+ ./$(DEPDIR)/gimptransformtool.Po \
+ ./$(DEPDIR)/gimpunifiedtransformtool.Po \
+ ./$(DEPDIR)/gimpvectoroptions.Po ./$(DEPDIR)/gimpvectortool.Po \
+ ./$(DEPDIR)/gimpwarpoptions.Po ./$(DEPDIR)/gimpwarptool.Po \
+ ./$(DEPDIR)/tool_manager.Po ./$(DEPDIR)/tools-enums.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 = $(libapptools_a_SOURCES)
+DIST_SOURCES = $(libapptools_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@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Tools\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(GEGL_CFLAGS) \
+ $(GTK_CFLAGS) \
+ -I$(includedir)
+
+noinst_LIBRARIES = libapptools.a
+libapptools_a_sources = \
+ tools-enums.h \
+ tools-types.h \
+ gimp-tool-options-manager.c \
+ gimp-tool-options-manager.h \
+ gimp-tools.c \
+ gimp-tools.h \
+ tool_manager.c \
+ tool_manager.h \
+ \
+ gimpairbrushtool.c \
+ gimpairbrushtool.h \
+ gimpalignoptions.c \
+ gimpalignoptions.h \
+ gimpaligntool.c \
+ gimpaligntool.h \
+ gimpbrightnesscontrasttool.c \
+ gimpbrightnesscontrasttool.h \
+ gimpbrushtool.c \
+ gimpbrushtool.h \
+ gimpbucketfilloptions.c \
+ gimpbucketfilloptions.h \
+ gimpbucketfilltool.c \
+ gimpbucketfilltool.h \
+ gimpbycolorselecttool.c \
+ gimpbycolorselecttool.h \
+ gimpcageoptions.c \
+ gimpcageoptions.h \
+ gimpcagetool.c \
+ gimpcagetool.h \
+ gimpcloneoptions-gui.c \
+ gimpcloneoptions-gui.h \
+ gimpclonetool.c \
+ gimpclonetool.h \
+ gimpcoloroptions.c \
+ gimpcoloroptions.h \
+ gimpcolortool.c \
+ gimpcolortool.h \
+ gimpcolorpickeroptions.c \
+ gimpcolorpickeroptions.h \
+ gimpcolorpickertool.c \
+ gimpcolorpickertool.h \
+ gimpconvolvetool.c \
+ gimpconvolvetool.h \
+ gimpcropoptions.c \
+ gimpcropoptions.h \
+ gimpcroptool.c \
+ gimpcroptool.h \
+ gimpcurvestool.c \
+ gimpcurvestool.h \
+ gimpdodgeburntool.c \
+ gimpdodgeburntool.h \
+ gimpdrawtool.c \
+ gimpdrawtool.h \
+ gimpeditselectiontool.c \
+ gimpeditselectiontool.h \
+ gimpellipseselecttool.c \
+ gimpellipseselecttool.h \
+ gimperasertool.c \
+ gimperasertool.h \
+ gimpfilteroptions.c \
+ gimpfilteroptions.h \
+ gimpfiltertool.c \
+ gimpfiltertool.h \
+ gimpfiltertool-settings.c \
+ gimpfiltertool-settings.h \
+ gimpfiltertool-widgets.c \
+ gimpfiltertool-widgets.h \
+ gimpflipoptions.c \
+ gimpflipoptions.h \
+ gimpfliptool.c \
+ gimpfliptool.h \
+ gimpforegroundselectoptions.c \
+ gimpforegroundselectoptions.h \
+ gimpforegroundselecttool.c \
+ gimpforegroundselecttool.h \
+ gimpforegroundselecttoolundo.c \
+ gimpforegroundselecttoolundo.h \
+ gimpfreeselecttool.c \
+ gimpfreeselecttool.h \
+ gimpfuzzyselecttool.c \
+ gimpfuzzyselecttool.h \
+ gimpgegltool.c \
+ gimpgegltool.h \
+ gimpgenerictransformtool.c \
+ gimpgenerictransformtool.h \
+ gimpgradientoptions.c \
+ gimpgradientoptions.h \
+ gimpgradienttool.c \
+ gimpgradienttool.h \
+ gimpgradienttool-editor.c \
+ gimpgradienttool-editor.h \
+ gimpguidetool.c \
+ gimpguidetool.h \
+ gimphandletransformoptions.c \
+ gimphandletransformoptions.h \
+ gimphandletransformtool.c \
+ gimphandletransformtool.h \
+ gimphealtool.c \
+ gimphealtool.h \
+ gimphistogramoptions.c \
+ gimphistogramoptions.h \
+ gimpinkoptions-gui.c \
+ gimpinkoptions-gui.h \
+ gimpinktool.c \
+ gimpinktool.h \
+ gimpiscissorsoptions.c \
+ gimpiscissorsoptions.h \
+ gimpiscissorstool.c \
+ gimpiscissorstool.h \
+ gimplevelstool.c \
+ gimplevelstool.h \
+ gimpoffsettool.c \
+ gimpoffsettool.h \
+ gimpoperationtool.c \
+ gimpoperationtool.h \
+ gimpmagnifyoptions.c \
+ gimpmagnifyoptions.h \
+ gimpmagnifytool.c \
+ gimpmagnifytool.h \
+ gimpmeasureoptions.c \
+ gimpmeasureoptions.h \
+ gimpmeasuretool.c \
+ gimpmeasuretool.h \
+ gimpmoveoptions.c \
+ gimpmoveoptions.h \
+ gimpmovetool.c \
+ gimpmovetool.h \
+ gimpmybrushoptions-gui.c \
+ gimpmybrushoptions-gui.h \
+ gimpmybrushtool.c \
+ gimpmybrushtool.h \
+ gimpnpointdeformationoptions.c \
+ gimpnpointdeformationoptions.h \
+ gimpnpointdeformationtool.c \
+ gimpnpointdeformationtool.h \
+ gimppaintbrushtool.c \
+ gimppaintbrushtool.h \
+ gimppaintoptions-gui.c \
+ gimppaintoptions-gui.h \
+ gimppainttool.c \
+ gimppainttool.h \
+ gimppainttool-paint.c \
+ gimppainttool-paint.h \
+ gimppenciltool.c \
+ gimppenciltool.h \
+ gimpperspectiveclonetool.c \
+ gimpperspectiveclonetool.h \
+ gimpperspectivetool.c \
+ gimpperspectivetool.h \
+ gimppolygonselecttool.c \
+ gimppolygonselecttool.h \
+ gimprectangleselecttool.c \
+ gimprectangleselecttool.h \
+ gimprectangleselectoptions.c \
+ gimprectangleselectoptions.h \
+ gimprectangleoptions.c \
+ gimprectangleoptions.h \
+ gimpregionselectoptions.c \
+ gimpregionselectoptions.h \
+ gimpregionselecttool.c \
+ gimpregionselecttool.h \
+ gimprotatetool.c \
+ gimprotatetool.h \
+ gimpsamplepointtool.c \
+ gimpsamplepointtool.h \
+ gimpscaletool.c \
+ gimpscaletool.h \
+ gimpseamlesscloneoptions.c \
+ gimpseamlesscloneoptions.h \
+ gimpseamlessclonetool.c \
+ gimpseamlessclonetool.h \
+ gimpselectionoptions.c \
+ gimpselectionoptions.h \
+ gimpselectiontool.c \
+ gimpselectiontool.h \
+ gimpsheartool.c \
+ gimpsheartool.h \
+ gimpsmudgetool.c \
+ gimpsmudgetool.h \
+ gimpsourcetool.c \
+ gimpsourcetool.h \
+ gimptextoptions.c \
+ gimptextoptions.h \
+ gimptexttool.c \
+ gimptexttool.h \
+ gimptexttool-editor.c \
+ gimptexttool-editor.h \
+ gimpthresholdtool.c \
+ gimpthresholdtool.h \
+ gimptilehandleriscissors.c \
+ gimptilehandleriscissors.h \
+ gimptool.c \
+ gimptool.h \
+ gimptool-progress.c \
+ gimptool-progress.h \
+ gimptoolcontrol.c \
+ gimptoolcontrol.h \
+ gimptooloptions-gui.c \
+ gimptooloptions-gui.h \
+ gimptools-utils.c \
+ gimptools-utils.h \
+ gimptransform3doptions.c \
+ gimptransform3doptions.h \
+ gimptransform3dtool.c \
+ gimptransform3dtool.h \
+ gimptransformgridoptions.c \
+ gimptransformgridoptions.h \
+ gimptransformgridtool.c \
+ gimptransformgridtool.h \
+ gimptransformgridtoolundo.c \
+ gimptransformgridtoolundo.h \
+ gimptransformoptions.c \
+ gimptransformoptions.h \
+ gimptransformtool.c \
+ gimptransformtool.h \
+ gimpunifiedtransformtool.c \
+ gimpunifiedtransformtool.h \
+ gimpvectoroptions.c \
+ gimpvectoroptions.h \
+ gimpvectortool.c \
+ gimpvectortool.h \
+ gimpwarpoptions.c \
+ gimpwarpoptions.h \
+ gimpwarptool.c \
+ gimpwarptool.h
+
+libapptools_a_built_sources = tools-enums.c
+libapptools_a_SOURCES = $(libapptools_a_built_sources) $(libapptools_a_sources)
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-tec
+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/tools/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu app/tools/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)
+
+libapptools.a: $(libapptools_a_OBJECTS) $(libapptools_a_DEPENDENCIES) $(EXTRA_libapptools_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libapptools.a
+ $(AM_V_AR)$(libapptools_a_AR) libapptools.a $(libapptools_a_OBJECTS) $(libapptools_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libapptools.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-tool-options-manager.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-tools.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpairbrushtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpalignoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpaligntool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrightnesscontrasttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbucketfilloptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbucketfilltool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbycolorselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcageoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcagetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcloneoptions-gui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpclonetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcoloroptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorpickeroptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorpickertool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolortool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpconvolvetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcropoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcroptool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcurvestool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdodgeburntool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpeditselectiontool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpellipseselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimperasertool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfilteroptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfiltertool-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfiltertool-widgets.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfiltertool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpflipoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfliptool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpforegroundselectoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpforegroundselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpforegroundselecttoolundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfreeselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfuzzyselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgegltool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgenerictransformtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradientoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradienttool-editor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradienttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpguidetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphandletransformoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphandletransformtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphealtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphistogramoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpinkoptions-gui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpinktool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpiscissorsoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpiscissorstool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplevelstool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmagnifyoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmagnifytool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmeasureoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmeasuretool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmoveoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmovetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrushoptions-gui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrushtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpnpointdeformationoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpnpointdeformationtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoffsettool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoperationtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintbrushtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintoptions-gui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppainttool-paint.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppainttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppenciltool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpperspectiveclonetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpperspectivetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppolygonselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimprectangleoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimprectangleselectoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimprectangleselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpregionselectoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpregionselecttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimprotatetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsamplepointtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscaletool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpseamlesscloneoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpseamlessclonetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpselectionoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpselectiontool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsheartool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsmudgetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsourcetool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptextoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptexttool-editor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptexttool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpthresholdtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptilehandleriscissors.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptool-progress.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolcontrol.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooloptions-gui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptools-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransform3doptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransform3dtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransformgridoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransformgridtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransformgridtoolundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransformoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptransformtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpunifiedtransformtool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectoroptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectortool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwarpoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwarptool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tool_manager.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tools-enums.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)/gimp-tool-options-manager.Po
+ -rm -f ./$(DEPDIR)/gimp-tools.Po
+ -rm -f ./$(DEPDIR)/gimpairbrushtool.Po
+ -rm -f ./$(DEPDIR)/gimpalignoptions.Po
+ -rm -f ./$(DEPDIR)/gimpaligntool.Po
+ -rm -f ./$(DEPDIR)/gimpbrightnesscontrasttool.Po
+ -rm -f ./$(DEPDIR)/gimpbrushtool.Po
+ -rm -f ./$(DEPDIR)/gimpbucketfilloptions.Po
+ -rm -f ./$(DEPDIR)/gimpbucketfilltool.Po
+ -rm -f ./$(DEPDIR)/gimpbycolorselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpcageoptions.Po
+ -rm -f ./$(DEPDIR)/gimpcagetool.Po
+ -rm -f ./$(DEPDIR)/gimpcloneoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimpclonetool.Po
+ -rm -f ./$(DEPDIR)/gimpcoloroptions.Po
+ -rm -f ./$(DEPDIR)/gimpcolorpickeroptions.Po
+ -rm -f ./$(DEPDIR)/gimpcolorpickertool.Po
+ -rm -f ./$(DEPDIR)/gimpcolortool.Po
+ -rm -f ./$(DEPDIR)/gimpconvolvetool.Po
+ -rm -f ./$(DEPDIR)/gimpcropoptions.Po
+ -rm -f ./$(DEPDIR)/gimpcroptool.Po
+ -rm -f ./$(DEPDIR)/gimpcurvestool.Po
+ -rm -f ./$(DEPDIR)/gimpdodgeburntool.Po
+ -rm -f ./$(DEPDIR)/gimpdrawtool.Po
+ -rm -f ./$(DEPDIR)/gimpeditselectiontool.Po
+ -rm -f ./$(DEPDIR)/gimpellipseselecttool.Po
+ -rm -f ./$(DEPDIR)/gimperasertool.Po
+ -rm -f ./$(DEPDIR)/gimpfilteroptions.Po
+ -rm -f ./$(DEPDIR)/gimpfiltertool-settings.Po
+ -rm -f ./$(DEPDIR)/gimpfiltertool-widgets.Po
+ -rm -f ./$(DEPDIR)/gimpfiltertool.Po
+ -rm -f ./$(DEPDIR)/gimpflipoptions.Po
+ -rm -f ./$(DEPDIR)/gimpfliptool.Po
+ -rm -f ./$(DEPDIR)/gimpforegroundselectoptions.Po
+ -rm -f ./$(DEPDIR)/gimpforegroundselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpforegroundselecttoolundo.Po
+ -rm -f ./$(DEPDIR)/gimpfreeselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpfuzzyselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpgegltool.Po
+ -rm -f ./$(DEPDIR)/gimpgenerictransformtool.Po
+ -rm -f ./$(DEPDIR)/gimpgradientoptions.Po
+ -rm -f ./$(DEPDIR)/gimpgradienttool-editor.Po
+ -rm -f ./$(DEPDIR)/gimpgradienttool.Po
+ -rm -f ./$(DEPDIR)/gimpguidetool.Po
+ -rm -f ./$(DEPDIR)/gimphandletransformoptions.Po
+ -rm -f ./$(DEPDIR)/gimphandletransformtool.Po
+ -rm -f ./$(DEPDIR)/gimphealtool.Po
+ -rm -f ./$(DEPDIR)/gimphistogramoptions.Po
+ -rm -f ./$(DEPDIR)/gimpinkoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimpinktool.Po
+ -rm -f ./$(DEPDIR)/gimpiscissorsoptions.Po
+ -rm -f ./$(DEPDIR)/gimpiscissorstool.Po
+ -rm -f ./$(DEPDIR)/gimplevelstool.Po
+ -rm -f ./$(DEPDIR)/gimpmagnifyoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmagnifytool.Po
+ -rm -f ./$(DEPDIR)/gimpmeasureoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmeasuretool.Po
+ -rm -f ./$(DEPDIR)/gimpmoveoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmovetool.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushtool.Po
+ -rm -f ./$(DEPDIR)/gimpnpointdeformationoptions.Po
+ -rm -f ./$(DEPDIR)/gimpnpointdeformationtool.Po
+ -rm -f ./$(DEPDIR)/gimpoffsettool.Po
+ -rm -f ./$(DEPDIR)/gimpoperationtool.Po
+ -rm -f ./$(DEPDIR)/gimppaintbrushtool.Po
+ -rm -f ./$(DEPDIR)/gimppaintoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimppainttool-paint.Po
+ -rm -f ./$(DEPDIR)/gimppainttool.Po
+ -rm -f ./$(DEPDIR)/gimppenciltool.Po
+ -rm -f ./$(DEPDIR)/gimpperspectiveclonetool.Po
+ -rm -f ./$(DEPDIR)/gimpperspectivetool.Po
+ -rm -f ./$(DEPDIR)/gimppolygonselecttool.Po
+ -rm -f ./$(DEPDIR)/gimprectangleoptions.Po
+ -rm -f ./$(DEPDIR)/gimprectangleselectoptions.Po
+ -rm -f ./$(DEPDIR)/gimprectangleselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpregionselectoptions.Po
+ -rm -f ./$(DEPDIR)/gimpregionselecttool.Po
+ -rm -f ./$(DEPDIR)/gimprotatetool.Po
+ -rm -f ./$(DEPDIR)/gimpsamplepointtool.Po
+ -rm -f ./$(DEPDIR)/gimpscaletool.Po
+ -rm -f ./$(DEPDIR)/gimpseamlesscloneoptions.Po
+ -rm -f ./$(DEPDIR)/gimpseamlessclonetool.Po
+ -rm -f ./$(DEPDIR)/gimpselectionoptions.Po
+ -rm -f ./$(DEPDIR)/gimpselectiontool.Po
+ -rm -f ./$(DEPDIR)/gimpsheartool.Po
+ -rm -f ./$(DEPDIR)/gimpsmudgetool.Po
+ -rm -f ./$(DEPDIR)/gimpsourcetool.Po
+ -rm -f ./$(DEPDIR)/gimptextoptions.Po
+ -rm -f ./$(DEPDIR)/gimptexttool-editor.Po
+ -rm -f ./$(DEPDIR)/gimptexttool.Po
+ -rm -f ./$(DEPDIR)/gimpthresholdtool.Po
+ -rm -f ./$(DEPDIR)/gimptilehandleriscissors.Po
+ -rm -f ./$(DEPDIR)/gimptool-progress.Po
+ -rm -f ./$(DEPDIR)/gimptool.Po
+ -rm -f ./$(DEPDIR)/gimptoolcontrol.Po
+ -rm -f ./$(DEPDIR)/gimptooloptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimptools-utils.Po
+ -rm -f ./$(DEPDIR)/gimptransform3doptions.Po
+ -rm -f ./$(DEPDIR)/gimptransform3dtool.Po
+ -rm -f ./$(DEPDIR)/gimptransformgridoptions.Po
+ -rm -f ./$(DEPDIR)/gimptransformgridtool.Po
+ -rm -f ./$(DEPDIR)/gimptransformgridtoolundo.Po
+ -rm -f ./$(DEPDIR)/gimptransformoptions.Po
+ -rm -f ./$(DEPDIR)/gimptransformtool.Po
+ -rm -f ./$(DEPDIR)/gimpunifiedtransformtool.Po
+ -rm -f ./$(DEPDIR)/gimpvectoroptions.Po
+ -rm -f ./$(DEPDIR)/gimpvectortool.Po
+ -rm -f ./$(DEPDIR)/gimpwarpoptions.Po
+ -rm -f ./$(DEPDIR)/gimpwarptool.Po
+ -rm -f ./$(DEPDIR)/tool_manager.Po
+ -rm -f ./$(DEPDIR)/tools-enums.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)/gimp-tool-options-manager.Po
+ -rm -f ./$(DEPDIR)/gimp-tools.Po
+ -rm -f ./$(DEPDIR)/gimpairbrushtool.Po
+ -rm -f ./$(DEPDIR)/gimpalignoptions.Po
+ -rm -f ./$(DEPDIR)/gimpaligntool.Po
+ -rm -f ./$(DEPDIR)/gimpbrightnesscontrasttool.Po
+ -rm -f ./$(DEPDIR)/gimpbrushtool.Po
+ -rm -f ./$(DEPDIR)/gimpbucketfilloptions.Po
+ -rm -f ./$(DEPDIR)/gimpbucketfilltool.Po
+ -rm -f ./$(DEPDIR)/gimpbycolorselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpcageoptions.Po
+ -rm -f ./$(DEPDIR)/gimpcagetool.Po
+ -rm -f ./$(DEPDIR)/gimpcloneoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimpclonetool.Po
+ -rm -f ./$(DEPDIR)/gimpcoloroptions.Po
+ -rm -f ./$(DEPDIR)/gimpcolorpickeroptions.Po
+ -rm -f ./$(DEPDIR)/gimpcolorpickertool.Po
+ -rm -f ./$(DEPDIR)/gimpcolortool.Po
+ -rm -f ./$(DEPDIR)/gimpconvolvetool.Po
+ -rm -f ./$(DEPDIR)/gimpcropoptions.Po
+ -rm -f ./$(DEPDIR)/gimpcroptool.Po
+ -rm -f ./$(DEPDIR)/gimpcurvestool.Po
+ -rm -f ./$(DEPDIR)/gimpdodgeburntool.Po
+ -rm -f ./$(DEPDIR)/gimpdrawtool.Po
+ -rm -f ./$(DEPDIR)/gimpeditselectiontool.Po
+ -rm -f ./$(DEPDIR)/gimpellipseselecttool.Po
+ -rm -f ./$(DEPDIR)/gimperasertool.Po
+ -rm -f ./$(DEPDIR)/gimpfilteroptions.Po
+ -rm -f ./$(DEPDIR)/gimpfiltertool-settings.Po
+ -rm -f ./$(DEPDIR)/gimpfiltertool-widgets.Po
+ -rm -f ./$(DEPDIR)/gimpfiltertool.Po
+ -rm -f ./$(DEPDIR)/gimpflipoptions.Po
+ -rm -f ./$(DEPDIR)/gimpfliptool.Po
+ -rm -f ./$(DEPDIR)/gimpforegroundselectoptions.Po
+ -rm -f ./$(DEPDIR)/gimpforegroundselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpforegroundselecttoolundo.Po
+ -rm -f ./$(DEPDIR)/gimpfreeselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpfuzzyselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpgegltool.Po
+ -rm -f ./$(DEPDIR)/gimpgenerictransformtool.Po
+ -rm -f ./$(DEPDIR)/gimpgradientoptions.Po
+ -rm -f ./$(DEPDIR)/gimpgradienttool-editor.Po
+ -rm -f ./$(DEPDIR)/gimpgradienttool.Po
+ -rm -f ./$(DEPDIR)/gimpguidetool.Po
+ -rm -f ./$(DEPDIR)/gimphandletransformoptions.Po
+ -rm -f ./$(DEPDIR)/gimphandletransformtool.Po
+ -rm -f ./$(DEPDIR)/gimphealtool.Po
+ -rm -f ./$(DEPDIR)/gimphistogramoptions.Po
+ -rm -f ./$(DEPDIR)/gimpinkoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimpinktool.Po
+ -rm -f ./$(DEPDIR)/gimpiscissorsoptions.Po
+ -rm -f ./$(DEPDIR)/gimpiscissorstool.Po
+ -rm -f ./$(DEPDIR)/gimplevelstool.Po
+ -rm -f ./$(DEPDIR)/gimpmagnifyoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmagnifytool.Po
+ -rm -f ./$(DEPDIR)/gimpmeasureoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmeasuretool.Po
+ -rm -f ./$(DEPDIR)/gimpmoveoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmovetool.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushtool.Po
+ -rm -f ./$(DEPDIR)/gimpnpointdeformationoptions.Po
+ -rm -f ./$(DEPDIR)/gimpnpointdeformationtool.Po
+ -rm -f ./$(DEPDIR)/gimpoffsettool.Po
+ -rm -f ./$(DEPDIR)/gimpoperationtool.Po
+ -rm -f ./$(DEPDIR)/gimppaintbrushtool.Po
+ -rm -f ./$(DEPDIR)/gimppaintoptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimppainttool-paint.Po
+ -rm -f ./$(DEPDIR)/gimppainttool.Po
+ -rm -f ./$(DEPDIR)/gimppenciltool.Po
+ -rm -f ./$(DEPDIR)/gimpperspectiveclonetool.Po
+ -rm -f ./$(DEPDIR)/gimpperspectivetool.Po
+ -rm -f ./$(DEPDIR)/gimppolygonselecttool.Po
+ -rm -f ./$(DEPDIR)/gimprectangleoptions.Po
+ -rm -f ./$(DEPDIR)/gimprectangleselectoptions.Po
+ -rm -f ./$(DEPDIR)/gimprectangleselecttool.Po
+ -rm -f ./$(DEPDIR)/gimpregionselectoptions.Po
+ -rm -f ./$(DEPDIR)/gimpregionselecttool.Po
+ -rm -f ./$(DEPDIR)/gimprotatetool.Po
+ -rm -f ./$(DEPDIR)/gimpsamplepointtool.Po
+ -rm -f ./$(DEPDIR)/gimpscaletool.Po
+ -rm -f ./$(DEPDIR)/gimpseamlesscloneoptions.Po
+ -rm -f ./$(DEPDIR)/gimpseamlessclonetool.Po
+ -rm -f ./$(DEPDIR)/gimpselectionoptions.Po
+ -rm -f ./$(DEPDIR)/gimpselectiontool.Po
+ -rm -f ./$(DEPDIR)/gimpsheartool.Po
+ -rm -f ./$(DEPDIR)/gimpsmudgetool.Po
+ -rm -f ./$(DEPDIR)/gimpsourcetool.Po
+ -rm -f ./$(DEPDIR)/gimptextoptions.Po
+ -rm -f ./$(DEPDIR)/gimptexttool-editor.Po
+ -rm -f ./$(DEPDIR)/gimptexttool.Po
+ -rm -f ./$(DEPDIR)/gimpthresholdtool.Po
+ -rm -f ./$(DEPDIR)/gimptilehandleriscissors.Po
+ -rm -f ./$(DEPDIR)/gimptool-progress.Po
+ -rm -f ./$(DEPDIR)/gimptool.Po
+ -rm -f ./$(DEPDIR)/gimptoolcontrol.Po
+ -rm -f ./$(DEPDIR)/gimptooloptions-gui.Po
+ -rm -f ./$(DEPDIR)/gimptools-utils.Po
+ -rm -f ./$(DEPDIR)/gimptransform3doptions.Po
+ -rm -f ./$(DEPDIR)/gimptransform3dtool.Po
+ -rm -f ./$(DEPDIR)/gimptransformgridoptions.Po
+ -rm -f ./$(DEPDIR)/gimptransformgridtool.Po
+ -rm -f ./$(DEPDIR)/gimptransformgridtoolundo.Po
+ -rm -f ./$(DEPDIR)/gimptransformoptions.Po
+ -rm -f ./$(DEPDIR)/gimptransformtool.Po
+ -rm -f ./$(DEPDIR)/gimpunifiedtransformtool.Po
+ -rm -f ./$(DEPDIR)/gimpvectoroptions.Po
+ -rm -f ./$(DEPDIR)/gimpvectortool.Po
+ -rm -f ./$(DEPDIR)/gimpwarpoptions.Po
+ -rm -f ./$(DEPDIR)/gimpwarptool.Po
+ -rm -f ./$(DEPDIR)/tool_manager.Po
+ -rm -f ./$(DEPDIR)/tools-enums.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-tec: $(srcdir)/tools-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 \"core/core-enums.h\"\n#include \"tools-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)/tools-enums.c: xgen-tec
+ $(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/tools/gimp-tool-options-manager.c b/app/tools/gimp-tool-options-manager.c
new file mode 100644
index 0000000..a466d65
--- /dev/null
+++ b/app/tools/gimp-tool-options-manager.c
@@ -0,0 +1,462 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-tool-options-manager.c
+ * Copyright (C) 2018 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 "libgimpconfig/gimpconfig.h"
+
+#include "tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimptoolinfo.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimp-tool-options-manager.h"
+
+
+typedef struct _GimpToolOptionsManager GimpToolOptionsManager;
+
+struct _GimpToolOptionsManager
+{
+ Gimp *gimp;
+ GimpPaintOptions *global_paint_options;
+ GimpContextPropMask global_props;
+
+ GimpToolInfo *active_tool;
+};
+
+
+static GQuark manager_quark = 0;
+
+
+/* local function prototypes */
+
+static GimpContextPropMask
+ tool_options_manager_get_global_props
+ (GimpCoreConfig *config);
+
+static void tool_options_manager_global_notify (GimpCoreConfig *config,
+ const GParamSpec *pspec,
+ GimpToolOptionsManager *manager);
+static void tool_options_manager_paint_options_notify
+ (GimpPaintOptions *src,
+ const GParamSpec *pspec,
+ GimpPaintOptions *dest);
+
+static void tool_options_manager_copy_paint_props
+ (GimpPaintOptions *src,
+ GimpPaintOptions *dest,
+ GimpContextPropMask prop_mask);
+
+static void tool_options_manager_tool_changed (GimpContext *user_context,
+ GimpToolInfo *tool_info,
+ GimpToolOptionsManager *manager);
+
+
+/* public functions */
+
+void
+gimp_tool_options_manager_init (Gimp *gimp)
+{
+ GimpToolOptionsManager *manager;
+ GimpContext *user_context;
+ GimpCoreConfig *config;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (manager_quark == 0);
+
+ manager_quark = g_quark_from_static_string ("gimp-tool-options-manager");
+
+ config = gimp->config;
+
+ manager = g_slice_new0 (GimpToolOptionsManager);
+
+ manager->gimp = gimp;
+
+ manager->global_paint_options =
+ g_object_new (GIMP_TYPE_PAINT_OPTIONS,
+ "gimp", gimp,
+ "name", "tool-options-manager-global-paint-options",
+ NULL);
+
+ manager->global_props = tool_options_manager_get_global_props (config);
+
+ g_object_set_qdata (G_OBJECT (gimp), manager_quark, manager);
+
+ user_context = gimp_get_user_context (gimp);
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = list->data;
+
+ /* the global props that are actually used by the tool are
+ * always shared with the user context by undefining them...
+ */
+ gimp_context_define_properties (GIMP_CONTEXT (tool_info->tool_options),
+ manager->global_props &
+ tool_info->context_props,
+ FALSE);
+
+ /* ...and setting the user context as parent
+ */
+ gimp_context_set_parent (GIMP_CONTEXT (tool_info->tool_options),
+ user_context);
+
+ /* make sure paint tools also share their brush, dynamics,
+ * gradient properties if the resp. context properties are
+ * global
+ */
+ if (GIMP_IS_PAINT_OPTIONS (tool_info->tool_options))
+ {
+ g_signal_connect (tool_info->tool_options, "notify",
+ G_CALLBACK (tool_options_manager_paint_options_notify),
+ manager->global_paint_options);
+
+ g_signal_connect (manager->global_paint_options, "notify",
+ G_CALLBACK (tool_options_manager_paint_options_notify),
+ tool_info->tool_options);
+
+ tool_options_manager_copy_paint_props (manager->global_paint_options,
+ GIMP_PAINT_OPTIONS (tool_info->tool_options),
+ tool_info->context_props &
+ manager->global_props);
+ }
+ }
+
+ g_signal_connect (gimp->config, "notify::global-brush",
+ G_CALLBACK (tool_options_manager_global_notify),
+ manager);
+ g_signal_connect (gimp->config, "notify::global-dynamics",
+ G_CALLBACK (tool_options_manager_global_notify),
+ manager);
+ g_signal_connect (gimp->config, "notify::global-pattern",
+ G_CALLBACK (tool_options_manager_global_notify),
+ manager);
+ g_signal_connect (gimp->config, "notify::global-palette",
+ G_CALLBACK (tool_options_manager_global_notify),
+ manager);
+ g_signal_connect (gimp->config, "notify::global-gradient",
+ G_CALLBACK (tool_options_manager_global_notify),
+ manager);
+ g_signal_connect (gimp->config, "notify::global-font",
+ G_CALLBACK (tool_options_manager_global_notify),
+ manager);
+
+ g_signal_connect (user_context, "tool-changed",
+ G_CALLBACK (tool_options_manager_tool_changed),
+ manager);
+
+ tool_options_manager_tool_changed (user_context,
+ gimp_context_get_tool (user_context),
+ manager);
+}
+
+void
+gimp_tool_options_manager_exit (Gimp *gimp)
+{
+ GimpToolOptionsManager *manager;
+ GimpContext *user_context;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ manager = g_object_get_qdata (G_OBJECT (gimp), manager_quark);
+
+ g_return_if_fail (manager != NULL);
+
+ user_context = gimp_get_user_context (gimp);
+
+ g_signal_handlers_disconnect_by_func (user_context,
+ tool_options_manager_tool_changed,
+ manager);
+
+ g_signal_handlers_disconnect_by_func (gimp->config,
+ tool_options_manager_global_notify,
+ manager);
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = list->data;
+
+ gimp_context_set_parent (GIMP_CONTEXT (tool_info->tool_options), NULL);
+
+ if (GIMP_IS_PAINT_OPTIONS (tool_info->tool_options))
+ {
+ g_signal_handlers_disconnect_by_func (tool_info->tool_options,
+ tool_options_manager_paint_options_notify,
+ manager->global_paint_options);
+
+ g_signal_handlers_disconnect_by_func (manager->global_paint_options,
+ tool_options_manager_paint_options_notify,
+ tool_info->tool_options);
+ }
+ }
+
+ g_clear_object (&manager->global_paint_options);
+
+ g_slice_free (GimpToolOptionsManager, manager);
+
+ g_object_set_qdata (G_OBJECT (gimp), manager_quark, NULL);
+}
+
+
+/* private functions */
+
+static GimpContextPropMask
+tool_options_manager_get_global_props (GimpCoreConfig *config)
+{
+ GimpContextPropMask global_props = 0;
+
+ /* FG and BG are always shared between all tools */
+ global_props |= GIMP_CONTEXT_PROP_MASK_FOREGROUND;
+ global_props |= GIMP_CONTEXT_PROP_MASK_BACKGROUND;
+
+ if (config->global_brush)
+ global_props |= GIMP_CONTEXT_PROP_MASK_BRUSH;
+ if (config->global_dynamics)
+ global_props |= GIMP_CONTEXT_PROP_MASK_DYNAMICS;
+ if (config->global_pattern)
+ global_props |= GIMP_CONTEXT_PROP_MASK_PATTERN;
+ if (config->global_palette)
+ global_props |= GIMP_CONTEXT_PROP_MASK_PALETTE;
+ if (config->global_gradient)
+ global_props |= GIMP_CONTEXT_PROP_MASK_GRADIENT;
+ if (config->global_font)
+ global_props |= GIMP_CONTEXT_PROP_MASK_FONT;
+
+ return global_props;
+}
+
+static void
+tool_options_manager_global_notify (GimpCoreConfig *config,
+ const GParamSpec *pspec,
+ GimpToolOptionsManager *manager)
+{
+ GimpContextPropMask global_props;
+ GimpContextPropMask enabled_global_props;
+ GimpContextPropMask disabled_global_props;
+ GList *list;
+
+ global_props = tool_options_manager_get_global_props (config);
+
+ enabled_global_props = global_props & ~manager->global_props;
+ disabled_global_props = manager->global_props & ~global_props;
+
+ /* copy the newly enabled global props to all tool options, and
+ * disconnect the newly disabled ones from the user context
+ */
+ for (list = gimp_get_tool_info_iter (manager->gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = list->data;
+
+ /* don't change the active tool, it is always fully connected
+ * to the user_context anyway because we set its
+ * defined/undefined context props in tool_changed()
+ */
+ if (tool_info == manager->active_tool)
+ continue;
+
+ /* defining the newly disabled ones disconnects them from the
+ * parent user context
+ */
+ gimp_context_define_properties (GIMP_CONTEXT (tool_info->tool_options),
+ tool_info->context_props &
+ disabled_global_props,
+ TRUE);
+
+ /* undefining the newly enabled ones copies the value from the
+ * parent user context
+ */
+ gimp_context_define_properties (GIMP_CONTEXT (tool_info->tool_options),
+ tool_info->context_props &
+ enabled_global_props,
+ FALSE);
+
+ if (GIMP_IS_PAINT_OPTIONS (tool_info->tool_options))
+ tool_options_manager_copy_paint_props (manager->global_paint_options,
+ GIMP_PAINT_OPTIONS (tool_info->tool_options),
+ tool_info->context_props &
+ enabled_global_props);
+ }
+
+ manager->global_props = global_props;
+}
+
+static void
+tool_options_manager_paint_options_notify (GimpPaintOptions *src,
+ const GParamSpec *pspec,
+ GimpPaintOptions *dest)
+{
+ Gimp *gimp = GIMP_CONTEXT (src)->gimp;
+ GimpCoreConfig *config = gimp->config;
+ GimpToolOptionsManager *manager;
+ GimpToolInfo *tool_info;
+ GimpContextPropMask prop_mask = 0;
+ gboolean active = FALSE;
+
+ manager = g_object_get_qdata (G_OBJECT (gimp), manager_quark);
+
+ /* one of the options is the global one, the other is the tool's,
+ * get the tool_info from the tool's options
+ */
+ if (manager->global_paint_options == src)
+ tool_info = gimp_context_get_tool (GIMP_CONTEXT (dest));
+ else
+ tool_info = gimp_context_get_tool (GIMP_CONTEXT (src));
+
+ if (tool_info == manager->active_tool)
+ active = TRUE;
+
+ if ((active || config->global_brush) &&
+ tool_info->context_props & GIMP_CONTEXT_PROP_MASK_BRUSH)
+ {
+ prop_mask |= GIMP_CONTEXT_PROP_MASK_BRUSH;
+ }
+
+ if ((active || config->global_dynamics) &&
+ tool_info->context_props & GIMP_CONTEXT_PROP_MASK_DYNAMICS)
+ {
+ prop_mask |= GIMP_CONTEXT_PROP_MASK_DYNAMICS;
+ }
+
+ if ((active || config->global_gradient) &&
+ tool_info->context_props & GIMP_CONTEXT_PROP_MASK_GRADIENT)
+ {
+ prop_mask |= GIMP_CONTEXT_PROP_MASK_GRADIENT;
+ }
+
+ if (gimp_paint_options_is_prop (pspec->name, prop_mask))
+ {
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, pspec->value_type);
+
+ g_object_get_property (G_OBJECT (src), pspec->name, &value);
+
+ g_signal_handlers_block_by_func (dest,
+ tool_options_manager_paint_options_notify,
+ src);
+
+ g_object_set_property (G_OBJECT (dest), pspec->name, &value);
+
+ g_signal_handlers_unblock_by_func (dest,
+ tool_options_manager_paint_options_notify,
+ src);
+
+ g_value_unset (&value);
+ }
+}
+
+static void
+tool_options_manager_copy_paint_props (GimpPaintOptions *src,
+ GimpPaintOptions *dest,
+ GimpContextPropMask prop_mask)
+{
+ g_signal_handlers_block_by_func (dest,
+ tool_options_manager_paint_options_notify,
+ src);
+
+ gimp_paint_options_copy_props (src, dest, prop_mask);
+
+ g_signal_handlers_unblock_by_func (dest,
+ tool_options_manager_paint_options_notify,
+ src);
+}
+
+static void
+tool_options_manager_tool_changed (GimpContext *user_context,
+ GimpToolInfo *tool_info,
+ GimpToolOptionsManager *manager)
+{
+ if (tool_info == manager->active_tool)
+ return;
+
+ /* FIXME: gimp_busy HACK
+ * the tool manager will stop the emission, so simply return
+ */
+ if (user_context->gimp->busy)
+ return;
+
+ if (manager->active_tool)
+ {
+ GimpToolInfo *active = manager->active_tool;
+
+ /* disconnect the old active tool from all context properties
+ * it uses, but are not currently global
+ */
+ gimp_context_define_properties (GIMP_CONTEXT (active->tool_options),
+ active->context_props &
+ ~manager->global_props,
+ TRUE);
+ }
+
+ manager->active_tool = tool_info;
+
+ if (manager->active_tool)
+ {
+ GimpToolInfo *active = manager->active_tool;
+
+ /* make sure the tool options GUI always exists, this call
+ * creates it if needed, so tools always have their option GUI
+ * available even if the tool options dockable is not open, see
+ * for example issue #3435
+ */
+ gimp_tools_get_tool_options_gui (active->tool_options);
+
+ /* copy the new tool's context properties that are not
+ * currently global to the user context, so they get used by
+ * everything
+ */
+ gimp_context_copy_properties (GIMP_CONTEXT (active->tool_options),
+ gimp_get_user_context (manager->gimp),
+ active->context_props &
+ ~manager->global_props);
+
+ if (GIMP_IS_PAINT_OPTIONS (active->tool_options))
+ tool_options_manager_copy_paint_props (GIMP_PAINT_OPTIONS (active->tool_options),
+ manager->global_paint_options,
+ active->context_props &
+ ~manager->global_props);
+
+ /* then, undefine these properties so the tool syncs with the
+ * user context automatically
+ */
+ gimp_context_define_properties (GIMP_CONTEXT (active->tool_options),
+ active->context_props &
+ ~manager->global_props,
+ FALSE);
+ }
+}
diff --git a/app/tools/gimp-tool-options-manager.h b/app/tools/gimp-tool-options-manager.h
new file mode 100644
index 0000000..ea6f6ff
--- /dev/null
+++ b/app/tools/gimp-tool-options-manager.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-tool-options-manager.h
+ * Copyright (C) 2018 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_OPTIONS_MANAGER_H__
+#define __GIMP_TOOL_OPTIONS_MANAGER_H__
+
+
+void gimp_tool_options_manager_init (Gimp *gimp);
+void gimp_tool_options_manager_exit (Gimp *gimp);
+
+
+#endif /* __GIMP_TOOL_OPTIONS_MANAGER_H__ */
diff --git a/app/tools/gimp-tools.c b/app/tools/gimp-tools.c
new file mode 100644
index 0000000..7c35be9
--- /dev/null
+++ b/app/tools/gimp-tools.c
@@ -0,0 +1,831 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis and others
+ *
+ * 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-contexts.h"
+#include "core/gimp-internal-data.h"
+#include "core/gimpcontext.h"
+#include "core/gimplist.h"
+#include "core/gimptoolgroup.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimptooloptions.h"
+
+#include "gimp-tool-options-manager.h"
+#include "gimp-tools.h"
+#include "gimptooloptions-gui.h"
+#include "tool_manager.h"
+
+#include "gimpairbrushtool.h"
+#include "gimpaligntool.h"
+#include "gimpbrightnesscontrasttool.h"
+#include "gimpbucketfilltool.h"
+#include "gimpbycolorselecttool.h"
+#include "gimpcagetool.h"
+#include "gimpclonetool.h"
+#include "gimpcolorpickertool.h"
+#include "gimpconvolvetool.h"
+#include "gimpcroptool.h"
+#include "gimpcurvestool.h"
+#include "gimpdodgeburntool.h"
+#include "gimpellipseselecttool.h"
+#include "gimperasertool.h"
+#include "gimpfliptool.h"
+#include "gimpfreeselecttool.h"
+#include "gimpforegroundselecttool.h"
+#include "gimpfuzzyselecttool.h"
+#include "gimpgegltool.h"
+#include "gimpgradienttool.h"
+#include "gimphandletransformtool.h"
+#include "gimphealtool.h"
+#include "gimpinktool.h"
+#include "gimpiscissorstool.h"
+#include "gimplevelstool.h"
+#include "gimpoperationtool.h"
+#include "gimpmagnifytool.h"
+#include "gimpmeasuretool.h"
+#include "gimpmovetool.h"
+#include "gimpmybrushtool.h"
+#include "gimpnpointdeformationtool.h"
+#include "gimpoffsettool.h"
+#include "gimppaintbrushtool.h"
+#include "gimppenciltool.h"
+#include "gimpperspectiveclonetool.h"
+#include "gimpperspectivetool.h"
+#include "gimpthresholdtool.h"
+#include "gimprectangleselecttool.h"
+#include "gimprotatetool.h"
+#include "gimpseamlessclonetool.h"
+#include "gimpscaletool.h"
+#include "gimpsheartool.h"
+#include "gimpsmudgetool.h"
+#include "gimptexttool.h"
+#include "gimptransform3dtool.h"
+#include "gimpunifiedtransformtool.h"
+#include "gimpvectortool.h"
+#include "gimpwarptool.h"
+
+#include "gimp-intl.h"
+
+
+#define TOOL_RC_FILE_VERSION 1
+
+
+/* local function prototypes */
+
+static void gimp_tools_register (GType tool_type,
+ GType tool_options_type,
+ GimpToolOptionsGUIFunc options_gui_func,
+ GimpContextPropMask context_props,
+ const gchar *identifier,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *menu_label,
+ const gchar *menu_accel,
+ const gchar *help_domain,
+ const gchar *help_data,
+ const gchar *icon_name,
+ gpointer data);
+
+static void gimp_tools_copy_structure (Gimp *gimp,
+ GimpContainer *src_container,
+ GimpContainer *dest_container,
+ GHashTable *tools);
+
+/* private variables */
+
+static GBinding *toolbox_groups_binding = NULL;
+static gboolean tool_options_deleted = FALSE;
+
+
+/* public functions */
+
+void
+gimp_tools_init (Gimp *gimp)
+{
+ GimpToolRegisterFunc register_funcs[] =
+ {
+ /* selection tools */
+
+ gimp_rectangle_select_tool_register,
+ gimp_ellipse_select_tool_register,
+ gimp_free_select_tool_register,
+ gimp_fuzzy_select_tool_register,
+ gimp_by_color_select_tool_register,
+ gimp_iscissors_tool_register,
+ gimp_foreground_select_tool_register,
+
+ /* path tool */
+
+ gimp_vector_tool_register,
+
+ /* non-modifying tools */
+
+ gimp_color_picker_tool_register,
+ gimp_magnify_tool_register,
+ gimp_measure_tool_register,
+
+ /* transform tools */
+
+ gimp_move_tool_register,
+ gimp_align_tool_register,
+ gimp_crop_tool_register,
+ gimp_unified_transform_tool_register,
+ gimp_rotate_tool_register,
+ gimp_scale_tool_register,
+ gimp_shear_tool_register,
+ gimp_handle_transform_tool_register,
+ gimp_perspective_tool_register,
+ gimp_transform_3d_tool_register,
+ gimp_flip_tool_register,
+ gimp_cage_tool_register,
+ gimp_warp_tool_register,
+ gimp_n_point_deformation_tool_register,
+
+ /* paint tools */
+
+ gimp_seamless_clone_tool_register,
+ gimp_text_tool_register,
+ gimp_bucket_fill_tool_register,
+ gimp_gradient_tool_register,
+ gimp_pencil_tool_register,
+ gimp_paintbrush_tool_register,
+ gimp_eraser_tool_register,
+ gimp_airbrush_tool_register,
+ gimp_ink_tool_register,
+ gimp_mybrush_tool_register,
+ gimp_clone_tool_register,
+ gimp_heal_tool_register,
+ gimp_perspective_clone_tool_register,
+ gimp_convolve_tool_register,
+ gimp_smudge_tool_register,
+ gimp_dodge_burn_tool_register,
+
+ /* filter tools */
+
+ gimp_brightness_contrast_tool_register,
+ gimp_threshold_tool_register,
+ gimp_levels_tool_register,
+ gimp_curves_tool_register,
+ gimp_offset_tool_register,
+ gimp_gegl_tool_register,
+ gimp_operation_tool_register
+ };
+
+ gint i;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp_tool_options_create_folder ();
+
+ gimp_container_freeze (gimp->tool_info_list);
+
+ for (i = 0; i < G_N_ELEMENTS (register_funcs); i++)
+ {
+ register_funcs[i] (gimp_tools_register, gimp);
+ }
+
+ gimp_container_thaw (gimp->tool_info_list);
+
+ gimp_tool_options_manager_init (gimp);
+
+ tool_manager_init (gimp);
+
+ toolbox_groups_binding = g_object_bind_property (
+ gimp->config, "toolbox-groups",
+ gimp->tool_item_ui_list, "flat",
+ G_BINDING_INVERT_BOOLEAN |
+ G_BINDING_SYNC_CREATE);
+}
+
+void
+gimp_tools_exit (Gimp *gimp)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ g_clear_object (&toolbox_groups_binding);
+
+ tool_manager_exit (gimp);
+
+ gimp_tool_options_manager_exit (gimp);
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = list->data;
+
+ gimp_tools_set_tool_options_gui (tool_info->tool_options, NULL);
+ }
+}
+
+void
+gimp_tools_restore (Gimp *gimp)
+{
+ GimpObject *object;
+ GList *list;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* restore tool order */
+ gimp_tools_reset (gimp, gimp->tool_item_list, TRUE);
+
+ /* make the generic operation tool invisible by default */
+ object = gimp_container_get_child_by_name (gimp->tool_info_list,
+ "gimp-operation-tool");
+ if (object)
+ g_object_set (object, "visible", FALSE, NULL);
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (list->data);
+
+ /* get default values from prefs (see bug #120832) */
+ gimp_config_reset (GIMP_CONFIG (tool_info->tool_options));
+ }
+
+ if (! gimp_contexts_load (gimp, &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_WARNING, error->message);
+ g_clear_error (&error);
+ }
+
+ if (! gimp_internal_data_load (gimp, &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_WARNING, error->message);
+ g_clear_error (&error);
+ }
+
+ /* make sure there is always a tool active, so broken config files
+ * can't leave us with no initial tool
+ */
+ if (! gimp_context_get_tool (gimp_get_user_context (gimp)))
+ {
+ gimp_context_set_tool (gimp_get_user_context (gimp),
+ gimp_get_tool_info_iter (gimp)->data);
+ }
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (list->data);
+ GimpToolOptionsGUIFunc options_gui_func;
+
+ /* copy all context properties except those the tool actually
+ * uses, because the subsequent deserialize() on the tool
+ * options will only set the properties that were set to
+ * non-default values at the time of saving, and we want to
+ * keep these default values as if they have been saved.
+ * (see bug #541586).
+ */
+ gimp_context_copy_properties (gimp_get_user_context (gimp),
+ GIMP_CONTEXT (tool_info->tool_options),
+ GIMP_CONTEXT_PROP_MASK_ALL &~
+ (tool_info->context_props |
+ GIMP_CONTEXT_PROP_MASK_TOOL |
+ GIMP_CONTEXT_PROP_MASK_PAINT_INFO));
+
+ gimp_tool_options_deserialize (tool_info->tool_options, NULL);
+
+ options_gui_func = g_object_get_data (G_OBJECT (tool_info),
+ "gimp-tool-options-gui-func");
+
+ if (! options_gui_func)
+ options_gui_func = gimp_tool_options_empty_gui;
+
+ gimp_tools_set_tool_options_gui_func (tool_info->tool_options,
+ options_gui_func);
+ }
+}
+
+void
+gimp_tools_save (Gimp *gimp,
+ gboolean save_tool_options,
+ gboolean always_save)
+{
+ GimpConfigWriter *writer;
+ GFile *file;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (save_tool_options && (! tool_options_deleted || always_save))
+ {
+ GList *list;
+ GError *error = NULL;
+
+ if (! gimp_contexts_save (gimp, &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_WARNING,
+ error->message);
+ g_clear_error (&error);
+ }
+
+ if (! gimp_internal_data_save (gimp, &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_WARNING,
+ error->message);
+ g_clear_error (&error);
+ }
+
+ gimp_tool_options_create_folder ();
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (list->data);
+
+ gimp_tool_options_serialize (tool_info->tool_options, NULL);
+ }
+ }
+
+ file = gimp_directory_file ("toolrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ writer = gimp_config_writer_new_gfile (file, TRUE, "GIMP toolrc", NULL);
+
+ if (writer)
+ {
+ gimp_tools_serialize (gimp, gimp->tool_item_list, writer);
+
+ gimp_config_writer_finish (writer, "end of toolrc", NULL);
+ }
+
+ g_object_unref (file);
+}
+
+gboolean
+gimp_tools_clear (Gimp *gimp,
+ GError **error)
+{
+ GList *list;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list && success;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (list->data);
+
+ success = gimp_tool_options_delete (tool_info->tool_options, NULL);
+ }
+
+ if (success)
+ success = gimp_contexts_clear (gimp, error);
+
+ if (success)
+ success = gimp_internal_data_clear (gimp, error);
+
+ if (success)
+ tool_options_deleted = TRUE;
+
+ return success;
+}
+
+gboolean
+gimp_tools_serialize (Gimp *gimp,
+ GimpContainer *container,
+ GimpConfigWriter *writer)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+
+ gimp_config_writer_open (writer, "file-version");
+ gimp_config_writer_printf (writer, "%d", TOOL_RC_FILE_VERSION);
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_linefeed (writer);
+
+ return gimp_config_serialize (GIMP_CONFIG (container), writer, NULL);
+}
+
+gboolean
+gimp_tools_deserialize (Gimp *gimp,
+ GimpContainer *container,
+ GScanner *scanner)
+{
+ enum
+ {
+ FILE_VERSION = 1
+ };
+
+ GimpContainer *src_container;
+ GTokenType token;
+ guint scope_id;
+ guint old_scope_id;
+ gint file_version = 0;
+ gboolean result = FALSE;
+
+ scope_id = g_type_qname (GIMP_TYPE_TOOL_GROUP);
+ old_scope_id = g_scanner_set_scope (scanner, scope_id);
+
+ g_scanner_scope_add_symbol (scanner, scope_id,
+ "file-version",
+ GINT_TO_POINTER (FILE_VERSION));
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token &&
+ (token != G_TOKEN_LEFT_PAREN ||
+ ! file_version))
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_SYMBOL;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT (scanner->value.v_symbol))
+ {
+ case FILE_VERSION:
+ token = G_TOKEN_INT;
+ if (gimp_scanner_parse_int (scanner, &file_version))
+ token = G_TOKEN_RIGHT_PAREN;
+ break;
+ }
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ g_scanner_set_scope (scanner, old_scope_id);
+
+ if (token != G_TOKEN_LEFT_PAREN)
+ {
+ g_scanner_get_next_token (scanner);
+ g_scanner_unexp_token (scanner, token, NULL, NULL, NULL,
+ _("fatal parse error"), TRUE);
+
+ return FALSE;
+ }
+ else if (file_version != TOOL_RC_FILE_VERSION)
+ {
+ g_scanner_error (scanner, "wrong toolrc file format version");
+
+ return FALSE;
+ }
+
+ gimp_container_freeze (container);
+
+ /* make sure the various GimpToolItem types are registered */
+ g_type_class_unref (g_type_class_ref (GIMP_TYPE_TOOL_GROUP));
+ g_type_class_unref (g_type_class_ref (GIMP_TYPE_TOOL_INFO));
+
+ gimp_container_clear (container);
+
+ src_container = g_object_new (GIMP_TYPE_LIST,
+ "children-type", GIMP_TYPE_TOOL_ITEM,
+ "append", TRUE,
+ NULL);
+
+ if (gimp_config_deserialize (GIMP_CONFIG (src_container),
+ scanner, 0, NULL))
+ {
+ GHashTable *tools;
+ GList *list;
+
+ result = TRUE;
+
+ tools = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ gimp_tools_copy_structure (gimp, src_container, container, tools);
+
+ for (list = gimp_get_tool_info_iter (gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *tool_info = list->data;
+
+ if (! tool_info->hidden && ! g_hash_table_contains (tools, tool_info))
+ {
+ if (tool_info->experimental)
+ {
+ /* if an experimental tool is not in the file, just add it to
+ * the tool-item list.
+ */
+ gimp_container_add (container, GIMP_OBJECT (tool_info));
+ }
+ else
+ {
+ /* otherwise, it means we added a new stable tool. this must
+ * be the user toolrc file; rejct it, so that we fall back to
+ * the default toolrc file, which should contain the missing
+ * tool.
+ */
+ g_scanner_error (scanner, "missing tools in toolrc file");
+
+ result = FALSE;
+
+ break;
+ }
+ }
+ }
+
+ g_hash_table_unref (tools);
+ }
+
+ g_object_unref (src_container);
+
+ gimp_container_thaw (container);
+
+ return result;
+}
+
+void
+gimp_tools_reset (Gimp *gimp,
+ GimpContainer *container,
+ gboolean user_toolrc)
+{
+ GList *files = NULL;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ if (user_toolrc)
+ files = g_list_prepend (files, gimp_directory_file ("toolrc", NULL));
+ files = g_list_prepend (files, gimp_sysconf_directory_file ("toolrc", NULL));
+
+ files = g_list_reverse (files);
+
+ gimp_container_freeze (container);
+
+ gimp_container_clear (container);
+
+ for (list = files; list; list = g_list_next (list))
+ {
+ GScanner *scanner;
+ GFile *file = list->data;
+ GError *error = NULL;
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ scanner = gimp_scanner_new_gfile (file, &error);
+
+ if (scanner && gimp_tools_deserialize (gimp, container, scanner))
+ {
+ gimp_scanner_destroy (scanner);
+
+ break;
+ }
+ else
+ {
+ if (error->code != G_IO_ERROR_NOT_FOUND)
+ {
+ gimp_message_literal (gimp, NULL,
+ GIMP_MESSAGE_WARNING, error->message);
+ }
+
+ g_clear_error (&error);
+
+ gimp_container_clear (container);
+ }
+
+ g_clear_pointer (&scanner, gimp_scanner_destroy);
+ }
+
+ g_list_free_full (files, g_object_unref);
+
+ if (gimp_container_is_empty (container))
+ {
+ if (gimp->be_verbose)
+ g_print ("Using default tool order\n");
+
+ gimp_tools_copy_structure (gimp, gimp->tool_info_list, container, NULL);
+ }
+
+ gimp_container_thaw (container);
+}
+
+
+/* private functions */
+
+static void
+gimp_tools_register (GType tool_type,
+ GType tool_options_type,
+ GimpToolOptionsGUIFunc options_gui_func,
+ GimpContextPropMask context_props,
+ const gchar *identifier,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *menu_label,
+ const gchar *menu_accel,
+ const gchar *help_domain,
+ const gchar *help_data,
+ const gchar *icon_name,
+ gpointer data)
+{
+ Gimp *gimp = (Gimp *) data;
+ GimpToolInfo *tool_info;
+ const gchar *paint_core_name;
+ gboolean visible;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (g_type_is_a (tool_type, GIMP_TYPE_TOOL));
+ g_return_if_fail (tool_options_type == G_TYPE_NONE ||
+ g_type_is_a (tool_options_type, GIMP_TYPE_TOOL_OPTIONS));
+
+ if (tool_options_type == G_TYPE_NONE)
+ tool_options_type = GIMP_TYPE_TOOL_OPTIONS;
+
+ if (tool_type == GIMP_TYPE_PENCIL_TOOL)
+ {
+ paint_core_name = "gimp-pencil";
+ }
+ else if (tool_type == GIMP_TYPE_PAINTBRUSH_TOOL)
+ {
+ paint_core_name = "gimp-paintbrush";
+ }
+ else if (tool_type == GIMP_TYPE_ERASER_TOOL)
+ {
+ paint_core_name = "gimp-eraser";
+ }
+ else if (tool_type == GIMP_TYPE_AIRBRUSH_TOOL)
+ {
+ paint_core_name = "gimp-airbrush";
+ }
+ else if (tool_type == GIMP_TYPE_CLONE_TOOL)
+ {
+ paint_core_name = "gimp-clone";
+ }
+ else if (tool_type == GIMP_TYPE_HEAL_TOOL)
+ {
+ paint_core_name = "gimp-heal";
+ }
+ else if (tool_type == GIMP_TYPE_PERSPECTIVE_CLONE_TOOL)
+ {
+ paint_core_name = "gimp-perspective-clone";
+ }
+ else if (tool_type == GIMP_TYPE_CONVOLVE_TOOL)
+ {
+ paint_core_name = "gimp-convolve";
+ }
+ else if (tool_type == GIMP_TYPE_SMUDGE_TOOL)
+ {
+ paint_core_name = "gimp-smudge";
+ }
+ else if (tool_type == GIMP_TYPE_DODGE_BURN_TOOL)
+ {
+ paint_core_name = "gimp-dodge-burn";
+ }
+ else if (tool_type == GIMP_TYPE_INK_TOOL)
+ {
+ paint_core_name = "gimp-ink";
+ }
+ else if (tool_type == GIMP_TYPE_MYBRUSH_TOOL)
+ {
+ paint_core_name = "gimp-mybrush";
+ }
+ else
+ {
+ paint_core_name = "gimp-paintbrush";
+ }
+
+ tool_info = gimp_tool_info_new (gimp,
+ tool_type,
+ tool_options_type,
+ context_props,
+ identifier,
+ label,
+ tooltip,
+ menu_label,
+ menu_accel,
+ help_domain,
+ help_data,
+ paint_core_name,
+ icon_name);
+
+ visible = (! g_type_is_a (tool_type, GIMP_TYPE_FILTER_TOOL));
+
+ gimp_tool_item_set_visible (GIMP_TOOL_ITEM (tool_info), visible);
+
+ /* hack to hide the operation tool entirely */
+ if (tool_type == GIMP_TYPE_OPERATION_TOOL)
+ tool_info->hidden = TRUE;
+
+ /* hack to not require experimental tools to be present in toolrc */
+ if (tool_type == GIMP_TYPE_N_POINT_DEFORMATION_TOOL ||
+ tool_type == GIMP_TYPE_SEAMLESS_CLONE_TOOL)
+ {
+ tool_info->experimental = TRUE;
+ }
+
+ g_object_set_data (G_OBJECT (tool_info), "gimp-tool-options-gui-func",
+ options_gui_func);
+
+ gimp_container_add (gimp->tool_info_list, GIMP_OBJECT (tool_info));
+ g_object_unref (tool_info);
+
+ if (tool_type == GIMP_TYPE_PAINTBRUSH_TOOL)
+ gimp_tool_info_set_standard (gimp, tool_info);
+}
+
+static void
+gimp_tools_copy_structure (Gimp *gimp,
+ GimpContainer *src_container,
+ GimpContainer *dest_container,
+ GHashTable *tools)
+{
+ GList *list;
+
+ for (list = GIMP_LIST (src_container)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolItem *src_tool_item = list->data;
+ GimpToolItem *dest_tool_item = NULL;
+
+ if (GIMP_IS_TOOL_GROUP (src_tool_item))
+ {
+ dest_tool_item = GIMP_TOOL_ITEM (gimp_tool_group_new ());
+
+ gimp_tools_copy_structure (
+ gimp,
+ gimp_viewable_get_children (GIMP_VIEWABLE (src_tool_item)),
+ gimp_viewable_get_children (GIMP_VIEWABLE (dest_tool_item)),
+ tools);
+
+ gimp_tool_group_set_active_tool (
+ GIMP_TOOL_GROUP (dest_tool_item),
+ gimp_tool_group_get_active_tool (GIMP_TOOL_GROUP (src_tool_item)));
+ }
+ else
+ {
+ dest_tool_item = GIMP_TOOL_ITEM (
+ gimp_get_tool_info (gimp, gimp_object_get_name (src_tool_item)));
+
+ if (dest_tool_item)
+ {
+ if (! GIMP_TOOL_INFO (dest_tool_item)->hidden)
+ {
+ g_object_ref (dest_tool_item);
+
+ if (tools)
+ g_hash_table_add (tools, dest_tool_item);
+ }
+ else
+ {
+ dest_tool_item = NULL;
+ }
+ }
+ }
+
+ if (dest_tool_item)
+ {
+ gimp_tool_item_set_visible (
+ dest_tool_item,
+ gimp_tool_item_get_visible (src_tool_item));
+
+ gimp_container_add (dest_container,
+ GIMP_OBJECT (dest_tool_item));
+
+ g_object_unref (dest_tool_item);
+ }
+ }
+}
diff --git a/app/tools/gimp-tools.h b/app/tools/gimp-tools.h
new file mode 100644
index 0000000..5be61c3
--- /dev/null
+++ b/app/tools/gimp-tools.h
@@ -0,0 +1,45 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis and others
+ *
+ * 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_TOOLS_H__
+#define __GIMP_TOOLS_H__
+
+
+void gimp_tools_init (Gimp *gimp);
+void gimp_tools_exit (Gimp *gimp);
+
+void gimp_tools_restore (Gimp *gimp);
+void gimp_tools_save (Gimp *gimp,
+ gboolean save_tool_options,
+ gboolean always_save);
+
+gboolean gimp_tools_clear (Gimp *gimp,
+ GError **error);
+
+gboolean gimp_tools_serialize (Gimp *gimp,
+ GimpContainer *container,
+ GimpConfigWriter *writer);
+gboolean gimp_tools_deserialize (Gimp *gimp,
+ GimpContainer *container,
+ GScanner *scanner);
+
+void gimp_tools_reset (Gimp *gimp,
+ GimpContainer *container,
+ gboolean user_toolrc);
+
+
+#endif /* __GIMP_TOOLS_H__ */
diff --git a/app/tools/gimpairbrushtool.c b/app/tools/gimpairbrushtool.c
new file mode 100644
index 0000000..111c897
--- /dev/null
+++ b/app/tools/gimpairbrushtool.c
@@ -0,0 +1,150 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimpairbrush.h"
+#include "paint/gimpairbrushoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+
+#include "gimpairbrushtool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimppainttool-paint.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_airbrush_tool_constructed (GObject *object);
+
+static void gimp_airbrush_tool_airbrush_stamp (GimpAirbrush *airbrush,
+ GimpAirbrushTool *airbrush_tool);
+
+static void gimp_airbrush_tool_stamp (GimpAirbrushTool *airbrush_tool,
+ gpointer data);
+
+static GtkWidget * gimp_airbrush_options_gui (GimpToolOptions *tool_options);
+
+
+G_DEFINE_TYPE (GimpAirbrushTool, gimp_airbrush_tool, GIMP_TYPE_PAINTBRUSH_TOOL)
+
+#define parent_class gimp_airbrush_tool_parent_class
+
+
+void
+gimp_airbrush_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_AIRBRUSH_TOOL,
+ GIMP_TYPE_AIRBRUSH_OPTIONS,
+ gimp_airbrush_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT,
+ "gimp-airbrush-tool",
+ _("Airbrush"),
+ _("Airbrush Tool: Paint using a brush, with variable pressure"),
+ N_("_Airbrush"), "A",
+ NULL, GIMP_HELP_TOOL_AIRBRUSH,
+ GIMP_ICON_TOOL_AIRBRUSH,
+ data);
+}
+
+static void
+gimp_airbrush_tool_class_init (GimpAirbrushToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_airbrush_tool_constructed;
+}
+
+static void
+gimp_airbrush_tool_init (GimpAirbrushTool *airbrush)
+{
+ GimpTool *tool = GIMP_TOOL (airbrush);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_AIRBRUSH);
+}
+
+static void
+gimp_airbrush_tool_constructed (GObject *object)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ g_signal_connect_object (paint_tool->core, "stamp",
+ G_CALLBACK (gimp_airbrush_tool_airbrush_stamp),
+ object, 0);
+}
+
+static void
+gimp_airbrush_tool_airbrush_stamp (GimpAirbrush *airbrush,
+ GimpAirbrushTool *airbrush_tool)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (airbrush_tool);
+
+ gimp_paint_tool_paint_push (
+ paint_tool,
+ (GimpPaintToolPaintFunc) gimp_airbrush_tool_stamp,
+ NULL);
+}
+
+static void
+gimp_airbrush_tool_stamp (GimpAirbrushTool *airbrush_tool,
+ gpointer data)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (airbrush_tool);
+
+ gimp_airbrush_stamp (GIMP_AIRBRUSH (paint_tool->core));
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_airbrush_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *scale;
+
+ button = gimp_prop_check_button_new (config, "motion-only", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ scale = gimp_prop_spin_scale_new (config, "rate", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ scale = gimp_prop_spin_scale_new (config, "flow", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return vbox;
+}
diff --git a/app/tools/gimpairbrushtool.h b/app/tools/gimpairbrushtool.h
new file mode 100644
index 0000000..a23321b
--- /dev/null
+++ b/app/tools/gimpairbrushtool.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 __GIMP_AIRBRUSH_TOOL_H__
+#define __GIMP_AIRBRUSH_TOOL_H__
+
+
+#include "gimppaintbrushtool.h"
+
+
+#define GIMP_TYPE_AIRBRUSH_TOOL (gimp_airbrush_tool_get_type ())
+#define GIMP_AIRBRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_AIRBRUSH_TOOL, GimpAirbrushTool))
+#define GIMP_AIRBRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_AIRBRUSH_TOOL, GimpAirbrushToolClass))
+#define GIMP_IS_AIRBRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_AIRBRUSH_TOOL))
+#define GIMP_IS_AIRBRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_AIRBRUSH_TOOL))
+#define GIMP_AIRBRUSH_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_AIRBRUSH_TOOL, GimpAirbrushToolClass))
+
+
+typedef struct _GimpAirbrushTool GimpAirbrushTool;
+typedef struct _GimpAirbrushToolClass GimpAirbrushToolClass;
+
+struct _GimpAirbrushTool
+{
+ GimpPaintbrushTool parent_instance;
+};
+
+struct _GimpAirbrushToolClass
+{
+ GimpPaintbrushToolClass parent_class;
+};
+
+
+void gimp_airbrush_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_airbrush_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_AIRBRUSH_TOOL_H__ */
diff --git a/app/tools/gimpalignoptions.c b/app/tools/gimpalignoptions.c
new file mode 100644
index 0000000..2564a2a
--- /dev/null
+++ b/app/tools/gimpalignoptions.c
@@ -0,0 +1,403 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpmarshal.h"
+
+#include "gimpalignoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ ALIGN_BUTTON_CLICKED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_ALIGN_REFERENCE,
+ PROP_OFFSET_X,
+ PROP_OFFSET_Y
+};
+
+
+static void gimp_align_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_align_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpAlignOptions, gimp_align_options, GIMP_TYPE_TOOL_OPTIONS)
+
+#define parent_class gimp_selection_options_parent_class
+
+static guint align_options_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_align_options_class_init (GimpAlignOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_align_options_set_property;
+ object_class->get_property = gimp_align_options_get_property;
+
+ klass->align_button_clicked = NULL;
+
+ align_options_signals[ALIGN_BUTTON_CLICKED] =
+ g_signal_new ("align-button-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpAlignOptionsClass,
+ align_button_clicked),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_ALIGNMENT_TYPE);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ALIGN_REFERENCE,
+ "align-reference",
+ _("Relative to"),
+ _("Reference image object a layer will be aligned on"),
+ GIMP_TYPE_ALIGN_REFERENCE_TYPE,
+ GIMP_ALIGN_REFERENCE_FIRST,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OFFSET_X,
+ "offset-x",
+ _("Offset"),
+ _("Horizontal offset for distribution"),
+ -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OFFSET_Y,
+ "offset-y",
+ _("Offset"),
+ _("Vertical offset for distribution"),
+ -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_align_options_init (GimpAlignOptions *options)
+{
+}
+
+static void
+gimp_align_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAlignOptions *options = GIMP_ALIGN_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_ALIGN_REFERENCE:
+ options->align_reference = g_value_get_enum (value);
+ break;
+
+ case PROP_OFFSET_X:
+ options->offset_x = g_value_get_double (value);
+ break;
+
+ case PROP_OFFSET_Y:
+ options->offset_y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_align_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAlignOptions *options = GIMP_ALIGN_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_ALIGN_REFERENCE:
+ g_value_set_enum (value, options->align_reference);
+ break;
+
+ case PROP_OFFSET_X:
+ g_value_set_double (value, options->offset_x);
+ break;
+
+ case PROP_OFFSET_Y:
+ g_value_set_double (value, options->offset_y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_align_options_button_clicked (GtkButton *button,
+ GimpAlignOptions *options)
+{
+ GimpAlignmentType action;
+
+ action = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "align-action"));
+
+ g_signal_emit (options, align_options_signals[ALIGN_BUTTON_CLICKED], 0,
+ action);
+}
+
+static GtkWidget *
+gimp_align_options_button_new (GimpAlignOptions *options,
+ GimpAlignmentType action,
+ GtkWidget *parent,
+ const gchar *tooltip)
+{
+ GtkWidget *button;
+ GtkWidget *image;
+ const gchar *icon_name = NULL;
+
+ switch (action)
+ {
+ case GIMP_ALIGN_LEFT:
+ icon_name = GIMP_ICON_GRAVITY_WEST;
+ break;
+ case GIMP_ALIGN_HCENTER:
+ icon_name = GIMP_ICON_CENTER_HORIZONTAL;
+ break;
+ case GIMP_ALIGN_RIGHT:
+ icon_name = GIMP_ICON_GRAVITY_EAST;
+ break;
+ case GIMP_ALIGN_TOP:
+ icon_name = GIMP_ICON_GRAVITY_NORTH;
+ break;
+ case GIMP_ALIGN_VCENTER:
+ icon_name = GIMP_ICON_CENTER_VERTICAL;
+ break;
+ case GIMP_ALIGN_BOTTOM:
+ icon_name = GIMP_ICON_GRAVITY_SOUTH;
+ break;
+ case GIMP_ARRANGE_LEFT:
+ icon_name = GIMP_ICON_GRAVITY_WEST;
+ break;
+ case GIMP_ARRANGE_HCENTER:
+ icon_name = GIMP_ICON_CENTER_HORIZONTAL;
+ break;
+ case GIMP_ARRANGE_RIGHT:
+ icon_name = GIMP_ICON_GRAVITY_EAST;
+ break;
+ case GIMP_ARRANGE_TOP:
+ icon_name = GIMP_ICON_GRAVITY_NORTH;
+ break;
+ case GIMP_ARRANGE_VCENTER:
+ icon_name = GIMP_ICON_CENTER_VERTICAL;
+ break;
+ case GIMP_ARRANGE_BOTTOM:
+ icon_name = GIMP_ICON_GRAVITY_SOUTH;
+ break;
+ case GIMP_ARRANGE_HFILL:
+ icon_name = GIMP_ICON_FILL_HORIZONTAL;
+ break;
+ case GIMP_ARRANGE_VFILL:
+ icon_name = GIMP_ICON_FILL_VERTICAL;
+ break;
+ default:
+ g_return_val_if_reached (NULL);
+ break;
+ }
+
+ button = gtk_button_new ();
+ gtk_widget_set_sensitive (button, FALSE);
+ gtk_widget_show (button);
+
+ image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show (image);
+
+ gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (button, tooltip, NULL);
+
+ g_object_set_data (G_OBJECT (button), "align-action",
+ GINT_TO_POINTER (action));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_align_options_button_clicked),
+ options);
+
+ return button;
+}
+
+GtkWidget *
+gimp_align_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpAlignOptions *options = GIMP_ALIGN_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *align_vbox;
+ GtkWidget *hbox;
+ GtkWidget *frame;
+ GtkWidget *label;
+ GtkWidget *spinbutton;
+ GtkWidget *combo;
+ gint n = 0;
+
+ frame = gimp_frame_new (_("Align"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ align_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (frame), align_vbox);
+ gtk_widget_show (align_vbox);
+
+ combo = gimp_prop_enum_combo_box_new (config, "align-reference", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Relative to"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (align_vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (align_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ALIGN_LEFT, hbox,
+ _("Align left edge of target"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ALIGN_HCENTER, hbox,
+ _("Align center of target"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ALIGN_RIGHT, hbox,
+ _("Align right edge of target"));
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (align_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ALIGN_TOP, hbox,
+ _("Align top edge of target"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ALIGN_VCENTER, hbox,
+ _("Align middle of target"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ALIGN_BOTTOM, hbox,
+ _("Align bottom of target"));
+
+ frame = gimp_frame_new (_("Distribute"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ align_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (frame), align_vbox);
+ gtk_widget_show (align_vbox);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (align_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_LEFT, hbox,
+ _("Distribute left edges of targets"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_HCENTER, hbox,
+ _("Distribute horizontal centers of targets"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_RIGHT, hbox,
+ _("Distribute right edges of targets"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_HFILL, hbox,
+ _("Distribute targets evenly in the horizontal"));
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (align_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_TOP, hbox,
+ _("Distribute top edges of targets"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_VCENTER, hbox,
+ _("Distribute vertical centers of targets"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_BOTTOM, hbox,
+ _("Distribute bottoms of targets"));
+
+ options->button[n++] =
+ gimp_align_options_button_new (options, GIMP_ARRANGE_VFILL, hbox,
+ _("Distribute targets evenly in the vertical"));
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (align_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new (_("Offset X:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ spinbutton = gimp_prop_spin_button_new (config, "offset-x",
+ 1, 20, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (align_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new (_("Offset Y:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ spinbutton = gimp_prop_spin_button_new (config, "offset-y",
+ 1, 20, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+
+ return vbox;
+}
diff --git a/app/tools/gimpalignoptions.h b/app/tools/gimpalignoptions.h
new file mode 100644
index 0000000..5d4fe04
--- /dev/null
+++ b/app/tools/gimpalignoptions.h
@@ -0,0 +1,64 @@
+/* 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_ALIGN_OPTIONS_H__
+#define __GIMP_ALIGN_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define ALIGN_OPTIONS_N_BUTTONS 14
+
+
+#define GIMP_TYPE_ALIGN_OPTIONS (gimp_align_options_get_type ())
+#define GIMP_ALIGN_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ALIGN_OPTIONS, GimpAlignOptions))
+#define GIMP_ALIGN_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ALIGN_OPTIONS, GimpAlignOptionsClass))
+#define GIMP_IS_ALIGN_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ALIGN_OPTIONS))
+#define GIMP_IS_ALIGN_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ALIGN_OPTIONS))
+#define GIMP_ALIGN_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ALIGN_OPTIONS, GimpAlignOptionsClass))
+
+
+typedef struct _GimpAlignOptions GimpAlignOptions;
+typedef struct _GimpAlignOptionsClass GimpAlignOptionsClass;
+
+struct _GimpAlignOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpAlignReferenceType align_reference;
+ gdouble offset_x;
+ gdouble offset_y;
+
+ GtkWidget *button[ALIGN_OPTIONS_N_BUTTONS];
+};
+
+struct _GimpAlignOptionsClass
+{
+ GimpToolOptionsClass parent_class;
+
+ void (* align_button_clicked) (GimpAlignOptions *options,
+ GimpAlignmentType align_type);
+};
+
+
+GType gimp_align_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_align_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_ALIGN_OPTIONS_H__ */
diff --git a/app/tools/gimpaligntool.c b/app/tools/gimpaligntool.c
new file mode 100644
index 0000000..4698993
--- /dev/null
+++ b/app/tools/gimpaligntool.c
@@ -0,0 +1,824 @@
+/* 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 "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-arrange.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimplayer.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+
+#include "gimpalignoptions.h"
+#include "gimpaligntool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 3 /* move distance after which we do a box-select */
+
+
+/* local function prototypes */
+
+static void gimp_align_tool_constructed (GObject *object);
+
+static void gimp_align_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_align_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_align_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_align_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_align_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_align_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_align_tool_status_update (GimpTool *tool,
+ GimpDisplay *display,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_align_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_align_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_align_tool_align (GimpAlignTool *align_tool,
+ GimpAlignmentType align_type);
+
+static void gimp_align_tool_object_removed (GObject *object,
+ GimpAlignTool *align_tool);
+static void gimp_align_tool_clear_selected (GimpAlignTool *align_tool);
+
+
+G_DEFINE_TYPE (GimpAlignTool, gimp_align_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_align_tool_parent_class
+
+
+void
+gimp_align_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_ALIGN_TOOL,
+ GIMP_TYPE_ALIGN_OPTIONS,
+ gimp_align_options_gui,
+ 0,
+ "gimp-align-tool",
+ _("Alignment"),
+ _("Alignment Tool: Align or arrange layers and other objects"),
+ N_("_Align"), "Q",
+ NULL, GIMP_HELP_TOOL_ALIGN,
+ GIMP_ICON_TOOL_ALIGN,
+ data);
+}
+
+static void
+gimp_align_tool_class_init (GimpAlignToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_align_tool_constructed;
+
+ tool_class->control = gimp_align_tool_control;
+ tool_class->button_press = gimp_align_tool_button_press;
+ tool_class->button_release = gimp_align_tool_button_release;
+ tool_class->motion = gimp_align_tool_motion;
+ tool_class->key_press = gimp_align_tool_key_press;
+ tool_class->oper_update = gimp_align_tool_oper_update;
+ tool_class->cursor_update = gimp_align_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_align_tool_draw;
+}
+
+static void
+gimp_align_tool_init (GimpAlignTool *align_tool)
+{
+ GimpTool *tool = GIMP_TOOL (align_tool);
+
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER);
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_MOVE);
+
+ align_tool->function = ALIGN_TOOL_IDLE;
+}
+
+static void
+gimp_align_tool_constructed (GObject *object)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (object);
+ GimpAlignOptions *options;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ options = GIMP_ALIGN_TOOL_GET_OPTIONS (align_tool);
+
+ g_signal_connect_object (options, "align-button-clicked",
+ G_CALLBACK (gimp_align_tool_align),
+ align_tool, G_CONNECT_SWAPPED);
+}
+
+static void
+gimp_align_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_align_tool_clear_selected (align_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_align_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ /* If the tool was being used in another image... reset it */
+ if (display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ tool->display = display;
+
+ gimp_tool_control_activate (tool->control);
+
+ align_tool->x2 = align_tool->x1 = coords->x;
+ align_tool->y2 = align_tool->y1 = coords->y;
+
+ if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+/* some rather complex logic here. If the user clicks without modifiers,
+ * then we start a new list, and use the first object in it as reference.
+ * If the user clicks using Shift, or draws a rubber-band box, then
+ * we add objects to the list, but do not specify which one should
+ * be used as reference.
+ */
+static void
+gimp_align_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+ GimpAlignOptions *options = GIMP_ALIGN_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GObject *object = NULL;
+ GimpImage *image = gimp_display_get_image (display);
+ GdkModifierType extend_mask;
+ gint i;
+
+ extend_mask = gimp_get_extend_selection_mask ();
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_control_halt (tool->control);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ align_tool->x2 = align_tool->x1;
+ align_tool->y2 = align_tool->y1;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ return;
+ }
+
+ if (! (state & extend_mask)) /* start a new list */
+ {
+ gimp_align_tool_clear_selected (align_tool);
+ align_tool->set_reference = FALSE;
+ }
+
+ /* if mouse has moved less than EPSILON pixels since button press,
+ * select the nearest thing, otherwise make a rubber-band rectangle
+ */
+ if (hypot (coords->x - align_tool->x1,
+ coords->y - align_tool->y1) < EPSILON)
+ {
+ GimpVectors *vectors;
+ GimpGuide *guide;
+ GimpLayer *layer;
+ gint snap_distance = display->config->snap_distance;
+
+ if ((vectors = gimp_image_pick_vectors (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance))))
+ {
+ object = G_OBJECT (vectors);
+ }
+ else if (gimp_display_shell_get_show_guides (shell) &&
+ (guide = gimp_image_pick_guide (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance))))
+ {
+ object = G_OBJECT (guide);
+ }
+ else if ((layer = gimp_image_pick_layer_by_bounds (image,
+ coords->x, coords->y)))
+ {
+ object = G_OBJECT (layer);
+ }
+
+ if (object)
+ {
+ if (! g_list_find (align_tool->selected_objects, object))
+ {
+ align_tool->selected_objects =
+ g_list_append (align_tool->selected_objects, object);
+
+ g_signal_connect (object, "removed",
+ G_CALLBACK (gimp_align_tool_object_removed),
+ align_tool);
+
+ /* if an object has been selected using unmodified click,
+ * it should be used as the reference
+ */
+ if (! (state & extend_mask))
+ align_tool->set_reference = TRUE;
+ }
+ }
+ }
+ else /* FIXME: look for vectors too */
+ {
+ gint X0 = MIN (coords->x, align_tool->x1);
+ gint X1 = MAX (coords->x, align_tool->x1);
+ gint Y0 = MIN (coords->y, align_tool->y1);
+ gint Y1 = MAX (coords->y, align_tool->y1);
+ GList *all_layers;
+ GList *list;
+
+ all_layers = gimp_image_get_layer_list (image);
+
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ gint x0, y0, x1, y1;
+
+ if (! gimp_item_get_visible (GIMP_ITEM (layer)))
+ continue;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &x0, &y0);
+ x1 = x0 + gimp_item_get_width (GIMP_ITEM (layer));
+ y1 = y0 + gimp_item_get_height (GIMP_ITEM (layer));
+
+ if (x0 < X0 || y0 < Y0 || x1 > X1 || y1 > Y1)
+ continue;
+
+ if (g_list_find (align_tool->selected_objects, layer))
+ continue;
+
+ align_tool->selected_objects =
+ g_list_append (align_tool->selected_objects, layer);
+
+ g_signal_connect (layer, "removed",
+ G_CALLBACK (gimp_align_tool_object_removed),
+ align_tool);
+ }
+
+ g_list_free (all_layers);
+ }
+
+ for (i = 0; i < ALIGN_OPTIONS_N_BUTTONS; i++)
+ {
+ if (options->button[i])
+ gtk_widget_set_sensitive (options->button[i],
+ align_tool->selected_objects != NULL);
+ }
+
+ align_tool->x2 = align_tool->x1;
+ align_tool->y2 = align_tool->y1;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_align_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ align_tool->x2 = coords->x;
+ align_tool->y2 = coords->y;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static gboolean
+gimp_align_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ if (display == tool->display)
+ {
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Escape:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_align_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gint snap_distance = display->config->snap_distance;
+ gboolean add;
+
+ add = ((state & gimp_get_extend_selection_mask ()) &&
+ align_tool->selected_objects);
+
+ if (gimp_image_pick_vectors (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance)))
+ {
+ if (add)
+ align_tool->function = ALIGN_TOOL_ADD_PATH;
+ else
+ align_tool->function = ALIGN_TOOL_PICK_PATH;
+ }
+ else if (gimp_display_shell_get_show_guides (shell) &&
+ gimp_image_pick_guide (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance)))
+ {
+ if (add)
+ align_tool->function = ALIGN_TOOL_ADD_GUIDE;
+ else
+ align_tool->function = ALIGN_TOOL_PICK_GUIDE;
+ }
+ else if (gimp_image_pick_layer_by_bounds (image, coords->x, coords->y))
+ {
+ if (add)
+ align_tool->function = ALIGN_TOOL_ADD_LAYER;
+ else
+ align_tool->function = ALIGN_TOOL_PICK_LAYER;
+ }
+ else
+ {
+ align_tool->function = ALIGN_TOOL_IDLE;
+ }
+
+ gimp_align_tool_status_update (tool, display, state, proximity);
+}
+
+static void
+gimp_align_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+ GimpToolCursorType tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ /* always add '+' when Shift is pressed, even if nothing is selected */
+ if (state & gimp_get_extend_selection_mask ())
+ modifier = GIMP_CURSOR_MODIFIER_PLUS;
+
+ switch (align_tool->function)
+ {
+ case ALIGN_TOOL_IDLE:
+ tool_cursor = GIMP_TOOL_CURSOR_RECT_SELECT;
+ break;
+
+ case ALIGN_TOOL_PICK_LAYER:
+ case ALIGN_TOOL_ADD_LAYER:
+ tool_cursor = GIMP_TOOL_CURSOR_HAND;
+ break;
+
+ case ALIGN_TOOL_PICK_GUIDE:
+ case ALIGN_TOOL_ADD_GUIDE:
+ tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ break;
+
+ case ALIGN_TOOL_PICK_PATH:
+ case ALIGN_TOOL_ADD_PATH:
+ tool_cursor = GIMP_TOOL_CURSOR_PATHS;
+ break;
+
+ case ALIGN_TOOL_DRAG_BOX:
+ break;
+ }
+
+ gimp_tool_control_set_cursor (tool->control, GIMP_CURSOR_MOUSE);
+ gimp_tool_control_set_tool_cursor (tool->control, tool_cursor);
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_align_tool_status_update (GimpTool *tool,
+ GimpDisplay *display,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (tool);
+ GdkModifierType extend_mask;
+
+ extend_mask = gimp_get_extend_selection_mask ();
+
+ gimp_tool_pop_status (tool, display);
+
+ if (proximity)
+ {
+ gchar *status = NULL;
+
+ if (! align_tool->selected_objects)
+ {
+ /* no need to suggest Shift if nothing is selected */
+ state |= extend_mask;
+ }
+
+ switch (align_tool->function)
+ {
+ case ALIGN_TOOL_IDLE:
+ status = gimp_suggest_modifiers (_("Click on a layer, path or guide, "
+ "or Click-Drag to pick several "
+ "layers"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case ALIGN_TOOL_PICK_LAYER:
+ status = gimp_suggest_modifiers (_("Click to pick this layer as "
+ "first item"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case ALIGN_TOOL_ADD_LAYER:
+ status = g_strdup (_("Click to add this layer to the list"));
+ break;
+
+ case ALIGN_TOOL_PICK_GUIDE:
+ status = gimp_suggest_modifiers (_("Click to pick this guide as "
+ "first item"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case ALIGN_TOOL_ADD_GUIDE:
+ status = g_strdup (_("Click to add this guide to the list"));
+ break;
+
+ case ALIGN_TOOL_PICK_PATH:
+ status = gimp_suggest_modifiers (_("Click to pick this path as "
+ "first item"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case ALIGN_TOOL_ADD_PATH:
+ status = g_strdup (_("Click to add this path to the list"));
+ break;
+
+ case ALIGN_TOOL_DRAG_BOX:
+ break;
+ }
+
+ if (status)
+ {
+ gimp_tool_push_status (tool, display, "%s", status);
+ g_free (status);
+ }
+ }
+}
+
+static void
+gimp_align_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpAlignTool *align_tool = GIMP_ALIGN_TOOL (draw_tool);
+ GList *list;
+ gint x, y, w, h;
+
+ /* draw rubber-band rectangle */
+ x = MIN (align_tool->x2, align_tool->x1);
+ y = MIN (align_tool->y2, align_tool->y1);
+ w = MAX (align_tool->x2, align_tool->x1) - x;
+ h = MAX (align_tool->y2, align_tool->y1) - y;
+
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE, x, y, w, h);
+
+ for (list = align_tool->selected_objects;
+ list;
+ list = g_list_next (list))
+ {
+ if (GIMP_IS_ITEM (list->data))
+ {
+ GimpItem *item = list->data;
+ gint off_x, off_y;
+
+ gimp_item_bounds (item, &x, &y, &w, &h);
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+ x += off_x;
+ y += off_y;
+
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ x, y,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_NORTH_WEST);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ x + w, y,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_NORTH_EAST);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ x, y + h,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_SOUTH_WEST);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ x + w, y + h,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_SOUTH_EAST);
+ }
+ else if (GIMP_IS_GUIDE (list->data))
+ {
+ GimpGuide *guide = list->data;
+ GimpImage *image = gimp_display_get_image (GIMP_TOOL (draw_tool)->display);
+ gint x, y;
+ gint w, h;
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_VERTICAL:
+ x = gimp_guide_get_position (guide);
+ h = gimp_image_get_height (image);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ x, h,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_SOUTH);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ x, 0,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_NORTH);
+ break;
+
+ case GIMP_ORIENTATION_HORIZONTAL:
+ y = gimp_guide_get_position (guide);
+ w = gimp_image_get_width (image);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ w, y,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_EAST);
+ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_FILLED_SQUARE,
+ 0, y,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_WEST);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+static void
+gimp_align_tool_align (GimpAlignTool *align_tool,
+ GimpAlignmentType align_type)
+{
+ GimpAlignOptions *options = GIMP_ALIGN_TOOL_GET_OPTIONS (align_tool);
+ GimpImage *image;
+ GObject *reference_object = NULL;
+ GList *list;
+ gint offset = 0;
+
+ /* if nothing is selected, just return */
+ if (! align_tool->selected_objects)
+ return;
+
+ image = gimp_display_get_image (GIMP_TOOL (align_tool)->display);
+
+ switch (align_type)
+ {
+ case GIMP_ALIGN_LEFT:
+ case GIMP_ALIGN_HCENTER:
+ case GIMP_ALIGN_RIGHT:
+ case GIMP_ALIGN_TOP:
+ case GIMP_ALIGN_VCENTER:
+ case GIMP_ALIGN_BOTTOM:
+ offset = 0;
+ break;
+
+ case GIMP_ARRANGE_LEFT:
+ case GIMP_ARRANGE_HCENTER:
+ case GIMP_ARRANGE_RIGHT:
+ case GIMP_ARRANGE_HFILL:
+ offset = options->offset_x;
+ break;
+
+ case GIMP_ARRANGE_TOP:
+ case GIMP_ARRANGE_VCENTER:
+ case GIMP_ARRANGE_BOTTOM:
+ case GIMP_ARRANGE_VFILL:
+ offset = options->offset_y;
+ break;
+ }
+
+ /* if only one object is selected, use the image as reference
+ * if multiple objects are selected, use the first one as reference if
+ * "set_reference" is TRUE, otherwise use NULL.
+ */
+
+ list = align_tool->selected_objects;
+
+ switch (options->align_reference)
+ {
+ case GIMP_ALIGN_REFERENCE_IMAGE:
+ reference_object = G_OBJECT (image);
+ break;
+
+ case GIMP_ALIGN_REFERENCE_FIRST:
+ if (g_list_length (list) == 1)
+ {
+ reference_object = G_OBJECT (image);
+ }
+ else
+ {
+ if (align_tool->set_reference)
+ {
+ reference_object = G_OBJECT (list->data);
+ list = g_list_next (list);
+ }
+ else
+ {
+ reference_object = NULL;
+ }
+ }
+ break;
+
+ case GIMP_ALIGN_REFERENCE_SELECTION:
+ reference_object = G_OBJECT (gimp_image_get_mask (image));
+ break;
+
+ case GIMP_ALIGN_REFERENCE_ACTIVE_LAYER:
+ reference_object = G_OBJECT (gimp_image_get_active_layer (image));
+ break;
+
+ case GIMP_ALIGN_REFERENCE_ACTIVE_CHANNEL:
+ reference_object = G_OBJECT (gimp_image_get_active_channel (image));
+ break;
+
+ case GIMP_ALIGN_REFERENCE_ACTIVE_PATH:
+ reference_object = G_OBJECT (gimp_image_get_active_vectors (image));
+ break;
+ }
+
+ if (! reference_object)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (align_tool));
+
+ gimp_image_arrange_objects (image, list,
+ align_type,
+ reference_object,
+ align_type,
+ offset);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (align_tool));
+
+ gimp_image_flush (image);
+}
+
+static void
+gimp_align_tool_object_removed (GObject *object,
+ GimpAlignTool *align_tool)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (align_tool));
+
+ if (align_tool->selected_objects)
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_align_tool_object_removed,
+ align_tool);
+
+ align_tool->selected_objects = g_list_remove (align_tool->selected_objects,
+ object);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (align_tool));
+}
+
+static void
+gimp_align_tool_clear_selected (GimpAlignTool *align_tool)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (align_tool));
+
+ while (align_tool->selected_objects)
+ gimp_align_tool_object_removed (align_tool->selected_objects->data,
+ align_tool);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (align_tool));
+}
diff --git a/app/tools/gimpaligntool.h b/app/tools/gimpaligntool.h
new file mode 100644
index 0000000..e84a9cb
--- /dev/null
+++ b/app/tools/gimpaligntool.h
@@ -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/>.
+ */
+
+#ifndef __GIMP_ALIGN_TOOL_H__
+#define __GIMP_ALIGN_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+/* tool function/operation/state/mode */
+typedef enum
+{
+ ALIGN_TOOL_IDLE,
+ ALIGN_TOOL_PICK_LAYER,
+ ALIGN_TOOL_ADD_LAYER,
+ ALIGN_TOOL_PICK_GUIDE,
+ ALIGN_TOOL_ADD_GUIDE,
+ ALIGN_TOOL_PICK_PATH,
+ ALIGN_TOOL_ADD_PATH,
+ ALIGN_TOOL_DRAG_BOX
+} GimpAlignToolFunction;
+
+
+#define GIMP_TYPE_ALIGN_TOOL (gimp_align_tool_get_type ())
+#define GIMP_ALIGN_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ALIGN_TOOL, GimpAlignTool))
+#define GIMP_ALIGN_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ALIGN_TOOL, GimpAlignToolClass))
+#define GIMP_IS_ALIGN_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ALIGN_TOOL))
+#define GIMP_IS_ALIGN_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ALIGN_TOOL))
+#define GIMP_ALIGN_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ALIGN_TOOL, GimpAlignToolClass))
+
+#define GIMP_ALIGN_TOOL_GET_OPTIONS(t) (GIMP_ALIGN_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpAlignTool GimpAlignTool;
+typedef struct _GimpAlignToolClass GimpAlignToolClass;
+
+struct _GimpAlignTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpAlignToolFunction function;
+ GList *selected_objects;
+
+ gint x1, y1, x2, y2; /* rubber-band rectangle */
+
+ gboolean set_reference;
+};
+
+struct _GimpAlignToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_align_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_align_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ALIGN_TOOL_H__ */
diff --git a/app/tools/gimpbrightnesscontrasttool.c b/app/tools/gimpbrightnesscontrasttool.c
new file mode 100644
index 0000000..26229a7
--- /dev/null
+++ b/app/tools/gimpbrightnesscontrasttool.c
@@ -0,0 +1,314 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "operations/gimpbrightnesscontrastconfig.h"
+
+#include "core/gimpdrawable.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpwidgets-constructors.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpbrightnesscontrasttool.h"
+#include "gimpfilteroptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+#define SLIDER_WIDTH 200
+
+
+static gboolean gimp_brightness_contrast_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_brightness_contrast_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_brightness_contrast_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_brightness_contrast_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static gchar *
+ gimp_brightness_contrast_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description);
+static void gimp_brightness_contrast_tool_dialog (GimpFilterTool *filter_tool);
+
+static void brightness_contrast_to_levels_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool);
+
+
+G_DEFINE_TYPE (GimpBrightnessContrastTool, gimp_brightness_contrast_tool,
+ GIMP_TYPE_FILTER_TOOL)
+
+#define parent_class gimp_brightness_contrast_tool_parent_class
+
+
+void
+gimp_brightness_contrast_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL,
+ GIMP_TYPE_FILTER_OPTIONS, NULL,
+ 0,
+ "gimp-brightness-contrast-tool",
+ _("Brightness-Contrast"),
+ _("Adjust brightness and contrast"),
+ N_("B_rightness-Contrast..."), NULL,
+ NULL, GIMP_HELP_TOOL_BRIGHTNESS_CONTRAST,
+ GIMP_ICON_TOOL_BRIGHTNESS_CONTRAST,
+ data);
+}
+
+static void
+gimp_brightness_contrast_tool_class_init (GimpBrightnessContrastToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ tool_class->initialize = gimp_brightness_contrast_tool_initialize;
+ tool_class->button_press = gimp_brightness_contrast_tool_button_press;
+ tool_class->button_release = gimp_brightness_contrast_tool_button_release;
+ tool_class->motion = gimp_brightness_contrast_tool_motion;
+
+ filter_tool_class->get_operation = gimp_brightness_contrast_tool_get_operation;
+ filter_tool_class->dialog = gimp_brightness_contrast_tool_dialog;
+}
+
+static void
+gimp_brightness_contrast_tool_init (GimpBrightnessContrastTool *bc_tool)
+{
+}
+
+static gboolean
+gimp_brightness_contrast_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpBrightnessContrastTool *bc_tool = GIMP_BRIGHTNESS_CONTRAST_TOOL (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ return FALSE;
+ }
+
+ if (gimp_drawable_get_component_type (drawable) == GIMP_COMPONENT_TYPE_U8)
+ {
+ gimp_prop_widget_set_factor (bc_tool->brightness_scale,
+ 127.0, 1.0, 8.0, 0);
+ gimp_prop_widget_set_factor (bc_tool->contrast_scale,
+ 127.0, 1.0, 8.0, 0);
+ }
+ else
+ {
+ gimp_prop_widget_set_factor (bc_tool->brightness_scale,
+ 0.5, 0.01, 0.1, 3);
+ gimp_prop_widget_set_factor (bc_tool->contrast_scale,
+ 0.5, 0.01, 0.1, 3);
+ }
+
+ return TRUE;
+}
+
+static gchar *
+gimp_brightness_contrast_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description)
+{
+ *description = g_strdup (_("Adjust Brightness and Contrast"));
+
+ return g_strdup ("gimp:brightness-contrast");
+}
+
+static void
+gimp_brightness_contrast_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpBrightnessContrastTool *bc_tool = GIMP_BRIGHTNESS_CONTRAST_TOOL (tool);
+
+ bc_tool->dragging = ! gimp_filter_tool_on_guide (GIMP_FILTER_TOOL (tool),
+ coords, display);
+
+ if (! bc_tool->dragging)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ }
+ else
+ {
+ gdouble brightness;
+ gdouble contrast;
+
+ g_object_get (GIMP_FILTER_TOOL (tool)->config,
+ "brightness", &brightness,
+ "contrast", &contrast,
+ NULL);
+
+ bc_tool->x = coords->x - contrast * 127.0;
+ bc_tool->y = coords->y + brightness * 127.0;
+ bc_tool->dx = contrast * 127.0;
+ bc_tool->dy = - brightness * 127.0;
+
+ tool->display = display;
+
+ gimp_tool_control_activate (tool->control);
+ }
+}
+
+static void
+gimp_brightness_contrast_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpBrightnessContrastTool *bc_tool = GIMP_BRIGHTNESS_CONTRAST_TOOL (tool);
+
+ if (! bc_tool->dragging)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+ }
+ else
+ {
+ gimp_tool_control_halt (tool->control);
+
+ bc_tool->dragging = FALSE;
+
+ if (bc_tool->dx == 0 && bc_tool->dy == 0)
+ return;
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_config_reset (GIMP_CONFIG (GIMP_FILTER_TOOL (tool)->config));
+ }
+}
+
+static void
+gimp_brightness_contrast_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpBrightnessContrastTool *bc_tool = GIMP_BRIGHTNESS_CONTRAST_TOOL (tool);
+
+ if (! bc_tool->dragging)
+ {
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
+ display);
+ }
+ else
+ {
+ bc_tool->dx = (coords->x - bc_tool->x);
+ bc_tool->dy = - (coords->y - bc_tool->y);
+
+ g_object_set (GIMP_FILTER_TOOL (tool)->config,
+ "brightness", CLAMP (bc_tool->dy, -127.0, 127.0) / 127.0,
+ "contrast", CLAMP (bc_tool->dx, -127.0, 127.0) / 127.0,
+ NULL);
+ }
+}
+
+
+/********************************/
+/* Brightness Contrast dialog */
+/********************************/
+
+static void
+gimp_brightness_contrast_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpBrightnessContrastTool *bc_tool = GIMP_BRIGHTNESS_CONTRAST_TOOL (filter_tool);
+ GtkWidget *main_vbox;
+ GtkWidget *scale;
+ GtkWidget *button;
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ /* Create the brightness scale widget */
+ scale = gimp_prop_spin_scale_new (filter_tool->config, "brightness",
+ _("_Brightness"), 0.01, 0.1, 3);
+ gtk_box_pack_start (GTK_BOX (main_vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ bc_tool->brightness_scale = scale;
+
+ /* Create the contrast scale widget */
+ scale = gimp_prop_spin_scale_new (filter_tool->config, "contrast",
+ _("_Contrast"), 0.01, 0.1, 3);
+ gtk_box_pack_start (GTK_BOX (main_vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ bc_tool->contrast_scale = scale;
+
+ button = gimp_icon_button_new (GIMP_ICON_TOOL_LEVELS,
+ _("Edit these Settings as Levels"));
+ gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (brightness_contrast_to_levels_callback),
+ filter_tool);
+}
+
+static void
+brightness_contrast_to_levels_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool)
+{
+ GimpLevelsConfig *levels;
+
+ levels = gimp_brightness_contrast_config_to_levels_config (GIMP_BRIGHTNESS_CONTRAST_CONFIG (filter_tool->config));
+
+ gimp_filter_tool_edit_as (filter_tool,
+ "gimp-levels-tool",
+ GIMP_CONFIG (levels));
+
+ g_object_unref (levels);
+}
diff --git a/app/tools/gimpbrightnesscontrasttool.h b/app/tools/gimpbrightnesscontrasttool.h
new file mode 100644
index 0000000..64ce278
--- /dev/null
+++ b/app/tools/gimpbrightnesscontrasttool.h
@@ -0,0 +1,61 @@
+/* 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_BRIGHTNESS_CONTRAST_TOOL_H__
+#define __GIMP_BRIGHTNESS_CONTRAST_TOOL_H__
+
+
+#include "gimpfiltertool.h"
+
+
+#define GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL (gimp_brightness_contrast_tool_get_type ())
+#define GIMP_BRIGHTNESS_CONTRAST_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL, GimpBrightnessContrastTool))
+#define GIMP_BRIGHTNESS_CONTRAST_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL, GimpBrightnessContrastToolClass))
+#define GIMP_IS_BRIGHTNESS_CONTRAST_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL))
+#define GIMP_IS_BRIGHTNESS_CONTRAST_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL))
+#define GIMP_BRIGHTNESS_CONTRAST_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRIGHTNESS_CONTRAST_TOOL, GimpBrightnessContrastToolClass))
+
+
+typedef struct _GimpBrightnessContrastTool GimpBrightnessContrastTool;
+typedef struct _GimpBrightnessContrastToolClass GimpBrightnessContrastToolClass;
+
+struct _GimpBrightnessContrastTool
+{
+ GimpFilterTool parent_instance;
+
+ gboolean dragging;
+ gdouble x, y;
+ gdouble dx, dy;
+
+ /* dialog */
+ GtkWidget *brightness_scale;
+ GtkWidget *contrast_scale;
+};
+
+struct _GimpBrightnessContrastToolClass
+{
+ GimpFilterToolClass parent_class;
+};
+
+
+void gimp_brightness_contrast_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_brightness_contrast_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_BRIGHTNESS_CONTRAST_TOOL_H__ */
diff --git a/app/tools/gimpbrushtool.c b/app/tools/gimpbrushtool.c
new file mode 100644
index 0000000..08c8ed9
--- /dev/null
+++ b/app/tools/gimpbrushtool.c
@@ -0,0 +1,451 @@
+/* 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 "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpbezierdesc.h"
+#include "core/gimpbrush.h"
+#include "core/gimpimage.h"
+#include "core/gimptoolinfo.h"
+
+#include "paint/gimpbrushcore.h"
+#include "paint/gimppaintoptions.h"
+
+#include "display/gimpcanvashandle.h"
+#include "display/gimpcanvaspath.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+
+#include "gimpbrushtool.h"
+#include "gimppainttool-paint.h"
+#include "gimptoolcontrol.h"
+
+
+static void gimp_brush_tool_constructed (GObject *object);
+
+static void gimp_brush_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_brush_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_brush_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_brush_tool_paint_start (GimpPaintTool *paint_tool);
+static void gimp_brush_tool_paint_end (GimpPaintTool *paint_tool);
+static void gimp_brush_tool_paint_flush (GimpPaintTool *paint_tool);
+static GimpCanvasItem *
+ gimp_brush_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y);
+
+static void gimp_brush_tool_brush_changed (GimpContext *context,
+ GimpBrush *brush,
+ GimpBrushTool *brush_tool);
+static void gimp_brush_tool_set_brush (GimpBrushCore *brush_core,
+ GimpBrush *brush,
+ GimpBrushTool *brush_tool);
+
+static const GimpBezierDesc *
+ gimp_brush_tool_get_boundary (GimpBrushTool *brush_tool,
+ gint *width,
+ gint *height);
+
+
+G_DEFINE_TYPE (GimpBrushTool, gimp_brush_tool, GIMP_TYPE_PAINT_TOOL)
+
+#define parent_class gimp_brush_tool_parent_class
+
+
+static void
+gimp_brush_tool_class_init (GimpBrushToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_brush_tool_constructed;
+
+ tool_class->oper_update = gimp_brush_tool_oper_update;
+ tool_class->cursor_update = gimp_brush_tool_cursor_update;
+ tool_class->options_notify = gimp_brush_tool_options_notify;
+
+ paint_tool_class->paint_start = gimp_brush_tool_paint_start;
+ paint_tool_class->paint_end = gimp_brush_tool_paint_end;
+ paint_tool_class->paint_flush = gimp_brush_tool_paint_flush;
+ paint_tool_class->get_outline = gimp_brush_tool_get_outline;
+}
+
+static void
+gimp_brush_tool_init (GimpBrushTool *brush_tool)
+{
+ GimpTool *tool = GIMP_TOOL (brush_tool);
+
+ gimp_tool_control_set_action_size (tool->control,
+ "tools/tools-paintbrush-size-set");
+ gimp_tool_control_set_action_aspect (tool->control,
+ "tools/tools-paintbrush-aspect-ratio-set");
+ gimp_tool_control_set_action_angle (tool->control,
+ "tools/tools-paintbrush-angle-set");
+ gimp_tool_control_set_action_spacing (tool->control,
+ "tools/tools-paintbrush-spacing-set");
+ gimp_tool_control_set_action_hardness (tool->control,
+ "tools/tools-paintbrush-hardness-set");
+ gimp_tool_control_set_action_force (tool->control,
+ "tools/tools-paintbrush-force-set");
+ gimp_tool_control_set_action_object_1 (tool->control,
+ "context/context-brush-select-set");
+}
+
+static void
+gimp_brush_tool_constructed (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_BRUSH_CORE (paint_tool->core));
+
+ g_signal_connect_object (gimp_tool_get_options (tool), "brush-changed",
+ G_CALLBACK (gimp_brush_tool_brush_changed),
+ paint_tool, 0);
+
+ g_signal_connect_object (paint_tool->core, "set-brush",
+ G_CALLBACK (gimp_brush_tool_set_brush),
+ paint_tool, 0);
+}
+
+static void
+gimp_brush_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (tool);
+ GimpDrawable *drawable;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+
+ drawable = gimp_image_get_active_drawable (gimp_display_get_image (display));
+
+ if (! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)) &&
+ drawable && proximity)
+ {
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+
+ gimp_brush_core_set_brush (brush_core,
+ gimp_context_get_brush (context));
+
+ gimp_brush_core_set_dynamics (brush_core,
+ gimp_context_get_dynamics (context));
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
+ {
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ coords);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_brush_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpBrushTool *brush_tool = GIMP_BRUSH_TOOL (tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (GIMP_PAINT_TOOL (brush_tool)->core);
+
+ if (! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ if (! brush_core->main_brush || ! brush_core->dynamics)
+ {
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ return;
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_brush_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "brush-size") ||
+ ! strcmp (pspec->name, "brush-angle") ||
+ ! strcmp (pspec->name, "brush-aspect-ratio"))
+ {
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+
+ g_signal_emit_by_name (brush_core, "set-brush",
+ brush_core->main_brush);
+ }
+}
+
+static void
+gimp_brush_tool_paint_start (GimpPaintTool *paint_tool)
+{
+ GimpBrushTool *brush_tool = GIMP_BRUSH_TOOL (paint_tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+ const GimpBezierDesc *boundary;
+
+ if (GIMP_PAINT_TOOL_CLASS (parent_class)->paint_start)
+ GIMP_PAINT_TOOL_CLASS (parent_class)->paint_start (paint_tool);
+
+ boundary = gimp_brush_tool_get_boundary (brush_tool,
+ &brush_tool->boundary_width,
+ &brush_tool->boundary_height);
+
+ if (boundary)
+ brush_tool->boundary = gimp_bezier_desc_copy (boundary);
+
+ brush_tool->boundary_scale = brush_core->scale;
+ brush_tool->boundary_aspect_ratio = brush_core->aspect_ratio;
+ brush_tool->boundary_angle = brush_core->angle;
+ brush_tool->boundary_reflect = brush_core->reflect;
+ brush_tool->boundary_hardness = brush_core->hardness;
+}
+
+static void
+gimp_brush_tool_paint_end (GimpPaintTool *paint_tool)
+{
+ GimpBrushTool *brush_tool = GIMP_BRUSH_TOOL (paint_tool);
+
+ g_clear_pointer (&brush_tool->boundary, gimp_bezier_desc_free);
+
+ if (GIMP_PAINT_TOOL_CLASS (parent_class)->paint_end)
+ GIMP_PAINT_TOOL_CLASS (parent_class)->paint_end (paint_tool);
+}
+
+static void
+gimp_brush_tool_paint_flush (GimpPaintTool *paint_tool)
+{
+ GimpBrushTool *brush_tool = GIMP_BRUSH_TOOL (paint_tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+ const GimpBezierDesc *boundary;
+
+ if (GIMP_PAINT_TOOL_CLASS (parent_class)->paint_flush)
+ GIMP_PAINT_TOOL_CLASS (parent_class)->paint_flush (paint_tool);
+
+ if (brush_tool->boundary_scale != brush_core->scale ||
+ brush_tool->boundary_aspect_ratio != brush_core->aspect_ratio ||
+ brush_tool->boundary_angle != brush_core->angle ||
+ brush_tool->boundary_reflect != brush_core->reflect ||
+ brush_tool->boundary_hardness != brush_core->hardness)
+ {
+ g_clear_pointer (&brush_tool->boundary, gimp_bezier_desc_free);
+
+ boundary = gimp_brush_tool_get_boundary (brush_tool,
+ &brush_tool->boundary_width,
+ &brush_tool->boundary_height);
+
+ if (boundary)
+ brush_tool->boundary = gimp_bezier_desc_copy (boundary);
+
+ brush_tool->boundary_scale = brush_core->scale;
+ brush_tool->boundary_aspect_ratio = brush_core->aspect_ratio;
+ brush_tool->boundary_angle = brush_core->angle;
+ brush_tool->boundary_reflect = brush_core->reflect;
+ brush_tool->boundary_hardness = brush_core->hardness;
+ }
+}
+
+static GimpCanvasItem *
+gimp_brush_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y)
+{
+ GimpBrushTool *brush_tool = GIMP_BRUSH_TOOL (paint_tool);
+ GimpCanvasItem *item;
+
+ item = gimp_brush_tool_create_outline (brush_tool, display, x, y);
+
+ if (! item)
+ {
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+
+ if (brush_core->main_brush && brush_core->dynamics)
+ {
+ /* if an outline was expected, but got scaled away by
+ * transform/dynamics, draw a circle in the "normal" size.
+ */
+ GimpPaintOptions *options;
+
+ options = GIMP_PAINT_TOOL_GET_OPTIONS (brush_tool);
+
+ gimp_paint_tool_set_draw_fallback (paint_tool,
+ TRUE, options->brush_size);
+ }
+ }
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_brush_tool_create_outline (GimpBrushTool *brush_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y)
+{
+ GimpTool *tool;
+ GimpDisplayShell *shell;
+ const GimpBezierDesc *boundary = NULL;
+ gint width = 0;
+ gint height = 0;
+
+ g_return_val_if_fail (GIMP_IS_BRUSH_TOOL (brush_tool), NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ if (gimp_paint_tool_paint_is_active (GIMP_PAINT_TOOL (brush_tool)))
+ {
+ boundary = brush_tool->boundary;
+ width = brush_tool->boundary_width;
+ height = brush_tool->boundary_height;
+ }
+ else
+ {
+ boundary = gimp_brush_tool_get_boundary (brush_tool, &width, &height);
+ }
+
+ if (! boundary)
+ return NULL;
+
+ tool = GIMP_TOOL (brush_tool);
+ shell = gimp_display_get_shell (display);
+
+ /* don't draw the boundary if it becomes too small */
+ if (SCALEX (shell, width) > 4 &&
+ SCALEY (shell, height) > 4)
+ {
+ x -= width / 2.0;
+ y -= height / 2.0;
+
+ if (gimp_tool_control_get_precision (tool->control) ==
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER)
+ {
+#define EPSILON 0.000001
+ /* Add EPSILON before rounding since e.g.
+ * (5.0 - 0.5) may end up at (4.499999999....)
+ * due to floating point fnords
+ */
+ x = RINT (x + EPSILON);
+ y = RINT (y + EPSILON);
+#undef EPSILON
+ }
+
+ return gimp_canvas_path_new (shell, boundary, x, y, FALSE,
+ GIMP_PATH_STYLE_OUTLINE);
+ }
+
+ return NULL;
+}
+
+static void
+gimp_brush_tool_brush_changed (GimpContext *context,
+ GimpBrush *brush,
+ GimpBrushTool *brush_tool)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (brush_tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+
+ gimp_brush_core_set_brush (brush_core, brush);
+
+}
+
+static void
+gimp_brush_tool_set_brush (GimpBrushCore *brush_core,
+ GimpBrush *brush,
+ GimpBrushTool *brush_tool)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (brush_tool));
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
+ {
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (brush_core);
+
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ NULL,
+ GIMP_PAINT_TOOL_GET_OPTIONS (brush_tool),
+ &paint_core->cur_coords);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (brush_tool));
+}
+
+static const GimpBezierDesc *
+gimp_brush_tool_get_boundary (GimpBrushTool *brush_tool,
+ gint *width,
+ gint *height)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (brush_tool);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_tool->core);
+
+ if (paint_tool->draw_brush &&
+ brush_core->main_brush &&
+ brush_core->dynamics &&
+ brush_core->scale > 0.0)
+ {
+ return gimp_brush_transform_boundary (brush_core->main_brush,
+ brush_core->scale,
+ brush_core->aspect_ratio,
+ brush_core->angle,
+ brush_core->reflect,
+ brush_core->hardness,
+ width,
+ height);
+ }
+
+ return NULL;
+}
diff --git a/app/tools/gimpbrushtool.h b/app/tools/gimpbrushtool.h
new file mode 100644
index 0000000..6ddddd7
--- /dev/null
+++ b/app/tools/gimpbrushtool.h
@@ -0,0 +1,63 @@
+/* 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_BRUSH_TOOL_H__
+#define __GIMP_BRUSH_TOOL_H__
+
+
+#include "gimppainttool.h"
+
+
+#define GIMP_TYPE_BRUSH_TOOL (gimp_brush_tool_get_type ())
+#define GIMP_BRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_TOOL, GimpBrushTool))
+#define GIMP_BRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_TOOL, GimpBrushToolClass))
+#define GIMP_IS_BRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_TOOL))
+#define GIMP_IS_BRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_TOOL))
+#define GIMP_BRUSH_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_TOOL, GimpBrushToolClass))
+
+
+typedef struct _GimpBrushToolClass GimpBrushToolClass;
+
+struct _GimpBrushTool
+{
+ GimpPaintTool parent_instance;
+
+ GimpBezierDesc *boundary;
+ gint boundary_width;
+ gint boundary_height;
+ gdouble boundary_scale;
+ gdouble boundary_aspect_ratio;
+ gdouble boundary_angle;
+ gboolean boundary_reflect;
+ gdouble boundary_hardness;
+};
+
+struct _GimpBrushToolClass
+{
+ GimpPaintToolClass parent_class;
+};
+
+
+GType gimp_brush_tool_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_brush_tool_create_outline (GimpBrushTool *brush_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y);
+
+
+#endif /* __GIMP_BRUSH_TOOL_H__ */
diff --git a/app/tools/gimpbucketfilloptions.c b/app/tools/gimpbucketfilloptions.c
new file mode 100644
index 0000000..f5615c2
--- /dev/null
+++ b/app/tools/gimpbucketfilloptions.c
@@ -0,0 +1,544 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpdatafactory.h"
+#include "core/gimptoolinfo.h"
+
+#include "display/gimpdisplay.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpviewablebox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpbucketfilloptions.h"
+#include "gimppaintoptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_FILL_MODE,
+ PROP_FILL_AREA,
+ PROP_FILL_TRANSPARENT,
+ PROP_SAMPLE_MERGED,
+ PROP_DIAGONAL_NEIGHBORS,
+ PROP_ANTIALIAS,
+ PROP_FEATHER,
+ PROP_FEATHER_RADIUS,
+ PROP_THRESHOLD,
+ PROP_LINE_ART_SOURCE,
+ PROP_LINE_ART_THRESHOLD,
+ PROP_LINE_ART_MAX_GROW,
+ PROP_LINE_ART_MAX_GAP_LENGTH,
+ PROP_FILL_CRITERION
+};
+
+struct _GimpBucketFillOptionsPrivate
+{
+ GtkWidget *diagonal_neighbors_checkbox;
+ GtkWidget *threshold_scale;
+
+ GtkWidget *similar_color_frame;
+ GtkWidget *line_art_frame;
+};
+
+static void gimp_bucket_fill_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_bucket_fill_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_bucket_fill_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_bucket_fill_options_reset (GimpConfig *config);
+static void gimp_bucket_fill_options_update_area (GimpBucketFillOptions *options);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpBucketFillOptions, gimp_bucket_fill_options,
+ GIMP_TYPE_PAINT_OPTIONS,
+ G_ADD_PRIVATE (GimpBucketFillOptions)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_bucket_fill_options_config_iface_init))
+
+#define parent_class gimp_bucket_fill_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_bucket_fill_options_class_init (GimpBucketFillOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_bucket_fill_options_set_property;
+ object_class->get_property = gimp_bucket_fill_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FILL_MODE,
+ "fill-mode",
+ _("Fill type"),
+ NULL,
+ GIMP_TYPE_BUCKET_FILL_MODE,
+ GIMP_BUCKET_FILL_FG,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FILL_AREA,
+ "fill-area",
+ _("Fill selection"),
+ _("Which area will be filled"),
+ GIMP_TYPE_BUCKET_FILL_AREA,
+ GIMP_BUCKET_FILL_SIMILAR_COLORS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FILL_TRANSPARENT,
+ "fill-transparent",
+ _("Fill transparent areas"),
+ _("Allow completely transparent regions "
+ "to be filled"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
+ "sample-merged",
+ _("Sample merged"),
+ _("Base filled area on all visible layers"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DIAGONAL_NEIGHBORS,
+ "diagonal-neighbors",
+ _("Diagonal neighbors"),
+ _("Treat diagonally neighboring pixels as "
+ "connected"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ANTIALIAS,
+ "antialias",
+ _("Antialiasing"),
+ _("Base fill opacity on color difference from "
+ "the clicked pixel (see threshold) or on line "
+ " art borders. Disable antialiasing to fill "
+ "the entire area uniformly."),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FEATHER,
+ "feather",
+ _("Feather edges"),
+ _("Enable feathering of fill edges"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FEATHER_RADIUS,
+ "feather-radius",
+ _("Radius"),
+ _("Radius of feathering"),
+ 0.0, 100.0, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_THRESHOLD,
+ "threshold",
+ _("Threshold"),
+ _("Maximum color difference"),
+ 0.0, 255.0, 15.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_LINE_ART_SOURCE,
+ "line-art-source",
+ _("Source"),
+ _("Source image for line art computation"),
+ GIMP_TYPE_LINE_ART_SOURCE,
+ GIMP_LINE_ART_SOURCE_SAMPLE_MERGED,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LINE_ART_THRESHOLD,
+ "line-art-threshold",
+ _("Line art detection threshold"),
+ _("Threshold to detect contour (higher values will include more pixels)"),
+ 0.0, 1.0, 0.92,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_LINE_ART_MAX_GROW,
+ "line-art-max-grow",
+ _("Maximum growing size"),
+ _("Maximum number of pixels grown under the line art"),
+ 1, 100, 3,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_LINE_ART_MAX_GAP_LENGTH,
+ "line-art-max-gap-length",
+ _("Maximum gap length"),
+ _("Maximum gap (in pixels) in line art which can be closed"),
+ 0, 1000, 100,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FILL_CRITERION,
+ "fill-criterion",
+ _("Fill by"),
+ _("Criterion used for determining color similarity"),
+ GIMP_TYPE_SELECT_CRITERION,
+ GIMP_SELECT_CRITERION_COMPOSITE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_bucket_fill_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ config_iface->reset = gimp_bucket_fill_options_reset;
+}
+
+static void
+gimp_bucket_fill_options_init (GimpBucketFillOptions *options)
+{
+ options->priv = gimp_bucket_fill_options_get_instance_private (options);
+}
+
+static void
+gimp_bucket_fill_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_FILL_MODE:
+ options->fill_mode = g_value_get_enum (value);
+ break;
+ case PROP_FILL_AREA:
+ options->fill_area = g_value_get_enum (value);
+ gimp_bucket_fill_options_update_area (options);
+ break;
+ case PROP_FILL_TRANSPARENT:
+ options->fill_transparent = g_value_get_boolean (value);
+ break;
+ case PROP_SAMPLE_MERGED:
+ options->sample_merged = g_value_get_boolean (value);
+ break;
+ case PROP_DIAGONAL_NEIGHBORS:
+ options->diagonal_neighbors = g_value_get_boolean (value);
+ break;
+ case PROP_ANTIALIAS:
+ options->antialias = g_value_get_boolean (value);
+ break;
+ case PROP_FEATHER:
+ options->feather = g_value_get_boolean (value);
+ break;
+ case PROP_FEATHER_RADIUS:
+ options->feather_radius = g_value_get_double (value);
+ break;
+ case PROP_THRESHOLD:
+ options->threshold = g_value_get_double (value);
+ break;
+ case PROP_LINE_ART_SOURCE:
+ options->line_art_source = g_value_get_enum (value);
+ break;
+ case PROP_LINE_ART_THRESHOLD:
+ options->line_art_threshold = g_value_get_double (value);
+ break;
+ case PROP_LINE_ART_MAX_GROW:
+ options->line_art_max_grow = g_value_get_int (value);
+ break;
+ case PROP_LINE_ART_MAX_GAP_LENGTH:
+ options->line_art_max_gap_length = g_value_get_int (value);
+ break;
+ case PROP_FILL_CRITERION:
+ options->fill_criterion = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_bucket_fill_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_FILL_MODE:
+ g_value_set_enum (value, options->fill_mode);
+ break;
+ case PROP_FILL_AREA:
+ g_value_set_enum (value, options->fill_area);
+ break;
+ case PROP_FILL_TRANSPARENT:
+ g_value_set_boolean (value, options->fill_transparent);
+ break;
+ case PROP_SAMPLE_MERGED:
+ g_value_set_boolean (value, options->sample_merged);
+ break;
+ case PROP_DIAGONAL_NEIGHBORS:
+ g_value_set_boolean (value, options->diagonal_neighbors);
+ break;
+ case PROP_ANTIALIAS:
+ g_value_set_boolean (value, options->antialias);
+ break;
+ case PROP_FEATHER:
+ g_value_set_boolean (value, options->feather);
+ break;
+ case PROP_FEATHER_RADIUS:
+ g_value_set_double (value, options->feather_radius);
+ break;
+ case PROP_THRESHOLD:
+ g_value_set_double (value, options->threshold);
+ break;
+ case PROP_LINE_ART_SOURCE:
+ g_value_set_enum (value, options->line_art_source);
+ break;
+ case PROP_LINE_ART_THRESHOLD:
+ g_value_set_double (value, options->line_art_threshold);
+ break;
+ case PROP_LINE_ART_MAX_GROW:
+ g_value_set_int (value, options->line_art_max_grow);
+ break;
+ case PROP_LINE_ART_MAX_GAP_LENGTH:
+ g_value_set_int (value, options->line_art_max_gap_length);
+ break;
+ case PROP_FILL_CRITERION:
+ g_value_set_enum (value, options->fill_criterion);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_bucket_fill_options_reset (GimpConfig *config)
+{
+ GimpToolOptions *tool_options = GIMP_TOOL_OPTIONS (config);
+ GParamSpec *pspec;
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+ "threshold");
+
+ if (pspec)
+ G_PARAM_SPEC_DOUBLE (pspec)->default_value =
+ tool_options->tool_info->gimp->config->default_threshold;
+
+ parent_config_iface->reset (config);
+}
+
+static void
+gimp_bucket_fill_options_update_area (GimpBucketFillOptions *options)
+{
+ /* GUI not created yet. */
+ if (! options->priv->threshold_scale)
+ return;
+
+ switch (options->fill_area)
+ {
+ case GIMP_BUCKET_FILL_LINE_ART:
+ gtk_widget_hide (options->priv->similar_color_frame);
+ gtk_widget_show (options->priv->line_art_frame);
+ break;
+ case GIMP_BUCKET_FILL_SIMILAR_COLORS:
+ gtk_widget_show (options->priv->similar_color_frame);
+ gtk_widget_hide (options->priv->line_art_frame);
+ break;
+ default:
+ gtk_widget_hide (options->priv->similar_color_frame);
+ gtk_widget_hide (options->priv->line_art_frame);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_bucket_fill_options_gui (GimpToolOptions *tool_options)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_OPTIONS (tool_options);
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *box2;
+ GtkWidget *frame;
+ GtkWidget *hbox;
+ GtkWidget *widget;
+ GtkWidget *scale;
+ GtkWidget *combo;
+ gchar *str;
+ gboolean bold;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = GDK_MOD1_MASK;
+
+ /* fill type */
+ str = g_strdup_printf (_("Fill Type (%s)"),
+ gimp_get_mod_string (toggle_mask)),
+ frame = gimp_prop_enum_radio_frame_new (config, "fill-mode", str, 0, 0);
+ g_free (str);
+
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ hbox = gimp_prop_pattern_box_new (NULL, GIMP_CONTEXT (tool_options),
+ NULL, 2,
+ "pattern-view-type", "pattern-view-size");
+ gimp_enum_radio_frame_add (GTK_FRAME (frame), hbox,
+ GIMP_BUCKET_FILL_PATTERN, TRUE);
+
+ /* fill selection */
+ str = g_strdup_printf (_("Affected Area (%s)"),
+ gimp_get_mod_string (extend_mask));
+ frame = gimp_prop_enum_radio_frame_new (config, "fill-area", str, 0, 0);
+ g_free (str);
+
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* Similar color frame */
+ frame = gimp_frame_new (_("Finding Similar Colors"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ options->priv->similar_color_frame = frame;
+ gtk_widget_show (frame);
+
+ box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), box2);
+ gtk_widget_show (box2);
+
+ /* the fill transparent areas toggle */
+ widget = gimp_prop_check_button_new (config, "fill-transparent", NULL);
+ gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ /* the sample merged toggle */
+ widget = gimp_prop_check_button_new (config, "sample-merged", NULL);
+ gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ /* the diagonal neighbors toggle */
+ widget = gimp_prop_check_button_new (config, "diagonal-neighbors", NULL);
+ gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
+ options->priv->diagonal_neighbors_checkbox = widget;
+ gtk_widget_show (widget);
+
+ /* the antialias toggle */
+ widget = gimp_prop_check_button_new (config, "antialias", NULL);
+ gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ /* the threshold scale */
+ scale = gimp_prop_spin_scale_new (config, "threshold", NULL,
+ 1.0, 16.0, 1);
+ gtk_box_pack_start (GTK_BOX (box2), scale, FALSE, FALSE, 0);
+ options->priv->threshold_scale = scale;
+ gtk_widget_show (scale);
+
+ /* the fill criterion combo */
+ combo = gimp_prop_enum_combo_box_new (config, "fill-criterion", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Fill by"));
+ gtk_box_pack_start (GTK_BOX (box2), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ /* Line art frame */
+ frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ options->priv->line_art_frame = frame;
+ gtk_widget_show (frame);
+
+ /* Line art: label widget */
+ box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), box2);
+ gtk_widget_show (box2);
+
+ widget = gtk_label_new (_("Line Art Detection"));
+ gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
+ gtk_widget_style_get (GTK_WIDGET (frame),
+ "label-bold", &bold,
+ NULL);
+ gimp_label_set_attributes (GTK_LABEL (widget),
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_widget_show (widget);
+
+ options->line_art_busy_box = gimp_busy_box_new (_("(computing...)"));
+ gtk_box_pack_start (GTK_BOX (box2), options->line_art_busy_box,
+ FALSE, FALSE, 0);
+
+ box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), box2);
+ gtk_widget_show (box2);
+
+ /* Line Art: source combo (replace sample merged!) */
+ combo = gimp_prop_enum_combo_box_new (config, "line-art-source", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Source"));
+ gtk_box_pack_start (GTK_BOX (box2), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ /* the fill transparent areas toggle */
+ widget = gimp_prop_check_button_new (config, "fill-transparent", NULL);
+ gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ /* Line Art: feather radius scale */
+ scale = gimp_prop_spin_scale_new (config, "feather-radius", NULL,
+ 1.0, 10.0, 1);
+
+ frame = gimp_prop_expanding_frame_new (config, "feather", NULL,
+ scale, NULL);
+ gtk_box_pack_start (GTK_BOX (box2), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* Line Art: max growing size */
+ scale = gimp_prop_spin_scale_new (config, "line-art-max-grow", NULL,
+ 1, 5, 0);
+ gtk_box_pack_start (GTK_BOX (box2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* Line Art: stroke threshold */
+ scale = gimp_prop_spin_scale_new (config, "line-art-threshold", NULL,
+ 0.05, 0.1, 2);
+ gtk_box_pack_start (GTK_BOX (box2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* Line Art: max gap length */
+ scale = gimp_prop_spin_scale_new (config, "line-art-max-gap-length", NULL,
+ 1, 5, 0);
+ gtk_box_pack_start (GTK_BOX (box2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ gimp_bucket_fill_options_update_area (options);
+
+ return vbox;
+}
diff --git a/app/tools/gimpbucketfilloptions.h b/app/tools/gimpbucketfilloptions.h
new file mode 100644
index 0000000..8c001fb
--- /dev/null
+++ b/app/tools/gimpbucketfilloptions.h
@@ -0,0 +1,68 @@
+/* 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_BUCKET_FILL_OPTIONS_H__
+#define __GIMP_BUCKET_FILL_OPTIONS_H__
+
+
+#include "paint/gimppaintoptions.h"
+
+
+#define GIMP_TYPE_BUCKET_FILL_OPTIONS (gimp_bucket_fill_options_get_type ())
+#define GIMP_BUCKET_FILL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUCKET_FILL_OPTIONS, GimpBucketFillOptions))
+#define GIMP_BUCKET_FILL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUCKET_FILL_OPTIONS, GimpBucketFillOptionsClass))
+#define GIMP_IS_BUCKET_FILL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUCKET_FILL_OPTIONS))
+#define GIMP_IS_BUCKET_FILL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUCKET_FILL_OPTIONS))
+#define GIMP_BUCKET_FILL_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUCKET_FILL_OPTIONS, GimpBucketFillOptionsClass))
+
+
+typedef struct _GimpBucketFillOptions GimpBucketFillOptions;
+typedef struct _GimpBucketFillOptionsPrivate GimpBucketFillOptionsPrivate;
+typedef struct _GimpPaintOptionsClass GimpBucketFillOptionsClass;
+
+struct _GimpBucketFillOptions
+{
+ GimpPaintOptions paint_options;
+
+ GimpBucketFillMode fill_mode;
+ GimpBucketFillArea fill_area;
+ gboolean fill_transparent;
+ gboolean sample_merged;
+ gboolean diagonal_neighbors;
+ gboolean antialias;
+ gboolean feather;
+ gdouble feather_radius;
+ gdouble threshold;
+
+ GtkWidget *line_art_busy_box;
+ GimpLineArtSource line_art_source;
+ gdouble line_art_threshold;
+ gint line_art_max_grow;
+ gint line_art_max_gap_length;
+
+ GimpSelectCriterion fill_criterion;
+
+ GimpBucketFillOptionsPrivate *priv;
+};
+
+
+GType gimp_bucket_fill_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_bucket_fill_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_BUCKET_FILL_OPTIONS_H__ */
diff --git a/app/tools/gimpbucketfilltool.c b/app/tools/gimpbucketfilltool.c
new file mode 100644
index 0000000..6197f87
--- /dev/null
+++ b/app/tools/gimpbucketfilltool.c
@@ -0,0 +1,1052 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpasync.h"
+#include "core/gimpcancelable.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpdrawable-bucket-fill.h"
+#include "core/gimpdrawable-edit.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpfilloptions.h"
+#include "core/gimpimage.h"
+#include "core/gimpimageproxy.h"
+#include "core/gimpitem.h"
+#include "core/gimplineart.h"
+#include "core/gimppickable.h"
+#include "core/gimppickable-contiguous-region.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimpwaitable.h"
+
+#include "gegl/gimp-gegl-nodes.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+
+#include "gimpbucketfilloptions.h"
+#include "gimpbucketfilltool.h"
+#include "gimpcoloroptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+struct _GimpBucketFillToolPrivate
+{
+ GimpLineArt *line_art;
+ GimpImage *line_art_image;
+ GimpDisplayShell *line_art_shell;
+
+ /* For preview */
+ GeglNode *graph;
+ GeglNode *fill_node;
+ GeglNode *offset_node;
+
+ GeglBuffer *fill_mask;
+
+ GimpDrawableFilter *filter;
+
+ /* Temp property save */
+ GimpBucketFillMode fill_mode;
+ GimpBucketFillArea fill_area;
+};
+
+
+/* local function prototypes */
+
+static void gimp_bucket_fill_tool_constructed (GObject *object);
+static void gimp_bucket_fill_tool_finalize (GObject *object);
+
+static void gimp_bucket_fill_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_bucket_fill_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_bucket_fill_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_bucket_fill_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_bucket_fill_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_bucket_fill_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_bucket_fill_tool_line_art_computing_start (GimpBucketFillTool *tool);
+static void gimp_bucket_fill_tool_line_art_computing_end (GimpBucketFillTool *tool);
+
+static gboolean gimp_bucket_fill_tool_coords_in_active_pickable
+ (GimpBucketFillTool *tool,
+ GimpDisplay *display,
+ const GimpCoords *coords);
+
+static void gimp_bucket_fill_tool_start (GimpBucketFillTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+static void gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpFillOptions *fill_options);
+static void gimp_bucket_fill_tool_commit (GimpBucketFillTool *tool);
+static void gimp_bucket_fill_tool_halt (GimpBucketFillTool *tool);
+static void gimp_bucket_fill_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+static void gimp_bucket_fill_tool_create_graph (GimpBucketFillTool *tool);
+
+static void gimp_bucket_fill_tool_reset_line_art (GimpBucketFillTool *tool);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpBucketFillTool, gimp_bucket_fill_tool,
+ GIMP_TYPE_COLOR_TOOL)
+
+#define parent_class gimp_bucket_fill_tool_parent_class
+
+
+void
+gimp_bucket_fill_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_BUCKET_FILL_TOOL,
+ GIMP_TYPE_BUCKET_FILL_OPTIONS,
+ gimp_bucket_fill_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND |
+ GIMP_CONTEXT_PROP_MASK_OPACITY |
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE |
+ GIMP_CONTEXT_PROP_MASK_PATTERN,
+ "gimp-bucket-fill-tool",
+ _("Bucket Fill"),
+ _("Bucket Fill Tool: Fill selected area with a color or pattern"),
+ N_("_Bucket Fill"), "<shift>B",
+ NULL, GIMP_HELP_TOOL_BUCKET_FILL,
+ GIMP_ICON_TOOL_BUCKET_FILL,
+ data);
+}
+
+static void
+gimp_bucket_fill_tool_class_init (GimpBucketFillToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_bucket_fill_tool_constructed;
+ object_class->finalize = gimp_bucket_fill_tool_finalize;
+
+ tool_class->button_press = gimp_bucket_fill_tool_button_press;
+ tool_class->motion = gimp_bucket_fill_tool_motion;
+ tool_class->button_release = gimp_bucket_fill_tool_button_release;
+ tool_class->modifier_key = gimp_bucket_fill_tool_modifier_key;
+ tool_class->cursor_update = gimp_bucket_fill_tool_cursor_update;
+ tool_class->options_notify = gimp_bucket_fill_tool_options_notify;
+}
+
+static void
+gimp_bucket_fill_tool_init (GimpBucketFillTool *bucket_fill_tool)
+{
+ GimpTool *tool = GIMP_TOOL (bucket_fill_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_BUCKET_FILL);
+ gimp_tool_control_set_action_opacity (tool->control,
+ "context/context-opacity-set");
+ gimp_tool_control_set_action_object_1 (tool->control,
+ "context/context-pattern-select-set");
+
+ bucket_fill_tool->priv =
+ gimp_bucket_fill_tool_get_instance_private (bucket_fill_tool);
+}
+
+static void
+gimp_bucket_fill_tool_constructed (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+ GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (object);
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ Gimp *gimp = GIMP_CONTEXT (options)->gimp;
+ GimpContext *context = gimp_get_user_context (gimp);
+ GimpLineArt *line_art;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_tool_control_set_motion_mode (tool->control,
+ options->fill_area == GIMP_BUCKET_FILL_LINE_ART ?
+ GIMP_MOTION_MODE_EXACT : GIMP_MOTION_MODE_COMPRESS);
+
+ line_art = gimp_line_art_new ();
+ g_object_bind_property (options, "fill-transparent",
+ line_art, "select-transparent",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (options, "line-art-threshold",
+ line_art, "threshold",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (options, "line-art-max-grow",
+ line_art, "max-grow",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (options, "line-art-max-gap-length",
+ line_art, "spline-max-length",
+ G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
+ g_object_bind_property (options, "line-art-max-gap-length",
+ line_art, "segment-max-length",
+ G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
+ g_signal_connect_swapped (line_art, "computing-start",
+ G_CALLBACK (gimp_bucket_fill_tool_line_art_computing_start),
+ tool);
+ g_signal_connect_swapped (line_art, "computing-end",
+ G_CALLBACK (gimp_bucket_fill_tool_line_art_computing_end),
+ tool);
+ gimp_line_art_bind_gap_length (line_art, TRUE);
+ bucket_tool->priv->line_art = line_art;
+
+ gimp_bucket_fill_tool_reset_line_art (bucket_tool);
+
+ g_signal_connect_swapped (options, "notify::line-art-source",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+ g_signal_connect_swapped (context, "display-changed",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+
+ GIMP_COLOR_TOOL (tool)->pick_target =
+ (options->fill_mode == GIMP_BUCKET_FILL_BG) ?
+ GIMP_COLOR_PICK_TARGET_BACKGROUND : GIMP_COLOR_PICK_TARGET_FOREGROUND;
+}
+
+static void
+gimp_bucket_fill_tool_finalize (GObject *object)
+{
+ GimpBucketFillTool *tool = GIMP_BUCKET_FILL_TOOL (object);
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ Gimp *gimp = GIMP_CONTEXT (options)->gimp;
+ GimpContext *context = gimp_get_user_context (gimp);
+
+ if (tool->priv->line_art_image)
+ {
+ g_signal_handlers_disconnect_by_data (gimp_image_get_layers (tool->priv->line_art_image), tool);
+ g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool);
+ tool->priv->line_art_image = NULL;
+ }
+ if (tool->priv->line_art_shell)
+ {
+ g_signal_handlers_disconnect_by_func (
+ tool->priv->line_art_shell,
+ gimp_bucket_fill_tool_reset_line_art,
+ tool);
+ tool->priv->line_art_shell = NULL;
+ }
+ g_clear_object (&tool->priv->line_art);
+
+ g_signal_handlers_disconnect_by_data (options, tool);
+ g_signal_handlers_disconnect_by_data (context, tool);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_bucket_fill_tool_coords_in_active_pickable (GimpBucketFillTool *tool,
+ GimpDisplay *display,
+ const GimpCoords *coords)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gboolean sample_merged = FALSE;
+
+ switch (options->fill_area)
+ {
+ case GIMP_BUCKET_FILL_SELECTION:
+ break;
+
+ case GIMP_BUCKET_FILL_SIMILAR_COLORS:
+ sample_merged = options->sample_merged;
+ break;
+
+ case GIMP_BUCKET_FILL_LINE_ART:
+ sample_merged = options->line_art_source ==
+ GIMP_LINE_ART_SOURCE_SAMPLE_MERGED;
+ break;
+ }
+
+ return gimp_image_coords_in_active_pickable (image, coords,
+ shell->show_all, sample_merged,
+ TRUE);
+}
+
+static void
+gimp_bucket_fill_tool_start (GimpBucketFillTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ g_return_if_fail (! tool->priv->filter);
+
+ gimp_line_art_freeze (tool->priv->line_art);
+
+ GIMP_TOOL (tool)->display = display;
+ GIMP_TOOL (tool)->drawable = drawable;
+
+ gimp_bucket_fill_tool_create_graph (tool);
+
+ tool->priv->filter = gimp_drawable_filter_new (drawable, _("Bucket fill"),
+ tool->priv->graph,
+ GIMP_ICON_TOOL_BUCKET_FILL);
+
+ gimp_drawable_filter_set_region (tool->priv->filter,
+ GIMP_FILTER_REGION_DRAWABLE);
+
+ /* We only set these here, and don't need to update it since we assume
+ * the settings can't change while the fill started.
+ */
+ gimp_drawable_filter_set_mode (tool->priv->filter,
+ gimp_context_get_paint_mode (context),
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (gimp_context_get_paint_mode (context)));
+ gimp_drawable_filter_set_opacity (tool->priv->filter,
+ gimp_context_get_opacity (context));
+
+ g_signal_connect (tool->priv->filter, "flush",
+ G_CALLBACK (gimp_bucket_fill_tool_filter_flush),
+ tool);
+}
+
+static void
+gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpFillOptions *fill_options)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (tool->priv->filter)
+ {
+ GeglBuffer *fill = NULL;
+ gdouble x = coords->x;
+ gdouble y = coords->y;
+
+ if (options->fill_area == GIMP_BUCKET_FILL_SIMILAR_COLORS)
+ {
+ if (! options->sample_merged)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x -= (gdouble) off_x;
+ y -= (gdouble) off_y;
+ }
+ fill = gimp_drawable_get_bucket_fill_buffer (drawable,
+ fill_options,
+ options->fill_transparent,
+ options->fill_criterion,
+ options->threshold / 255.0,
+ shell->show_all,
+ options->sample_merged,
+ options->diagonal_neighbors,
+ x, y,
+ &tool->priv->fill_mask,
+ &x, &y, NULL, NULL);
+ }
+ else
+ {
+ gint source_off_x = 0;
+ gint source_off_y = 0;
+
+ if (options->line_art_source != GIMP_LINE_ART_SOURCE_SAMPLE_MERGED)
+ {
+ GimpPickable *input;
+
+ input = gimp_line_art_get_input (tool->priv->line_art);
+ g_return_if_fail (GIMP_IS_ITEM (input));
+
+ gimp_item_get_offset (GIMP_ITEM (input), &source_off_x, &source_off_y);
+
+ x -= (gdouble) source_off_x;
+ y -= (gdouble) source_off_y;
+ }
+ fill = gimp_drawable_get_line_art_fill_buffer (drawable,
+ tool->priv->line_art,
+ fill_options,
+ options->line_art_source ==
+ GIMP_LINE_ART_SOURCE_SAMPLE_MERGED,
+ x, y,
+ &tool->priv->fill_mask,
+ &x, &y, NULL, NULL);
+ if (options->line_art_source != GIMP_LINE_ART_SOURCE_SAMPLE_MERGED)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x -= (gdouble) off_x - (gdouble) source_off_x;
+ y -= (gdouble) off_y - (gdouble) source_off_y;
+ }
+ }
+ if (fill)
+ {
+ gegl_node_set (tool->priv->fill_node,
+ "buffer", fill,
+ NULL);
+ gegl_node_set (tool->priv->offset_node,
+ "x", x,
+ "y", y,
+ NULL);
+ gimp_drawable_filter_apply (tool->priv->filter, NULL);
+ g_object_unref (fill);
+ }
+ }
+}
+
+static void
+gimp_bucket_fill_tool_commit (GimpBucketFillTool *tool)
+{
+ if (tool->priv->filter)
+ {
+ gimp_drawable_filter_commit (tool->priv->filter,
+ GIMP_PROGRESS (tool), FALSE);
+ gimp_image_flush (gimp_display_get_image (GIMP_TOOL (tool)->display));
+ }
+}
+
+static void
+gimp_bucket_fill_tool_halt (GimpBucketFillTool *tool)
+{
+ if (tool->priv->graph)
+ {
+ g_clear_object (&tool->priv->graph);
+ tool->priv->fill_node = NULL;
+ tool->priv->offset_node = NULL;
+ }
+
+ if (tool->priv->filter)
+ {
+ gimp_drawable_filter_abort (tool->priv->filter);
+ g_clear_object (&tool->priv->filter);
+ }
+
+ g_clear_object (&tool->priv->fill_mask);
+
+ if (gimp_line_art_is_frozen (tool->priv->line_art))
+ gimp_line_art_thaw (tool->priv->line_art);
+
+ GIMP_TOOL (tool)->display = NULL;
+ GIMP_TOOL (tool)->drawable = NULL;
+}
+
+static void
+gimp_bucket_fill_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_bucket_fill_tool_create_graph (GimpBucketFillTool *tool)
+{
+ GeglNode *graph;
+ GeglNode *output;
+ GeglNode *fill_node;
+ GeglNode *offset_node;
+
+ g_return_if_fail (! tool->priv->graph &&
+ ! tool->priv->fill_node &&
+ ! tool->priv->offset_node);
+
+ graph = gegl_node_new ();
+
+ fill_node = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ NULL);
+ offset_node = gegl_node_new_child (graph,
+ "operation", "gegl:translate",
+ NULL);
+ output = gegl_node_get_output_proxy (graph, "output");
+ gegl_node_link_many (fill_node, offset_node, output, NULL);
+
+ tool->priv->graph = graph;
+ tool->priv->fill_node = fill_node;
+ tool->priv->offset_node = offset_node;
+}
+
+static void
+gimp_bucket_fill_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ return;
+ }
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ gimp_tool_message_literal (tool, display,
+ _("Cannot modify the pixels of layer groups."));
+ return;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The active layer is not visible."));
+ return;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The active layer's pixels are locked."));
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ return;
+ }
+
+ if (options->fill_area == GIMP_BUCKET_FILL_LINE_ART &&
+ ! gimp_line_art_get_input (bucket_tool->priv->line_art))
+ {
+ gimp_tool_message_literal (tool, display,
+ _("No valid line art source selected."));
+ return;
+ }
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL &&
+ gimp_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
+ display, coords))
+ {
+ GimpContext *context = GIMP_CONTEXT (options);
+ GimpFillOptions *fill_options;
+ GError *error = NULL;
+
+ fill_options = gimp_fill_options_new (image->gimp, NULL, FALSE);
+
+ if (gimp_fill_options_set_by_fill_mode (fill_options, context,
+ options->fill_mode,
+ &error))
+ {
+ gimp_fill_options_set_antialias (fill_options, options->antialias);
+ gimp_fill_options_set_feather (fill_options, options->feather,
+ options->feather_radius);
+
+ gimp_context_set_opacity (GIMP_CONTEXT (fill_options),
+ gimp_context_get_opacity (context));
+ gimp_context_set_paint_mode (GIMP_CONTEXT (fill_options),
+ gimp_context_get_paint_mode (context));
+
+ if (options->fill_area == GIMP_BUCKET_FILL_SELECTION)
+ {
+ gimp_drawable_edit_fill (drawable, fill_options, NULL);
+ gimp_image_flush (image);
+ }
+ else /* GIMP_BUCKET_FILL_SIMILAR_COLORS || GIMP_BUCKET_FILL_LINE_ART */
+ {
+ gimp_bucket_fill_tool_start (bucket_tool, coords, display);
+ gimp_bucket_fill_tool_preview (bucket_tool, coords, display,
+ fill_options);
+ }
+ }
+ else
+ {
+ gimp_message_literal (display->gimp, G_OBJECT (display),
+ GIMP_MESSAGE_WARNING, error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (fill_options);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+}
+
+static void
+gimp_bucket_fill_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display);
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ return;
+
+ if (gimp_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
+ display, coords) &&
+ /* Fill selection only needs to happen once. */
+ options->fill_area != GIMP_BUCKET_FILL_SELECTION)
+ {
+ GimpContext *context = GIMP_CONTEXT (options);
+ GimpFillOptions *fill_options;
+ GError *error = NULL;
+
+ fill_options = gimp_fill_options_new (image->gimp, NULL, FALSE);
+
+ if (gimp_fill_options_set_by_fill_mode (fill_options, context,
+ options->fill_mode,
+ &error))
+ {
+ gimp_fill_options_set_antialias (fill_options, options->antialias);
+ gimp_fill_options_set_feather (fill_options, options->feather,
+ options->feather_radius);
+
+ gimp_context_set_opacity (GIMP_CONTEXT (fill_options),
+ gimp_context_get_opacity (context));
+ gimp_context_set_paint_mode (GIMP_CONTEXT (fill_options),
+ gimp_context_get_paint_mode (context));
+
+ gimp_bucket_fill_tool_preview (bucket_tool, coords, display,
+ fill_options);
+ }
+ else
+ {
+ gimp_message_literal (display->gimp, G_OBJECT (display),
+ GIMP_MESSAGE_WARNING, error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (fill_options);
+ }
+}
+
+static void
+gimp_bucket_fill_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time,
+ state, release_type,
+ display);
+ return;
+ }
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_bucket_fill_tool_commit (bucket_tool);
+
+ if (options->fill_area != GIMP_BUCKET_FILL_SELECTION)
+ gimp_bucket_fill_tool_halt (bucket_tool);
+
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+}
+
+static void
+gimp_bucket_fill_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+
+ if (key == GDK_MOD1_MASK)
+ {
+ if (press)
+ {
+ GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_mode = options->fill_mode;
+
+ switch (options->fill_mode)
+ {
+ case GIMP_BUCKET_FILL_FG:
+ g_object_set (options, "fill-mode", GIMP_BUCKET_FILL_BG, NULL);
+ break;
+
+ default:
+ /* GIMP_BUCKET_FILL_BG || GIMP_BUCKET_FILL_PATTERN */
+ g_object_set (options, "fill-mode", GIMP_BUCKET_FILL_FG, NULL);
+ break;
+
+ break;
+ }
+ }
+ else /* release */
+ {
+ g_object_set (options, "fill-mode",
+ GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_mode,
+ NULL);
+ }
+ }
+ else if (key == gimp_get_toggle_behavior_mask ())
+ {
+ GimpToolInfo *info = gimp_get_tool_info (display->gimp,
+ "gimp-color-picker-tool");
+
+ if (! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ switch (GIMP_COLOR_TOOL (tool)->pick_target)
+ {
+ case GIMP_COLOR_PICK_TARGET_BACKGROUND:
+ gimp_tool_push_status (tool, display,
+ _("Click in any image to pick the "
+ "background color"));
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_FOREGROUND:
+ default:
+ gimp_tool_push_status (tool, display,
+ _("Click in any image to pick the "
+ "foreground color"));
+ break;
+ }
+
+ GIMP_TOOL (tool)->display = display;
+ gimp_color_tool_enable (GIMP_COLOR_TOOL (tool),
+ GIMP_COLOR_OPTIONS (info->tool_options));
+ }
+ else
+ {
+ gimp_tool_pop_status (tool, display);
+ gimp_color_tool_disable (GIMP_COLOR_TOOL (tool));
+ GIMP_TOOL (tool)->display = NULL;
+ }
+ }
+ else if (key == gimp_get_extend_selection_mask ())
+ {
+ if (press)
+ {
+ GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_area = options->fill_area;
+
+ switch (options->fill_area)
+ {
+ case GIMP_BUCKET_FILL_SIMILAR_COLORS:
+ g_object_set (options,
+ "fill-area", GIMP_BUCKET_FILL_SELECTION,
+ NULL);
+ break;
+
+ default:
+ /* GIMP_BUCKET_FILL_SELECTION || GIMP_BUCKET_FILL_LINE_ART */
+ g_object_set (options,
+ "fill-area", GIMP_BUCKET_FILL_SIMILAR_COLORS,
+ NULL);
+ break;
+ }
+ }
+ else /* release */
+ {
+ g_object_set (options, "fill-area",
+ GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_area,
+ NULL);
+ }
+ }
+}
+
+static void
+gimp_bucket_fill_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_BAD;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (gimp_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
+ display, coords))
+ {
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (! gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) &&
+ ! gimp_item_is_content_locked (GIMP_ITEM (drawable)) &&
+ (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
+ config->edit_non_visible))
+ {
+ switch (options->fill_mode)
+ {
+ case GIMP_BUCKET_FILL_FG:
+ modifier = GIMP_CURSOR_MODIFIER_FOREGROUND;
+ break;
+
+ case GIMP_BUCKET_FILL_BG:
+ modifier = GIMP_CURSOR_MODIFIER_BACKGROUND;
+ break;
+
+ case GIMP_BUCKET_FILL_PATTERN:
+ modifier = GIMP_CURSOR_MODIFIER_PATTERN;
+ break;
+ }
+ }
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_bucket_fill_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+ GimpBucketFillOptions *bucket_options = GIMP_BUCKET_FILL_OPTIONS (options);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "fill-area"))
+ {
+ /* We want more motion events when the tool is used in a paint tool
+ * fashion. Unfortunately we only set exact mode in line art fill,
+ * because we can't as easily remove events from the similar color
+ * mode just because a point has already been selected (unless
+ * threshold were 0, but that's an edge case).
+ */
+ gimp_tool_control_set_motion_mode (tool->control,
+ bucket_options->fill_area == GIMP_BUCKET_FILL_LINE_ART ?
+ GIMP_MOTION_MODE_EXACT : GIMP_MOTION_MODE_COMPRESS);
+
+ gimp_bucket_fill_tool_reset_line_art (bucket_tool);
+ }
+ else if (! strcmp (pspec->name, "fill-mode"))
+ {
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ gimp_tool_pop_status (tool, tool->display);
+
+ switch (bucket_options->fill_mode)
+ {
+ case GIMP_BUCKET_FILL_BG:
+ GIMP_COLOR_TOOL (tool)->pick_target = GIMP_COLOR_PICK_TARGET_BACKGROUND;
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ gimp_tool_push_status (tool, tool->display,
+ _("Click in any image to pick the "
+ "background color"));
+ break;
+
+ case GIMP_BUCKET_FILL_FG:
+ default:
+ GIMP_COLOR_TOOL (tool)->pick_target = GIMP_COLOR_PICK_TARGET_FOREGROUND;
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ gimp_tool_push_status (tool, tool->display,
+ _("Click in any image to pick the "
+ "foreground color"));
+ break;
+ }
+ }
+}
+
+static void
+gimp_bucket_fill_tool_line_art_computing_start (GimpBucketFillTool *tool)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+
+ gtk_widget_show (options->line_art_busy_box);
+}
+
+static void
+gimp_bucket_fill_tool_line_art_computing_end (GimpBucketFillTool *tool)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+
+ gtk_widget_hide (options->line_art_busy_box);
+}
+
+static void
+gimp_bucket_fill_tool_reset_line_art (GimpBucketFillTool *tool)
+{
+ GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+ GimpLineArt *line_art = tool->priv->line_art;
+ GimpDisplayShell *shell = NULL;
+ GimpImage *image = NULL;
+
+ if (options->fill_area == GIMP_BUCKET_FILL_LINE_ART)
+ {
+ GimpContext *context;
+ GimpDisplay *display;
+
+ context = gimp_get_user_context (GIMP_CONTEXT (options)->gimp);
+ display = gimp_context_get_display (context);
+
+ if (display)
+ {
+ shell = gimp_display_get_shell (display);
+ image = gimp_display_get_image (display);
+ }
+ }
+
+ if (image != tool->priv->line_art_image)
+ {
+ if (tool->priv->line_art_image)
+ {
+ g_signal_handlers_disconnect_by_data (gimp_image_get_layers (tool->priv->line_art_image), tool);
+ g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool);
+ }
+
+ tool->priv->line_art_image = image;
+
+ if (image)
+ {
+ g_signal_connect_swapped (image, "active-layer-changed",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+ g_signal_connect_swapped (image, "active-channel-changed",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+
+ g_signal_connect_swapped (gimp_image_get_layers (image), "add",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+ g_signal_connect_swapped (gimp_image_get_layers (image), "remove",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+ g_signal_connect_swapped (gimp_image_get_layers (image), "reorder",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+ }
+ }
+
+ if (shell != tool->priv->line_art_shell)
+ {
+ if (tool->priv->line_art_shell)
+ {
+ g_signal_handlers_disconnect_by_func (
+ tool->priv->line_art_shell,
+ gimp_bucket_fill_tool_reset_line_art,
+ tool);
+ }
+
+ tool->priv->line_art_shell = shell;
+
+ if (shell)
+ {
+ g_signal_connect_swapped (shell, "notify::show-all",
+ G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
+ tool);
+ }
+ }
+
+ if (image)
+ {
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ drawable = NULL;
+
+ if (options->line_art_source == GIMP_LINE_ART_SOURCE_SAMPLE_MERGED)
+ {
+ GimpImageProxy *image_proxy = gimp_image_proxy_new (image);
+
+ gimp_image_proxy_set_show_all (image_proxy, shell->show_all);
+
+ gimp_line_art_set_input (line_art, GIMP_PICKABLE (image_proxy));
+
+ g_object_unref (image_proxy);
+ }
+ else if (drawable)
+ {
+ GimpItem *parent;
+ GimpContainer *container;
+ GimpObject *neighbour = NULL;
+ GimpPickable *source = NULL;
+ gint index;
+
+ parent = gimp_item_get_parent (GIMP_ITEM (drawable));
+ if (parent)
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
+ else
+ container = gimp_image_get_layers (image);
+
+ index = gimp_item_get_index (GIMP_ITEM (drawable));
+
+ if (options->line_art_source == GIMP_LINE_ART_SOURCE_ACTIVE_LAYER)
+ source = GIMP_PICKABLE (drawable);
+ else if (options->line_art_source == GIMP_LINE_ART_SOURCE_LOWER_LAYER)
+ neighbour = gimp_container_get_child_by_index (container, index + 1);
+ else if (options->line_art_source == GIMP_LINE_ART_SOURCE_UPPER_LAYER)
+ neighbour = gimp_container_get_child_by_index (container, index - 1);
+
+ source = neighbour ? GIMP_PICKABLE (neighbour) : source;
+ gimp_line_art_set_input (line_art, source);
+ }
+ else
+ {
+ gimp_line_art_set_input (line_art, NULL);
+ }
+ }
+ else
+ {
+ gimp_line_art_set_input (line_art, NULL);
+ }
+}
diff --git a/app/tools/gimpbucketfilltool.h b/app/tools/gimpbucketfilltool.h
new file mode 100644
index 0000000..37a1b96
--- /dev/null
+++ b/app/tools/gimpbucketfilltool.h
@@ -0,0 +1,58 @@
+/* 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_BUCKET_FILL_TOOL_H__
+#define __GIMP_BUCKET_FILL_TOOL_H__
+
+
+#include "gimpcolortool.h"
+
+
+#define GIMP_TYPE_BUCKET_FILL_TOOL (gimp_bucket_fill_tool_get_type ())
+#define GIMP_BUCKET_FILL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUCKET_FILL_TOOL, GimpBucketFillTool))
+#define GIMP_BUCKET_FILL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUCKET_FILL_TOOL, GimpBucketFillToolClass))
+#define GIMP_IS_BUCKET_FILL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUCKET_FILL_TOOL))
+#define GIMP_IS_BUCKET_FILL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUCKET_FILL_TOOL))
+#define GIMP_BUCKET_FILL_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUCKET_FILL_TOOL, GimpBucketFillToolClass))
+
+#define GIMP_BUCKET_FILL_TOOL_GET_OPTIONS(t) (GIMP_BUCKET_FILL_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpBucketFillTool GimpBucketFillTool;
+typedef struct _GimpBucketFillToolClass GimpBucketFillToolClass;
+typedef struct _GimpBucketFillToolPrivate GimpBucketFillToolPrivate;
+
+struct _GimpBucketFillTool
+{
+ GimpColorTool parent_instance;
+
+ GimpBucketFillToolPrivate *priv;
+};
+
+struct _GimpBucketFillToolClass
+{
+ GimpColorToolClass parent_class;
+};
+
+
+void gimp_bucket_fill_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_bucket_fill_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_BUCKET_FILL_TOOL_H__ */
diff --git a/app/tools/gimpbycolorselecttool.c b/app/tools/gimpbycolorselecttool.c
new file mode 100644
index 0000000..4a54502
--- /dev/null
+++ b/app/tools/gimpbycolorselecttool.c
@@ -0,0 +1,143 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbycolorselecttool.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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimppickable.h"
+#include "core/gimppickable-contiguous-region.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpbycolorselecttool.h"
+#include "gimpregionselectoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static GeglBuffer * gimp_by_color_select_tool_get_mask (GimpRegionSelectTool *region_select,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE (GimpByColorSelectTool, gimp_by_color_select_tool,
+ GIMP_TYPE_REGION_SELECT_TOOL)
+
+#define parent_class gimp_by_color_select_tool_parent_class
+
+
+void
+gimp_by_color_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_BY_COLOR_SELECT_TOOL,
+ GIMP_TYPE_REGION_SELECT_OPTIONS,
+ gimp_region_select_options_gui,
+ 0,
+ "gimp-by-color-select-tool",
+ _("Select by Color"),
+ _("Select by Color Tool: Select regions with similar colors"),
+ N_("_By Color Select"), "<shift>O",
+ NULL, GIMP_HELP_TOOL_BY_COLOR_SELECT,
+ GIMP_ICON_TOOL_BY_COLOR_SELECT,
+ data);
+}
+
+static void
+gimp_by_color_select_tool_class_init (GimpByColorSelectToolClass *klass)
+{
+ GimpRegionSelectToolClass *region_class;
+
+ region_class = GIMP_REGION_SELECT_TOOL_CLASS (klass);
+
+ region_class->undo_desc = C_("command", "Select by Color");
+ region_class->get_mask = gimp_by_color_select_tool_get_mask;
+}
+
+static void
+gimp_by_color_select_tool_init (GimpByColorSelectTool *by_color_select)
+{
+ GimpTool *tool = GIMP_TOOL (by_color_select);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_HAND);
+}
+
+static GeglBuffer *
+gimp_by_color_select_tool_get_mask (GimpRegionSelectTool *region_select,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (region_select);
+ GimpSelectionOptions *sel_options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpPickable *pickable;
+ GimpRGB srgb;
+ gint x, y;
+
+ x = region_select->x;
+ y = region_select->y;
+
+ if (! options->sample_merged)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x -= off_x;
+ y -= off_y;
+
+ pickable = GIMP_PICKABLE (drawable);
+ }
+ else
+ {
+ pickable = GIMP_PICKABLE (image);
+ }
+
+ gimp_pickable_flush (pickable);
+
+ if (gimp_pickable_get_color_at (pickable, x, y, &srgb))
+ {
+ GimpRGB color;
+
+ gimp_pickable_srgb_to_image_color (pickable, &srgb, &color);
+
+ return gimp_pickable_contiguous_region_by_color (pickable,
+ sel_options->antialias,
+ options->threshold / 255.0,
+ options->select_transparent,
+ options->select_criterion,
+ &color);
+ }
+
+ return NULL;
+}
diff --git a/app/tools/gimpbycolorselecttool.h b/app/tools/gimpbycolorselecttool.h
new file mode 100644
index 0000000..92768ff
--- /dev/null
+++ b/app/tools/gimpbycolorselecttool.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbycolorselectool.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_BY_COLOR_SELECT_TOOL_H__
+#define __GIMP_BY_COLOR_SELECT_TOOL_H__
+
+
+#include "gimpregionselecttool.h"
+
+
+#define GIMP_TYPE_BY_COLOR_SELECT_TOOL (gimp_by_color_select_tool_get_type ())
+#define GIMP_BY_COLOR_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BY_COLOR_SELECT_TOOL, GimpByColorSelectTool))
+#define GIMP_BY_COLOR_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BY_COLOR_SELECT_TOOL, GimpByColorSelectToolClass))
+#define GIMP_IS_BY_COLOR_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BY_COLOR_SELECT_TOOL))
+#define GIMP_IS_BY_COLOR_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BY_COLOR_SELECT_TOOL))
+#define GIMP_BY_COLOR_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BY_COLOR_SELECT_TOOL, GimpByColorSelectToolClass))
+
+
+typedef struct _GimpByColorSelectTool GimpByColorSelectTool;
+typedef struct _GimpByColorSelectToolClass GimpByColorSelectToolClass;
+
+struct _GimpByColorSelectTool
+{
+ GimpRegionSelectTool parent_instance;
+};
+
+struct _GimpByColorSelectToolClass
+{
+ GimpRegionSelectToolClass parent_class;
+};
+
+
+void gimp_by_color_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_by_color_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_BY_COLOR_SELECT_TOOL_H__ */
diff --git a/app/tools/gimpcageoptions.c b/app/tools/gimpcageoptions.c
new file mode 100644
index 0000000..0b1aecc
--- /dev/null
+++ b/app/tools/gimpcageoptions.c
@@ -0,0 +1,152 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpcageoptions.c
+ * Copyright (C) 2010 Michael Muré <batolettre@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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "gimpcageoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CAGE_MODE,
+ PROP_FILL_PLAIN_COLOR
+};
+
+
+static void gimp_cage_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_cage_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpCageOptions, gimp_cage_options,
+ GIMP_TYPE_TOOL_OPTIONS)
+
+#define parent_class gimp_cage_options_parent_class
+
+
+static void
+gimp_cage_options_class_init (GimpCageOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_cage_options_set_property;
+ object_class->get_property = gimp_cage_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_CAGE_MODE,
+ "cage-mode",
+ NULL, NULL,
+ GIMP_TYPE_CAGE_MODE,
+ GIMP_CAGE_MODE_CAGE_CHANGE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FILL_PLAIN_COLOR,
+ "fill-plain-color",
+ _("Fill the original position\n"
+ "of the cage with a color"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_cage_options_init (GimpCageOptions *options)
+{
+}
+
+static void
+gimp_cage_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCageOptions *options = GIMP_CAGE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_CAGE_MODE:
+ options->cage_mode = g_value_get_enum (value);
+ break;
+ case PROP_FILL_PLAIN_COLOR:
+ options->fill_plain_color = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_cage_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCageOptions *options = GIMP_CAGE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_CAGE_MODE:
+ g_value_set_enum (value, options->cage_mode);
+ break;
+ case PROP_FILL_PLAIN_COLOR:
+ g_value_set_boolean (value, options->fill_plain_color);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_cage_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *mode;
+ GtkWidget *button;
+
+ mode = gimp_prop_enum_radio_box_new (config, "cage-mode", 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), mode, FALSE, FALSE, 0);
+ gtk_widget_show (mode);
+
+ button = gimp_prop_check_button_new (config, "fill-plain-color", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ return vbox;
+}
diff --git a/app/tools/gimpcageoptions.h b/app/tools/gimpcageoptions.h
new file mode 100644
index 0000000..b4e2fcd
--- /dev/null
+++ b/app/tools/gimpcageoptions.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpcageoptions.h
+ * Copyright (C) 2010 Michael Muré <batolettre@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_CAGE_OPTIONS_H__
+#define __GIMP_CAGE_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_CAGE_OPTIONS (gimp_cage_options_get_type ())
+#define GIMP_CAGE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CAGE_OPTIONS, GimpCageOptions))
+#define GIMP_CAGE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CAGE_OPTIONS, GimpCageOptionsClass))
+#define GIMP_IS_CAGE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CAGE_OPTIONS))
+#define GIMP_IS_CAGE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CAGE_OPTIONS))
+#define GIMP_CAGE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CAGE_OPTIONS, GimpCageOptionsClass))
+
+
+typedef struct _GimpCageOptions GimpCageOptions;
+typedef struct _GimpCageOptionsClass GimpCageOptionsClass;
+
+struct _GimpCageOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpCageMode cage_mode;
+ gboolean fill_plain_color;
+};
+
+struct _GimpCageOptionsClass
+{
+ GimpToolOptionsClass parent_class;
+};
+
+
+GType gimp_cage_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_cage_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_CAGE_OPTIONS_H__ */
diff --git a/app/tools/gimpcagetool.c b/app/tools/gimpcagetool.c
new file mode 100644
index 0000000..37bb4c2
--- /dev/null
+++ b/app/tools/gimpcagetool.c
@@ -0,0 +1,1301 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpcagetool.c
+ * Copyright (C) 2010 Michael Muré <batolettre@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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/gimpcageconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+
+#include "gimpcagetool.h"
+#include "gimpcageoptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+/* XXX: if this state list is updated, in particular if for some reason,
+ a new CAGE_STATE_* was to be inserted after CAGE_STATE_CLOSING, check
+ if the function gimp_cage_tool_is_complete() has to be updated.
+ Current algorithm is that all DEFORM_* states are complete states,
+ and all CAGE_* states are incomplete states. */
+enum
+{
+ CAGE_STATE_INIT,
+ CAGE_STATE_WAIT,
+ CAGE_STATE_MOVE_HANDLE,
+ CAGE_STATE_SELECTING,
+ CAGE_STATE_CLOSING,
+ DEFORM_STATE_WAIT,
+ DEFORM_STATE_MOVE_HANDLE,
+ DEFORM_STATE_SELECTING
+};
+
+
+static gboolean gimp_cage_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_cage_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_cage_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_cage_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_cage_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_cage_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_cage_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_cage_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_cage_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_cage_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_cage_tool_start (GimpCageTool *ct,
+ GimpDisplay *display);
+static void gimp_cage_tool_halt (GimpCageTool *ct);
+static void gimp_cage_tool_commit (GimpCageTool *ct);
+
+static gint gimp_cage_tool_is_on_handle (GimpCageTool *ct,
+ GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ gint handle_size);
+static gint gimp_cage_tool_is_on_edge (GimpCageTool *ct,
+ gdouble x,
+ gdouble y,
+ gint handle_size);
+
+static gboolean gimp_cage_tool_is_complete (GimpCageTool *ct);
+static void gimp_cage_tool_remove_last_handle (GimpCageTool *ct);
+static void gimp_cage_tool_compute_coef (GimpCageTool *ct);
+static void gimp_cage_tool_create_filter (GimpCageTool *ct);
+static void gimp_cage_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+static void gimp_cage_tool_filter_update (GimpCageTool *ct);
+
+static void gimp_cage_tool_create_render_node (GimpCageTool *ct);
+static void gimp_cage_tool_render_node_update (GimpCageTool *ct);
+
+
+G_DEFINE_TYPE (GimpCageTool, gimp_cage_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_cage_tool_parent_class
+
+
+void
+gimp_cage_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_CAGE_TOOL,
+ GIMP_TYPE_CAGE_OPTIONS,
+ gimp_cage_options_gui,
+ 0,
+ "gimp-cage-tool",
+ _("Cage Transform"),
+ _("Cage Transform: Deform a selection with a cage"),
+ N_("_Cage Transform"), "<shift>G",
+ NULL, GIMP_HELP_TOOL_CAGE,
+ GIMP_ICON_TOOL_CAGE,
+ data);
+}
+
+static void
+gimp_cage_tool_class_init (GimpCageToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->initialize = gimp_cage_tool_initialize;
+ tool_class->control = gimp_cage_tool_control;
+ tool_class->button_press = gimp_cage_tool_button_press;
+ tool_class->button_release = gimp_cage_tool_button_release;
+ tool_class->key_press = gimp_cage_tool_key_press;
+ tool_class->motion = gimp_cage_tool_motion;
+ tool_class->cursor_update = gimp_cage_tool_cursor_update;
+ tool_class->oper_update = gimp_cage_tool_oper_update;
+ tool_class->options_notify = gimp_cage_tool_options_notify;
+
+ draw_tool_class->draw = gimp_cage_tool_draw;
+}
+
+static void
+gimp_cage_tool_init (GimpCageTool *self)
+{
+ GimpTool *tool = GIMP_TOOL (self);
+
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PERSPECTIVE);
+
+ self->config = g_object_new (GIMP_TYPE_CAGE_CONFIG, NULL);
+ self->hovering_handle = -1;
+ self->tool_state = CAGE_STATE_INIT;
+}
+
+static gboolean
+gimp_cage_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (! drawable)
+ return FALSE;
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot modify the pixels of layer groups."));
+ return FALSE;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer's pixels are locked."));
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ return FALSE;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer is not visible."));
+ return FALSE;
+ }
+
+ gimp_cage_tool_start (GIMP_CAGE_TOOL (tool), display);
+
+ return TRUE;
+}
+
+static void
+gimp_cage_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_cage_tool_halt (ct);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_cage_tool_commit (ct);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_cage_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ gint handle = -1;
+ gint edge = -1;
+
+ gimp_tool_control_activate (tool->control);
+
+ if (ct->config)
+ {
+ handle = gimp_cage_tool_is_on_handle (ct,
+ draw_tool,
+ display,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+ edge = gimp_cage_tool_is_on_edge (ct,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+ }
+
+ ct->movement_start_x = coords->x;
+ ct->movement_start_y = coords->y;
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_INIT:
+ /* No handle yet, we add the first one and switch the tool to
+ * moving handle state.
+ */
+ gimp_cage_config_add_cage_point (ct->config,
+ coords->x - ct->offset_x,
+ coords->y - ct->offset_y);
+ gimp_cage_config_select_point (ct->config, 0);
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ break;
+
+ case CAGE_STATE_WAIT:
+ if (handle == -1 && edge <= 0)
+ {
+ /* User clicked on the background, we add a new handle
+ * and move it
+ */
+ gimp_cage_config_add_cage_point (ct->config,
+ coords->x - ct->offset_x,
+ coords->y - ct->offset_y);
+ gimp_cage_config_select_point (ct->config,
+ gimp_cage_config_get_n_points (ct->config) - 1);
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ }
+ else if (handle == 0 && gimp_cage_config_get_n_points (ct->config) > 2)
+ {
+ /* User clicked on the first handle, we wait for
+ * release for closing the cage and switching to
+ * deform if possible
+ */
+ gimp_cage_config_select_point (ct->config, 0);
+ ct->tool_state = CAGE_STATE_CLOSING;
+ }
+ else if (handle >= 0)
+ {
+ /* User clicked on a handle, so we move it */
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ /* Multiple selection */
+
+ gimp_cage_config_toggle_point_selection (ct->config, handle);
+ }
+ else
+ {
+ /* New selection */
+
+ if (! gimp_cage_config_point_is_selected (ct->config, handle))
+ {
+ gimp_cage_config_select_point (ct->config, handle);
+ }
+ }
+
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ }
+ else if (edge > 0)
+ {
+ /* User clicked on an edge, we add a new handle here and select it */
+
+ gimp_cage_config_insert_cage_point (ct->config, edge,
+ coords->x, coords->y);
+ gimp_cage_config_select_point (ct->config, edge);
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ }
+ break;
+
+ case DEFORM_STATE_WAIT:
+ if (handle == -1)
+ {
+ /* User clicked on the background, we start a rubber band
+ * selection
+ */
+ ct->selection_start_x = coords->x;
+ ct->selection_start_y = coords->y;
+ ct->tool_state = DEFORM_STATE_SELECTING;
+ }
+
+ if (handle >= 0)
+ {
+ /* User clicked on a handle, so we move it */
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ /* Multiple selection */
+
+ gimp_cage_config_toggle_point_selection (ct->config, handle);
+ }
+ else
+ {
+ /* New selection */
+
+ if (! gimp_cage_config_point_is_selected (ct->config, handle))
+ {
+ gimp_cage_config_select_point (ct->config, handle);
+ }
+ }
+
+ ct->tool_state = DEFORM_STATE_MOVE_HANDLE;
+ }
+ break;
+ }
+}
+
+void
+gimp_cage_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (ct));
+
+ gimp_tool_control_halt (tool->control);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* Cancelling */
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_CLOSING:
+ ct->tool_state = CAGE_STATE_WAIT;
+ break;
+
+ case CAGE_STATE_MOVE_HANDLE:
+ gimp_cage_config_remove_last_cage_point (ct->config);
+ ct->tool_state = CAGE_STATE_WAIT;
+ break;
+
+ case CAGE_STATE_SELECTING:
+ ct->tool_state = CAGE_STATE_WAIT;
+ break;
+
+ case DEFORM_STATE_MOVE_HANDLE:
+ gimp_cage_tool_filter_update (ct);
+ ct->tool_state = DEFORM_STATE_WAIT;
+ break;
+
+ case DEFORM_STATE_SELECTING:
+ ct->tool_state = DEFORM_STATE_WAIT;
+ break;
+ }
+
+ gimp_cage_config_reset_displacement (ct->config);
+ }
+ else
+ {
+ /* Normal release */
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_CLOSING:
+ ct->dirty_coef = TRUE;
+ gimp_cage_config_commit_displacement (ct->config);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CLICK)
+ g_object_set (options, "cage-mode", GIMP_CAGE_MODE_DEFORM, NULL);
+ break;
+
+ case CAGE_STATE_MOVE_HANDLE:
+ ct->dirty_coef = TRUE;
+ ct->tool_state = CAGE_STATE_WAIT;
+ gimp_cage_config_commit_displacement (ct->config);
+ break;
+
+ case CAGE_STATE_SELECTING:
+ {
+ GeglRectangle area =
+ { MIN (ct->selection_start_x, coords->x) - ct->offset_x,
+ MIN (ct->selection_start_y, coords->y) - ct->offset_y,
+ ABS (ct->selection_start_x - coords->x),
+ ABS (ct->selection_start_y - coords->y) };
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ gimp_cage_config_select_add_area (ct->config,
+ GIMP_CAGE_MODE_CAGE_CHANGE,
+ area);
+ }
+ else
+ {
+ gimp_cage_config_select_area (ct->config,
+ GIMP_CAGE_MODE_CAGE_CHANGE,
+ area);
+ }
+
+ ct->tool_state = CAGE_STATE_WAIT;
+ }
+ break;
+
+ case DEFORM_STATE_MOVE_HANDLE:
+ ct->tool_state = DEFORM_STATE_WAIT;
+ gimp_cage_config_commit_displacement (ct->config);
+ gegl_node_set (ct->cage_node,
+ "config", ct->config,
+ NULL);
+ gimp_cage_tool_filter_update (ct);
+ break;
+
+ case DEFORM_STATE_SELECTING:
+ {
+ GeglRectangle area =
+ { MIN (ct->selection_start_x, coords->x) - ct->offset_x,
+ MIN (ct->selection_start_y, coords->y) - ct->offset_y,
+ ABS (ct->selection_start_x - coords->x),
+ ABS (ct->selection_start_y - coords->y) };
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ gimp_cage_config_select_add_area (ct->config,
+ GIMP_CAGE_MODE_DEFORM, area);
+ }
+ else
+ {
+ gimp_cage_config_select_area (ct->config,
+ GIMP_CAGE_MODE_DEFORM, area);
+ }
+
+ ct->tool_state = DEFORM_STATE_WAIT;
+ }
+ break;
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_cage_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ ct->cursor_x = coords->x;
+ ct->cursor_y = coords->y;
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_MOVE_HANDLE:
+ case CAGE_STATE_CLOSING:
+ case DEFORM_STATE_MOVE_HANDLE:
+ gimp_cage_config_add_displacement (ct->config,
+ options->cage_mode,
+ ct->cursor_x - ct->movement_start_x,
+ ct->cursor_y - ct->movement_start_y);
+ break;
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static gboolean
+gimp_cage_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+
+ if (! ct->config)
+ return FALSE;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ if (ct->tool_state == CAGE_STATE_WAIT)
+ {
+ if (gimp_cage_config_get_n_points (ct->config) != 0)
+ gimp_cage_tool_remove_last_handle (ct);
+ }
+ else if (ct->tool_state == DEFORM_STATE_WAIT)
+ {
+ gimp_cage_config_remove_selected_points (ct->config);
+
+ /* if the cage have less than 3 handles, we reopen it */
+ if (gimp_cage_config_get_n_points (ct->config) <= 2)
+ {
+ ct->tool_state = CAGE_STATE_WAIT;
+ }
+
+ gimp_cage_tool_compute_coef (ct);
+ gimp_cage_tool_render_node_update (ct);
+ }
+ return TRUE;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ if (! gimp_cage_tool_is_complete (ct) &&
+ gimp_cage_config_get_n_points (ct->config) > 2)
+ {
+ g_object_set (gimp_tool_get_options (tool),
+ "cage-mode", GIMP_CAGE_MODE_DEFORM,
+ NULL);
+ }
+ else if (ct->tool_state == DEFORM_STATE_WAIT)
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ }
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_cage_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (ct->config)
+ {
+ ct->hovering_handle = gimp_cage_tool_is_on_handle (ct,
+ draw_tool,
+ display,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+
+ ct->hovering_edge = gimp_cage_tool_is_on_edge (ct,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+ }
+
+ gimp_draw_tool_pause (draw_tool);
+
+ ct->cursor_x = coords->x;
+ ct->cursor_y = coords->y;
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+static void
+gimp_cage_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_PLUS;
+
+ if (tool->display)
+ {
+ if (ct->hovering_handle != -1)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+ else if (ct->hovering_edge != -1 &&
+ options->cage_mode == GIMP_CAGE_MODE_CAGE_CHANGE)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ }
+ else
+ {
+ if (gimp_cage_tool_is_complete (ct))
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+ else
+ {
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
+ gimp_item_is_content_locked (GIMP_ITEM (drawable)) ||
+ ! (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
+ config->edit_non_visible))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_cage_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! tool->display)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (strcmp (pspec->name, "cage-mode") == 0)
+ {
+ GimpCageMode mode;
+
+ g_object_get (options,
+ "cage-mode", &mode,
+ NULL);
+
+ if (mode == GIMP_CAGE_MODE_DEFORM)
+ {
+ /* switch to deform mode */
+
+ if (gimp_cage_config_get_n_points (ct->config) > 2)
+ {
+ gimp_cage_config_reset_displacement (ct->config);
+ gimp_cage_config_reverse_cage_if_needed (ct->config);
+ gimp_tool_push_status (tool, tool->display,
+ _("Press ENTER to commit the transform"));
+ ct->tool_state = DEFORM_STATE_WAIT;
+
+ if (! ct->render_node)
+ {
+ gimp_cage_tool_create_render_node (ct);
+ }
+
+ if (ct->dirty_coef)
+ {
+ gimp_cage_tool_compute_coef (ct);
+ gimp_cage_tool_render_node_update (ct);
+ }
+
+ if (! ct->filter)
+ gimp_cage_tool_create_filter (ct);
+
+ gimp_cage_tool_filter_update (ct);
+ }
+ else
+ {
+ g_object_set (options,
+ "cage-mode", GIMP_CAGE_MODE_CAGE_CHANGE,
+ NULL);
+ }
+ }
+ else
+ {
+ /* switch to edit mode */
+ if (ct->filter)
+ {
+ gimp_drawable_filter_abort (ct->filter);
+
+ gimp_tool_pop_status (tool, tool->display);
+ ct->tool_state = CAGE_STATE_WAIT;
+ }
+ }
+ }
+ else if (strcmp (pspec->name, "fill-plain-color") == 0)
+ {
+ if (ct->tool_state == DEFORM_STATE_WAIT)
+ {
+ gimp_cage_tool_render_node_update (ct);
+ gimp_cage_tool_filter_update (ct);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_cage_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (draw_tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpCageConfig *config = ct->config;
+ GimpCanvasGroup *stroke_group;
+ gint n_vertices;
+ gint i;
+ GimpHandleType handle;
+
+ n_vertices = gimp_cage_config_get_n_points (config);
+
+ if (n_vertices == 0)
+ return;
+
+ if (ct->tool_state == CAGE_STATE_INIT)
+ return;
+
+ stroke_group = gimp_draw_tool_add_stroke_group (draw_tool);
+
+ gimp_draw_tool_push_group (draw_tool, stroke_group);
+
+ /* If needed, draw line to the cursor. */
+ if (! gimp_cage_tool_is_complete (ct))
+ {
+ GimpVector2 last_point;
+
+ last_point = gimp_cage_config_get_point_coordinate (ct->config,
+ options->cage_mode,
+ n_vertices - 1);
+
+ gimp_draw_tool_add_line (draw_tool,
+ last_point.x + ct->offset_x,
+ last_point.y + ct->offset_y,
+ ct->cursor_x,
+ ct->cursor_y);
+ }
+
+ gimp_draw_tool_pop_group (draw_tool);
+
+ /* Draw the cage with handles. */
+ for (i = 0; i < n_vertices; i++)
+ {
+ GimpCanvasItem *item;
+ GimpVector2 point1, point2;
+
+ point1 = gimp_cage_config_get_point_coordinate (ct->config,
+ options->cage_mode,
+ i);
+ point1.x += ct->offset_x;
+ point1.y += ct->offset_y;
+
+ if (i > 0 || gimp_cage_tool_is_complete (ct))
+ {
+ gint index_point2;
+
+ if (i == 0)
+ index_point2 = n_vertices - 1;
+ else
+ index_point2 = i - 1;
+
+ point2 = gimp_cage_config_get_point_coordinate (ct->config,
+ options->cage_mode,
+ index_point2);
+ point2.x += ct->offset_x;
+ point2.y += ct->offset_y;
+
+ if (i != ct->hovering_edge ||
+ gimp_cage_tool_is_complete (ct))
+ {
+ gimp_draw_tool_push_group (draw_tool, stroke_group);
+ }
+
+ item = gimp_draw_tool_add_line (draw_tool,
+ point1.x,
+ point1.y,
+ point2.x,
+ point2.y);
+
+ if (i == ct->hovering_edge &&
+ ! gimp_cage_tool_is_complete (ct))
+ {
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+ else
+ {
+ gimp_draw_tool_pop_group (draw_tool);
+ }
+ }
+
+ if (gimp_cage_config_point_is_selected (ct->config, i))
+ {
+ if (i == ct->hovering_handle)
+ handle = GIMP_HANDLE_FILLED_SQUARE;
+ else
+ handle = GIMP_HANDLE_SQUARE;
+ }
+ else
+ {
+ if (i == ct->hovering_handle)
+ handle = GIMP_HANDLE_FILLED_CIRCLE;
+ else
+ handle = GIMP_HANDLE_CIRCLE;
+ }
+
+ item = gimp_draw_tool_add_handle (draw_tool,
+ handle,
+ point1.x,
+ point1.y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ if (i == ct->hovering_handle)
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+
+ if (ct->tool_state == DEFORM_STATE_SELECTING ||
+ ct->tool_state == CAGE_STATE_SELECTING)
+ {
+ gimp_draw_tool_add_rectangle (draw_tool,
+ FALSE,
+ MIN (ct->selection_start_x, ct->cursor_x),
+ MIN (ct->selection_start_y, ct->cursor_y),
+ ABS (ct->selection_start_x - ct->cursor_x),
+ ABS (ct->selection_start_y - ct->cursor_y));
+ }
+}
+
+static void
+gimp_cage_tool_start (GimpCageTool *ct,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (ct);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ g_clear_object (&ct->config);
+
+ g_clear_object (&ct->coef);
+ ct->dirty_coef = TRUE;
+
+ if (ct->filter)
+ {
+ gimp_drawable_filter_abort (ct->filter);
+ g_clear_object (&ct->filter);
+ }
+
+ if (ct->render_node)
+ {
+ g_clear_object (&ct->render_node);
+ ct->coef_node = NULL;
+ ct->cage_node = NULL;
+ }
+
+ ct->config = g_object_new (GIMP_TYPE_CAGE_CONFIG, NULL);
+ ct->hovering_handle = -1;
+ ct->hovering_edge = -1;
+ ct->tool_state = CAGE_STATE_INIT;
+
+ /* Setting up cage offset to convert the cage point coords to
+ * drawable coords
+ */
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable),
+ &ct->offset_x, &ct->offset_y);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (ct), display);
+}
+
+static void
+gimp_cage_tool_halt (GimpCageTool *ct)
+{
+ GimpTool *tool = GIMP_TOOL (ct);
+
+ g_clear_object (&ct->config);
+ g_clear_object (&ct->coef);
+ g_clear_object (&ct->render_node);
+ ct->coef_node = NULL;
+ ct->cage_node = NULL;
+
+ if (ct->filter)
+ {
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_abort (ct->filter);
+ g_clear_object (&ct->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+ ct->tool_state = CAGE_STATE_INIT;
+
+ g_object_set (gimp_tool_get_options (tool),
+ "cage-mode", GIMP_CAGE_MODE_CAGE_CHANGE,
+ NULL);
+}
+
+static void
+gimp_cage_tool_commit (GimpCageTool *ct)
+{
+ if (ct->filter)
+ {
+ GimpTool *tool = GIMP_TOOL (ct);
+
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_commit (ct->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&ct->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+}
+
+static gint
+gimp_cage_tool_is_on_handle (GimpCageTool *ct,
+ GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ gint handle_size)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpCageConfig *config = ct->config;
+ gdouble dist = G_MAXDOUBLE;
+ gint i;
+ GimpVector2 cage_point;
+ guint n_cage_vertices;
+
+ g_return_val_if_fail (GIMP_IS_CAGE_TOOL (ct), -1);
+
+ n_cage_vertices = gimp_cage_config_get_n_points (config);
+
+ if (n_cage_vertices == 0)
+ return -1;
+
+ for (i = 0; i < n_cage_vertices; i++)
+ {
+ cage_point = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ i);
+ cage_point.x += ct->offset_x;
+ cage_point.y += ct->offset_y;
+
+ dist = gimp_draw_tool_calc_distance_square (GIMP_DRAW_TOOL (draw_tool),
+ display,
+ x, y,
+ cage_point.x,
+ cage_point.y);
+
+ if (dist <= SQR (handle_size / 2))
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+gimp_cage_tool_is_on_edge (GimpCageTool *ct,
+ gdouble x,
+ gdouble y,
+ gint handle_size)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpCageConfig *config = ct->config;
+ gint i;
+ guint n_cage_vertices;
+ GimpVector2 A, B, C, AB, BC, AC;
+ gdouble lAB, lBC, lAC, lEB, lEC;
+
+ g_return_val_if_fail (GIMP_IS_CAGE_TOOL (ct), -1);
+
+ n_cage_vertices = gimp_cage_config_get_n_points (config);
+
+ if (n_cage_vertices < 2)
+ return -1;
+
+ A = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ n_cage_vertices-1);
+ B = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ 0);
+ C.x = x;
+ C.y = y;
+
+ for (i = 0; i < n_cage_vertices; i++)
+ {
+ gimp_vector2_sub (&AB, &A, &B);
+ gimp_vector2_sub (&BC, &B, &C);
+ gimp_vector2_sub (&AC, &A, &C);
+
+ lAB = gimp_vector2_length (&AB);
+ lBC = gimp_vector2_length (&BC);
+ lAC = gimp_vector2_length (&AC);
+ lEB = lAB / 2 + (SQR (lBC) - SQR (lAC)) / (2 * lAB);
+ lEC = sqrt (SQR (lBC) - SQR (lEB));
+
+ if ((lEC < handle_size / 2) && (ABS (SQR (lBC) - SQR (lAC)) <= SQR (lAB)))
+ return i;
+
+ A = B;
+ B = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ (i+1) % n_cage_vertices);
+ }
+
+ return -1;
+}
+
+static gboolean
+gimp_cage_tool_is_complete (GimpCageTool *ct)
+{
+ return (ct->tool_state > CAGE_STATE_CLOSING);
+}
+
+static void
+gimp_cage_tool_remove_last_handle (GimpCageTool *ct)
+{
+ GimpCageConfig *config = ct->config;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (ct));
+
+ gimp_cage_config_remove_last_cage_point (config);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (ct));
+}
+
+static void
+gimp_cage_tool_compute_coef (GimpCageTool *ct)
+{
+ GimpCageConfig *config = ct->config;
+ GimpProgress *progress;
+ const Babl *format;
+ GeglNode *gegl;
+ GeglNode *input;
+ GeglNode *output;
+ GeglProcessor *processor;
+ GeglBuffer *buffer;
+ gdouble value;
+
+ progress = gimp_progress_start (GIMP_PROGRESS (ct), FALSE,
+ _("Computing Cage Coefficients"));
+
+ g_clear_object (&ct->coef);
+
+ format = babl_format_n (babl_type ("float"),
+ gimp_cage_config_get_n_points (config) * 2);
+
+
+ gegl = gegl_node_new ();
+
+ input = gegl_node_new_child (gegl,
+ "operation", "gimp:cage-coef-calc",
+ "config", ct->config,
+ NULL);
+
+ output = gegl_node_new_child (gegl,
+ "operation", "gegl:buffer-sink",
+ "buffer", &buffer,
+ "format", format,
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ output, "input");
+
+ processor = gegl_node_new_processor (output, NULL);
+
+ while (gegl_processor_work (processor, &value))
+ {
+ if (progress)
+ gimp_progress_set_value (progress, value);
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ g_object_unref (processor);
+
+ ct->coef = buffer;
+ g_object_unref (gegl);
+
+ ct->dirty_coef = FALSE;
+}
+
+static void
+gimp_cage_tool_create_render_node (GimpCageTool *ct)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GeglNode *render;
+ GeglNode *input;
+ GeglNode *output;
+
+ g_return_if_fail (ct->render_node == NULL);
+ /* render_node is not supposed to be recreated */
+
+ ct->render_node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (ct->render_node, "input");
+ output = gegl_node_get_output_proxy (ct->render_node, "output");
+
+ ct->coef_node = gegl_node_new_child (ct->render_node,
+ "operation", "gegl:buffer-source",
+ "buffer", ct->coef,
+ NULL);
+
+ ct->cage_node = gegl_node_new_child (ct->render_node,
+ "operation", "gimp:cage-transform",
+ "config", ct->config,
+ "fill-plain-color", options->fill_plain_color,
+ NULL);
+
+ render = gegl_node_new_child (ct->render_node,
+ "operation", "gegl:map-absolute",
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ ct->cage_node, "input");
+
+ gegl_node_connect_to (ct->coef_node, "output",
+ ct->cage_node, "aux");
+
+ gegl_node_connect_to (input, "output",
+ render, "input");
+
+ gegl_node_connect_to (ct->cage_node, "output",
+ render, "aux");
+
+ gegl_node_connect_to (render, "output",
+ output, "input");
+
+ gimp_gegl_progress_connect (ct->cage_node, GIMP_PROGRESS (ct),
+ _("Cage Transform"));
+}
+
+static void
+gimp_cage_tool_render_node_update (GimpCageTool *ct)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ gboolean fill;
+ GeglBuffer *buffer;
+
+ gegl_node_get (ct->cage_node,
+ "fill-plain-color", &fill,
+ NULL);
+
+ if (fill != options->fill_plain_color)
+ {
+ gegl_node_set (ct->cage_node,
+ "fill-plain-color", options->fill_plain_color,
+ NULL);
+ }
+
+ gegl_node_get (ct->coef_node,
+ "buffer", &buffer,
+ NULL);
+
+ if (buffer != ct->coef)
+ {
+ gegl_node_set (ct->coef_node,
+ "buffer", ct->coef,
+ NULL);
+ }
+
+ if (buffer)
+ g_object_unref (buffer);
+}
+
+static void
+gimp_cage_tool_create_filter (GimpCageTool *ct)
+{
+ if (! ct->render_node)
+ gimp_cage_tool_create_render_node (ct);
+
+ ct->filter = gimp_drawable_filter_new (GIMP_TOOL (ct)->drawable,
+ _("Cage transform"),
+ ct->render_node,
+ GIMP_ICON_TOOL_CAGE);
+ gimp_drawable_filter_set_region (ct->filter, GIMP_FILTER_REGION_DRAWABLE);
+
+ g_signal_connect (ct->filter, "flush",
+ G_CALLBACK (gimp_cage_tool_filter_flush),
+ ct);
+}
+
+static void
+gimp_cage_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_cage_tool_filter_update (GimpCageTool *ct)
+{
+ gimp_drawable_filter_apply (ct->filter, NULL);
+}
diff --git a/app/tools/gimpcagetool.h b/app/tools/gimpcagetool.h
new file mode 100644
index 0000000..91daec0
--- /dev/null
+++ b/app/tools/gimpcagetool.h
@@ -0,0 +1,85 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpcagetool.h
+ * Copyright (C) 2010 Michael Muré <batolettre@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_CAGE_TOOL_H__
+#define __GIMP_CAGE_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_CAGE_TOOL (gimp_cage_tool_get_type ())
+#define GIMP_CAGE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CAGE_TOOL, GimpCageTool))
+#define GIMP_CAGE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CAGE_TOOL, GimpCageToolClass))
+#define GIMP_IS_CAGE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CAGE_TOOL))
+#define GIMP_IS_CAGE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CAGE_TOOL))
+#define GIMP_CAGE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CAGE_TOOL, GimpCageToolClass))
+
+#define GIMP_CAGE_TOOL_GET_OPTIONS(t) (GIMP_CAGE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpCageTool GimpCageTool;
+typedef struct _GimpCageToolClass GimpCageToolClass;
+
+struct _GimpCageTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpCageConfig *config;
+
+ gint offset_x; /* used to convert the cage point coords */
+ gint offset_y; /* to drawable coords */
+
+ gdouble cursor_x; /* Hold the cursor x position */
+ gdouble cursor_y; /* Hold the cursor y position */
+
+ gdouble movement_start_x; /* Where the movement started */
+ gdouble movement_start_y; /* Where the movement started */
+
+ gdouble selection_start_x; /* Where the selection started */
+ gdouble selection_start_y; /* Where the selection started */
+
+ gint hovering_handle; /* Handle which the cursor is above */
+ gint hovering_edge; /* Edge which the cursor is above */
+
+ GeglBuffer *coef; /* Gegl buffer where the coefficient of the transformation are stored */
+ gboolean dirty_coef; /* Indicate if the coef are still valid */
+
+ GeglNode *render_node; /* Gegl node graph to render the transformation */
+ GeglNode *cage_node; /* Gegl node that compute the cage transform */
+ GeglNode *coef_node; /* Gegl node that read in the coef buffer */
+
+ gint tool_state; /* Current state in statemachine */
+
+ GimpDrawableFilter *filter; /* For preview */
+};
+
+struct _GimpCageToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_cage_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_cage_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CAGE_TOOL_H__ */
diff --git a/app/tools/gimpcloneoptions-gui.c b/app/tools/gimpcloneoptions-gui.c
new file mode 100644
index 0000000..a676c1d
--- /dev/null
+++ b/app/tools/gimpcloneoptions-gui.c
@@ -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/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimpcloneoptions.h"
+
+#include "widgets/gimpviewablebox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcloneoptions-gui.h"
+#include "gimppaintoptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean
+gimp_clone_options_sync_source (GBinding *binding,
+ const GValue *source_value,
+ GValue *target_value,
+ gpointer user_data)
+{
+ GimpCloneType type = g_value_get_enum (source_value);
+
+ g_value_set_boolean (target_value,
+ type == GPOINTER_TO_INT (user_data));
+
+ return TRUE;
+}
+
+GtkWidget *
+gimp_clone_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *combo;
+ GtkWidget *source_vbox;
+ GtkWidget *button;
+ GtkWidget *hbox;
+
+ /* the source frame */
+ frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the source type menu */
+ combo = gimp_prop_enum_combo_box_new (config, "clone-type", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Source"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), combo);
+ gtk_widget_show (combo);
+
+ source_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), source_vbox);
+ gtk_widget_show (source_vbox);
+
+ button = gimp_prop_check_button_new (config, "sample-merged", NULL);
+ gtk_box_pack_start (GTK_BOX (source_vbox), button, FALSE, FALSE, 0);
+
+ g_object_bind_property_full (config, "clone-type",
+ button, "visible",
+ G_BINDING_SYNC_CREATE,
+ gimp_clone_options_sync_source,
+ NULL,
+ GINT_TO_POINTER (GIMP_CLONE_IMAGE), NULL);
+
+ hbox = gimp_prop_pattern_box_new (NULL, GIMP_CONTEXT (tool_options),
+ NULL, 2,
+ "pattern-view-type", "pattern-view-size");
+ gtk_box_pack_start (GTK_BOX (source_vbox), hbox, FALSE, FALSE, 0);
+
+ g_object_bind_property_full (config, "clone-type",
+ hbox, "visible",
+ G_BINDING_SYNC_CREATE,
+ gimp_clone_options_sync_source,
+ NULL,
+ GINT_TO_POINTER (GIMP_CLONE_PATTERN), NULL);
+
+ combo = gimp_prop_enum_combo_box_new (config, "align-mode", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Alignment"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ return vbox;
+}
diff --git a/app/tools/gimpcloneoptions-gui.h b/app/tools/gimpcloneoptions-gui.h
new file mode 100644
index 0000000..09a178d
--- /dev/null
+++ b/app/tools/gimpcloneoptions-gui.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_CLONE_OPTIONS_GUI_H__
+#define __GIMP_CLONE_OPTIONS_GUI_H__
+
+
+GtkWidget * gimp_clone_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_CLONE_OPTIONS_GUI_H__ */
diff --git a/app/tools/gimpclonetool.c b/app/tools/gimpclonetool.c
new file mode 100644
index 0000000..32f8612
--- /dev/null
+++ b/app/tools/gimpclonetool.c
@@ -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/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "paint/gimpclone.h"
+#include "paint/gimpcloneoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpclonetool.h"
+#include "gimpcloneoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_clone_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable);
+
+
+G_DEFINE_TYPE (GimpCloneTool, gimp_clone_tool, GIMP_TYPE_SOURCE_TOOL)
+
+#define parent_class gimp_clone_tool_parent_class
+
+
+void
+gimp_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_CLONE_TOOL,
+ GIMP_TYPE_CLONE_OPTIONS,
+ gimp_clone_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_PATTERN,
+ "gimp-clone-tool",
+ _("Clone"),
+ _("Clone Tool: Selectively copy from an image or pattern, using a brush"),
+ N_("_Clone"), "C",
+ NULL, GIMP_HELP_TOOL_CLONE,
+ GIMP_ICON_TOOL_CLONE,
+ data);
+}
+
+static void
+gimp_clone_tool_class_init (GimpCloneToolClass *klass)
+{
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ paint_tool_class->is_alpha_only = gimp_clone_tool_is_alpha_only;
+}
+
+static void
+gimp_clone_tool_init (GimpCloneTool *clone)
+{
+ GimpTool *tool = GIMP_TOOL (clone);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_CLONE);
+ gimp_tool_control_set_action_object_2 (tool->control,
+ "context/context-pattern-select-set");
+
+ paint_tool->status = _("Click to clone");
+ paint_tool->status_ctrl = _("%s to set a new clone source");
+
+ source_tool->status_paint = _("Click to clone");
+ /* Translators: the translation of "Click" must be the first word */
+ source_tool->status_set_source = _("Click to set a new clone source");
+ source_tool->status_set_source_ctrl = _("%s to set a new clone source");
+}
+
+static gboolean
+gimp_clone_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable)
+{
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpLayerMode paint_mode = gimp_context_get_paint_mode (context);
+
+ return gimp_layer_mode_is_alpha_only (paint_mode);
+}
diff --git a/app/tools/gimpclonetool.h b/app/tools/gimpclonetool.h
new file mode 100644
index 0000000..9d74d5a
--- /dev/null
+++ b/app/tools/gimpclonetool.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 __GIMP_CLONE_TOOL_H__
+#define __GIMP_CLONE_TOOL_H__
+
+
+#include "gimpsourcetool.h"
+
+
+#define GIMP_TYPE_CLONE_TOOL (gimp_clone_tool_get_type ())
+#define GIMP_CLONE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CLONE_TOOL, GimpCloneTool))
+#define GIMP_CLONE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CLONE_TOOL, GimpCloneToolClass))
+#define GIMP_IS_CLONE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CLONE_TOOL))
+#define GIMP_IS_CLONE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CLONE_TOOL))
+#define GIMP_CLONE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CLONE_TOOL, GimpCloneToolClass))
+
+
+typedef struct _GimpCloneTool GimpCloneTool;
+typedef struct _GimpCloneToolClass GimpCloneToolClass;
+
+struct _GimpCloneTool
+{
+ GimpSourceTool parent_instance;
+};
+
+struct _GimpCloneToolClass
+{
+ GimpSourceToolClass parent_class;
+};
+
+
+void gimp_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_clone_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CLONE_TOOL_H__ */
diff --git a/app/tools/gimpcoloroptions.c b/app/tools/gimpcoloroptions.c
new file mode 100644
index 0000000..62a543a
--- /dev/null
+++ b/app/tools/gimpcoloroptions.c
@@ -0,0 +1,170 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+
+#include "gimpcoloroptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SAMPLE_MERGED,
+ PROP_SAMPLE_AVERAGE,
+ PROP_AVERAGE_RADIUS
+};
+
+
+static void gimp_color_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_color_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpColorOptions, gimp_color_options,
+ GIMP_TYPE_TOOL_OPTIONS)
+
+
+static void
+gimp_color_options_class_init (GimpColorOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_color_options_set_property;
+ object_class->get_property = gimp_color_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
+ "sample-merged",
+ _("Sample merged"),
+ _("Use merged color value from "
+ "all composited visible layers"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_AVERAGE,
+ "sample-average",
+ _("Sample average"),
+ _("Use averaged color value from "
+ "nearby pixels"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_AVERAGE_RADIUS,
+ "average-radius",
+ _("Radius"),
+ _("Color Picker Average Radius"),
+ 1.0, 300.0, 3.0,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_color_options_init (GimpColorOptions *options)
+{
+}
+
+static void
+gimp_color_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpColorOptions *options = GIMP_COLOR_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SAMPLE_MERGED:
+ options->sample_merged = g_value_get_boolean (value);
+ break;
+ case PROP_SAMPLE_AVERAGE:
+ options->sample_average = g_value_get_boolean (value);
+ break;
+ case PROP_AVERAGE_RADIUS:
+ options->average_radius = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_color_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpColorOptions *options = GIMP_COLOR_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SAMPLE_MERGED:
+ g_value_set_boolean (value, options->sample_merged);
+ break;
+ case PROP_SAMPLE_AVERAGE:
+ g_value_set_boolean (value, options->sample_average);
+ break;
+ case PROP_AVERAGE_RADIUS:
+ g_value_set_double (value, options->average_radius);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_color_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *frame;
+ GtkWidget *scale;
+
+ /* the sample average options */
+ scale = gimp_prop_spin_scale_new (config, "average-radius", NULL,
+ 1.0, 10.0, 0);
+
+ frame = gimp_prop_expanding_frame_new (config, "sample-average", NULL,
+ scale, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ /* The Sample merged checkbox. */
+ button = gimp_prop_check_button_new (config, "sample-merged", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ return vbox;
+}
diff --git a/app/tools/gimpcoloroptions.h b/app/tools/gimpcoloroptions.h
new file mode 100644
index 0000000..1d69438
--- /dev/null
+++ b/app/tools/gimpcoloroptions.h
@@ -0,0 +1,55 @@
+/* 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_COLOR_OPTIONS_H__
+#define __GIMP_COLOR_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_COLOR_OPTIONS (gimp_color_options_get_type ())
+#define GIMP_COLOR_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_OPTIONS, GimpColorOptions))
+#define GIMP_COLOR_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_OPTIONS, GimpColorOptionsClass))
+#define GIMP_IS_COLOR_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_OPTIONS))
+#define GIMP_IS_COLOR_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_OPTIONS))
+#define GIMP_COLOR_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_OPTIONS, GimpColorOptionsClass))
+
+
+typedef struct _GimpColorOptionsClass GimpColorOptionsClass;
+
+struct _GimpColorOptions
+{
+ GimpToolOptions parent_instance;
+
+ gboolean sample_merged;
+ gboolean sample_average;
+ gdouble average_radius;
+};
+
+struct _GimpColorOptionsClass
+{
+ GimpToolOptionsClass parent_instance;
+};
+
+
+GType gimp_color_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_color_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_COLOR_OPTIONS_H__ */
diff --git a/app/tools/gimpcolorpickeroptions.c b/app/tools/gimpcolorpickeroptions.c
new file mode 100644
index 0000000..f2ba08b
--- /dev/null
+++ b/app/tools/gimpcolorpickeroptions.c
@@ -0,0 +1,208 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcolorpickeroptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SAMPLE_AVERAGE, /* overrides a GimpColorOptions property */
+ PROP_PICK_TARGET,
+ PROP_USE_INFO_WINDOW,
+ PROP_FRAME1_MODE,
+ PROP_FRAME2_MODE
+};
+
+
+static void gimp_color_picker_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_color_picker_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpColorPickerOptions, gimp_color_picker_options,
+ GIMP_TYPE_COLOR_OPTIONS)
+
+
+static void
+gimp_color_picker_options_class_init (GimpColorPickerOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_color_picker_options_set_property;
+ object_class->get_property = gimp_color_picker_options_get_property;
+
+ /* override a GimpColorOptions property to get a different default value */
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_AVERAGE,
+ "sample-average",
+ _("Sample average"),
+ _("Use averaged color value from "
+ "nearby pixels"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_PICK_TARGET,
+ "pick-target",
+ _("Pick Target"),
+ _("Choose what the color picker will do"),
+ GIMP_TYPE_COLOR_PICK_TARGET,
+ GIMP_COLOR_PICK_TARGET_FOREGROUND,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_INFO_WINDOW,
+ "use-info-window",
+ _("Use info window"),
+ _("Open a floating dialog to view picked "
+ "color values in various color models"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FRAME1_MODE,
+ "frame1-mode",
+ "Frame 1 Mode", NULL,
+ GIMP_TYPE_COLOR_PICK_MODE,
+ GIMP_COLOR_PICK_MODE_PIXEL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FRAME2_MODE,
+ "frame2-mode",
+ "Frame 2 Mode", NULL,
+ GIMP_TYPE_COLOR_PICK_MODE,
+ GIMP_COLOR_PICK_MODE_RGB_PERCENT,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_color_picker_options_init (GimpColorPickerOptions *options)
+{
+}
+
+static void
+gimp_color_picker_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpColorPickerOptions *options = GIMP_COLOR_PICKER_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SAMPLE_AVERAGE:
+ GIMP_COLOR_OPTIONS (options)->sample_average = g_value_get_boolean (value);
+ break;
+ case PROP_PICK_TARGET:
+ options->pick_target = g_value_get_enum (value);
+ break;
+ case PROP_USE_INFO_WINDOW:
+ options->use_info_window = g_value_get_boolean (value);
+ break;
+ case PROP_FRAME1_MODE:
+ options->frame1_mode = g_value_get_enum (value);
+ break;
+ case PROP_FRAME2_MODE:
+ options->frame2_mode = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_color_picker_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpColorPickerOptions *options = GIMP_COLOR_PICKER_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SAMPLE_AVERAGE:
+ g_value_set_boolean (value,
+ GIMP_COLOR_OPTIONS (options)->sample_average);
+ break;
+ case PROP_PICK_TARGET:
+ g_value_set_enum (value, options->pick_target);
+ break;
+ case PROP_USE_INFO_WINDOW:
+ g_value_set_boolean (value, options->use_info_window);
+ break;
+ case PROP_FRAME1_MODE:
+ g_value_set_enum (value, options->frame1_mode);
+ break;
+ case PROP_FRAME2_MODE:
+ g_value_set_enum (value, options->frame2_mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_color_picker_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_color_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *frame;
+ gchar *str;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ /* the pick FG/BG frame */
+ str = g_strdup_printf (_("Pick Target (%s)"),
+ gimp_get_mod_string (toggle_mask));
+ frame = gimp_prop_enum_radio_frame_new (config, "pick-target", str, -1, -1);
+ g_free (str);
+
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ /* the use_info_window toggle button */
+ str = g_strdup_printf (_("Use info window (%s)"),
+ gimp_get_mod_string (extend_mask));
+ button = gimp_prop_check_button_new (config, "use-info-window", str);
+ g_free (str);
+
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ return vbox;
+}
diff --git a/app/tools/gimpcolorpickeroptions.h b/app/tools/gimpcolorpickeroptions.h
new file mode 100644
index 0000000..8fc7024
--- /dev/null
+++ b/app/tools/gimpcolorpickeroptions.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_COLOR_PICKER_OPTIONS_H__
+#define __GIMP_COLOR_PICKER_OPTIONS_H__
+
+
+#include "gimpcoloroptions.h"
+
+
+#define GIMP_TYPE_COLOR_PICKER_OPTIONS (gimp_color_picker_options_get_type ())
+#define GIMP_COLOR_PICKER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PICKER_OPTIONS, GimpColorPickerOptions))
+#define GIMP_COLOR_PICKER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PICKER_OPTIONS, GimpColorPickerOptionsClass))
+#define GIMP_IS_COLOR_PICKER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PICKER_OPTIONS))
+#define GIMP_IS_COLOR_PICKER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PICKER_OPTIONS))
+#define GIMP_COLOR_PICKER_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PICKER_OPTIONS, GimpColorPickerOptionsClass))
+
+
+typedef struct _GimpColorPickerOptions GimpColorPickerOptions;
+typedef struct _GimpToolOptionsClass GimpColorPickerOptionsClass;
+
+struct _GimpColorPickerOptions
+{
+ GimpColorOptions parent_instance;
+
+ GimpColorPickTarget pick_target;
+ gboolean use_info_window;
+ GimpColorPickMode frame1_mode;
+ GimpColorPickMode frame2_mode;
+};
+
+
+GType gimp_color_picker_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_color_picker_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_COLOR_PICKER_OPTIONS_H__ */
diff --git a/app/tools/gimpcolorpickertool.c b/app/tools/gimpcolorpickertool.c
new file mode 100644
index 0000000..7e9b117
--- /dev/null
+++ b/app/tools/gimpcolorpickertool.c
@@ -0,0 +1,455 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimpcolorframe.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolgui.h"
+
+#include "gimpcolorpickeroptions.h"
+#include "gimpcolorpickertool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_color_picker_tool_constructed (GObject *object);
+static void gimp_color_picker_tool_dispose (GObject *object);
+
+static void gimp_color_picker_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_color_picker_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_color_picker_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+
+static void gimp_color_picker_tool_picked (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color);
+
+static void gimp_color_picker_tool_info_create (GimpColorPickerTool *picker_tool,
+ GimpDisplay *display);
+static void gimp_color_picker_tool_info_response (GimpToolGui *gui,
+ gint response_id,
+ GimpColorPickerTool *picker_tool);
+static void gimp_color_picker_tool_info_update (GimpColorPickerTool *picker_tool,
+ GimpDisplay *display,
+ gboolean sample_average,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color,
+ gint x,
+ gint y);
+
+
+G_DEFINE_TYPE (GimpColorPickerTool, gimp_color_picker_tool,
+ GIMP_TYPE_COLOR_TOOL)
+
+#define parent_class gimp_color_picker_tool_parent_class
+
+
+void
+gimp_color_picker_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_COLOR_PICKER_TOOL,
+ GIMP_TYPE_COLOR_PICKER_OPTIONS,
+ gimp_color_picker_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-color-picker-tool",
+ _("Color Picker"),
+ _("Color Picker Tool: Set colors from image pixels"),
+ N_("C_olor Picker"), "O",
+ NULL, GIMP_HELP_TOOL_COLOR_PICKER,
+ GIMP_ICON_TOOL_COLOR_PICKER,
+ data);
+}
+
+static void
+gimp_color_picker_tool_class_init (GimpColorPickerToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpColorToolClass *color_tool_class = GIMP_COLOR_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_color_picker_tool_constructed;
+ object_class->dispose = gimp_color_picker_tool_dispose;
+
+ tool_class->control = gimp_color_picker_tool_control;
+ tool_class->modifier_key = gimp_color_picker_tool_modifier_key;
+ tool_class->oper_update = gimp_color_picker_tool_oper_update;
+
+ color_tool_class->picked = gimp_color_picker_tool_picked;
+}
+
+static void
+gimp_color_picker_tool_init (GimpColorPickerTool *picker_tool)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (picker_tool);
+
+ color_tool->pick_target = GIMP_COLOR_PICK_TARGET_FOREGROUND;
+}
+
+static void
+gimp_color_picker_tool_constructed (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_color_tool_enable (GIMP_COLOR_TOOL (object),
+ GIMP_COLOR_TOOL_GET_OPTIONS (tool));
+}
+
+static void
+gimp_color_picker_tool_dispose (GObject *object)
+{
+ GimpColorPickerTool *picker_tool = GIMP_COLOR_PICKER_TOOL (object);
+
+ g_clear_object (&picker_tool->gui);
+ picker_tool->color_area = NULL;
+ picker_tool->color_frame1 = NULL;
+ picker_tool->color_frame2 = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_color_picker_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpColorPickerTool *picker_tool = GIMP_COLOR_PICKER_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ if (picker_tool->gui)
+ gimp_tool_gui_hide (picker_tool->gui);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_color_picker_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpColorPickerOptions *options = GIMP_COLOR_PICKER_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_extend_selection_mask ())
+ {
+ g_object_set (options, "use-info-window", ! options->use_info_window,
+ NULL);
+ }
+ else if (key == gimp_get_toggle_behavior_mask ())
+ {
+ switch (options->pick_target)
+ {
+ case GIMP_COLOR_PICK_TARGET_FOREGROUND:
+ g_object_set (options,
+ "pick-target", GIMP_COLOR_PICK_TARGET_BACKGROUND,
+ NULL);
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_BACKGROUND:
+ g_object_set (options,
+ "pick-target", GIMP_COLOR_PICK_TARGET_FOREGROUND,
+ NULL);
+ break;
+
+ default:
+ break;
+ }
+
+ }
+}
+
+static void
+gimp_color_picker_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpColorPickerTool *picker_tool = GIMP_COLOR_PICKER_TOOL (tool);
+ GimpColorPickerOptions *options = GIMP_COLOR_PICKER_TOOL_GET_OPTIONS (tool);
+
+ GIMP_COLOR_TOOL (tool)->pick_target = options->pick_target;
+
+ gimp_tool_pop_status (tool, display);
+
+ if (proximity)
+ {
+ gchar *status_help = NULL;
+ GdkModifierType extend_mask = 0;
+ GdkModifierType toggle_mask;
+
+ if (! picker_tool->gui)
+ extend_mask = gimp_get_extend_selection_mask ();
+
+ toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ switch (options->pick_target)
+ {
+ case GIMP_COLOR_PICK_TARGET_NONE:
+ status_help = gimp_suggest_modifiers (_("Click in any image to view"
+ " its color"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_FOREGROUND:
+ status_help = gimp_suggest_modifiers (_("Click in any image to pick"
+ " the foreground color"),
+ (extend_mask | toggle_mask) &
+ ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_BACKGROUND:
+ status_help = gimp_suggest_modifiers (_("Click in any image to pick"
+ " the background color"),
+ (extend_mask | toggle_mask) &
+ ~state,
+ NULL, NULL, NULL);
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_PALETTE:
+ status_help = gimp_suggest_modifiers (_("Click in any image to add"
+ " the color to the palette"),
+ extend_mask & ~state,
+ NULL, NULL, NULL);
+ break;
+ }
+
+ if (status_help != NULL)
+ {
+ gimp_tool_push_status (tool, display, "%s", status_help);
+ g_free (status_help);
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+}
+
+static void
+gimp_color_picker_tool_picked (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color)
+{
+ GimpColorPickerTool *picker_tool = GIMP_COLOR_PICKER_TOOL (color_tool);
+ GimpColorPickerOptions *options;
+
+ options = GIMP_COLOR_PICKER_TOOL_GET_OPTIONS (color_tool);
+
+ if (options->use_info_window && ! picker_tool->gui)
+ gimp_color_picker_tool_info_create (picker_tool, display);
+
+ if (picker_tool->gui &&
+ (options->use_info_window ||
+ gimp_tool_gui_get_visible (picker_tool->gui)))
+ {
+ gimp_color_picker_tool_info_update (picker_tool, display,
+ GIMP_COLOR_OPTIONS (options)->sample_average,
+ sample_format, pixel, color,
+ (gint) floor (coords->x),
+ (gint) floor (coords->y));
+ }
+
+ GIMP_COLOR_TOOL_CLASS (parent_class)->picked (color_tool,
+ coords, display, pick_state,
+ sample_format, pixel, color);
+}
+
+static void
+gimp_color_picker_tool_info_create (GimpColorPickerTool *picker_tool,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (picker_tool);
+ GimpToolOptions *options = GIMP_TOOL_GET_OPTIONS (tool);
+ GimpContext *context = GIMP_CONTEXT (tool->tool_info->tool_options);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GtkWidget *hbox;
+ GtkWidget *frame;
+ GimpRGB color;
+
+ picker_tool->gui = gimp_tool_gui_new (tool->tool_info,
+ NULL,
+ _("Color Picker Information"),
+ NULL, NULL,
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ TRUE,
+
+ _("_Close"), GTK_RESPONSE_CLOSE,
+
+ NULL);
+
+ gimp_tool_gui_set_auto_overlay (picker_tool->gui, TRUE);
+ gimp_tool_gui_set_focus_on_map (picker_tool->gui, FALSE);
+ gimp_tool_gui_set_viewable (picker_tool->gui, GIMP_VIEWABLE (drawable));
+
+ g_signal_connect (picker_tool->gui, "response",
+ G_CALLBACK (gimp_color_picker_tool_info_response),
+ picker_tool);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (picker_tool->gui)),
+ hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ picker_tool->color_frame1 = gimp_color_frame_new ();
+ gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (picker_tool->color_frame1),
+ context->gimp->config->color_management);
+ gimp_color_frame_set_has_coords (GIMP_COLOR_FRAME (picker_tool->color_frame1),
+ TRUE);
+ g_object_bind_property (options, "frame1-mode",
+ picker_tool->color_frame1, "mode",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+ gtk_box_pack_start (GTK_BOX (hbox), picker_tool->color_frame1,
+ FALSE, FALSE, 0);
+ gtk_widget_show (picker_tool->color_frame1);
+
+ picker_tool->color_frame2 = gimp_color_frame_new ();
+ gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (picker_tool->color_frame2),
+ context->gimp->config->color_management);
+ g_object_bind_property (options, "frame2-mode",
+ picker_tool->color_frame2, "mode",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+ gtk_box_pack_start (GTK_BOX (hbox), picker_tool->color_frame2,
+ FALSE, FALSE, 0);
+ gtk_widget_show (picker_tool->color_frame2);
+
+ frame = gtk_frame_new (NULL);
+ gimp_widget_set_fully_opaque (frame, TRUE);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ gimp_rgba_set (&color, 0.0, 0.0, 0.0, 0.0);
+ picker_tool->color_area =
+ gimp_color_area_new (&color,
+ gimp_drawable_has_alpha (drawable) ?
+ GIMP_COLOR_AREA_LARGE_CHECKS :
+ GIMP_COLOR_AREA_FLAT,
+ GDK_BUTTON1_MASK | GDK_BUTTON2_MASK);
+ gimp_color_area_set_color_config (GIMP_COLOR_AREA (picker_tool->color_area),
+ context->gimp->config->color_management);
+ gtk_widget_set_size_request (picker_tool->color_area, 48, -1);
+ gtk_drag_dest_unset (picker_tool->color_area);
+ gtk_container_add (GTK_CONTAINER (frame), picker_tool->color_area);
+ gtk_widget_show (picker_tool->color_area);
+}
+
+static void
+gimp_color_picker_tool_info_response (GimpToolGui *gui,
+ gint response_id,
+ GimpColorPickerTool *picker_tool)
+{
+ GimpTool *tool = GIMP_TOOL (picker_tool);
+
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, NULL);
+}
+
+static void
+gimp_color_picker_tool_info_update (GimpColorPickerTool *picker_tool,
+ GimpDisplay *display,
+ gboolean sample_average,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color,
+ gint x,
+ gint y)
+{
+ GimpTool *tool = GIMP_TOOL (picker_tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ tool->display = display;
+
+ gimp_tool_gui_set_shell (picker_tool->gui,
+ gimp_display_get_shell (display));
+ gimp_tool_gui_set_viewable (picker_tool->gui,
+ GIMP_VIEWABLE (drawable));
+
+ gimp_color_area_set_color (GIMP_COLOR_AREA (picker_tool->color_area),
+ color);
+
+ gimp_color_frame_set_color (GIMP_COLOR_FRAME (picker_tool->color_frame1),
+ sample_average, sample_format, pixel, color,
+ x, y);
+ gimp_color_frame_set_color (GIMP_COLOR_FRAME (picker_tool->color_frame2),
+ sample_average, sample_format, pixel, color,
+ x, y);
+
+ gimp_tool_gui_show (picker_tool->gui);
+}
diff --git a/app/tools/gimpcolorpickertool.h b/app/tools/gimpcolorpickertool.h
new file mode 100644
index 0000000..4967a52
--- /dev/null
+++ b/app/tools/gimpcolorpickertool.h
@@ -0,0 +1,60 @@
+/* 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_COLOR_PICKER_TOOL_H__
+#define __GIMP_COLOR_PICKER_TOOL_H__
+
+
+#include "gimpcolortool.h"
+
+
+#define GIMP_TYPE_COLOR_PICKER_TOOL (gimp_color_picker_tool_get_type ())
+#define GIMP_COLOR_PICKER_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PICKER_TOOL, GimpColorPickerTool))
+#define GIMP_COLOR_PICKER_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PICKER_TOOL, GimpColorPickerToolClass))
+#define GIMP_IS_COLOR_PICKER_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PICKER_TOOL))
+#define GIMP_IS_COLOR_PICKER_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PICKER_TOOL))
+#define GIMP_COLOR_PICKER_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PICKER_TOOL, GimpColorPickerToolClass))
+
+#define GIMP_COLOR_PICKER_TOOL_GET_OPTIONS(t) (GIMP_COLOR_PICKER_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpColorPickerTool GimpColorPickerTool;
+typedef struct _GimpColorPickerToolClass GimpColorPickerToolClass;
+
+struct _GimpColorPickerTool
+{
+ GimpColorTool parent_instance;
+
+ GimpToolGui *gui;
+ GtkWidget *color_area;
+ GtkWidget *color_frame1;
+ GtkWidget *color_frame2;
+};
+
+struct _GimpColorPickerToolClass
+{
+ GimpColorToolClass parent_class;
+};
+
+
+void gimp_color_picker_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_color_picker_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_COLOR_PICKER_TOOL_H__ */
diff --git a/app/tools/gimpcolortool.c b/app/tools/gimpcolortool.c
new file mode 100644
index 0000000..683a713
--- /dev/null
+++ b/app/tools/gimpcolortool.c
@@ -0,0 +1,702 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpdata.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-pick-color.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpimage-sample-points.h"
+#include "core/gimpmarshal.h"
+#include "core/gimpsamplepoint.h"
+
+#include "widgets/gimpcolormapeditor.h"
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpdockable.h"
+#include "widgets/gimpdockcontainer.h"
+#include "widgets/gimppaletteeditor.h"
+#include "widgets/gimpwidgets-utils.h"
+#include "widgets/gimpwindowstrategy.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+
+#include "gimpcoloroptions.h"
+#include "gimpcolortool.h"
+#include "gimpsamplepointtool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PICKED,
+ LAST_SIGNAL
+};
+
+
+/* local function prototypes */
+
+static void gimp_color_tool_finalize (GObject *object);
+
+static void gimp_color_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_color_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_color_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_color_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_color_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_color_tool_draw (GimpDrawTool *draw_tool);
+
+static gboolean
+ gimp_color_tool_real_can_pick (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+static gboolean gimp_color_tool_real_pick (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_color_tool_real_picked (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color);
+
+static gboolean gimp_color_tool_can_pick (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+static void gimp_color_tool_pick (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state);
+
+
+G_DEFINE_TYPE (GimpColorTool, gimp_color_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_color_tool_parent_class
+
+static guint gimp_color_tool_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_color_tool_class_init (GimpColorToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ gimp_color_tool_signals[PICKED] =
+ g_signal_new ("picked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpColorToolClass, picked),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED,
+ G_TYPE_NONE, 6,
+ G_TYPE_POINTER,
+ GIMP_TYPE_DISPLAY,
+ GIMP_TYPE_COLOR_PICK_STATE,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ GIMP_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ object_class->finalize = gimp_color_tool_finalize;
+
+ tool_class->button_press = gimp_color_tool_button_press;
+ tool_class->button_release = gimp_color_tool_button_release;
+ tool_class->motion = gimp_color_tool_motion;
+ tool_class->oper_update = gimp_color_tool_oper_update;
+ tool_class->cursor_update = gimp_color_tool_cursor_update;
+
+ draw_class->draw = gimp_color_tool_draw;
+
+ klass->can_pick = gimp_color_tool_real_can_pick;
+ klass->pick = gimp_color_tool_real_pick;
+ klass->picked = gimp_color_tool_real_picked;
+}
+
+static void
+gimp_color_tool_init (GimpColorTool *color_tool)
+{
+ GimpTool *tool = GIMP_TOOL (color_tool);
+
+ gimp_tool_control_set_action_size (tool->control,
+ "tools/tools-color-average-radius-set");
+}
+
+static void
+gimp_color_tool_finalize (GObject *object)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (object);
+
+ g_clear_object (&color_tool->options);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_color_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (tool);
+
+ if (color_tool->enabled)
+ {
+ if (color_tool->sample_point)
+ {
+ gimp_sample_point_tool_start_edit (tool, display,
+ color_tool->sample_point);
+ }
+ else if (gimp_color_tool_can_pick (color_tool, coords, display))
+ {
+ gimp_color_tool_pick (color_tool, coords, display,
+ GIMP_COLOR_PICK_STATE_START);
+
+ gimp_tool_control_activate (tool->control);
+ }
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ }
+}
+
+static void
+gimp_color_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (tool);
+
+ if (color_tool->enabled)
+ {
+ gimp_tool_control_halt (tool->control);
+
+ if (! color_tool->sample_point &&
+ gimp_color_tool_can_pick (color_tool, coords, display))
+ {
+ gimp_color_tool_pick (color_tool, coords, display,
+ GIMP_COLOR_PICK_STATE_END);
+ }
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+ }
+}
+
+static void
+gimp_color_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (tool);
+
+ if (color_tool->enabled)
+ {
+ if (! color_tool->sample_point)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ color_tool->can_pick = gimp_color_tool_can_pick (color_tool,
+ coords, display);
+ color_tool->center_x = coords->x;
+ color_tool->center_y = coords->y;
+
+ if (color_tool->can_pick)
+ {
+ gimp_color_tool_pick (color_tool, coords, display,
+ GIMP_COLOR_PICK_STATE_UPDATE);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
+ display);
+ }
+}
+
+static void
+gimp_color_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (tool);
+
+ if (color_tool->enabled)
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpSamplePoint *sample_point = NULL;
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (! draw_tool->widget &&
+
+ gimp_draw_tool_is_active (draw_tool) &&
+ (draw_tool->display != display ||
+ ! proximity))
+ {
+ gimp_draw_tool_stop (draw_tool);
+ }
+
+ if (gimp_display_shell_get_show_sample_points (shell) &&
+ proximity)
+ {
+ GimpImage *image = gimp_display_get_image (display);
+ gint snap_distance = display->config->snap_distance;
+
+ sample_point =
+ gimp_image_pick_sample_point (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance));
+ }
+
+ color_tool->sample_point = sample_point;
+
+ color_tool->can_pick = gimp_color_tool_can_pick (color_tool,
+ coords, display);
+ color_tool->center_x = coords->x;
+ color_tool->center_y = coords->y;
+
+ if (! draw_tool->widget &&
+
+ ! gimp_draw_tool_is_active (draw_tool) &&
+ proximity)
+ {
+ gimp_draw_tool_start (draw_tool, display);
+ }
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+ }
+}
+
+static void
+gimp_color_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (tool);
+
+ if (color_tool->enabled)
+ {
+ if (color_tool->sample_point)
+ {
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_COLOR_PICKER,
+ GIMP_CURSOR_MODIFIER_MOVE);
+ }
+ else
+ {
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_BAD;
+
+ if (gimp_color_tool_can_pick (color_tool, coords, display))
+ {
+ switch (color_tool->pick_target)
+ {
+ case GIMP_COLOR_PICK_TARGET_NONE:
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+ break;
+ case GIMP_COLOR_PICK_TARGET_FOREGROUND:
+ modifier = GIMP_CURSOR_MODIFIER_FOREGROUND;
+ break;
+ case GIMP_COLOR_PICK_TARGET_BACKGROUND:
+ modifier = GIMP_CURSOR_MODIFIER_BACKGROUND;
+ break;
+ case GIMP_COLOR_PICK_TARGET_PALETTE:
+ modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ break;
+ }
+ }
+
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_COLOR_PICKER,
+ GIMP_TOOL_CURSOR_COLOR_PICKER,
+ modifier);
+ }
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+ }
+}
+
+static void
+gimp_color_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpColorTool *color_tool = GIMP_COLOR_TOOL (draw_tool);
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+
+ if (color_tool->enabled)
+ {
+ if (color_tool->sample_point)
+ {
+ GimpImage *image = gimp_display_get_image (draw_tool->display);
+ GimpCanvasItem *item;
+ gint index;
+ gint x;
+ gint y;
+
+ gimp_sample_point_get_position (color_tool->sample_point, &x, &y);
+
+ index = g_list_index (gimp_image_get_sample_points (image),
+ color_tool->sample_point) + 1;
+
+ item = gimp_draw_tool_add_sample_point (draw_tool, x, y, index);
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+ else if (color_tool->can_pick && color_tool->options->sample_average)
+ {
+ gdouble radius = color_tool->options->average_radius;
+
+ gimp_draw_tool_add_rectangle (draw_tool,
+ FALSE,
+ color_tool->center_x - radius,
+ color_tool->center_y - radius,
+ 2 * radius + 1,
+ 2 * radius + 1);
+ }
+ }
+}
+
+static gboolean
+gimp_color_tool_real_can_pick (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display)
+{
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+
+ return gimp_image_coords_in_active_pickable (image, coords,
+ shell->show_all,
+ color_tool->options->sample_merged,
+ FALSE);
+}
+
+static gboolean
+gimp_color_tool_real_pick (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ g_return_val_if_fail (drawable != NULL, FALSE);
+
+ return gimp_image_pick_color (image, drawable,
+ coords->x, coords->y,
+ shell->show_all,
+ color_tool->options->sample_merged,
+ color_tool->options->sample_average,
+ color_tool->options->average_radius,
+ sample_format,
+ pixel,
+ color);
+}
+
+static void
+gimp_color_tool_real_picked (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color)
+{
+ GimpTool *tool = GIMP_TOOL (color_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImageWindow *image_window;
+ GimpDialogFactory *dialog_factory;
+ GimpContext *context;
+
+ image_window = gimp_display_shell_get_window (shell);
+ dialog_factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window));
+
+ /* use this tool's own options here (NOT color_tool->options) */
+ context = GIMP_CONTEXT (gimp_tool_get_options (tool));
+
+ if (color_tool->pick_target == GIMP_COLOR_PICK_TARGET_FOREGROUND ||
+ color_tool->pick_target == GIMP_COLOR_PICK_TARGET_BACKGROUND)
+ {
+ GtkWidget *widget;
+
+ widget = gimp_dialog_factory_find_widget (dialog_factory,
+ "gimp-indexed-palette");
+ if (widget)
+ {
+ GtkWidget *editor = gtk_bin_get_child (GTK_BIN (widget));
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (babl_format_is_palette (sample_format))
+ {
+ guchar *index = pixel;
+
+ gimp_colormap_editor_set_index (GIMP_COLORMAP_EDITOR (editor),
+ *index, NULL);
+ }
+ else if (gimp_image_get_base_type (image) == GIMP_INDEXED)
+ {
+ /* When Sample merged is set, we don't have the index
+ * information and it is possible to pick colors out of
+ * the colormap (with compositing). In such a case, the
+ * sample format will not be a palette format even though
+ * the image is indexed. Still search if the color exists
+ * in the colormap.
+ * Note that even if it does, we might still pick the
+ * wrong color, since several indexes may contain the same
+ * color and we can't know for sure which is the right
+ * one.
+ */
+ gint index = gimp_colormap_editor_get_index (GIMP_COLORMAP_EDITOR (editor),
+ color);
+ if (index > -1)
+ gimp_colormap_editor_set_index (GIMP_COLORMAP_EDITOR (editor),
+ index, NULL);
+ }
+ }
+
+ widget = gimp_dialog_factory_find_widget (dialog_factory,
+ "gimp-palette-editor");
+ if (widget)
+ {
+ GtkWidget *editor = gtk_bin_get_child (GTK_BIN (widget));
+ gint index;
+
+ index = gimp_palette_editor_get_index (GIMP_PALETTE_EDITOR (editor),
+ color);
+ if (index != -1)
+ gimp_palette_editor_set_index (GIMP_PALETTE_EDITOR (editor),
+ index, NULL);
+ }
+ }
+
+ switch (color_tool->pick_target)
+ {
+ case GIMP_COLOR_PICK_TARGET_NONE:
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_FOREGROUND:
+ gimp_context_set_foreground (context, color);
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_BACKGROUND:
+ gimp_context_set_background (context, color);
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_PALETTE:
+ {
+ GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (shell));
+ gint monitor = gimp_widget_get_monitor (GTK_WIDGET (shell));
+ GtkWidget *dockable;
+
+ dockable =
+ gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (display->gimp)),
+ display->gimp,
+ dialog_factory,
+ screen,
+ monitor,
+ "gimp-palette-editor");
+
+ if (dockable)
+ {
+ GtkWidget *palette_editor;
+ GimpData *data;
+
+ /* don't blink like mad when updating */
+ if (pick_state != GIMP_COLOR_PICK_STATE_START)
+ gimp_widget_blink_cancel (dockable);
+
+ palette_editor = gtk_bin_get_child (GTK_BIN (dockable));
+
+ data = gimp_data_editor_get_data (GIMP_DATA_EDITOR (palette_editor));
+
+ if (! data)
+ {
+ data = GIMP_DATA (gimp_context_get_palette (context));
+
+ gimp_data_editor_set_data (GIMP_DATA_EDITOR (palette_editor),
+ data);
+ }
+
+ gimp_palette_editor_pick_color (GIMP_PALETTE_EDITOR (palette_editor),
+ color, pick_state);
+ }
+ }
+ break;
+ }
+}
+
+static gboolean
+gimp_color_tool_can_pick (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display)
+{
+ GimpColorToolClass *klass;
+
+ klass = GIMP_COLOR_TOOL_GET_CLASS (tool);
+
+ return klass->can_pick && klass->can_pick (tool, coords, display);
+}
+
+static void
+gimp_color_tool_pick (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state)
+{
+ GimpColorToolClass *klass;
+ const Babl *sample_format;
+ gdouble pixel[4];
+ GimpRGB color;
+
+ klass = GIMP_COLOR_TOOL_GET_CLASS (tool);
+
+ if (klass->pick &&
+ klass->pick (tool, coords, display, &sample_format, pixel, &color))
+ {
+ g_signal_emit (tool, gimp_color_tool_signals[PICKED], 0,
+ coords, display, pick_state,
+ sample_format, pixel, &color);
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_color_tool_enable (GimpColorTool *color_tool,
+ GimpColorOptions *options)
+{
+ GimpTool *tool;
+
+ g_return_if_fail (GIMP_IS_COLOR_TOOL (color_tool));
+ g_return_if_fail (GIMP_IS_COLOR_OPTIONS (options));
+
+ tool = GIMP_TOOL (color_tool);
+
+ if (gimp_tool_control_is_active (tool->control))
+ {
+ g_warning ("Trying to enable GimpColorTool while it is active.");
+ return;
+ }
+
+ g_set_object (&color_tool->options, options);
+
+ /* color picking doesn't snap, see bug #768058 */
+ color_tool->saved_snap_to = gimp_tool_control_get_snap_to (tool->control);
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+
+ color_tool->enabled = TRUE;
+}
+
+void
+gimp_color_tool_disable (GimpColorTool *color_tool)
+{
+ GimpTool *tool;
+
+ g_return_if_fail (GIMP_IS_COLOR_TOOL (color_tool));
+
+ tool = GIMP_TOOL (color_tool);
+
+ if (gimp_tool_control_is_active (tool->control))
+ {
+ g_warning ("Trying to disable GimpColorTool while it is active.");
+ return;
+ }
+
+ g_clear_object (&color_tool->options);
+
+ gimp_tool_control_set_snap_to (tool->control, color_tool->saved_snap_to);
+ color_tool->saved_snap_to = FALSE;
+
+ color_tool->enabled = FALSE;
+}
+
+gboolean
+gimp_color_tool_is_enabled (GimpColorTool *color_tool)
+{
+ return color_tool->enabled;
+}
diff --git a/app/tools/gimpcolortool.h b/app/tools/gimpcolortool.h
new file mode 100644
index 0000000..f645590
--- /dev/null
+++ b/app/tools/gimpcolortool.h
@@ -0,0 +1,87 @@
+/* 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_COLOR_TOOL_H__
+#define __GIMP_COLOR_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_COLOR_TOOL (gimp_color_tool_get_type ())
+#define GIMP_COLOR_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_TOOL, GimpColorTool))
+#define GIMP_COLOR_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_TOOL, GimpColorToolClass))
+#define GIMP_IS_COLOR_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_TOOL))
+#define GIMP_IS_COLOR_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_TOOL))
+#define GIMP_COLOR_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_TOOL, GimpColorToolClass))
+
+#define GIMP_COLOR_TOOL_GET_OPTIONS(t) (GIMP_COLOR_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpColorToolClass GimpColorToolClass;
+
+struct _GimpColorTool
+{
+ GimpDrawTool parent_instance;
+
+ gboolean enabled;
+ GimpColorOptions *options;
+ gboolean saved_snap_to;
+
+ GimpColorPickTarget pick_target;
+
+ gboolean can_pick;
+ gint center_x;
+ gint center_y;
+ GimpSamplePoint *sample_point;
+};
+
+struct _GimpColorToolClass
+{
+ GimpDrawToolClass parent_class;
+
+ /* virtual functions */
+ gboolean (* can_pick) (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+ gboolean (* pick) (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color);
+
+ /* signals */
+ void (* picked) (GimpColorTool *tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color);
+};
+
+
+GType gimp_color_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_color_tool_enable (GimpColorTool *color_tool,
+ GimpColorOptions *options);
+void gimp_color_tool_disable (GimpColorTool *color_tool);
+gboolean gimp_color_tool_is_enabled (GimpColorTool *color_tool);
+
+
+#endif /* __GIMP_COLOR_TOOL_H__ */
diff --git a/app/tools/gimpconvolvetool.c b/app/tools/gimpconvolvetool.c
new file mode 100644
index 0000000..57f888d
--- /dev/null
+++ b/app/tools/gimpconvolvetool.c
@@ -0,0 +1,230 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimpconvolveoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpconvolvetool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_convolve_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_convolve_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_convolve_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_convolve_tool_status_update (GimpTool *tool,
+ GimpConvolveType type);
+
+static GtkWidget * gimp_convolve_options_gui (GimpToolOptions *options);
+
+
+G_DEFINE_TYPE (GimpConvolveTool, gimp_convolve_tool, GIMP_TYPE_BRUSH_TOOL)
+
+#define parent_class gimp_convolve_tool_parent_class
+
+
+void
+gimp_convolve_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_CONVOLVE_TOOL,
+ GIMP_TYPE_CONVOLVE_OPTIONS,
+ gimp_convolve_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK,
+ "gimp-convolve-tool",
+ _("Blur / Sharpen"),
+ _("Blur / Sharpen Tool: Selective blurring or unblurring using a brush"),
+ N_("Bl_ur / Sharpen"), "<shift>U",
+ NULL, GIMP_HELP_TOOL_CONVOLVE,
+ GIMP_ICON_TOOL_BLUR,
+ data);
+}
+
+static void
+gimp_convolve_tool_class_init (GimpConvolveToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ tool_class->modifier_key = gimp_convolve_tool_modifier_key;
+ tool_class->cursor_update = gimp_convolve_tool_cursor_update;
+ tool_class->oper_update = gimp_convolve_tool_oper_update;
+}
+
+static void
+gimp_convolve_tool_init (GimpConvolveTool *convolve)
+{
+ GimpTool *tool = GIMP_TOOL (convolve);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_BLUR);
+ gimp_tool_control_set_toggle_cursor_modifier (tool->control,
+ GIMP_CURSOR_MODIFIER_MINUS);
+
+ gimp_convolve_tool_status_update (tool, GIMP_CONVOLVE_BLUR);
+}
+
+static void
+gimp_convolve_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpConvolveTool *convolve = GIMP_CONVOLVE_TOOL (tool);
+ GimpConvolveOptions *options = GIMP_CONVOLVE_TOOL_GET_OPTIONS (tool);
+ GdkModifierType line_mask = GIMP_PAINT_TOOL_LINE_MASK;
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if (((key == toggle_mask) &&
+ ! (state & line_mask) && /* leave stuff untouched in line draw mode */
+ press != convolve->toggled)
+
+ ||
+
+ (key == line_mask && /* toggle back after keypresses CTRL(hold)-> */
+ ! press && /* SHIFT(hold)->CTRL(release)->SHIFT(release) */
+ convolve->toggled &&
+ ! (state & toggle_mask)))
+ {
+ convolve->toggled = press;
+
+ switch (options->type)
+ {
+ case GIMP_CONVOLVE_BLUR:
+ g_object_set (options, "type", GIMP_CONVOLVE_SHARPEN, NULL);
+ break;
+
+ case GIMP_CONVOLVE_SHARPEN:
+ g_object_set (options, "type", GIMP_CONVOLVE_BLUR, NULL);
+ break;
+ }
+ }
+}
+
+static void
+gimp_convolve_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpConvolveOptions *options = GIMP_CONVOLVE_TOOL_GET_OPTIONS (tool);
+
+ gimp_tool_control_set_toggled (tool->control,
+ options->type == GIMP_CONVOLVE_SHARPEN);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_convolve_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpConvolveOptions *options = GIMP_CONVOLVE_TOOL_GET_OPTIONS (tool);
+
+ gimp_convolve_tool_status_update (tool, options->type);
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+}
+
+static void
+gimp_convolve_tool_status_update (GimpTool *tool,
+ GimpConvolveType type)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+
+ switch (type)
+ {
+ case GIMP_CONVOLVE_BLUR:
+ paint_tool->status = _("Click to blur");
+ paint_tool->status_line = _("Click to blur the line");
+ paint_tool->status_ctrl = _("%s to sharpen");
+ break;
+
+ case GIMP_CONVOLVE_SHARPEN:
+ paint_tool->status = _("Click to sharpen");
+ paint_tool->status_line = _("Click to sharpen the line");
+ paint_tool->status_ctrl = _("%s to blur");
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_convolve_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *scale;
+ gchar *str;
+ GdkModifierType toggle_mask;
+
+ toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ /* the type radio box */
+ str = g_strdup_printf (_("Convolve Type (%s)"),
+ gimp_get_mod_string (toggle_mask));
+
+ frame = gimp_prop_enum_radio_frame_new (config, "type",
+ str, 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ g_free (str);
+
+ /* the rate scale */
+ scale = gimp_prop_spin_scale_new (config, "rate", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return vbox;
+}
diff --git a/app/tools/gimpconvolvetool.h b/app/tools/gimpconvolvetool.h
new file mode 100644
index 0000000..1abf6ff
--- /dev/null
+++ b/app/tools/gimpconvolvetool.h
@@ -0,0 +1,57 @@
+/* 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_CONVOLVE_TOOL_H__
+#define __GIMP_CONVOLVE_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+
+
+#define GIMP_TYPE_CONVOLVE_TOOL (gimp_convolve_tool_get_type ())
+#define GIMP_CONVOLVE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONVOLVE_TOOL, GimpConvolveTool))
+#define GIMP_CONVOLVE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONVOLVE_TOOL, GimpConvolveToolClass))
+#define GIMP_IS_CONVOLVE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONVOLVE_TOOL))
+#define GIMP_IS_CONVOLVE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONVOLVE_TOOL))
+#define GIMP_CONVOLVE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONVOLVE_TOOL, GimpConvolveToolClass))
+
+#define GIMP_CONVOLVE_TOOL_GET_OPTIONS(t) (GIMP_CONVOLVE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpConvolveTool GimpConvolveTool;
+typedef struct _GimpConvolveToolClass GimpConvolveToolClass;
+
+struct _GimpConvolveTool
+{
+ GimpBrushTool parent_instance;
+
+ gboolean toggled;
+};
+
+struct _GimpConvolveToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+void gimp_convolve_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_convolve_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CONVOLVE_TOOL_H__ */
diff --git a/app/tools/gimpcropoptions.c b/app/tools/gimpcropoptions.c
new file mode 100644
index 0000000..24c5dab
--- /dev/null
+++ b/app/tools/gimpcropoptions.c
@@ -0,0 +1,240 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimprectangleoptions.h"
+#include "gimpcropoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_LAYER_ONLY = GIMP_RECTANGLE_OPTIONS_PROP_LAST + 1,
+ PROP_ALLOW_GROWING,
+ PROP_FILL_TYPE,
+ PROP_DELETE_PIXELS
+};
+
+
+static void gimp_crop_options_rectangle_options_iface_init (GimpRectangleOptionsInterface *iface);
+
+static void gimp_crop_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_crop_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpCropOptions, gimp_crop_options,
+ GIMP_TYPE_TOOL_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_RECTANGLE_OPTIONS,
+ gimp_crop_options_rectangle_options_iface_init))
+
+
+static void
+gimp_crop_options_class_init (GimpCropOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_crop_options_set_property;
+ object_class->get_property = gimp_crop_options_get_property;
+
+ /* The 'highlight' property is defined here because we want different
+ * default values for the Crop and the Rectangle Select tools.
+ */
+ GIMP_CONFIG_PROP_BOOLEAN (object_class,
+ GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT,
+ "highlight",
+ _("Highlight"),
+ _("Dim everything outside selection"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class,
+ GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT_OPACITY,
+ "highlight-opacity",
+ _("Highlight opacity"),
+ _("How much to dim everything outside selection"),
+ 0.0, 1.0, 0.5,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_LAYER_ONLY,
+ "layer-only",
+ _("Current layer only"),
+ _("Crop only currently selected layer"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DELETE_PIXELS,
+ "delete-pixels",
+ _("Delete cropped pixels"),
+ _("Discard non-locked layer data that falls out of the crop region"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ALLOW_GROWING,
+ "allow-growing",
+ _("Allow growing"),
+ _("Allow resizing canvas by dragging cropping frame "
+ "beyond image boundary"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FILL_TYPE,
+ "fill-type",
+ _("Fill with"),
+ _("How to fill new areas created by 'Allow growing'"),
+ GIMP_TYPE_FILL_TYPE,
+ GIMP_FILL_TRANSPARENT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ gimp_rectangle_options_install_properties (object_class);
+}
+
+static void
+gimp_crop_options_init (GimpCropOptions *options)
+{
+}
+
+static void
+gimp_crop_options_rectangle_options_iface_init (GimpRectangleOptionsInterface *iface)
+{
+}
+
+static void
+gimp_crop_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCropOptions *options = GIMP_CROP_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER_ONLY:
+ options->layer_only = g_value_get_boolean (value);
+ break;
+
+ case PROP_DELETE_PIXELS:
+ options->delete_pixels = g_value_get_boolean (value);
+ break;
+
+ case PROP_ALLOW_GROWING:
+ options->allow_growing = g_value_get_boolean (value);
+ break;
+
+ case PROP_FILL_TYPE:
+ options->fill_type = g_value_get_enum (value);
+ break;
+
+ default:
+ gimp_rectangle_options_set_property (object, property_id, value, pspec);
+ break;
+ }
+}
+
+static void
+gimp_crop_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCropOptions *options = GIMP_CROP_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER_ONLY:
+ g_value_set_boolean (value, options->layer_only);
+ break;
+
+ case PROP_DELETE_PIXELS:
+ g_value_set_boolean (value, options->delete_pixels);
+ break;
+
+ case PROP_ALLOW_GROWING:
+ g_value_set_boolean (value, options->allow_growing);
+ break;
+
+ case PROP_FILL_TYPE:
+ g_value_set_enum (value, options->fill_type);
+ break;
+
+ default:
+ gimp_rectangle_options_get_property (object, property_id, value, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_crop_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *vbox_rectangle;
+ GtkWidget *button;
+ GtkWidget *combo;
+ GtkWidget *frame;
+
+ /* layer toggle */
+ button = gimp_prop_check_button_new (config, "layer-only", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* delete pixels toggle */
+ button = gimp_prop_check_button_new (config, "delete-pixels", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_object_bind_property (G_OBJECT (config), "layer-only",
+ G_OBJECT (button), "sensitive",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ /* fill type combo */
+ combo = gimp_prop_enum_combo_box_new (config, "fill-type", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Fill with"));
+
+ /* allow growing toggle/frame */
+ frame = gimp_prop_expanding_frame_new (config, "allow-growing", NULL,
+ combo, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* rectangle options */
+ vbox_rectangle = gimp_rectangle_options_gui (tool_options);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox_rectangle, FALSE, FALSE, 0);
+ gtk_widget_show (vbox_rectangle);
+
+ return vbox;
+}
diff --git a/app/tools/gimpcropoptions.h b/app/tools/gimpcropoptions.h
new file mode 100644
index 0000000..e123dca
--- /dev/null
+++ b/app/tools/gimpcropoptions.h
@@ -0,0 +1,61 @@
+/* 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_CROP_OPTIONS_H__
+#define __GIMP_CROP_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_CROP_OPTIONS (gimp_crop_options_get_type ())
+#define GIMP_CROP_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CROP_OPTIONS, GimpCropOptions))
+#define GIMP_CROP_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CROP_OPTIONS, GimpCropOptionsClass))
+#define GIMP_IS_CROP_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CROP_OPTIONS))
+#define GIMP_IS_CROP_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CROP_OPTIONS))
+#define GIMP_CROP_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CROP_OPTIONS, GimpCropOptionsClass))
+
+
+typedef struct _GimpCropOptions GimpCropOptions;
+typedef struct _GimpToolOptionsClass GimpCropOptionsClass;
+
+struct _GimpCropOptions
+{
+ GimpToolOptions parent_instance;
+
+ /* Work on the current layer rather than the image. */
+ gboolean layer_only;
+
+ /* Allow the crop rectangle to be larger than the image/layer. This
+ * will resize the image/layer.
+ */
+ gboolean allow_growing;
+
+ /* How to fill new areas created by 'allow_growing. */
+ GimpFillType fill_type;
+
+ /* Whether to discard layer data that falls out of the crop region */
+ gboolean delete_pixels;
+};
+
+
+GType gimp_crop_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_crop_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_CROP_OPTIONS_H__ */
diff --git a/app/tools/gimpcroptool.c b/app/tools/gimpcroptool.c
new file mode 100644
index 0000000..9e103a8
--- /dev/null
+++ b/app/tools/gimpcroptool.c
@@ -0,0 +1,721 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-crop.h"
+#include "core/gimpitem.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolrectangle.h"
+
+#include "gimpcropoptions.h"
+#include "gimpcroptool.h"
+#include "gimprectangleoptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_crop_tool_constructed (GObject *object);
+static void gimp_crop_tool_dispose (GObject *object);
+
+static void gimp_crop_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_crop_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_crop_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_crop_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_crop_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_crop_tool_rectangle_changed (GimpToolWidget *rectangle,
+ GimpCropTool *crop_tool);
+static void gimp_crop_tool_rectangle_response (GimpToolWidget *rectangle,
+ gint response_id,
+ GimpCropTool *crop_tool);
+static void gimp_crop_tool_rectangle_change_complete (GimpToolRectangle *rectangle,
+ GimpCropTool *crop_tool);
+
+static void gimp_crop_tool_start (GimpCropTool *crop_tool,
+ GimpDisplay *display);
+static void gimp_crop_tool_commit (GimpCropTool *crop_tool);
+static void gimp_crop_tool_halt (GimpCropTool *crop_tool);
+
+static void gimp_crop_tool_update_option_defaults (GimpCropTool *crop_tool,
+ gboolean ignore_pending);
+static GimpRectangleConstraint
+ gimp_crop_tool_get_constraint (GimpCropTool *crop_tool);
+
+static void gimp_crop_tool_image_changed (GimpCropTool *crop_tool,
+ GimpImage *image,
+ GimpContext *context);
+static void gimp_crop_tool_image_size_changed (GimpCropTool *crop_tool);
+static void gimp_crop_tool_image_active_layer_changed (GimpCropTool *crop_tool);
+static void gimp_crop_tool_layer_size_changed (GimpCropTool *crop_tool);
+
+static void gimp_crop_tool_auto_shrink (GimpCropTool *crop_tool);
+
+
+G_DEFINE_TYPE (GimpCropTool, gimp_crop_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_crop_tool_parent_class
+
+
+/* public functions */
+
+void
+gimp_crop_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_CROP_TOOL,
+ GIMP_TYPE_CROP_OPTIONS,
+ gimp_crop_options_gui,
+ 0,
+ "gimp-crop-tool",
+ _("Crop"),
+ _("Crop Tool: Remove edge areas from image or layer"),
+ N_("_Crop"), "<shift>C",
+ NULL, GIMP_HELP_TOOL_CROP,
+ GIMP_ICON_TOOL_CROP,
+ data);
+}
+
+static void
+gimp_crop_tool_class_init (GimpCropToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_crop_tool_constructed;
+ object_class->dispose = gimp_crop_tool_dispose;
+
+ tool_class->control = gimp_crop_tool_control;
+ tool_class->button_press = gimp_crop_tool_button_press;
+ tool_class->button_release = gimp_crop_tool_button_release;
+ tool_class->motion = gimp_crop_tool_motion;
+ tool_class->options_notify = gimp_crop_tool_options_notify;
+}
+
+static void
+gimp_crop_tool_init (GimpCropTool *crop_tool)
+{
+ GimpTool *tool = GIMP_TOOL (crop_tool);
+
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER);
+ gimp_tool_control_set_cursor (tool->control,
+ GIMP_CURSOR_CROSSHAIR_SMALL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_CROP);
+
+ gimp_draw_tool_set_default_status (GIMP_DRAW_TOOL (tool),
+ _("Click-Drag to draw a crop rectangle"));
+}
+
+static void
+gimp_crop_tool_constructed (GObject *object)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (object);
+ GimpContext *context;
+ GimpToolInfo *tool_info;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ tool_info = GIMP_TOOL (crop_tool)->tool_info;
+
+ context = gimp_get_user_context (tool_info->gimp);
+
+ g_signal_connect_object (context, "image-changed",
+ G_CALLBACK (gimp_crop_tool_image_changed),
+ crop_tool,
+ G_CONNECT_SWAPPED);
+
+ /* Make sure we are connected to "size-changed" for the initial
+ * image.
+ */
+ gimp_crop_tool_image_changed (crop_tool,
+ gimp_context_get_image (context),
+ context);
+}
+
+static void
+gimp_crop_tool_dispose (GObject *object)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (object);
+
+ /* Clean up current_image and current_layer. */
+ gimp_crop_tool_image_changed (crop_tool, NULL, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_crop_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_crop_tool_halt (crop_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_crop_tool_commit (crop_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_crop_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
+
+ if (tool->display && display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ if (! tool->display)
+ {
+ gimp_crop_tool_start (crop_tool, display);
+
+ gimp_tool_widget_hover (crop_tool->widget, coords, state, TRUE);
+
+ /* HACK: force CREATING on a newly created rectangle; otherwise,
+ * property bindings would cause the rectangle to start with the
+ * size from tool options.
+ */
+ gimp_tool_rectangle_set_function (GIMP_TOOL_RECTANGLE (crop_tool->widget),
+ GIMP_TOOL_RECTANGLE_CREATING);
+ }
+
+ if (gimp_tool_widget_button_press (crop_tool->widget, coords, time, state,
+ press_type))
+ {
+ crop_tool->grab_widget = crop_tool->widget;
+ }
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_crop_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (crop_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (crop_tool->grab_widget,
+ coords, time, state, release_type);
+ crop_tool->grab_widget = NULL;
+ }
+
+ gimp_tool_push_status (tool, display, _("Click or press Enter to crop"));
+}
+
+static void
+gimp_crop_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
+
+ if (crop_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (crop_tool->grab_widget, coords, time, state);
+ }
+}
+
+static void
+gimp_crop_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
+
+ if (! strcmp (pspec->name, "layer-only") ||
+ ! strcmp (pspec->name, "allow-growing"))
+ {
+ if (crop_tool->widget)
+ {
+ gimp_tool_rectangle_set_constraint (GIMP_TOOL_RECTANGLE (crop_tool->widget),
+ gimp_crop_tool_get_constraint (crop_tool));
+ }
+ else
+ {
+ gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
+ }
+ }
+}
+
+static void
+gimp_crop_tool_rectangle_changed (GimpToolWidget *rectangle,
+ GimpCropTool *crop_tool)
+{
+}
+
+static void
+gimp_crop_tool_rectangle_response (GimpToolWidget *rectangle,
+ gint response_id,
+ GimpCropTool *crop_tool)
+{
+ GimpTool *tool = GIMP_TOOL (crop_tool);
+
+ switch (response_id)
+ {
+ case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ break;
+ }
+}
+
+static void
+gimp_crop_tool_rectangle_change_complete (GimpToolRectangle *rectangle,
+ GimpCropTool *crop_tool)
+{
+ gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
+}
+
+static void
+gimp_crop_tool_start (GimpCropTool *crop_tool,
+ GimpDisplay *display)
+{
+ static const gchar *properties[] =
+ {
+ "highlight",
+ "highlight-opacity",
+ "guide",
+ "x",
+ "y",
+ "width",
+ "height",
+ "fixed-rule-active",
+ "fixed-rule",
+ "desired-fixed-width",
+ "desired-fixed-height",
+ "desired-fixed-size-width",
+ "desired-fixed-size-height",
+ "aspect-numerator",
+ "aspect-denominator",
+ "fixed-center"
+ };
+
+ GimpTool *tool = GIMP_TOOL (crop_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpCropOptions *options = GIMP_CROP_TOOL_GET_OPTIONS (crop_tool);
+ GimpToolWidget *widget;
+ gint i;
+
+ tool->display = display;
+
+ crop_tool->widget = widget = gimp_tool_rectangle_new (shell);
+
+ g_object_set (widget,
+ "status-title", _("Crop to: "),
+ NULL);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), widget);
+
+ for (i = 0; i < G_N_ELEMENTS (properties); i++)
+ {
+ GBinding *binding =
+ g_object_bind_property (G_OBJECT (options), properties[i],
+ G_OBJECT (widget), properties[i],
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ crop_tool->bindings = g_list_prepend (crop_tool->bindings, binding);
+ }
+
+ gimp_rectangle_options_connect (GIMP_RECTANGLE_OPTIONS (options),
+ gimp_display_get_image (shell->display),
+ G_CALLBACK (gimp_crop_tool_auto_shrink),
+ crop_tool);
+
+ gimp_tool_rectangle_set_constraint (GIMP_TOOL_RECTANGLE (widget),
+ gimp_crop_tool_get_constraint (crop_tool));
+
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (gimp_crop_tool_rectangle_changed),
+ crop_tool);
+ g_signal_connect (widget, "response",
+ G_CALLBACK (gimp_crop_tool_rectangle_response),
+ crop_tool);
+ g_signal_connect (widget, "change-complete",
+ G_CALLBACK (gimp_crop_tool_rectangle_change_complete),
+ crop_tool);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+}
+
+static void
+gimp_crop_tool_commit (GimpCropTool *crop_tool)
+{
+ GimpTool *tool = GIMP_TOOL (crop_tool);
+
+ if (tool->display)
+ {
+ GimpCropOptions *options = GIMP_CROP_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+ gdouble x, y;
+ gdouble x2, y2;
+ gint w, h;
+
+ gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (crop_tool->widget),
+ &x, &y, &x2, &y2);
+ w = x2 - x;
+ h = y2 - y;
+
+ gimp_tool_pop_status (tool, tool->display);
+
+ /* if rectangle exists, crop it */
+ if (w > 0 && h > 0)
+ {
+ if (options->layer_only)
+ {
+ GimpLayer *layer = gimp_image_get_active_layer (image);
+ gint off_x, off_y;
+
+ if (! layer)
+ {
+ gimp_tool_message_literal (tool, tool->display,
+ _("There is no active layer to crop."));
+ return;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (layer)))
+ {
+ gimp_tool_message_literal (tool, tool->display,
+ _("The active layer's pixels are locked."));
+ gimp_tools_blink_lock_box (tool->display->gimp,
+ GIMP_ITEM (layer));
+ return;
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+
+ off_x -= x;
+ off_y -= y;
+
+ gimp_item_resize (GIMP_ITEM (layer),
+ GIMP_CONTEXT (options), options->fill_type,
+ w, h, off_x, off_y);
+ }
+ else
+ {
+ gimp_image_crop (image,
+ GIMP_CONTEXT (options), GIMP_FILL_TRANSPARENT,
+ x, y, w, h, options->delete_pixels);
+ }
+
+ gimp_image_flush (image);
+ }
+ }
+}
+
+static void
+gimp_crop_tool_halt (GimpCropTool *crop_tool)
+{
+ GimpTool *tool = GIMP_TOOL (crop_tool);
+ GimpCropOptions *options = GIMP_CROP_TOOL_GET_OPTIONS (crop_tool);
+
+ if (tool->display)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ gimp_display_shell_set_highlight (shell, NULL, 0.0);
+
+ gimp_rectangle_options_disconnect (GIMP_RECTANGLE_OPTIONS (options),
+ G_CALLBACK (gimp_crop_tool_auto_shrink),
+ crop_tool);
+ }
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ /* disconnect bindings manually so they are really gone *now*, we
+ * might be in the middle of a signal emission that keeps the
+ * widget and its bindings alive.
+ */
+ g_list_free_full (crop_tool->bindings, (GDestroyNotify) g_object_unref);
+ crop_tool->bindings = NULL;
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
+ g_clear_object (&crop_tool->widget);
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+
+ gimp_crop_tool_update_option_defaults (crop_tool, TRUE);
+}
+
+/**
+ * gimp_crop_tool_update_option_defaults:
+ * @crop_tool:
+ * @ignore_pending: %TRUE to ignore any pending crop rectangle.
+ *
+ * Sets the default Fixed: Aspect ratio and Fixed: Size option
+ * properties.
+ */
+static void
+gimp_crop_tool_update_option_defaults (GimpCropTool *crop_tool,
+ gboolean ignore_pending)
+{
+ GimpTool *tool = GIMP_TOOL (crop_tool);
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (crop_tool->widget);
+ GimpRectangleOptions *options;
+
+ options = GIMP_RECTANGLE_OPTIONS (GIMP_TOOL_GET_OPTIONS (tool));
+
+ if (rectangle && ! ignore_pending)
+ {
+ /* There is a pending rectangle and we should not ignore it, so
+ * set default Fixed: Aspect ratio to the same as the current
+ * pending rectangle width/height.
+ */
+
+ gimp_tool_rectangle_pending_size_set (rectangle,
+ G_OBJECT (options),
+ "default-aspect-numerator",
+ "default-aspect-denominator");
+
+ g_object_set (G_OBJECT (options),
+ "use-string-current", TRUE,
+ NULL);
+ }
+ else
+ {
+ /* There is no pending rectangle, set default Fixed: Aspect
+ * ratio to that of the current image/layer.
+ */
+
+ if (! rectangle)
+ {
+ /* ugly hack: if we don't have a widget, construct a temporary one
+ * so that we can use it to call
+ * gimp_tool_rectangle_constraint_size_set().
+ */
+
+ GimpContext *context = gimp_get_user_context (tool->tool_info->gimp);
+ GimpDisplay *display = gimp_context_get_display (context);
+
+ if (display)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ rectangle = GIMP_TOOL_RECTANGLE (gimp_tool_rectangle_new (shell));
+
+ gimp_tool_rectangle_set_constraint (
+ rectangle, gimp_crop_tool_get_constraint (crop_tool));
+ }
+ }
+
+ if (rectangle)
+ {
+ gimp_tool_rectangle_constraint_size_set (rectangle,
+ G_OBJECT (options),
+ "default-aspect-numerator",
+ "default-aspect-denominator");
+
+ if (! crop_tool->widget)
+ g_object_unref (rectangle);
+ }
+
+ g_object_set (G_OBJECT (options),
+ "use-string-current", FALSE,
+ NULL);
+ }
+}
+
+static GimpRectangleConstraint
+gimp_crop_tool_get_constraint (GimpCropTool *crop_tool)
+{
+ GimpCropOptions *crop_options = GIMP_CROP_TOOL_GET_OPTIONS (crop_tool);
+
+ if (crop_options->allow_growing)
+ {
+ return GIMP_RECTANGLE_CONSTRAIN_NONE;
+ }
+ else
+ {
+ return crop_options->layer_only ? GIMP_RECTANGLE_CONSTRAIN_DRAWABLE :
+ GIMP_RECTANGLE_CONSTRAIN_IMAGE;
+ }
+}
+
+static void
+gimp_crop_tool_image_changed (GimpCropTool *crop_tool,
+ GimpImage *image,
+ GimpContext *context)
+{
+ if (crop_tool->current_image)
+ {
+ g_signal_handlers_disconnect_by_func (crop_tool->current_image,
+ gimp_crop_tool_image_size_changed,
+ NULL);
+ g_signal_handlers_disconnect_by_func (crop_tool->current_image,
+ gimp_crop_tool_image_active_layer_changed,
+ NULL);
+
+ g_object_remove_weak_pointer (G_OBJECT (crop_tool->current_image),
+ (gpointer) &crop_tool->current_image);
+ }
+
+ crop_tool->current_image = image;
+
+ if (crop_tool->current_image)
+ {
+ g_object_add_weak_pointer (G_OBJECT (crop_tool->current_image),
+ (gpointer) &crop_tool->current_image);
+
+ g_signal_connect_object (crop_tool->current_image, "size-changed",
+ G_CALLBACK (gimp_crop_tool_image_size_changed),
+ crop_tool,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (crop_tool->current_image, "active-layer-changed",
+ G_CALLBACK (gimp_crop_tool_image_active_layer_changed),
+ crop_tool,
+ G_CONNECT_SWAPPED);
+ }
+
+ /* Make sure we are connected to "size-changed" for the initial
+ * layer.
+ */
+ gimp_crop_tool_image_active_layer_changed (crop_tool);
+
+ gimp_crop_tool_update_option_defaults (GIMP_CROP_TOOL (crop_tool), FALSE);
+}
+
+static void
+gimp_crop_tool_image_size_changed (GimpCropTool *crop_tool)
+{
+ gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
+}
+
+static void
+gimp_crop_tool_image_active_layer_changed (GimpCropTool *crop_tool)
+{
+ if (crop_tool->current_layer)
+ {
+ g_signal_handlers_disconnect_by_func (crop_tool->current_layer,
+ gimp_crop_tool_layer_size_changed,
+ NULL);
+
+ g_object_remove_weak_pointer (G_OBJECT (crop_tool->current_layer),
+ (gpointer) &crop_tool->current_layer);
+ }
+
+ if (crop_tool->current_image)
+ {
+ crop_tool->current_layer =
+ gimp_image_get_active_layer (crop_tool->current_image);
+ }
+ else
+ {
+ crop_tool->current_layer = NULL;
+ }
+
+ if (crop_tool->current_layer)
+ {
+ g_object_add_weak_pointer (G_OBJECT (crop_tool->current_layer),
+ (gpointer) &crop_tool->current_layer);
+
+ g_signal_connect_object (crop_tool->current_layer, "size-changed",
+ G_CALLBACK (gimp_crop_tool_layer_size_changed),
+ crop_tool,
+ G_CONNECT_SWAPPED);
+ }
+
+ gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
+}
+
+static void
+gimp_crop_tool_layer_size_changed (GimpCropTool *crop_tool)
+{
+ gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
+}
+
+static void
+gimp_crop_tool_auto_shrink (GimpCropTool *crop_tool)
+{
+ gboolean shrink_merged ;
+
+ g_object_get (gimp_tool_get_options (GIMP_TOOL (crop_tool)),
+ "shrink-merged", &shrink_merged,
+ NULL);
+
+ gimp_tool_rectangle_auto_shrink (GIMP_TOOL_RECTANGLE (crop_tool->widget),
+ shrink_merged);
+}
diff --git a/app/tools/gimpcroptool.h b/app/tools/gimpcroptool.h
new file mode 100644
index 0000000..3f20430
--- /dev/null
+++ b/app/tools/gimpcroptool.h
@@ -0,0 +1,62 @@
+/* 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_CROP_TOOL_H__
+#define __GIMP_CROP_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_CROP_TOOL (gimp_crop_tool_get_type ())
+#define GIMP_CROP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CROP_TOOL, GimpCropTool))
+#define GIMP_CROP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CROP_TOOL, GimpCropToolClass))
+#define GIMP_IS_CROP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CROP_TOOL))
+#define GIMP_IS_CROP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CROP_TOOL))
+#define GIMP_CROP_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CROP_TOOL, GimpCropToolClass))
+
+#define GIMP_CROP_TOOL_GET_OPTIONS(t) (GIMP_CROP_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpCropTool GimpCropTool;
+typedef struct _GimpCropToolClass GimpCropToolClass;
+
+struct _GimpCropTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpImage *current_image;
+ GimpLayer *current_layer;
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+ GList *bindings;
+};
+
+struct _GimpCropToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_crop_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_crop_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CROP_TOOL_H__ */
diff --git a/app/tools/gimpcurvestool.c b/app/tools/gimpcurvestool.c
new file mode 100644
index 0000000..d514323
--- /dev/null
+++ b/app/tools/gimpcurvestool.c
@@ -0,0 +1,1156 @@
+/* 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 <errno.h>
+
+#include <glib/gstdio.h>
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "operations/gimpcurvesconfig.h"
+#include "operations/gimpoperationcurves.h"
+
+#include "core/gimp.h"
+#include "core/gimpcurve.h"
+#include "core/gimpcurve-map.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdrawable-histogram.h"
+#include "core/gimperror.h"
+#include "core/gimphistogram.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimpcolorbar.h"
+#include "widgets/gimpcurveview.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpcurvestool.h"
+#include "gimphistogramoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define GRAPH_SIZE 256
+#define BAR_SIZE 12
+#define RADIUS 4
+
+
+/* local function prototypes */
+
+static gboolean gimp_curves_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_curves_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static gboolean gimp_curves_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_curves_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+
+static gchar * gimp_curves_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description);
+static void gimp_curves_tool_dialog (GimpFilterTool *filter_tool);
+static void gimp_curves_tool_reset (GimpFilterTool *filter_tool);
+static void gimp_curves_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec);
+static gboolean gimp_curves_tool_settings_import (GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error);
+static gboolean gimp_curves_tool_settings_export (GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error);
+static void gimp_curves_tool_color_picked (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color);
+
+static void gimp_curves_tool_export_setup (GimpSettingsBox *settings_box,
+ GtkFileChooserDialog *dialog,
+ gboolean export,
+ GimpCurvesTool *tool);
+static void gimp_curves_tool_update_channel (GimpCurvesTool *tool);
+static void gimp_curves_tool_update_point (GimpCurvesTool *tool);
+
+static void curves_curve_dirty_callback (GimpCurve *curve,
+ GimpCurvesTool *tool);
+
+static void curves_channel_callback (GtkWidget *widget,
+ GimpCurvesTool *tool);
+static void curves_channel_reset_callback (GtkWidget *widget,
+ GimpCurvesTool *tool);
+
+static gboolean curves_menu_sensitivity (gint value,
+ gpointer data);
+
+static void curves_graph_selection_callback (GtkWidget *widget,
+ GimpCurvesTool *tool);
+
+static void curves_point_coords_callback (GtkWidget *widget,
+ GimpCurvesTool *tool);
+static void curves_point_type_callback (GtkWidget *widget,
+ GimpCurvesTool *tool);
+
+static void curves_curve_type_callback (GtkWidget *widget,
+ GimpCurvesTool *tool);
+
+static gboolean curves_get_channel_color (GtkWidget *widget,
+ GimpHistogramChannel channel,
+ GimpRGB *color);
+
+
+G_DEFINE_TYPE (GimpCurvesTool, gimp_curves_tool, GIMP_TYPE_FILTER_TOOL)
+
+#define parent_class gimp_curves_tool_parent_class
+
+
+/* public functions */
+
+void
+gimp_curves_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_CURVES_TOOL,
+ GIMP_TYPE_HISTOGRAM_OPTIONS,
+ gimp_color_options_gui,
+ 0,
+ "gimp-curves-tool",
+ _("Curves"),
+ _("Adjust color curves"),
+ N_("_Curves..."), NULL,
+ NULL, GIMP_HELP_TOOL_CURVES,
+ GIMP_ICON_TOOL_CURVES,
+ data);
+}
+
+
+/* private functions */
+
+static void
+gimp_curves_tool_class_init (GimpCurvesToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ tool_class->initialize = gimp_curves_tool_initialize;
+ tool_class->button_release = gimp_curves_tool_button_release;
+ tool_class->key_press = gimp_curves_tool_key_press;
+ tool_class->oper_update = gimp_curves_tool_oper_update;
+
+ filter_tool_class->get_operation = gimp_curves_tool_get_operation;
+ filter_tool_class->dialog = gimp_curves_tool_dialog;
+ filter_tool_class->reset = gimp_curves_tool_reset;
+ filter_tool_class->config_notify = gimp_curves_tool_config_notify;
+ filter_tool_class->settings_import = gimp_curves_tool_settings_import;
+ filter_tool_class->settings_export = gimp_curves_tool_settings_export;
+ filter_tool_class->color_picked = gimp_curves_tool_color_picked;
+}
+
+static void
+gimp_curves_tool_init (GimpCurvesTool *tool)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tool->picked_color); i++)
+ tool->picked_color[i] = -1.0;
+}
+
+static gboolean
+gimp_curves_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesTool *c_tool = GIMP_CURVES_TOOL (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpCurvesConfig *config;
+ GimpHistogram *histogram;
+ GimpHistogramChannel channel;
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ return FALSE;
+ }
+
+ config = GIMP_CURVES_CONFIG (filter_tool->config);
+
+ histogram = gimp_histogram_new (config->linear);
+ g_object_unref (gimp_drawable_calculate_histogram_async (
+ drawable, histogram, FALSE));
+ gimp_histogram_view_set_background (GIMP_HISTOGRAM_VIEW (c_tool->graph),
+ histogram);
+ g_object_unref (histogram);
+
+ if (gimp_drawable_get_component_type (drawable) == GIMP_COMPONENT_TYPE_U8)
+ {
+ c_tool->scale = 255.0;
+
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (c_tool->point_input), 0);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (c_tool->point_output), 0);
+
+ gtk_entry_set_width_chars (GTK_ENTRY (c_tool->point_input), 3);
+ gtk_entry_set_width_chars (GTK_ENTRY (c_tool->point_output), 3);
+ }
+ else
+ {
+ c_tool->scale = 100.0;
+
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (c_tool->point_input), 2);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (c_tool->point_output), 2);
+
+ gtk_entry_set_width_chars (GTK_ENTRY (c_tool->point_input), 6);
+ gtk_entry_set_width_chars (GTK_ENTRY (c_tool->point_output), 6);
+ }
+
+ gimp_curve_view_set_range_x (GIMP_CURVE_VIEW (c_tool->graph),
+ 0, c_tool->scale);
+ gimp_curve_view_set_range_y (GIMP_CURVE_VIEW (c_tool->graph),
+ 0, c_tool->scale);
+
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (c_tool->point_output),
+ 0, c_tool->scale);
+
+ for (channel = GIMP_HISTOGRAM_VALUE;
+ channel <= GIMP_HISTOGRAM_ALPHA;
+ channel++)
+ {
+ g_signal_connect (config->curve[channel], "dirty",
+ G_CALLBACK (curves_curve_dirty_callback),
+ tool);
+ }
+
+ gimp_curves_tool_update_point (c_tool);
+
+ /* always pick colors */
+ gimp_filter_tool_enable_color_picking (filter_tool, NULL, FALSE);
+
+ return TRUE;
+}
+
+static void
+gimp_curves_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpCurvesTool *c_tool = GIMP_CURVES_TOOL (tool);
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ GimpCurve *curve = config->curve[config->channel];
+ gdouble value = c_tool->picked_color[config->channel];
+ gint point;
+
+ point = gimp_curve_get_point_at (curve, value);
+
+ if (point < 0)
+ {
+ GimpCurvePointType type = GIMP_CURVE_POINT_SMOOTH;
+
+ point = gimp_curve_view_get_selected (
+ GIMP_CURVE_VIEW (c_tool->graph));
+
+ if (point >= 0)
+ type = gimp_curve_get_point_type (curve, point);
+
+ point = gimp_curve_add_point (
+ curve,
+ value, gimp_curve_map_value (curve, value));
+
+ gimp_curve_set_point_type (curve, point, type);
+ }
+
+ gimp_curve_view_set_selected (GIMP_CURVE_VIEW (c_tool->graph), point);
+ }
+ else if (state & gimp_get_toggle_behavior_mask ())
+ {
+ GimpHistogramChannel channel;
+ GimpCurvePointType type = GIMP_CURVE_POINT_SMOOTH;
+ gint point;
+
+ point = gimp_curve_view_get_selected (GIMP_CURVE_VIEW (c_tool->graph));
+
+ if (point >= 0)
+ {
+ type = gimp_curve_get_point_type (config->curve[config->channel],
+ point);
+ }
+
+ for (channel = GIMP_HISTOGRAM_VALUE;
+ channel <= GIMP_HISTOGRAM_ALPHA;
+ channel++)
+ {
+ GimpCurve *curve = config->curve[channel];
+ gdouble value = c_tool->picked_color[channel];
+
+ if (value != -1)
+ {
+ point = gimp_curve_get_point_at (curve, value);
+
+ if (point < 0)
+ {
+ point = gimp_curve_add_point (
+ curve,
+ value, gimp_curve_map_value (curve, value));
+
+ gimp_curve_set_point_type (curve, point, type);
+ }
+
+ if (channel == config->channel)
+ {
+ gimp_curve_view_set_selected (GIMP_CURVE_VIEW (c_tool->graph),
+ point);
+ }
+ }
+ }
+ }
+
+ /* chain up to halt the tool */
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+}
+
+static gboolean
+gimp_curves_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpCurvesTool *c_tool = GIMP_CURVES_TOOL (tool);
+
+ if (tool->display && c_tool->graph)
+ {
+ if (gtk_widget_event (c_tool->graph, (GdkEvent *) kevent))
+ return TRUE;
+ }
+
+ return GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+}
+
+static void
+gimp_curves_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ if (gimp_filter_tool_on_guide (GIMP_FILTER_TOOL (tool),
+ coords, display))
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+ }
+ else
+ {
+ GimpColorPickTarget target;
+ gchar *status = NULL;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ gimp_tool_pop_status (tool, display);
+
+ if (state & extend_mask)
+ {
+ target = GIMP_COLOR_PICK_TARGET_PALETTE;
+ status = g_strdup (_("Click to add a control point"));
+ }
+ else if (state & toggle_mask)
+ {
+ target = GIMP_COLOR_PICK_TARGET_PALETTE;
+ status = g_strdup (_("Click to add control points to all channels"));
+ }
+ else
+ {
+ target = GIMP_COLOR_PICK_TARGET_NONE;
+ status = gimp_suggest_modifiers (_("Click to locate on curve"),
+ (extend_mask | toggle_mask) & ~state,
+ _("%s: add control point"),
+ _("%s: add control points to all channels"),
+ NULL);
+ }
+
+ GIMP_COLOR_TOOL (tool)->pick_target = target;
+
+ if (proximity)
+ gimp_tool_push_status (tool, display, "%s", status);
+
+ g_free (status);
+ }
+}
+
+static gchar *
+gimp_curves_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description)
+{
+ *description = g_strdup (_("Adjust Color Curves"));
+
+ return g_strdup ("gimp:curves");
+}
+
+
+/*******************/
+/* Curves dialog */
+/*******************/
+
+static void
+gimp_curves_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpCurvesTool *tool = GIMP_CURVES_TOOL (filter_tool);
+ GimpToolOptions *tool_options = GIMP_TOOL_GET_OPTIONS (filter_tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GtkListStore *store;
+ GtkWidget *main_vbox;
+ GtkWidget *frame_vbox;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *hbox2;
+ GtkWidget *label;
+ GtkWidget *main_frame;
+ GtkWidget *frame;
+ GtkWidget *table;
+ GtkWidget *button;
+ GtkWidget *bar;
+ GtkWidget *combo;
+
+ g_signal_connect (filter_tool->settings_box, "file-dialog-setup",
+ G_CALLBACK (gimp_curves_tool_export_setup),
+ filter_tool);
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ /* The combo box for selecting channels */
+ main_frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (main_vbox), main_frame, TRUE, TRUE, 0);
+ gtk_widget_show (main_frame);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_frame_set_label_widget (GTK_FRAME (main_frame), hbox);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("Cha_nnel:"));
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ store = gimp_enum_store_new_with_range (GIMP_TYPE_HISTOGRAM_CHANNEL,
+ GIMP_HISTOGRAM_VALUE,
+ GIMP_HISTOGRAM_ALPHA);
+ tool->channel_menu =
+ gimp_enum_combo_box_new_with_model (GIMP_ENUM_STORE (store));
+ g_object_unref (store);
+
+ g_object_add_weak_pointer (G_OBJECT (tool->channel_menu),
+ (gpointer) &tool->channel_menu);
+
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (tool->channel_menu),
+ config->channel);
+ gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (tool->channel_menu),
+ "gimp-channel");
+ gimp_int_combo_box_set_sensitivity (GIMP_INT_COMBO_BOX (tool->channel_menu),
+ curves_menu_sensitivity, filter_tool, NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), tool->channel_menu, FALSE, FALSE, 0);
+ gtk_widget_show (tool->channel_menu);
+
+ g_signal_connect (tool->channel_menu, "changed",
+ G_CALLBACK (curves_channel_callback),
+ tool);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), tool->channel_menu);
+
+ button = gtk_button_new_with_mnemonic (_("R_eset Channel"));
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (curves_channel_reset_callback),
+ tool);
+
+ /* The histogram scale radio buttons */
+ hbox2 = gimp_prop_enum_icon_box_new (G_OBJECT (tool_options),
+ "histogram-scale", "gimp-histogram",
+ 0, 0);
+ gtk_box_pack_end (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ /* The linear/perceptual radio buttons */
+ hbox2 = gimp_prop_boolean_icon_box_new (G_OBJECT (config),
+ "linear",
+ GIMP_ICON_COLOR_SPACE_LINEAR,
+ GIMP_ICON_COLOR_SPACE_PERCEPTUAL,
+ _("Adjust curves in linear light"),
+ _("Adjust curves perceptually"));
+ gtk_box_pack_end (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ frame_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+ gtk_container_add (GTK_CONTAINER (main_frame), frame_vbox);
+ gtk_widget_show (frame_vbox);
+
+ /* The table for the color bars and the graph */
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_box_pack_start (GTK_BOX (frame_vbox), table, TRUE, TRUE, 0);
+
+ /* The left color bar */
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 0, 1,
+ GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+ gtk_widget_show (vbox);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, RADIUS);
+ gtk_widget_show (frame);
+
+ tool->yrange = gimp_color_bar_new (GTK_ORIENTATION_VERTICAL);
+ gtk_widget_set_size_request (tool->yrange, BAR_SIZE, -1);
+ gtk_container_add (GTK_CONTAINER (frame), tool->yrange);
+ gtk_widget_show (tool->yrange);
+
+ /* The curves graph */
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_table_attach (GTK_TABLE (table), frame, 1, 2, 0, 1,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+ gtk_widget_show (frame);
+
+ tool->graph = gimp_curve_view_new ();
+
+ g_object_add_weak_pointer (G_OBJECT (tool->graph),
+ (gpointer) &tool->graph);
+
+ gimp_curve_view_set_range_x (GIMP_CURVE_VIEW (tool->graph), 0, 255);
+ gimp_curve_view_set_range_y (GIMP_CURVE_VIEW (tool->graph), 0, 255);
+ gtk_widget_set_size_request (tool->graph,
+ GRAPH_SIZE + RADIUS * 2,
+ GRAPH_SIZE + RADIUS * 2);
+ g_object_set (tool->graph,
+ "border-width", RADIUS,
+ "subdivisions", 1,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (frame), tool->graph);
+ gtk_widget_show (tool->graph);
+
+ g_object_bind_property (G_OBJECT (tool_options), "histogram-scale",
+ G_OBJECT (tool->graph), "histogram-scale",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ g_signal_connect (tool->graph, "selection-changed",
+ G_CALLBACK (curves_graph_selection_callback),
+ tool);
+
+ /* The bottom color bar */
+ hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_table_attach (GTK_TABLE (table), hbox2, 1, 2, 1, 2,
+ GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (hbox2);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (hbox2), frame, TRUE, TRUE, RADIUS);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_set_homogeneous (GTK_BOX (vbox), TRUE);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ tool->xrange = gimp_color_bar_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_size_request (tool->xrange, -1, BAR_SIZE / 2);
+ gtk_box_pack_start (GTK_BOX (vbox), tool->xrange, TRUE, TRUE, 0);
+ gtk_widget_show (tool->xrange);
+
+ bar = gimp_color_bar_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_pack_start (GTK_BOX (vbox), bar, TRUE, TRUE, 0);
+ gtk_widget_show (bar);
+
+ gtk_widget_show (table);
+
+ /* The point properties box */
+ tool->point_box = hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (frame_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (tool->point_box);
+
+ label = gtk_label_new_with_mnemonic (_("_Input:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ tool->point_input = gimp_spin_button_new_with_range (0.0, 0.0, 1.0);
+ gtk_box_pack_start (GTK_BOX (hbox), tool->point_input, FALSE, FALSE, 0);
+ gtk_widget_show (tool->point_input);
+
+ g_signal_connect (tool->point_input, "value-changed",
+ G_CALLBACK (curves_point_coords_callback),
+ tool);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), tool->point_input);
+
+ label = gtk_label_new_with_mnemonic (_("O_utput:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ tool->point_output = gimp_spin_button_new_with_range (0.0, 0.0, 1.0);
+ gtk_box_pack_start (GTK_BOX (hbox), tool->point_output, FALSE, FALSE, 0);
+ gtk_widget_show (tool->point_output);
+
+ g_signal_connect (tool->point_output, "value-changed",
+ G_CALLBACK (curves_point_coords_callback),
+ tool);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), tool->point_output);
+
+ label = gtk_label_new_with_mnemonic (_("T_ype:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ hbox2 = gimp_enum_icon_box_new (GIMP_TYPE_CURVE_POINT_TYPE,
+ "gimp-curve-point",
+ GTK_ICON_SIZE_MENU,
+ G_CALLBACK (curves_point_type_callback),
+ tool,
+ &tool->point_type);
+ gtk_box_pack_start (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (frame_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), tool->point_type);
+
+ label = gtk_label_new_with_mnemonic (_("Curve _type:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ /* The curve-type combo */
+ tool->curve_type = combo = gimp_enum_combo_box_new (GIMP_TYPE_CURVE_TYPE);
+ gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (combo),
+ "gimp-curve");
+ gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), 0,
+ G_CALLBACK (curves_curve_type_callback),
+ tool);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ gimp_curves_tool_update_channel (tool);
+}
+
+static void
+gimp_curves_tool_reset (GimpFilterTool *filter_tool)
+{
+ GimpHistogramChannel channel;
+
+ g_object_get (filter_tool->config,
+ "channel", &channel,
+ NULL);
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->reset (filter_tool);
+
+ g_object_set (filter_tool->config,
+ "channel", channel,
+ NULL);
+}
+
+static void
+gimp_curves_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec)
+{
+ GimpCurvesTool *curves_tool = GIMP_CURVES_TOOL (filter_tool);
+ GimpCurvesConfig *curves_config = GIMP_CURVES_CONFIG (config);
+ GimpCurve *curve = curves_config->curve[curves_config->channel];
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->config_notify (filter_tool,
+ config, pspec);
+
+ if (! curves_tool->channel_menu ||
+ ! curves_tool->graph)
+ return;
+
+ if (! strcmp (pspec->name, "linear"))
+ {
+ GimpHistogram *histogram;
+
+ histogram = gimp_histogram_new (curves_config->linear);
+ g_object_unref (gimp_drawable_calculate_histogram_async (
+ GIMP_TOOL (filter_tool)->drawable, histogram, FALSE));
+ gimp_histogram_view_set_background (GIMP_HISTOGRAM_VIEW (curves_tool->graph),
+ histogram);
+ g_object_unref (histogram);
+ }
+ else if (! strcmp (pspec->name, "channel"))
+ {
+ gimp_curves_tool_update_channel (GIMP_CURVES_TOOL (filter_tool));
+ }
+ else if (! strcmp (pspec->name, "curve"))
+ {
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (curves_tool->curve_type),
+ curve->curve_type);
+ }
+}
+
+static gboolean
+gimp_curves_tool_settings_import (GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error)
+{
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ gchar header[64];
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (input, header, sizeof (header),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (header))
+ {
+ g_prefix_error (error, _("Could not read header: "));
+ return FALSE;
+ }
+
+ g_seekable_seek (G_SEEKABLE (input), 0, G_SEEK_SET, NULL, NULL);
+
+ if (g_str_has_prefix (header, "# GIMP Curves File\n"))
+ return gimp_curves_config_load_cruft (config, input, error);
+
+ return GIMP_FILTER_TOOL_CLASS (parent_class)->settings_import (filter_tool,
+ input,
+ error);
+}
+
+static gboolean
+gimp_curves_tool_settings_export (GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpCurvesTool *tool = GIMP_CURVES_TOOL (filter_tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+
+ if (tool->export_old_format)
+ return gimp_curves_config_save_cruft (config, output, error);
+
+ return GIMP_FILTER_TOOL_CLASS (parent_class)->settings_export (filter_tool,
+ output,
+ error);
+}
+
+static void
+gimp_curves_tool_color_picked (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color)
+{
+ GimpCurvesTool *tool = GIMP_CURVES_TOOL (filter_tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GimpDrawable *drawable = GIMP_TOOL (tool)->drawable;
+ GimpRGB rgb = *color;
+
+ if (config->linear)
+ babl_process (babl_fish (babl_format ("R'G'B'A double"),
+ babl_format ("RGBA double")),
+ &rgb, &rgb, 1);
+
+ tool->picked_color[GIMP_HISTOGRAM_RED] = rgb.r;
+ tool->picked_color[GIMP_HISTOGRAM_GREEN] = rgb.g;
+ tool->picked_color[GIMP_HISTOGRAM_BLUE] = rgb.b;
+
+ if (gimp_drawable_has_alpha (drawable))
+ tool->picked_color[GIMP_HISTOGRAM_ALPHA] = rgb.a;
+ else
+ tool->picked_color[GIMP_HISTOGRAM_ALPHA] = -1;
+
+ tool->picked_color[GIMP_HISTOGRAM_VALUE] = MAX (MAX (rgb.r, rgb.g), rgb.b);
+
+ gimp_curve_view_set_xpos (GIMP_CURVE_VIEW (tool->graph),
+ tool->picked_color[config->channel]);
+}
+
+static void
+gimp_curves_tool_export_setup (GimpSettingsBox *settings_box,
+ GtkFileChooserDialog *dialog,
+ gboolean export,
+ GimpCurvesTool *tool)
+{
+ GtkWidget *button;
+
+ if (! export)
+ return;
+
+ button = gtk_check_button_new_with_mnemonic (_("Use _old curves file format"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+ tool->export_old_format);
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), button);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &tool->export_old_format);
+}
+
+static void
+gimp_curves_tool_update_channel (GimpCurvesTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GimpCurve *curve = config->curve[config->channel];
+ GimpHistogramChannel channel;
+
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (tool->channel_menu),
+ config->channel);
+
+ switch (config->channel)
+ {
+ guchar r[256];
+ guchar g[256];
+ guchar b[256];
+
+ case GIMP_HISTOGRAM_VALUE:
+ case GIMP_HISTOGRAM_ALPHA:
+ case GIMP_HISTOGRAM_RGB:
+ case GIMP_HISTOGRAM_LUMINANCE:
+ gimp_curve_get_uchar (curve, sizeof (r), r);
+
+ gimp_color_bar_set_buffers (GIMP_COLOR_BAR (tool->xrange),
+ r, r, r);
+ break;
+
+ case GIMP_HISTOGRAM_RED:
+ case GIMP_HISTOGRAM_GREEN:
+ case GIMP_HISTOGRAM_BLUE:
+ gimp_curve_get_uchar (config->curve[GIMP_HISTOGRAM_RED],
+ sizeof (r), r);
+ gimp_curve_get_uchar (config->curve[GIMP_HISTOGRAM_GREEN],
+ sizeof (g), g);
+ gimp_curve_get_uchar (config->curve[GIMP_HISTOGRAM_BLUE],
+ sizeof (b), b);
+
+ gimp_color_bar_set_buffers (GIMP_COLOR_BAR (tool->xrange),
+ r, g, b);
+ break;
+ }
+
+ gimp_histogram_view_set_channel (GIMP_HISTOGRAM_VIEW (tool->graph),
+ config->channel);
+ gimp_curve_view_set_xpos (GIMP_CURVE_VIEW (tool->graph),
+ tool->picked_color[config->channel]);
+
+ gimp_color_bar_set_channel (GIMP_COLOR_BAR (tool->yrange),
+ config->channel);
+
+ gimp_curve_view_remove_all_backgrounds (GIMP_CURVE_VIEW (tool->graph));
+
+ for (channel = GIMP_HISTOGRAM_VALUE;
+ channel <= GIMP_HISTOGRAM_ALPHA;
+ channel++)
+ {
+ GimpRGB curve_color;
+ gboolean has_color;
+
+ has_color = curves_get_channel_color (tool->graph, channel, &curve_color);
+
+ if (channel == config->channel)
+ {
+ gimp_curve_view_set_curve (GIMP_CURVE_VIEW (tool->graph), curve,
+ has_color ? &curve_color : NULL);
+ }
+ else
+ {
+ gimp_curve_view_add_background (GIMP_CURVE_VIEW (tool->graph),
+ config->curve[channel],
+ has_color ? &curve_color : NULL);
+ }
+ }
+
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (tool->curve_type),
+ curve->curve_type);
+
+ gimp_curves_tool_update_point (tool);
+}
+
+static void
+gimp_curves_tool_update_point (GimpCurvesTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GimpCurve *curve = config->curve[config->channel];
+ gint point;
+
+ point = gimp_curve_view_get_selected (GIMP_CURVE_VIEW (tool->graph));
+
+ gtk_widget_set_sensitive (tool->point_box, point >= 0);
+
+ if (point >= 0)
+ {
+ gdouble min = 0.0;
+ gdouble max = 1.0;
+ gdouble x;
+ gdouble y;
+
+ if (point > 0)
+ gimp_curve_get_point (curve, point - 1, &min, NULL);
+
+ if (point < gimp_curve_get_n_points (curve) - 1)
+ gimp_curve_get_point (curve, point + 1, &max, NULL);
+
+ gimp_curve_get_point (curve, point, &x, &y);
+
+ x *= tool->scale;
+ y *= tool->scale;
+ min *= tool->scale;
+ max *= tool->scale;
+
+ g_signal_handlers_block_by_func (tool->point_input,
+ curves_point_coords_callback,
+ tool);
+ g_signal_handlers_block_by_func (tool->point_output,
+ curves_point_coords_callback,
+ tool);
+
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (tool->point_input), min, max);
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (tool->point_input), x);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (tool->point_output), y);
+
+
+ g_signal_handlers_unblock_by_func (tool->point_input,
+ curves_point_coords_callback,
+ tool);
+ g_signal_handlers_unblock_by_func (tool->point_output,
+ curves_point_coords_callback,
+ tool);
+
+ g_signal_handlers_block_by_func (tool->point_type,
+ curves_point_type_callback,
+ tool);
+
+ gimp_int_radio_group_set_active (
+ GTK_RADIO_BUTTON (tool->point_type),
+ gimp_curve_get_point_type (curve, point));
+
+ g_signal_handlers_unblock_by_func (tool->point_type,
+ curves_point_type_callback,
+ tool);
+ }
+}
+
+static void
+curves_curve_dirty_callback (GimpCurve *curve,
+ GimpCurvesTool *tool)
+{
+ if (tool->graph &&
+ gimp_curve_view_get_curve (GIMP_CURVE_VIEW (tool->graph)) == curve)
+ {
+ gimp_curves_tool_update_point (tool);
+ }
+}
+
+static void
+curves_channel_callback (GtkWidget *widget,
+ GimpCurvesTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ gint value;
+
+ if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value) &&
+ config->channel != value)
+ {
+ g_object_set (config,
+ "channel", value,
+ NULL);
+ }
+}
+
+static void
+curves_channel_reset_callback (GtkWidget *widget,
+ GimpCurvesTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+
+ gimp_curve_reset (config->curve[config->channel], FALSE);
+}
+
+static gboolean
+curves_menu_sensitivity (gint value,
+ gpointer data)
+{
+ GimpDrawable *drawable = GIMP_TOOL (data)->drawable;
+ GimpHistogramChannel channel = value;
+
+ if (!drawable)
+ return FALSE;
+
+ switch (channel)
+ {
+ case GIMP_HISTOGRAM_VALUE:
+ return TRUE;
+
+ case GIMP_HISTOGRAM_RED:
+ case GIMP_HISTOGRAM_GREEN:
+ case GIMP_HISTOGRAM_BLUE:
+ return gimp_drawable_is_rgb (drawable);
+
+ case GIMP_HISTOGRAM_ALPHA:
+ return gimp_drawable_has_alpha (drawable);
+
+ case GIMP_HISTOGRAM_RGB:
+ return FALSE;
+
+ case GIMP_HISTOGRAM_LUMINANCE:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static void
+curves_graph_selection_callback (GtkWidget *widget,
+ GimpCurvesTool *tool)
+{
+ gimp_curves_tool_update_point (tool);
+}
+
+static void
+curves_point_coords_callback (GtkWidget *widget,
+ GimpCurvesTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GimpCurve *curve = config->curve[config->channel];
+ gint point;
+
+ point = gimp_curve_view_get_selected (GIMP_CURVE_VIEW (tool->graph));
+
+ if (point >= 0)
+ {
+ gdouble x;
+ gdouble y;
+
+ x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (tool->point_input));
+ y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (tool->point_output));
+
+ x /= tool->scale;
+ y /= tool->scale;
+
+ gimp_curve_set_point (curve, point, x, y);
+ }
+}
+
+static void
+curves_point_type_callback (GtkWidget *widget,
+ GimpCurvesTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GimpCurve *curve = config->curve[config->channel];
+ gint point;
+
+ point = gimp_curve_view_get_selected (GIMP_CURVE_VIEW (tool->graph));
+
+ if (point >= 0 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+ {
+ GimpCurvePointType type;
+
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
+ "gimp-item-data"));
+
+ gimp_curve_set_point_type (curve, point, type);
+ }
+}
+
+static void
+curves_curve_type_callback (GtkWidget *widget,
+ GimpCurvesTool *tool)
+{
+ gint value;
+
+ if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value))
+ {
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpCurvesConfig *config = GIMP_CURVES_CONFIG (filter_tool->config);
+ GimpCurveType curve_type = value;
+
+ if (config->curve[config->channel]->curve_type != curve_type)
+ gimp_curve_set_curve_type (config->curve[config->channel], curve_type);
+ }
+}
+
+static gboolean
+curves_get_channel_color (GtkWidget *widget,
+ GimpHistogramChannel channel,
+ GimpRGB *color)
+{
+ static const GimpRGB channel_colors[GIMP_HISTOGRAM_RGB] =
+ {
+ { 0.0, 0.0, 0.0, 1.0 },
+ { 1.0, 0.0, 0.0, 1.0 },
+ { 0.0, 1.0, 0.0, 1.0 },
+ { 0.0, 0.0, 1.0, 1.0 },
+ { 0.5, 0.5, 0.5, 1.0 }
+ };
+
+ if (channel == GIMP_HISTOGRAM_VALUE)
+ return FALSE;
+
+ if (channel == GIMP_HISTOGRAM_ALPHA)
+ {
+ GtkStyle *style = gtk_widget_get_style (widget);
+
+ gimp_rgba_set (color,
+ style->text_aa[GTK_STATE_NORMAL].red / 65535.0,
+ style->text_aa[GTK_STATE_NORMAL].green / 65535.0,
+ style->text_aa[GTK_STATE_NORMAL].blue / 65535.0,
+ 1.0);
+ return TRUE;
+ }
+
+ *color = channel_colors[channel];
+ return TRUE;
+}
diff --git a/app/tools/gimpcurvestool.h b/app/tools/gimpcurvestool.h
new file mode 100644
index 0000000..8d3ba8b
--- /dev/null
+++ b/app/tools/gimpcurvestool.h
@@ -0,0 +1,69 @@
+/* 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_CURVES_TOOL_H__
+#define __GIMP_CURVES_TOOL_H__
+
+
+#include "gimpfiltertool.h"
+
+
+#define GIMP_TYPE_CURVES_TOOL (gimp_curves_tool_get_type ())
+#define GIMP_CURVES_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CURVES_TOOL, GimpCurvesTool))
+#define GIMP_IS_CURVES_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CURVES_TOOL))
+#define GIMP_CURVES_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CURVES_TOOL, GimpCurvesToolClass))
+#define GIMP_IS_CURVES_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CURVES_TOOL))
+
+
+typedef struct _GimpCurvesTool GimpCurvesTool;
+typedef struct _GimpCurvesToolClass GimpCurvesToolClass;
+
+struct _GimpCurvesTool
+{
+ GimpFilterTool parent_instance;
+
+ /* dialog */
+ gdouble scale;
+ gdouble picked_color[5];
+
+ GtkWidget *channel_menu;
+ GtkWidget *xrange;
+ GtkWidget *yrange;
+ GtkWidget *graph;
+ GtkWidget *point_box;
+ GtkWidget *point_input;
+ GtkWidget *point_output;
+ GtkWidget *point_type;
+ GtkWidget *curve_type;
+
+ /* export dialog */
+ gboolean export_old_format;
+};
+
+struct _GimpCurvesToolClass
+{
+ GimpFilterToolClass parent_class;
+};
+
+
+void gimp_curves_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_curves_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CURVES_TOOL_H__ */
diff --git a/app/tools/gimpdodgeburntool.c b/app/tools/gimpdodgeburntool.c
new file mode 100644
index 0000000..399eb5f
--- /dev/null
+++ b/app/tools/gimpdodgeburntool.c
@@ -0,0 +1,243 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimpdodgeburnoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdodgeburntool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_dodge_burn_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_dodge_burn_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_dodge_burn_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_dodge_burn_tool_status_update (GimpTool *tool,
+ GimpDodgeBurnType type);
+
+static GtkWidget * gimp_dodge_burn_options_gui (GimpToolOptions *tool_options);
+
+
+G_DEFINE_TYPE (GimpDodgeBurnTool, gimp_dodge_burn_tool, GIMP_TYPE_BRUSH_TOOL)
+
+#define parent_class gimp_dodge_burn_tool_parent_class
+
+
+void
+gimp_dodge_burn_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_DODGE_BURN_TOOL,
+ GIMP_TYPE_DODGE_BURN_OPTIONS,
+ gimp_dodge_burn_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK,
+ "gimp-dodge-burn-tool",
+ _("Dodge / Burn"),
+ _("Dodge / Burn Tool: Selectively lighten or darken using a brush"),
+ N_("Dod_ge / Burn"), "<shift>D",
+ NULL, GIMP_HELP_TOOL_DODGE_BURN,
+ GIMP_ICON_TOOL_DODGE,
+ data);
+}
+
+static void
+gimp_dodge_burn_tool_class_init (GimpDodgeBurnToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ tool_class->modifier_key = gimp_dodge_burn_tool_modifier_key;
+ tool_class->cursor_update = gimp_dodge_burn_tool_cursor_update;
+ tool_class->oper_update = gimp_dodge_burn_tool_oper_update;
+}
+
+static void
+gimp_dodge_burn_tool_init (GimpDodgeBurnTool *dodgeburn)
+{
+ GimpTool *tool = GIMP_TOOL (dodgeburn);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_DODGE);
+ gimp_tool_control_set_toggle_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_BURN);
+
+ gimp_dodge_burn_tool_status_update (tool, GIMP_DODGE_BURN_TYPE_BURN);
+}
+
+static void
+gimp_dodge_burn_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpDodgeBurnTool *dodgeburn = GIMP_DODGE_BURN_TOOL (tool);
+ GimpDodgeBurnOptions *options = GIMP_DODGE_BURN_TOOL_GET_OPTIONS (tool);
+ GdkModifierType line_mask = GIMP_PAINT_TOOL_LINE_MASK;
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if ((key == toggle_mask &&
+ ! (state & line_mask) && /* leave stuff untouched in line draw mode */
+ press != dodgeburn->toggled)
+
+ ||
+
+ (key == line_mask && /* toggle back after keypresses CTRL(hold)-> */
+ ! press && /* SHIFT(hold)->CTRL(release)->SHIFT(release) */
+ dodgeburn->toggled &&
+ ! (state & toggle_mask)))
+ {
+ dodgeburn->toggled = press;
+
+ switch (options->type)
+ {
+ case GIMP_DODGE_BURN_TYPE_DODGE:
+ g_object_set (options, "type", GIMP_DODGE_BURN_TYPE_BURN, NULL);
+ break;
+
+ case GIMP_DODGE_BURN_TYPE_BURN:
+ g_object_set (options, "type", GIMP_DODGE_BURN_TYPE_DODGE, NULL);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
+ display);
+}
+
+static void
+gimp_dodge_burn_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpDodgeBurnOptions *options = GIMP_DODGE_BURN_TOOL_GET_OPTIONS (tool);
+
+ gimp_tool_control_set_toggled (tool->control,
+ options->type == GIMP_DODGE_BURN_TYPE_BURN);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+}
+
+static void
+gimp_dodge_burn_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpDodgeBurnOptions *options = GIMP_DODGE_BURN_TOOL_GET_OPTIONS (tool);
+
+ gimp_dodge_burn_tool_status_update (tool, options->type);
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+}
+
+static void
+gimp_dodge_burn_tool_status_update (GimpTool *tool,
+ GimpDodgeBurnType type)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+
+ switch (type)
+ {
+ case GIMP_DODGE_BURN_TYPE_DODGE:
+ paint_tool->status = _("Click to dodge");
+ paint_tool->status_line = _("Click to dodge the line");
+ paint_tool->status_ctrl = _("%s to burn");
+ break;
+
+ case GIMP_DODGE_BURN_TYPE_BURN:
+ paint_tool->status = _("Click to burn");
+ paint_tool->status_line = _("Click to burn the line");
+ paint_tool->status_ctrl = _("%s to dodge");
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_dodge_burn_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *scale;
+ gchar *str;
+ GdkModifierType toggle_mask;
+
+ toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ /* the type (dodge or burn) */
+ str = g_strdup_printf (_("Type (%s)"),
+ gimp_get_mod_string (toggle_mask));
+
+ frame = gimp_prop_enum_radio_frame_new (config, "type",
+ str, 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ g_free (str);
+
+ /* mode (highlights, midtones, or shadows) */
+ frame = gimp_prop_enum_radio_frame_new (config, "mode", NULL,
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the exposure scale */
+ scale = gimp_prop_spin_scale_new (config, "exposure", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return vbox;
+}
diff --git a/app/tools/gimpdodgeburntool.h b/app/tools/gimpdodgeburntool.h
new file mode 100644
index 0000000..68687ea
--- /dev/null
+++ b/app/tools/gimpdodgeburntool.h
@@ -0,0 +1,56 @@
+/* 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_DODGE_BURN_TOOL_H__
+#define __GIMP_DODGE_BURN_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+
+
+#define GIMP_TYPE_DODGE_BURN_TOOL (gimp_dodge_burn_tool_get_type ())
+#define GIMP_DODGE_BURN_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DODGE_BURN_TOOL, GimpDodgeBurnTool))
+#define GIMP_IS_DODGE_BURN_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DODGE_BURN_TOOL))
+#define GIMP_DODGE_BURN_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DODGE_BURN_TOOL, GimpDodgeBurnToolClass))
+#define GIMP_IS_DODGE_BURN_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DODGE_BURN_TOOL))
+
+#define GIMP_DODGE_BURN_TOOL_GET_OPTIONS(t) (GIMP_DODGE_BURN_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpDodgeBurnTool GimpDodgeBurnTool;
+typedef struct _GimpDodgeBurnToolClass GimpDodgeBurnToolClass;
+
+struct _GimpDodgeBurnTool
+{
+ GimpBrushTool parent_instance;
+
+ gboolean toggled;
+};
+
+struct _GimpDodgeBurnToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+void gimp_dodge_burn_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_dodge_burn_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DODGEBURN_TOOL_H__ */
diff --git a/app/tools/gimpdrawtool.c b/app/tools/gimpdrawtool.c
new file mode 100644
index 0000000..b97e30a
--- /dev/null
+++ b/app/tools/gimpdrawtool.c
@@ -0,0 +1,1281 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "tools-types.h"
+
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+
+#include "display/gimpcanvas.h"
+#include "display/gimpcanvasarc.h"
+#include "display/gimpcanvasboundary.h"
+#include "display/gimpcanvasgroup.h"
+#include "display/gimpcanvasguide.h"
+#include "display/gimpcanvashandle.h"
+#include "display/gimpcanvasitem-utils.h"
+#include "display/gimpcanvasline.h"
+#include "display/gimpcanvaspen.h"
+#include "display/gimpcanvaspolygon.h"
+#include "display/gimpcanvasrectangle.h"
+#include "display/gimpcanvassamplepoint.h"
+#include "display/gimpcanvastextcursor.h"
+#include "display/gimpcanvastransformpreview.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-items.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolwidget.h"
+
+#include "gimpdrawtool.h"
+#include "gimptoolcontrol.h"
+
+
+#define USE_TIMEOUT
+#define DRAW_FPS 120
+#define DRAW_TIMEOUT (1000 /* milliseconds */ / (2 * DRAW_FPS))
+#define MINIMUM_DRAW_INTERVAL (G_TIME_SPAN_SECOND / DRAW_FPS)
+
+
+static void gimp_draw_tool_dispose (GObject *object);
+
+static gboolean gimp_draw_tool_has_display (GimpTool *tool,
+ GimpDisplay *display);
+static GimpDisplay * gimp_draw_tool_has_image (GimpTool *tool,
+ GimpImage *image);
+static void gimp_draw_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static gboolean gimp_draw_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static gboolean gimp_draw_tool_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_draw_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_draw_tool_active_modifier_key
+ (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_draw_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_draw_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_draw_tool_widget_status (GimpToolWidget *widget,
+ const gchar *status,
+ GimpTool *tool);
+static void gimp_draw_tool_widget_status_coords
+ (GimpToolWidget *widget,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help,
+ GimpTool *tool);
+static void gimp_draw_tool_widget_message
+ (GimpToolWidget *widget,
+ const gchar *message,
+ GimpTool *tool);
+static void gimp_draw_tool_widget_snap_offsets
+ (GimpToolWidget *widget,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ GimpTool *tool);
+
+static void gimp_draw_tool_draw (GimpDrawTool *draw_tool);
+static void gimp_draw_tool_undraw (GimpDrawTool *draw_tool);
+static void gimp_draw_tool_real_draw (GimpDrawTool *draw_tool);
+
+
+G_DEFINE_TYPE (GimpDrawTool, gimp_draw_tool, GIMP_TYPE_TOOL)
+
+#define parent_class gimp_draw_tool_parent_class
+
+
+static void
+gimp_draw_tool_class_init (GimpDrawToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ object_class->dispose = gimp_draw_tool_dispose;
+
+ tool_class->has_display = gimp_draw_tool_has_display;
+ tool_class->has_image = gimp_draw_tool_has_image;
+ tool_class->control = gimp_draw_tool_control;
+ tool_class->key_press = gimp_draw_tool_key_press;
+ tool_class->key_release = gimp_draw_tool_key_release;
+ tool_class->modifier_key = gimp_draw_tool_modifier_key;
+ tool_class->active_modifier_key = gimp_draw_tool_active_modifier_key;
+ tool_class->oper_update = gimp_draw_tool_oper_update;
+ tool_class->cursor_update = gimp_draw_tool_cursor_update;
+
+ klass->draw = gimp_draw_tool_real_draw;
+}
+
+static void
+gimp_draw_tool_init (GimpDrawTool *draw_tool)
+{
+ draw_tool->display = NULL;
+ draw_tool->paused_count = 0;
+ draw_tool->preview = NULL;
+ draw_tool->item = NULL;
+}
+
+static void
+gimp_draw_tool_dispose (GObject *object)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (object);
+
+ if (draw_tool->draw_timeout)
+ {
+ g_source_remove (draw_tool->draw_timeout);
+ draw_tool->draw_timeout = 0;
+ }
+
+ gimp_draw_tool_set_widget (draw_tool, NULL);
+ gimp_draw_tool_set_default_status (draw_tool, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gboolean
+gimp_draw_tool_has_display (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ return (display == draw_tool->display ||
+ GIMP_TOOL_CLASS (parent_class)->has_display (tool, display));
+}
+
+static GimpDisplay *
+gimp_draw_tool_has_image (GimpTool *tool,
+ GimpImage *image)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ GimpDisplay *display;
+
+ display = GIMP_TOOL_CLASS (parent_class)->has_image (tool, image);
+
+ if (! display && draw_tool->display)
+ {
+ if (image && gimp_display_get_image (draw_tool->display) == image)
+ display = draw_tool->display;
+
+ /* NULL image means any display */
+ if (! image)
+ display = draw_tool->display;
+ }
+
+ return display;
+}
+
+static void
+gimp_draw_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ if (gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_stop (draw_tool);
+ gimp_draw_tool_set_widget (draw_tool, NULL);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static gboolean
+gimp_draw_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (draw_tool->widget && display == draw_tool->display)
+ {
+ return gimp_tool_widget_key_press (draw_tool->widget, kevent);
+ }
+
+ return GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+}
+
+static gboolean
+gimp_draw_tool_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (draw_tool->widget && display == draw_tool->display)
+ {
+ return gimp_tool_widget_key_release (draw_tool->widget, kevent);
+ }
+
+ return GIMP_TOOL_CLASS (parent_class)->key_release (tool, kevent, display);
+}
+
+static void
+gimp_draw_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (draw_tool->widget && display == draw_tool->display)
+ {
+ gimp_tool_widget_hover_modifier (draw_tool->widget, key, press, state);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
+ display);
+}
+
+static void
+gimp_draw_tool_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (draw_tool->widget && display == draw_tool->display)
+ {
+ gimp_tool_widget_motion_modifier (draw_tool->widget, key, press, state);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->active_modifier_key (tool, key, press, state,
+ display);
+}
+
+static void
+gimp_draw_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (draw_tool->widget && display == draw_tool->display)
+ {
+ gimp_tool_widget_hover (draw_tool->widget, coords, state, proximity);
+ }
+ else if (proximity && draw_tool->default_status)
+ {
+ gimp_tool_replace_status (tool, display, "%s", draw_tool->default_status);
+ }
+ else if (! proximity)
+ {
+ gimp_tool_pop_status (tool, display);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+ }
+}
+
+static void
+gimp_draw_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (draw_tool->widget && display == draw_tool->display)
+ {
+ GimpCursorType cursor;
+ GimpToolCursorType tool_cursor;
+ GimpCursorModifier modifier;
+
+ cursor = gimp_tool_control_get_cursor (tool->control);
+ tool_cursor = gimp_tool_control_get_tool_cursor (tool->control);
+ modifier = gimp_tool_control_get_cursor_modifier (tool->control);
+
+ if (gimp_tool_widget_get_cursor (draw_tool->widget, coords, state,
+ &cursor, &tool_cursor, &modifier))
+ {
+ gimp_tool_set_cursor (tool, display,
+ cursor, tool_cursor, modifier);
+ return;
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+}
+
+static void
+gimp_draw_tool_widget_status (GimpToolWidget *widget,
+ const gchar *status,
+ GimpTool *tool)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ {
+ if (status)
+ gimp_tool_replace_status (tool, draw_tool->display, "%s", status);
+ else
+ gimp_tool_pop_status (tool, draw_tool->display);
+ }
+}
+
+static void
+gimp_draw_tool_widget_status_coords (GimpToolWidget *widget,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help,
+ GimpTool *tool)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ {
+ gimp_tool_pop_status (tool, draw_tool->display);
+ gimp_tool_push_status_coords (tool, draw_tool->display,
+ gimp_tool_control_get_precision (
+ tool->control),
+ title, x, separator, y, help);
+ }
+}
+
+static void
+gimp_draw_tool_widget_message (GimpToolWidget *widget,
+ const gchar *message,
+ GimpTool *tool)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ gimp_tool_message_literal (tool, draw_tool->display, message);
+}
+
+static void
+gimp_draw_tool_widget_snap_offsets (GimpToolWidget *widget,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ GimpTool *tool)
+{
+ gimp_tool_control_set_snap_offsets (tool->control,
+ offset_x, offset_y,
+ width, height);
+}
+
+#ifdef USE_TIMEOUT
+static gboolean
+gimp_draw_tool_draw_timeout (GimpDrawTool *draw_tool)
+{
+ guint64 now = g_get_monotonic_time ();
+
+ /* keep the timeout running if the last drawing just happened */
+ if ((now - draw_tool->last_draw_time) <= MINIMUM_DRAW_INTERVAL)
+ return TRUE;
+
+ draw_tool->draw_timeout = 0;
+
+ gimp_draw_tool_draw (draw_tool);
+
+ return FALSE;
+}
+#endif
+
+static void
+gimp_draw_tool_draw (GimpDrawTool *draw_tool)
+{
+ guint64 now = g_get_monotonic_time ();
+
+ if (draw_tool->display &&
+ draw_tool->paused_count == 0 &&
+ (! draw_tool->draw_timeout ||
+ (now - draw_tool->last_draw_time) > MINIMUM_DRAW_INTERVAL))
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (draw_tool->display);
+
+ if (draw_tool->draw_timeout)
+ {
+ g_source_remove (draw_tool->draw_timeout);
+ draw_tool->draw_timeout = 0;
+ }
+
+ gimp_draw_tool_undraw (draw_tool);
+
+ GIMP_DRAW_TOOL_GET_CLASS (draw_tool)->draw (draw_tool);
+
+ if (draw_tool->group_stack)
+ {
+ g_warning ("%s: draw_tool->group_stack not empty after calling "
+ "GimpDrawTool::draw() of %s",
+ G_STRFUNC,
+ g_type_name (G_TYPE_FROM_INSTANCE (draw_tool)));
+
+ while (draw_tool->group_stack)
+ gimp_draw_tool_pop_group (draw_tool);
+ }
+
+ if (draw_tool->preview)
+ gimp_display_shell_add_preview_item (shell, draw_tool->preview);
+
+ if (draw_tool->item)
+ gimp_display_shell_add_tool_item (shell, draw_tool->item);
+
+#if 0
+ gimp_display_shell_flush (shell, TRUE);
+#endif
+
+ draw_tool->last_draw_time = g_get_monotonic_time ();
+
+#if 0
+ g_printerr ("drawing tool stuff took %f seconds\n",
+ (draw_tool->last_draw_time - now) / 1000000.0);
+#endif
+ }
+}
+
+static void
+gimp_draw_tool_undraw (GimpDrawTool *draw_tool)
+{
+ if (draw_tool->display)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (draw_tool->display);
+
+ if (draw_tool->preview)
+ {
+ gimp_display_shell_remove_preview_item (shell, draw_tool->preview);
+ g_clear_object (&draw_tool->preview);
+ }
+
+ if (draw_tool->item)
+ {
+ gimp_display_shell_remove_tool_item (shell, draw_tool->item);
+ g_clear_object (&draw_tool->item);
+ }
+ }
+}
+
+static void
+gimp_draw_tool_real_draw (GimpDrawTool *draw_tool)
+{
+ if (draw_tool->widget)
+ {
+ GimpCanvasItem *item = gimp_tool_widget_get_item (draw_tool->widget);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ }
+}
+
+void
+gimp_draw_tool_start (GimpDrawTool *draw_tool,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_draw_tool_is_active (draw_tool) == FALSE);
+
+ draw_tool->display = display;
+
+ gimp_draw_tool_draw (draw_tool);
+}
+
+void
+gimp_draw_tool_stop (GimpDrawTool *draw_tool)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (gimp_draw_tool_is_active (draw_tool) == TRUE);
+
+ gimp_draw_tool_undraw (draw_tool);
+
+ if (draw_tool->draw_timeout)
+ {
+ g_source_remove (draw_tool->draw_timeout);
+ draw_tool->draw_timeout = 0;
+ }
+
+ draw_tool->last_draw_time = 0;
+
+ draw_tool->display = NULL;
+}
+
+gboolean
+gimp_draw_tool_is_active (GimpDrawTool *draw_tool)
+{
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), FALSE);
+
+ return draw_tool->display != NULL;
+}
+
+void
+gimp_draw_tool_pause (GimpDrawTool *draw_tool)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+
+ draw_tool->paused_count++;
+
+ if (draw_tool->draw_timeout)
+ {
+ g_source_remove (draw_tool->draw_timeout);
+ draw_tool->draw_timeout = 0;
+ }
+}
+
+void
+gimp_draw_tool_resume (GimpDrawTool *draw_tool)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (draw_tool->paused_count > 0);
+
+ draw_tool->paused_count--;
+
+ if (draw_tool->paused_count == 0)
+ {
+#ifdef USE_TIMEOUT
+ /* Don't install the timeout if the draw tool isn't active, so
+ * suspend()/resume() can always be called, and have no side
+ * effect on an inactive tool. See bug #687851.
+ */
+ if (gimp_draw_tool_is_active (draw_tool) && ! draw_tool->draw_timeout)
+ {
+ draw_tool->draw_timeout =
+ gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE,
+ DRAW_TIMEOUT,
+ (GSourceFunc) gimp_draw_tool_draw_timeout,
+ draw_tool, NULL);
+ }
+#endif
+
+ /* call draw() anyway, it will do nothing if the timeout is
+ * running, but will additionally check the drawing times to
+ * ensure the minimum framerate
+ */
+ gimp_draw_tool_draw (draw_tool);
+ }
+}
+
+/**
+ * gimp_draw_tool_calc_distance:
+ * @draw_tool: a #GimpDrawTool
+ * @display: a #GimpDisplay
+ * @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_draw_tool_calc_distance_square() instead.
+ *
+ * Returns: the distance between the given points in display coordinates
+ **/
+gdouble
+gimp_draw_tool_calc_distance (GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ return sqrt (gimp_draw_tool_calc_distance_square (draw_tool, display,
+ x1, y1, x2, y2));
+}
+
+/**
+ * gimp_draw_tool_calc_distance_square:
+ * @draw_tool: a #GimpDrawTool
+ * @display: a #GimpDisplay
+ * @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_draw_tool_calc_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_draw_tool_calc_distance_square (GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpDisplayShell *shell;
+ gdouble tx1, ty1;
+ gdouble tx2, ty2;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), 0.0);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), 0.0);
+
+ shell = gimp_display_get_shell (display);
+
+ gimp_display_shell_transform_xy_f (shell, x1, y1, &tx1, &ty1);
+ gimp_display_shell_transform_xy_f (shell, x2, y2, &tx2, &ty2);
+
+ return SQR (tx2 - tx1) + SQR (ty2 - ty1);
+}
+
+void
+gimp_draw_tool_set_widget (GimpDrawTool *draw_tool,
+ GimpToolWidget *widget)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (widget == NULL || GIMP_IS_TOOL_WIDGET (widget));
+
+ if (widget == draw_tool->widget)
+ return;
+
+ if (draw_tool->widget)
+ {
+ gimp_tool_widget_set_focus (draw_tool->widget, FALSE);
+
+ g_signal_handlers_disconnect_by_func (draw_tool->widget,
+ gimp_draw_tool_widget_status,
+ draw_tool);
+ g_signal_handlers_disconnect_by_func (draw_tool->widget,
+ gimp_draw_tool_widget_status_coords,
+ draw_tool);
+ g_signal_handlers_disconnect_by_func (draw_tool->widget,
+ gimp_draw_tool_widget_message,
+ draw_tool);
+ g_signal_handlers_disconnect_by_func (draw_tool->widget,
+ gimp_draw_tool_widget_snap_offsets,
+ draw_tool);
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ {
+ GimpCanvasItem *item = gimp_tool_widget_get_item (draw_tool->widget);
+
+ gimp_draw_tool_remove_item (draw_tool, item);
+ }
+
+ g_object_unref (draw_tool->widget);
+ }
+
+ draw_tool->widget = widget;
+
+ if (draw_tool->widget)
+ {
+ g_object_ref (draw_tool->widget);
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ {
+ GimpCanvasItem *item = gimp_tool_widget_get_item (draw_tool->widget);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ }
+
+ g_signal_connect (draw_tool->widget, "status",
+ G_CALLBACK (gimp_draw_tool_widget_status),
+ draw_tool);
+ g_signal_connect (draw_tool->widget, "status-coords",
+ G_CALLBACK (gimp_draw_tool_widget_status_coords),
+ draw_tool);
+ g_signal_connect (draw_tool->widget, "message",
+ G_CALLBACK (gimp_draw_tool_widget_message),
+ draw_tool);
+ g_signal_connect (draw_tool->widget, "snap-offsets",
+ G_CALLBACK (gimp_draw_tool_widget_snap_offsets),
+ draw_tool);
+
+ gimp_tool_widget_set_focus (draw_tool->widget, TRUE);
+ }
+}
+
+void
+gimp_draw_tool_set_default_status (GimpDrawTool *draw_tool,
+ const gchar *status)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+
+ if (draw_tool->default_status)
+ g_free (draw_tool->default_status);
+
+ draw_tool->default_status = g_strdup (status);
+}
+
+void
+gimp_draw_tool_add_preview (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ if (! draw_tool->preview)
+ draw_tool->preview =
+ gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));
+
+ gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (draw_tool->preview), item);
+}
+
+void
+gimp_draw_tool_remove_preview (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (draw_tool->preview != NULL);
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (draw_tool->preview), item);
+}
+
+void
+gimp_draw_tool_add_item (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item)
+{
+ GimpCanvasGroup *group;
+
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+
+ if (! draw_tool->item)
+ draw_tool->item =
+ gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));
+
+ group = GIMP_CANVAS_GROUP (draw_tool->item);
+
+ if (draw_tool->group_stack)
+ group = draw_tool->group_stack->data;
+
+ gimp_canvas_group_add_item (group, item);
+}
+
+void
+gimp_draw_tool_remove_item (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
+ g_return_if_fail (draw_tool->item != NULL);
+
+ gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (draw_tool->item), item);
+}
+
+GimpCanvasGroup *
+gimp_draw_tool_add_stroke_group (GimpDrawTool *draw_tool)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));
+ gimp_canvas_group_set_group_stroking (GIMP_CANVAS_GROUP (item), TRUE);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return GIMP_CANVAS_GROUP (item);
+}
+
+GimpCanvasGroup *
+gimp_draw_tool_add_fill_group (GimpDrawTool *draw_tool)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));
+ gimp_canvas_group_set_group_filling (GIMP_CANVAS_GROUP (item), TRUE);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return GIMP_CANVAS_GROUP (item);
+}
+
+void
+gimp_draw_tool_push_group (GimpDrawTool *draw_tool,
+ GimpCanvasGroup *group)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
+
+ draw_tool->group_stack = g_list_prepend (draw_tool->group_stack, group);
+}
+
+void
+gimp_draw_tool_pop_group (GimpDrawTool *draw_tool)
+{
+ g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
+ g_return_if_fail (draw_tool->group_stack != NULL);
+
+ draw_tool->group_stack = g_list_remove (draw_tool->group_stack,
+ draw_tool->group_stack->data);
+}
+
+/**
+ * gimp_draw_tool_add_line:
+ * @draw_tool: the #GimpDrawTool
+ * @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 takes image space coordinates and transforms them to
+ * screen window coordinates, then draws a line between the resulting
+ * coordindates.
+ **/
+GimpCanvasItem *
+gimp_draw_tool_add_line (GimpDrawTool *draw_tool,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_line_new (gimp_display_get_shell (draw_tool->display),
+ x1, y1, x2, y2);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+/**
+ * gimp_draw_tool_add_guide:
+ * @draw_tool: the #GimpDrawTool
+ * @orientation: the orientation of the guide line
+ * @position: the position of the guide line in image coordinates
+ *
+ * This function draws a guide line across the canvas.
+ **/
+GimpCanvasItem *
+gimp_draw_tool_add_guide (GimpDrawTool *draw_tool,
+ GimpOrientationType orientation,
+ gint position,
+ GimpGuideStyle style)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_guide_new (gimp_display_get_shell (draw_tool->display),
+ orientation, position,
+ style);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+/**
+ * gimp_draw_tool_add_crosshair:
+ * @draw_tool: the #GimpDrawTool
+ * @position_x: the position of the vertical guide line in image coordinates
+ * @position_y: the position of the horizontal guide line in image coordinates
+ *
+ * This function draws two crossing guide lines across the canvas.
+ **/
+GimpCanvasItem *
+gimp_draw_tool_add_crosshair (GimpDrawTool *draw_tool,
+ gint position_x,
+ gint position_y)
+{
+ GimpCanvasGroup *group;
+
+ group = gimp_draw_tool_add_stroke_group (draw_tool);
+
+ gimp_draw_tool_push_group (draw_tool, group);
+ gimp_draw_tool_add_guide (draw_tool,
+ GIMP_ORIENTATION_VERTICAL, position_x,
+ GIMP_GUIDE_STYLE_NONE);
+ gimp_draw_tool_add_guide (draw_tool,
+ GIMP_ORIENTATION_HORIZONTAL, position_y,
+ GIMP_GUIDE_STYLE_NONE);
+ gimp_draw_tool_pop_group (draw_tool);
+
+ return GIMP_CANVAS_ITEM (group);
+}
+
+/**
+ * gimp_draw_tool_add_sample_point:
+ * @draw_tool: the #GimpDrawTool
+ * @x: X position of the sample point
+ * @y: Y position of the sample point
+ * @index: Index of the sample point
+ *
+ * This function draws a sample point
+ **/
+GimpCanvasItem *
+gimp_draw_tool_add_sample_point (GimpDrawTool *draw_tool,
+ gint x,
+ gint y,
+ gint index)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_sample_point_new (gimp_display_get_shell (draw_tool->display),
+ x, y, index, TRUE);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+/**
+ * gimp_draw_tool_add_rectangle:
+ * @draw_tool: the #GimpDrawTool
+ * @filled: whether to fill the rectangle
+ * @x: horizontal image coordinate
+ * @y: vertical image coordinate
+ * @width: width in image coordinates
+ * @height: height in image coordinates
+ *
+ * This function takes image space coordinates and transforms them to
+ * screen window coordinates, then draws the resulting rectangle.
+ **/
+GimpCanvasItem *
+gimp_draw_tool_add_rectangle (GimpDrawTool *draw_tool,
+ gboolean filled,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_rectangle_new (gimp_display_get_shell (draw_tool->display),
+ x, y, width, height, filled);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_arc (GimpDrawTool *draw_tool,
+ gboolean filled,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gdouble start_angle,
+ gdouble slice_angle)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_arc_new (gimp_display_get_shell (draw_tool->display),
+ x + width / 2.0,
+ y + height / 2.0,
+ width / 2.0,
+ height / 2.0,
+ start_angle,
+ slice_angle,
+ filled);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_handle (GimpDrawTool *draw_tool,
+ GimpHandleType type,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_handle_new (gimp_display_get_shell (draw_tool->display),
+ type, anchor, x, y, width, height);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_lines (GimpDrawTool *draw_tool,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpMatrix3 *transform,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ if (points == NULL || n_points < 2)
+ return NULL;
+
+ item = gimp_canvas_polygon_new (gimp_display_get_shell (draw_tool->display),
+ points, n_points, transform, filled);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_strokes (GimpDrawTool *draw_tool,
+ const GimpCoords *points,
+ gint n_points,
+ GimpMatrix3 *transform,
+ gboolean filled)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ if (points == NULL || n_points < 2)
+ return NULL;
+
+ item = gimp_canvas_polygon_new_from_coords (gimp_display_get_shell (draw_tool->display),
+ points, n_points, transform, filled);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_pen (GimpDrawTool *draw_tool,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpContext *context,
+ GimpActiveColor color,
+ gint width)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ if (points == NULL || n_points < 2)
+ return NULL;
+
+ item = gimp_canvas_pen_new (gimp_display_get_shell (draw_tool->display),
+ points, n_points, context, color, width);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+/**
+ * gimp_draw_tool_add_boundary:
+ * @draw_tool: a #GimpDrawTool
+ * @bound_segs: the sorted brush outline
+ * @n_bound_segs: the number of segments in @bound_segs
+ * @matrix: transform matrix for the boundary
+ * @offset_x: x offset
+ * @offset_y: y offset
+ *
+ * Draw the boundary of the brush that @draw_tool uses. The boundary
+ * should be sorted with sort_boundary(), and @n_bound_segs should
+ * include the sentinel segments inserted by sort_boundary() that
+ * indicate the end of connected segment sequences (groups) .
+ */
+GimpCanvasItem *
+gimp_draw_tool_add_boundary (GimpDrawTool *draw_tool,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ GimpMatrix3 *transform,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+ g_return_val_if_fail (n_bound_segs > 0, NULL);
+ g_return_val_if_fail (bound_segs != NULL, NULL);
+
+ item = gimp_canvas_boundary_new (gimp_display_get_shell (draw_tool->display),
+ bound_segs, n_bound_segs,
+ transform,
+ offset_x, offset_y);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_text_cursor (GimpDrawTool *draw_tool,
+ PangoRectangle *cursor,
+ gboolean overwrite,
+ GimpTextDirection direction)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+
+ item = gimp_canvas_text_cursor_new (gimp_display_get_shell (draw_tool->display),
+ cursor, overwrite, direction);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_draw_tool_add_transform_preview (GimpDrawTool *draw_tool,
+ GimpPickable *pickable,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpCanvasItem *item;
+
+ g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+ g_return_val_if_fail (transform != NULL, NULL);
+
+ item = gimp_canvas_transform_preview_new (gimp_display_get_shell (draw_tool->display),
+ pickable, transform,
+ x1, y1, x2, y2);
+
+ gimp_draw_tool_add_preview (draw_tool, item);
+ g_object_unref (item);
+
+ return item;
+}
+
+gboolean
+gimp_draw_tool_on_handle (GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ 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_DRAW_TOOL (draw_tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ shell = gimp_display_get_shell (display);
+
+ 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;
+}
diff --git a/app/tools/gimpdrawtool.h b/app/tools/gimpdrawtool.h
new file mode 100644
index 0000000..68b72d5
--- /dev/null
+++ b/app/tools/gimpdrawtool.h
@@ -0,0 +1,206 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others.
+ *
+ * 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_DRAW_TOOL_H__
+#define __GIMP_DRAW_TOOL_H__
+
+
+#include "gimptool.h"
+
+
+#define GIMP_TOOL_HANDLE_SIZE_CIRCLE 13
+#define GIMP_TOOL_HANDLE_SIZE_CROSS 15
+#define GIMP_TOOL_HANDLE_SIZE_CROSSHAIR 43
+#define GIMP_TOOL_HANDLE_SIZE_LARGE 25
+#define GIMP_TOOL_HANDLE_SIZE_SMALL 7
+
+
+#define GIMP_TYPE_DRAW_TOOL (gimp_draw_tool_get_type ())
+#define GIMP_DRAW_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAW_TOOL, GimpDrawTool))
+#define GIMP_DRAW_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAW_TOOL, GimpDrawToolClass))
+#define GIMP_IS_DRAW_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAW_TOOL))
+#define GIMP_IS_DRAW_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAW_TOOL))
+#define GIMP_DRAW_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAW_TOOL, GimpDrawToolClass))
+
+
+typedef struct _GimpDrawToolClass GimpDrawToolClass;
+
+struct _GimpDrawTool
+{
+ GimpTool parent_instance;
+
+ GimpDisplay *display; /* The display we are drawing to (may be
+ * a different one than tool->display)
+ */
+
+ gint paused_count; /* count to keep track of multiple pauses */
+ guint draw_timeout; /* draw delay timeout ID */
+ guint64 last_draw_time; /* time of last draw(), monotonically */
+
+ GimpToolWidget *widget;
+ gchar *default_status;
+ GimpCanvasItem *preview;
+ GimpCanvasItem *item;
+ GList *group_stack;
+};
+
+struct _GimpDrawToolClass
+{
+ GimpToolClass parent_class;
+
+ /* virtual function */
+
+ void (* draw) (GimpDrawTool *draw_tool);
+};
+
+
+GType gimp_draw_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_draw_tool_start (GimpDrawTool *draw_tool,
+ GimpDisplay *display);
+void gimp_draw_tool_stop (GimpDrawTool *draw_tool);
+
+gboolean gimp_draw_tool_is_active (GimpDrawTool *draw_tool);
+
+void gimp_draw_tool_pause (GimpDrawTool *draw_tool);
+void gimp_draw_tool_resume (GimpDrawTool *draw_tool);
+
+gdouble gimp_draw_tool_calc_distance (GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+gdouble gimp_draw_tool_calc_distance_square (GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+void gimp_draw_tool_set_widget (GimpDrawTool *draw_tool,
+ GimpToolWidget *widget);
+void gimp_draw_tool_set_default_status (GimpDrawTool *draw_tool,
+ const gchar *status);
+
+void gimp_draw_tool_add_preview (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item);
+void gimp_draw_tool_remove_preview (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item);
+
+void gimp_draw_tool_add_item (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item);
+void gimp_draw_tool_remove_item (GimpDrawTool *draw_tool,
+ GimpCanvasItem *item);
+
+GimpCanvasGroup* gimp_draw_tool_add_stroke_group (GimpDrawTool *draw_tool);
+GimpCanvasGroup* gimp_draw_tool_add_fill_group (GimpDrawTool *draw_tool);
+
+void gimp_draw_tool_push_group (GimpDrawTool *draw_tool,
+ GimpCanvasGroup *group);
+void gimp_draw_tool_pop_group (GimpDrawTool *draw_tool);
+
+GimpCanvasItem * gimp_draw_tool_add_line (GimpDrawTool *draw_tool,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+GimpCanvasItem * gimp_draw_tool_add_guide (GimpDrawTool *draw_tool,
+ GimpOrientationType orientation,
+ gint position,
+ GimpGuideStyle style);
+GimpCanvasItem * gimp_draw_tool_add_crosshair (GimpDrawTool *draw_tool,
+ gint position_x,
+ gint position_y);
+GimpCanvasItem * gimp_draw_tool_add_sample_point (GimpDrawTool *draw_tool,
+ gint x,
+ gint y,
+ gint index);
+GimpCanvasItem * gimp_draw_tool_add_rectangle (GimpDrawTool *draw_tool,
+ gboolean filled,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height);
+GimpCanvasItem * gimp_draw_tool_add_arc (GimpDrawTool *draw_tool,
+ gboolean filled,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gdouble start_angle,
+ gdouble slice_angle);
+GimpCanvasItem * gimp_draw_tool_add_transform_preview(GimpDrawTool *draw_tool,
+ GimpPickable *pickable,
+ const GimpMatrix3 *transform,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+
+GimpCanvasItem * gimp_draw_tool_add_handle (GimpDrawTool *draw_tool,
+ GimpHandleType type,
+ gdouble x,
+ gdouble y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor);
+
+GimpCanvasItem * gimp_draw_tool_add_lines (GimpDrawTool *draw_tool,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpMatrix3 *transform,
+ gboolean filled);
+
+GimpCanvasItem * gimp_draw_tool_add_strokes (GimpDrawTool *draw_tool,
+ const GimpCoords *points,
+ gint n_points,
+ GimpMatrix3 *transform,
+ gboolean filled);
+
+GimpCanvasItem * gimp_draw_tool_add_pen (GimpDrawTool *draw_tool,
+ const GimpVector2 *points,
+ gint n_points,
+ GimpContext *context,
+ GimpActiveColor color,
+ gint width);
+
+GimpCanvasItem * gimp_draw_tool_add_boundary (GimpDrawTool *draw_tool,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ GimpMatrix3 *transform,
+ gdouble offset_x,
+ gdouble offset_y);
+
+GimpCanvasItem * gimp_draw_tool_add_text_cursor (GimpDrawTool *draw_tool,
+ PangoRectangle *cursor,
+ gboolean overwrite,
+ GimpTextDirection direction);
+
+gboolean gimp_draw_tool_on_handle (GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ GimpHandleType type,
+ gdouble handle_x,
+ gdouble handle_y,
+ gint width,
+ gint height,
+ GimpHandleAnchor anchor);
+
+
+#endif /* __GIMP_DRAW_TOOL_H__ */
diff --git a/app/tools/gimpeditselectiontool.c b/app/tools/gimpeditselectiontool.c
new file mode 100644
index 0000000..8b261ef
--- /dev/null
+++ b/app/tools/gimpeditselectiontool.c
@@ -0,0 +1,1287 @@
+/* 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 <stdarg.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimp-utils.h"
+#include "core/gimpboundary.h"
+#include "core/gimpgrouplayer.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-item-list.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpitem-linked.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimpprojection.h"
+#include "core/gimpselection.h"
+#include "core/gimpundostack.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+#include "display/gimpdisplayshell-selection.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimpdrawtool.h"
+#include "gimpeditselectiontool.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+#include "tool_manager.h"
+
+#include "gimp-intl.h"
+
+
+#define ARROW_VELOCITY 25
+
+
+typedef struct _GimpEditSelectionTool GimpEditSelectionTool;
+typedef struct _GimpEditSelectionToolClass GimpEditSelectionToolClass;
+
+struct _GimpEditSelectionTool
+{
+ GimpDrawTool parent_instance;
+
+ gdouble start_x; /* Coords where button was pressed */
+ gdouble start_y;
+
+ gint last_x; /* Last x and y coords */
+ gint last_y;
+
+ gint current_x; /* Current x and y coords */
+ gint current_y;
+
+ gint cuml_x; /* Cumulative changes to x and y */
+ gint cuml_y;
+
+ gint sel_x; /* Bounding box of selection mask */
+ gint sel_y; /* Bounding box of selection mask */
+ gint sel_width;
+ gint sel_height;
+
+ gint num_segs_in; /* Num seg in selection boundary */
+ gint num_segs_out; /* Num seg in selection boundary */
+ GimpBoundSeg *segs_in; /* Pointer to the channel sel. segs */
+ GimpBoundSeg *segs_out; /* Pointer to the channel sel. segs */
+
+ gdouble center_x; /* Where to draw the mark of center */
+ gdouble center_y;
+
+ GimpTranslateMode edit_mode; /* Translate the mask or layer? */
+
+ GList *live_items; /* Items that are transformed live */
+ GList *delayed_items; /* Items that are transformed later */
+
+ gboolean first_move; /* Don't push undos after the first */
+
+ gboolean propagate_release;
+
+ gboolean constrain; /* Constrain the movement */
+
+ gdouble last_motion_x; /* Previous coords sent to _motion */
+ gdouble last_motion_y;
+};
+
+struct _GimpEditSelectionToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+static void gimp_edit_selection_tool_finalize (GObject *object);
+
+static void gimp_edit_selection_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_edit_selection_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_edit_selection_tool_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_edit_selection_tool_draw (GimpDrawTool *tool);
+
+static GimpItem * gimp_edit_selection_tool_get_active_item (GimpEditSelectionTool *edit_select,
+ GimpImage *image);
+static void gimp_edit_selection_tool_calc_coords (GimpEditSelectionTool *edit_select,
+ GimpImage *image,
+ gdouble x,
+ gdouble y);
+static void gimp_edit_selection_tool_start_undo_group (GimpEditSelectionTool *edit_select,
+ GimpImage *image);
+
+
+G_DEFINE_TYPE (GimpEditSelectionTool, gimp_edit_selection_tool,
+ GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_edit_selection_tool_parent_class
+
+
+static void
+gimp_edit_selection_tool_class_init (GimpEditSelectionToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_edit_selection_tool_finalize;
+
+ tool_class->button_release = gimp_edit_selection_tool_button_release;
+ tool_class->motion = gimp_edit_selection_tool_motion;
+ tool_class->active_modifier_key = gimp_edit_selection_tool_active_modifier_key;
+
+ draw_class->draw = gimp_edit_selection_tool_draw;
+}
+
+static void
+gimp_edit_selection_tool_init (GimpEditSelectionTool *edit_select)
+{
+ GimpTool *tool = GIMP_TOOL (edit_select);
+
+ edit_select->first_move = TRUE;
+
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+}
+
+static void
+gimp_edit_selection_tool_finalize (GObject *object)
+{
+ GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (object);
+
+ g_clear_pointer (&edit_select->segs_in, g_free);
+ edit_select->num_segs_in = 0;
+
+ g_clear_pointer (&edit_select->segs_out, g_free);
+ edit_select->num_segs_out = 0;
+
+ g_clear_pointer (&edit_select->live_items, g_list_free);
+ g_clear_pointer (&edit_select->delayed_items, g_list_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+void
+gimp_edit_selection_tool_start (GimpTool *parent_tool,
+ GimpDisplay *display,
+ const GimpCoords *coords,
+ GimpTranslateMode edit_mode,
+ gboolean propagate_release)
+{
+ GimpEditSelectionTool *edit_select;
+ GimpTool *tool;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpItem *active_item;
+ GList *list;
+ gint off_x, off_y;
+
+ edit_select = g_object_new (GIMP_TYPE_EDIT_SELECTION_TOOL,
+ "tool-info", parent_tool->tool_info,
+ NULL);
+
+ edit_select->propagate_release = propagate_release;
+
+ tool = GIMP_TOOL (edit_select);
+
+ shell = gimp_display_get_shell (display);
+ image = gimp_display_get_image (display);
+
+ /* Make a check to see if it should be a floating selection translation */
+ if ((edit_mode == GIMP_TRANSLATE_MODE_MASK_TO_LAYER ||
+ edit_mode == GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER) &&
+ gimp_image_get_floating_selection (image))
+ {
+ edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL;
+ }
+
+ if (edit_mode == GIMP_TRANSLATE_MODE_LAYER)
+ {
+ GimpLayer *layer = gimp_image_get_active_layer (image);
+
+ if (gimp_layer_is_floating_sel (layer))
+ edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL;
+ }
+
+ edit_select->edit_mode = edit_mode;
+
+ gimp_edit_selection_tool_start_undo_group (edit_select, image);
+
+ /* Remember starting point for use in constrained movement */
+ edit_select->start_x = coords->x;
+ edit_select->start_y = coords->y;
+
+ active_item = gimp_edit_selection_tool_get_active_item (edit_select, image);
+
+ gimp_item_get_offset (active_item, &off_x, &off_y);
+
+ /* Manually set the last coords to the starting point */
+ edit_select->last_x = coords->x - off_x;
+ edit_select->last_y = coords->y - off_y;
+
+ edit_select->constrain = FALSE;
+
+ /* Find the active item's selection bounds */
+ {
+ GimpChannel *channel;
+ const GimpBoundSeg *segs_in;
+ const GimpBoundSeg *segs_out;
+
+ if (GIMP_IS_CHANNEL (active_item))
+ channel = GIMP_CHANNEL (active_item);
+ else
+ channel = gimp_image_get_mask (image);
+
+ gimp_channel_boundary (channel,
+ &segs_in, &segs_out,
+ &edit_select->num_segs_in,
+ &edit_select->num_segs_out,
+ 0, 0, 0, 0);
+
+ edit_select->segs_in = g_memdup (segs_in,
+ edit_select->num_segs_in *
+ sizeof (GimpBoundSeg));
+
+ edit_select->segs_out = g_memdup (segs_out,
+ edit_select->num_segs_out *
+ sizeof (GimpBoundSeg));
+
+ if (edit_select->edit_mode == GIMP_TRANSLATE_MODE_VECTORS)
+ {
+ edit_select->sel_x = 0;
+ edit_select->sel_y = 0;
+ edit_select->sel_width = gimp_image_get_width (image);
+ edit_select->sel_height = gimp_image_get_height (image);
+ }
+ else
+ {
+ /* find the bounding box of the selection mask - this is used
+ * for the case of a GIMP_TRANSLATE_MODE_MASK_TO_LAYER, where
+ * the translation will result in floating the selection mask
+ * and translating the resulting layer
+ */
+ gimp_item_mask_intersect (active_item,
+ &edit_select->sel_x,
+ &edit_select->sel_y,
+ &edit_select->sel_width,
+ &edit_select->sel_height);
+ }
+ }
+
+ gimp_edit_selection_tool_calc_coords (edit_select, image,
+ coords->x, coords->y);
+
+ {
+ gint x, y, w, h;
+
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ case GIMP_TRANSLATE_MODE_MASK:
+ case GIMP_TRANSLATE_MODE_LAYER_MASK:
+ gimp_item_bounds (active_item, &x, &y, &w, &h);
+ x += off_x;
+ y += off_y;
+ break;
+
+ case GIMP_TRANSLATE_MODE_MASK_TO_LAYER:
+ case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER:
+ x = edit_select->sel_x + off_x;
+ y = edit_select->sel_y + off_y;
+ w = edit_select->sel_width;
+ h = edit_select->sel_height;
+ break;
+
+ case GIMP_TRANSLATE_MODE_LAYER:
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ if (gimp_item_get_linked (active_item))
+ {
+ GList *linked;
+
+ linked = gimp_image_item_list_get_list (image,
+ GIMP_IS_LAYER (active_item) ?
+ GIMP_ITEM_TYPE_LAYERS :
+ GIMP_ITEM_TYPE_VECTORS,
+ GIMP_ITEM_SET_LINKED);
+ linked = gimp_image_item_list_filter (linked);
+
+ gimp_image_item_list_bounds (image, linked, &x, &y, &w, &h);
+
+ g_list_free (linked);
+ }
+ else
+ {
+ gimp_item_bounds (active_item, &x, &y, &w, &h);
+ x += off_x;
+ y += off_y;
+ }
+ break;
+ }
+
+ gimp_tool_control_set_snap_offsets (tool->control,
+ x - coords->x,
+ y - coords->y,
+ w, h);
+
+ /* Save where to draw the mark of the center */
+ edit_select->center_x = x + w / 2.0;
+ edit_select->center_y = y + h / 2.0;
+ }
+
+ if (gimp_item_get_linked (active_item))
+ {
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ case GIMP_TRANSLATE_MODE_LAYER:
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ edit_select->live_items =
+ gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_LAYERS |
+ GIMP_ITEM_TYPE_VECTORS,
+ GIMP_ITEM_SET_LINKED);
+ edit_select->live_items =
+ gimp_image_item_list_filter (edit_select->live_items);
+
+ edit_select->delayed_items =
+ gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_CHANNELS,
+ GIMP_ITEM_SET_LINKED);
+ edit_select->delayed_items =
+ gimp_image_item_list_filter (edit_select->delayed_items);
+ break;
+
+ default:
+ /* other stuff can't be linked so don't bother */
+ break;
+ }
+ }
+ else
+ {
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ case GIMP_TRANSLATE_MODE_LAYER:
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ edit_select->live_items = g_list_append (NULL, active_item);
+ break;
+
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ case GIMP_TRANSLATE_MODE_LAYER_MASK:
+ case GIMP_TRANSLATE_MODE_MASK:
+ edit_select->delayed_items = g_list_append (NULL, active_item);
+ break;
+
+ default:
+ /* MASK_TO_LAYER and MASK_COPY_TO_LAYER create a live_item later */
+ break;
+ }
+ }
+
+ for (list = edit_select->live_items; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (item));
+
+ gimp_item_start_transform (item, TRUE);
+ }
+
+ tool_manager_push_tool (display->gimp, tool);
+
+ gimp_tool_control_activate (tool->control);
+ tool->display = display;
+
+ /* pause the current selection */
+ gimp_display_shell_selection_pause (shell);
+
+ /* initialize the statusbar display */
+ gimp_tool_push_status_coords (tool, display,
+ gimp_tool_control_get_precision (tool->control),
+ _("Move: "), 0, ", ", 0, NULL);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (edit_select), display);
+}
+
+
+static void
+gimp_edit_selection_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GList *list;
+
+ /* resume the current selection */
+ gimp_display_shell_selection_resume (shell);
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_control_halt (tool->control);
+
+ /* Stop and free the selection core */
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (edit_select));
+
+ tool_manager_pop_tool (display->gimp);
+
+ /* move the items -- whether there has been movement or not!
+ * (to ensure that there's something on the undo stack)
+ */
+ gimp_image_item_list_translate (image,
+ edit_select->delayed_items,
+ edit_select->cuml_x,
+ edit_select->cuml_y,
+ TRUE);
+
+ for (list = edit_select->live_items; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ gimp_item_end_transform (item, TRUE);
+
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (item));
+ }
+
+ gimp_image_undo_group_end (image);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* Operation cancelled - undo the undo-group! */
+ gimp_image_undo (image);
+ }
+
+ gimp_image_flush (image);
+
+ if (edit_select->propagate_release &&
+ tool_manager_get_active (display->gimp))
+ {
+ tool_manager_button_release_active (display->gimp,
+ coords, time, state,
+ display);
+ }
+
+ g_object_unref (edit_select);
+}
+
+static void
+gimp_edit_selection_tool_update_motion (GimpEditSelectionTool *edit_select,
+ gdouble new_x,
+ gdouble new_y,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (edit_select);
+ GimpTool *tool = GIMP_TOOL (edit_select);
+ GimpImage *image = gimp_display_get_image (display);
+ gint dx;
+ gint dy;
+
+ gdk_flush ();
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (edit_select->constrain)
+ {
+ gimp_constrain_line (edit_select->start_x, edit_select->start_y,
+ &new_x, &new_y,
+ GIMP_CONSTRAIN_LINE_45_DEGREES, 0.0, 1.0, 1.0);
+ }
+
+ gimp_edit_selection_tool_calc_coords (edit_select, image,
+ new_x, new_y);
+
+ dx = edit_select->current_x - edit_select->last_x;
+ dy = edit_select->current_y - edit_select->last_y;
+
+ /* if there has been movement, move */
+ if (dx != 0 || dy != 0)
+ {
+ GimpItem *active_item;
+ GError *error = NULL;
+
+ active_item = gimp_edit_selection_tool_get_active_item (edit_select,
+ image);
+
+ edit_select->cuml_x += dx;
+ edit_select->cuml_y += dy;
+
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_LAYER_MASK:
+ case GIMP_TRANSLATE_MODE_MASK:
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ edit_select->last_x = edit_select->current_x;
+ edit_select->last_y = edit_select->current_y;
+
+ /* fallthru */
+
+ case GIMP_TRANSLATE_MODE_LAYER:
+ gimp_image_item_list_translate (image,
+ edit_select->live_items,
+ dx, dy,
+ edit_select->first_move);
+ break;
+
+ case GIMP_TRANSLATE_MODE_MASK_TO_LAYER:
+ case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER:
+ if (! gimp_selection_float (GIMP_SELECTION (gimp_image_get_mask (image)),
+ GIMP_DRAWABLE (active_item),
+ gimp_get_user_context (display->gimp),
+ edit_select->edit_mode ==
+ GIMP_TRANSLATE_MODE_MASK_TO_LAYER,
+ 0, 0, &error))
+ {
+ /* no region to float, abort safely */
+ gimp_message_literal (display->gimp, G_OBJECT (display),
+ GIMP_MESSAGE_WARNING,
+ error->message);
+ g_clear_error (&error);
+ gimp_draw_tool_resume (draw_tool);
+
+ return;
+ }
+
+ edit_select->last_x -= edit_select->sel_x;
+ edit_select->last_y -= edit_select->sel_y;
+ edit_select->sel_x = 0;
+ edit_select->sel_y = 0;
+
+ edit_select->edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL;
+
+ active_item = gimp_edit_selection_tool_get_active_item (edit_select,
+ image);
+
+ edit_select->live_items = g_list_prepend (NULL, active_item);
+
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (active_item));
+
+ gimp_item_start_transform (active_item, TRUE);
+
+ /* fallthru */
+
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ gimp_image_item_list_translate (image,
+ edit_select->live_items,
+ dx, dy,
+ edit_select->first_move);
+ break;
+ }
+
+ edit_select->first_move = FALSE;
+ }
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+
+ gimp_tool_pop_status (tool, display);
+ gimp_tool_push_status_coords (tool, display,
+ gimp_tool_control_get_precision (tool->control),
+ _("Move: "),
+ edit_select->cuml_x,
+ ", ",
+ edit_select->cuml_y,
+ NULL);
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+
+static void
+gimp_edit_selection_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (tool);
+
+ edit_select->last_motion_x = coords->x;
+ edit_select->last_motion_y = coords->y;
+
+ gimp_edit_selection_tool_update_motion (edit_select,
+ coords->x, coords->y,
+ display);
+}
+
+static void
+gimp_edit_selection_tool_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (tool);
+
+ edit_select->constrain = (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_edit_selection_tool_update_motion (edit_select,
+ edit_select->last_motion_x,
+ edit_select->last_motion_y,
+ display);
+ }
+}
+
+static void
+gimp_edit_selection_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (draw_tool);
+ GimpDisplay *display = GIMP_TOOL (draw_tool)->display;
+ GimpImage *image = gimp_display_get_image (display);
+ GimpItem *active_item;
+ gint off_x;
+ gint off_y;
+
+ active_item = gimp_edit_selection_tool_get_active_item (edit_select, image);
+
+ gimp_item_get_offset (active_item, &off_x, &off_y);
+
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ case GIMP_TRANSLATE_MODE_LAYER_MASK:
+ case GIMP_TRANSLATE_MODE_MASK:
+ {
+ gboolean floating_sel = FALSE;
+
+ if (edit_select->edit_mode == GIMP_TRANSLATE_MODE_MASK)
+ {
+ GimpLayer *layer = gimp_image_get_active_layer (image);
+
+ if (layer)
+ floating_sel = gimp_layer_is_floating_sel (layer);
+ }
+
+ if (! floating_sel && edit_select->segs_in)
+ {
+ gimp_draw_tool_add_boundary (draw_tool,
+ edit_select->segs_in,
+ edit_select->num_segs_in,
+ NULL,
+ edit_select->cuml_x + off_x,
+ edit_select->cuml_y + off_y);
+ }
+
+ if (edit_select->segs_out)
+ {
+ gimp_draw_tool_add_boundary (draw_tool,
+ edit_select->segs_out,
+ edit_select->num_segs_out,
+ NULL,
+ edit_select->cuml_x + off_x,
+ edit_select->cuml_y + off_y);
+ }
+ else if (edit_select->edit_mode != GIMP_TRANSLATE_MODE_MASK)
+ {
+ gimp_draw_tool_add_rectangle (draw_tool,
+ FALSE,
+ edit_select->cuml_x + off_x,
+ edit_select->cuml_y + off_y,
+ gimp_item_get_width (active_item),
+ gimp_item_get_height (active_item));
+ }
+ }
+ break;
+
+ case GIMP_TRANSLATE_MODE_MASK_TO_LAYER:
+ case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER:
+ gimp_draw_tool_add_rectangle (draw_tool,
+ FALSE,
+ edit_select->sel_x + off_x,
+ edit_select->sel_y + off_y,
+ edit_select->sel_width,
+ edit_select->sel_height);
+ break;
+
+ case GIMP_TRANSLATE_MODE_LAYER:
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ {
+ gint x, y, w, h;
+
+ if (gimp_item_get_linked (active_item))
+ {
+ GList *linked;
+
+ linked = gimp_image_item_list_get_list (image,
+ GIMP_IS_LAYER (active_item) ?
+ GIMP_ITEM_TYPE_LAYERS :
+ GIMP_ITEM_TYPE_VECTORS,
+ GIMP_ITEM_SET_LINKED);
+ linked = gimp_image_item_list_filter (linked);
+
+ gimp_image_item_list_bounds (image, linked, &x, &y, &w, &h);
+
+ g_list_free (linked);
+ }
+ else
+ {
+ gimp_item_bounds (active_item, &x, &y, &w, &h);
+ x += off_x;
+ y += off_y;
+ }
+
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ x, y, w, h);
+ }
+ break;
+
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ if (edit_select->segs_in)
+ {
+ gimp_draw_tool_add_boundary (draw_tool,
+ edit_select->segs_in,
+ edit_select->num_segs_in,
+ NULL,
+ edit_select->cuml_x,
+ edit_select->cuml_y);
+ }
+ break;
+ }
+
+ /* Mark the center because we snap to it */
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CROSS,
+ edit_select->center_x + edit_select->cuml_x,
+ edit_select->center_y + edit_select->cuml_y,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_TOOL_HANDLE_SIZE_SMALL,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+}
+
+static GimpItem *
+gimp_edit_selection_tool_get_active_item (GimpEditSelectionTool *edit_select,
+ GimpImage *image)
+{
+ GimpItem *active_item;
+
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ active_item = GIMP_ITEM (gimp_image_get_active_vectors (image));
+ break;
+
+ case GIMP_TRANSLATE_MODE_LAYER:
+ active_item = GIMP_ITEM (gimp_image_get_active_layer (image));
+ break;
+
+ case GIMP_TRANSLATE_MODE_MASK:
+ active_item = GIMP_ITEM (gimp_image_get_mask (image));
+ break;
+
+ default:
+ active_item = GIMP_ITEM (gimp_image_get_active_drawable (image));
+ break;
+ }
+
+ return active_item;
+}
+
+static void
+gimp_edit_selection_tool_calc_coords (GimpEditSelectionTool *edit_select,
+ GimpImage *image,
+ gdouble x,
+ gdouble y)
+{
+ GimpItem *active_item;
+ gint off_x, off_y;
+ gdouble x1, y1;
+ gdouble dx, dy;
+
+ active_item = gimp_edit_selection_tool_get_active_item (edit_select, image);
+
+ gimp_item_get_offset (active_item, &off_x, &off_y);
+
+ dx = (x - off_x) - edit_select->last_x;
+ dy = (y - off_y) - edit_select->last_y;
+
+ x1 = edit_select->sel_x + dx;
+ y1 = edit_select->sel_y + dy;
+
+ edit_select->current_x = ((gint) floor (x1) -
+ (edit_select->sel_x - edit_select->last_x));
+ edit_select->current_y = ((gint) floor (y1) -
+ (edit_select->sel_y - edit_select->last_y));
+}
+
+static void
+gimp_edit_selection_tool_start_undo_group (GimpEditSelectionTool *edit_select,
+ GimpImage *image)
+{
+ GimpItem *active_item;
+ const gchar *undo_desc = NULL;
+
+ active_item = gimp_edit_selection_tool_get_active_item (edit_select, image);
+
+ switch (edit_select->edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ case GIMP_TRANSLATE_MODE_LAYER_MASK:
+ case GIMP_TRANSLATE_MODE_MASK:
+ case GIMP_TRANSLATE_MODE_LAYER:
+ undo_desc = GIMP_ITEM_GET_CLASS (active_item)->translate_desc;
+ break;
+
+ case GIMP_TRANSLATE_MODE_MASK_TO_LAYER:
+ case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER:
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ undo_desc = _("Move Floating Selection");
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ gimp_image_undo_group_start (image,
+ edit_select->edit_mode ==
+ GIMP_TRANSLATE_MODE_MASK ?
+ GIMP_UNDO_GROUP_MASK :
+ GIMP_UNDO_GROUP_ITEM_DISPLACE,
+ undo_desc);
+}
+
+static gint
+process_event_queue_keys (GdkEventKey *kevent,
+ ... /* GdkKeyType, GdkModifierType, value ... 0 */)
+{
+
+#define FILTER_MAX_KEYS 50
+
+ va_list argp;
+ GdkEvent *event;
+ GList *event_list = NULL;
+ GList *list;
+ guint keys[FILTER_MAX_KEYS];
+ GdkModifierType modifiers[FILTER_MAX_KEYS];
+ gint values[FILTER_MAX_KEYS];
+ gint i = 0;
+ gint n_keys = 0;
+ gint value = 0;
+ gboolean done = FALSE;
+ GtkWidget *orig_widget;
+
+ va_start (argp, kevent);
+
+ while (n_keys < FILTER_MAX_KEYS &&
+ (keys[n_keys] = va_arg (argp, guint)) != 0)
+ {
+ modifiers[n_keys] = va_arg (argp, GdkModifierType);
+ values[n_keys] = va_arg (argp, gint);
+ n_keys++;
+ }
+
+ va_end (argp);
+
+ for (i = 0; i < n_keys; i++)
+ if (kevent->keyval == keys[i] &&
+ (kevent->state & modifiers[i]) == modifiers[i])
+ value += values[i];
+
+ orig_widget = gtk_get_event_widget ((GdkEvent *) kevent);
+
+ while (gdk_events_pending () > 0 && ! done)
+ {
+ gboolean discard_event = FALSE;
+
+ event = gdk_event_get ();
+
+ if (! event || orig_widget != gtk_get_event_widget (event))
+ {
+ done = TRUE;
+ }
+ else
+ {
+ if (event->any.type == GDK_KEY_PRESS)
+ {
+ for (i = 0; i < n_keys; i++)
+ if (event->key.keyval == keys[i] &&
+ (event->key.state & modifiers[i]) == modifiers[i])
+ {
+ discard_event = TRUE;
+ value += values[i];
+ }
+
+ if (! discard_event)
+ done = TRUE;
+ }
+ /* should there be more types here? */
+ else if (event->any.type != GDK_KEY_RELEASE &&
+ event->any.type != GDK_MOTION_NOTIFY &&
+ event->any.type != GDK_EXPOSE)
+ done = FALSE;
+ }
+
+ if (! event)
+ ; /* Do nothing */
+ else if (! discard_event)
+ event_list = g_list_prepend (event_list, event);
+ else
+ gdk_event_free (event);
+ }
+
+ event_list = g_list_reverse (event_list);
+
+ /* unget the unused events and free the list */
+ for (list = event_list; list; list = g_list_next (list))
+ {
+ gdk_event_put ((GdkEvent *) list->data);
+ gdk_event_free ((GdkEvent *) list->data);
+ }
+
+ g_list_free (event_list);
+
+ return value;
+
+#undef FILTER_MAX_KEYS
+}
+
+gboolean
+gimp_edit_selection_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpTransformType translate_type;
+
+ if (kevent->state & GDK_MOD1_MASK)
+ {
+ translate_type = GIMP_TRANSFORM_TYPE_SELECTION;
+ }
+ else if (kevent->state & gimp_get_toggle_behavior_mask ())
+ {
+ translate_type = GIMP_TRANSFORM_TYPE_PATH;
+ }
+ else
+ {
+ translate_type = GIMP_TRANSFORM_TYPE_LAYER;
+ }
+
+ return gimp_edit_selection_tool_translate (tool, kevent, translate_type,
+ display, NULL);
+}
+
+gboolean
+gimp_edit_selection_tool_translate (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpTransformType translate_type,
+ GimpDisplay *display,
+ GtkWidget *type_box)
+{
+ gint inc_x = 0;
+ gint inc_y = 0;
+ GimpUndo *undo;
+ gboolean push_undo = TRUE;
+ GimpImage *image = gimp_display_get_image (display);
+ GimpItem *item = NULL;
+ GimpTranslateMode edit_mode = GIMP_TRANSLATE_MODE_MASK;
+ GimpUndoType undo_type = GIMP_UNDO_GROUP_MASK;
+ const gchar *undo_desc = NULL;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ const gchar *null_message = NULL;
+ const gchar *locked_message = NULL;
+ gint velocity;
+
+ /* bail out early if it is not an arrow key event */
+
+ if (kevent->keyval != GDK_KEY_Left &&
+ kevent->keyval != GDK_KEY_Right &&
+ kevent->keyval != GDK_KEY_Up &&
+ kevent->keyval != GDK_KEY_Down)
+ return FALSE;
+
+ /* adapt arrow velocity to the zoom factor when holding <shift> */
+ velocity = (ARROW_VELOCITY /
+ gimp_zoom_model_get_factor (gimp_display_get_shell (display)->zoom));
+ velocity = MAX (1.0, velocity);
+
+ /* check the event queue for key events with the same modifier mask
+ * as the current event, allowing only extend_mask to vary between
+ * them.
+ */
+ inc_x = process_event_queue_keys (kevent,
+ GDK_KEY_Left,
+ kevent->state | extend_mask,
+ -1 * velocity,
+
+ GDK_KEY_Left,
+ kevent->state & ~extend_mask,
+ -1,
+
+ GDK_KEY_Right,
+ kevent->state | extend_mask,
+ 1 * velocity,
+
+ GDK_KEY_Right,
+ kevent->state & ~extend_mask,
+ 1,
+
+ 0);
+
+ inc_y = process_event_queue_keys (kevent,
+ GDK_KEY_Up,
+ kevent->state | extend_mask,
+ -1 * velocity,
+
+ GDK_KEY_Up,
+ kevent->state & ~extend_mask,
+ -1,
+
+ GDK_KEY_Down,
+ kevent->state | extend_mask,
+ 1 * velocity,
+
+ GDK_KEY_Down,
+ kevent->state & ~extend_mask,
+ 1,
+
+ 0);
+
+ switch (translate_type)
+ {
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ item = GIMP_ITEM (gimp_image_get_mask (image));
+
+ if (gimp_channel_is_empty (GIMP_CHANNEL (item)))
+ item = NULL;
+
+ edit_mode = GIMP_TRANSLATE_MODE_MASK;
+ undo_type = GIMP_UNDO_GROUP_MASK;
+
+ if (! item)
+ {
+ /* cannot happen, don't translate this message */
+ null_message = "There is no selection to move.";
+ }
+ else if (gimp_item_is_position_locked (item))
+ {
+ /* cannot happen, don't translate this message */
+ locked_message = "The selection's position is locked.";
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_PATH:
+ item = GIMP_ITEM (gimp_image_get_active_vectors (image));
+
+ edit_mode = GIMP_TRANSLATE_MODE_VECTORS;
+ undo_type = GIMP_UNDO_GROUP_ITEM_DISPLACE;
+
+ if (! item)
+ {
+ null_message = _("There is no path to move.");
+ }
+ else if (gimp_item_is_position_locked (item))
+ {
+ locked_message = _("The active path's position is locked.");
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ item = GIMP_ITEM (gimp_image_get_active_drawable (image));
+
+ undo_type = GIMP_UNDO_GROUP_ITEM_DISPLACE;
+
+ if (! item)
+ {
+ null_message = _("There is no layer to move.");
+ }
+ else if (GIMP_IS_LAYER_MASK (item))
+ {
+ edit_mode = GIMP_TRANSLATE_MODE_LAYER_MASK;
+
+ if (gimp_item_is_position_locked (item))
+ {
+ locked_message = _("The active layer's position is locked.");
+ }
+ else if (gimp_item_is_content_locked (item))
+ {
+ locked_message = _("The active layer's pixels are locked.");
+ }
+ }
+ else if (GIMP_IS_CHANNEL (item))
+ {
+ edit_mode = GIMP_TRANSLATE_MODE_CHANNEL;
+
+ if (gimp_item_is_position_locked (item))
+ {
+ locked_message = _("The active channel's position is locked.");
+ }
+ else if (gimp_item_is_content_locked (item))
+ {
+ locked_message = _("The active channel's pixels are locked.");
+ }
+ }
+ else if (gimp_layer_is_floating_sel (GIMP_LAYER (item)))
+ {
+ edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL;
+
+ if (gimp_item_is_position_locked (item))
+ {
+ locked_message = _("The active layer's position is locked.");
+ }
+ }
+ else
+ {
+ edit_mode = GIMP_TRANSLATE_MODE_LAYER;
+
+ if (gimp_item_is_position_locked (item))
+ {
+ locked_message = _("The active layer's position is locked.");
+ }
+ }
+
+ break;
+
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ g_return_val_if_reached (FALSE);
+ }
+
+ if (! item)
+ {
+ gimp_tool_message_literal (tool, display, null_message);
+ if (type_box)
+ gimp_widget_blink (type_box);
+ return TRUE;
+ }
+ else if (locked_message)
+ {
+ gimp_tool_message_literal (tool, display, locked_message);
+ gimp_tools_blink_lock_box (display->gimp, item);
+ return TRUE;
+ }
+
+ if (inc_x == 0 && inc_y == 0)
+ return TRUE;
+
+ switch (edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ undo_desc = _("Move Floating Selection");
+ break;
+
+ default:
+ undo_desc = GIMP_ITEM_GET_CLASS (item)->translate_desc;
+ break;
+ }
+
+ /* compress undo */
+ undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK, undo_type);
+
+ if (undo &&
+ g_object_get_data (G_OBJECT (undo),
+ "edit-selection-tool") == (gpointer) tool &&
+ g_object_get_data (G_OBJECT (undo),
+ "edit-selection-item") == (gpointer) item &&
+ g_object_get_data (G_OBJECT (undo),
+ "edit-selection-type") == GINT_TO_POINTER (edit_mode))
+ {
+ push_undo = FALSE;
+ }
+
+ if (push_undo)
+ {
+ if (gimp_image_undo_group_start (image, undo_type, undo_desc))
+ {
+ undo = gimp_image_undo_can_compress (image,
+ GIMP_TYPE_UNDO_STACK,
+ undo_type);
+
+ if (undo)
+ {
+ g_object_set_data (G_OBJECT (undo), "edit-selection-tool",
+ tool);
+ g_object_set_data (G_OBJECT (undo), "edit-selection-item",
+ item);
+ g_object_set_data (G_OBJECT (undo), "edit-selection-type",
+ GINT_TO_POINTER (edit_mode));
+ }
+ }
+ }
+
+ switch (edit_mode)
+ {
+ case GIMP_TRANSLATE_MODE_LAYER_MASK:
+ case GIMP_TRANSLATE_MODE_MASK:
+ gimp_item_translate (item, inc_x, inc_y, push_undo);
+ break;
+
+ case GIMP_TRANSLATE_MODE_MASK_TO_LAYER:
+ case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER:
+ /* this won't happen */
+ break;
+
+ case GIMP_TRANSLATE_MODE_VECTORS:
+ case GIMP_TRANSLATE_MODE_CHANNEL:
+ case GIMP_TRANSLATE_MODE_LAYER:
+ if (gimp_item_get_linked (item))
+ {
+ gimp_item_linked_translate (item, inc_x, inc_y, push_undo);
+ }
+ else
+ {
+ gimp_item_translate (item, inc_x, inc_y, push_undo);
+ }
+ break;
+
+ case GIMP_TRANSLATE_MODE_FLOATING_SEL:
+ gimp_item_translate (item, inc_x, inc_y, push_undo);
+ break;
+ }
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+ else
+ gimp_undo_refresh_preview (undo,
+ gimp_get_user_context (display->gimp));
+
+ gimp_image_flush (image);
+
+ return TRUE;
+}
diff --git a/app/tools/gimpeditselectiontool.h b/app/tools/gimpeditselectiontool.h
new file mode 100644
index 0000000..b34055c
--- /dev/null
+++ b/app/tools/gimpeditselectiontool.h
@@ -0,0 +1,50 @@
+/* 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_EDIT_SELECTION_TOOL_H__
+#define __GIMP_EDIT_SELECTION_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_EDIT_SELECTION_TOOL (gimp_edit_selection_tool_get_type ())
+#define GIMP_EDIT_SELECTION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_EDIT_SELECTION_TOOL, GimpEditSelectionTool))
+#define GIMP_EDIT_SELECTION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_EDIT_SELECTION_TOOL, GimpEditSelectionToolClass))
+#define GIMP_IS_EDIT_SELECTION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_EDIT_SELECTION_TOOL))
+#define GIMP_IS_EDIT_SELECTION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_EDIT_SELECTION_TOOL))
+
+
+GType gimp_edit_selection_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_edit_selection_tool_start (GimpTool *parent_tool,
+ GimpDisplay *display,
+ const GimpCoords *coords,
+ GimpTranslateMode edit_mode,
+ gboolean propagate_release);
+
+gboolean gimp_edit_selection_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+gboolean gimp_edit_selection_tool_translate (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpTransformType translate_type,
+ GimpDisplay *display,
+ GtkWidget *type_box);
+
+
+#endif /* __GIMP_EDIT_SELECTION_TOOL_H__ */
diff --git a/app/tools/gimpellipseselecttool.c b/app/tools/gimpellipseselecttool.c
new file mode 100644
index 0000000..5481d57
--- /dev/null
+++ b/app/tools/gimpellipseselecttool.c
@@ -0,0 +1,112 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpchannel-select.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpellipseselecttool.h"
+#include "gimprectangleselectoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_ellipse_select_tool_select (GimpRectangleSelectTool *rect_tool,
+ GimpChannelOps operation,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+
+G_DEFINE_TYPE (GimpEllipseSelectTool, gimp_ellipse_select_tool,
+ GIMP_TYPE_RECTANGLE_SELECT_TOOL)
+
+#define parent_class gimp_ellipse_select_tool_parent_class
+
+
+void
+gimp_ellipse_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_ELLIPSE_SELECT_TOOL,
+ GIMP_TYPE_RECTANGLE_SELECT_OPTIONS,
+ gimp_rectangle_select_options_gui,
+ 0,
+ "gimp-ellipse-select-tool",
+ _("Ellipse Select"),
+ _("Ellipse Select Tool: Select an elliptical region"),
+ N_("_Ellipse Select"), "E",
+ NULL, GIMP_HELP_TOOL_ELLIPSE_SELECT,
+ GIMP_ICON_TOOL_ELLIPSE_SELECT,
+ data);
+}
+
+static void
+gimp_ellipse_select_tool_class_init (GimpEllipseSelectToolClass *klass)
+{
+ GimpRectangleSelectToolClass *rect_tool_class;
+
+ rect_tool_class = GIMP_RECTANGLE_SELECT_TOOL_CLASS (klass);
+
+ rect_tool_class->select = gimp_ellipse_select_tool_select;
+ rect_tool_class->draw_ellipse = TRUE;
+}
+
+static void
+gimp_ellipse_select_tool_init (GimpEllipseSelectTool *ellipse_select)
+{
+ GimpTool *tool = GIMP_TOOL (ellipse_select);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_ELLIPSE_SELECT);
+}
+
+static void
+gimp_ellipse_select_tool_select (GimpRectangleSelectTool *rect_tool,
+ GimpChannelOps operation,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (rect_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_channel_select_ellipse (gimp_image_get_mask (image),
+ x, y, w, h,
+ operation,
+ options->antialias,
+ options->feather,
+ options->feather_radius,
+ options->feather_radius,
+ TRUE);
+}
diff --git a/app/tools/gimpellipseselecttool.h b/app/tools/gimpellipseselecttool.h
new file mode 100644
index 0000000..b3018c8
--- /dev/null
+++ b/app/tools/gimpellipseselecttool.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 __GIMP_ELLIPSE_SELECT_TOOL_H__
+#define __GIMP_ELLIPSE_SELECT_TOOL_H__
+
+
+#include "gimprectangleselecttool.h"
+
+
+#define GIMP_TYPE_ELLIPSE_SELECT_TOOL (gimp_ellipse_select_tool_get_type ())
+#define GIMP_ELLIPSE_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ELLIPSE_SELECT_TOOL, GimpEllipseSelectTool))
+#define GIMP_ELLIPSE_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ELLIPSE_SELECT_TOOL, GimpEllipseSelectToolClass))
+#define GIMP_IS_ELLIPSE_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ELLIPSE_SELECT_TOOL))
+#define GIMP_IS_ELLIPSE_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ELLIPSE_SELECT_TOOL))
+#define GIMP_ELLIPSE_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ELLIPSE_SELECT_TOOL, GimpEllipseSelectToolClass))
+
+
+typedef struct _GimpEllipseSelectTool GimpEllipseSelectTool;
+typedef struct _GimpEllipseSelectToolClass GimpEllipseSelectToolClass;
+
+struct _GimpEllipseSelectTool
+{
+ GimpRectangleSelectTool parent_instance;
+};
+
+struct _GimpEllipseSelectToolClass
+{
+ GimpRectangleSelectToolClass parent_class;
+};
+
+
+void gimp_ellipse_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_ellipse_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ELLIPSE_SELECT_TOOL_H__ */
diff --git a/app/tools/gimperasertool.c b/app/tools/gimperasertool.c
new file mode 100644
index 0000000..d6e2ac9
--- /dev/null
+++ b/app/tools/gimperasertool.c
@@ -0,0 +1,176 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpdrawable.h"
+
+#include "paint/gimperaseroptions.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimperasertool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_eraser_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_eraser_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static gboolean gimp_eraser_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable);
+
+static GtkWidget * gimp_eraser_options_gui (GimpToolOptions *tool_options);
+
+
+G_DEFINE_TYPE (GimpEraserTool, gimp_eraser_tool, GIMP_TYPE_BRUSH_TOOL)
+
+#define parent_class gimp_eraser_tool_parent_class
+
+
+void
+gimp_eraser_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_ERASER_TOOL,
+ GIMP_TYPE_ERASER_OPTIONS,
+ gimp_eraser_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK,
+ "gimp-eraser-tool",
+ _("Eraser"),
+ _("Eraser Tool: Erase to background or transparency using a brush"),
+ N_("_Eraser"), "<shift>E",
+ NULL, GIMP_HELP_TOOL_ERASER,
+ GIMP_ICON_TOOL_ERASER,
+ data);
+}
+
+static void
+gimp_eraser_tool_class_init (GimpEraserToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ tool_class->modifier_key = gimp_eraser_tool_modifier_key;
+ tool_class->cursor_update = gimp_eraser_tool_cursor_update;
+
+ paint_tool_class->is_alpha_only = gimp_eraser_tool_is_alpha_only;
+}
+
+static void
+gimp_eraser_tool_init (GimpEraserTool *eraser)
+{
+ GimpTool *tool = GIMP_TOOL (eraser);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (eraser);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_ERASER);
+ gimp_tool_control_set_toggle_cursor_modifier (tool->control,
+ GIMP_CURSOR_MODIFIER_MINUS);
+
+ gimp_paint_tool_enable_color_picker (paint_tool,
+ GIMP_COLOR_PICK_TARGET_BACKGROUND);
+
+ paint_tool->status = _("Click to erase");
+ paint_tool->status_line = _("Click to erase the line");
+ paint_tool->status_ctrl = _("%s to pick a background color");
+}
+
+static void
+gimp_eraser_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ if (key == GDK_MOD1_MASK)
+ {
+ GimpEraserOptions *options = GIMP_ERASER_TOOL_GET_OPTIONS (tool);
+
+ g_object_set (options,
+ "anti-erase", ! options->anti_erase,
+ NULL);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state, display);
+}
+
+static void
+gimp_eraser_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpEraserOptions *options = GIMP_ERASER_TOOL_GET_OPTIONS (tool);
+
+ gimp_tool_control_set_toggled (tool->control, options->anti_erase);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static gboolean
+gimp_eraser_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable)
+{
+ GimpEraserOptions *options = GIMP_ERASER_TOOL_GET_OPTIONS (paint_tool);
+
+ if (! options->anti_erase)
+ return gimp_drawable_has_alpha (drawable);
+ else
+ return TRUE;
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_eraser_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *button;
+ gchar *str;
+
+ /* the anti_erase toggle */
+ str = g_strdup_printf (_("Anti erase (%s)"),
+ gimp_get_mod_string (GDK_MOD1_MASK));
+
+ button = gimp_prop_check_button_new (config, "anti-erase", str);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_free (str);
+
+ return vbox;
+}
diff --git a/app/tools/gimperasertool.h b/app/tools/gimperasertool.h
new file mode 100644
index 0000000..706d43c
--- /dev/null
+++ b/app/tools/gimperasertool.h
@@ -0,0 +1,55 @@
+/* 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_ERASER_TOOL_H__
+#define __GIMP_ERASER_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+
+
+#define GIMP_TYPE_ERASER_TOOL (gimp_eraser_tool_get_type ())
+#define GIMP_ERASER_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ERASER_TOOL, GimpEraserTool))
+#define GIMP_ERASER_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ERASER_TOOL, GimpEraserToolClass))
+#define GIMP_IS_ERASER_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ERASER_TOOL))
+#define GIMP_IS_ERASER_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ERASER_TOOL))
+#define GIMP_ERASER_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ERASER_TOOL, GimpEraserToolClass))
+
+#define GIMP_ERASER_TOOL_GET_OPTIONS(t) (GIMP_ERASER_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpEraserTool GimpEraserTool;
+typedef struct _GimpEraserToolClass GimpEraserToolClass;
+
+struct _GimpEraserTool
+{
+ GimpBrushTool parent_instance;
+};
+
+struct _GimpEraserToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+void gimp_eraser_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_eraser_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ERASER_TOOL_H__ */
diff --git a/app/tools/gimpfilteroptions.c b/app/tools/gimpfilteroptions.c
new file mode 100644
index 0000000..9bded52
--- /dev/null
+++ b/app/tools/gimpfilteroptions.c
@@ -0,0 +1,269 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "libgimpconfig/gimpconfig.h"
+
+#include "tools-types.h"
+
+#include "gimpfilteroptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PREVIEW,
+ PROP_PREVIEW_SPLIT,
+ PROP_PREVIEW_SPLIT_ALIGNMENT,
+ PROP_PREVIEW_SPLIT_POSITION,
+ PROP_CONTROLLER,
+ PROP_BLENDING_OPTIONS_EXPANDED,
+ PROP_COLOR_OPTIONS_EXPANDED
+};
+
+
+static void gimp_filter_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_filter_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpFilterOptions, gimp_filter_options,
+ GIMP_TYPE_COLOR_OPTIONS)
+
+#define parent_class gimp_filter_options_parent_class
+
+
+static void
+gimp_filter_options_class_init (GimpFilterOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_filter_options_set_property;
+ object_class->get_property = gimp_filter_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_PREVIEW,
+ "preview",
+ _("_Preview"),
+ NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_PREVIEW_SPLIT,
+ g_param_spec_boolean ("preview-split",
+ _("Split _view"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PREVIEW_SPLIT_ALIGNMENT,
+ g_param_spec_enum ("preview-split-alignment",
+ NULL, NULL,
+ GIMP_TYPE_ALIGNMENT_TYPE,
+ GIMP_ALIGN_LEFT,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PREVIEW_SPLIT_POSITION,
+ g_param_spec_int ("preview-split-position",
+ NULL, NULL,
+ G_MININT, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONTROLLER,
+ "controller",
+ _("On-canvas con_trols"),
+ _("Show on-canvas filter controls"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BLENDING_OPTIONS_EXPANDED,
+ "blending-options-expanded",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_COLOR_OPTIONS_EXPANDED,
+ "color-options-expanded",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_filter_options_init (GimpFilterOptions *options)
+{
+}
+
+static void
+gimp_filter_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFilterOptions *options = GIMP_FILTER_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_PREVIEW:
+ options->preview = g_value_get_boolean (value);
+ break;
+
+ case PROP_PREVIEW_SPLIT:
+ options->preview_split = g_value_get_boolean (value);
+ break;
+
+ case PROP_PREVIEW_SPLIT_ALIGNMENT:
+ options->preview_split_alignment = g_value_get_enum (value);
+ break;
+
+ case PROP_PREVIEW_SPLIT_POSITION:
+ options->preview_split_position = g_value_get_int (value);
+ break;
+
+ case PROP_CONTROLLER:
+ options->controller = g_value_get_boolean (value);
+ break;
+
+ case PROP_BLENDING_OPTIONS_EXPANDED:
+ options->blending_options_expanded = g_value_get_boolean (value);
+ break;
+
+ case PROP_COLOR_OPTIONS_EXPANDED:
+ options->color_options_expanded = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_filter_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFilterOptions *options = GIMP_FILTER_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_PREVIEW:
+ g_value_set_boolean (value, options->preview);
+ break;
+
+ case PROP_PREVIEW_SPLIT:
+ g_value_set_boolean (value, options->preview_split);
+ break;
+
+ case PROP_PREVIEW_SPLIT_ALIGNMENT:
+ g_value_set_enum (value, options->preview_split_alignment);
+ break;
+
+ case PROP_PREVIEW_SPLIT_POSITION:
+ g_value_set_int (value, options->preview_split_position);
+ break;
+
+ case PROP_CONTROLLER:
+ g_value_set_boolean (value, options->controller);
+ break;
+
+ case PROP_BLENDING_OPTIONS_EXPANDED:
+ g_value_set_boolean (value, options->blending_options_expanded);
+ break;
+
+ case PROP_COLOR_OPTIONS_EXPANDED:
+ g_value_set_boolean (value, options->color_options_expanded);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_filter_options_switch_preview_side (GimpFilterOptions *options)
+{
+ GimpAlignmentType alignment;
+
+ g_return_if_fail (GIMP_IS_FILTER_OPTIONS (options));
+
+ switch (options->preview_split_alignment)
+ {
+ case GIMP_ALIGN_LEFT: alignment = GIMP_ALIGN_RIGHT; break;
+ case GIMP_ALIGN_RIGHT: alignment = GIMP_ALIGN_LEFT; break;
+ case GIMP_ALIGN_TOP: alignment = GIMP_ALIGN_BOTTOM; break;
+ case GIMP_ALIGN_BOTTOM: alignment = GIMP_ALIGN_TOP; break;
+ default:
+ g_return_if_reached ();
+ }
+
+ g_object_set (options, "preview-split-alignment", alignment, NULL);
+}
+
+void
+gimp_filter_options_switch_preview_orientation (GimpFilterOptions *options,
+ gint position_x,
+ gint position_y)
+{
+ GimpAlignmentType alignment;
+ gint position;
+
+ g_return_if_fail (GIMP_IS_FILTER_OPTIONS (options));
+
+ switch (options->preview_split_alignment)
+ {
+ case GIMP_ALIGN_LEFT: alignment = GIMP_ALIGN_TOP; break;
+ case GIMP_ALIGN_RIGHT: alignment = GIMP_ALIGN_BOTTOM; break;
+ case GIMP_ALIGN_TOP: alignment = GIMP_ALIGN_LEFT; break;
+ case GIMP_ALIGN_BOTTOM: alignment = GIMP_ALIGN_RIGHT; break;
+ default:
+ g_return_if_reached ();
+ }
+
+ if (alignment == GIMP_ALIGN_LEFT ||
+ alignment == GIMP_ALIGN_RIGHT)
+ {
+ position = position_x;
+ }
+ else
+ {
+ position = position_y;
+ }
+
+ g_object_set (options,
+ "preview-split-alignment", alignment,
+ "preview-split-position", position,
+ NULL);
+}
diff --git a/app/tools/gimpfilteroptions.h b/app/tools/gimpfilteroptions.h
new file mode 100644
index 0000000..7cd3b5f
--- /dev/null
+++ b/app/tools/gimpfilteroptions.h
@@ -0,0 +1,63 @@
+/* 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_FILTER_OPTIONS_H__
+#define __GIMP_FILTER_OPTIONS_H__
+
+
+#include "gimpcoloroptions.h"
+
+
+#define GIMP_TYPE_FILTER_OPTIONS (gimp_filter_options_get_type ())
+#define GIMP_FILTER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILTER_OPTIONS, GimpFilterOptions))
+#define GIMP_FILTER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILTER_OPTIONS, GimpFilterOptionsClass))
+#define GIMP_IS_FILTER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILTER_OPTIONS))
+#define GIMP_IS_FILTER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILTER_OPTIONS))
+#define GIMP_FILTER_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILTER_OPTIONS, GimpFilterOptionsClass))
+
+
+typedef struct _GimpFilterOptionsClass GimpFilterOptionsClass;
+
+struct _GimpFilterOptions
+{
+ GimpColorOptions parent_instance;
+
+ gboolean preview;
+ gboolean preview_split;
+ GimpAlignmentType preview_split_alignment;
+ gint preview_split_position;
+ gboolean controller;
+
+ gboolean blending_options_expanded;
+ gboolean color_options_expanded;
+};
+
+struct _GimpFilterOptionsClass
+{
+ GimpColorOptionsClass parent_instance;
+};
+
+
+GType gimp_filter_options_get_type (void) G_GNUC_CONST;
+
+void gimp_filter_options_switch_preview_side (GimpFilterOptions *options);
+void gimp_filter_options_switch_preview_orientation (GimpFilterOptions *options,
+ gint position_x,
+ gint position_y);
+
+
+#endif /* __GIMP_FILTER_OPTIONS_H__ */
diff --git a/app/tools/gimpfiltertool-settings.c b/app/tools/gimpfiltertool-settings.c
new file mode 100644
index 0000000..c8364ff
--- /dev/null
+++ b/app/tools/gimpfiltertool-settings.c
@@ -0,0 +1,252 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfiltertool-settings.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 "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimpsettingsbox.h"
+
+#include "display/gimptoolgui.h"
+
+#include "gimpfiltertool.h"
+#include "gimpfiltertool-settings.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static gboolean gimp_filter_tool_settings_import (GimpSettingsBox *box,
+ GFile *file,
+ GimpFilterTool *filter_tool);
+static gboolean gimp_filter_tool_settings_export (GimpSettingsBox *box,
+ GFile *file,
+ GimpFilterTool *filter_tool);
+
+
+/* public functions */
+
+GtkWidget *
+gimp_filter_tool_get_settings_box (GimpFilterTool *filter_tool)
+{
+ GimpToolInfo *tool_info = GIMP_TOOL (filter_tool)->tool_info;
+ GQuark quark = g_quark_from_static_string ("settings-folder");
+ GType type = G_TYPE_FROM_INSTANCE (filter_tool->config);
+ GFile *settings_folder;
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *combo;
+ gchar *import_title;
+ gchar *export_title;
+
+ settings_folder = g_type_get_qdata (type, quark);
+
+ import_title = g_strdup_printf (_("Import '%s' Settings"),
+ gimp_tool_get_label (GIMP_TOOL (filter_tool)));
+ export_title = g_strdup_printf (_("Export '%s' Settings"),
+ gimp_tool_get_label (GIMP_TOOL (filter_tool)));
+
+ box = gimp_settings_box_new (tool_info->gimp,
+ filter_tool->config,
+ filter_tool->settings,
+ import_title,
+ export_title,
+ gimp_tool_get_help_id (GIMP_TOOL (filter_tool)),
+ settings_folder,
+ NULL);
+
+ g_free (import_title);
+ g_free (export_title);
+
+ g_signal_connect (box, "import",
+ G_CALLBACK (gimp_filter_tool_settings_import),
+ filter_tool);
+
+ g_signal_connect (box, "export",
+ G_CALLBACK (gimp_filter_tool_settings_export),
+ filter_tool);
+
+ g_signal_connect_swapped (box, "selected",
+ G_CALLBACK (gimp_filter_tool_set_config),
+ filter_tool);
+
+ label = gtk_label_new_with_mnemonic (_("Pre_sets:"));
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (box), label, 0);
+ gtk_widget_show (label);
+
+ combo = gimp_settings_box_get_combo (GIMP_SETTINGS_BOX (box));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ return box;
+}
+
+gboolean
+gimp_filter_tool_real_settings_import (GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error)
+{
+ return gimp_config_deserialize_stream (GIMP_CONFIG (filter_tool->config),
+ input,
+ NULL, error);
+}
+
+gboolean
+gimp_filter_tool_real_settings_export (GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ gchar *header;
+ gchar *footer;
+ gboolean success;
+
+ header = g_strdup_printf ("GIMP '%s' settings",
+ gimp_tool_get_label (tool));
+ footer = g_strdup_printf ("end of '%s' settings",
+ gimp_tool_get_label (tool));
+
+ success = gimp_config_serialize_to_stream (GIMP_CONFIG (filter_tool->config),
+ output,
+ header, footer,
+ NULL, error);
+
+ g_free (header);
+ g_free (footer);
+
+ return success;
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_filter_tool_settings_import (GimpSettingsBox *box,
+ GFile *file,
+ GimpFilterTool *filter_tool)
+{
+ GimpFilterToolClass *tool_class = GIMP_FILTER_TOOL_GET_CLASS (filter_tool);
+ GInputStream *input;
+ GError *error = NULL;
+
+ g_return_val_if_fail (tool_class->settings_import != NULL, FALSE);
+
+ if (GIMP_TOOL (filter_tool)->tool_info->gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ input = G_INPUT_STREAM (g_file_read (file, NULL, &error));
+ if (! input)
+ {
+ gimp_message (GIMP_TOOL (filter_tool)->tool_info->gimp,
+ G_OBJECT (gimp_tool_gui_get_dialog (filter_tool->gui)),
+ GIMP_MESSAGE_ERROR,
+ _("Could not open '%s' for reading: %s"),
+ gimp_file_get_utf8_name (file),
+ error->message);
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ if (! tool_class->settings_import (filter_tool, input, &error))
+ {
+ gimp_message (GIMP_TOOL (filter_tool)->tool_info->gimp,
+ G_OBJECT (gimp_tool_gui_get_dialog (filter_tool->gui)),
+ GIMP_MESSAGE_ERROR,
+ _("Error reading '%s': %s"),
+ gimp_file_get_utf8_name (file),
+ error->message);
+ g_clear_error (&error);
+ g_object_unref (input);
+ return FALSE;
+ }
+
+ g_object_unref (input);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_filter_tool_settings_export (GimpSettingsBox *box,
+ GFile *file,
+ GimpFilterTool *filter_tool)
+{
+ GimpFilterToolClass *tool_class = GIMP_FILTER_TOOL_GET_CLASS (filter_tool);
+ GOutputStream *output;
+ GError *error = NULL;
+
+ g_return_val_if_fail (tool_class->settings_export != NULL, FALSE);
+
+ if (GIMP_TOOL (filter_tool)->tool_info->gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, &error));
+ if (! output)
+ {
+ gimp_message_literal (GIMP_TOOL (filter_tool)->tool_info->gimp,
+ G_OBJECT (gimp_tool_gui_get_dialog (filter_tool->gui)),
+ GIMP_MESSAGE_ERROR,
+ error->message);
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ if (! tool_class->settings_export (filter_tool, output, &error))
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ gimp_message (GIMP_TOOL (filter_tool)->tool_info->gimp,
+ G_OBJECT (gimp_tool_gui_get_dialog (filter_tool->gui)),
+ GIMP_MESSAGE_ERROR,
+ _("Error writing '%s': %s"),
+ gimp_file_get_utf8_name (file),
+ error->message);
+ g_clear_error (&error);
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ g_object_unref (output);
+
+ return FALSE;
+ }
+
+ g_object_unref (output);
+
+ gimp_message (GIMP_TOOL (filter_tool)->tool_info->gimp,
+ G_OBJECT (GIMP_TOOL (filter_tool)->display),
+ GIMP_MESSAGE_INFO,
+ _("Settings saved to '%s'"),
+ gimp_file_get_utf8_name (file));
+
+ return TRUE;
+}
diff --git a/app/tools/gimpfiltertool-settings.h b/app/tools/gimpfiltertool-settings.h
new file mode 100644
index 0000000..9367b1c
--- /dev/null
+++ b/app/tools/gimpfiltertool-settings.h
@@ -0,0 +1,37 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfiltertool-settings.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_FILTER_TOOL_SETTINGS_H__
+#define __GIMP_FILTER_TOOL_SETTINGS_H__
+
+
+GtkWidget * gimp_filter_tool_get_settings_box (GimpFilterTool *filter_tool);
+
+
+/* virtual functions of GimpSettingsTool, don't call directly */
+
+gboolean gimp_filter_tool_real_settings_import (GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error);
+gboolean gimp_filter_tool_real_settings_export (GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_FILTER_TOOL_SETTINGS_H__ */
diff --git a/app/tools/gimpfiltertool-widgets.c b/app/tools/gimpfiltertool-widgets.c
new file mode 100644
index 0000000..d5cf6fe
--- /dev/null
+++ b/app/tools/gimpfiltertool-widgets.c
@@ -0,0 +1,964 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfiltertool-widgets.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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "tools-types.h"
+
+#include "core/gimpcontainer.h"
+#include "core/gimpitem.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolfocus.h"
+#include "display/gimptoolgyroscope.h"
+#include "display/gimptoolline.h"
+#include "display/gimptooltransformgrid.h"
+#include "display/gimptoolwidgetgroup.h"
+
+#include "gimpfilteroptions.h"
+#include "gimpfiltertool.h"
+#include "gimpfiltertool-widgets.h"
+
+
+typedef struct _Controller Controller;
+
+struct _Controller
+{
+ GimpFilterTool *filter_tool;
+ GimpControllerType controller_type;
+ GimpToolWidget *widget;
+ GCallback creator_callback;
+ gpointer creator_data;
+};
+
+
+/* local function prototypes */
+
+static Controller * gimp_filter_tool_controller_new (void);
+static void gimp_filter_tool_controller_free (Controller *controller);
+
+static void gimp_filter_tool_set_line (Controller *controller,
+ GeglRectangle *area,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+static void gimp_filter_tool_line_changed (GimpToolWidget *widget,
+ Controller *controller);
+
+static void gimp_filter_tool_set_slider_line (Controller *controller,
+ GeglRectangle *area,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ const GimpControllerSlider *sliders,
+ gint n_sliders);
+static void gimp_filter_tool_slider_line_changed (GimpToolWidget *widget,
+ Controller *controller);
+
+static void gimp_filter_tool_set_transform_grid (Controller *controller,
+ GeglRectangle *area,
+ const GimpMatrix3 *transform);
+static void gimp_filter_tool_transform_grid_changed (GimpToolWidget *widget,
+ Controller *controller);
+
+static void gimp_filter_tool_set_transform_grids (Controller *controller,
+ GeglRectangle *area,
+ const GimpMatrix3 *transforms,
+ gint n_transforms);
+static void gimp_filter_tool_transform_grids_changed (GimpToolWidget *widget,
+ Controller *controller);
+
+static void gimp_filter_tool_set_gyroscope (Controller *controller,
+ GeglRectangle *area,
+ gdouble yaw,
+ gdouble pitch,
+ gdouble roll,
+ gdouble zoom,
+ gboolean invert);
+static void gimp_filter_tool_gyroscope_changed (GimpToolWidget *widget,
+ Controller *controller);
+
+static void gimp_filter_tool_set_focus (Controller *controller,
+ GeglRectangle *area,
+ GimpLimitType type,
+ gdouble x,
+ gdouble y,
+ gdouble radius,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gdouble inner_limit,
+ gdouble midpoint);
+static void gimp_filter_tool_focus_changed (GimpToolWidget *widget,
+ Controller *controller);
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_filter_tool_create_widget (GimpFilterTool *filter_tool,
+ GimpControllerType controller_type,
+ const gchar *status_title,
+ GCallback callback,
+ gpointer callback_data,
+ GCallback *set_func,
+ gpointer *set_func_data)
+{
+ GimpTool *tool;
+ GimpDisplayShell *shell;
+ Controller *controller;
+
+ g_return_val_if_fail (GIMP_IS_FILTER_TOOL (filter_tool), NULL);
+ g_return_val_if_fail (filter_tool->config != NULL, NULL);
+
+ tool = GIMP_TOOL (filter_tool);
+
+ g_return_val_if_fail (tool->display != NULL, NULL);
+
+ shell = gimp_display_get_shell (tool->display);
+
+ controller = gimp_filter_tool_controller_new ();
+
+ controller->filter_tool = filter_tool;
+ controller->controller_type = controller_type;
+ controller->creator_callback = callback;
+ controller->creator_data = callback_data;
+
+ switch (controller_type)
+ {
+ case GIMP_CONTROLLER_TYPE_LINE:
+ controller->widget = gimp_tool_line_new (shell, 100, 100, 500, 500);
+
+ g_object_set (controller->widget,
+ "status-title", status_title,
+ NULL);
+
+ g_signal_connect (controller->widget, "changed",
+ G_CALLBACK (gimp_filter_tool_line_changed),
+ controller);
+
+ *set_func = (GCallback) gimp_filter_tool_set_line;
+ *set_func_data = controller;
+ break;
+
+ case GIMP_CONTROLLER_TYPE_SLIDER_LINE:
+ controller->widget = gimp_tool_line_new (shell, 100, 100, 500, 500);
+
+ g_object_set (controller->widget,
+ "status-title", status_title,
+ NULL);
+
+ g_signal_connect (controller->widget, "changed",
+ G_CALLBACK (gimp_filter_tool_slider_line_changed),
+ controller);
+
+ *set_func = (GCallback) gimp_filter_tool_set_slider_line;
+ *set_func_data = controller;
+ break;
+
+ case GIMP_CONTROLLER_TYPE_TRANSFORM_GRID:
+ {
+ GimpMatrix3 transform;
+ gint off_x, off_y;
+ GeglRectangle area;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_matrix3_identity (&transform);
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ x1 = off_x + area.x;
+ y1 = off_y + area.y;
+ x2 = x1 + area.width;
+ y2 = y1 + area.height;
+
+ controller->widget = gimp_tool_transform_grid_new (shell, &transform,
+ x1, y1, x2, y2);
+
+ g_object_set (controller->widget,
+ "pivot-x", (x1 + x2) / 2.0,
+ "pivot-y", (y1 + y2) / 2.0,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-corner-handles", TRUE,
+ "use-perspective-handles", TRUE,
+ "use-side-handles", TRUE,
+ "use-shear-handles", TRUE,
+ "use-pivot-handle", TRUE,
+ NULL);
+
+ g_signal_connect (controller->widget, "changed",
+ G_CALLBACK (gimp_filter_tool_transform_grid_changed),
+ controller);
+
+ *set_func = (GCallback) gimp_filter_tool_set_transform_grid;
+ *set_func_data = controller;
+ }
+ break;
+
+ case GIMP_CONTROLLER_TYPE_TRANSFORM_GRIDS:
+ {
+ controller->widget = gimp_tool_widget_group_new (shell);
+
+ gimp_tool_widget_group_set_auto_raise (
+ GIMP_TOOL_WIDGET_GROUP (controller->widget), TRUE);
+
+ g_signal_connect (controller->widget, "changed",
+ G_CALLBACK (gimp_filter_tool_transform_grids_changed),
+ controller);
+
+ *set_func = (GCallback) gimp_filter_tool_set_transform_grids;
+ *set_func_data = controller;
+ }
+ break;
+
+ case GIMP_CONTROLLER_TYPE_GYROSCOPE:
+ {
+ GeglRectangle area;
+ gint off_x, off_y;
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ controller->widget = gimp_tool_gyroscope_new (shell);
+
+ g_object_set (controller->widget,
+ "speed", 1.0 / MAX (area.width, area.height),
+ "pivot-x", off_x + area.x + area.width / 2.0,
+ "pivot-y", off_y + area.y + area.height / 2.0,
+ NULL);
+
+ g_signal_connect (controller->widget, "changed",
+ G_CALLBACK (gimp_filter_tool_gyroscope_changed),
+ controller);
+
+ *set_func = (GCallback) gimp_filter_tool_set_gyroscope;
+ *set_func_data = controller;
+ }
+ break;
+
+ case GIMP_CONTROLLER_TYPE_FOCUS:
+ controller->widget = gimp_tool_focus_new (shell);
+
+ g_signal_connect (controller->widget, "changed",
+ G_CALLBACK (gimp_filter_tool_focus_changed),
+ controller);
+
+ *set_func = (GCallback) gimp_filter_tool_set_focus;
+ *set_func_data = controller;
+ break;
+ }
+
+ g_object_add_weak_pointer (G_OBJECT (controller->widget),
+ (gpointer) &controller->widget);
+
+ g_object_set_data_full (filter_tool->config,
+ "gimp-filter-tool-controller", controller,
+ (GDestroyNotify) gimp_filter_tool_controller_free);
+
+ return controller->widget;
+}
+
+static void
+gimp_filter_tool_reset_transform_grid (GimpToolWidget *widget,
+ GimpFilterTool *filter_tool)
+{
+ GimpMatrix3 *transform;
+ gint off_x, off_y;
+ GeglRectangle area;
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble pivot_x, pivot_y;
+
+ g_object_get (widget,
+ "transform", &transform,
+ NULL);
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ x1 = off_x + area.x;
+ y1 = off_y + area.y;
+ x2 = x1 + area.width;
+ y2 = y1 + area.height;
+
+ gimp_matrix3_transform_point (transform,
+ (x1 + x2) / 2.0, (y1 + y2) / 2.0,
+ &pivot_x, &pivot_y);
+
+ g_object_set (widget,
+ "pivot-x", pivot_x,
+ "pivot-y", pivot_y,
+ NULL);
+
+ g_free (transform);
+}
+
+void
+gimp_filter_tool_reset_widget (GimpFilterTool *filter_tool,
+ GimpToolWidget *widget)
+{
+ Controller *controller;
+
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+ g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget));
+ g_return_if_fail (filter_tool->config != NULL);
+
+ controller = g_object_get_data (filter_tool->config,
+ "gimp-filter-tool-controller");
+
+ g_return_if_fail (controller != NULL);
+
+ switch (controller->controller_type)
+ {
+ case GIMP_CONTROLLER_TYPE_TRANSFORM_GRID:
+ {
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_transform_grid_changed,
+ controller);
+
+ gimp_filter_tool_reset_transform_grid (controller->widget, filter_tool);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_transform_grid_changed,
+ controller);
+ }
+ break;
+
+ case GIMP_CONTROLLER_TYPE_TRANSFORM_GRIDS:
+ {
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_transform_grids_changed,
+ controller);
+
+ gimp_container_foreach (
+ gimp_tool_widget_group_get_children (
+ GIMP_TOOL_WIDGET_GROUP (controller->widget)),
+ (GFunc) gimp_filter_tool_reset_transform_grid,
+ filter_tool);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_transform_grids_changed,
+ controller);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/* private functions */
+
+static Controller *
+gimp_filter_tool_controller_new (void)
+{
+ return g_slice_new0 (Controller);
+}
+
+static void
+gimp_filter_tool_controller_free (Controller *controller)
+{
+ if (controller->widget)
+ {
+ g_signal_handlers_disconnect_by_data (controller->widget, controller);
+
+ g_object_remove_weak_pointer (G_OBJECT (controller->widget),
+ (gpointer) &controller->widget);
+ }
+
+ g_slice_free (Controller, controller);
+}
+
+static void
+gimp_filter_tool_set_line (Controller *controller,
+ GeglRectangle *area,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GimpTool *tool;
+ GimpDrawable *drawable;
+
+ if (! controller->widget)
+ return;
+
+ tool = GIMP_TOOL (controller->filter_tool);
+ drawable = tool->drawable;
+
+ if (drawable)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x1 += off_x + area->x;
+ y1 += off_y + area->y;
+ x2 += off_x + area->x;
+ y2 += off_y + area->y;
+ }
+
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_line_changed,
+ controller);
+
+ g_object_set (controller->widget,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_line_changed,
+ controller);
+}
+
+static void
+gimp_filter_tool_line_changed (GimpToolWidget *widget,
+ Controller *controller)
+{
+ GimpFilterTool *filter_tool = controller->filter_tool;
+ GimpControllerLineCallback line_callback;
+ gdouble x1, y1, x2, y2;
+ gint off_x, off_y;
+ GeglRectangle area;
+
+ line_callback = (GimpControllerLineCallback) controller->creator_callback;
+
+ g_object_get (widget,
+ "x1", &x1,
+ "y1", &y1,
+ "x2", &x2,
+ "y2", &y2,
+ NULL);
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ x1 -= off_x + area.x;
+ y1 -= off_y + area.y;
+ x2 -= off_x + area.x;
+ y2 -= off_y + area.y;
+
+ line_callback (controller->creator_data,
+ &area, x1, y1, x2, y2);
+}
+
+static void
+gimp_filter_tool_set_slider_line (Controller *controller,
+ GeglRectangle *area,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ const GimpControllerSlider *sliders,
+ gint n_sliders)
+{
+ GimpTool *tool;
+ GimpDrawable *drawable;
+
+ if (! controller->widget)
+ return;
+
+ tool = GIMP_TOOL (controller->filter_tool);
+ drawable = tool->drawable;
+
+ if (drawable)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x1 += off_x + area->x;
+ y1 += off_y + area->y;
+ x2 += off_x + area->x;
+ y2 += off_y + area->y;
+ }
+
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_slider_line_changed,
+ controller);
+
+ g_object_set (controller->widget,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+
+ gimp_tool_line_set_sliders (GIMP_TOOL_LINE (controller->widget),
+ sliders, n_sliders);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_slider_line_changed,
+ controller);
+}
+
+static void
+gimp_filter_tool_slider_line_changed (GimpToolWidget *widget,
+ Controller *controller)
+{
+ GimpFilterTool *filter_tool = controller->filter_tool;
+ GimpControllerSliderLineCallback slider_line_callback;
+ gdouble x1, y1, x2, y2;
+ const GimpControllerSlider *sliders;
+ gint n_sliders;
+ gint off_x, off_y;
+ GeglRectangle area;
+
+ slider_line_callback =
+ (GimpControllerSliderLineCallback) controller->creator_callback;
+
+ g_object_get (widget,
+ "x1", &x1,
+ "y1", &y1,
+ "x2", &x2,
+ "y2", &y2,
+ NULL);
+
+ sliders = gimp_tool_line_get_sliders (GIMP_TOOL_LINE (controller->widget),
+ &n_sliders);
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ x1 -= off_x + area.x;
+ y1 -= off_y + area.y;
+ x2 -= off_x + area.x;
+ y2 -= off_y + area.y;
+
+ slider_line_callback (controller->creator_data,
+ &area, x1, y1, x2, y2, sliders, n_sliders);
+}
+
+static void
+gimp_filter_tool_set_transform_grid (Controller *controller,
+ GeglRectangle *area,
+ const GimpMatrix3 *transform)
+{
+ GimpTool *tool;
+ GimpDrawable *drawable;
+ gdouble x1 = area->x;
+ gdouble y1 = area->y;
+ gdouble x2 = area->x + area->width;
+ gdouble y2 = area->y + area->height;
+ GimpMatrix3 matrix;
+
+ if (! controller->widget)
+ return;
+
+ tool = GIMP_TOOL (controller->filter_tool);
+ drawable = tool->drawable;
+
+ if (drawable)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x1 += off_x;
+ y1 += off_y;
+ x2 += off_x;
+ y2 += off_y;
+ }
+
+ gimp_matrix3_identity (&matrix);
+ gimp_matrix3_translate (&matrix, -x1, -y1);
+ gimp_matrix3_mult (transform, &matrix);
+ gimp_matrix3_translate (&matrix, +x1, +y1);
+
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_transform_grid_changed,
+ controller);
+
+ g_object_set (controller->widget,
+ "transform", &matrix,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_transform_grid_changed,
+ controller);
+}
+
+static void
+gimp_filter_tool_transform_grid_changed (GimpToolWidget *widget,
+ Controller *controller)
+{
+ GimpFilterTool *filter_tool = controller->filter_tool;
+ GimpControllerTransformGridCallback transform_grid_callback;
+ gint off_x, off_y;
+ GeglRectangle area;
+ GimpMatrix3 *transform;
+ GimpMatrix3 matrix;
+
+ transform_grid_callback =
+ (GimpControllerTransformGridCallback) controller->creator_callback;
+
+ g_object_get (widget,
+ "transform", &transform,
+ NULL);
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ gimp_matrix3_identity (&matrix);
+ gimp_matrix3_translate (&matrix, +(off_x + area.x), +(off_y + area.y));
+ gimp_matrix3_mult (transform, &matrix);
+ gimp_matrix3_translate (&matrix, -(off_x + area.x), -(off_y + area.y));
+
+ transform_grid_callback (controller->creator_data,
+ &area, &matrix);
+
+ g_free (transform);
+}
+
+static void
+gimp_filter_tool_set_transform_grids (Controller *controller,
+ GeglRectangle *area,
+ const GimpMatrix3 *transforms,
+ gint n_transforms)
+{
+ GimpTool *tool;
+ GimpDisplayShell *shell;
+ GimpDrawable *drawable;
+ GimpContainer *grids;
+ GimpToolWidget *grid = NULL;
+ gdouble x1 = area->x;
+ gdouble y1 = area->y;
+ gdouble x2 = area->x + area->width;
+ gdouble y2 = area->y + area->height;
+ GimpMatrix3 matrix;
+ gint i;
+
+ if (! controller->widget)
+ return;
+
+ tool = GIMP_TOOL (controller->filter_tool);
+ shell = gimp_display_get_shell (tool->display);
+ drawable = tool->drawable;
+
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_transform_grids_changed,
+ controller);
+
+ if (drawable)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x1 += off_x;
+ y1 += off_y;
+ x2 += off_x;
+ y2 += off_y;
+ }
+
+ grids = gimp_tool_widget_group_get_children (
+ GIMP_TOOL_WIDGET_GROUP (controller->widget));
+
+ if (n_transforms > gimp_container_get_n_children (grids))
+ {
+ gimp_matrix3_identity (&matrix);
+
+ for (i = gimp_container_get_n_children (grids); i < n_transforms; i++)
+ {
+ gdouble pivot_x;
+ gdouble pivot_y;
+
+ grid = gimp_tool_transform_grid_new (shell, &matrix, x1, y1, x2, y2);
+
+ if (i > 0 && ! memcmp (&transforms[i], &transforms[i - 1],
+ sizeof (GimpMatrix3)))
+ {
+ g_object_get (gimp_container_get_last_child (grids),
+ "pivot-x", &pivot_x,
+ "pivot-y", &pivot_y,
+ NULL);
+ }
+ else
+ {
+ pivot_x = (x1 + x2) / 2.0;
+ pivot_y = (y1 + y2) / 2.0;
+
+ gimp_matrix3_transform_point (&transforms[i],
+ pivot_x, pivot_y,
+ &pivot_x, &pivot_y);
+ }
+
+ g_object_set (grid,
+ "pivot-x", pivot_x,
+ "pivot-y", pivot_y,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-corner-handles", TRUE,
+ "use-perspective-handles", TRUE,
+ "use-side-handles", TRUE,
+ "use-shear-handles", TRUE,
+ "use-pivot-handle", TRUE,
+ NULL);
+
+ gimp_container_add (grids, GIMP_OBJECT (grid));
+
+ g_object_unref (grid);
+ }
+
+ gimp_tool_widget_set_focus (grid, TRUE);
+ }
+ else
+ {
+ while (gimp_container_get_n_children (grids) > n_transforms)
+ gimp_container_remove (grids, gimp_container_get_last_child (grids));
+ }
+
+ for (i = 0; i < n_transforms; i++)
+ {
+ gimp_matrix3_identity (&matrix);
+ gimp_matrix3_translate (&matrix, -x1, -y1);
+ gimp_matrix3_mult (&transforms[i], &matrix);
+ gimp_matrix3_translate (&matrix, +x1, +y1);
+
+ g_object_set (gimp_container_get_child_by_index (grids, i),
+ "transform", &matrix,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ NULL);
+ }
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_transform_grids_changed,
+ controller);
+}
+
+static void
+gimp_filter_tool_transform_grids_changed (GimpToolWidget *widget,
+ Controller *controller)
+{
+ GimpFilterTool *filter_tool = controller->filter_tool;
+ GimpControllerTransformGridsCallback transform_grids_callback;
+ GimpContainer *grids;
+ gint off_x, off_y;
+ GeglRectangle area;
+ GimpMatrix3 *transforms;
+ gint n_transforms;
+ gint i;
+
+ transform_grids_callback =
+ (GimpControllerTransformGridsCallback) controller->creator_callback;
+
+ grids = gimp_tool_widget_group_get_children (
+ GIMP_TOOL_WIDGET_GROUP (controller->widget));
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ n_transforms = gimp_container_get_n_children (grids);
+ transforms = g_new (GimpMatrix3, n_transforms);
+
+ for (i = 0; i < n_transforms; i++)
+ {
+ GimpMatrix3 *transform;
+
+ g_object_get (gimp_container_get_child_by_index (grids, i),
+ "transform", &transform,
+ NULL);
+
+ gimp_matrix3_identity (&transforms[i]);
+ gimp_matrix3_translate (&transforms[i],
+ +(off_x + area.x), +(off_y + area.y));
+ gimp_matrix3_mult (transform, &transforms[i]);
+ gimp_matrix3_translate (&transforms[i],
+ -(off_x + area.x), -(off_y + area.y));
+
+ g_free (transform);
+ }
+
+ transform_grids_callback (controller->creator_data,
+ &area, transforms, n_transforms);
+
+ g_free (transforms);
+}
+
+static void
+gimp_filter_tool_set_gyroscope (Controller *controller,
+ GeglRectangle *area,
+ gdouble yaw,
+ gdouble pitch,
+ gdouble roll,
+ gdouble zoom,
+ gboolean invert)
+{
+ if (! controller->widget)
+ return;
+
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_gyroscope_changed,
+ controller);
+
+ g_object_set (controller->widget,
+ "yaw", yaw,
+ "pitch", pitch,
+ "roll", roll,
+ "zoom", zoom,
+ "invert", invert,
+ NULL);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_gyroscope_changed,
+ controller);
+}
+
+static void
+gimp_filter_tool_gyroscope_changed (GimpToolWidget *widget,
+ Controller *controller)
+{
+ GimpFilterTool *filter_tool = controller->filter_tool;
+ GimpControllerGyroscopeCallback gyroscope_callback;
+ gint off_x, off_y;
+ GeglRectangle area;
+ gdouble yaw;
+ gdouble pitch;
+ gdouble roll;
+ gdouble zoom;
+ gboolean invert;
+
+ gyroscope_callback =
+ (GimpControllerGyroscopeCallback) controller->creator_callback;
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ g_object_get (widget,
+ "yaw", &yaw,
+ "pitch", &pitch,
+ "roll", &roll,
+ "zoom", &zoom,
+ "invert", &invert,
+ NULL);
+
+ gyroscope_callback (controller->creator_data,
+ &area, yaw, pitch, roll, zoom, invert);
+}
+
+static void
+gimp_filter_tool_set_focus (Controller *controller,
+ GeglRectangle *area,
+ GimpLimitType type,
+ gdouble x,
+ gdouble y,
+ gdouble radius,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gdouble inner_limit,
+ gdouble midpoint)
+{
+ GimpTool *tool;
+ GimpDrawable *drawable;
+
+ if (! controller->widget)
+ return;
+
+ tool = GIMP_TOOL (controller->filter_tool);
+ drawable = tool->drawable;
+
+ if (drawable)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x += off_x + area->x;
+ y += off_y + area->y;
+ }
+
+ g_signal_handlers_block_by_func (controller->widget,
+ gimp_filter_tool_focus_changed,
+ controller);
+
+ g_object_set (controller->widget,
+ "type", type,
+ "x", x,
+ "y", y,
+ "radius", radius,
+ "aspect-ratio", aspect_ratio,
+ "angle", angle,
+ "inner-limit", inner_limit,
+ "midpoint", midpoint,
+ NULL);
+
+ g_signal_handlers_unblock_by_func (controller->widget,
+ gimp_filter_tool_focus_changed,
+ controller);
+}
+
+static void
+gimp_filter_tool_focus_changed (GimpToolWidget *widget,
+ Controller *controller)
+{
+ GimpFilterTool *filter_tool = controller->filter_tool;
+ GimpControllerFocusCallback focus_callback;
+ GimpLimitType type;
+ gdouble x, y;
+ gdouble radius;
+ gdouble aspect_ratio;
+ gdouble angle;
+ gdouble inner_limit;
+ gdouble midpoint;
+ gint off_x, off_y;
+ GeglRectangle area;
+
+ focus_callback = (GimpControllerFocusCallback) controller->creator_callback;
+
+ g_object_get (widget,
+ "type", &type,
+ "x", &x,
+ "y", &y,
+ "radius", &radius,
+ "aspect-ratio", &aspect_ratio,
+ "angle", &angle,
+ "inner-limit", &inner_limit,
+ "midpoint", &midpoint,
+ NULL);
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ x -= off_x + area.x;
+ y -= off_y + area.y;
+
+ focus_callback (controller->creator_data,
+ &area,
+ type,
+ x,
+ y,
+ radius,
+ aspect_ratio,
+ angle,
+ inner_limit,
+ midpoint);
+}
diff --git a/app/tools/gimpfiltertool-widgets.h b/app/tools/gimpfiltertool-widgets.h
new file mode 100644
index 0000000..1b47e8f
--- /dev/null
+++ b/app/tools/gimpfiltertool-widgets.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfiltertool-widgets.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_FILTER_TOOL_WIDGETS_H__
+#define __GIMP_FILTER_TOOL_WIDGETS_H__
+
+
+GimpToolWidget * gimp_filter_tool_create_widget (GimpFilterTool *filter_tool,
+ GimpControllerType controller_type,
+ const gchar *status_title,
+ GCallback callback,
+ gpointer callback_data,
+ GCallback *set_func,
+ gpointer *set_func_data);
+
+void gimp_filter_tool_reset_widget (GimpFilterTool *filter_tool,
+ GimpToolWidget *widget);
+
+
+#endif /* __GIMP_FILTER_TOOL_WIDGETS_H__ */
diff --git a/app/tools/gimpfiltertool.c b/app/tools/gimpfiltertool.c
new file mode 100644
index 0000000..2dbe0d6
--- /dev/null
+++ b/app/tools/gimpfiltertool.c
@@ -0,0 +1,2027 @@
+/* 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/>.
+ */
+
+/* This file contains a base class for tools that implement on canvas
+ * preview for non destructive editing. The processing of the pixels can
+ * be done either by a gegl op or by a C function (apply_func).
+ *
+ * For the core side of this, please see app/core/gimpdrawablefilter.c.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gegl-plugin.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "operations/gimp-operation-config.h"
+#include "operations/gimpoperationsettings.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimpchannel.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-pick-color.h"
+#include "core/gimplayer.h"
+#include "core/gimplist.h"
+#include "core/gimppickable.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+#include "core/gimpsettings.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimplayermodebox.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpsettingsbox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptoolwidget.h"
+
+#include "gimpfilteroptions.h"
+#include "gimpfiltertool.h"
+#include "gimpfiltertool-settings.h"
+#include "gimpfiltertool-widgets.h"
+#include "gimpguidetool.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+#include "tool_manager.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_filter_tool_finalize (GObject *object);
+
+static gboolean gimp_filter_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_filter_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_filter_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_filter_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_filter_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_filter_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_filter_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_filter_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_filter_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static gboolean gimp_filter_tool_can_pick_color (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+static gboolean gimp_filter_tool_pick_color (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_filter_tool_color_picked (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color);
+
+static void gimp_filter_tool_real_reset (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_real_set_config(GimpFilterTool *filter_tool,
+ GimpConfig *config);
+static void gimp_filter_tool_real_config_notify
+ (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec);
+
+static void gimp_filter_tool_halt (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_commit (GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_dialog (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_reset (GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_create_filter (GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_update_dialog (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_update_dialog_operation_settings
+ (GimpFilterTool *filter_tool);
+
+
+static void gimp_filter_tool_region_changed (GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_flush (GimpDrawableFilter *filter,
+ GimpFilterTool *filter_tool);
+static void gimp_filter_tool_config_notify (GObject *object,
+ const GParamSpec *pspec,
+ GimpFilterTool *filter_tool);
+static void gimp_filter_tool_unset_setting (GObject *object,
+ const GParamSpec *pspec,
+ GimpFilterTool *filter_tool);
+static void gimp_filter_tool_lock_position_changed
+ (GimpDrawable *drawable,
+ GimpFilterTool *filter_tool);
+static void gimp_filter_tool_mask_changed (GimpImage *image,
+ GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_add_guide (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_remove_guide (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_move_guide (GimpFilterTool *filter_tool);
+static void gimp_filter_tool_guide_removed (GimpGuide *guide,
+ GimpFilterTool *filter_tool);
+static void gimp_filter_tool_guide_moved (GimpGuide *guide,
+ const GParamSpec *pspec,
+ GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_update_filter (GimpFilterTool *filter_tool);
+
+static void gimp_filter_tool_set_has_settings (GimpFilterTool *filter_tool,
+ gboolean has_settings);
+
+
+G_DEFINE_TYPE (GimpFilterTool, gimp_filter_tool, GIMP_TYPE_COLOR_TOOL)
+
+#define parent_class gimp_filter_tool_parent_class
+
+
+static void
+gimp_filter_tool_class_init (GimpFilterToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpColorToolClass *color_tool_class = GIMP_COLOR_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_filter_tool_finalize;
+
+ tool_class->initialize = gimp_filter_tool_initialize;
+ tool_class->control = gimp_filter_tool_control;
+ tool_class->button_press = gimp_filter_tool_button_press;
+ tool_class->button_release = gimp_filter_tool_button_release;
+ tool_class->motion = gimp_filter_tool_motion;
+ tool_class->key_press = gimp_filter_tool_key_press;
+ tool_class->oper_update = gimp_filter_tool_oper_update;
+ tool_class->cursor_update = gimp_filter_tool_cursor_update;
+ tool_class->options_notify = gimp_filter_tool_options_notify;
+
+ color_tool_class->can_pick = gimp_filter_tool_can_pick_color;
+ color_tool_class->pick = gimp_filter_tool_pick_color;
+ color_tool_class->picked = gimp_filter_tool_color_picked;
+
+ klass->get_operation = NULL;
+ klass->dialog = NULL;
+ klass->reset = gimp_filter_tool_real_reset;
+ klass->set_config = gimp_filter_tool_real_set_config;
+ klass->config_notify = gimp_filter_tool_real_config_notify;
+ klass->settings_import = gimp_filter_tool_real_settings_import;
+ klass->settings_export = gimp_filter_tool_real_settings_export;
+}
+
+static void
+gimp_filter_tool_init (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+}
+
+static void
+gimp_filter_tool_finalize (GObject *object)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (object);
+
+ g_clear_object (&filter_tool->operation);
+ g_clear_object (&filter_tool->config);
+ g_clear_object (&filter_tool->default_config);
+ g_clear_object (&filter_tool->settings);
+ g_clear_pointer (&filter_tool->description, g_free);
+ g_clear_object (&filter_tool->gui);
+ filter_tool->settings_box = NULL;
+ filter_tool->controller_toggle = NULL;
+ filter_tool->clip_combo = NULL;
+ filter_tool->region_combo = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+#define RESPONSE_RESET 1
+
+static gboolean
+gimp_filter_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpToolInfo *tool_info = tool->tool_info;
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ if (! drawable)
+ return FALSE;
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot modify the pixels of layer groups."));
+ return FALSE;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer's pixels are locked."));
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ return FALSE;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer is not visible."));
+ return FALSE;
+ }
+
+ gimp_filter_tool_get_operation (filter_tool);
+
+ gimp_filter_tool_disable_color_picking (filter_tool);
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ if (filter_tool->config)
+ gimp_config_reset (GIMP_CONFIG (filter_tool->config));
+
+ if (! filter_tool->gui)
+ {
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *toggle;
+
+ /* disabled for at least GIMP 2.8 */
+ filter_tool->overlay = FALSE;
+
+ filter_tool->gui =
+ gimp_tool_gui_new (tool_info,
+ gimp_tool_get_label (tool),
+ filter_tool->description,
+ gimp_tool_get_icon_name (tool),
+ gimp_tool_get_help_id (tool),
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ filter_tool->overlay,
+
+ _("_Reset"), RESPONSE_RESET,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_OK"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gimp_tool_gui_set_default_response (filter_tool->gui, GTK_RESPONSE_OK);
+
+ gimp_tool_gui_set_alternative_button_order (filter_tool->gui,
+ RESPONSE_RESET,
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ vbox = gimp_tool_gui_get_vbox (filter_tool->gui);
+
+ g_signal_connect_object (filter_tool->gui, "response",
+ G_CALLBACK (gimp_filter_tool_response),
+ G_OBJECT (filter_tool), 0);
+
+ if (filter_tool->config)
+ {
+ filter_tool->settings_box =
+ gimp_filter_tool_get_settings_box (filter_tool);
+ gtk_box_pack_start (GTK_BOX (vbox), filter_tool->settings_box,
+ FALSE, FALSE, 0);
+
+ if (filter_tool->has_settings)
+ gtk_widget_show (filter_tool->settings_box);
+ }
+
+ /* The preview and split view toggles */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ toggle = gimp_prop_check_button_new (G_OBJECT (tool_info->tool_options),
+ "preview", NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
+ gtk_widget_show (toggle);
+
+ toggle = gimp_prop_check_button_new (G_OBJECT (tool_info->tool_options),
+ "preview-split", NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_object_bind_property (G_OBJECT (tool_info->tool_options), "preview",
+ toggle, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ /* The show-controller toggle */
+ filter_tool->controller_toggle =
+ gimp_prop_check_button_new (G_OBJECT (tool_info->tool_options),
+ "controller", NULL);
+ gtk_box_pack_end (GTK_BOX (vbox), filter_tool->controller_toggle,
+ FALSE, FALSE, 0);
+ if (filter_tool->widget)
+ gtk_widget_show (filter_tool->controller_toggle);
+
+ /* The operation-settings box */
+ filter_tool->operation_settings_box = gtk_box_new (
+ GTK_ORIENTATION_VERTICAL, 2);
+ gtk_box_pack_end (GTK_BOX (vbox), filter_tool->operation_settings_box,
+ FALSE, FALSE, 0);
+ gtk_widget_show (filter_tool->operation_settings_box);
+
+ gimp_filter_tool_update_dialog_operation_settings (filter_tool);
+
+ /* Fill in subclass widgets */
+ gimp_filter_tool_dialog (filter_tool);
+ }
+ else
+ {
+ gimp_tool_gui_set_title (filter_tool->gui,
+ gimp_tool_get_label (tool));
+ gimp_tool_gui_set_description (filter_tool->gui, filter_tool->description);
+ gimp_tool_gui_set_icon_name (filter_tool->gui,
+ gimp_tool_get_icon_name (tool));
+ gimp_tool_gui_set_help_id (filter_tool->gui,
+ gimp_tool_get_help_id (tool));
+ }
+
+ gimp_tool_gui_set_shell (filter_tool->gui, shell);
+ gimp_tool_gui_set_viewable (filter_tool->gui, GIMP_VIEWABLE (drawable));
+
+ gimp_tool_gui_show (filter_tool->gui);
+
+ g_signal_connect_object (drawable, "lock-position-changed",
+ G_CALLBACK (gimp_filter_tool_lock_position_changed),
+ filter_tool, 0);
+
+ g_signal_connect_object (image, "mask-changed",
+ G_CALLBACK (gimp_filter_tool_mask_changed),
+ filter_tool, 0);
+
+ gimp_filter_tool_mask_changed (image, filter_tool);
+
+ gimp_filter_tool_create_filter (filter_tool);
+
+ return TRUE;
+}
+
+static void
+gimp_filter_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_filter_tool_halt (filter_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_filter_tool_commit (filter_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_filter_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ if (gimp_filter_tool_on_guide (filter_tool, coords, display))
+ {
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (tool);
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ gimp_filter_options_switch_preview_side (options);
+ }
+ else if (state & gimp_get_toggle_behavior_mask ())
+ {
+ GimpItem *item = GIMP_ITEM (tool->drawable);
+ gint pos_x;
+ gint pos_y;
+
+ pos_x = CLAMP (RINT (coords->x) - gimp_item_get_offset_x (item),
+ 0, gimp_item_get_width (item));
+ pos_y = CLAMP (RINT (coords->y) - gimp_item_get_offset_y (item),
+ 0, gimp_item_get_height (item));
+
+ gimp_filter_options_switch_preview_orientation (options,
+ pos_x, pos_y);
+ }
+ else
+ {
+ gimp_guide_tool_start_edit (tool, display,
+ filter_tool->preview_guide);
+ }
+ }
+ else if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ }
+ else if (filter_tool->widget)
+ {
+ if (gimp_tool_widget_button_press (filter_tool->widget, coords, time,
+ state, press_type))
+ {
+ filter_tool->grab_widget = filter_tool->widget;
+
+ gimp_tool_control_activate (tool->control);
+ }
+ }
+}
+
+static void
+gimp_filter_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ if (filter_tool->grab_widget)
+ {
+ gimp_tool_control_halt (tool->control);
+
+ gimp_tool_widget_button_release (filter_tool->grab_widget,
+ coords, time, state, release_type);
+ filter_tool->grab_widget = NULL;
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+ }
+}
+
+static void
+gimp_filter_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ if (filter_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (filter_tool->grab_widget, coords, time, state);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
+ display);
+ }
+}
+
+static gboolean
+gimp_filter_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ if (filter_tool->gui && display == tool->display)
+ {
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_filter_tool_response (filter_tool->gui,
+ GTK_RESPONSE_OK,
+ filter_tool);
+ return TRUE;
+
+ case GDK_KEY_BackSpace:
+ gimp_filter_tool_response (filter_tool->gui,
+ RESPONSE_RESET,
+ filter_tool);
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_filter_tool_response (filter_tool->gui,
+ GTK_RESPONSE_CANCEL,
+ filter_tool);
+ return TRUE;
+ }
+ }
+
+ return GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+}
+
+static void
+gimp_filter_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ gimp_tool_pop_status (tool, display);
+
+ if (gimp_filter_tool_on_guide (filter_tool, coords, display))
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+ gchar *status = NULL;
+
+ if (state & extend_mask)
+ {
+ status = g_strdup (_("Click to switch the original and filtered sides"));
+ }
+ else if (state & toggle_mask)
+ {
+ status = g_strdup (_("Click to switch between vertical and horizontal"));
+ }
+ else
+ {
+ status = gimp_suggest_modifiers (_("Click to move the split guide"),
+ (extend_mask | toggle_mask) & ~state,
+ _("%s: switch original and filtered"),
+ _("%s: switch horizontal and vertical"),
+ NULL);
+ }
+
+ if (proximity)
+ gimp_tool_push_status (tool, display, "%s", status);
+
+ g_free (status);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+ }
+}
+
+static void
+gimp_filter_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+
+ if (gimp_filter_tool_on_guide (filter_tool, coords, display))
+ {
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_HAND,
+ GIMP_CURSOR_MODIFIER_MOVE);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+ }
+}
+
+static void
+gimp_filter_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpFilterOptions *filter_options = GIMP_FILTER_OPTIONS (options);
+
+ if (! strcmp (pspec->name, "preview") &&
+ filter_tool->filter)
+ {
+ gimp_filter_tool_update_filter (filter_tool);
+
+ if (filter_options->preview)
+ {
+ gimp_drawable_filter_apply (filter_tool->filter, NULL);
+
+ if (filter_options->preview_split)
+ gimp_filter_tool_add_guide (filter_tool);
+ }
+ else
+ {
+ if (filter_options->preview_split)
+ gimp_filter_tool_remove_guide (filter_tool);
+ }
+ }
+ else if (! strcmp (pspec->name, "preview-split") &&
+ filter_tool->filter)
+ {
+ if (filter_options->preview_split)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpItem *item = GIMP_ITEM (tool->drawable);
+ gint x, y, width, height;
+ gint position;
+
+ gimp_display_shell_untransform_viewport (shell, TRUE,
+ &x, &y, &width, &height);
+
+ if (! gimp_rectangle_intersect (gimp_item_get_offset_x (item),
+ gimp_item_get_offset_y (item),
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ x, y, width, height,
+ &x, &y, &width, &height))
+ {
+ x = gimp_item_get_offset_x (item);
+ y = gimp_item_get_offset_y (item);
+ width = gimp_item_get_width (item);
+ height = gimp_item_get_height (item);
+ }
+
+ if (filter_options->preview_split_alignment == GIMP_ALIGN_LEFT ||
+ filter_options->preview_split_alignment == GIMP_ALIGN_RIGHT)
+ {
+ position = (x + width / 2) - gimp_item_get_offset_x (item);
+ }
+ else
+ {
+ position = (y + height / 2) - gimp_item_get_offset_y (item);
+ }
+
+ g_object_set (
+ options,
+ "preview-split-position", position,
+ NULL);
+ }
+
+ gimp_filter_tool_update_filter (filter_tool);
+
+ if (filter_options->preview_split)
+ gimp_filter_tool_add_guide (filter_tool);
+ else
+ gimp_filter_tool_remove_guide (filter_tool);
+ }
+ else if (! strcmp (pspec->name, "preview-split-alignment") ||
+ ! strcmp (pspec->name, "preview-split-position"))
+ {
+ gimp_filter_tool_update_filter (filter_tool);
+
+ if (filter_options->preview_split)
+ gimp_filter_tool_move_guide (filter_tool);
+ }
+ else if (! strcmp (pspec->name, "controller") &&
+ filter_tool->widget)
+ {
+ gimp_tool_widget_set_visible (filter_tool->widget,
+ filter_options->controller);
+ }
+}
+
+static gboolean
+gimp_filter_tool_can_pick_color (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (color_tool);
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (color_tool);
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (gimp_image_get_active_drawable (image) != tool->drawable)
+ return FALSE;
+
+ return filter_tool->pick_abyss ||
+ GIMP_COLOR_TOOL_CLASS (parent_class)->can_pick (color_tool,
+ coords, display);
+}
+
+static gboolean
+gimp_filter_tool_pick_color (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (color_tool);
+ gboolean picked;
+
+ picked = GIMP_COLOR_TOOL_CLASS (parent_class)->pick (color_tool, coords,
+ display, sample_format,
+ pixel, color);
+
+ if (! picked && filter_tool->pick_abyss)
+ {
+ color->r = 0.0;
+ color->g = 0.0;
+ color->b = 0.0;
+ color->a = 0.0;
+
+ picked = TRUE;
+ }
+
+ return picked;
+}
+
+static void
+gimp_filter_tool_color_picked (GimpColorTool *color_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display,
+ GimpColorPickState pick_state,
+ const Babl *sample_format,
+ gpointer pixel,
+ const GimpRGB *color)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (color_tool);
+
+ if (filter_tool->active_picker)
+ {
+ GimpPickerCallback callback;
+ gpointer callback_data;
+
+ callback = g_object_get_data (G_OBJECT (filter_tool->active_picker),
+ "picker-callback");
+ callback_data = g_object_get_data (G_OBJECT (filter_tool->active_picker),
+ "picker-callback-data");
+
+ if (callback)
+ {
+ callback (callback_data,
+ filter_tool->pick_identifier,
+ coords->x,
+ coords->y,
+ sample_format, color);
+
+ return;
+ }
+ }
+
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->color_picked (filter_tool,
+ filter_tool->pick_identifier,
+ coords->x,
+ coords->y,
+ sample_format, color);
+}
+
+static void
+gimp_filter_tool_real_reset (GimpFilterTool *filter_tool)
+{
+ if (filter_tool->config)
+ {
+ if (filter_tool->default_config)
+ {
+ gimp_config_copy (GIMP_CONFIG (filter_tool->default_config),
+ GIMP_CONFIG (filter_tool->config),
+ 0);
+ }
+ else
+ {
+ gimp_config_reset (GIMP_CONFIG (filter_tool->config));
+ }
+ }
+}
+
+static void
+gimp_filter_tool_real_set_config (GimpFilterTool *filter_tool,
+ GimpConfig *config)
+{
+ GimpFilterRegion region;
+
+ /* copy the "gimp-region" property first, to avoid incorrectly adjusting the
+ * copied operation properties in gimp_operation_tool_region_changed().
+ */
+ g_object_get (config,
+ "gimp-region", &region,
+ NULL);
+ g_object_set (filter_tool->config,
+ "gimp-region", region,
+ NULL);
+
+ gimp_config_copy (GIMP_CONFIG (config),
+ GIMP_CONFIG (filter_tool->config), 0);
+
+ /* reset the "time" property, otherwise explicitly storing the
+ * config as setting will also copy the time, and the stored object
+ * will be considered to be among the automatically stored recently
+ * used settings
+ */
+ g_object_set (filter_tool->config,
+ "time", (gint64) 0,
+ NULL);
+}
+
+static void
+gimp_filter_tool_real_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec)
+{
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+
+ if (filter_tool->filter)
+ {
+ /* note that we may be called with a NULL pspec. see
+ * gimp_operation_tool_aux_input_notify().
+ */
+ if (pspec)
+ {
+ if (! strcmp (pspec->name, "gimp-clip") ||
+ ! strcmp (pspec->name, "gimp-mode") ||
+ ! strcmp (pspec->name, "gimp-opacity") ||
+ ! strcmp (pspec->name, "gimp-color-managed") ||
+ ! strcmp (pspec->name, "gimp-gamma-hack"))
+ {
+ gimp_filter_tool_update_filter (filter_tool);
+ }
+ else if (! strcmp (pspec->name, "gimp-region"))
+ {
+ gimp_filter_tool_update_filter (filter_tool);
+
+ gimp_filter_tool_region_changed (filter_tool);
+ }
+ }
+
+ if (options->preview)
+ gimp_drawable_filter_apply (filter_tool->filter, NULL);
+ }
+}
+
+static void
+gimp_filter_tool_halt (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+
+ gimp_filter_tool_disable_color_picking (filter_tool);
+
+ if (tool->display)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ g_signal_handlers_disconnect_by_func (tool->drawable,
+ gimp_filter_tool_lock_position_changed,
+ filter_tool);
+
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_filter_tool_mask_changed,
+ filter_tool);
+ }
+
+ if (filter_tool->gui)
+ {
+ /* explicitly clear the dialog contents first, since we might be called
+ * during the dialog's delete event, in which case the dialog will be
+ * externally reffed, and will only die *after* gimp_filter_tool_halt()
+ * returns, and, in particular, after filter_tool->config has been
+ * cleared. we want to make sure the gui is destroyed while
+ * filter_tool->config is still alive, since the gui's destruction may
+ * fire signals whose handlers rely on it.
+ */
+ gimp_gtk_container_clear (
+ GTK_CONTAINER (gimp_filter_tool_dialog_get_vbox (filter_tool)));
+
+ g_clear_object (&filter_tool->gui);
+ filter_tool->settings_box = NULL;
+ filter_tool->controller_toggle = NULL;
+ filter_tool->clip_combo = NULL;
+ filter_tool->region_combo = NULL;
+ }
+
+ if (filter_tool->filter)
+ {
+ gimp_drawable_filter_abort (filter_tool->filter);
+ g_clear_object (&filter_tool->filter);
+
+ gimp_filter_tool_remove_guide (filter_tool);
+ }
+
+ g_clear_object (&filter_tool->operation);
+
+ if (filter_tool->config)
+ {
+ g_signal_handlers_disconnect_by_func (filter_tool->config,
+ gimp_filter_tool_config_notify,
+ filter_tool);
+ g_signal_handlers_disconnect_by_func (filter_tool->config,
+ gimp_filter_tool_unset_setting,
+ filter_tool);
+ g_clear_object (&filter_tool->config);
+ }
+
+ g_clear_object (&filter_tool->default_config);
+ g_clear_object (&filter_tool->settings);
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ gimp_filter_tool_set_widget (filter_tool, NULL);
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+}
+
+static void
+gimp_filter_tool_commit (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+
+ if (filter_tool->gui)
+ gimp_tool_gui_hide (filter_tool->gui);
+
+ if (filter_tool->filter)
+ {
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (tool);
+
+ if (! options->preview)
+ gimp_drawable_filter_apply (filter_tool->filter, NULL);
+
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_commit (filter_tool->filter,
+ GIMP_PROGRESS (tool), TRUE);
+ g_clear_object (&filter_tool->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_filter_tool_remove_guide (filter_tool);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+
+ if (filter_tool->config && filter_tool->has_settings)
+ {
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (tool->tool_info->gimp->config);
+
+ gimp_settings_box_add_current (GIMP_SETTINGS_BOX (filter_tool->settings_box),
+ config->filter_tool_max_recent);
+ }
+ }
+}
+
+static void
+gimp_filter_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->dialog (filter_tool);
+}
+
+static void
+gimp_filter_tool_update_dialog_operation_settings (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ if (filter_tool->operation_settings_box)
+ {
+ gimp_gtk_container_clear (
+ GTK_CONTAINER (filter_tool->operation_settings_box));
+
+ if (filter_tool->config)
+ {
+ GtkWidget *vbox;
+ GtkWidget *expander;
+ GtkWidget *frame;
+ GtkWidget *vbox2;
+ GtkWidget *combo;
+ GtkWidget *mode_box;
+ GtkWidget *scale;
+ GtkWidget *toggle;
+
+ vbox = filter_tool->operation_settings_box;
+
+ /* The clipping combo */
+ filter_tool->clip_combo =
+ gimp_prop_enum_combo_box_new (filter_tool->config,
+ "gimp-clip",
+ GIMP_TRANSFORM_RESIZE_ADJUST,
+ GIMP_TRANSFORM_RESIZE_CLIP);
+ gimp_int_combo_box_set_label (
+ GIMP_INT_COMBO_BOX (filter_tool->clip_combo), _("Clipping"));
+ gtk_box_pack_start (GTK_BOX (vbox), filter_tool->clip_combo,
+ FALSE, FALSE, 0);
+
+ /* The region combo */
+ filter_tool->region_combo =
+ gimp_prop_enum_combo_box_new (filter_tool->config,
+ "gimp-region",
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), filter_tool->region_combo,
+ FALSE, FALSE, 0);
+
+ /* The blending-options expander */
+ expander = gtk_expander_new (_("Blending Options"));
+ gtk_box_pack_start (GTK_BOX (vbox), expander,
+ FALSE, FALSE, 0);
+ gtk_widget_show (expander);
+
+ g_object_bind_property (options, "blending-options-expanded",
+ expander, "expanded",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ frame = gimp_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (expander), frame);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* The mode box */
+ mode_box = gimp_prop_layer_mode_box_new (
+ filter_tool->config, "gimp-mode",
+ GIMP_LAYER_MODE_CONTEXT_FILTER);
+ gimp_layer_mode_box_set_label (GIMP_LAYER_MODE_BOX (mode_box),
+ _("Mode"));
+ gtk_box_pack_start (GTK_BOX (vbox2), mode_box,
+ FALSE, FALSE, 0);
+ gtk_widget_show (mode_box);
+
+ /* The opacity scale */
+ scale = gimp_prop_spin_scale_new (filter_tool->config,
+ "gimp-opacity",
+ NULL,
+ 1.0, 10.0, 1);
+ gimp_prop_widget_set_factor (scale, 100.0, 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale,
+ FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* The Color Options expander */
+ expander = gtk_expander_new (_("Advanced Color Options"));
+ gtk_box_pack_start (GTK_BOX (vbox), expander,
+ FALSE, FALSE, 0);
+
+ g_object_bind_property (image->gimp->config,
+ "filter-tool-show-color-options",
+ expander, "visible",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (options, "color-options-expanded",
+ expander, "expanded",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ frame = gimp_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (expander), frame);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* The color managed combo */
+ combo = gimp_prop_boolean_combo_box_new (
+ filter_tool->config, "gimp-color-managed",
+ _("Convert pixels to built-in sRGB to apply filter (slow)"),
+ _("Assume pixels are built-in sRGB (ignore actual image color space)"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), combo,
+ FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ /* The gamma hack toggle */
+ toggle = gimp_prop_check_button_new (filter_tool->config,
+ "gimp-gamma-hack", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle,
+ FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+ }
+ }
+}
+
+static void
+gimp_filter_tool_reset (GimpFilterTool *filter_tool)
+{
+ if (filter_tool->config)
+ g_object_freeze_notify (filter_tool->config);
+
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->reset (filter_tool);
+
+ if (filter_tool->config)
+ g_object_thaw_notify (filter_tool->config);
+
+ if (filter_tool->widget)
+ gimp_filter_tool_reset_widget (filter_tool, filter_tool->widget);
+}
+
+static void
+gimp_filter_tool_create_filter (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+
+ if (filter_tool->filter)
+ {
+ gimp_drawable_filter_abort (filter_tool->filter);
+ g_object_unref (filter_tool->filter);
+ }
+
+ gimp_assert (filter_tool->operation);
+
+ filter_tool->filter = gimp_drawable_filter_new (tool->drawable,
+ gimp_tool_get_undo_desc (tool),
+ filter_tool->operation,
+ gimp_tool_get_icon_name (tool));
+
+ gimp_filter_tool_update_filter (filter_tool);
+
+ g_signal_connect (filter_tool->filter, "flush",
+ G_CALLBACK (gimp_filter_tool_flush),
+ filter_tool);
+
+ gimp_gegl_progress_connect (filter_tool->operation,
+ GIMP_PROGRESS (filter_tool),
+ gimp_tool_get_undo_desc (tool));
+
+ if (options->preview)
+ gimp_drawable_filter_apply (filter_tool->filter, NULL);
+}
+
+static void
+gimp_filter_tool_update_dialog (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+
+ if (filter_tool->gui)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+ GimpChannel *mask = gimp_image_get_mask (image);
+ const Babl *format;
+
+ if (filter_tool->filter)
+ format = gimp_drawable_filter_get_format (filter_tool->filter);
+ else
+ format = gimp_drawable_get_format (tool->drawable);
+
+ if (gimp_channel_is_empty (mask))
+ {
+ gtk_widget_set_visible (
+ filter_tool->clip_combo,
+ gimp_item_get_clip (GIMP_ITEM (tool->drawable), FALSE) == FALSE &&
+ ! gimp_gegl_node_is_point_operation (filter_tool->operation) &&
+ babl_format_has_alpha (format));
+
+ gtk_widget_hide (filter_tool->region_combo);
+ }
+ else
+ {
+ gtk_widget_hide (filter_tool->clip_combo);
+
+ gtk_widget_set_visible (
+ filter_tool->region_combo,
+ ! gimp_gegl_node_is_point_operation (filter_tool->operation) ||
+ gimp_gegl_node_has_key (filter_tool->operation,
+ "position-dependent"));
+ }
+ }
+}
+
+static void
+gimp_filter_tool_region_changed (GimpFilterTool *filter_tool)
+{
+ if (filter_tool->filter &&
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->region_changed)
+ {
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->region_changed (filter_tool);
+ }
+}
+
+static void
+gimp_filter_tool_flush (GimpDrawableFilter *filter,
+ GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_filter_tool_config_notify (GObject *object,
+ const GParamSpec *pspec,
+ GimpFilterTool *filter_tool)
+{
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->config_notify (filter_tool,
+ GIMP_CONFIG (object),
+ pspec);
+}
+
+static void
+gimp_filter_tool_unset_setting (GObject *object,
+ const GParamSpec *pspec,
+ GimpFilterTool *filter_tool)
+{
+ g_signal_handlers_disconnect_by_func (filter_tool->config,
+ gimp_filter_tool_unset_setting,
+ filter_tool);
+
+ gimp_settings_box_unset (GIMP_SETTINGS_BOX (filter_tool->settings_box));
+}
+
+static void
+gimp_filter_tool_lock_position_changed (GimpDrawable *drawable,
+ GimpFilterTool *filter_tool)
+{
+ gimp_filter_tool_update_dialog (filter_tool);
+}
+
+static void
+gimp_filter_tool_mask_changed (GimpImage *image,
+ GimpFilterTool *filter_tool)
+{
+ GimpOperationSettings *settings;
+
+ settings = GIMP_OPERATION_SETTINGS (filter_tool->config);
+
+ gimp_filter_tool_update_dialog (filter_tool);
+
+ if (settings && settings->region == GIMP_FILTER_REGION_SELECTION)
+ gimp_filter_tool_region_changed (filter_tool);
+}
+
+static void
+gimp_filter_tool_add_guide (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+ GimpItem *item;
+ GimpImage *image;
+ GimpOrientationType orientation;
+ gint position;
+
+ if (filter_tool->preview_guide)
+ return;
+
+ item = GIMP_ITEM (tool->drawable);
+ image = gimp_item_get_image (item);
+
+ if (options->preview_split_alignment == GIMP_ALIGN_LEFT ||
+ options->preview_split_alignment == GIMP_ALIGN_RIGHT)
+ {
+ orientation = GIMP_ORIENTATION_VERTICAL;
+ position = gimp_item_get_offset_x (item) +
+ options->preview_split_position;
+ }
+ else
+ {
+ orientation = GIMP_ORIENTATION_HORIZONTAL;
+ position = gimp_item_get_offset_y (item) +
+ options->preview_split_position;
+ }
+
+ filter_tool->preview_guide =
+ gimp_guide_custom_new (orientation,
+ image->gimp->next_guide_ID++,
+ GIMP_GUIDE_STYLE_SPLIT_VIEW);
+
+ gimp_image_add_guide (image, filter_tool->preview_guide, position);
+
+ g_signal_connect (filter_tool->preview_guide, "removed",
+ G_CALLBACK (gimp_filter_tool_guide_removed),
+ filter_tool);
+ g_signal_connect (filter_tool->preview_guide, "notify::position",
+ G_CALLBACK (gimp_filter_tool_guide_moved),
+ filter_tool);
+}
+
+static void
+gimp_filter_tool_remove_guide (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpImage *image;
+
+ if (! filter_tool->preview_guide)
+ return;
+
+ image = gimp_item_get_image (GIMP_ITEM (tool->drawable));
+
+ gimp_image_remove_guide (image, filter_tool->preview_guide, FALSE);
+}
+
+static void
+gimp_filter_tool_move_guide (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+ GimpItem *item;
+ GimpOrientationType orientation;
+ gint position;
+
+ if (! filter_tool->preview_guide)
+ return;
+
+ item = GIMP_ITEM (tool->drawable);
+
+ if (options->preview_split_alignment == GIMP_ALIGN_LEFT ||
+ options->preview_split_alignment == GIMP_ALIGN_RIGHT)
+ {
+ orientation = GIMP_ORIENTATION_VERTICAL;
+ position = gimp_item_get_offset_x (item) +
+ options->preview_split_position;
+ }
+ else
+ {
+ orientation = GIMP_ORIENTATION_HORIZONTAL;
+ position = gimp_item_get_offset_x (item) +
+ options->preview_split_position;
+ }
+
+ if (orientation != gimp_guide_get_orientation (filter_tool->preview_guide) ||
+ position != gimp_guide_get_position (filter_tool->preview_guide))
+ {
+ gimp_guide_set_orientation (filter_tool->preview_guide, orientation);
+ gimp_image_move_guide (gimp_item_get_image (item),
+ filter_tool->preview_guide, position, FALSE);
+ }
+}
+
+static void
+gimp_filter_tool_guide_removed (GimpGuide *guide,
+ GimpFilterTool *filter_tool)
+{
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (filter_tool->preview_guide),
+ gimp_filter_tool_guide_removed,
+ filter_tool);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (filter_tool->preview_guide),
+ gimp_filter_tool_guide_moved,
+ filter_tool);
+
+ g_clear_object (&filter_tool->preview_guide);
+
+ g_object_set (options,
+ "preview-split", FALSE,
+ NULL);
+}
+
+static void
+gimp_filter_tool_guide_moved (GimpGuide *guide,
+ const GParamSpec *pspec,
+ GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+ GimpItem *item = GIMP_ITEM (tool->drawable);
+ gint position;
+
+ if (options->preview_split_alignment == GIMP_ALIGN_LEFT ||
+ options->preview_split_alignment == GIMP_ALIGN_RIGHT)
+ {
+ position = CLAMP (gimp_guide_get_position (guide) -
+ gimp_item_get_offset_x (item),
+ 0, gimp_item_get_width (item));
+ }
+ else
+ {
+ position = CLAMP (gimp_guide_get_position (guide) -
+ gimp_item_get_offset_y (item),
+ 0, gimp_item_get_height (item));
+ }
+
+ g_object_set (options,
+ "preview-split-position", position,
+ NULL);
+}
+
+static void
+gimp_filter_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpFilterTool *filter_tool)
+{
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+
+ switch (response_id)
+ {
+ case RESPONSE_RESET:
+ gimp_filter_tool_reset (filter_tool);
+ break;
+
+ case GTK_RESPONSE_OK:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+ break;
+
+ default:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ break;
+ }
+}
+
+static void
+gimp_filter_tool_update_filter (GimpFilterTool *filter_tool)
+{
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+ GimpOperationSettings *settings;
+
+ if (! filter_tool->filter)
+ return;
+
+ settings = GIMP_OPERATION_SETTINGS (filter_tool->config);
+
+ gimp_drawable_filter_set_preview (filter_tool->filter,
+ options->preview);
+ gimp_drawable_filter_set_preview_split (filter_tool->filter,
+ options->preview_split,
+ options->preview_split_alignment,
+ options->preview_split_position);
+ gimp_drawable_filter_set_add_alpha (filter_tool->filter,
+ gimp_gegl_node_has_key (
+ filter_tool->operation,
+ "needs-alpha"));
+
+ gimp_operation_settings_sync_drawable_filter (settings, filter_tool->filter);
+}
+
+static void
+gimp_filter_tool_set_has_settings (GimpFilterTool *filter_tool,
+ gboolean has_settings)
+{
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+
+ filter_tool->has_settings = has_settings;
+
+ if (! filter_tool->settings_box)
+ return;
+
+ if (filter_tool->has_settings)
+ {
+ GimpTool *tool = GIMP_TOOL (filter_tool);
+ GQuark quark = g_quark_from_static_string ("settings-folder");
+ GType type = G_TYPE_FROM_INSTANCE (filter_tool->config);
+ GFile *settings_folder;
+ gchar *import_title;
+ gchar *export_title;
+
+ settings_folder = g_type_get_qdata (type, quark);
+
+ import_title = g_strdup_printf (_("Import '%s' Settings"),
+ gimp_tool_get_label (tool));
+ export_title = g_strdup_printf (_("Export '%s' Settings"),
+ gimp_tool_get_label (tool));
+
+ g_object_set (filter_tool->settings_box,
+ "visible", TRUE,
+ "config", filter_tool->config,
+ "container", filter_tool->settings,
+ "help-id", gimp_tool_get_help_id (tool),
+ "import-title", import_title,
+ "export-title", export_title,
+ "default-folder", settings_folder,
+ "last-file", NULL,
+ NULL);
+
+ g_free (import_title);
+ g_free (export_title);
+ }
+ else
+ {
+ g_object_set (filter_tool->settings_box,
+ "visible", FALSE,
+ "config", NULL,
+ "container", NULL,
+ "help-id", NULL,
+ "import-title", NULL,
+ "export-title", NULL,
+ "default-folder", NULL,
+ "last-file", NULL,
+ NULL);
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_filter_tool_get_operation (GimpFilterTool *filter_tool)
+{
+ GimpTool *tool;
+ GimpFilterToolClass *klass;
+ gchar *operation_name;
+ GParamSpec **pspecs;
+
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+
+ tool = GIMP_TOOL (filter_tool);
+ klass = GIMP_FILTER_TOOL_GET_CLASS (filter_tool);
+
+ if (filter_tool->filter)
+ {
+ gimp_drawable_filter_abort (filter_tool->filter);
+ g_clear_object (&filter_tool->filter);
+
+ gimp_filter_tool_remove_guide (filter_tool);
+ }
+
+ g_clear_object (&filter_tool->operation);
+
+ if (filter_tool->config)
+ {
+ g_signal_handlers_disconnect_by_func (filter_tool->config,
+ gimp_filter_tool_config_notify,
+ filter_tool);
+ g_signal_handlers_disconnect_by_func (filter_tool->config,
+ gimp_filter_tool_unset_setting,
+ filter_tool);
+ g_clear_object (&filter_tool->config);
+ }
+
+ g_clear_object (&filter_tool->default_config);
+ g_clear_object (&filter_tool->settings);
+ g_clear_pointer (&filter_tool->description, g_free);
+
+ operation_name = klass->get_operation (filter_tool,
+ &filter_tool->description);
+
+ if (! operation_name)
+ operation_name = g_strdup ("gegl:nop");
+
+ if (! filter_tool->description)
+ filter_tool->description = g_strdup (gimp_tool_get_label (tool));
+
+ filter_tool->operation = gegl_node_new_child (NULL,
+ "operation", operation_name,
+ NULL);
+
+ filter_tool->config =
+ g_object_new (gimp_operation_config_get_type (tool->tool_info->gimp,
+ operation_name,
+ gimp_tool_get_icon_name (tool),
+ GIMP_TYPE_OPERATION_SETTINGS),
+ NULL);
+
+ gimp_operation_config_sync_node (filter_tool->config,
+ filter_tool->operation);
+ gimp_operation_config_connect_node (filter_tool->config,
+ filter_tool->operation);
+
+ filter_tool->settings =
+ gimp_operation_config_get_container (tool->tool_info->gimp,
+ G_TYPE_FROM_INSTANCE (filter_tool->config),
+ (GCompareFunc) gimp_settings_compare);
+ g_object_ref (filter_tool->settings);
+
+ pspecs =
+ gimp_operation_config_list_properties (filter_tool->config,
+ G_TYPE_FROM_INSTANCE (filter_tool->config),
+ 0, NULL);
+
+ gimp_filter_tool_set_has_settings (filter_tool, (pspecs != NULL));
+
+ g_free (pspecs);
+
+ if (filter_tool->gui)
+ {
+ gimp_tool_gui_set_title (filter_tool->gui,
+ gimp_tool_get_label (tool));
+ gimp_tool_gui_set_description (filter_tool->gui, filter_tool->description);
+ gimp_tool_gui_set_icon_name (filter_tool->gui,
+ gimp_tool_get_icon_name (tool));
+ gimp_tool_gui_set_help_id (filter_tool->gui,
+ gimp_tool_get_help_id (tool));
+
+ gimp_filter_tool_update_dialog_operation_settings (filter_tool);
+ }
+
+ gimp_filter_tool_update_dialog (filter_tool);
+
+ g_free (operation_name);
+
+ g_object_set (GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool),
+ "preview-split", FALSE,
+ NULL);
+
+ g_signal_connect_object (filter_tool->config, "notify",
+ G_CALLBACK (gimp_filter_tool_config_notify),
+ G_OBJECT (filter_tool), 0);
+
+ if (tool->drawable)
+ gimp_filter_tool_create_filter (filter_tool);
+}
+
+void
+gimp_filter_tool_set_config (GimpFilterTool *filter_tool,
+ GimpConfig *config)
+{
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+ g_return_if_fail (GIMP_IS_OPERATION_SETTINGS (config));
+
+ /* if the user didn't change a setting since the last set_config(),
+ * this handler is still connected
+ */
+ g_signal_handlers_disconnect_by_func (filter_tool->config,
+ gimp_filter_tool_unset_setting,
+ filter_tool);
+
+ GIMP_FILTER_TOOL_GET_CLASS (filter_tool)->set_config (filter_tool, config);
+
+ if (filter_tool->widget)
+ gimp_filter_tool_reset_widget (filter_tool, filter_tool->widget);
+
+ if (filter_tool->settings_box)
+ g_signal_connect_object (filter_tool->config, "notify",
+ G_CALLBACK (gimp_filter_tool_unset_setting),
+ G_OBJECT (filter_tool), 0);
+}
+
+void
+gimp_filter_tool_edit_as (GimpFilterTool *filter_tool,
+ const gchar *new_tool_id,
+ GimpConfig *config)
+{
+ GimpDisplay *display;
+ GimpContext *user_context;
+ GimpToolInfo *tool_info;
+ GimpTool *new_tool;
+
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+ g_return_if_fail (new_tool_id != NULL);
+ g_return_if_fail (GIMP_IS_CONFIG (config));
+
+ display = GIMP_TOOL (filter_tool)->display;
+
+ user_context = gimp_get_user_context (display->gimp);
+
+ tool_info = (GimpToolInfo *)
+ gimp_container_get_child_by_name (display->gimp->tool_info_list,
+ new_tool_id);
+
+ gimp_tool_control (GIMP_TOOL (filter_tool), GIMP_TOOL_ACTION_HALT, display);
+ gimp_context_set_tool (user_context, tool_info);
+ tool_manager_initialize_active (display->gimp, display);
+
+ new_tool = tool_manager_get_active (display->gimp);
+
+ GIMP_FILTER_TOOL (new_tool)->default_config = g_object_ref (G_OBJECT (config));
+
+ gimp_filter_tool_reset (GIMP_FILTER_TOOL (new_tool));
+}
+
+gboolean
+gimp_filter_tool_on_guide (GimpFilterTool *filter_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display)
+{
+ GimpDisplayShell *shell;
+
+ g_return_val_if_fail (GIMP_IS_FILTER_TOOL (filter_tool), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ shell = gimp_display_get_shell (display);
+
+ if (filter_tool->filter &&
+ filter_tool->preview_guide &&
+ gimp_display_shell_get_show_guides (shell))
+ {
+ const gint snap_distance = display->config->snap_distance;
+ GimpOrientationType orientation;
+ gint position;
+
+ orientation = gimp_guide_get_orientation (filter_tool->preview_guide);
+ position = gimp_guide_get_position (filter_tool->preview_guide);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ if (fabs (coords->y - position) <= FUNSCALEY (shell, snap_distance))
+ return TRUE;
+ }
+ else
+ {
+ if (fabs (coords->x - position) <= FUNSCALEX (shell, snap_distance))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+GtkWidget *
+gimp_filter_tool_dialog_get_vbox (GimpFilterTool *filter_tool)
+{
+ g_return_val_if_fail (GIMP_IS_FILTER_TOOL (filter_tool), NULL);
+
+ return gimp_tool_gui_get_vbox (filter_tool->gui);
+}
+
+void
+gimp_filter_tool_enable_color_picking (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gboolean pick_abyss)
+{
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+
+ gimp_filter_tool_disable_color_picking (filter_tool);
+
+ /* note that ownership over 'identifier' is not transferred, and its
+ * lifetime should be managed by the caller.
+ */
+ filter_tool->pick_identifier = identifier;
+ filter_tool->pick_abyss = pick_abyss;
+
+ gimp_color_tool_enable (GIMP_COLOR_TOOL (filter_tool),
+ GIMP_COLOR_TOOL_GET_OPTIONS (filter_tool));
+}
+
+void
+gimp_filter_tool_disable_color_picking (GimpFilterTool *filter_tool)
+{
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+
+ if (filter_tool->active_picker)
+ {
+ GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (filter_tool->active_picker);
+
+ filter_tool->active_picker = NULL;
+
+ gtk_toggle_button_set_active (toggle, FALSE);
+ }
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (filter_tool)))
+ gimp_color_tool_disable (GIMP_COLOR_TOOL (filter_tool));
+}
+
+static void
+gimp_filter_tool_color_picker_toggled (GtkWidget *widget,
+ GimpFilterTool *filter_tool)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+ {
+ gpointer identifier;
+ gboolean pick_abyss;
+
+ if (filter_tool->active_picker == widget)
+ return;
+
+ identifier = g_object_get_data (G_OBJECT (widget),
+ "picker-identifier");
+ pick_abyss = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
+ "picker-pick-abyss"));
+
+ gimp_filter_tool_enable_color_picking (filter_tool,
+ identifier, pick_abyss);
+
+ filter_tool->active_picker = widget;
+ }
+ else if (filter_tool->active_picker == widget)
+ {
+ gimp_filter_tool_disable_color_picking (filter_tool);
+ }
+}
+
+GtkWidget *
+gimp_filter_tool_add_color_picker (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ const gchar *icon_name,
+ const gchar *tooltip,
+ gboolean pick_abyss,
+ GimpPickerCallback callback,
+ gpointer callback_data)
+{
+ GtkWidget *button;
+ GtkWidget *image;
+
+ g_return_val_if_fail (GIMP_IS_FILTER_TOOL (filter_tool), NULL);
+ g_return_val_if_fail (icon_name != NULL, NULL);
+
+ button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
+ "draw-indicator", FALSE,
+ NULL);
+
+ image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
+ gtk_misc_set_padding (GTK_MISC (image), 2, 2);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show (image);
+
+ if (tooltip)
+ gimp_help_set_help_data (button, tooltip, NULL);
+
+ g_object_set_data (G_OBJECT (button),
+ "picker-identifier", identifier);
+ g_object_set_data (G_OBJECT (button),
+ "picker-pick-abyss", GINT_TO_POINTER (pick_abyss));
+ g_object_set_data (G_OBJECT (button),
+ "picker-callback", callback);
+ g_object_set_data (G_OBJECT (button),
+ "picker-callback-data", callback_data);
+
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (gimp_filter_tool_color_picker_toggled),
+ filter_tool);
+
+ return button;
+}
+
+GCallback
+gimp_filter_tool_add_controller (GimpFilterTool *filter_tool,
+ GimpControllerType controller_type,
+ const gchar *status_title,
+ GCallback callback,
+ gpointer callback_data,
+ gpointer *set_func_data)
+{
+ GimpToolWidget *widget;
+ GCallback set_func;
+
+ g_return_val_if_fail (GIMP_IS_FILTER_TOOL (filter_tool), NULL);
+ g_return_val_if_fail (callback != NULL, NULL);
+ g_return_val_if_fail (callback_data != NULL, NULL);
+ g_return_val_if_fail (set_func_data != NULL, NULL);
+
+ widget = gimp_filter_tool_create_widget (filter_tool,
+ controller_type,
+ status_title,
+ callback,
+ callback_data,
+ &set_func,
+ set_func_data);
+ gimp_filter_tool_set_widget (filter_tool, widget);
+ g_object_unref (widget);
+
+ return set_func;
+}
+
+void
+gimp_filter_tool_set_widget (GimpFilterTool *filter_tool,
+ GimpToolWidget *widget)
+{
+ g_return_if_fail (GIMP_IS_FILTER_TOOL (filter_tool));
+ g_return_if_fail (widget == NULL || GIMP_IS_TOOL_WIDGET (widget));
+
+ if (widget == filter_tool->widget)
+ return;
+
+ if (filter_tool->widget)
+ {
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (filter_tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (filter_tool));
+
+ g_object_unref (filter_tool->widget);
+ }
+
+ filter_tool->widget = widget;
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (filter_tool), widget);
+
+ if (filter_tool->widget)
+ {
+ GimpFilterOptions *options = GIMP_FILTER_TOOL_GET_OPTIONS (filter_tool);
+
+ g_object_ref (filter_tool->widget);
+
+ gimp_tool_widget_set_visible (filter_tool->widget,
+ options->controller);
+
+ if (GIMP_TOOL (filter_tool)->display)
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (filter_tool),
+ GIMP_TOOL (filter_tool)->display);
+ }
+
+ if (filter_tool->controller_toggle)
+ {
+ gtk_widget_set_visible (filter_tool->controller_toggle,
+ filter_tool->widget != NULL);
+ }
+}
+
+gboolean
+gimp_filter_tool_get_drawable_area (GimpFilterTool *filter_tool,
+ gint *drawable_offset_x,
+ gint *drawable_offset_y,
+ GeglRectangle *drawable_area)
+{
+ GimpTool *tool;
+ GimpOperationSettings *settings;
+ GimpDrawable *drawable;
+
+ g_return_val_if_fail (GIMP_IS_FILTER_TOOL (filter_tool), FALSE);
+ g_return_val_if_fail (drawable_offset_x != NULL, FALSE);
+ g_return_val_if_fail (drawable_offset_y != NULL, FALSE);
+ g_return_val_if_fail (drawable_area != NULL, FALSE);
+
+ tool = GIMP_TOOL (filter_tool);
+ settings = GIMP_OPERATION_SETTINGS (filter_tool->config);
+
+ *drawable_offset_x = 0;
+ *drawable_offset_y = 0;
+
+ drawable_area->x = 0;
+ drawable_area->y = 0;
+ drawable_area->width = 1;
+ drawable_area->height = 1;
+
+ drawable = tool->drawable;
+
+ if (drawable && settings)
+ {
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ drawable_offset_x, drawable_offset_y);
+
+ switch (settings->region)
+ {
+ case GIMP_FILTER_REGION_SELECTION:
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &drawable_area->x,
+ &drawable_area->y,
+ &drawable_area->width,
+ &drawable_area->height))
+ {
+ drawable_area->x = 0;
+ drawable_area->y = 0;
+ drawable_area->width = 1;
+ drawable_area->height = 1;
+ }
+ break;
+
+ case GIMP_FILTER_REGION_DRAWABLE:
+ drawable_area->width = gimp_item_get_width (GIMP_ITEM (drawable));
+ drawable_area->height = gimp_item_get_height (GIMP_ITEM (drawable));
+ break;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/app/tools/gimpfiltertool.h b/app/tools/gimpfiltertool.h
new file mode 100644
index 0000000..b1c91cb
--- /dev/null
+++ b/app/tools/gimpfiltertool.h
@@ -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/>.
+ */
+
+#ifndef __GIMP_FILTER_TOOL_H__
+#define __GIMP_FILTER_TOOL_H__
+
+
+#include "gimpcolortool.h"
+
+
+#define GIMP_TYPE_FILTER_TOOL (gimp_filter_tool_get_type ())
+#define GIMP_FILTER_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILTER_TOOL, GimpFilterTool))
+#define GIMP_FILTER_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILTER_TOOL, GimpFilterToolClass))
+#define GIMP_IS_FILTER_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILTER_TOOL))
+#define GIMP_IS_FILTER_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILTER_TOOL))
+#define GIMP_FILTER_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILTER_TOOL, GimpFilterToolClass))
+
+#define GIMP_FILTER_TOOL_GET_OPTIONS(t) (GIMP_FILTER_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpFilterToolClass GimpFilterToolClass;
+
+struct _GimpFilterTool
+{
+ GimpColorTool parent_instance;
+
+ GeglNode *operation;
+ GObject *config;
+ GObject *default_config;
+ GimpContainer *settings;
+
+ gchar *description;
+
+ gboolean has_settings;
+
+ GimpDrawableFilter *filter;
+
+ GimpGuide *preview_guide;
+
+ gpointer pick_identifier;
+ gboolean pick_abyss;
+
+ /* dialog */
+ gboolean overlay;
+ GimpToolGui *gui;
+ GtkWidget *settings_box;
+ GtkWidget *controller_toggle;
+ GtkWidget *operation_settings_box;
+ GtkWidget *clip_combo;
+ GtkWidget *region_combo;
+ GtkWidget *active_picker;
+
+ /* widget */
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+};
+
+struct _GimpFilterToolClass
+{
+ GimpColorToolClass parent_class;
+
+ /* virtual functions */
+ gchar * (* get_operation) (GimpFilterTool *filter_tool,
+ gchar **description);
+ void (* dialog) (GimpFilterTool *filter_tool);
+ void (* reset) (GimpFilterTool *filter_tool);
+ void (* set_config) (GimpFilterTool *filter_tool,
+ GimpConfig *config);
+ void (* config_notify) (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec);
+
+ gboolean (* settings_import) (GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error);
+ gboolean (* settings_export) (GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error);
+
+ void (* region_changed) (GimpFilterTool *filter_tool);
+ void (* color_picked) (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color);
+};
+
+
+GType gimp_filter_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_filter_tool_get_operation (GimpFilterTool *filter_tool);
+
+void gimp_filter_tool_set_config (GimpFilterTool *filter_tool,
+ GimpConfig *config);
+
+void gimp_filter_tool_edit_as (GimpFilterTool *filter_tool,
+ const gchar *new_tool_id,
+ GimpConfig *config);
+
+gboolean gimp_filter_tool_on_guide (GimpFilterTool *filter_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+
+GtkWidget * gimp_filter_tool_dialog_get_vbox (GimpFilterTool *filter_tool);
+
+void gimp_filter_tool_enable_color_picking (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gboolean pick_abyss);
+void gimp_filter_tool_disable_color_picking (GimpFilterTool *filter_tool);
+
+GtkWidget * gimp_filter_tool_add_color_picker (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ const gchar *icon_name,
+ const gchar *tooltip,
+ gboolean pick_abyss,
+ GimpPickerCallback callback,
+ gpointer callback_data);
+GCallback gimp_filter_tool_add_controller (GimpFilterTool *filter_tool,
+ GimpControllerType controller_type,
+ const gchar *status_title,
+ GCallback callback,
+ gpointer callback_data,
+ gpointer *set_func_data);
+
+void gimp_filter_tool_set_widget (GimpFilterTool *filter_tool,
+ GimpToolWidget *widget);
+
+gboolean gimp_filter_tool_get_drawable_area (GimpFilterTool *filter_tool,
+ gint *drawable_offset_x,
+ gint *drawable_offset_y,
+ GeglRectangle *drawable_area);
+
+
+#endif /* __GIMP_FILTER_TOOL_H__ */
diff --git a/app/tools/gimpflipoptions.c b/app/tools/gimpflipoptions.c
new file mode 100644
index 0000000..6b9eb2d
--- /dev/null
+++ b/app/tools/gimpflipoptions.c
@@ -0,0 +1,164 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpflipoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_FLIP_TYPE
+};
+
+
+static void gimp_flip_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_flip_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpFlipOptions, gimp_flip_options,
+ GIMP_TYPE_TRANSFORM_OPTIONS)
+
+
+static void
+gimp_flip_options_class_init (GimpFlipOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_flip_options_set_property;
+ object_class->get_property = gimp_flip_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FLIP_TYPE,
+ "flip-type",
+ _("Flip Type"),
+ _("Direction of flipping"),
+ GIMP_TYPE_ORIENTATION_TYPE,
+ GIMP_ORIENTATION_HORIZONTAL,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_flip_options_init (GimpFlipOptions *options)
+{
+}
+
+static void
+gimp_flip_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFlipOptions *options = GIMP_FLIP_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_FLIP_TYPE:
+ options->flip_type = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_flip_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFlipOptions *options = GIMP_FLIP_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_FLIP_TYPE:
+ g_value_set_enum (value, options->flip_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_flip_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpFlipOptions *options = GIMP_FLIP_OPTIONS (tool_options);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_OPTIONS (tool_options);
+ GtkWidget *vbox;
+ GtkWidget *frame;
+ GtkWidget *combo;
+ gchar *str;
+ GtkListStore *clip_model;
+ GdkModifierType toggle_mask;
+
+ vbox = gimp_transform_options_gui (tool_options, FALSE, FALSE, FALSE);
+
+ toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ /* tool toggle */
+ str = g_strdup_printf (_("Direction (%s)"),
+ gimp_get_mod_string (toggle_mask));
+
+ frame = gimp_prop_enum_radio_frame_new (config, "flip-type",
+ str,
+ GIMP_ORIENTATION_HORIZONTAL,
+ GIMP_ORIENTATION_VERTICAL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ g_free (str);
+
+ options->direction_frame = frame;
+
+ /* the clipping menu */
+ clip_model = gimp_enum_store_new_with_range (GIMP_TYPE_TRANSFORM_RESIZE,
+ GIMP_TRANSFORM_RESIZE_ADJUST,
+ GIMP_TRANSFORM_RESIZE_CLIP);
+
+ combo = gimp_prop_enum_combo_box_new (config, "clip", 0, 0);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (clip_model));
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), tr_options->clip);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Clipping"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ g_object_unref (clip_model);
+
+ return vbox;
+}
diff --git a/app/tools/gimpflipoptions.h b/app/tools/gimpflipoptions.h
new file mode 100644
index 0000000..1191054
--- /dev/null
+++ b/app/tools/gimpflipoptions.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_FLIP_OPTIONS_H__
+#define __GIMP_FLIP_OPTIONS_H__
+
+
+#include "gimptransformoptions.h"
+
+
+#define GIMP_TYPE_FLIP_OPTIONS (gimp_flip_options_get_type ())
+#define GIMP_FLIP_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FLIP_OPTIONS, GimpFlipOptions))
+#define GIMP_FLIP_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FLIP_OPTIONS, GimpFlipOptionsClass))
+#define GIMP_IS_FLIP_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FLIP_OPTIONS))
+#define GIMP_IS_FLIP_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FLIP_OPTIONS))
+#define GIMP_FLIP_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FLIP_OPTIONS, GimpFlipOptionsClass))
+
+
+typedef struct _GimpFlipOptions GimpFlipOptions;
+typedef struct _GimpToolOptionsClass GimpFlipOptionsClass;
+
+struct _GimpFlipOptions
+{
+ GimpTransformOptions parent_instance;
+
+ GimpOrientationType flip_type;
+
+ /* options gui */
+ GtkWidget *direction_frame;
+};
+
+
+GType gimp_flip_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_flip_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_FLIP_OPTIONS_H__ */
diff --git a/app/tools/gimpfliptool.c b/app/tools/gimpfliptool.c
new file mode 100644
index 0000000..8714b61
--- /dev/null
+++ b/app/tools/gimpfliptool.c
@@ -0,0 +1,460 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimpdrawable-transform.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-flip.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpitem-linked.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimppickable.h"
+#include "core/gimpprogress.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+
+#include "gimpflipoptions.h"
+#include "gimpfliptool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_flip_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_flip_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_flip_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_flip_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_flip_tool_draw (GimpDrawTool *draw_tool);
+
+static gchar * gimp_flip_tool_get_undo_desc (GimpTransformTool *tr_tool);
+static GeglBuffer * gimp_flip_tool_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+static GimpOrientationType gimp_flip_tool_get_flip_type (GimpFlipTool *flip);
+
+
+G_DEFINE_TYPE (GimpFlipTool, gimp_flip_tool, GIMP_TYPE_TRANSFORM_TOOL)
+
+#define parent_class gimp_flip_tool_parent_class
+
+
+void
+gimp_flip_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_FLIP_TOOL,
+ GIMP_TYPE_FLIP_OPTIONS,
+ gimp_flip_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-flip-tool",
+ _("Flip"),
+ _("Flip Tool: "
+ "Reverse the layer, selection or path horizontally or vertically"),
+ N_("_Flip"), "<shift>F",
+ NULL, GIMP_HELP_TOOL_FLIP,
+ GIMP_ICON_TOOL_FLIP,
+ data);
+}
+
+static void
+gimp_flip_tool_class_init (GimpFlipToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+
+ tool_class->button_press = gimp_flip_tool_button_press;
+ tool_class->modifier_key = gimp_flip_tool_modifier_key;
+ tool_class->oper_update = gimp_flip_tool_oper_update;
+ tool_class->cursor_update = gimp_flip_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_flip_tool_draw;
+
+ tr_class->get_undo_desc = gimp_flip_tool_get_undo_desc;
+ tr_class->transform = gimp_flip_tool_transform;
+
+ tr_class->undo_desc = C_("undo-type", "Flip");
+ tr_class->progress_text = _("Flipping");
+}
+
+static void
+gimp_flip_tool_init (GimpFlipTool *flip_tool)
+{
+ GimpTool *tool = GIMP_TOOL (flip_tool);
+
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+ gimp_tool_control_set_cursor (tool->control, GIMP_CURSOR_MOUSE);
+ gimp_tool_control_set_toggle_cursor (tool->control, GIMP_CURSOR_MOUSE);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FLIP_HORIZONTAL);
+ gimp_tool_control_set_toggle_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FLIP_VERTICAL);
+
+ flip_tool->guide = NULL;
+}
+
+static void
+gimp_flip_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+
+ tool->display = display;
+
+ gimp_transform_tool_transform (tr_tool, display);
+
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+}
+
+static void
+gimp_flip_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpFlipOptions *options = GIMP_FLIP_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_toggle_behavior_mask ())
+ {
+ switch (options->flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ g_object_set (options,
+ "flip-type", GIMP_ORIENTATION_VERTICAL,
+ NULL);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ g_object_set (options,
+ "flip-type", GIMP_ORIENTATION_HORIZONTAL,
+ NULL);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static void
+gimp_flip_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpFlipTool *flip = GIMP_FLIP_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ GimpFlipOptions *options = GIMP_FLIP_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpGuide *guide = NULL;
+
+ if (gimp_display_shell_get_show_guides (shell) &&
+ proximity)
+ {
+ gint snap_distance = display->config->snap_distance;
+
+ guide = gimp_image_pick_guide (image, coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance));
+ }
+
+ if (flip->guide != guide ||
+ (guide && ! gimp_draw_tool_is_active (draw_tool)))
+ {
+ gimp_draw_tool_pause (draw_tool);
+
+ if (gimp_draw_tool_is_active (draw_tool) &&
+ draw_tool->display != display)
+ gimp_draw_tool_stop (draw_tool);
+
+ flip->guide = guide;
+
+ if (! gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_start (draw_tool, display);
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+
+ gtk_widget_set_sensitive (options->direction_frame, guide == NULL);
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool,
+ coords, state, proximity,
+ display);
+}
+
+static void
+gimp_flip_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpFlipTool *flip = GIMP_FLIP_TOOL (tool);
+
+ if (! gimp_transform_tool_check_active_object (tr_tool, display, NULL))
+ {
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ return;
+ }
+
+ gimp_tool_control_set_toggled (tool->control,
+ gimp_flip_tool_get_flip_type (flip) ==
+ GIMP_ORIENTATION_VERTICAL);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_flip_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpFlipTool *flip = GIMP_FLIP_TOOL (draw_tool);
+
+ if (flip->guide)
+ {
+ GimpCanvasItem *item;
+ GimpGuideStyle style;
+
+ style = gimp_guide_get_style (flip->guide);
+
+ item = gimp_draw_tool_add_guide (draw_tool,
+ gimp_guide_get_orientation (flip->guide),
+ gimp_guide_get_position (flip->guide),
+ style);
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+}
+
+static gchar *
+gimp_flip_tool_get_undo_desc (GimpTransformTool *tr_tool)
+{
+ GimpFlipTool *flip = GIMP_FLIP_TOOL (tr_tool);
+
+ switch (gimp_flip_tool_get_flip_type (flip))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ return g_strdup (C_("undo-type", "Flip horizontally"));
+
+ case GIMP_ORIENTATION_VERTICAL:
+ return g_strdup (C_("undo-type", "Flip vertically"));
+
+ default:
+ /* probably this is not actually reached today, but
+ * could be if someone defined FLIP_DIAGONAL, say...
+ */
+ return GIMP_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc (tr_tool);
+ }
+}
+
+static GeglBuffer *
+gimp_flip_tool_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ GimpFlipTool *flip = GIMP_FLIP_TOOL (tr_tool);
+ GimpFlipOptions *options = GIMP_FLIP_TOOL_GET_OPTIONS (tr_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+ GimpOrientationType flip_type = GIMP_ORIENTATION_UNKNOWN;
+ gdouble axis = 0.0;
+ gboolean clip_result = FALSE;
+ GeglBuffer *ret = NULL;
+
+ flip_type = gimp_flip_tool_get_flip_type (flip);
+
+ if (flip->guide)
+ {
+ axis = gimp_guide_get_position (flip->guide);
+ }
+ else
+ {
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ axis = ((gdouble) tr_tool->x1 +
+ (gdouble) (tr_tool->x2 - tr_tool->x1) / 2.0);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ axis = ((gdouble) tr_tool->y1 +
+ (gdouble) (tr_tool->y2 - tr_tool->y1) / 2.0);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ switch (tr_options->clip)
+ {
+ case GIMP_TRANSFORM_RESIZE_ADJUST:
+ clip_result = FALSE;
+ break;
+
+ case GIMP_TRANSFORM_RESIZE_CLIP:
+ clip_result = TRUE;
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ if (orig_buffer)
+ {
+ /* this happens when transforming a selection cut out of a
+ * normal drawable
+ */
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (object), NULL);
+
+ ret = gimp_drawable_transform_buffer_flip (GIMP_DRAWABLE (object),
+ context,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ flip_type, axis,
+ clip_result,
+ buffer_profile,
+ new_offset_x,
+ new_offset_y);
+ }
+ else if (GIMP_IS_ITEM (object))
+ {
+ /* this happens for entire drawables, paths and layer groups */
+
+ GimpItem *item = GIMP_ITEM (object);
+
+ if (gimp_item_get_linked (item))
+ {
+ gimp_item_linked_flip (item, context,
+ flip_type, axis, clip_result);
+ }
+ else
+ {
+ clip_result = gimp_item_get_clip (item, clip_result);
+
+ gimp_item_flip (item, context,
+ flip_type, axis, clip_result);
+ }
+ }
+ else
+ {
+ /* this happens for images */
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool);
+ GimpProgress *progress;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (object), NULL);
+
+ progress = gimp_progress_start (GIMP_PROGRESS (tr_tool), FALSE,
+ "%s", tr_class->progress_text);
+
+ gimp_image_flip_full (GIMP_IMAGE (object), context,
+ flip_type, axis, clip_result,
+ progress);
+
+ if (progress)
+ gimp_progress_end (progress);
+ }
+
+ return ret;
+}
+
+static GimpOrientationType
+gimp_flip_tool_get_flip_type (GimpFlipTool *flip)
+{
+ GimpFlipOptions *options = GIMP_FLIP_TOOL_GET_OPTIONS (flip);
+
+ if (flip->guide)
+ {
+ switch (gimp_guide_get_orientation (flip->guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ return GIMP_ORIENTATION_VERTICAL;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ return GIMP_ORIENTATION_HORIZONTAL;
+
+ default:
+ return gimp_guide_get_orientation (flip->guide);
+ }
+ }
+ else
+ {
+ return options->flip_type;
+ }
+}
diff --git a/app/tools/gimpfliptool.h b/app/tools/gimpfliptool.h
new file mode 100644
index 0000000..4415863
--- /dev/null
+++ b/app/tools/gimpfliptool.h
@@ -0,0 +1,57 @@
+/* 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_FLIP_TOOL_H__
+#define __GIMP_FLIP_TOOL_H__
+
+
+#include "gimptransformtool.h"
+
+
+#define GIMP_TYPE_FLIP_TOOL (gimp_flip_tool_get_type ())
+#define GIMP_FLIP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FLIP_TOOL, GimpFlipTool))
+#define GIMP_FLIP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FLIP_TOOL, GimpFlipToolClass))
+#define GIMP_IS_FLIP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FLIP_TOOL))
+#define GIMP_IS_FLIP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FLIP_TOOL))
+#define GIMP_FLIP_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FLIP_TOOL, GimpFlipToolClass))
+
+#define GIMP_FLIP_TOOL_GET_OPTIONS(t) (GIMP_FLIP_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpFlipTool GimpFlipTool;
+typedef struct _GimpFlipToolClass GimpFlipToolClass;
+
+struct _GimpFlipTool
+{
+ GimpTransformTool parent_instance;
+
+ GimpGuide *guide;
+};
+
+struct _GimpFlipToolClass
+{
+ GimpTransformToolClass parent_class;
+};
+
+
+void gimp_flip_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_flip_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_FLIP_TOOL_H__ */
diff --git a/app/tools/gimpforegroundselectoptions.c b/app/tools/gimpforegroundselectoptions.c
new file mode 100644
index 0000000..8dcf6eb
--- /dev/null
+++ b/app/tools/gimpforegroundselectoptions.c
@@ -0,0 +1,395 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimpcolorpanel.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpwidgets-constructors.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpforegroundselectoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+/*
+ * for matting-global: iterations int
+ * for matting-levin: levels int, active levels int
+ */
+
+enum
+{
+ PROP_0,
+ PROP_DRAW_MODE,
+ PROP_PREVIEW_MODE,
+ PROP_STROKE_WIDTH,
+ PROP_MASK_COLOR,
+ PROP_ENGINE,
+ PROP_ITERATIONS,
+ PROP_LEVELS,
+ PROP_ACTIVE_LEVELS
+};
+
+
+static void gimp_foreground_select_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_foreground_select_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpForegroundSelectOptions, gimp_foreground_select_options,
+ GIMP_TYPE_SELECTION_OPTIONS)
+
+
+static void
+gimp_foreground_select_options_class_init (GimpForegroundSelectOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpRGB blue = {0.0, 0.0, 1.0, 0.5};
+
+ object_class->set_property = gimp_foreground_select_options_set_property;
+ object_class->get_property = gimp_foreground_select_options_get_property;
+
+ /* override the antialias default value from GimpSelectionOptions */
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_DRAW_MODE,
+ "draw-mode",
+ _("Draw Mode"),
+ _("Paint over areas to mark color values for "
+ "inclusion or exclusion from selection"),
+ GIMP_TYPE_MATTING_DRAW_MODE,
+ GIMP_MATTING_DRAW_MODE_FOREGROUND,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_PREVIEW_MODE,
+ "preview-mode",
+ _("Preview Mode"),
+ _("Preview Mode"),
+ GIMP_TYPE_MATTING_PREVIEW_MODE,
+ GIMP_MATTING_PREVIEW_MODE_ON_COLOR,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_STROKE_WIDTH,
+ "stroke-width",
+ _("Stroke width"),
+ _("Size of the brush used for refinements"),
+ 1, 6000, 10,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_RGB (object_class, PROP_MASK_COLOR,
+ "mask-color",
+ _("Preview color"),
+ _("Color of selection preview mask"),
+ GIMP_TYPE_RGB,
+ &blue,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ENGINE,
+ "engine",
+ _("Engine"),
+ _("Matting engine to use"),
+ GIMP_TYPE_MATTING_ENGINE,
+ GIMP_MATTING_ENGINE_LEVIN,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_LEVELS,
+ "levels",
+ _("Levels"),
+ _("Number of downsampled levels to use"),
+ 1, 10, 2,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_ACTIVE_LEVELS,
+ "active-levels",
+ _("Active levels"),
+ _("Number of levels to perform solving"),
+ 1, 10, 2,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_ITERATIONS,
+ "iterations",
+ _("Iterations"),
+ _("Number of iterations to perform"),
+ 1, 10, 2,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_foreground_select_options_init (GimpForegroundSelectOptions *options)
+{
+}
+
+static void
+gimp_foreground_select_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpForegroundSelectOptions *options = GIMP_FOREGROUND_SELECT_OPTIONS (object);
+ GimpRGB *color;
+
+ switch (property_id)
+ {
+ case PROP_DRAW_MODE:
+ options->draw_mode = g_value_get_enum (value);
+ break;
+
+ case PROP_PREVIEW_MODE:
+ options->preview_mode = g_value_get_enum (value);
+ break;
+
+ case PROP_STROKE_WIDTH:
+ options->stroke_width = g_value_get_int (value);
+ break;
+
+ case PROP_MASK_COLOR:
+ color = g_value_get_boxed (value);
+ options->mask_color = *color;
+ break;
+
+ case PROP_ENGINE:
+ options->engine = g_value_get_enum (value);
+ if ((options->engine == GIMP_MATTING_ENGINE_LEVIN) &&
+ !(gegl_has_operation ("gegl:matting-levin")))
+ {
+ options->engine = GIMP_MATTING_ENGINE_GLOBAL;
+ }
+ break;
+
+ case PROP_LEVELS:
+ options->levels = g_value_get_int (value);
+ break;
+
+ case PROP_ACTIVE_LEVELS:
+ options->active_levels = g_value_get_int (value);
+ break;
+
+ case PROP_ITERATIONS:
+ options->iterations = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_foreground_select_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpForegroundSelectOptions *options = GIMP_FOREGROUND_SELECT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_DRAW_MODE:
+ g_value_set_enum (value, options->draw_mode);
+ break;
+
+ case PROP_PREVIEW_MODE:
+ g_value_set_enum (value, options->preview_mode);
+ break;
+
+ case PROP_STROKE_WIDTH:
+ g_value_set_int (value, options->stroke_width);
+ break;
+
+ case PROP_MASK_COLOR:
+ g_value_set_boxed (value, &options->mask_color);
+ break;
+
+ case PROP_ENGINE:
+ g_value_set_enum (value, options->engine);
+ break;
+
+ case PROP_LEVELS:
+ g_value_set_int (value, options->levels);
+ break;
+
+ case PROP_ACTIVE_LEVELS:
+ g_value_set_int (value, options->active_levels);
+ break;
+
+ case PROP_ITERATIONS:
+ g_value_set_int (value, options->iterations);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_foreground_select_options_reset_stroke_width (GtkWidget *button,
+ GimpToolOptions *tool_options)
+{
+ g_object_set (tool_options, "stroke-width", 10, NULL);
+}
+
+static gboolean
+gimp_foreground_select_options_sync_engine (GBinding *binding,
+ const GValue *source_value,
+ GValue *target_value,
+ gpointer user_data)
+{
+ gint type = g_value_get_enum (source_value);
+
+ g_value_set_boolean (target_value,
+ type == GPOINTER_TO_INT (user_data));
+
+ return TRUE;
+}
+
+GtkWidget *
+gimp_foreground_select_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_selection_options_gui (tool_options);
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *frame;
+ GtkWidget *scale;
+ GtkWidget *combo;
+ GtkWidget *inner_vbox;
+ GtkWidget *antialias_toggle;
+
+ antialias_toggle = GIMP_SELECTION_OPTIONS (tool_options)->antialias_toggle;
+ gtk_widget_hide (antialias_toggle);
+
+ frame = gimp_prop_enum_radio_frame_new (config, "draw-mode", NULL,
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ /* stroke width */
+ scale = gimp_prop_spin_scale_new (config, "stroke-width", NULL,
+ 1.0, 10.0, 2);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 1000.0);
+ gimp_spin_scale_set_gamma (GIMP_SPIN_SCALE (scale), 1.7);
+ gtk_box_pack_start (GTK_BOX (hbox), scale, TRUE, TRUE, 0);
+ gtk_widget_show (scale);
+
+ button = gimp_icon_button_new (GIMP_ICON_RESET, NULL);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_image_set_from_icon_name (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (button))),
+ GIMP_ICON_RESET, GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_foreground_select_options_reset_stroke_width),
+ tool_options);
+
+ gimp_help_set_help_data (button,
+ _("Reset stroke width native size"), NULL);
+
+ /* preview mode */
+
+ frame = gimp_prop_enum_radio_frame_new (config, "preview-mode", NULL,
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ /* mask color */
+ button = gimp_prop_color_button_new (config, "mask-color",
+ NULL,
+ 128, 24,
+ GIMP_COLOR_AREA_SMALL_CHECKS);
+ gimp_color_panel_set_context (GIMP_COLOR_PANEL (button), GIMP_CONTEXT (config));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* engine */
+ frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ combo = gimp_prop_enum_combo_box_new (config, "engine", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Engine"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), combo);
+
+ if (!gegl_has_operation ("gegl:matting-levin"))
+ gtk_widget_set_sensitive (combo, FALSE);
+ gtk_widget_show (combo);
+
+ inner_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), inner_vbox);
+ gtk_widget_show (inner_vbox);
+
+ /* engine parameters */
+ scale = gimp_prop_spin_scale_new (config, "levels", NULL,
+ 1.0, 1.0, 0);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), scale, FALSE, FALSE, 0);
+
+ g_object_bind_property_full (config, "engine",
+ scale, "visible",
+ G_BINDING_SYNC_CREATE,
+ gimp_foreground_select_options_sync_engine,
+ NULL,
+ GINT_TO_POINTER (GIMP_MATTING_ENGINE_LEVIN),
+ NULL);
+
+ scale = gimp_prop_spin_scale_new (config, "active-levels", NULL,
+ 1.0, 1.0, 0);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), scale, FALSE, FALSE, 0);
+
+ g_object_bind_property_full (config, "engine",
+ scale, "visible",
+ G_BINDING_SYNC_CREATE,
+ gimp_foreground_select_options_sync_engine,
+ NULL,
+ GINT_TO_POINTER (GIMP_MATTING_ENGINE_LEVIN),
+ NULL);
+
+ scale = gimp_prop_spin_scale_new (config, "iterations", NULL,
+ 1.0, 1.0, 0);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), scale, FALSE, FALSE, 0);
+
+ g_object_bind_property_full (config, "engine",
+ scale, "visible",
+ G_BINDING_SYNC_CREATE,
+ gimp_foreground_select_options_sync_engine,
+ NULL,
+ GINT_TO_POINTER (GIMP_MATTING_ENGINE_GLOBAL),
+ NULL);
+
+ return vbox;
+}
diff --git a/app/tools/gimpforegroundselectoptions.h b/app/tools/gimpforegroundselectoptions.h
new file mode 100644
index 0000000..e6cd1a9
--- /dev/null
+++ b/app/tools/gimpforegroundselectoptions.h
@@ -0,0 +1,64 @@
+/* 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_FOREGROUND_SELECT_OPTIONS_H__
+#define __GIMP_FOREGROUND_SELECT_OPTIONS_H__
+
+
+#include "gimpselectionoptions.h"
+
+
+#define GIMP_TYPE_FOREGROUND_SELECT_OPTIONS (gimp_foreground_select_options_get_type ())
+#define GIMP_FOREGROUND_SELECT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FOREGROUND_SELECT_OPTIONS, GimpForegroundSelectOptions))
+#define GIMP_FOREGROUND_SELECT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FOREGROUND_SELECT_OPTIONS, GimpForegroundSelectOptionsClass))
+#define GIMP_IS_FOREGROUND_SELECT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FOREGROUND_SELECT_OPTIONS))
+#define GIMP_IS_FOREGROUND_SELECT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FOREGROUND_SELECT_OPTIONS))
+#define GIMP_FOREGROUND_SELECT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FOREGROUND_SELECT_OPTIONS, GimpForegroundSelectOptionsClass))
+
+
+typedef struct _GimpForegroundSelectOptions GimpForegroundSelectOptions;
+typedef struct _GimpForegroundSelectOptionsClass GimpForegroundSelectOptionsClass;
+
+struct _GimpForegroundSelectOptions
+{
+ GimpSelectionOptions parent_instance;
+
+ GimpMattingDrawMode draw_mode;
+ GimpMattingPreviewMode preview_mode;
+ gint stroke_width;
+ GimpRGB mask_color;
+ GimpMattingEngine engine;
+ gint levels;
+ gint active_levels;
+ gint iterations;
+};
+
+struct _GimpForegroundSelectOptionsClass
+{
+ GimpSelectionOptionsClass parent_class;
+};
+
+
+GType gimp_foreground_select_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_foreground_select_options_gui (GimpToolOptions *tool_options);
+
+
+
+#endif /* __GIMP_FOREGROUND_SELECT_OPTIONS_H__ */
+
diff --git a/app/tools/gimpforegroundselecttool.c b/app/tools/gimpforegroundselecttool.c
new file mode 100644
index 0000000..480cff1
--- /dev/null
+++ b/app/tools/gimpforegroundselecttool.c
@@ -0,0 +1,1396 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpForegroundSelectTool
+ * Copyright (C) 2005 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-mask.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimpchannel-select.h"
+#include "core/gimpdrawable-foreground-extract.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimpprogress.h"
+#include "core/gimpscanconvert.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpcanvasbufferpreview.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolgui.h"
+
+#include "gimpforegroundselecttool.h"
+#include "gimpforegroundselectoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+#define FAR_OUTSIDE -10000
+
+
+typedef struct _StrokeUndo StrokeUndo;
+
+struct _StrokeUndo
+{
+ GeglBuffer *saved_trimap;
+ gint trimap_x;
+ gint trimap_y;
+ GimpMattingDrawMode draw_mode;
+ gint stroke_width;
+};
+
+
+static void gimp_foreground_select_tool_finalize (GObject *object);
+
+static gboolean gimp_foreground_select_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_foreground_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_foreground_select_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_active_modifier_key
+ (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static const gchar * gimp_foreground_select_tool_can_undo
+ (GimpTool *tool,
+ GimpDisplay *display);
+static const gchar * gimp_foreground_select_tool_can_redo
+ (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_foreground_select_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_foreground_select_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+static void gimp_foreground_select_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_foreground_select_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_foreground_select_tool_confirm (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display);
+
+static void gimp_foreground_select_tool_halt (GimpForegroundSelectTool *fg_select);
+static void gimp_foreground_select_tool_commit (GimpForegroundSelectTool *fg_select);
+
+static void gimp_foreground_select_tool_set_trimap (GimpForegroundSelectTool *fg_select);
+static void gimp_foreground_select_tool_set_preview (GimpForegroundSelectTool *fg_select);
+static void gimp_foreground_select_tool_preview (GimpForegroundSelectTool *fg_select);
+
+static void gimp_foreground_select_tool_stroke_paint (GimpForegroundSelectTool *fg_select);
+static void gimp_foreground_select_tool_cancel_paint (GimpForegroundSelectTool *fg_select);
+
+static void gimp_foreground_select_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpForegroundSelectTool *fg_select);
+static void gimp_foreground_select_tool_preview_toggled(GtkToggleButton *button,
+ GimpForegroundSelectTool *fg_select);
+
+static void gimp_foreground_select_tool_update_gui (GimpForegroundSelectTool *fg_select);
+
+static StrokeUndo * gimp_foreground_select_undo_new (GeglBuffer *trimap,
+ GArray *stroke,
+ GimpMattingDrawMode draw_mode,
+ gint stroke_width);
+static void gimp_foreground_select_undo_pop (StrokeUndo *undo,
+ GeglBuffer *trimap);
+static void gimp_foreground_select_undo_free (StrokeUndo *undo);
+
+
+G_DEFINE_TYPE (GimpForegroundSelectTool, gimp_foreground_select_tool,
+ GIMP_TYPE_POLYGON_SELECT_TOOL)
+
+#define parent_class gimp_foreground_select_tool_parent_class
+
+
+void
+gimp_foreground_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_FOREGROUND_SELECT_TOOL,
+ GIMP_TYPE_FOREGROUND_SELECT_OPTIONS,
+ gimp_foreground_select_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-foreground-select-tool",
+ _("Foreground Select"),
+ _("Foreground Select Tool: Select a region containing foreground objects"),
+ N_("F_oreground Select"), NULL,
+ NULL, GIMP_HELP_TOOL_FOREGROUND_SELECT,
+ GIMP_ICON_TOOL_FOREGROUND_SELECT,
+ data);
+}
+
+static void
+gimp_foreground_select_tool_class_init (GimpForegroundSelectToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+ GimpPolygonSelectToolClass *polygon_select_tool_class;
+
+ polygon_select_tool_class = GIMP_POLYGON_SELECT_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_foreground_select_tool_finalize;
+
+ tool_class->initialize = gimp_foreground_select_tool_initialize;
+ tool_class->control = gimp_foreground_select_tool_control;
+ tool_class->button_press = gimp_foreground_select_tool_button_press;
+ tool_class->button_release = gimp_foreground_select_tool_button_release;
+ tool_class->motion = gimp_foreground_select_tool_motion;
+ tool_class->key_press = gimp_foreground_select_tool_key_press;
+ tool_class->modifier_key = gimp_foreground_select_tool_modifier_key;
+ tool_class->active_modifier_key = gimp_foreground_select_tool_active_modifier_key;
+ tool_class->oper_update = gimp_foreground_select_tool_oper_update;
+ tool_class->cursor_update = gimp_foreground_select_tool_cursor_update;
+ tool_class->can_undo = gimp_foreground_select_tool_can_undo;
+ tool_class->can_redo = gimp_foreground_select_tool_can_redo;
+ tool_class->undo = gimp_foreground_select_tool_undo;
+ tool_class->redo = gimp_foreground_select_tool_redo;
+ tool_class->options_notify = gimp_foreground_select_tool_options_notify;
+
+ draw_tool_class->draw = gimp_foreground_select_tool_draw;
+
+ polygon_select_tool_class->confirm = gimp_foreground_select_tool_confirm;
+}
+
+static void
+gimp_foreground_select_tool_init (GimpForegroundSelectTool *fg_select)
+{
+ GimpTool *tool = GIMP_TOOL (fg_select);
+
+ gimp_tool_control_set_motion_mode (tool->control, GIMP_MOTION_MODE_EXACT);
+ gimp_tool_control_set_scroll_lock (tool->control, FALSE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE_SIZE |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FREE_SELECT);
+
+ gimp_tool_control_set_action_size (tool->control,
+ "tools/tools-foreground-select-brush-size-set");
+
+ fg_select->state = MATTING_STATE_FREE_SELECT;
+ fg_select->grayscale_preview = NULL;
+}
+
+static void
+gimp_foreground_select_tool_finalize (GObject *object)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (object);
+
+ g_clear_object (&fg_select->gui);
+ fg_select->preview_toggle = NULL;
+
+ if (fg_select->stroke)
+ g_warning ("%s: stroke should be NULL at this point", G_STRLOC);
+
+ if (fg_select->mask)
+ g_warning ("%s: mask should be NULL at this point", G_STRLOC);
+
+ if (fg_select->trimap)
+ g_warning ("%s: mask should be NULL at this point", G_STRLOC);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_foreground_select_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ if (! drawable)
+ return FALSE;
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer is not visible."));
+ return FALSE;
+ }
+
+ tool->display = display;
+
+ /* enable double click for the FreeSelectTool, because it may have been
+ * disabled if the tool has switched to MATTING_STATE_PAINT_TRIMAP,
+ * in gimp_foreground_select_tool_set_trimap().
+ */
+ gimp_tool_control_set_wants_double_click (tool->control, TRUE);
+
+ fg_select->state = MATTING_STATE_FREE_SELECT;
+
+ if (! fg_select->gui)
+ {
+ fg_select->gui =
+ gimp_tool_gui_new (tool->tool_info,
+ NULL,
+ _("Dialog for foreground select"),
+ NULL, NULL,
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ TRUE,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Select"), GTK_RESPONSE_APPLY,
+
+ NULL);
+
+ gimp_tool_gui_set_auto_overlay (fg_select->gui, TRUE);
+
+ g_signal_connect (fg_select->gui, "response",
+ G_CALLBACK (gimp_foreground_select_tool_response),
+ fg_select);
+
+ fg_select->preview_toggle =
+ gtk_check_button_new_with_mnemonic (_("_Preview mask"));
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (fg_select->gui)),
+ fg_select->preview_toggle, FALSE, FALSE, 0);
+ gtk_widget_show (fg_select->preview_toggle);
+
+ g_signal_connect (fg_select->preview_toggle, "toggled",
+ G_CALLBACK (gimp_foreground_select_tool_preview_toggled),
+ fg_select);
+ }
+
+ gimp_tool_gui_set_description (fg_select->gui,
+ _("Select foreground pixels"));
+
+ gimp_tool_gui_set_response_sensitive (fg_select->gui, GTK_RESPONSE_APPLY,
+ FALSE);
+ gtk_widget_set_sensitive (fg_select->preview_toggle, FALSE);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fg_select->preview_toggle),
+ FALSE);
+
+ gimp_tool_gui_set_shell (fg_select->gui, shell);
+ gimp_tool_gui_set_viewable (fg_select->gui, GIMP_VIEWABLE (drawable));
+
+ gimp_tool_gui_show (fg_select->gui);
+
+ return TRUE;
+}
+
+static void
+gimp_foreground_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_foreground_select_tool_halt (fg_select);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_foreground_select_tool_commit (fg_select);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_foreground_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ }
+ else
+ {
+ GimpVector2 point = gimp_vector2_new (coords->x, coords->y);
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (gimp_draw_tool_is_active (draw_tool) && draw_tool->display != display)
+ gimp_draw_tool_stop (draw_tool);
+
+ gimp_tool_control_activate (tool->control);
+
+ fg_select->last_coords = *coords;
+
+ g_return_if_fail (fg_select->stroke == NULL);
+ fg_select->stroke = g_array_new (FALSE, FALSE, sizeof (GimpVector2));
+
+ g_array_append_val (fg_select->stroke, point);
+
+ if (! gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_start (draw_tool, display);
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+}
+
+static void
+gimp_foreground_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+ }
+ else
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_control_halt (tool->control);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ gimp_foreground_select_tool_cancel_paint (fg_select);
+ }
+ else
+ {
+ gimp_foreground_select_tool_stroke_paint (fg_select);
+
+ if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ gimp_foreground_select_tool_preview (fg_select);
+ else
+ gimp_foreground_select_tool_set_trimap (fg_select);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+}
+
+static void
+gimp_foreground_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
+ display);
+ }
+ else
+ {
+ GimpVector2 *last = &g_array_index (fg_select->stroke,
+ GimpVector2,
+ fg_select->stroke->len - 1);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ fg_select->last_coords = *coords;
+
+ if (last->x != (gint) coords->x || last->y != (gint) coords->y)
+ {
+ GimpVector2 point = gimp_vector2_new (coords->x, coords->y);
+
+ g_array_append_val (fg_select->stroke, point);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+}
+
+static gboolean
+gimp_foreground_select_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ return GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+ }
+ else
+ {
+ if (display != tool->display)
+ return FALSE;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fg_select->preview_toggle),
+ TRUE);
+ else
+ gimp_foreground_select_tool_response (fg_select->gui,
+ GTK_RESPONSE_APPLY, fg_select);
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ gimp_foreground_select_tool_response (fg_select->gui,
+ GTK_RESPONSE_CANCEL, fg_select);
+ else
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fg_select->preview_toggle),
+ FALSE);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+ }
+}
+
+static void
+gimp_foreground_select_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
+ display);
+ }
+ else
+ {
+#if 0
+ if (key == gimp_get_toggle_behavior_mask ())
+ {
+ GimpForegroundSelectOptions *options;
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (tool);
+
+ g_object_set (options,
+ "background", ! options->background,
+ NULL);
+ }
+#endif
+ }
+}
+
+static void
+gimp_foreground_select_tool_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GIMP_TOOL_CLASS (parent_class)->active_modifier_key (tool, key, press,
+ state, display);
+ }
+}
+
+static void
+gimp_foreground_select_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ GimpForegroundSelectOptions *options;
+ const gchar *status_stage = NULL;
+ const gchar *status_mode = NULL;
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (fg_select);
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ if (GIMP_SELECTION_TOOL (tool)->function == SELECTION_SELECT)
+ {
+ gint n_points;
+
+ gimp_polygon_select_tool_get_points (GIMP_POLYGON_SELECT_TOOL (tool),
+ NULL, &n_points);
+
+ if (n_points > 2)
+ {
+ status_mode = _("Roughly outline the object to extract");
+ status_stage = _("press Enter to refine.");
+ }
+ else
+ {
+ status_stage = _("Roughly outline the object to extract");
+ }
+ }
+ }
+ else
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (proximity)
+ {
+ fg_select->last_coords = *coords;
+ }
+ else
+ {
+ fg_select->last_coords.x = FAR_OUTSIDE;
+ fg_select->last_coords.y = FAR_OUTSIDE;
+ }
+
+ gimp_draw_tool_resume (draw_tool);
+
+ if (options->draw_mode == GIMP_MATTING_DRAW_MODE_FOREGROUND)
+ status_mode = _("Selecting foreground");
+ else if (options->draw_mode == GIMP_MATTING_DRAW_MODE_BACKGROUND)
+ status_mode = _("Selecting background");
+ else
+ status_mode = _("Selecting unknown");
+
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ status_stage = _("press Enter to preview.");
+ else
+ status_stage = _("press Escape to exit preview or Enter to apply.");
+ }
+
+ if (proximity && status_stage)
+ {
+ if (status_mode)
+ gimp_tool_replace_status (tool, display, "%s, %s", status_mode, status_stage);
+ else
+ gimp_tool_replace_status (tool, display, "%s", status_stage);
+ }
+}
+
+static void
+gimp_foreground_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ {
+ switch (GIMP_SELECTION_TOOL (tool)->function)
+ {
+ case SELECTION_MOVE_MASK:
+ case SELECTION_MOVE:
+ case SELECTION_MOVE_COPY:
+ case SELECTION_ANCHOR:
+ return;
+ default:
+ break;
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static const gchar *
+gimp_foreground_select_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->undo_stack)
+ {
+ StrokeUndo *undo = fg_select->undo_stack->data;
+ const gchar *desc;
+
+ if (gimp_enum_get_value (GIMP_TYPE_MATTING_DRAW_MODE, undo->draw_mode,
+ NULL, NULL, &desc, NULL))
+ {
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+static const gchar *
+gimp_foreground_select_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+
+ if (fg_select->redo_stack)
+ {
+ StrokeUndo *undo = fg_select->redo_stack->data;
+ const gchar *desc;
+
+ if (gimp_enum_get_value (GIMP_TYPE_MATTING_DRAW_MODE, undo->draw_mode,
+ NULL, NULL, &desc, NULL))
+ {
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+gimp_foreground_select_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ StrokeUndo *undo = fg_select->undo_stack->data;
+
+ gimp_foreground_select_undo_pop (undo, fg_select->trimap);
+
+ fg_select->undo_stack = g_list_remove (fg_select->undo_stack, undo);
+ fg_select->redo_stack = g_list_prepend (fg_select->redo_stack, undo);
+
+ if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ gimp_foreground_select_tool_preview (fg_select);
+ else
+ gimp_foreground_select_tool_set_trimap (fg_select);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_foreground_select_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ StrokeUndo *undo = fg_select->redo_stack->data;
+
+ gimp_foreground_select_undo_pop (undo, fg_select->trimap);
+
+ fg_select->redo_stack = g_list_remove (fg_select->redo_stack, undo);
+ fg_select->undo_stack = g_list_prepend (fg_select->undo_stack, undo);
+
+ if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ gimp_foreground_select_tool_preview (fg_select);
+ else
+ gimp_foreground_select_tool_set_trimap (fg_select);
+
+ return TRUE;
+}
+
+static void
+gimp_foreground_select_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ GimpForegroundSelectOptions *fg_options;
+
+ fg_options = GIMP_FOREGROUND_SELECT_OPTIONS (options);
+
+ if (! tool->display)
+ return;
+
+ if (! strcmp (pspec->name, "mask-color") ||
+ ! strcmp (pspec->name, "preview-mode"))
+ {
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ {
+ gimp_foreground_select_tool_set_trimap (fg_select);
+ }
+ else if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ {
+ gimp_foreground_select_tool_set_preview (fg_select);
+ }
+ }
+ else if (! strcmp (pspec->name, "engine"))
+ {
+ if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ {
+ gimp_foreground_select_tool_preview (fg_select);
+ }
+ }
+ else if (! strcmp (pspec->name, "iterations"))
+ {
+ if (fg_options->engine == GIMP_MATTING_ENGINE_GLOBAL &&
+ fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ {
+ gimp_foreground_select_tool_preview (fg_select);
+ }
+ }
+ else if (! strcmp (pspec->name, "levels") ||
+ ! strcmp (pspec->name, "active-levels"))
+ {
+ if (fg_options->engine == GIMP_MATTING_ENGINE_LEVIN &&
+ fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ {
+ gimp_foreground_select_tool_preview (fg_select);
+ }
+ }
+}
+
+static void
+gimp_foreground_select_tool_get_area (GeglBuffer *mask,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2)
+{
+ gint width;
+ gint height;
+
+ gimp_gegl_mask_bounds (mask, x1, y1, x2, y2);
+
+ width = *x2 - *x1;
+ height = *y2 - *y1;
+
+ *x1 = MAX (*x1 - width / 2, 0);
+ *y1 = MAX (*y1 - height / 2, 0);
+ *x2 = MIN (*x2 + width / 2, gimp_item_get_width (GIMP_ITEM (mask)));
+ *y2 = MIN (*y2 + height / 2, gimp_item_get_height (GIMP_ITEM (mask)));
+}
+
+static void
+gimp_foreground_select_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpTool *tool = GIMP_TOOL (draw_tool);
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (tool);
+ GimpForegroundSelectOptions *options;
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (tool);
+
+ if (fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+ return;
+ }
+ else
+ {
+ gint x = fg_select->last_coords.x;
+ gint y = fg_select->last_coords.y;
+ gdouble radius = options->stroke_width / 2.0f;
+
+ if (fg_select->stroke)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (draw_tool->display);
+
+ gimp_draw_tool_add_pen (draw_tool,
+ (const GimpVector2 *) fg_select->stroke->data,
+ fg_select->stroke->len,
+ GIMP_CONTEXT (options),
+ GIMP_ACTIVE_COLOR_FOREGROUND,
+ options->stroke_width * shell->scale_y);
+ }
+
+ /* warn if the user is drawing outside of the working area */
+ if (FALSE)
+ {
+ gint x1, y1;
+ gint x2, y2;
+
+ gimp_foreground_select_tool_get_area (fg_select->mask,
+ &x1, &y1, &x2, &y2);
+
+ if (x < x1 + radius || x > x2 - radius ||
+ y < y1 + radius || y > y2 - radius)
+ {
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ x1, y1,
+ x2 - x1, y2 - y1);
+ }
+ }
+
+ if (x > FAR_OUTSIDE && y > FAR_OUTSIDE)
+ gimp_draw_tool_add_arc (draw_tool, FALSE,
+ x - radius, y - radius,
+ 2 * radius, 2 * radius,
+ 0.0, 2.0 * G_PI);
+
+ if (fg_select->grayscale_preview)
+ gimp_draw_tool_add_preview (draw_tool, fg_select->grayscale_preview);
+ }
+}
+
+static void
+gimp_foreground_select_tool_confirm (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display)
+{
+ GimpForegroundSelectTool *fg_select = GIMP_FOREGROUND_SELECT_TOOL (poly_sel);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpItem *item = GIMP_ITEM (drawable);
+
+ if (drawable && fg_select->state == MATTING_STATE_FREE_SELECT)
+ {
+ GimpScanConvert *scan_convert = gimp_scan_convert_new ();
+ const GimpVector2 *points;
+ gint n_points;
+
+ gimp_polygon_select_tool_get_points (poly_sel, &points, &n_points);
+
+ gimp_scan_convert_add_polyline (scan_convert, n_points, points, TRUE);
+
+ fg_select->trimap =
+ gegl_buffer_new (GEGL_RECTANGLE (gimp_item_get_offset_x (item),
+ gimp_item_get_offset_y (item),
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ gimp_image_get_mask_format (image));
+
+ gimp_scan_convert_render_value (scan_convert, fg_select->trimap,
+ 0, 0, 0.5);
+ gimp_scan_convert_free (scan_convert);
+
+ fg_select->grayscale_preview =
+ gimp_canvas_buffer_preview_new (gimp_display_get_shell (display),
+ fg_select->trimap);
+
+ gimp_foreground_select_tool_set_trimap (fg_select);
+ }
+}
+
+static void
+gimp_foreground_select_tool_halt (GimpForegroundSelectTool *fg_select)
+{
+ GimpTool *tool = GIMP_TOOL (fg_select);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (fg_select);
+
+ if (draw_tool->preview)
+ {
+ gimp_draw_tool_remove_preview (draw_tool, fg_select->grayscale_preview);
+ }
+
+ g_clear_object (&fg_select->grayscale_preview);
+ g_clear_object (&fg_select->trimap);
+ g_clear_object (&fg_select->mask);
+
+ if (fg_select->undo_stack)
+ {
+ g_list_free_full (fg_select->undo_stack,
+ (GDestroyNotify) gimp_foreground_select_undo_free);
+ fg_select->undo_stack = NULL;
+ }
+
+ if (fg_select->redo_stack)
+ {
+ g_list_free_full (fg_select->redo_stack,
+ (GDestroyNotify) gimp_foreground_select_undo_free);
+ fg_select->redo_stack = NULL;
+ }
+
+ if (tool->display)
+ gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
+ NULL, 0, 0, NULL, FALSE);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FREE_SELECT);
+ gimp_tool_control_set_toggle_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FREE_SELECT);
+
+ gimp_tool_control_set_toggled (tool->control, FALSE);
+
+ /* set precision to SUBPIXEL, because it may have been changed to
+ * PIXEL_CENTER if the tool has switched to MATTING_STATE_PAINT_TRIMAP,
+ * in gimp_foreground_select_tool_set_trimap().
+ */
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+
+ fg_select->state = MATTING_STATE_FREE_SELECT;
+
+ /* update the undo actions / menu items */
+ if (tool->display)
+ gimp_image_flush (gimp_display_get_image (tool->display));
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+
+ if (fg_select->gui)
+ gimp_tool_gui_hide (fg_select->gui);
+}
+
+static void
+gimp_foreground_select_tool_commit (GimpForegroundSelectTool *fg_select)
+{
+ GimpTool *tool = GIMP_TOOL (fg_select);
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (fg_select);
+
+ if (tool->display && fg_select->state != MATTING_STATE_FREE_SELECT)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ if (fg_select->state != MATTING_STATE_PREVIEW_MASK)
+ gimp_foreground_select_tool_preview (fg_select);
+
+ gimp_channel_select_buffer (gimp_image_get_mask (image),
+ C_("command", "Foreground Select"),
+ fg_select->mask,
+ 0, /* x offset */
+ 0, /* y offset */
+ options->operation,
+ options->feather,
+ options->feather_radius,
+ options->feather_radius);
+
+ gimp_image_flush (image);
+ }
+}
+
+static void
+gimp_foreground_select_tool_set_trimap (GimpForegroundSelectTool *fg_select)
+{
+ GimpTool *tool = GIMP_TOOL (fg_select);
+ GimpForegroundSelectOptions *options;
+
+ g_return_if_fail (fg_select->trimap != NULL);
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (tool);
+
+ gimp_polygon_select_tool_halt (GIMP_POLYGON_SELECT_TOOL (fg_select));
+
+ if (options->preview_mode == GIMP_MATTING_PREVIEW_MODE_ON_COLOR)
+ {
+ if (fg_select->grayscale_preview)
+ gimp_canvas_item_set_visible (fg_select->grayscale_preview, FALSE);
+
+ gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
+ fg_select->trimap, 0, 0,
+ &options->mask_color, TRUE);
+ }
+ else
+ {
+ gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
+ NULL, 0, 0, NULL, FALSE);
+
+ if (fg_select->grayscale_preview)
+ {
+ g_object_set (fg_select->grayscale_preview, "buffer",
+ fg_select->trimap, NULL);
+
+ gimp_canvas_item_set_visible (fg_select->grayscale_preview, TRUE);
+ }
+ }
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PAINTBRUSH);
+ gimp_tool_control_set_toggle_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PAINTBRUSH);
+
+ gimp_tool_control_set_toggled (tool->control, FALSE);
+
+ /* disable double click in paint trimap state */
+ gimp_tool_control_set_wants_double_click (tool->control, FALSE);
+
+ /* set precision to PIXEL_CENTER in paint trimap state */
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+
+ fg_select->state = MATTING_STATE_PAINT_TRIMAP;
+
+ gimp_foreground_select_tool_update_gui (fg_select);
+}
+
+static void
+gimp_foreground_select_tool_set_preview (GimpForegroundSelectTool *fg_select)
+{
+
+ GimpTool *tool = GIMP_TOOL (fg_select);
+ GimpForegroundSelectOptions *options;
+
+ g_return_if_fail (fg_select->mask != NULL);
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (tool);
+
+ if (options->preview_mode == GIMP_MATTING_PREVIEW_MODE_ON_COLOR)
+ {
+ if (fg_select->grayscale_preview)
+ gimp_canvas_item_set_visible (fg_select->grayscale_preview, FALSE);
+
+ gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
+ fg_select->mask, 0, 0,
+ &options->mask_color, TRUE);
+ }
+ else
+ {
+ gimp_display_shell_set_mask (gimp_display_get_shell (tool->display),
+ NULL, 0, 0, NULL, FALSE);
+
+ if (fg_select->grayscale_preview)
+ {
+ g_object_set (fg_select->grayscale_preview, "buffer",
+ fg_select->mask, NULL);
+ gimp_canvas_item_set_visible (fg_select->grayscale_preview, TRUE);
+ }
+ }
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PAINTBRUSH);
+ gimp_tool_control_set_toggle_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PAINTBRUSH);
+
+ gimp_tool_control_set_toggled (tool->control, FALSE);
+
+ fg_select->state = MATTING_STATE_PREVIEW_MASK;
+
+ gimp_foreground_select_tool_update_gui (fg_select);
+}
+
+static void
+gimp_foreground_select_tool_preview (GimpForegroundSelectTool *fg_select)
+{
+ GimpTool *tool = GIMP_TOOL (fg_select);
+ GimpForegroundSelectOptions *options;
+ GimpImage *image = gimp_display_get_image (tool->display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (tool);
+
+ g_clear_object (&fg_select->mask);
+
+ fg_select->mask = gimp_drawable_foreground_extract (drawable,
+ options->engine,
+ options->iterations,
+ options->levels,
+ options->active_levels,
+ fg_select->trimap,
+ GIMP_PROGRESS (fg_select));
+
+ gimp_foreground_select_tool_set_preview (fg_select);
+}
+
+static void
+gimp_foreground_select_tool_stroke_paint (GimpForegroundSelectTool *fg_select)
+{
+ GimpForegroundSelectOptions *options;
+ GimpScanConvert *scan_convert;
+ StrokeUndo *undo;
+ gint width;
+ gdouble opacity;
+
+ options = GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS (fg_select);
+
+ g_return_if_fail (fg_select->stroke != NULL);
+
+ width = ROUND ((gdouble) options->stroke_width);
+
+ if (fg_select->redo_stack)
+ {
+ g_list_free_full (fg_select->redo_stack,
+ (GDestroyNotify) gimp_foreground_select_undo_free);
+ fg_select->redo_stack = NULL;
+ }
+
+ undo = gimp_foreground_select_undo_new (fg_select->trimap,
+ fg_select->stroke,
+ options->draw_mode, width);
+ if (! undo)
+ {
+ g_array_free (fg_select->stroke, TRUE);
+ fg_select->stroke = NULL;
+ return;
+ }
+
+ fg_select->undo_stack = g_list_prepend (fg_select->undo_stack, undo);
+
+ scan_convert = gimp_scan_convert_new ();
+
+ if (fg_select->stroke->len == 1)
+ {
+ GimpVector2 points[2];
+
+ points[0] = points[1] = ((GimpVector2 *) fg_select->stroke->data)[0];
+
+ points[1].x += 0.01;
+ points[1].y += 0.01;
+
+ gimp_scan_convert_add_polyline (scan_convert, 2, points, FALSE);
+ }
+ else
+ {
+ gimp_scan_convert_add_polyline (scan_convert,
+ fg_select->stroke->len,
+ (GimpVector2 *) fg_select->stroke->data,
+ FALSE);
+ }
+
+ gimp_scan_convert_stroke (scan_convert,
+ width,
+ GIMP_JOIN_ROUND, GIMP_CAP_ROUND, 10.0,
+ 0.0, NULL);
+
+ if (options->draw_mode == GIMP_MATTING_DRAW_MODE_FOREGROUND)
+ opacity = 1.0;
+ else if (options->draw_mode == GIMP_MATTING_DRAW_MODE_BACKGROUND)
+ opacity = 0.0;
+ else
+ opacity = 0.5;
+
+ gimp_scan_convert_compose_value (scan_convert, fg_select->trimap,
+ 0, 0,
+ opacity);
+
+ gimp_scan_convert_free (scan_convert);
+
+ g_array_free (fg_select->stroke, TRUE);
+ fg_select->stroke = NULL;
+
+ /* update the undo actions / menu items */
+ gimp_image_flush (gimp_display_get_image (GIMP_TOOL (fg_select)->display));
+}
+
+static void
+gimp_foreground_select_tool_cancel_paint (GimpForegroundSelectTool *fg_select)
+{
+ g_return_if_fail (fg_select->stroke != NULL);
+
+ g_array_free (fg_select->stroke, TRUE);
+ fg_select->stroke = NULL;
+}
+
+static void
+gimp_foreground_select_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpForegroundSelectTool *fg_select)
+{
+ GimpTool *tool = GIMP_TOOL (fg_select);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_APPLY:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+ break;
+
+ default:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ break;
+ }
+}
+
+static void
+gimp_foreground_select_tool_preview_toggled (GtkToggleButton *button,
+ GimpForegroundSelectTool *fg_select)
+{
+ if (fg_select->state != MATTING_STATE_FREE_SELECT)
+ {
+ if (gtk_toggle_button_get_active (button))
+ {
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ gimp_foreground_select_tool_preview (fg_select);
+ }
+ else
+ {
+ if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ gimp_foreground_select_tool_set_trimap (fg_select);
+ }
+ }
+}
+
+static void
+gimp_foreground_select_tool_update_gui (GimpForegroundSelectTool *fg_select)
+{
+ if (fg_select->state == MATTING_STATE_PAINT_TRIMAP)
+ {
+ gimp_tool_gui_set_description (fg_select->gui, _("Paint mask"));
+ }
+ else if (fg_select->state == MATTING_STATE_PREVIEW_MASK)
+ {
+ gimp_tool_gui_set_description (fg_select->gui, _("Preview"));
+ }
+
+ gimp_tool_gui_set_response_sensitive (fg_select->gui, GTK_RESPONSE_APPLY,
+ TRUE);
+ gtk_widget_set_sensitive (fg_select->preview_toggle, TRUE);
+}
+
+static StrokeUndo *
+gimp_foreground_select_undo_new (GeglBuffer *trimap,
+ GArray *stroke,
+ GimpMattingDrawMode draw_mode,
+ gint stroke_width)
+
+{
+ StrokeUndo *undo;
+ const GeglRectangle *extent;
+ gint x1, y1, x2, y2;
+ gint width, height;
+ gint i;
+
+ extent = gegl_buffer_get_extent (trimap);
+
+ x1 = G_MAXINT;
+ y1 = G_MAXINT;
+ x2 = G_MININT;
+ y2 = G_MININT;
+
+ for (i = 0; i < stroke->len; i++)
+ {
+ GimpVector2 *point = &g_array_index (stroke, GimpVector2, i);
+
+ x1 = MIN (x1, floor (point->x));
+ y1 = MIN (y1, floor (point->y));
+ x2 = MAX (x2, ceil (point->x));
+ y2 = MAX (y2, ceil (point->y));
+ }
+
+ x1 -= (stroke_width + 1) / 2;
+ y1 -= (stroke_width + 1) / 2;
+ x2 += (stroke_width + 1) / 2;
+ y2 += (stroke_width + 1) / 2;
+
+ x1 = MAX (x1, extent->x);
+ y1 = MAX (y1, extent->y);
+ x2 = MIN (x2, extent->x + extent->width);
+ y2 = MIN (y2, extent->x + extent->height);
+
+ width = x2 - x1;
+ height = y2 - y1;
+
+ if (width <= 0 || height <= 0)
+ return NULL;
+
+ undo = g_slice_new0 (StrokeUndo);
+ undo->saved_trimap = gegl_buffer_new (GEGL_RECTANGLE (x1, y1, width, height),
+ gegl_buffer_get_format (trimap));
+
+ gimp_gegl_buffer_copy (
+ trimap, GEGL_RECTANGLE (x1, y1, width, height),
+ GEGL_ABYSS_NONE,
+ undo->saved_trimap, NULL);
+
+ undo->trimap_x = x1;
+ undo->trimap_y = y1;
+
+ undo->draw_mode = draw_mode;
+ undo->stroke_width = stroke_width;
+
+ return undo;
+}
+
+static void
+gimp_foreground_select_undo_pop (StrokeUndo *undo,
+ GeglBuffer *trimap)
+{
+ GeglBuffer *buffer;
+ gint width, height;
+
+ buffer = gimp_gegl_buffer_dup (undo->saved_trimap);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ gimp_gegl_buffer_copy (trimap,
+ GEGL_RECTANGLE (undo->trimap_x, undo->trimap_y,
+ width, height),
+ GEGL_ABYSS_NONE,
+ undo->saved_trimap, NULL);
+
+ gimp_gegl_buffer_copy (buffer,
+ GEGL_RECTANGLE (undo->trimap_x, undo->trimap_y,
+ width, height),
+ GEGL_ABYSS_NONE,
+ trimap, NULL);
+
+ g_object_unref (buffer);
+}
+
+static void
+gimp_foreground_select_undo_free (StrokeUndo *undo)
+{
+ g_clear_object (&undo->saved_trimap);
+
+ g_slice_free (StrokeUndo, undo);
+}
diff --git a/app/tools/gimpforegroundselecttool.h b/app/tools/gimpforegroundselecttool.h
new file mode 100644
index 0000000..648652c
--- /dev/null
+++ b/app/tools/gimpforegroundselecttool.h
@@ -0,0 +1,78 @@
+/* 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_FOREGROUND_SELECT_TOOL_H__
+#define __GIMP_FOREGROUND_SELECT_TOOL_H__
+
+
+#include "gimppolygonselecttool.h"
+
+
+typedef enum
+{
+ MATTING_STATE_FREE_SELECT = 0,
+ MATTING_STATE_PAINT_TRIMAP,
+ MATTING_STATE_PREVIEW_MASK,
+} MattingState;
+
+
+#define GIMP_TYPE_FOREGROUND_SELECT_TOOL (gimp_foreground_select_tool_get_type ())
+#define GIMP_FOREGROUND_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FOREGROUND_SELECT_TOOL, GimpForegroundSelectTool))
+#define GIMP_FOREGROUND_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FOREGROUND_SELECT_TOOL, GimpForegroundSelectToolClass))
+#define GIMP_IS_FOREGROUND_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FOREGROUND_SELECT_TOOL))
+#define GIMP_IS_FOREGROUND_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FOREGROUND_SELECT_TOOL))
+#define GIMP_FOREGROUND_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FOREGROUND_SELECT_TOOL, GimpForegroundSelectToolClass))
+
+#define GIMP_FOREGROUND_SELECT_TOOL_GET_OPTIONS(t) (GIMP_FOREGROUND_SELECT_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpForegroundSelectTool GimpForegroundSelectTool;
+typedef struct _GimpForegroundSelectToolClass GimpForegroundSelectToolClass;
+
+struct _GimpForegroundSelectTool
+{
+ GimpPolygonSelectTool parent_instance;
+
+ MattingState state;
+
+ GimpCoords last_coords;
+ GArray *stroke;
+ GeglBuffer *trimap;
+ GeglBuffer *mask;
+
+ GList *undo_stack;
+ GList *redo_stack;
+
+ GimpToolGui *gui;
+ GtkWidget *preview_toggle;
+
+ GimpCanvasItem *grayscale_preview;
+};
+
+struct _GimpForegroundSelectToolClass
+{
+ GimpPolygonSelectToolClass parent_class;
+};
+
+
+void gimp_foreground_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_foreground_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_FOREGROUND_SELECT_TOOL_H__ */
diff --git a/app/tools/gimpforegroundselecttoolundo.c b/app/tools/gimpforegroundselecttoolundo.c
new file mode 100644
index 0000000..381dc0b
--- /dev/null
+++ b/app/tools/gimpforegroundselecttoolundo.c
@@ -0,0 +1,168 @@
+/* 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/>.
+ */
+
+#if 0
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "tools-types.h"
+
+#include "gimpforegroundselecttool.h"
+#include "gimpforegroundselecttoolundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_FOREGROUND_SELECT_TOOL
+};
+
+
+static void gimp_foreground_select_tool_undo_constructed (GObject *object);
+static void gimp_foreground_select_tool_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_foreground_select_tool_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_foreground_select_tool_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_foreground_select_tool_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpForegroundSelectToolUndo, gimp_foreground_select_tool_undo,
+ GIMP_TYPE_UNDO)
+
+#define parent_class gimp_foreground_select_tool_undo_parent_class
+
+
+static void
+gimp_foreground_select_tool_undo_class_init (GimpForegroundSelectToolUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_foreground_select_tool_undo_constructed;
+ object_class->set_property = gimp_foreground_select_tool_undo_set_property;
+ object_class->get_property = gimp_foreground_select_tool_undo_get_property;
+
+ undo_class->pop = gimp_foreground_select_tool_undo_pop;
+ undo_class->free = gimp_foreground_select_tool_undo_free;
+
+ g_object_class_install_property (object_class, PROP_FOREGROUND_SELECT_TOOL,
+ g_param_spec_object ("foreground-select-tool",
+ NULL, NULL,
+ GIMP_TYPE_FOREGROUND_SELECT_TOOL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_foreground_select_tool_undo_init (GimpForegroundSelectToolUndo *undo)
+{
+}
+
+static void
+gimp_foreground_select_tool_undo_constructed (GObject *object)
+{
+ GimpForegroundSelectToolUndo *fg_select_tool_undo;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ fg_select_tool_undo = GIMP_FOREGROUND_SELECT_TOOL_UNDO (object);
+
+ gimp_assert (GIMP_IS_FOREGROUND_SELECT_TOOL (fg_select_tool_undo->foreground_select_tool));
+
+ g_object_add_weak_pointer (G_OBJECT (fg_select_tool_undo->foreground_select_tool),
+ (gpointer) &fg_select_tool_undo->foreground_select_tool);
+}
+
+static void
+gimp_foreground_select_tool_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpForegroundSelectToolUndo *fg_select_tool_undo =
+ GIMP_FOREGROUND_SELECT_TOOL_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_FOREGROUND_SELECT_TOOL:
+ fg_select_tool_undo->foreground_select_tool = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_foreground_select_tool_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpForegroundSelectToolUndo *fg_select_tool_undo =
+ GIMP_FOREGROUND_SELECT_TOOL_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_FOREGROUND_SELECT_TOOL:
+ g_value_set_object (value, fg_select_tool_undo->foreground_select_tool);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_foreground_select_tool_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+}
+
+static void
+gimp_foreground_select_tool_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpForegroundSelectToolUndo *fg_select_tool_undo = GIMP_FOREGROUND_SELECT_TOOL_UNDO (undo);
+
+ if (fg_select_tool_undo->foreground_select_tool)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (fg_select_tool_undo->foreground_select_tool),
+ (gpointer) &fg_select_tool_undo->foreground_select_tool);
+ fg_select_tool_undo->foreground_select_tool = NULL;
+ }
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
+
+#endif
diff --git a/app/tools/gimpforegroundselecttoolundo.h b/app/tools/gimpforegroundselecttoolundo.h
new file mode 100644
index 0000000..63477f8
--- /dev/null
+++ b/app/tools/gimpforegroundselecttoolundo.h
@@ -0,0 +1,56 @@
+/* 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/>.
+ */
+
+#if 0
+
+#ifndef __GIMP_FOREGROUND_SELECT_TOOL_UNDO_H__
+#define __GIMP_FOREGROUND_SELECT_TOOL_UNDO_H__
+
+
+#include "core/gimpundo.h"
+
+
+#define GIMP_TYPE_FOREGROUND_SELECT_TOOL_UNDO (gimp_foreground_select_tool_undo_get_type ())
+#define GIMP_FOREGROUND_SELECT_TOOL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FOREGROUND_SELECT_TOOL_UNDO, GimpForegroundSelectToolUndo))
+#define GIMP_FOREGROUND_SELECT_TOOL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FOREGROUND_SELECT_TOOL_UNDO, GimpForegroundSelectToolUndoClass))
+#define GIMP_IS_FOREGROUND_SELECT_TOOL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FOREGROUND_SELECT_TOOL_UNDO))
+#define GIMP_IS_FOREGROUND_SELECT_TOOL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FOREGROUND_SELECT_TOOL_UNDO))
+#define GIMP_FOREGROUND_SELECT_TOOL_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FOREGROUND_SELECT_TOOL_UNDO, GimpForegroundSelectToolUndoClass))
+
+
+typedef struct _GimpForegroundSelectToolUndo GimpForegroundSelectToolUndo;
+typedef struct _GimpForegroundSelectToolUndoClass GimpForegroundSelectToolUndoClass;
+
+struct _GimpForegroundSelectToolUndo
+{
+ GimpUndo parent_instance;
+
+ GimpForegroundSelectTool *foreground_select_tool;
+};
+
+struct _GimpForegroundSelectToolUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_foreground_select_tool_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_FOREGROUND_SELECT_TOOL_UNDO_H__ */
+
+#endif
diff --git a/app/tools/gimpfreeselecttool.c b/app/tools/gimpfreeselecttool.c
new file mode 100644
index 0000000..c97096f
--- /dev/null
+++ b/app/tools/gimpfreeselecttool.c
@@ -0,0 +1,355 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimpchannel-select.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer-floating-selection.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolpolygon.h"
+
+#include "gimpfreeselecttool.h"
+#include "gimpselectionoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+struct _GimpFreeSelectToolPrivate
+{
+ gboolean started;
+ gboolean changed;
+
+ /* The selection operation active when the tool was started */
+ GimpChannelOps operation_at_start;
+};
+
+
+static void gimp_free_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_free_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_free_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_free_select_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static gboolean gimp_free_select_tool_have_selection (GimpSelectionTool *sel_tool,
+ GimpDisplay *display);
+
+static void gimp_free_select_tool_change_complete (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display);
+
+static void gimp_free_select_tool_commit (GimpFreeSelectTool *free_sel,
+ GimpDisplay *display);
+static void gimp_free_select_tool_halt (GimpFreeSelectTool *free_sel);
+
+static gboolean gimp_free_select_tool_select (GimpFreeSelectTool *free_sel,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpFreeSelectTool, gimp_free_select_tool,
+ GIMP_TYPE_POLYGON_SELECT_TOOL)
+
+#define parent_class gimp_free_select_tool_parent_class
+
+
+void
+gimp_free_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_FREE_SELECT_TOOL,
+ GIMP_TYPE_SELECTION_OPTIONS,
+ gimp_selection_options_gui,
+ 0,
+ "gimp-free-select-tool",
+ _("Free Select"),
+ _("Free Select Tool: Select a hand-drawn region with free "
+ "and polygonal segments"),
+ N_("_Free Select"), "F",
+ NULL, GIMP_HELP_TOOL_FREE_SELECT,
+ GIMP_ICON_TOOL_FREE_SELECT,
+ data);
+}
+
+static void
+gimp_free_select_tool_class_init (GimpFreeSelectToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpSelectionToolClass *sel_class = GIMP_SELECTION_TOOL_CLASS (klass);
+ GimpPolygonSelectToolClass *poly_sel_class = GIMP_POLYGON_SELECT_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_free_select_tool_control;
+ tool_class->button_press = gimp_free_select_tool_button_press;
+ tool_class->button_release = gimp_free_select_tool_button_release;
+ tool_class->options_notify = gimp_free_select_tool_options_notify;
+
+ sel_class->have_selection = gimp_free_select_tool_have_selection;
+
+ poly_sel_class->change_complete = gimp_free_select_tool_change_complete;
+}
+
+static void
+gimp_free_select_tool_init (GimpFreeSelectTool *free_sel)
+{
+ GimpTool *tool = GIMP_TOOL (free_sel);
+ GimpSelectionTool *sel_tool = GIMP_SELECTION_TOOL (tool);
+
+ free_sel->priv = gimp_free_select_tool_get_instance_private (free_sel);
+
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_SELECTION);
+ gimp_tool_control_set_dirty_action (tool->control,
+ GIMP_TOOL_ACTION_COMMIT);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FREE_SELECT);
+
+ sel_tool->allow_move = TRUE;
+}
+
+static void
+gimp_free_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpFreeSelectTool *free_sel = GIMP_FREE_SELECT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_free_select_tool_halt (free_sel);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_free_select_tool_commit (free_sel, display);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_free_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpFreeSelectTool *free_sel = GIMP_FREE_SELECT_TOOL (tool);
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpFreeSelectToolPrivate *priv = free_sel->priv;
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL &&
+ gimp_selection_tool_start_edit (GIMP_SELECTION_TOOL (poly_sel),
+ display, coords))
+ return;
+
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL &&
+ gimp_polygon_select_tool_is_grabbed (poly_sel))
+ {
+ if (! priv->started)
+ {
+ priv->started = TRUE;
+ priv->operation_at_start = options->operation;
+ }
+
+ gimp_selection_tool_start_change (
+ GIMP_SELECTION_TOOL (tool),
+ ! gimp_polygon_select_tool_is_closed (poly_sel),
+ priv->operation_at_start);
+
+ priv->changed = FALSE;
+ }
+}
+
+static void
+gimp_free_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpFreeSelectTool *free_sel = GIMP_FREE_SELECT_TOOL (tool);
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpFreeSelectToolPrivate *priv = free_sel->priv;
+
+ if (gimp_polygon_select_tool_is_grabbed (poly_sel))
+ {
+ gimp_selection_tool_end_change (GIMP_SELECTION_TOOL (tool),
+ ! priv->changed);
+
+ priv->changed = FALSE;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+}
+
+static void
+gimp_free_select_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ if (! strcmp (pspec->name, "antialias") ||
+ ! strcmp (pspec->name, "feather") ||
+ ! strcmp (pspec->name, "feather-radius"))
+ {
+ if (tool->display)
+ {
+ gimp_free_select_tool_change_complete (
+ GIMP_POLYGON_SELECT_TOOL (tool), tool->display);
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+}
+
+static gboolean
+gimp_free_select_tool_have_selection (GimpSelectionTool *sel_tool,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (sel_tool);
+ GimpTool *tool = GIMP_TOOL (sel_tool);
+
+ if (display == tool->display)
+ {
+ gint n_points;
+
+ gimp_polygon_select_tool_get_points (poly_sel, NULL, &n_points);
+
+ if (n_points > 2)
+ return TRUE;
+ }
+
+ return GIMP_SELECTION_TOOL_CLASS (parent_class)->have_selection (sel_tool,
+ display);
+}
+
+static void
+gimp_free_select_tool_change_complete (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display)
+{
+ GimpFreeSelectTool *free_sel = GIMP_FREE_SELECT_TOOL (poly_sel);
+ GimpFreeSelectToolPrivate *priv = free_sel->priv;
+
+ priv->changed = TRUE;
+
+ gimp_selection_tool_start_change (GIMP_SELECTION_TOOL (free_sel),
+ FALSE,
+ priv->operation_at_start);
+
+ if (gimp_polygon_select_tool_is_closed (poly_sel))
+ gimp_free_select_tool_select (free_sel, display);
+
+ gimp_selection_tool_end_change (GIMP_SELECTION_TOOL (free_sel),
+ FALSE);
+}
+
+static void
+gimp_free_select_tool_halt (GimpFreeSelectTool *free_sel)
+{
+ GimpFreeSelectToolPrivate *priv = free_sel->priv;
+
+ priv->started = FALSE;
+}
+
+static void
+gimp_free_select_tool_commit (GimpFreeSelectTool *free_sel,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (free_sel);
+
+ if (! gimp_polygon_select_tool_is_closed (poly_sel))
+ {
+ if (gimp_free_select_tool_select (free_sel, display))
+ gimp_image_flush (gimp_display_get_image (display));
+ }
+}
+
+static gboolean
+gimp_free_select_tool_select (GimpFreeSelectTool *free_sel,
+ GimpDisplay *display)
+{
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (free_sel);
+ GimpTool *tool = GIMP_TOOL (free_sel);
+ GimpFreeSelectToolPrivate *priv = free_sel->priv;
+ GimpImage *image = gimp_display_get_image (display);
+ const GimpVector2 *points;
+ gint n_points;
+
+ gimp_polygon_select_tool_get_points (GIMP_POLYGON_SELECT_TOOL (free_sel),
+ &points, &n_points);
+
+ if (n_points > 2)
+ {
+ /* prevent this change from halting the tool */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_channel_select_polygon (gimp_image_get_mask (image),
+ C_("command", "Free Select"),
+ n_points,
+ points,
+ priv->operation_at_start,
+ options->antialias,
+ options->feather,
+ options->feather_radius,
+ options->feather_radius,
+ TRUE);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/app/tools/gimpfreeselecttool.h b/app/tools/gimpfreeselecttool.h
new file mode 100644
index 0000000..0212040
--- /dev/null
+++ b/app/tools/gimpfreeselecttool.h
@@ -0,0 +1,56 @@
+/* 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_FREE_SELECT_TOOL_H__
+#define __GIMP_FREE_SELECT_TOOL_H__
+
+
+#include "gimppolygonselecttool.h"
+
+
+#define GIMP_TYPE_FREE_SELECT_TOOL (gimp_free_select_tool_get_type ())
+#define GIMP_FREE_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FREE_SELECT_TOOL, GimpFreeSelectTool))
+#define GIMP_FREE_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FREE_SELECT_TOOL, GimpFreeSelectToolClass))
+#define GIMP_IS_FREE_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FREE_SELECT_TOOL))
+#define GIMP_IS_FREE_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FREE_SELECT_TOOL))
+#define GIMP_FREE_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FREE_SELECT_TOOL, GimpFreeSelectToolClass))
+
+
+typedef struct _GimpFreeSelectTool GimpFreeSelectTool;
+typedef struct _GimpFreeSelectToolPrivate GimpFreeSelectToolPrivate;
+typedef struct _GimpFreeSelectToolClass GimpFreeSelectToolClass;
+
+struct _GimpFreeSelectTool
+{
+ GimpPolygonSelectTool parent_instance;
+
+ GimpFreeSelectToolPrivate *priv;
+};
+
+struct _GimpFreeSelectToolClass
+{
+ GimpPolygonSelectToolClass parent_class;
+};
+
+
+void gimp_free_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_free_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_FREE_SELECT_TOOL_H__ */
diff --git a/app/tools/gimpfuzzyselecttool.c b/app/tools/gimpfuzzyselecttool.c
new file mode 100644
index 0000000..785804a
--- /dev/null
+++ b/app/tools/gimpfuzzyselecttool.c
@@ -0,0 +1,132 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfuzzyselecttool.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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimppickable.h"
+#include "core/gimppickable-contiguous-region.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpfuzzyselecttool.h"
+#include "gimpregionselectoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static GeglBuffer * gimp_fuzzy_select_tool_get_mask (GimpRegionSelectTool *region_select,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE (GimpFuzzySelectTool, gimp_fuzzy_select_tool,
+ GIMP_TYPE_REGION_SELECT_TOOL)
+
+#define parent_class gimp_fuzzy_select_tool_parent_class
+
+
+void
+gimp_fuzzy_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_FUZZY_SELECT_TOOL,
+ GIMP_TYPE_REGION_SELECT_OPTIONS,
+ gimp_region_select_options_gui,
+ 0,
+ "gimp-fuzzy-select-tool",
+ _("Fuzzy Select"),
+ _("Fuzzy Select Tool: Select a contiguous region on the basis of color"),
+ N_("Fu_zzy Select"), "U",
+ NULL, GIMP_HELP_TOOL_FUZZY_SELECT,
+ GIMP_ICON_TOOL_FUZZY_SELECT,
+ data);
+}
+
+static void
+gimp_fuzzy_select_tool_class_init (GimpFuzzySelectToolClass *klass)
+{
+ GimpRegionSelectToolClass *region_class;
+
+ region_class = GIMP_REGION_SELECT_TOOL_CLASS (klass);
+
+ region_class->undo_desc = C_("command", "Fuzzy Select");
+ region_class->get_mask = gimp_fuzzy_select_tool_get_mask;
+}
+
+static void
+gimp_fuzzy_select_tool_init (GimpFuzzySelectTool *fuzzy_select)
+{
+ GimpTool *tool = GIMP_TOOL (fuzzy_select);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_FUZZY_SELECT);
+}
+
+static GeglBuffer *
+gimp_fuzzy_select_tool_get_mask (GimpRegionSelectTool *region_select,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (region_select);
+ GimpSelectionOptions *sel_options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpPickable *pickable;
+ gint x, y;
+
+ x = region_select->x;
+ y = region_select->y;
+
+ if (! options->sample_merged)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ x -= off_x;
+ y -= off_y;
+
+ pickable = GIMP_PICKABLE (drawable);
+ }
+ else
+ {
+ pickable = GIMP_PICKABLE (image);
+ }
+
+ return gimp_pickable_contiguous_region_by_seed (pickable,
+ sel_options->antialias,
+ options->threshold / 255.0,
+ options->select_transparent,
+ options->select_criterion,
+ options->diagonal_neighbors,
+ x, y);
+}
diff --git a/app/tools/gimpfuzzyselecttool.h b/app/tools/gimpfuzzyselecttool.h
new file mode 100644
index 0000000..a1b2acf
--- /dev/null
+++ b/app/tools/gimpfuzzyselecttool.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfuzzyselecttool.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_FUZZY_SELECT_TOOL_H__
+#define __GIMP_FUZZY_SELECT_TOOL_H__
+
+
+#include "gimpregionselecttool.h"
+
+
+#define GIMP_TYPE_FUZZY_SELECT_TOOL (gimp_fuzzy_select_tool_get_type ())
+#define GIMP_FUZZY_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FUZZY_SELECT_TOOL, GimpFuzzySelectTool))
+#define GIMP_FUZZY_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FUZZY_SELECT_TOOL, GimpFuzzySelectToolClass))
+#define GIMP_IS_FUZZY_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FUZZY_SELECT_TOOL))
+#define GIMP_IS_FUZZY_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FUZZY_SELECT_TOOL))
+#define GIMP_FUZZY_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FUZZY_SELECT_TOOL, GimpFuzzySelectToolClass))
+
+
+typedef struct _GimpFuzzySelectTool GimpFuzzySelectTool;
+typedef struct _GimpFuzzySelectToolClass GimpFuzzySelectToolClass;
+
+struct _GimpFuzzySelectTool
+{
+ GimpRegionSelectTool parent_instance;
+};
+
+struct _GimpFuzzySelectToolClass
+{
+ GimpRegionSelectToolClass parent_class;
+};
+
+
+void gimp_fuzzy_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_fuzzy_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_FUZZY_SELECT_TOOL_H__ */
diff --git a/app/tools/gimpgegltool.c b/app/tools/gimpgegltool.c
new file mode 100644
index 0000000..69a0eec
--- /dev/null
+++ b/app/tools/gimpgegltool.c
@@ -0,0 +1,559 @@
+/* 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 <gegl-plugin.h>
+#include <gtk/gtk.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+
+#include "gimpfilteroptions.h"
+#include "gimpgegltool.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ COLUMN_NAME,
+ COLUMN_LABEL,
+ COLUMN_ICON_NAME,
+ N_COLUMNS
+};
+
+
+/* local function prototypes */
+
+static void gimp_gegl_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+
+static void gimp_gegl_tool_dialog (GimpFilterTool *filter_tool);
+
+static void gimp_gegl_tool_halt (GimpGeglTool *gegl_tool);
+
+static void gimp_gegl_tool_operation_changed (GtkWidget *widget,
+ GimpGeglTool *gegl_tool);
+
+
+G_DEFINE_TYPE (GimpGeglTool, gimp_gegl_tool, GIMP_TYPE_OPERATION_TOOL)
+
+#define parent_class gimp_gegl_tool_parent_class
+
+
+void
+gimp_gegl_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_GEGL_TOOL,
+ GIMP_TYPE_FILTER_OPTIONS,
+ gimp_color_options_gui,
+ 0,
+ "gimp-gegl-tool",
+ _("GEGL Operation"),
+ _("GEGL Tool: Use an arbitrary GEGL operation"),
+ N_("_GEGL Operation..."), NULL,
+ NULL, GIMP_HELP_TOOL_GEGL,
+ GIMP_ICON_GEGL,
+ data);
+}
+
+static void
+gimp_gegl_tool_class_init (GimpGeglToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_gegl_tool_control;
+
+ filter_tool_class->dialog = gimp_gegl_tool_dialog;
+}
+
+static void
+gimp_gegl_tool_init (GimpGeglTool *tool)
+{
+}
+
+static void
+gimp_gegl_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpGeglTool *gegl_tool = GIMP_GEGL_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_gegl_tool_halt (gegl_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static gboolean
+gimp_gegl_tool_operation_blacklisted (const gchar *name,
+ const gchar *categories_str)
+{
+ static const gchar * const category_blacklist[] =
+ {
+ "compositors",
+ "core",
+ "debug",
+ "display",
+ "hidden",
+ "input",
+ "output",
+ "programming",
+ "transform",
+ "video"
+ };
+ static const gchar * const name_blacklist[] =
+ {
+ /* these ops are already added to the menus via
+ * filters-actions or drawable-actions
+ */
+ "gegl:alien-map",
+ "gegl:antialias",
+ "gegl:apply-lens",
+ "gegl:bayer-matrix",
+ "gegl:bloom",
+ "gegl:bump-map",
+ "gegl:c2g",
+ "gegl:cartoon",
+ "gegl:cell-noise",
+ "gegl:channel-mixer",
+ "gegl:checkerboard",
+ "gegl:color",
+ "gegl:color-enhance",
+ "gegl:color-exchange",
+ "gegl:color-rotate",
+ "gegl:color-temperature",
+ "gegl:color-to-alpha",
+ "gegl:component-extract",
+ "gegl:convolution-matrix",
+ "gegl:cubism",
+ "gegl:deinterlace",
+ "gegl:difference-of-gaussians",
+ "gegl:diffraction-patterns",
+ "gegl:displace",
+ "gegl:distance-transform",
+ "gegl:dither",
+ "gegl:dropshadow",
+ "gegl:edge",
+ "gegl:edge-laplace",
+ "gegl:edge-neon",
+ "gegl:edge-sobel",
+ "gegl:emboss",
+ "gegl:engrave",
+ "gegl:exposure",
+ "gegl:fattal02",
+ "gegl:focus-blur",
+ "gegl:fractal-trace",
+ "gegl:gaussian-blur",
+ "gegl:gaussian-blur-selective",
+ "gegl:gegl",
+ "gegl:grid",
+ "gegl:high-pass",
+ "gegl:hue-chroma",
+ "gegl:illusion",
+ "gegl:image-gradient",
+ "gegl:invert-linear",
+ "gegl:invert-gamma",
+ "gegl:lens-blur",
+ "gegl:lens-distortion",
+ "gegl:lens-flare",
+ "gegl:linear-sinusoid",
+ "gegl:long-shadow",
+ "gegl:mantiuk06",
+ "gegl:maze",
+ "gegl:mean-curvature-blur",
+ "gegl:median-blur",
+ "gegl:mirrors",
+ "gegl:mono-mixer",
+ "gegl:mosaic",
+ "gegl:motion-blur-circular",
+ "gegl:motion-blur-linear",
+ "gegl:motion-blur-zoom",
+ "gegl:newsprint",
+ "gegl:noise-cie-lch",
+ "gegl:noise-hsv",
+ "gegl:noise-hurl",
+ "gegl:noise-pick",
+ "gegl:noise-reduction",
+ "gegl:noise-rgb",
+ "gegl:noise-slur",
+ "gegl:noise-solid",
+ "gegl:noise-spread",
+ "gegl:normal-map",
+ "gegl:oilify",
+ "gegl:panorama-projection",
+ "gegl:perlin-noise",
+ "gegl:photocopy",
+ "gegl:pixelize",
+ "gegl:plasma",
+ "gegl:polar-coordinates",
+ "gegl:recursive-transform",
+ "gegl:red-eye-removal",
+ "gegl:reinhard05",
+ "gegl:rgb-clip",
+ "gegl:ripple",
+ "gegl:saturation",
+ "gegl:sepia",
+ "gegl:shadows-highlights",
+ "gegl:shift",
+ "gegl:simplex-noise",
+ "gegl:sinus",
+ "gegl:slic",
+ "gegl:snn-mean",
+ "gegl:softglow",
+ "gegl:spherize",
+ "gegl:spiral",
+ "gegl:stereographic-projection",
+ "gegl:stretch-contrast",
+ "gegl:stretch-contrast-hsv",
+ "gegl:stress",
+ "gegl:supernova",
+ "gegl:texturize-canvas",
+ "gegl:tile-glass",
+ "gegl:tile-paper",
+ "gegl:tile-seamless",
+ "gegl:unsharp-mask",
+ "gegl:value-invert",
+ "gegl:value-propagate",
+ "gegl:variable-blur",
+ "gegl:video-degradation",
+ "gegl:vignette",
+ "gegl:waterpixels",
+ "gegl:wavelet-blur",
+ "gegl:waves",
+ "gegl:whirl-pinch",
+ "gegl:wind",
+
+ /* these ops are blacklisted for other reasons */
+ "gegl:contrast-curve",
+ "gegl:convert-format", /* pointless */
+ "gegl:ditto", /* pointless */
+ "gegl:fill-path",
+ "gegl:gray", /* we use gimp's op */
+ "gegl:hstack", /* pointless */
+ "gegl:introspect", /* pointless */
+ "gegl:layer", /* we use gimp's ops */
+ "gegl:lcms-from-profile", /* not usable here */
+ "gegl:linear-gradient", /* we use the blend tool */
+ "gegl:map-absolute", /* pointless */
+ "gegl:map-relative", /* pointless */
+ "gegl:matting-global", /* used in the foreground select tool */
+ "gegl:matting-levin", /* used in the foreground select tool */
+ "gegl:opacity", /* poinless */
+ "gegl:path",
+ "gegl:posterize", /* we use gimp's op */
+ "gegl:radial-gradient", /* we use the blend tool */
+ "gegl:rectangle", /* pointless */
+ "gegl:seamless-clone", /* used in the seamless clone tool */
+ "gegl:text", /* we use gimp's text rendering */
+ "gegl:threshold", /* we use gimp's op */
+ "gegl:tile", /* pointless */
+ "gegl:unpremul", /* pointless */
+ "gegl:vector-stroke",
+ };
+
+ gchar **categories;
+ gint i;
+
+ /* Operations with no name are abstract base classes */
+ if (! name)
+ return TRUE;
+
+ /* use this flag to include all ops for testing */
+ if (g_getenv ("GIMP_TESTING_NO_GEGL_BLACKLIST"))
+ return FALSE;
+
+ if (g_str_has_prefix (name, "gimp"))
+ return TRUE;
+
+ for (i = 0; i < G_N_ELEMENTS (name_blacklist); i++)
+ {
+ if (! strcmp (name, name_blacklist[i]))
+ return TRUE;
+ }
+
+ if (! categories_str)
+ return FALSE;
+
+ categories = g_strsplit (categories_str, ":", 0);
+
+ for (i = 0; i < G_N_ELEMENTS (category_blacklist); i++)
+ {
+ gint j;
+
+ for (j = 0; categories[j]; j++)
+ if (! strcmp (categories[j], category_blacklist[i]))
+ {
+ g_strfreev (categories);
+ return TRUE;
+ }
+ }
+
+ g_strfreev (categories);
+
+ return FALSE;
+}
+
+
+/* Builds a GList of the class structures of all subtypes of type.
+ */
+static GList *
+gimp_get_subtype_classes (GType type,
+ GList *classes)
+{
+ GeglOperationClass *klass;
+ GType *ops;
+ const gchar *categories;
+ guint n_ops;
+ gint i;
+
+ if (! type)
+ return classes;
+
+ klass = GEGL_OPERATION_CLASS (g_type_class_ref (type));
+ ops = g_type_children (type, &n_ops);
+
+ categories = gegl_operation_class_get_key (klass, "categories");
+
+ if (! gimp_gegl_tool_operation_blacklisted (klass->name, categories))
+ classes = g_list_prepend (classes, klass);
+
+ for (i = 0; i < n_ops; i++)
+ classes = gimp_get_subtype_classes (ops[i], classes);
+
+ if (ops)
+ g_free (ops);
+
+ return classes;
+}
+
+static gint
+gimp_gegl_tool_compare_operation_names (GeglOperationClass *a,
+ GeglOperationClass *b)
+{
+ const gchar *name_a = gegl_operation_class_get_key (a, "title");
+ const gchar *name_b = gegl_operation_class_get_key (b, "title");
+
+ if (! name_a) name_a = a->name;
+ if (! name_b) name_b = b->name;
+
+ return strcmp (name_a, name_b);
+}
+
+static GList *
+gimp_get_geglopclasses (void)
+{
+ GList *opclasses;
+
+ opclasses = gimp_get_subtype_classes (GEGL_TYPE_OPERATION, NULL);
+
+ opclasses = g_list_sort (opclasses,
+ (GCompareFunc)
+ gimp_gegl_tool_compare_operation_names);
+
+ return opclasses;
+}
+
+
+/*****************/
+/* Gegl dialog */
+/*****************/
+
+static void
+gimp_gegl_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpGeglTool *tool = GIMP_GEGL_TOOL (filter_tool);
+ GimpOperationTool *o_tool = GIMP_OPERATION_TOOL (filter_tool);
+ GtkListStore *store;
+ GtkCellRenderer *cell;
+ GtkWidget *main_vbox;
+ GtkWidget *hbox;
+ GtkWidget *combo;
+ GtkWidget *options_gui;
+ GtkWidget *options_box;
+ GList *opclasses;
+ GList *iter;
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->dialog (filter_tool);
+
+ options_box = g_weak_ref_get (&o_tool->options_box_ref);
+ g_return_if_fail (options_box);
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ /* The operation combo box */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (main_vbox), hbox, 0);
+ gtk_widget_show (hbox);
+
+ store = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+ opclasses = gimp_get_geglopclasses ();
+
+ for (iter = opclasses; iter; iter = iter->next)
+ {
+ GeglOperationClass *opclass = GEGL_OPERATION_CLASS (iter->data);
+ const gchar *icon_name = NULL;
+ const gchar *op_name = opclass->name;
+ const gchar *title;
+ gchar *label;
+
+ if (g_str_has_prefix (opclass->name, "gegl:"))
+ icon_name = GIMP_ICON_GEGL;
+
+ if (g_str_has_prefix (op_name, "gegl:"))
+ op_name += strlen ("gegl:");
+
+ title = gegl_operation_class_get_key (opclass, "title");
+
+ if (title)
+ label = g_strdup_printf ("%s (%s)", title, op_name);
+ else
+ label = g_strdup (op_name);
+
+ gtk_list_store_insert_with_values (store, NULL, -1,
+ COLUMN_NAME, opclass->name,
+ COLUMN_LABEL, label,
+ COLUMN_ICON_NAME, icon_name,
+ -1);
+
+ g_free (label);
+ }
+
+ g_list_free (opclasses);
+
+ combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+ g_object_unref (store);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell,
+ "icon-name", COLUMN_ICON_NAME);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
+ NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell,
+ "text", COLUMN_LABEL);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_gegl_tool_operation_changed),
+ tool);
+
+ tool->operation_combo = combo;
+
+ tool->description_label = gtk_label_new ("");
+ gtk_label_set_line_wrap (GTK_LABEL (tool->description_label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (tool->description_label), 0.0);
+ gtk_box_pack_start (GTK_BOX (main_vbox), tool->description_label,
+ FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (main_vbox), tool->description_label, 1);
+
+ /* The options vbox */
+ options_gui = gtk_label_new (_("Select an operation from the list above"));
+ gimp_label_set_attributes (GTK_LABEL (options_gui),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_misc_set_padding (GTK_MISC (options_gui), 0, 4);
+ gtk_container_add (GTK_CONTAINER (options_box), options_gui);
+ g_object_unref (options_box);
+ g_weak_ref_set (&o_tool->options_gui_ref, options_gui);
+ gtk_widget_show (options_gui);
+}
+
+static void
+gimp_gegl_tool_halt (GimpGeglTool *gegl_tool)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (gegl_tool);
+
+ gimp_operation_tool_set_operation (op_tool, NULL,
+ NULL, NULL, NULL, NULL, NULL);
+}
+
+static void
+gimp_gegl_tool_operation_changed (GtkWidget *widget,
+ GimpGeglTool *tool)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *operation;
+
+ if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter))
+ return;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_NAME, &operation,
+ -1);
+
+ if (operation)
+ {
+ const gchar *description;
+
+ description = gegl_operation_get_key (operation, "description");
+
+ if (description)
+ {
+ gtk_label_set_text (GTK_LABEL (tool->description_label), description);
+ gtk_widget_show (tool->description_label);
+ }
+ else
+ {
+ gtk_widget_hide (tool->description_label);
+ }
+
+ gimp_operation_tool_set_operation (GIMP_OPERATION_TOOL (tool),
+ operation,
+ _("GEGL Operation"),
+ _("GEGL Operation"),
+ NULL,
+ GIMP_ICON_GEGL,
+ GIMP_HELP_TOOL_GEGL);
+ g_free (operation);
+ }
+}
diff --git a/app/tools/gimpgegltool.h b/app/tools/gimpgegltool.h
new file mode 100644
index 0000000..0a7a8f4
--- /dev/null
+++ b/app/tools/gimpgegltool.h
@@ -0,0 +1,57 @@
+/* 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_GEGL_TOOL_H__
+#define __GIMP_GEGL_TOOL_H__
+
+
+#include "gimpoperationtool.h"
+
+
+#define GIMP_TYPE_GEGL_TOOL (gimp_gegl_tool_get_type ())
+#define GIMP_GEGL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GEGL_TOOL, GimpGeglTool))
+#define GIMP_GEGL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GEGL_TOOL, GimpGeglToolClass))
+#define GIMP_IS_GEGL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GEGL_TOOL))
+#define GIMP_IS_GEGL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GEGL_TOOL))
+#define GIMP_GEGL_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GEGL_TOOL, GimpGeglToolClass))
+
+
+typedef struct _GimpGeglTool GimpGeglTool;
+typedef struct _GimpGeglToolClass GimpGeglToolClass;
+
+struct _GimpGeglTool
+{
+ GimpOperationTool parent_instance;
+
+ /* dialog */
+ GtkWidget *operation_combo;
+ GtkWidget *description_label;
+};
+
+struct _GimpGeglToolClass
+{
+ GimpOperationToolClass parent_class;
+};
+
+
+void gimp_gegl_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_gegl_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_GEGL_TOOL_H__ */
diff --git a/app/tools/gimpgenerictransformtool.c b/app/tools/gimpgenerictransformtool.c
new file mode 100644
index 0000000..9f20af6
--- /dev/null
+++ b/app/tools/gimpgenerictransformtool.c
@@ -0,0 +1,194 @@
+/* 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 "tools-types.h"
+
+#include "core/gimp-transform-utils.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolgui.h"
+
+#include "gimpgenerictransformtool.h"
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static gboolean gimp_generic_transform_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+static void gimp_generic_transform_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_generic_transform_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_generic_transform_tool_prepare (GimpTransformGridTool *tg_tool);
+
+
+G_DEFINE_TYPE (GimpGenericTransformTool, gimp_generic_transform_tool,
+ GIMP_TYPE_TRANSFORM_GRID_TOOL)
+
+#define parent_class gimp_generic_transform_tool_parent_class
+
+
+static void
+gimp_generic_transform_tool_class_init (GimpGenericTransformToolClass *klass)
+{
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+
+ tg_class->info_to_matrix = gimp_generic_transform_tool_info_to_matrix;
+ tg_class->dialog = gimp_generic_transform_tool_dialog;
+ tg_class->dialog_update = gimp_generic_transform_tool_dialog_update;
+ tg_class->prepare = gimp_generic_transform_tool_prepare;
+}
+
+static void
+gimp_generic_transform_tool_init (GimpGenericTransformTool *unified_tool)
+{
+}
+
+static gboolean
+gimp_generic_transform_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ GimpGenericTransformTool *generic = GIMP_GENERIC_TRANSFORM_TOOL (tg_tool);
+
+ if (GIMP_GENERIC_TRANSFORM_TOOL_GET_CLASS (generic)->info_to_points)
+ GIMP_GENERIC_TRANSFORM_TOOL_GET_CLASS (generic)->info_to_points (generic);
+
+ gimp_matrix3_identity (transform);
+
+ return gimp_transform_matrix_generic (transform,
+ generic->input_points,
+ generic->output_points);
+}
+
+static void
+gimp_generic_transform_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+ GimpGenericTransformTool *generic = GIMP_GENERIC_TRANSFORM_TOOL (tg_tool);
+ GtkWidget *frame;
+ GtkWidget *vbox;
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkSizeGroup *size_group;
+ gint x, y;
+
+ frame = gimp_frame_new (_("Transform Matrix"));
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tg_tool->gui)), frame,
+ FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
+
+ table = generic->matrix_table = gtk_table_new (3, 3, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
+ gtk_size_group_add_widget (size_group, table);
+ gtk_widget_show (table);
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ label = generic->matrix_labels[y][x] = gtk_label_new (" ");
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_label_set_width_chars (GTK_LABEL (label), 8);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_SCALE, PANGO_SCALE_SMALL,
+ -1);
+ gtk_table_attach (GTK_TABLE (table), label,
+ x, x + 1, y, y + 1, GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+ }
+ }
+
+ label = generic->invalid_label = gtk_label_new (_("Invalid transform"));
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_size_group_add_widget (size_group, label);
+ gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
+
+ g_object_unref (size_group);
+}
+
+static void
+gimp_generic_transform_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ GimpGenericTransformTool *generic = GIMP_GENERIC_TRANSFORM_TOOL (tg_tool);
+ GimpMatrix3 transform;
+ gboolean transform_valid;
+
+ transform_valid = gimp_transform_grid_tool_info_to_matrix (tg_tool,
+ &transform);
+
+ if (transform_valid)
+ {
+ gint x, y;
+
+ gtk_widget_show (generic->matrix_table);
+ gtk_widget_hide (generic->invalid_label);
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ gchar buf[32];
+
+ g_snprintf (buf, sizeof (buf), "%.4f", transform.coeff[y][x]);
+
+ gtk_label_set_text (GTK_LABEL (generic->matrix_labels[y][x]), buf);
+ }
+ }
+ }
+ else
+ {
+ gtk_widget_show (generic->invalid_label);
+ gtk_widget_hide (generic->matrix_table);
+ }
+}
+
+static void
+gimp_generic_transform_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpGenericTransformTool *generic = GIMP_GENERIC_TRANSFORM_TOOL (tg_tool);
+
+ generic->input_points[0] = (GimpVector2) {tr_tool->x1, tr_tool->y1};
+ generic->input_points[1] = (GimpVector2) {tr_tool->x2, tr_tool->y1};
+ generic->input_points[2] = (GimpVector2) {tr_tool->x1, tr_tool->y2};
+ generic->input_points[3] = (GimpVector2) {tr_tool->x2, tr_tool->y2};
+
+ memcpy (generic->output_points, generic->input_points,
+ sizeof (generic->input_points));
+}
diff --git a/app/tools/gimpgenerictransformtool.h b/app/tools/gimpgenerictransformtool.h
new file mode 100644
index 0000000..8de1fc9
--- /dev/null
+++ b/app/tools/gimpgenerictransformtool.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_GENERIC_TRANSFORM_TOOL_H__
+#define __GIMP_GENERIC_TRANSFORM_TOOL_H__
+
+
+#include "gimptransformgridtool.h"
+
+
+#define GIMP_TYPE_GENERIC_TRANSFORM_TOOL (gimp_generic_transform_tool_get_type ())
+#define GIMP_GENERIC_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GENERIC_TRANSFORM_TOOL, GimpGenericTransformTool))
+#define GIMP_GENERIC_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GENERIC_TRANSFORM_TOOL, GimpGenericTransformToolClass))
+#define GIMP_IS_GENERIC_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GENERIC_TRANSFORM_TOOL))
+#define GIMP_IS_GENERIC_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GENERIC_TRANSFORM_TOOL))
+#define GIMP_GENERIC_TRANSFORM_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GENERIC_TRANSFORM_TOOL, GimpGenericTransformToolClass))
+
+
+typedef struct _GimpGenericTransformToolClass GimpGenericTransformToolClass;
+
+struct _GimpGenericTransformTool
+{
+ GimpTransformGridTool parent_instance;
+
+ GimpVector2 input_points[4];
+ GimpVector2 output_points[4];
+
+ GtkWidget *matrix_table;
+ GtkWidget *matrix_labels[3][3];
+ GtkWidget *invalid_label;
+};
+
+struct _GimpGenericTransformToolClass
+{
+ GimpTransformGridToolClass parent_class;
+
+ /* virtual functions */
+ void (* info_to_points) (GimpGenericTransformTool *generic);
+};
+
+
+GType gimp_generic_transform_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_GENERIC_TRANSFORM_TOOL_H__ */
diff --git a/app/tools/gimpgradientoptions.c b/app/tools/gimpgradientoptions.c
new file mode 100644
index 0000000..86fdd25
--- /dev/null
+++ b/app/tools/gimpgradientoptions.c
@@ -0,0 +1,414 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpdata.h"
+#include "core/gimpdatafactory.h"
+#include "core/gimp-gradients.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpviewablebox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpgradientoptions.h"
+#include "gimppaintoptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_OFFSET,
+ PROP_GRADIENT_TYPE,
+ PROP_DISTANCE_METRIC,
+ PROP_SUPERSAMPLE,
+ PROP_SUPERSAMPLE_DEPTH,
+ PROP_SUPERSAMPLE_THRESHOLD,
+ PROP_DITHER,
+ PROP_INSTANT,
+ PROP_MODIFY_ACTIVE
+};
+
+
+static void gimp_gradient_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_gradient_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gradient_options_repeat_gradient_type_notify (GimpGradientOptions *options,
+ GParamSpec *pspec,
+ GtkWidget *repeat_combo);
+static void gradient_options_metric_gradient_type_notify (GimpGradientOptions *options,
+ GParamSpec *pspec,
+ GtkWidget *repeat_combo);
+
+
+G_DEFINE_TYPE (GimpGradientOptions, gimp_gradient_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_gradient_options_class_init (GimpGradientOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_gradient_options_set_property;
+ object_class->get_property = gimp_gradient_options_get_property;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OFFSET,
+ "offset",
+ _("Offset"),
+ NULL,
+ 0.0, 100.0, 0.0,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_GRADIENT_TYPE,
+ "gradient-type",
+ _("Shape"),
+ NULL,
+ GIMP_TYPE_GRADIENT_TYPE,
+ GIMP_GRADIENT_LINEAR,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_DISTANCE_METRIC,
+ "distance-metric",
+ _("Metric"),
+ _("Metric to use for the distance calculation"),
+ GEGL_TYPE_DISTANCE_METRIC,
+ GEGL_DISTANCE_METRIC_EUCLIDEAN,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SUPERSAMPLE,
+ "supersample",
+ _("Adaptive Supersampling"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_SUPERSAMPLE_DEPTH,
+ "supersample-depth",
+ _("Max depth"),
+ NULL,
+ 1, 9, 3,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SUPERSAMPLE_THRESHOLD,
+ "supersample-threshold",
+ _("Threshold"),
+ NULL,
+ 0.0, 4.0, 0.2,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DITHER,
+ "dither",
+ _("Dithering"),
+ NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_INSTANT,
+ "instant",
+ _("Instant mode"),
+ _("Commit gradient instantly"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MODIFY_ACTIVE,
+ "modify-active",
+ _("Modify active gradient"),
+ _("Modify the active gradient in-place"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_gradient_options_init (GimpGradientOptions *options)
+{
+}
+
+static void
+gimp_gradient_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_OFFSET:
+ options->offset = g_value_get_double (value);
+ break;
+ case PROP_GRADIENT_TYPE:
+ options->gradient_type = g_value_get_enum (value);
+ break;
+ case PROP_DISTANCE_METRIC:
+ options->distance_metric = g_value_get_enum (value);
+ break;
+
+ case PROP_SUPERSAMPLE:
+ options->supersample = g_value_get_boolean (value);
+ break;
+ case PROP_SUPERSAMPLE_DEPTH:
+ options->supersample_depth = g_value_get_int (value);
+ break;
+ case PROP_SUPERSAMPLE_THRESHOLD:
+ options->supersample_threshold = g_value_get_double (value);
+ break;
+
+ case PROP_DITHER:
+ options->dither = g_value_get_boolean (value);
+ break;
+
+ case PROP_INSTANT:
+ options->instant = g_value_get_boolean (value);
+ break;
+ case PROP_MODIFY_ACTIVE:
+ options->modify_active = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_gradient_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_OFFSET:
+ g_value_set_double (value, options->offset);
+ break;
+ case PROP_GRADIENT_TYPE:
+ g_value_set_enum (value, options->gradient_type);
+ break;
+ case PROP_DISTANCE_METRIC:
+ g_value_set_enum (value, options->distance_metric);
+ break;
+
+ case PROP_SUPERSAMPLE:
+ g_value_set_boolean (value, options->supersample);
+ break;
+ case PROP_SUPERSAMPLE_DEPTH:
+ g_value_set_int (value, options->supersample_depth);
+ break;
+ case PROP_SUPERSAMPLE_THRESHOLD:
+ g_value_set_double (value, options->supersample_threshold);
+ break;
+
+ case PROP_DITHER:
+ g_value_set_boolean (value, options->dither);
+ break;
+
+ case PROP_INSTANT:
+ g_value_set_boolean (value, options->instant);
+ break;
+ case PROP_MODIFY_ACTIVE:
+ g_value_set_boolean (value, options->modify_active);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_gradient_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpContext *context = GIMP_CONTEXT (tool_options);
+ GimpGradientOptions *options = GIMP_GRADIENT_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *vbox2;
+ GtkWidget *frame;
+ GtkWidget *scale;
+ GtkWidget *combo;
+ GtkWidget *button;
+ GtkWidget *label;
+ gchar *str;
+ GdkModifierType extend_mask;
+ GimpGradient *gradient;
+
+ extend_mask = gimp_get_extend_selection_mask ();
+
+ /* the gradient */
+ button = gimp_prop_gradient_box_new (NULL, GIMP_CONTEXT (tool_options),
+ _("Gradient"), 2,
+ "gradient-view-type",
+ "gradient-view-size",
+ "gradient-reverse",
+ "gradient-blend-color-space",
+ "gimp-gradient-editor",
+ _("Edit this gradient"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the blend color space */
+ combo = gimp_prop_enum_combo_box_new (config, "gradient-blend-color-space",
+ 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo),
+ _("Blend Color Space"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ /* the gradient type menu */
+ combo = gimp_prop_enum_combo_box_new (config, "gradient-type", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Shape"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (combo),
+ "gimp-gradient");
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ /* the distance metric menu */
+ combo = gimp_prop_enum_combo_box_new (config, "distance-metric", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Metric"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ g_signal_connect (config, "notify::gradient-type",
+ G_CALLBACK (gradient_options_metric_gradient_type_notify),
+ combo);
+ gradient_options_metric_gradient_type_notify (options, NULL, combo);
+
+ /* the repeat option */
+ combo = gimp_prop_enum_combo_box_new (config, "gradient-repeat", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Repeat"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ g_signal_connect (config, "notify::gradient-type",
+ G_CALLBACK (gradient_options_repeat_gradient_type_notify),
+ combo);
+ gradient_options_repeat_gradient_type_notify (options, NULL, combo);
+
+ /* the offset scale */
+ scale = gimp_prop_spin_scale_new (config, "offset", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* the dither toggle */
+ button = gimp_prop_check_button_new (config, "dither", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* supersampling options */
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ frame = gimp_prop_expanding_frame_new (config, "supersample", NULL,
+ vbox2, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* max depth scale */
+ scale = gimp_prop_spin_scale_new (config, "supersample-depth", NULL,
+ 1.0, 1.0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* threshold scale */
+ scale = gimp_prop_spin_scale_new (config, "supersample-threshold", NULL,
+ 0.01, 0.1, 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* the instant toggle */
+ str = g_strdup_printf (_("Instant mode (%s)"),
+ gimp_get_mod_string (extend_mask));
+
+ button = gimp_prop_check_button_new (config, "instant", str);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_free (str);
+
+ options->instant_toggle = button;
+
+ /* the modify active toggle */
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ frame = gimp_prop_expanding_frame_new (config, "modify-active", NULL,
+ vbox2, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ options->modify_active_frame = frame;
+
+ label = gtk_label_new (_("The active gradient is non-writable "
+ "and cannot be edited directly. "
+ "Uncheck this option "
+ "to edit a copy of it."));
+ gtk_box_pack_start (GTK_BOX (vbox2), label, TRUE, TRUE, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_width_chars (GTK_LABEL (label), 24);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+
+ options->modify_active_hint = label;
+
+ gradient = gimp_context_get_gradient (GIMP_CONTEXT (options));
+
+ gtk_widget_set_sensitive (options->modify_active_frame,
+ gradient !=
+ gimp_gradients_get_custom (context->gimp));
+ gtk_widget_set_visible (options->modify_active_hint,
+ gradient &&
+ ! gimp_data_is_writable (GIMP_DATA (gradient)));
+
+ return vbox;
+}
+
+static void
+gradient_options_repeat_gradient_type_notify (GimpGradientOptions *options,
+ GParamSpec *pspec,
+ GtkWidget *repeat_combo)
+{
+ gtk_widget_set_sensitive (repeat_combo,
+ options->gradient_type < GIMP_GRADIENT_SHAPEBURST_ANGULAR);
+}
+
+static void
+gradient_options_metric_gradient_type_notify (GimpGradientOptions *options,
+ GParamSpec *pspec,
+ GtkWidget *repeat_combo)
+{
+ gtk_widget_set_sensitive (repeat_combo,
+ options->gradient_type >= GIMP_GRADIENT_SHAPEBURST_ANGULAR &&
+ options->gradient_type <= GIMP_GRADIENT_SHAPEBURST_DIMPLED);
+}
diff --git a/app/tools/gimpgradientoptions.h b/app/tools/gimpgradientoptions.h
new file mode 100644
index 0000000..0c07c0f
--- /dev/null
+++ b/app/tools/gimpgradientoptions.h
@@ -0,0 +1,65 @@
+/* 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_GRADIENT_OPTIONS_H__
+#define __GIMP_GRADIENT_OPTIONS_H__
+
+
+#include "paint/gimppaintoptions.h"
+
+
+#define GIMP_TYPE_GRADIENT_OPTIONS (gimp_gradient_options_get_type ())
+#define GIMP_GRADIENT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRADIENT_OPTIONS, GimpGradientOptions))
+#define GIMP_GRADIENT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRADIENT_OPTIONS, GimpGradientOptionsClass))
+#define GIMP_IS_GRADIENT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRADIENT_OPTIONS))
+#define GIMP_IS_GRADIENT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRADIENT_OPTIONS))
+#define GIMP_GRADIENT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRADIENT_OPTIONS, GimpGradientOptionsClass))
+
+
+typedef struct _GimpGradientOptions GimpGradientOptions;
+typedef struct _GimpPaintOptionsClass GimpGradientOptionsClass;
+
+struct _GimpGradientOptions
+{
+ GimpPaintOptions paint_options;
+
+ gdouble offset;
+ GimpGradientType gradient_type;
+ GeglDistanceMetric distance_metric;
+
+ gboolean supersample;
+ gint supersample_depth;
+ gdouble supersample_threshold;
+
+ gboolean dither;
+
+ gboolean instant;
+ gboolean modify_active;
+
+ /* options gui */
+ GtkWidget *instant_toggle;
+ GtkWidget *modify_active_frame;
+ GtkWidget *modify_active_hint;
+};
+
+
+GType gimp_gradient_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_gradient_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_GRADIENT_OPTIONS_H__ */
diff --git a/app/tools/gimpgradienttool-editor.c b/app/tools/gimpgradienttool-editor.c
new file mode 100644
index 0000000..ad4528b
--- /dev/null
+++ b/app/tools/gimpgradienttool-editor.c
@@ -0,0 +1,2520 @@
+/* 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "operations/gimp-operation-config.h"
+
+#include "core/gimpdata.h"
+#include "core/gimpgradient.h"
+#include "core/gimp-gradients.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimpcolorpanel.h"
+#include "widgets/gimpeditor.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptoolline.h"
+
+#include "gimpgradientoptions.h"
+#include "gimpgradienttool.h"
+#include "gimpgradienttool-editor.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 2e-10
+
+
+typedef enum
+{
+ DIRECTION_NONE,
+ DIRECTION_LEFT,
+ DIRECTION_RIGHT
+} Direction;
+
+
+typedef struct
+{
+ /* line endpoints at the beginning of the operation */
+ gdouble start_x;
+ gdouble start_y;
+ gdouble end_x;
+ gdouble end_y;
+
+ /* copy of the gradient at the beginning of the operation, owned by the gradient
+ * info, or NULL, if the gradient isn't affected
+ */
+ GimpGradient *gradient;
+
+ /* handle added by the operation, or HANDLE_NONE */
+ gint added_handle;
+ /* handle removed by the operation, or HANDLE_NONE */
+ gint removed_handle;
+ /* selected handle at the end of the operation, or HANDLE_NONE */
+ gint selected_handle;
+} GradientInfo;
+
+
+/* local function prototypes */
+
+static gboolean gimp_gradient_tool_editor_line_can_add_slider (GimpToolLine *line,
+ gdouble value,
+ GimpGradientTool *gradient_tool);
+static gint gimp_gradient_tool_editor_line_add_slider (GimpToolLine *line,
+ gdouble value,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_line_prepare_to_remove_slider (GimpToolLine *line,
+ gint slider,
+ gboolean remove,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_line_remove_slider (GimpToolLine *line,
+ gint slider,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_line_selection_changed (GimpToolLine *line,
+ GimpGradientTool *gradient_tool);
+static gboolean gimp_gradient_tool_editor_line_handle_clicked (GimpToolLine *line,
+ gint handle,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_gui_response (GimpToolGui *gui,
+ gint response_id,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_color_entry_color_clicked (GimpColorButton *button,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_color_entry_color_changed (GimpColorButton *button,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_color_entry_color_response (GimpColorButton *button,
+ GimpColorDialogState state,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_color_entry_type_changed (GtkComboBox *combo,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_endpoint_se_value_changed (GimpSizeEntry *se,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_stop_se_value_changed (GimpSizeEntry *se,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_stop_delete_clicked (GtkWidget *button,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_midpoint_se_value_changed (GimpSizeEntry *se,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_midpoint_type_changed (GtkComboBox *combo,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_midpoint_color_changed (GtkComboBox *combo,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_midpoint_new_stop_clicked (GtkWidget *button,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_midpoint_center_clicked (GtkWidget *button,
+ GimpGradientTool *gradient_tool);
+
+static gboolean gimp_gradient_tool_editor_flush_idle (GimpGradientTool *gradient_tool);
+
+static gboolean gimp_gradient_tool_editor_is_gradient_editable (GimpGradientTool *gradient_tool);
+
+static gboolean gimp_gradient_tool_editor_handle_is_endpoint (GimpGradientTool *gradient_tool,
+ gint handle);
+static gboolean gimp_gradient_tool_editor_handle_is_stop (GimpGradientTool *gradient_tool,
+ gint handle);
+static gboolean gimp_gradient_tool_editor_handle_is_midpoint (GimpGradientTool *gradient_tool,
+ gint handle);
+static GimpGradientSegment * gimp_gradient_tool_editor_handle_get_segment (GimpGradientTool *gradient_tool,
+ gint handle);
+
+static void gimp_gradient_tool_editor_block_handlers (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_unblock_handlers (GimpGradientTool *gradient_tool);
+static gboolean gimp_gradient_tool_editor_are_handlers_blocked (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_freeze_gradient (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_thaw_gradient (GimpGradientTool *gradient_tool);
+
+static gint gimp_gradient_tool_editor_add_stop (GimpGradientTool *gradient_tool,
+ gdouble value);
+static void gimp_gradient_tool_editor_delete_stop (GimpGradientTool *gradient_tool,
+ gint slider);
+static gint gimp_gradient_tool_editor_midpoint_to_stop (GimpGradientTool *gradient_tool,
+ gint slider);
+
+static void gimp_gradient_tool_editor_update_sliders (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_editor_purge_gradient_history (GSList **stack);
+static void gimp_gradient_tool_editor_purge_gradient (GimpGradientTool *gradient_tool);
+
+static GtkWidget * gimp_gradient_tool_editor_color_entry_new (GimpGradientTool *gradient_tool,
+ const gchar *title,
+ Direction direction,
+ GtkWidget *chain_button,
+ GtkWidget **color_panel,
+ GtkWidget **type_combo);
+static void gimp_gradient_tool_editor_init_endpoint_gui (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_init_stop_gui (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_init_midpoint_gui (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_update_endpoint_gui (GimpGradientTool *gradient_tool,
+ gint selection);
+static void gimp_gradient_tool_editor_update_stop_gui (GimpGradientTool *gradient_tool,
+ gint selection);
+static void gimp_gradient_tool_editor_update_midpoint_gui (GimpGradientTool *gradient_tool,
+ gint selection);
+static void gimp_gradient_tool_editor_update_gui (GimpGradientTool *gradient_tool);
+
+static GradientInfo * gimp_gradient_tool_editor_gradient_info_new (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_editor_gradient_info_free (GradientInfo *info);
+static void gimp_gradient_tool_editor_gradient_info_apply (GimpGradientTool *gradient_tool,
+ const GradientInfo *info,
+ gboolean set_selection);
+static gboolean gimp_gradient_tool_editor_gradient_info_is_trivial (GimpGradientTool *gradient_tool,
+ const GradientInfo *info);
+
+
+/* private functions */
+
+
+static gboolean
+gimp_gradient_tool_editor_line_can_add_slider (GimpToolLine *line,
+ gdouble value,
+ GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ gdouble offset = options->offset / 100.0;
+
+ return gimp_gradient_tool_editor_is_gradient_editable (gradient_tool) &&
+ value >= offset;
+}
+
+static gint
+gimp_gradient_tool_editor_line_add_slider (GimpToolLine *line,
+ gdouble value,
+ GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ gdouble offset = options->offset / 100.0;
+
+ /* adjust slider value according to the offset */
+ value = (value - offset) / (1.0 - offset);
+
+ /* flip the slider value, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ value = 1.0 - value;
+
+ return gimp_gradient_tool_editor_add_stop (gradient_tool, value);
+}
+
+static void
+gimp_gradient_tool_editor_line_prepare_to_remove_slider (GimpToolLine *line,
+ gint slider,
+ gboolean remove,
+ GimpGradientTool *gradient_tool)
+{
+ if (remove)
+ {
+ GradientInfo *info;
+ GimpGradient *tentative_gradient;
+
+ /* show a tentative gradient, demonstrating the result of actually
+ * removing the slider
+ */
+
+ info = gradient_tool->undo_stack->data;
+
+ if (info->added_handle == slider)
+ {
+ /* see comment in gimp_gradient_tool_editor_delete_stop() */
+
+ gimp_assert (info->gradient != NULL);
+
+ tentative_gradient = g_object_ref (info->gradient);
+ }
+ else
+ {
+ GimpGradientSegment *seg;
+ gint i;
+
+ tentative_gradient =
+ GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (gradient_tool->gradient)));
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, slider);
+
+ i = gimp_gradient_segment_range_get_n_segments (gradient_tool->gradient,
+ gradient_tool->gradient->segments,
+ seg) - 1;
+
+ seg = gimp_gradient_segment_get_nth (tentative_gradient->segments, i);
+
+ gimp_gradient_segment_range_merge (tentative_gradient,
+ seg, seg->next, NULL, NULL);
+ }
+
+ gimp_gradient_tool_set_tentative_gradient (gradient_tool, tentative_gradient);
+
+ g_object_unref (tentative_gradient);
+ }
+ else
+ {
+ gimp_gradient_tool_set_tentative_gradient (gradient_tool, NULL);
+ }
+}
+
+static void
+gimp_gradient_tool_editor_line_remove_slider (GimpToolLine *line,
+ gint slider,
+ GimpGradientTool *gradient_tool)
+{
+ gimp_gradient_tool_editor_delete_stop (gradient_tool, slider);
+ gimp_gradient_tool_set_tentative_gradient (gradient_tool, NULL);
+}
+
+static void
+gimp_gradient_tool_editor_line_selection_changed (GimpToolLine *line,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (gradient_tool->gui)
+ {
+ /* hide all color dialogs */
+ gimp_color_panel_dialog_response (
+ GIMP_COLOR_PANEL (gradient_tool->endpoint_color_panel),
+ GIMP_COLOR_DIALOG_OK);
+ gimp_color_panel_dialog_response (
+ GIMP_COLOR_PANEL (gradient_tool->stop_left_color_panel),
+ GIMP_COLOR_DIALOG_OK);
+ gimp_color_panel_dialog_response (
+ GIMP_COLOR_PANEL (gradient_tool->stop_right_color_panel),
+ GIMP_COLOR_DIALOG_OK);
+
+ /* reset the stop colors chain button */
+ if (gimp_gradient_tool_editor_handle_is_stop (gradient_tool, selection))
+ {
+ const GimpGradientSegment *seg;
+ gboolean homogeneous;
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool,
+ selection);
+
+ homogeneous = seg->right_color.r == seg->next->left_color.r &&
+ seg->right_color.g == seg->next->left_color.g &&
+ seg->right_color.b == seg->next->left_color.b &&
+ seg->right_color.a == seg->next->left_color.a &&
+ seg->right_color_type == seg->next->left_color_type;
+
+ gimp_chain_button_set_active (
+ GIMP_CHAIN_BUTTON (gradient_tool->stop_chain_button), homogeneous);
+ }
+ }
+
+ gimp_gradient_tool_editor_update_gui (gradient_tool);
+}
+
+static gboolean
+gimp_gradient_tool_editor_line_handle_clicked (GimpToolLine *line,
+ gint handle,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpGradientTool *gradient_tool)
+{
+ if (gimp_gradient_tool_editor_handle_is_midpoint (gradient_tool, handle))
+ {
+ if (press_type == GIMP_BUTTON_PRESS_DOUBLE &&
+ gimp_gradient_tool_editor_is_gradient_editable (gradient_tool))
+ {
+ gint stop;
+
+ stop = gimp_gradient_tool_editor_midpoint_to_stop (gradient_tool, handle);
+
+ gimp_tool_line_set_selection (line, stop);
+
+ /* return FALSE, so that the new slider can be dragged immediately */
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+static void
+gimp_gradient_tool_editor_gui_response (GimpToolGui *gui,
+ gint response_id,
+ GimpGradientTool *gradient_tool)
+{
+ switch (response_id)
+ {
+ default:
+ gimp_tool_line_set_selection (GIMP_TOOL_LINE (gradient_tool->widget),
+ GIMP_TOOL_LINE_HANDLE_NONE);
+ break;
+ }
+}
+
+static void
+gimp_gradient_tool_editor_color_entry_color_clicked (GimpColorButton *button,
+ GimpGradientTool *gradient_tool)
+{
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+}
+
+static void
+gimp_gradient_tool_editor_color_entry_color_changed (GimpColorButton *button,
+ GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ gint selection;
+ GimpRGB color;
+ Direction direction;
+ GtkWidget *chain_button;
+ GimpGradientSegment *seg;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ gimp_color_button_get_color (button, &color);
+
+ direction =
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
+ "gimp-gradient-tool-editor-direction"));
+ chain_button = g_object_get_data (G_OBJECT (button),
+ "gimp-gradient-tool-editor-chain-button");
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ /* swap the endpoint handles, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ {
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ selection = GIMP_TOOL_LINE_HANDLE_END;
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ selection = GIMP_TOOL_LINE_HANDLE_START;
+ break;
+ }
+ }
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ seg->left_color = color;
+ seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ seg->right_color = color;
+ seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ break;
+
+ default:
+ if (direction == DIRECTION_LEFT ||
+ (chain_button &&
+ gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chain_button))))
+ {
+ seg->right_color = color;
+ seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ }
+
+ if (direction == DIRECTION_RIGHT ||
+ (chain_button &&
+ gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chain_button))))
+ {
+ seg->next->left_color = color;
+ seg->next->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ }
+ }
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_color_entry_color_response (GimpColorButton *button,
+ GimpColorDialogState state,
+ GimpGradientTool *gradient_tool)
+{
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_color_entry_type_changed (GtkComboBox *combo,
+ GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ gint selection;
+ gint color_type;
+ Direction direction;
+ GtkWidget *chain_button;
+ GimpGradientSegment *seg;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &color_type))
+ return;
+
+ direction =
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo),
+ "gimp-gradient-tool-editor-direction"));
+ chain_button = g_object_get_data (G_OBJECT (combo),
+ "gimp-gradient-tool-editor-chain-button");
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ /* swap the endpoint handles, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ {
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ selection = GIMP_TOOL_LINE_HANDLE_END;
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ selection = GIMP_TOOL_LINE_HANDLE_START;
+ break;
+ }
+ }
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ seg->left_color_type = color_type;
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ seg->right_color_type = color_type;
+ break;
+
+ default:
+ if (direction == DIRECTION_LEFT ||
+ (chain_button &&
+ gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chain_button))))
+ {
+ seg->right_color_type = color_type;
+ }
+
+ if (direction == DIRECTION_RIGHT ||
+ (chain_button &&
+ gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chain_button))))
+ {
+ seg->next->left_color_type = color_type;
+ }
+ }
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_endpoint_se_value_changed (GimpSizeEntry *se,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ gdouble x;
+ gdouble y;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (selection == GIMP_TOOL_LINE_HANDLE_NONE)
+ return;
+
+ x = gimp_size_entry_get_refval (se, 0);
+ y = gimp_size_entry_get_refval (se, 1);
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_block_handlers (gradient_tool);
+
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ g_object_set (gradient_tool->widget,
+ "x1", x,
+ "y1", y,
+ NULL);
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ g_object_set (gradient_tool->widget,
+ "x2", x,
+ "y2", y,
+ NULL);
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ }
+
+ gimp_gradient_tool_editor_unblock_handlers (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_stop_se_value_changed (GimpSizeEntry *se,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ gdouble value;
+ GimpGradientSegment *seg;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (selection == GIMP_TOOL_LINE_HANDLE_NONE)
+ return;
+
+ value = gimp_size_entry_get_refval (se, 0) / 100.0;
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ gimp_gradient_segment_range_compress (gradient_tool->gradient,
+ seg, seg,
+ seg->left, value);
+ gimp_gradient_segment_range_compress (gradient_tool->gradient,
+ seg->next, seg->next,
+ value, seg->next->right);
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_stop_delete_clicked (GtkWidget *button,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ gimp_gradient_tool_editor_delete_stop (gradient_tool, selection);
+}
+
+static void
+gimp_gradient_tool_editor_midpoint_se_value_changed (GimpSizeEntry *se,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ gdouble value;
+ GimpGradientSegment *seg;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (selection == GIMP_TOOL_LINE_HANDLE_NONE)
+ return;
+
+ value = gimp_size_entry_get_refval (se, 0) / 100.0;
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ seg->middle = value;
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_midpoint_type_changed (GtkComboBox *combo,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ gint type;
+ GimpGradientSegment *seg;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &type))
+ return;
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ seg->type = type;
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_midpoint_color_changed (GtkComboBox *combo,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ gint color;
+ GimpGradientSegment *seg;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &color))
+ return;
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ seg->color = color;
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static void
+gimp_gradient_tool_editor_midpoint_new_stop_clicked (GtkWidget *button,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ gint stop;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ stop = gimp_gradient_tool_editor_midpoint_to_stop (gradient_tool, selection);
+
+ gimp_tool_line_set_selection (GIMP_TOOL_LINE (gradient_tool->widget), stop);
+}
+
+static void
+gimp_gradient_tool_editor_midpoint_center_clicked (GtkWidget *button,
+ GimpGradientTool *gradient_tool)
+{
+ gint selection;
+ GimpGradientSegment *seg;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ gimp_gradient_segment_range_recenter_handles (gradient_tool->gradient, seg, seg);
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static gboolean
+gimp_gradient_tool_editor_flush_idle (GimpGradientTool *gradient_tool)
+{
+ GimpDisplay *display = GIMP_TOOL (gradient_tool)->display;
+
+ gimp_image_flush (gimp_display_get_image (display));
+
+ gradient_tool->flush_idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gimp_gradient_tool_editor_is_gradient_editable (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+
+ return ! options->modify_active ||
+ gimp_data_is_writable (GIMP_DATA (gradient_tool->gradient));
+}
+
+static gboolean
+gimp_gradient_tool_editor_handle_is_endpoint (GimpGradientTool *gradient_tool,
+ gint handle)
+{
+ return handle == GIMP_TOOL_LINE_HANDLE_START ||
+ handle == GIMP_TOOL_LINE_HANDLE_END;
+}
+
+static gboolean
+gimp_gradient_tool_editor_handle_is_stop (GimpGradientTool *gradient_tool,
+ gint handle)
+{
+ gint n_sliders;
+
+ gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget), &n_sliders);
+
+ return handle >= 0 && handle < n_sliders / 2;
+}
+
+static gboolean
+gimp_gradient_tool_editor_handle_is_midpoint (GimpGradientTool *gradient_tool,
+ gint handle)
+{
+ gint n_sliders;
+
+ gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget), &n_sliders);
+
+ return handle >= n_sliders / 2;
+}
+
+static GimpGradientSegment *
+gimp_gradient_tool_editor_handle_get_segment (GimpGradientTool *gradient_tool,
+ gint handle)
+{
+ switch (handle)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ return gradient_tool->gradient->segments;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ return gimp_gradient_segment_get_last (gradient_tool->gradient->segments);
+
+ default:
+ {
+ const GimpControllerSlider *sliders;
+ gint n_sliders;
+ gint seg_i;
+
+ sliders = gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ &n_sliders);
+
+ gimp_assert (handle >= 0 && handle < n_sliders);
+
+ seg_i = GPOINTER_TO_INT (sliders[handle].data);
+
+ return gimp_gradient_segment_get_nth (gradient_tool->gradient->segments,
+ seg_i);
+ }
+ }
+}
+
+static void
+gimp_gradient_tool_editor_block_handlers (GimpGradientTool *gradient_tool)
+{
+ gradient_tool->block_handlers_count++;
+}
+
+static void
+gimp_gradient_tool_editor_unblock_handlers (GimpGradientTool *gradient_tool)
+{
+ gimp_assert (gradient_tool->block_handlers_count > 0);
+
+ gradient_tool->block_handlers_count--;
+}
+
+static gboolean
+gimp_gradient_tool_editor_are_handlers_blocked (GimpGradientTool *gradient_tool)
+{
+ return gradient_tool->block_handlers_count > 0;
+}
+
+static void
+gimp_gradient_tool_editor_freeze_gradient (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpGradient *custom;
+ GradientInfo *info;
+
+ gimp_gradient_tool_editor_block_handlers (gradient_tool);
+
+ custom = gimp_gradients_get_custom (GIMP_CONTEXT (options)->gimp);
+
+ if (gradient_tool->gradient == custom || options->modify_active)
+ {
+ gimp_assert (gimp_gradient_tool_editor_is_gradient_editable (gradient_tool));
+
+ gimp_data_freeze (GIMP_DATA (gradient_tool->gradient));
+ }
+ else
+ {
+ /* copy the active gradient to the custom gradient, and make the custom
+ * gradient active.
+ */
+ gimp_data_freeze (GIMP_DATA (custom));
+
+ gimp_data_copy (GIMP_DATA (custom), GIMP_DATA (gradient_tool->gradient));
+
+ gimp_context_set_gradient (GIMP_CONTEXT (options), custom);
+
+ gimp_assert (gradient_tool->gradient == custom);
+ gimp_assert (gimp_gradient_tool_editor_is_gradient_editable (gradient_tool));
+ }
+
+ if (gradient_tool->edit_count > 0)
+ {
+ info = gradient_tool->undo_stack->data;
+
+ if (! info->gradient)
+ {
+ info->gradient =
+ GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (gradient_tool->gradient)));
+ }
+ }
+}
+
+static void
+gimp_gradient_tool_editor_thaw_gradient (GimpGradientTool *gradient_tool)
+{
+ gimp_data_thaw (GIMP_DATA (gradient_tool->gradient));
+
+ gimp_gradient_tool_editor_update_sliders (gradient_tool);
+ gimp_gradient_tool_editor_update_gui (gradient_tool);
+
+ gimp_gradient_tool_editor_unblock_handlers (gradient_tool);
+}
+
+static gint
+gimp_gradient_tool_editor_add_stop (GimpGradientTool *gradient_tool,
+ gdouble value)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ GimpGradientSegment *seg;
+ gint stop;
+ GradientInfo *info;
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ gimp_gradient_split_at (gradient_tool->gradient,
+ GIMP_CONTEXT (options), NULL, value,
+ paint_options->gradient_options->gradient_blend_color_space,
+ &seg, NULL);
+
+ stop =
+ gimp_gradient_segment_range_get_n_segments (gradient_tool->gradient,
+ gradient_tool->gradient->segments,
+ seg) - 1;
+
+ info = gradient_tool->undo_stack->data;
+ info->added_handle = stop;
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+
+ return stop;
+}
+
+static void
+gimp_gradient_tool_editor_delete_stop (GimpGradientTool *gradient_tool,
+ gint slider)
+{
+ GradientInfo *info;
+
+ gimp_assert (gimp_gradient_tool_editor_handle_is_stop (gradient_tool, slider));
+
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ info = gradient_tool->undo_stack->data;
+
+ if (info->added_handle == slider)
+ {
+ /* when removing a stop that was added as part of the current action,
+ * restore the original gradient at the beginning of the action, rather
+ * than deleting the stop from the current gradient, so that the affected
+ * midpoint returns to its state at the beginning of the action, instead
+ * of being reset.
+ *
+ * note that this assumes that the gradient hasn't changed in any other
+ * way during the action, which is ugly, but currently always true.
+ */
+
+ gimp_assert (info->gradient != NULL);
+
+ gimp_data_copy (GIMP_DATA (gradient_tool->gradient),
+ GIMP_DATA (info->gradient));
+
+ g_clear_object (&info->gradient);
+
+ info->added_handle = GIMP_TOOL_LINE_HANDLE_NONE;
+ }
+ else
+ {
+ GimpGradientSegment *seg;
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, slider);
+
+ gimp_gradient_segment_range_merge (gradient_tool->gradient,
+ seg, seg->next, NULL, NULL);
+
+ info->removed_handle = slider;
+ }
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+}
+
+static gint
+gimp_gradient_tool_editor_midpoint_to_stop (GimpGradientTool *gradient_tool,
+ gint slider)
+{
+ const GimpControllerSlider *sliders;
+
+ gimp_assert (gimp_gradient_tool_editor_handle_is_midpoint (gradient_tool, slider));
+
+ sliders = gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ NULL);
+
+ if (sliders[slider].value > sliders[slider].min + EPSILON &&
+ sliders[slider].value < sliders[slider].max - EPSILON)
+ {
+ const GimpGradientSegment *seg;
+ gint stop;
+ GradientInfo *info;
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, slider);
+
+ stop = gimp_gradient_tool_editor_add_stop (gradient_tool, seg->middle);
+
+ info = gradient_tool->undo_stack->data;
+ info->removed_handle = slider;
+
+ slider = stop;
+ }
+
+ return slider;
+}
+
+static void
+gimp_gradient_tool_editor_update_sliders (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ gdouble offset = options->offset / 100.0;
+ gboolean editable;
+ GimpControllerSlider *sliders;
+ gint n_sliders;
+ gint n_segments;
+ GimpGradientSegment *seg;
+ GimpControllerSlider *slider;
+ gint i;
+
+ if (! gradient_tool->widget || options->instant)
+ return;
+
+ editable = gimp_gradient_tool_editor_is_gradient_editable (gradient_tool);
+
+ n_segments = gimp_gradient_segment_range_get_n_segments (
+ gradient_tool->gradient, gradient_tool->gradient->segments, NULL);
+
+ n_sliders = (n_segments - 1) + /* gradient stops, between each adjacent
+ * pair of segments */
+ (n_segments); /* midpoints, inside each segment */
+
+ sliders = g_new (GimpControllerSlider, n_sliders);
+
+ slider = sliders;
+
+ /* initialize the gradient-stop sliders */
+ for (seg = gradient_tool->gradient->segments, i = 0;
+ seg->next;
+ seg = seg->next, i++)
+ {
+ *slider = GIMP_CONTROLLER_SLIDER_DEFAULT;
+
+ slider->value = seg->right;
+ slider->min = seg->left;
+ slider->max = seg->next->right;
+
+ slider->movable = editable;
+ slider->removable = editable;
+
+ slider->data = GINT_TO_POINTER (i);
+
+ slider++;
+ }
+
+ /* initialize the midpoint sliders */
+ for (seg = gradient_tool->gradient->segments, i = 0;
+ seg;
+ seg = seg->next, i++)
+ {
+ *slider = GIMP_CONTROLLER_SLIDER_DEFAULT;
+
+ slider->value = seg->middle;
+ slider->min = seg->left;
+ slider->max = seg->right;
+
+ /* hide midpoints of zero-length segments, since they'd otherwise
+ * prevent the segment's endpoints from being selected
+ */
+ slider->visible = fabs (slider->max - slider->min) > EPSILON;
+ slider->movable = editable;
+
+ slider->autohide = TRUE;
+ slider->type = GIMP_HANDLE_FILLED_CIRCLE;
+ slider->size = 0.6;
+
+ slider->data = GINT_TO_POINTER (i);
+
+ slider++;
+ }
+
+ /* flip the slider limits and values, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ {
+ for (i = 0; i < n_sliders; i++)
+ {
+ gdouble temp;
+
+ sliders[i].value = 1.0 - sliders[i].value;
+ temp = sliders[i].min;
+ sliders[i].min = 1.0 - sliders[i].max;
+ sliders[i].max = 1.0 - temp;
+ }
+ }
+
+ /* adjust the sliders according to the offset */
+ for (i = 0; i < n_sliders; i++)
+ {
+ sliders[i].value = (1.0 - offset) * sliders[i].value + offset;
+ sliders[i].min = (1.0 - offset) * sliders[i].min + offset;
+ sliders[i].max = (1.0 - offset) * sliders[i].max + offset;
+ }
+
+ /* avoid updating the gradient in gimp_gradient_tool_editor_line_changed() */
+ gimp_gradient_tool_editor_block_handlers (gradient_tool);
+
+ gimp_tool_line_set_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ sliders, n_sliders);
+
+ gimp_gradient_tool_editor_unblock_handlers (gradient_tool);
+
+ g_free (sliders);
+}
+
+static void
+gimp_gradient_tool_editor_purge_gradient_history (GSList **stack)
+{
+ GSList *link;
+
+ /* eliminate all history steps that modify the gradient */
+ while ((link = *stack))
+ {
+ GradientInfo *info = link->data;
+
+ if (info->gradient)
+ {
+ gimp_gradient_tool_editor_gradient_info_free (info);
+
+ *stack = g_slist_delete_link (*stack, link);
+ }
+ else
+ {
+ stack = &link->next;
+ }
+ }
+}
+
+static void
+gimp_gradient_tool_editor_purge_gradient (GimpGradientTool *gradient_tool)
+{
+ if (gradient_tool->widget)
+ {
+ gimp_gradient_tool_editor_update_sliders (gradient_tool);
+
+ gimp_tool_line_set_selection (GIMP_TOOL_LINE (gradient_tool->widget),
+ GIMP_TOOL_LINE_HANDLE_NONE);
+ }
+
+ gimp_gradient_tool_editor_purge_gradient_history (&gradient_tool->undo_stack);
+ gimp_gradient_tool_editor_purge_gradient_history (&gradient_tool->redo_stack);
+}
+
+static GtkWidget *
+gimp_gradient_tool_editor_color_entry_new (GimpGradientTool *gradient_tool,
+ const gchar *title,
+ Direction direction,
+ GtkWidget *chain_button,
+ GtkWidget **color_panel,
+ GtkWidget **type_combo)
+{
+ GimpContext *context = GIMP_CONTEXT (GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool));
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *combo;
+ GimpRGB color = {};
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+
+ /* the color panel */
+ *color_panel = button = gimp_color_panel_new (title, &color,
+ GIMP_COLOR_AREA_SMALL_CHECKS,
+ 24, 24);
+ gimp_color_button_set_update (GIMP_COLOR_BUTTON (button), TRUE);
+ gimp_color_panel_set_context (GIMP_COLOR_PANEL (button), context);
+ gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+ gtk_widget_show (button);
+
+ g_object_set_data (G_OBJECT (button),
+ "gimp-gradient-tool-editor-direction",
+ GINT_TO_POINTER (direction));
+ g_object_set_data (G_OBJECT (button),
+ "gimp-gradient-tool-editor-chain-button",
+ chain_button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_gradient_tool_editor_color_entry_color_clicked),
+ gradient_tool);
+ g_signal_connect (button, "color-changed",
+ G_CALLBACK (gimp_gradient_tool_editor_color_entry_color_changed),
+ gradient_tool);
+ g_signal_connect (button, "response",
+ G_CALLBACK (gimp_gradient_tool_editor_color_entry_color_response),
+ gradient_tool);
+
+ /* the color type combo */
+ *type_combo = combo = gimp_enum_combo_box_new (GIMP_TYPE_GRADIENT_COLOR);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ g_object_set_data (G_OBJECT (combo),
+ "gimp-gradient-tool-editor-direction",
+ GINT_TO_POINTER (direction));
+ g_object_set_data (G_OBJECT (combo),
+ "gimp-gradient-tool-editor-chain-button",
+ chain_button);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_gradient_tool_editor_color_entry_type_changed),
+ gradient_tool);
+
+ return hbox;
+}
+
+static void
+gimp_gradient_tool_editor_init_endpoint_gui (GimpGradientTool *gradient_tool)
+{
+ GimpDisplay *display = GIMP_TOOL (gradient_tool)->display;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gdouble xres;
+ gdouble yres;
+ GtkWidget *editor;
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *spinbutton;
+ GtkWidget *se;
+ GtkWidget *hbox;
+ gint row = 0;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ /* the endpoint editor */
+ gradient_tool->endpoint_editor =
+ editor = gimp_editor_new ();
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (gradient_tool->gui)),
+ editor, FALSE, TRUE, 0);
+
+ /* the main table */
+ table = gtk_table_new (1, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_box_pack_start (GTK_BOX (editor), table, FALSE, TRUE, 0);
+ gtk_widget_show (table);
+
+ /* the position labels */
+ label = gtk_label_new (_("X:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (_("Y:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row + 1, row + 2,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the position size entry */
+ spinbutton = gimp_spin_button_new_with_range (0.0, 0.0, 1.0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 6);
+
+ gradient_tool->endpoint_se =
+ se = gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a",
+ TRUE, TRUE, FALSE, 6,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gtk_table_set_row_spacings (GTK_TABLE (se), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (se), 2);
+
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (se),
+ GTK_SPIN_BUTTON (spinbutton), NULL);
+ gtk_table_attach_defaults (GTK_TABLE (se), spinbutton, 1, 2, 0, 1);
+ gtk_widget_show (spinbutton);
+
+ gtk_table_attach (GTK_TABLE (table), se, 1, 2, row, row + 2,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (se);
+
+ gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (se), shell->unit);
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (se), 0, xres, FALSE);
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (se), 1, yres, FALSE);
+
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (se), 0,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (se), 1,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (se), 0,
+ 0, gimp_image_get_width (image));
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (se), 1,
+ 0, gimp_image_get_height (image));
+
+ g_signal_connect (se, "value-changed",
+ G_CALLBACK (gimp_gradient_tool_editor_endpoint_se_value_changed),
+ gradient_tool);
+
+ row += 2;
+
+ /* the color label */
+ label = gtk_label_new (_("Color:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the color entry */
+ hbox = gimp_gradient_tool_editor_color_entry_new (
+ gradient_tool, _("Change Endpoint Color"), DIRECTION_NONE, NULL,
+ &gradient_tool->endpoint_color_panel, &gradient_tool->endpoint_type_combo);
+ gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (hbox);
+
+ row++;
+}
+
+static void
+gimp_gradient_tool_editor_init_stop_gui (GimpGradientTool *gradient_tool)
+{
+ GtkWidget *editor;
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *se;
+ GtkWidget *table2;
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GtkWidget *separator;
+ gint row = 0;
+
+ /* the stop editor */
+ gradient_tool->stop_editor =
+ editor = gimp_editor_new ();
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (gradient_tool->gui)),
+ editor, FALSE, TRUE, 0);
+
+ /* the main table */
+ table = gtk_table_new (1, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_box_pack_start (GTK_BOX (editor), table, FALSE, TRUE, 0);
+ gtk_widget_show (table);
+
+ /* the position label */
+ label = gtk_label_new (_("Position:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the position size entry */
+ gradient_tool->stop_se =
+ se = gimp_size_entry_new (1, GIMP_UNIT_PERCENT, "%a",
+ FALSE, TRUE, FALSE, 6,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gimp_size_entry_show_unit_menu (GIMP_SIZE_ENTRY (se), FALSE);
+ gtk_table_attach (GTK_TABLE (table), se, 1, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (se);
+
+ g_signal_connect (se, "value-changed",
+ G_CALLBACK (gimp_gradient_tool_editor_stop_se_value_changed),
+ gradient_tool);
+
+ row++;
+
+ /* the color labels */
+ label = gtk_label_new (_("Left color:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (_("Right color:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row + 1, row + 2,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the color entries table */
+ table2 = gtk_table_new (1, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table2), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (table2), 2);
+ gtk_table_attach (GTK_TABLE (table), table2, 1, 2, row, row + 2,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (table2);
+
+ /* the color entries chain button */
+ gradient_tool->stop_chain_button =
+ button = gimp_chain_button_new (GIMP_CHAIN_RIGHT);
+ gtk_table_attach (GTK_TABLE (table2), button, 1, 2, 0, 2,
+ GTK_SHRINK | GTK_FILL,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ 0, 0);
+ gtk_widget_show (button);
+
+ /* the color entries */
+ hbox = gimp_gradient_tool_editor_color_entry_new (
+ gradient_tool, _("Change Stop Color"), DIRECTION_LEFT, button,
+ &gradient_tool->stop_left_color_panel, &gradient_tool->stop_left_type_combo);
+ gtk_table_attach (GTK_TABLE (table2), hbox, 0, 1, 0, 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (hbox);
+
+ hbox = gimp_gradient_tool_editor_color_entry_new (
+ gradient_tool, _("Change Stop Color"), DIRECTION_RIGHT, button,
+ &gradient_tool->stop_right_color_panel, &gradient_tool->stop_right_type_combo);
+ gtk_table_attach (GTK_TABLE (table2), hbox, 0, 1, 1, 2,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (hbox);
+
+ row += 2;
+
+ /* the action buttons separator */
+ separator = gtk_hseparator_new ();
+ gtk_table_attach (GTK_TABLE (table), separator, 0, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (separator);
+
+ row++;
+
+ /* the delete button */
+ gimp_editor_add_button (GIMP_EDITOR (editor),
+ GIMP_ICON_EDIT_DELETE, _("Delete stop"),
+ NULL,
+ G_CALLBACK (gimp_gradient_tool_editor_stop_delete_clicked),
+ NULL, gradient_tool);
+}
+
+static void
+gimp_gradient_tool_editor_init_midpoint_gui (GimpGradientTool *gradient_tool)
+{
+ GtkWidget *editor;
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *se;
+ GtkWidget *combo;
+ GtkWidget *separator;
+ gint row = 0;
+
+ /* the stop editor */
+ gradient_tool->midpoint_editor =
+ editor = gimp_editor_new ();
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (gradient_tool->gui)),
+ editor, FALSE, TRUE, 0);
+
+ /* the main table */
+ table = gtk_table_new (1, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_box_pack_start (GTK_BOX (editor), table, FALSE, TRUE, 0);
+ gtk_widget_show (table);
+
+ /* the position label */
+ label = gtk_label_new (_("Position:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the position size entry */
+ gradient_tool->midpoint_se =
+ se = gimp_size_entry_new (1, GIMP_UNIT_PERCENT, "%a",
+ FALSE, TRUE, FALSE, 6,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gimp_size_entry_show_unit_menu (GIMP_SIZE_ENTRY (se), FALSE);
+ gtk_table_attach (GTK_TABLE (table), se, 1, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (se);
+
+ g_signal_connect (se, "value-changed",
+ G_CALLBACK (gimp_gradient_tool_editor_midpoint_se_value_changed),
+ gradient_tool);
+
+ row++;
+
+ /* the type label */
+ label = gtk_label_new (_("Blending:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the type combo */
+ gradient_tool->midpoint_type_combo =
+ combo = gimp_enum_combo_box_new (GIMP_TYPE_GRADIENT_SEGMENT_TYPE);
+ gtk_table_attach (GTK_TABLE (table), combo, 1, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (combo);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_gradient_tool_editor_midpoint_type_changed),
+ gradient_tool);
+
+ row++;
+
+ /* the color label */
+ label = gtk_label_new (_("Coloring:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
+ GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ /* the color combo */
+ gradient_tool->midpoint_color_combo =
+ combo = gimp_enum_combo_box_new (GIMP_TYPE_GRADIENT_SEGMENT_COLOR);
+ gtk_table_attach (GTK_TABLE (table), combo, 1, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (combo);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_gradient_tool_editor_midpoint_color_changed),
+ gradient_tool);
+
+ row++;
+
+ /* the action buttons separator */
+ separator = gtk_hseparator_new ();
+ gtk_table_attach (GTK_TABLE (table), separator, 0, 2, row, row + 1,
+ GTK_SHRINK | GTK_FILL | GTK_EXPAND,
+ GTK_SHRINK | GTK_FILL,
+ 0, 0);
+ gtk_widget_show (separator);
+
+ row++;
+
+ /* the new stop button */
+ gradient_tool->midpoint_new_stop_button =
+ gimp_editor_add_button (GIMP_EDITOR (editor),
+ GIMP_ICON_DOCUMENT_NEW, _("New stop at midpoint"),
+ NULL,
+ G_CALLBACK (gimp_gradient_tool_editor_midpoint_new_stop_clicked),
+ NULL, gradient_tool);
+
+ /* the center button */
+ gradient_tool->midpoint_center_button =
+ gimp_editor_add_button (GIMP_EDITOR (editor),
+ GIMP_ICON_CENTER_HORIZONTAL, _("Center midpoint"),
+ NULL,
+ G_CALLBACK (gimp_gradient_tool_editor_midpoint_center_clicked),
+ NULL, gradient_tool);
+}
+
+static void
+gimp_gradient_tool_editor_update_endpoint_gui (GimpGradientTool *gradient_tool,
+ gint selection)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ GimpContext *context = GIMP_CONTEXT (options);
+ gboolean editable;
+ GimpGradientSegment *seg;
+ const gchar *title;
+ gdouble x;
+ gdouble y;
+ GimpRGB color;
+ GimpGradientColor color_type;
+
+ editable = gimp_gradient_tool_editor_is_gradient_editable (gradient_tool);
+
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ g_object_get (gradient_tool->widget,
+ "x1", &x,
+ "y1", &y,
+ NULL);
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ g_object_get (gradient_tool->widget,
+ "x2", &x,
+ "y2", &y,
+ NULL);
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ }
+
+ /* swap the endpoint handles, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ {
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ selection = GIMP_TOOL_LINE_HANDLE_END;
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ selection = GIMP_TOOL_LINE_HANDLE_START;
+ break;
+ }
+ }
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ title = _("Start Endpoint");
+
+ gimp_gradient_segment_get_left_flat_color (gradient_tool->gradient, context,
+ seg, &color);
+ color_type = seg->left_color_type;
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ title = _("End Endpoint");
+
+ gimp_gradient_segment_get_right_flat_color (gradient_tool->gradient, context,
+ seg, &color);
+ color_type = seg->right_color_type;
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ }
+
+ gimp_tool_gui_set_title (gradient_tool->gui, title);
+
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (gradient_tool->endpoint_se), 0, x);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (gradient_tool->endpoint_se), 1, y);
+
+ gimp_color_button_set_color (
+ GIMP_COLOR_BUTTON (gradient_tool->endpoint_color_panel), &color);
+ gimp_int_combo_box_set_active (
+ GIMP_INT_COMBO_BOX (gradient_tool->endpoint_type_combo), color_type);
+
+ gtk_widget_set_sensitive (gradient_tool->endpoint_color_panel, editable);
+ gtk_widget_set_sensitive (gradient_tool->endpoint_type_combo, editable);
+
+ gtk_widget_show (gradient_tool->endpoint_editor);
+}
+
+static void
+gimp_gradient_tool_editor_update_stop_gui (GimpGradientTool *gradient_tool,
+ gint selection)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+ gboolean editable;
+ GimpGradientSegment *seg;
+ gint index;
+ gchar *title;
+ gdouble min;
+ gdouble max;
+ gdouble value;
+ GimpRGB left_color;
+ GimpGradientColor left_color_type;
+ GimpRGB right_color;
+ GimpGradientColor right_color_type;
+
+ editable = gimp_gradient_tool_editor_is_gradient_editable (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ index = GPOINTER_TO_INT (
+ gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ NULL)[selection].data);
+
+ title = g_strdup_printf (_("Stop %d"), index + 1);
+
+ min = seg->left;
+ max = seg->next->right;
+ value = seg->right;
+
+ gimp_gradient_segment_get_right_flat_color (gradient_tool->gradient, context,
+ seg, &left_color);
+ left_color_type = seg->right_color_type;
+
+ gimp_gradient_segment_get_left_flat_color (gradient_tool->gradient, context,
+ seg->next, &right_color);
+ right_color_type = seg->next->left_color_type;
+
+ gimp_tool_gui_set_title (gradient_tool->gui, title);
+
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (gradient_tool->stop_se),
+ 0, 100.0 * min, 100.0 * max);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (gradient_tool->stop_se),
+ 0, 100.0 * value);
+
+ gimp_color_button_set_color (
+ GIMP_COLOR_BUTTON (gradient_tool->stop_left_color_panel), &left_color);
+ gimp_int_combo_box_set_active (
+ GIMP_INT_COMBO_BOX (gradient_tool->stop_left_type_combo), left_color_type);
+
+ gimp_color_button_set_color (
+ GIMP_COLOR_BUTTON (gradient_tool->stop_right_color_panel), &right_color);
+ gimp_int_combo_box_set_active (
+ GIMP_INT_COMBO_BOX (gradient_tool->stop_right_type_combo), right_color_type);
+
+ gtk_widget_set_sensitive (gradient_tool->stop_se, editable);
+ gtk_widget_set_sensitive (gradient_tool->stop_left_color_panel, editable);
+ gtk_widget_set_sensitive (gradient_tool->stop_left_type_combo, editable);
+ gtk_widget_set_sensitive (gradient_tool->stop_right_color_panel, editable);
+ gtk_widget_set_sensitive (gradient_tool->stop_right_type_combo, editable);
+ gtk_widget_set_sensitive (gradient_tool->stop_chain_button, editable);
+ gtk_widget_set_sensitive (
+ GTK_WIDGET (gimp_editor_get_button_box (GIMP_EDITOR (gradient_tool->stop_editor))),
+ editable);
+
+ g_free (title);
+
+ gtk_widget_show (gradient_tool->stop_editor);
+}
+
+static void
+gimp_gradient_tool_editor_update_midpoint_gui (GimpGradientTool *gradient_tool,
+ gint selection)
+{
+ gboolean editable;
+ const GimpGradientSegment *seg;
+ gint index;
+ gchar *title;
+ gdouble min;
+ gdouble max;
+ gdouble value;
+ GimpGradientSegmentType type;
+ GimpGradientSegmentColor color;
+
+ editable = gimp_gradient_tool_editor_is_gradient_editable (gradient_tool);
+
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, selection);
+
+ index = GPOINTER_TO_INT (
+ gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ NULL)[selection].data);
+
+ title = g_strdup_printf (_("Midpoint %d"), index + 1);
+
+ min = seg->left;
+ max = seg->right;
+ value = seg->middle;
+ type = seg->type;
+ color = seg->color;
+
+ gimp_tool_gui_set_title (gradient_tool->gui, title);
+
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (gradient_tool->midpoint_se),
+ 0, 100.0 * min, 100.0 * max);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (gradient_tool->midpoint_se),
+ 0, 100.0 * value);
+
+ gimp_int_combo_box_set_active (
+ GIMP_INT_COMBO_BOX (gradient_tool->midpoint_type_combo), type);
+
+ gimp_int_combo_box_set_active (
+ GIMP_INT_COMBO_BOX (gradient_tool->midpoint_color_combo), color);
+
+ gtk_widget_set_sensitive (gradient_tool->midpoint_new_stop_button,
+ value > min + EPSILON && value < max - EPSILON);
+ gtk_widget_set_sensitive (gradient_tool->midpoint_center_button,
+ fabs (value - (min + max) / 2.0) > EPSILON);
+
+ gtk_widget_set_sensitive (gradient_tool->midpoint_se, editable);
+ gtk_widget_set_sensitive (gradient_tool->midpoint_type_combo, editable);
+ gtk_widget_set_sensitive (gradient_tool->midpoint_color_combo, editable);
+ gtk_widget_set_sensitive (
+ GTK_WIDGET (gimp_editor_get_button_box (GIMP_EDITOR (gradient_tool->midpoint_editor))),
+ editable);
+
+ g_free (title);
+
+ gtk_widget_show (gradient_tool->midpoint_editor);
+}
+
+static void
+gimp_gradient_tool_editor_update_gui (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+
+ if (gradient_tool->gradient && gradient_tool->widget && ! options->instant)
+ {
+ gint selection;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (selection != GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ if (! gradient_tool->gui)
+ {
+ GimpDisplayShell *shell;
+
+ shell = gimp_tool_widget_get_shell (gradient_tool->widget);
+
+ gradient_tool->gui =
+ gimp_tool_gui_new (GIMP_TOOL (gradient_tool)->tool_info,
+ NULL, NULL, NULL, NULL,
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ TRUE,
+
+ _("_Close"), GTK_RESPONSE_CLOSE,
+
+ NULL);
+
+ gimp_tool_gui_set_shell (gradient_tool->gui, shell);
+ gimp_tool_gui_set_viewable (gradient_tool->gui,
+ GIMP_VIEWABLE (gradient_tool->gradient));
+ gimp_tool_gui_set_auto_overlay (gradient_tool->gui, TRUE);
+
+ g_signal_connect (gradient_tool->gui, "response",
+ G_CALLBACK (gimp_gradient_tool_editor_gui_response),
+ gradient_tool);
+
+ gimp_gradient_tool_editor_init_endpoint_gui (gradient_tool);
+ gimp_gradient_tool_editor_init_stop_gui (gradient_tool);
+ gimp_gradient_tool_editor_init_midpoint_gui (gradient_tool);
+ }
+
+ gimp_gradient_tool_editor_block_handlers (gradient_tool);
+
+ if (gimp_gradient_tool_editor_handle_is_endpoint (gradient_tool, selection))
+ gimp_gradient_tool_editor_update_endpoint_gui (gradient_tool, selection);
+ else
+ gtk_widget_hide (gradient_tool->endpoint_editor);
+
+ if (gimp_gradient_tool_editor_handle_is_stop (gradient_tool, selection))
+ gimp_gradient_tool_editor_update_stop_gui (gradient_tool, selection);
+ else
+ gtk_widget_hide (gradient_tool->stop_editor);
+
+ if (gimp_gradient_tool_editor_handle_is_midpoint (gradient_tool, selection))
+ gimp_gradient_tool_editor_update_midpoint_gui (gradient_tool, selection);
+ else
+ gtk_widget_hide (gradient_tool->midpoint_editor);
+
+ gimp_gradient_tool_editor_unblock_handlers (gradient_tool);
+
+ gimp_tool_gui_show (gradient_tool->gui);
+
+ return;
+ }
+ }
+
+ if (gradient_tool->gui)
+ gimp_tool_gui_hide (gradient_tool->gui);
+}
+
+static GradientInfo *
+gimp_gradient_tool_editor_gradient_info_new (GimpGradientTool *gradient_tool)
+{
+ GradientInfo *info = g_slice_new (GradientInfo);
+
+ info->start_x = gradient_tool->start_x;
+ info->start_y = gradient_tool->start_y;
+ info->end_x = gradient_tool->end_x;
+ info->end_y = gradient_tool->end_y;
+
+ info->gradient = NULL;
+
+ info->added_handle = GIMP_TOOL_LINE_HANDLE_NONE;
+ info->removed_handle = GIMP_TOOL_LINE_HANDLE_NONE;
+ info->selected_handle = GIMP_TOOL_LINE_HANDLE_NONE;
+
+ return info;
+}
+
+static void
+gimp_gradient_tool_editor_gradient_info_free (GradientInfo *info)
+{
+ if (info->gradient)
+ g_object_unref (info->gradient);
+
+ g_slice_free (GradientInfo, info);
+}
+
+static void
+gimp_gradient_tool_editor_gradient_info_apply (GimpGradientTool *gradient_tool,
+ const GradientInfo *info,
+ gboolean set_selection)
+{
+ gint selection;
+
+ gimp_assert (gradient_tool->widget != NULL);
+ gimp_assert (gradient_tool->gradient != NULL);
+
+ /* pick the handle to select */
+ if (info->gradient)
+ {
+ if (info->removed_handle != GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ /* we're undoing a stop-deletion or midpoint-to-stop operation;
+ * select the removed handle
+ */
+ selection = info->removed_handle;
+ }
+ else if (info->added_handle != GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ /* we're undoing a stop addition operation */
+ gimp_assert (gimp_gradient_tool_editor_handle_is_stop (gradient_tool,
+ info->added_handle));
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ /* if the selected handle is a stop... */
+ if (gimp_gradient_tool_editor_handle_is_stop (gradient_tool, selection))
+ {
+ /* if the added handle is selected, clear the selection */
+ if (selection == info->added_handle)
+ selection = GIMP_TOOL_LINE_HANDLE_NONE;
+ /* otherwise, keep the currently selected stop, possibly
+ * adjusting its handle index
+ */
+ else if (selection > info->added_handle)
+ selection--;
+ }
+ /* otherwise, if the selected handle is a midpoint... */
+ else if (gimp_gradient_tool_editor_handle_is_midpoint (gradient_tool, selection))
+ {
+ const GimpControllerSlider *sliders;
+ gint seg_i;
+
+ sliders =
+ gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ NULL);
+
+ seg_i = GPOINTER_TO_INT (sliders[selection].data);
+
+ /* if the midpoint belongs to one of the two segments incident to
+ * the added stop, clear the selection
+ */
+ if (seg_i == info->added_handle ||
+ seg_i == info->added_handle + 1)
+ {
+ selection = GIMP_TOOL_LINE_HANDLE_NONE;
+ }
+ /* otherwise, keep the currently selected stop, adjusting its
+ * handle index
+ */
+ else
+ {
+ /* midpoint handles follow stop handles; since we removed a
+ * stop, we must decrement the handle index
+ */
+ selection--;
+
+ if (seg_i > info->added_handle)
+ selection--;
+ }
+ }
+ /* otherwise, don't change the selection */
+ else
+ {
+ set_selection = FALSE;
+ }
+ }
+ else if (info->selected_handle != GIMP_TOOL_LINE_HANDLE_NONE)
+ {
+ /* we're undoing a property change operation; select the handle
+ * corresponding to the affected object
+ */
+ selection = info->selected_handle;
+ }
+ else
+ {
+ /* something went wrong... */
+ g_warn_if_reached ();
+
+ set_selection = FALSE;
+ }
+ }
+ else if ((info->start_x != gradient_tool->start_x ||
+ info->start_y != gradient_tool->start_y) &&
+ (info->end_x == gradient_tool->end_x &&
+ info->end_y == gradient_tool->end_y))
+ {
+ /* we're undoing a start-endpoint move operation; select the start
+ * endpoint
+ */
+ selection = GIMP_TOOL_LINE_HANDLE_START;
+ }
+ else if ((info->end_x != gradient_tool->end_x ||
+ info->end_y != gradient_tool->end_y) &&
+ (info->start_x == gradient_tool->start_x &&
+ info->start_y == gradient_tool->start_y))
+
+ {
+ /* we're undoing am end-endpoint move operation; select the end
+ * endpoint
+ */
+ selection = GIMP_TOOL_LINE_HANDLE_END;
+ }
+ else
+ {
+ /* we're undoing a line move operation; don't change the selection */
+ set_selection = FALSE;
+ }
+
+ gimp_gradient_tool_editor_block_handlers (gradient_tool);
+
+ g_object_set (gradient_tool->widget,
+ "x1", info->start_x,
+ "y1", info->start_y,
+ "x2", info->end_x,
+ "y2", info->end_y,
+ NULL);
+
+ if (info->gradient)
+ {
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ gimp_data_copy (GIMP_DATA (gradient_tool->gradient),
+ GIMP_DATA (info->gradient));
+
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ }
+
+ if (set_selection)
+ {
+ gimp_tool_line_set_selection (GIMP_TOOL_LINE (gradient_tool->widget),
+ selection);
+ }
+
+ gimp_gradient_tool_editor_update_gui (gradient_tool);
+
+ gimp_gradient_tool_editor_unblock_handlers (gradient_tool);
+}
+
+static gboolean
+gimp_gradient_tool_editor_gradient_info_is_trivial (GimpGradientTool *gradient_tool,
+ const GradientInfo *info)
+{
+ const GimpGradientSegment *seg1;
+ const GimpGradientSegment *seg2;
+
+ if (info->start_x != gradient_tool->start_x ||
+ info->start_y != gradient_tool->start_y ||
+ info->end_x != gradient_tool->end_x ||
+ info->end_y != gradient_tool->end_y)
+ {
+ return FALSE;
+ }
+
+ if (info->gradient)
+ {
+ for (seg1 = info->gradient->segments, seg2 = gradient_tool->gradient->segments;
+ seg1 && seg2;
+ seg1 = seg1->next, seg2 = seg2->next)
+ {
+ if (memcmp (seg1, seg2, G_STRUCT_OFFSET (GimpGradientSegment, prev)))
+ return FALSE;
+ }
+
+ if (seg1 || seg2)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+void
+gimp_gradient_tool_editor_options_notify (GimpGradientTool *gradient_tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ if (! strcmp (pspec->name, "modify-active"))
+ {
+ gimp_gradient_tool_editor_update_sliders (gradient_tool);
+ gimp_gradient_tool_editor_update_gui (gradient_tool);
+ }
+ else if (! strcmp (pspec->name, "gradient-reverse"))
+ {
+ gimp_gradient_tool_editor_update_sliders (gradient_tool);
+
+ /* if an endpoint is selected, swap the selected endpoint */
+ if (gradient_tool->widget)
+ {
+ gint selection;
+
+ selection =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ switch (selection)
+ {
+ case GIMP_TOOL_LINE_HANDLE_START:
+ gimp_tool_line_set_selection (GIMP_TOOL_LINE (gradient_tool->widget),
+ GIMP_TOOL_LINE_HANDLE_END);
+ break;
+
+ case GIMP_TOOL_LINE_HANDLE_END:
+ gimp_tool_line_set_selection (GIMP_TOOL_LINE (gradient_tool->widget),
+ GIMP_TOOL_LINE_HANDLE_START);
+ break;
+ }
+ }
+ }
+ else if (gradient_tool->render_node &&
+ gegl_node_find_property (gradient_tool->render_node, pspec->name))
+ {
+ gimp_gradient_tool_editor_update_sliders (gradient_tool);
+ }
+}
+
+void
+gimp_gradient_tool_editor_start (GimpGradientTool *gradient_tool)
+{
+ g_signal_connect (gradient_tool->widget, "can-add-slider",
+ G_CALLBACK (gimp_gradient_tool_editor_line_can_add_slider),
+ gradient_tool);
+ g_signal_connect (gradient_tool->widget, "add-slider",
+ G_CALLBACK (gimp_gradient_tool_editor_line_add_slider),
+ gradient_tool);
+ g_signal_connect (gradient_tool->widget, "prepare-to-remove-slider",
+ G_CALLBACK (gimp_gradient_tool_editor_line_prepare_to_remove_slider),
+ gradient_tool);
+ g_signal_connect (gradient_tool->widget, "remove-slider",
+ G_CALLBACK (gimp_gradient_tool_editor_line_remove_slider),
+ gradient_tool);
+ g_signal_connect (gradient_tool->widget, "selection-changed",
+ G_CALLBACK (gimp_gradient_tool_editor_line_selection_changed),
+ gradient_tool);
+ g_signal_connect (gradient_tool->widget, "handle-clicked",
+ G_CALLBACK (gimp_gradient_tool_editor_line_handle_clicked),
+ gradient_tool);
+}
+
+void
+gimp_gradient_tool_editor_halt (GimpGradientTool *gradient_tool)
+{
+ g_clear_object (&gradient_tool->gui);
+
+ gradient_tool->edit_count = 0;
+
+ if (gradient_tool->undo_stack)
+ {
+ g_slist_free_full (gradient_tool->undo_stack,
+ (GDestroyNotify) gimp_gradient_tool_editor_gradient_info_free);
+ gradient_tool->undo_stack = NULL;
+ }
+
+ if (gradient_tool->redo_stack)
+ {
+ g_slist_free_full (gradient_tool->redo_stack,
+ (GDestroyNotify) gimp_gradient_tool_editor_gradient_info_free);
+ gradient_tool->redo_stack = NULL;
+ }
+
+ if (gradient_tool->flush_idle_id)
+ {
+ g_source_remove (gradient_tool->flush_idle_id);
+ gradient_tool->flush_idle_id = 0;
+ }
+}
+
+gboolean
+gimp_gradient_tool_editor_line_changed (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_OPTIONS (options);
+ gdouble offset = options->offset / 100.0;
+ const GimpControllerSlider *sliders;
+ gint n_sliders;
+ gint i;
+ GimpGradientSegment *seg;
+ gboolean changed = FALSE;
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return FALSE;
+
+ if (! gradient_tool->gradient || offset == 1.0)
+ return FALSE;
+
+ sliders = gimp_tool_line_get_sliders (GIMP_TOOL_LINE (gradient_tool->widget),
+ &n_sliders);
+
+ if (n_sliders == 0)
+ return FALSE;
+
+ /* update the midpoints first, since moving the gradient stops may change the
+ * gradient's midpoints w.r.t. the sliders, but not the other way around.
+ */
+ for (seg = gradient_tool->gradient->segments, i = n_sliders / 2;
+ seg;
+ seg = seg->next, i++)
+ {
+ gdouble value;
+
+ value = sliders[i].value;
+
+ /* adjust slider value according to the offset */
+ value = (value - offset) / (1.0 - offset);
+
+ /* flip the slider value, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ value = 1.0 - value;
+
+ if (fabs (value - seg->middle) > EPSILON)
+ {
+ if (! changed)
+ {
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ /* refetch the segment, since the gradient might have changed */
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, i);
+
+ changed = TRUE;
+ }
+
+ seg->middle = value;
+ }
+ }
+
+ /* update the gradient stops */
+ for (seg = gradient_tool->gradient->segments, i = 0;
+ seg->next;
+ seg = seg->next, i++)
+ {
+ gdouble value;
+
+ value = sliders[i].value;
+
+ /* adjust slider value according to the offset */
+ value = (value - offset) / (1.0 - offset);
+
+ /* flip the slider value, if necessary */
+ if (paint_options->gradient_options->gradient_reverse)
+ value = 1.0 - value;
+
+ if (fabs (value - seg->right) > EPSILON)
+ {
+ if (! changed)
+ {
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+ gimp_gradient_tool_editor_freeze_gradient (gradient_tool);
+
+ /* refetch the segment, since the gradient might have changed */
+ seg = gimp_gradient_tool_editor_handle_get_segment (gradient_tool, i);
+
+ changed = TRUE;
+ }
+
+ gimp_gradient_segment_range_compress (gradient_tool->gradient,
+ seg, seg,
+ seg->left, value);
+ gimp_gradient_segment_range_compress (gradient_tool->gradient,
+ seg->next, seg->next,
+ value, seg->next->right);
+ }
+ }
+
+ if (changed)
+ {
+ gimp_gradient_tool_editor_thaw_gradient (gradient_tool);
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+ }
+
+ gimp_gradient_tool_editor_update_gui (gradient_tool);
+
+ return changed;
+}
+
+void
+gimp_gradient_tool_editor_fg_bg_changed (GimpGradientTool *gradient_tool)
+{
+ gimp_gradient_tool_editor_update_gui (gradient_tool);
+}
+
+void
+gimp_gradient_tool_editor_gradient_dirty (GimpGradientTool *gradient_tool)
+{
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ gimp_gradient_tool_editor_purge_gradient (gradient_tool);
+}
+
+void
+gimp_gradient_tool_editor_gradient_changed (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+
+ if (options->modify_active_frame)
+ {
+ gtk_widget_set_sensitive (options->modify_active_frame,
+ gradient_tool->gradient !=
+ gimp_gradients_get_custom (context->gimp));
+ }
+
+ if (options->modify_active_hint)
+ {
+ gtk_widget_set_visible (options->modify_active_hint,
+ gradient_tool->gradient &&
+ ! gimp_data_is_writable (GIMP_DATA (gradient_tool->gradient)));
+ }
+
+ if (gimp_gradient_tool_editor_are_handlers_blocked (gradient_tool))
+ return;
+
+ gimp_gradient_tool_editor_purge_gradient (gradient_tool);
+}
+
+const gchar *
+gimp_gradient_tool_editor_can_undo (GimpGradientTool *gradient_tool)
+{
+ if (! gradient_tool->undo_stack || gradient_tool->edit_count > 0)
+ return NULL;
+
+ return _("Gradient Step");
+}
+
+const gchar *
+gimp_gradient_tool_editor_can_redo (GimpGradientTool *gradient_tool)
+{
+ if (! gradient_tool->redo_stack || gradient_tool->edit_count > 0)
+ return NULL;
+
+ return _("Gradient Step");
+}
+
+gboolean
+gimp_gradient_tool_editor_undo (GimpGradientTool *gradient_tool)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+ GradientInfo *info;
+ GradientInfo *new_info;
+
+ gimp_assert (gradient_tool->undo_stack != NULL);
+ gimp_assert (gradient_tool->edit_count == 0);
+
+ info = gradient_tool->undo_stack->data;
+
+ new_info = gimp_gradient_tool_editor_gradient_info_new (gradient_tool);
+
+ if (info->gradient)
+ {
+ new_info->gradient =
+ GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (gradient_tool->gradient)));
+
+ /* swap the added and removed handles, so that gradient_info_apply() does
+ * the right thing on redo
+ */
+ new_info->added_handle = info->removed_handle;
+ new_info->removed_handle = info->added_handle;
+ new_info->selected_handle = info->selected_handle;
+ }
+
+ gradient_tool->undo_stack = g_slist_remove (gradient_tool->undo_stack, info);
+ gradient_tool->redo_stack = g_slist_prepend (gradient_tool->redo_stack, new_info);
+
+ gimp_gradient_tool_editor_gradient_info_apply (gradient_tool, info, TRUE);
+ gimp_gradient_tool_editor_gradient_info_free (info);
+
+ /* the initial state of the gradient tool is not useful; we might as well halt */
+ if (! gradient_tool->undo_stack)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ return TRUE;
+}
+
+gboolean
+gimp_gradient_tool_editor_redo (GimpGradientTool *gradient_tool)
+{
+ GradientInfo *info;
+ GradientInfo *new_info;
+
+ gimp_assert (gradient_tool->redo_stack != NULL);
+ gimp_assert (gradient_tool->edit_count == 0);
+
+ info = gradient_tool->redo_stack->data;
+
+ new_info = gimp_gradient_tool_editor_gradient_info_new (gradient_tool);
+
+ if (info->gradient)
+ {
+ new_info->gradient =
+ GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (gradient_tool->gradient)));
+
+ /* swap the added and removed handles, so that gradient_info_apply() does
+ * the right thing on undo
+ */
+ new_info->added_handle = info->removed_handle;
+ new_info->removed_handle = info->added_handle;
+ new_info->selected_handle = info->selected_handle;
+ }
+
+ gradient_tool->redo_stack = g_slist_remove (gradient_tool->redo_stack, info);
+ gradient_tool->undo_stack = g_slist_prepend (gradient_tool->undo_stack, new_info);
+
+ gimp_gradient_tool_editor_gradient_info_apply (gradient_tool, info, TRUE);
+ gimp_gradient_tool_editor_gradient_info_free (info);
+
+ return TRUE;
+}
+
+void
+gimp_gradient_tool_editor_start_edit (GimpGradientTool *gradient_tool)
+{
+ if (gradient_tool->edit_count++ == 0)
+ {
+ GradientInfo *info;
+
+ info = gimp_gradient_tool_editor_gradient_info_new (gradient_tool);
+
+ gradient_tool->undo_stack = g_slist_prepend (gradient_tool->undo_stack, info);
+
+ /* update the undo actions / menu items */
+ if (! gradient_tool->flush_idle_id)
+ {
+ gradient_tool->flush_idle_id =
+ g_idle_add ((GSourceFunc) gimp_gradient_tool_editor_flush_idle,
+ gradient_tool);
+ }
+ }
+}
+
+void
+gimp_gradient_tool_editor_end_edit (GimpGradientTool *gradient_tool,
+ gboolean cancel)
+{
+ /* can happen when halting using esc */
+ if (gradient_tool->edit_count == 0)
+ return;
+
+ if (--gradient_tool->edit_count == 0)
+ {
+ GradientInfo *info = gradient_tool->undo_stack->data;
+
+ info->selected_handle =
+ gimp_tool_line_get_selection (GIMP_TOOL_LINE (gradient_tool->widget));
+
+ if (cancel ||
+ gimp_gradient_tool_editor_gradient_info_is_trivial (gradient_tool, info))
+ {
+ /* if the edit is canceled, or if nothing changed, undo the last
+ * step
+ */
+ gimp_gradient_tool_editor_gradient_info_apply (gradient_tool, info, FALSE);
+
+ gradient_tool->undo_stack = g_slist_remove (gradient_tool->undo_stack,
+ info);
+ gimp_gradient_tool_editor_gradient_info_free (info);
+ }
+ else
+ {
+ /* otherwise, blow the redo stack */
+ g_slist_free_full (gradient_tool->redo_stack,
+ (GDestroyNotify) gimp_gradient_tool_editor_gradient_info_free);
+ gradient_tool->redo_stack = NULL;
+ }
+
+ /* update the undo actions / menu items */
+ if (! gradient_tool->flush_idle_id)
+ {
+ gradient_tool->flush_idle_id =
+ g_idle_add ((GSourceFunc) gimp_gradient_tool_editor_flush_idle,
+ gradient_tool);
+ }
+ }
+}
diff --git a/app/tools/gimpgradienttool-editor.h b/app/tools/gimpgradienttool-editor.h
new file mode 100644
index 0000000..db8fdab
--- /dev/null
+++ b/app/tools/gimpgradienttool-editor.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_GRADIENT_TOOL_EDITOR_H__
+#define __GIMP_GRADIENT_TOOL_EDITOR_H__
+
+
+void gimp_gradient_tool_editor_options_notify (GimpGradientTool *gradient_tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+void gimp_gradient_tool_editor_start (GimpGradientTool *gradient_tool);
+void gimp_gradient_tool_editor_halt (GimpGradientTool *gradient_tool);
+
+gboolean gimp_gradient_tool_editor_line_changed (GimpGradientTool *gradient_tool);
+
+void gimp_gradient_tool_editor_fg_bg_changed (GimpGradientTool *gradient_tool);
+
+void gimp_gradient_tool_editor_gradient_dirty (GimpGradientTool *gradient_tool);
+
+void gimp_gradient_tool_editor_gradient_changed (GimpGradientTool *gradient_tool);
+
+const gchar * gimp_gradient_tool_editor_can_undo (GimpGradientTool *gradient_tool);
+const gchar * gimp_gradient_tool_editor_can_redo (GimpGradientTool *gradient_tool);
+
+gboolean gimp_gradient_tool_editor_undo (GimpGradientTool *gradient_tool);
+gboolean gimp_gradient_tool_editor_redo (GimpGradientTool *gradient_tool);
+
+void gimp_gradient_tool_editor_start_edit (GimpGradientTool *gradient_tool);
+void gimp_gradient_tool_editor_end_edit (GimpGradientTool *gradient_tool,
+ gboolean cancel);
+
+
+#endif /* __GIMP_GRADIENT_TOOL_EDITOR_H__ */
diff --git a/app/tools/gimpgradienttool.c b/app/tools/gimpgradienttool.c
new file mode 100644
index 0000000..598f804
--- /dev/null
+++ b/app/tools/gimpgradienttool.c
@@ -0,0 +1,1106 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/gimp-operation-config.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdrawable-gradient.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpgradient.h"
+#include "core/gimpimage.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolline.h"
+
+#include "gimpgradientoptions.h"
+#include "gimpgradienttool.h"
+#include "gimpgradienttool-editor.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_gradient_tool_dispose (GObject *object);
+
+static gboolean gimp_gradient_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_gradient_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_gradient_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_gradient_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_gradient_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_gradient_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_gradient_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_gradient_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static const gchar * gimp_gradient_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+static const gchar * gimp_gradient_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_gradient_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_gradient_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+static void gimp_gradient_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_gradient_tool_start (GimpGradientTool *gradient_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display);
+static void gimp_gradient_tool_halt (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_commit (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_line_changed (GimpToolWidget *widget,
+ GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_line_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_precalc_shapeburst (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_create_graph (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_update_graph (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_fg_bg_changed (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_gradient_dirty (GimpGradientTool *gradient_tool);
+static void gimp_gradient_tool_set_gradient (GimpGradientTool *gradient_tool,
+ GimpGradient *gradient);
+
+static gboolean gimp_gradient_tool_is_shapeburst (GimpGradientTool *gradient_tool);
+
+static void gimp_gradient_tool_create_filter (GimpGradientTool *gradient_tool,
+ GimpDrawable *drawable);
+static void gimp_gradient_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+
+
+G_DEFINE_TYPE (GimpGradientTool, gimp_gradient_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_gradient_tool_parent_class
+
+
+void
+gimp_gradient_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_GRADIENT_TOOL,
+ GIMP_TYPE_GRADIENT_OPTIONS,
+ gimp_gradient_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND |
+ GIMP_CONTEXT_PROP_MASK_OPACITY |
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT,
+ "gimp-gradient-tool",
+ _("Gradient"),
+ _("Gradient Tool: Fill selected area with a color gradient"),
+ N_("Gra_dient"), "G",
+ NULL, GIMP_HELP_TOOL_GRADIENT,
+ GIMP_ICON_TOOL_GRADIENT,
+ data);
+}
+
+static void
+gimp_gradient_tool_class_init (GimpGradientToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ object_class->dispose = gimp_gradient_tool_dispose;
+
+ tool_class->initialize = gimp_gradient_tool_initialize;
+ tool_class->control = gimp_gradient_tool_control;
+ tool_class->button_press = gimp_gradient_tool_button_press;
+ tool_class->button_release = gimp_gradient_tool_button_release;
+ tool_class->motion = gimp_gradient_tool_motion;
+ tool_class->key_press = gimp_gradient_tool_key_press;
+ tool_class->modifier_key = gimp_gradient_tool_modifier_key;
+ tool_class->cursor_update = gimp_gradient_tool_cursor_update;
+ tool_class->can_undo = gimp_gradient_tool_can_undo;
+ tool_class->can_redo = gimp_gradient_tool_can_redo;
+ tool_class->undo = gimp_gradient_tool_undo;
+ tool_class->redo = gimp_gradient_tool_redo;
+ tool_class->options_notify = gimp_gradient_tool_options_notify;
+}
+
+static void
+gimp_gradient_tool_init (GimpGradientTool *gradient_tool)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_dirty_action (tool->control,
+ GIMP_TOOL_ACTION_COMMIT);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_wants_double_click (tool->control, TRUE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_GRADIENT);
+ gimp_tool_control_set_action_opacity (tool->control,
+ "context/context-opacity-set");
+ gimp_tool_control_set_action_object_1 (tool->control,
+ "context/context-gradient-select-set");
+
+ gimp_draw_tool_set_default_status (GIMP_DRAW_TOOL (tool),
+ _("Click-Drag to draw a gradient"));
+}
+
+static void
+gimp_gradient_tool_dispose (GObject *object)
+{
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (object);
+
+ gimp_gradient_tool_set_gradient (gradient_tool, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gboolean
+gimp_gradient_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ return FALSE;
+ }
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot modify the pixels of layer groups."));
+ return FALSE;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer's pixels are locked."));
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ return FALSE;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer is not visible."));
+ return FALSE;
+ }
+
+ if (! gimp_context_get_gradient (GIMP_CONTEXT (options)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No gradient available for use with this tool."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_gradient_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_gradient_tool_halt (gradient_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_gradient_tool_commit (gradient_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_gradient_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (tool);
+
+ if (tool->display && display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ if (! gradient_tool->widget)
+ {
+ gimp_gradient_tool_start (gradient_tool, coords, display);
+
+ gimp_tool_widget_hover (gradient_tool->widget, coords, state, TRUE);
+ }
+
+ /* call start_edit() before widget_button_press(), because we need to record
+ * the undo state before widget_button_press() potentially changes it. note
+ * that if widget_button_press() return FALSE, nothing changes and no undo
+ * step is created.
+ */
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+
+ if (gimp_tool_widget_button_press (gradient_tool->widget, coords, time, state,
+ press_type))
+ {
+ gradient_tool->grab_widget = gradient_tool->widget;
+ }
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_gradient_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (tool);
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (tool);
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (gradient_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (gradient_tool->grab_widget,
+ coords, time, state, release_type);
+ gradient_tool->grab_widget = NULL;
+
+ if (options->instant)
+ {
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ else
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ }
+ }
+
+ if (! options->instant)
+ {
+ gimp_gradient_tool_editor_end_edit (gradient_tool,
+ release_type ==
+ GIMP_BUTTON_RELEASE_CANCEL);
+ }
+}
+
+static void
+gimp_gradient_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (tool);
+
+ if (gradient_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (gradient_tool->grab_widget, coords, time, state);
+ }
+}
+
+static gboolean
+gimp_gradient_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ gboolean result;
+
+ /* call start_edit() before widget_key_press(), because we need to record the
+ * undo state before widget_key_press() potentially changes it. note that if
+ * widget_key_press() return FALSE, nothing changes and no undo step is
+ * created.
+ */
+ if (display == draw_tool->display)
+ gimp_gradient_tool_editor_start_edit (gradient_tool);
+
+ result = GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+
+ if (display == draw_tool->display)
+ gimp_gradient_tool_editor_end_edit (gradient_tool, FALSE);
+
+ return result;
+}
+
+static void
+gimp_gradient_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_extend_selection_mask ())
+ {
+ if (options->instant_toggle &&
+ gtk_widget_get_sensitive (options->instant_toggle))
+ {
+ g_object_set (options,
+ "instant", ! options->instant,
+ NULL);
+ }
+ }
+}
+
+static void
+gimp_gradient_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
+ gimp_item_is_content_locked (GIMP_ITEM (drawable)) ||
+ ! (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
+ config->edit_non_visible))
+ {
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ return;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static const gchar *
+gimp_gradient_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return gimp_gradient_tool_editor_can_undo (GIMP_GRADIENT_TOOL (tool));
+}
+
+static const gchar *
+gimp_gradient_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return gimp_gradient_tool_editor_can_redo (GIMP_GRADIENT_TOOL (tool));
+}
+
+static gboolean
+gimp_gradient_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return gimp_gradient_tool_editor_undo (GIMP_GRADIENT_TOOL (tool));
+}
+
+static gboolean
+gimp_gradient_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return gimp_gradient_tool_editor_redo (GIMP_GRADIENT_TOOL (tool));
+}
+
+static void
+gimp_gradient_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpContext *context = GIMP_CONTEXT (options);
+ GimpGradientTool *gradient_tool = GIMP_GRADIENT_TOOL (tool);
+
+ if (! strcmp (pspec->name, "gradient"))
+ {
+ gimp_gradient_tool_set_gradient (gradient_tool, context->gradient);
+
+ if (gradient_tool->filter)
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+ }
+ else if (gradient_tool->render_node &&
+ gegl_node_find_property (gradient_tool->render_node, pspec->name))
+ {
+ /* Sync any property changes on the config object that match the op */
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, pspec->value_type);
+
+ g_object_get_property (G_OBJECT (options), pspec->name, &value);
+ gegl_node_set_property (gradient_tool->render_node, pspec->name, &value);
+
+ g_value_unset (&value);
+
+ if (! strcmp (pspec->name, "gradient-type"))
+ {
+ GimpRepeatMode gradient_repeat;
+ GimpRepeatMode node_repeat;
+ GimpGradientType gradient_type;
+
+ gradient_repeat = GIMP_PAINT_OPTIONS (options)->gradient_options->gradient_repeat;
+ gradient_type = GIMP_GRADIENT_OPTIONS (options)->gradient_type;
+ gegl_node_get (gradient_tool->render_node,
+ "gradient-repeat", &node_repeat,
+ NULL);
+
+ if (gradient_type >= GIMP_GRADIENT_SHAPEBURST_ANGULAR)
+ {
+ /* These gradient types are only meant to work with repeat
+ * value of "none" so these are the only ones where we
+ * don't keep the render node and the gradient options in
+ * sync.
+ * We could instead reset the "gradient-repeat" value on
+ * GimpGradientOptions, but I assume one would want to revert
+ * back to the last set value if changing back the
+ * gradient type. So instead we just make the option
+ * insensitive (both in GUI and in render).
+ */
+ if (node_repeat != GIMP_REPEAT_NONE)
+ gegl_node_set (gradient_tool->render_node,
+ "gradient-repeat", GIMP_REPEAT_NONE,
+ NULL);
+ }
+ else if (node_repeat != gradient_repeat)
+ {
+ gegl_node_set (gradient_tool->render_node,
+ "gradient-repeat", gradient_repeat,
+ NULL);
+ }
+
+ if (gimp_gradient_tool_is_shapeburst (gradient_tool))
+ gimp_gradient_tool_precalc_shapeburst (gradient_tool);
+
+ gimp_gradient_tool_update_graph (gradient_tool);
+ }
+
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+ }
+ else if (gradient_tool->render_node &&
+ gimp_gradient_tool_is_shapeburst (gradient_tool) &&
+ g_strcmp0 (pspec->name, "distance-metric") == 0)
+ {
+ g_clear_object (&gradient_tool->dist_buffer);
+ gimp_gradient_tool_precalc_shapeburst (gradient_tool);
+ gimp_gradient_tool_update_graph (gradient_tool);
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+ }
+ else if (gradient_tool->filter &&
+ ! strcmp (pspec->name, "opacity"))
+ {
+ gimp_drawable_filter_set_opacity (gradient_tool->filter,
+ gimp_context_get_opacity (context));
+ }
+ else if (gradient_tool->filter &&
+ ! strcmp (pspec->name, "paint-mode"))
+ {
+ gimp_drawable_filter_set_mode (gradient_tool->filter,
+ gimp_context_get_paint_mode (context),
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (
+ gimp_context_get_paint_mode (context)));
+ }
+
+ gimp_gradient_tool_editor_options_notify (gradient_tool, options, pspec);
+}
+
+static void
+gimp_gradient_tool_start (GimpGradientTool *gradient_tool,
+ const GimpCoords *coords,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+
+ if (options->instant_toggle)
+ gtk_widget_set_sensitive (options->instant_toggle, FALSE);
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ gradient_tool->start_x = coords->x;
+ gradient_tool->start_y = coords->y;
+ gradient_tool->end_x = coords->x;
+ gradient_tool->end_y = coords->y;
+
+ gradient_tool->widget = gimp_tool_line_new (shell,
+ gradient_tool->start_x,
+ gradient_tool->start_y,
+ gradient_tool->end_x,
+ gradient_tool->end_y);
+
+ g_object_set (gradient_tool->widget,
+ "status-title", _("Gradient: "),
+ NULL);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), gradient_tool->widget);
+
+ g_signal_connect (gradient_tool->widget, "changed",
+ G_CALLBACK (gimp_gradient_tool_line_changed),
+ gradient_tool);
+ g_signal_connect (gradient_tool->widget, "response",
+ G_CALLBACK (gimp_gradient_tool_line_response),
+ gradient_tool);
+
+ g_signal_connect_swapped (context, "background-changed",
+ G_CALLBACK (gimp_gradient_tool_fg_bg_changed),
+ gradient_tool);
+ g_signal_connect_swapped (context, "foreground-changed",
+ G_CALLBACK (gimp_gradient_tool_fg_bg_changed),
+ gradient_tool);
+
+ gimp_gradient_tool_create_filter (gradient_tool, drawable);
+
+ /* Initially sync all of the properties */
+ gimp_operation_config_sync_node (G_OBJECT (options),
+ gradient_tool->render_node);
+
+ /* We don't allow repeat values for some shapes. */
+ if (options->gradient_type >= GIMP_GRADIENT_SHAPEBURST_ANGULAR)
+ gegl_node_set (gradient_tool->render_node,
+ "gradient-repeat", GIMP_REPEAT_NONE,
+ NULL);
+
+ /* Connect signal handlers for the gradient */
+ gimp_gradient_tool_set_gradient (gradient_tool, context->gradient);
+
+ if (gimp_gradient_tool_is_shapeburst (gradient_tool))
+ gimp_gradient_tool_precalc_shapeburst (gradient_tool);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (gradient_tool), display);
+
+ gimp_gradient_tool_editor_start (gradient_tool);
+}
+
+static void
+gimp_gradient_tool_halt (GimpGradientTool *gradient_tool)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+
+ gimp_gradient_tool_editor_halt (gradient_tool);
+
+ if (gradient_tool->graph)
+ {
+ g_clear_object (&gradient_tool->graph);
+ gradient_tool->render_node = NULL;
+#if 0
+ gradient_tool->subtract_node = NULL;
+ gradient_tool->divide_node = NULL;
+#endif
+ gradient_tool->dist_node = NULL;
+ }
+
+ g_clear_object (&gradient_tool->dist_buffer);
+
+ if (gradient_tool->filter)
+ {
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_abort (gradient_tool->filter);
+ g_object_unref (gradient_tool->filter);
+ gradient_tool->filter = NULL;
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+
+ gimp_gradient_tool_set_tentative_gradient (gradient_tool, NULL);
+
+ g_signal_handlers_disconnect_by_func (context,
+ G_CALLBACK (gimp_gradient_tool_fg_bg_changed),
+ gradient_tool);
+
+ if (tool->display)
+ gimp_tool_pop_status (tool, tool->display);
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (gradient_tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (gradient_tool));
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
+ g_clear_object (&gradient_tool->widget);
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+
+ if (options->instant_toggle)
+ gtk_widget_set_sensitive (options->instant_toggle, TRUE);
+}
+
+static void
+gimp_gradient_tool_commit (GimpGradientTool *gradient_tool)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+
+ if (gradient_tool->filter)
+ {
+ /* halt the editor before committing the filter so that the image-flush
+ * idle source is removed, to avoid flushing the image, and hence
+ * restarting the projection rendering, while applying the filter.
+ */
+ gimp_gradient_tool_editor_halt (gradient_tool);
+
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_commit (gradient_tool->filter,
+ GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&gradient_tool->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+}
+
+static void
+gimp_gradient_tool_line_changed (GimpToolWidget *widget,
+ GimpGradientTool *gradient_tool)
+{
+ gdouble start_x;
+ gdouble start_y;
+ gdouble end_x;
+ gdouble end_y;
+ gboolean update = FALSE;
+
+ g_object_get (widget,
+ "x1", &start_x,
+ "y1", &start_y,
+ "x2", &end_x,
+ "y2", &end_y,
+ NULL);
+
+ if (start_x != gradient_tool->start_x ||
+ start_y != gradient_tool->start_y ||
+ end_x != gradient_tool->end_x ||
+ end_y != gradient_tool->end_y)
+ {
+ gradient_tool->start_x = start_x;
+ gradient_tool->start_y = start_y;
+ gradient_tool->end_x = end_x;
+ gradient_tool->end_y = end_y;
+
+ update = TRUE;
+ }
+
+ if (gimp_gradient_tool_editor_line_changed (gradient_tool))
+ update = TRUE;
+
+ if (update)
+ {
+ gimp_gradient_tool_update_graph (gradient_tool);
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+ }
+}
+
+static void
+gimp_gradient_tool_line_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpGradientTool *gradient_tool)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+
+ switch (response_id)
+ {
+ case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ break;
+ }
+}
+
+static void
+gimp_gradient_tool_precalc_shapeburst (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+ gint x, y, width, height;
+
+ if (gradient_tool->dist_buffer || ! tool->drawable)
+ return;
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (tool->drawable),
+ &x, &y, &width, &height))
+ return;
+
+ gradient_tool->dist_buffer =
+ gimp_drawable_gradient_shapeburst_distmap (tool->drawable,
+ options->distance_metric,
+ GEGL_RECTANGLE (x, y, width, height),
+ GIMP_PROGRESS (gradient_tool));
+
+ if (gradient_tool->dist_node)
+ gegl_node_set (gradient_tool->dist_node,
+ "buffer", gradient_tool->dist_buffer,
+ NULL);
+
+ gimp_progress_end (GIMP_PROGRESS (gradient_tool));
+}
+
+
+/* gegl graph stuff */
+
+static void
+gimp_gradient_tool_create_graph (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+ GeglNode *output;
+
+ /* render_node is not supposed to be recreated */
+ g_return_if_fail (gradient_tool->graph == NULL);
+
+ gradient_tool->graph = gegl_node_new ();
+
+ gradient_tool->dist_node =
+ gegl_node_new_child (gradient_tool->graph,
+ "operation", "gegl:buffer-source",
+ "buffer", gradient_tool->dist_buffer,
+ NULL);
+
+#if 0
+ gradient_tool->subtract_node =
+ gegl_node_new_child (gradient_tool->graph,
+ "operation", "gegl:subtract",
+ NULL);
+
+ gradient_tool->divide_node =
+ gegl_node_new_child (gradient_tool->graph,
+ "operation", "gegl:divide",
+ NULL);
+#endif
+
+ gradient_tool->render_node =
+ gegl_node_new_child (gradient_tool->graph,
+ "operation", "gimp:gradient",
+ "context", context,
+ NULL);
+
+ output = gegl_node_get_output_proxy (gradient_tool->graph, "output");
+
+ gegl_node_link_many (gradient_tool->dist_node,
+#if 0
+ gradient_tool->subtract_node,
+ gradient_tool->divide_node,
+#endif
+ gradient_tool->render_node,
+ output,
+ NULL);
+
+ gimp_gegl_node_set_underlying_operation (gradient_tool->graph,
+ gradient_tool->render_node);
+
+ gimp_gradient_tool_update_graph (gradient_tool);
+}
+
+static void
+gimp_gradient_tool_update_graph (GimpGradientTool *gradient_tool)
+{
+ GimpTool *tool = GIMP_TOOL (gradient_tool);
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
+
+#if 0
+ if (gimp_gradient_tool_is_shapeburst (gradient_tool))
+ {
+ gfloat start, end;
+
+ gegl_buffer_get (gradient_tool->dist_buffer,
+ GEGL_RECTANGLE (gradient_tool->start_x - off_x,
+ gradient_tool->start_y - off_y,
+ 1, 1),
+ 1.0, babl_format("Y float"), &start,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ gegl_buffer_get (gradient_tool->dist_buffer,
+ GEGL_RECTANGLE (gradient_tool->end_x - off_x,
+ gradient_tool->end_y - off_y,
+ 1, 1),
+ 1.0, babl_format("Y float"), &end,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ if (start != end)
+ {
+ gegl_node_set (gradient_tool->subtract_node,
+ "value", (gdouble) start,
+ NULL);
+ gegl_node_set (gradient_tool->divide_node,
+ "value", (gdouble) (end - start),
+ NULL);
+ }
+ }
+ else
+#endif
+ {
+ GeglRectangle roi;
+ gdouble start_x, start_y;
+ gdouble end_x, end_y;
+
+ gimp_item_mask_intersect (GIMP_ITEM (tool->drawable),
+ &roi.x, &roi.y, &roi.width, &roi.height);
+
+ start_x = gradient_tool->start_x - off_x;
+ start_y = gradient_tool->start_y - off_y;
+ end_x = gradient_tool->end_x - off_x;
+ end_y = gradient_tool->end_y - off_y;
+
+ gimp_drawable_gradient_adjust_coords (tool->drawable,
+ options->gradient_type,
+ &roi,
+ &start_x, &start_y, &end_x, &end_y);
+
+ gegl_node_set (gradient_tool->render_node,
+ "start-x", start_x,
+ "start-y", start_y,
+ "end-x", end_x,
+ "end-y", end_y,
+ NULL);
+ }
+}
+
+static void
+gimp_gradient_tool_fg_bg_changed (GimpGradientTool *gradient_tool)
+{
+ if (! gradient_tool->filter || ! gradient_tool->gradient)
+ return;
+
+ if (gimp_gradient_has_fg_bg_segments (gradient_tool->gradient))
+ {
+ /* Set a property on the node. Otherwise it will cache and refuse to update */
+ gegl_node_set (gradient_tool->render_node,
+ "gradient", gradient_tool->gradient,
+ NULL);
+
+ /* Update the filter */
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+
+ gimp_gradient_tool_editor_fg_bg_changed (gradient_tool);
+ }
+}
+
+static void
+gimp_gradient_tool_gradient_dirty (GimpGradientTool *gradient_tool)
+{
+ if (! gradient_tool->filter)
+ return;
+
+ if (! gradient_tool->tentative_gradient)
+ {
+ /* Set a property on the node. Otherwise it will cache and refuse to update */
+ gegl_node_set (gradient_tool->render_node,
+ "gradient", gradient_tool->gradient,
+ NULL);
+
+ /* Update the filter */
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+ }
+
+ gimp_gradient_tool_editor_gradient_dirty (gradient_tool);
+}
+
+static void
+gimp_gradient_tool_set_gradient (GimpGradientTool *gradient_tool,
+ GimpGradient *gradient)
+{
+ if (gradient_tool->gradient)
+ g_signal_handlers_disconnect_by_func (gradient_tool->gradient,
+ G_CALLBACK (gimp_gradient_tool_gradient_dirty),
+ gradient_tool);
+
+
+ g_set_object (&gradient_tool->gradient, gradient);
+
+ if (gradient_tool->gradient)
+ {
+ g_signal_connect_swapped (gradient_tool->gradient, "dirty",
+ G_CALLBACK (gimp_gradient_tool_gradient_dirty),
+ gradient_tool);
+
+ if (gradient_tool->render_node)
+ gegl_node_set (gradient_tool->render_node,
+ "gradient", gradient_tool->gradient,
+ NULL);
+ }
+
+ gimp_gradient_tool_editor_gradient_changed (gradient_tool);
+}
+
+static gboolean
+gimp_gradient_tool_is_shapeburst (GimpGradientTool *gradient_tool)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+
+ return options->gradient_type >= GIMP_GRADIENT_SHAPEBURST_ANGULAR &&
+ options->gradient_type <= GIMP_GRADIENT_SHAPEBURST_DIMPLED;
+}
+
+
+/* image map stuff */
+
+static void
+gimp_gradient_tool_create_filter (GimpGradientTool *gradient_tool,
+ GimpDrawable *drawable)
+{
+ GimpGradientOptions *options = GIMP_GRADIENT_TOOL_GET_OPTIONS (gradient_tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+
+ if (! gradient_tool->graph)
+ gimp_gradient_tool_create_graph (gradient_tool);
+
+ gradient_tool->filter = gimp_drawable_filter_new (drawable,
+ C_("undo-type", "Gradient"),
+ gradient_tool->graph,
+ GIMP_ICON_TOOL_GRADIENT);
+
+ gimp_drawable_filter_set_region (gradient_tool->filter,
+ GIMP_FILTER_REGION_DRAWABLE);
+ gimp_drawable_filter_set_opacity (gradient_tool->filter,
+ gimp_context_get_opacity (context));
+ gimp_drawable_filter_set_mode (gradient_tool->filter,
+ gimp_context_get_paint_mode (context),
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (
+ gimp_context_get_paint_mode (context)));
+
+ g_signal_connect (gradient_tool->filter, "flush",
+ G_CALLBACK (gimp_gradient_tool_filter_flush),
+ gradient_tool);
+}
+
+static void
+gimp_gradient_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+
+/* protected functions */
+
+
+void
+gimp_gradient_tool_set_tentative_gradient (GimpGradientTool *gradient_tool,
+ GimpGradient *gradient)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT_TOOL (gradient_tool));
+ g_return_if_fail (gradient == NULL || GIMP_IS_GRADIENT (gradient));
+
+ if (g_set_object (&gradient_tool->tentative_gradient, gradient))
+ {
+ if (gradient_tool->render_node)
+ {
+ gegl_node_set (gradient_tool->render_node,
+ "gradient", gradient ? gradient : gradient_tool->gradient,
+ NULL);
+
+ gimp_drawable_filter_apply (gradient_tool->filter, NULL);
+ }
+ }
+}
diff --git a/app/tools/gimpgradienttool.h b/app/tools/gimpgradienttool.h
new file mode 100644
index 0000000..a28cbf5
--- /dev/null
+++ b/app/tools/gimpgradienttool.h
@@ -0,0 +1,111 @@
+/* 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_GRADIENT_TOOL_H__
+#define __GIMP_GRADIENT_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_GRADIENT_TOOL (gimp_gradient_tool_get_type ())
+#define GIMP_GRADIENT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRADIENT_TOOL, GimpGradientTool))
+#define GIMP_GRADIENT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRADIENT_TOOL, GimpGradientToolClass))
+#define GIMP_IS_GRADIENT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRADIENT_TOOL))
+#define GIMP_IS_GRADIENT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRADIENT_TOOL))
+#define GIMP_GRADIENT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRADIENT_TOOL, GimpGradientToolClass))
+
+#define GIMP_GRADIENT_TOOL_GET_OPTIONS(t) (GIMP_GRADIENT_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpGradientTool GimpGradientTool;
+typedef struct _GimpGradientToolClass GimpGradientToolClass;
+
+struct _GimpGradientTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpGradient *gradient;
+ GimpGradient *tentative_gradient;
+
+ gdouble start_x; /* starting x coord */
+ gdouble start_y; /* starting y coord */
+ gdouble end_x; /* ending x coord */
+ gdouble end_y; /* ending y coord */
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+
+ GeglNode *graph;
+ GeglNode *render_node;
+#if 0
+ GeglNode *subtract_node;
+ GeglNode *divide_node;
+#endif
+ GeglNode *dist_node;
+ GeglBuffer *dist_buffer;
+ GimpDrawableFilter *filter;
+
+ /* editor */
+
+ gint block_handlers_count;
+
+ gint edit_count;
+ GSList *undo_stack;
+ GSList *redo_stack;
+
+ guint flush_idle_id;
+
+ GimpToolGui *gui;
+ GtkWidget *endpoint_editor;
+ GtkWidget *endpoint_se;
+ GtkWidget *endpoint_color_panel;
+ GtkWidget *endpoint_type_combo;
+ GtkWidget *stop_editor;
+ GtkWidget *stop_se;
+ GtkWidget *stop_left_color_panel;
+ GtkWidget *stop_left_type_combo;
+ GtkWidget *stop_right_color_panel;
+ GtkWidget *stop_right_type_combo;
+ GtkWidget *stop_chain_button;
+ GtkWidget *midpoint_editor;
+ GtkWidget *midpoint_se;
+ GtkWidget *midpoint_type_combo;
+ GtkWidget *midpoint_color_combo;
+ GtkWidget *midpoint_new_stop_button;
+ GtkWidget *midpoint_center_button;
+};
+
+struct _GimpGradientToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_gradient_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_gradient_tool_get_type (void) G_GNUC_CONST;
+
+
+/* protected functions */
+
+void gimp_gradient_tool_set_tentative_gradient (GimpGradientTool *gradient_tool,
+ GimpGradient *gradient);
+
+
+#endif /* __GIMP_GRADIENT_TOOL_H__ */
diff --git a/app/tools/gimpguidetool.c b/app/tools/gimpguidetool.c
new file mode 100644
index 0000000..55d5732
--- /dev/null
+++ b/app/tools/gimpguidetool.c
@@ -0,0 +1,546 @@
+/* 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 "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-undo.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-selection.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimpguidetool.h"
+#include "gimptoolcontrol.h"
+#include "tool_manager.h"
+
+#include "gimp-intl.h"
+
+
+#define SWAP_ORIENT(orient) ((orient) == GIMP_ORIENTATION_HORIZONTAL ? \
+ GIMP_ORIENTATION_VERTICAL : \
+ GIMP_ORIENTATION_HORIZONTAL)
+
+
+/* local function prototypes */
+
+static void gimp_guide_tool_finalize (GObject *object);
+
+static void gimp_guide_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_guide_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_guide_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_guide_tool_start (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GList *guides,
+ GimpOrientationType orientation);
+
+static void gimp_guide_tool_push_status (GimpGuideTool *guide_tool,
+ GimpDisplay *display,
+ gboolean remove_guides);
+
+
+G_DEFINE_TYPE (GimpGuideTool, gimp_guide_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_guide_tool_parent_class
+
+
+static void
+gimp_guide_tool_class_init (GimpGuideToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_guide_tool_finalize;
+
+ tool_class->button_release = gimp_guide_tool_button_release;
+ tool_class->motion = gimp_guide_tool_motion;
+
+ draw_tool_class->draw = gimp_guide_tool_draw;
+}
+
+static void
+gimp_guide_tool_init (GimpGuideTool *guide_tool)
+{
+ GimpTool *tool = GIMP_TOOL (guide_tool);
+
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_MOVE);
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER);
+
+ guide_tool->guides = NULL;
+ guide_tool->n_guides = 0;
+}
+
+static void
+gimp_guide_tool_finalize (GObject *object)
+{
+ GimpGuideTool *guide_tool = GIMP_GUIDE_TOOL (object);
+ gint i;
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ g_clear_object (&guide_tool->guides[i].guide);
+
+ g_free (guide_tool->guides);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_guide_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpGuideTool *guide_tool = GIMP_GUIDE_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gint i;
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_control_halt (tool->control);
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuideToolGuide *guide = &guide_tool->guides[i];
+
+ /* custom guides are moved live */
+ if (guide->custom)
+ {
+ gimp_image_move_guide (image, guide->guide, guide->old_position,
+ TRUE);
+ }
+ }
+ }
+ else
+ {
+ gint n_non_custom_guides = 0;
+ gboolean remove_guides = FALSE;
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuideToolGuide *guide = &guide_tool->guides[i];
+ gint max_position;
+
+ if (guide->orientation == GIMP_ORIENTATION_HORIZONTAL)
+ max_position = gimp_image_get_height (image);
+ else
+ max_position = gimp_image_get_width (image);
+
+ n_non_custom_guides += ! guide->custom;
+
+ if (guide->position == GIMP_GUIDE_POSITION_UNDEFINED ||
+ guide->position < 0 ||
+ guide->position > max_position)
+ {
+ remove_guides = TRUE;
+ }
+ }
+
+ if (n_non_custom_guides > 1)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_GUIDE,
+ remove_guides ?
+ C_("undo-type", "Remove Guides") :
+ C_("undo-type", "Move Guides"));
+ }
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuideToolGuide *guide = &guide_tool->guides[i];
+
+ if (remove_guides)
+ {
+ /* removing a guide can cause other guides to be removed as well
+ * (in particular, in case of symmetry guides). these guides
+ * will be kept alive, since we hold a reference on them, but we
+ * need to make sure that they're still part of the image.
+ */
+ if (g_list_find (gimp_image_get_guides (image), guide->guide))
+ gimp_image_remove_guide (image, guide->guide, TRUE);
+ }
+ else
+ {
+ if (guide->guide)
+ {
+ /* custom guides are moved live */
+ if (! guide->custom)
+ {
+ gimp_image_move_guide (image, guide->guide,
+ guide->position, TRUE);
+ }
+ }
+ else
+ {
+ switch (guide->orientation)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_add_hguide (image,
+ guide->position,
+ TRUE);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_add_vguide (image,
+ guide->position,
+ TRUE);
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ }
+ }
+ }
+ }
+
+ if (n_non_custom_guides > 1)
+ gimp_image_undo_group_end (image);
+
+ gimp_image_flush (image);
+ }
+
+ gimp_display_shell_selection_resume (shell);
+
+ tool_manager_pop_tool (display->gimp);
+ g_object_unref (guide_tool);
+
+ {
+ GimpTool *active_tool = tool_manager_get_active (display->gimp);
+
+ if (GIMP_IS_DRAW_TOOL (active_tool))
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (active_tool));
+
+ tool_manager_oper_update_active (display->gimp, coords, state,
+ TRUE, display);
+ tool_manager_cursor_update_active (display->gimp, coords, state,
+ display);
+
+ if (GIMP_IS_DRAW_TOOL (active_tool))
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (active_tool));
+ }
+}
+
+static void
+gimp_guide_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+
+{
+ GimpGuideTool *guide_tool = GIMP_GUIDE_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gboolean remove_guides = FALSE;
+ gint tx, ty;
+ gint i;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_display_shell_transform_xy (shell,
+ coords->x, coords->y,
+ &tx, &ty);
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuideToolGuide *guide = &guide_tool->guides[i];
+ gint max_position;
+ gint position;
+
+ if (guide->orientation == GIMP_ORIENTATION_HORIZONTAL)
+ max_position = gimp_image_get_height (image);
+ else
+ max_position = gimp_image_get_width (image);
+
+ if (guide->orientation == GIMP_ORIENTATION_HORIZONTAL)
+ guide->position = RINT (coords->y);
+ else
+ guide->position = RINT (coords->x);
+
+ position = CLAMP (guide->position, 0, max_position);
+
+ if (tx < 0 || tx >= shell->disp_width ||
+ ty < 0 || ty >= shell->disp_height)
+ {
+ guide->position = GIMP_GUIDE_POSITION_UNDEFINED;
+
+ remove_guides = TRUE;
+ }
+ else
+ {
+ if (guide->position < 0 || guide->position > max_position)
+ remove_guides = TRUE;
+ }
+
+ /* custom guides are moved live */
+ if (guide->custom)
+ gimp_image_move_guide (image, guide->guide, position, TRUE);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_guide_tool_push_status (guide_tool, display, remove_guides);
+}
+
+static void
+gimp_guide_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpGuideTool *guide_tool = GIMP_GUIDE_TOOL (draw_tool);
+ gint i;
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuideToolGuide *guide = &guide_tool->guides[i];
+
+ if (guide->position != GIMP_GUIDE_POSITION_UNDEFINED)
+ {
+ /* custom guides are moved live */
+ if (! guide->custom)
+ {
+ gimp_draw_tool_add_guide (draw_tool,
+ guide->orientation,
+ guide->position,
+ GIMP_GUIDE_STYLE_NONE);
+ }
+ }
+ }
+}
+
+static void
+gimp_guide_tool_start (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GList *guides,
+ GimpOrientationType orientation)
+{
+ GimpGuideTool *guide_tool;
+ GimpTool *tool;
+
+ guide_tool = g_object_new (GIMP_TYPE_GUIDE_TOOL,
+ "tool-info", parent_tool->tool_info,
+ NULL);
+
+ tool = GIMP_TOOL (guide_tool);
+
+ gimp_display_shell_selection_pause (gimp_display_get_shell (display));
+
+ if (guides)
+ {
+ gint i;
+
+ guide_tool->n_guides = g_list_length (guides);
+ guide_tool->guides = g_new (GimpGuideToolGuide, guide_tool->n_guides);
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuide *guide = guides->data;
+
+ guide_tool->guides[i].guide = g_object_ref (guide);
+ guide_tool->guides[i].old_position = gimp_guide_get_position (guide);
+ guide_tool->guides[i].position = gimp_guide_get_position (guide);
+ guide_tool->guides[i].orientation = gimp_guide_get_orientation (guide);
+ guide_tool->guides[i].custom = gimp_guide_is_custom (guide);
+
+ guides = g_list_next (guides);
+ }
+ }
+ else
+ {
+ guide_tool->n_guides = 1;
+ guide_tool->guides = g_new (GimpGuideToolGuide, 1);
+
+ guide_tool->guides[0].guide = NULL;
+ guide_tool->guides[0].old_position = 0;
+ guide_tool->guides[0].position = GIMP_GUIDE_POSITION_UNDEFINED;
+ guide_tool->guides[0].orientation = orientation;
+ guide_tool->guides[0].custom = FALSE;
+ }
+
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_HAND,
+ GIMP_CURSOR_MODIFIER_MOVE);
+
+ tool_manager_push_tool (display->gimp, tool);
+
+ tool->display = display;
+ gimp_tool_control_activate (tool->control);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (guide_tool), display);
+
+ gimp_guide_tool_push_status (guide_tool, display, FALSE);
+}
+
+static void
+gimp_guide_tool_push_status (GimpGuideTool *guide_tool,
+ GimpDisplay *display,
+ gboolean remove_guides)
+{
+ GimpTool *tool = GIMP_TOOL (guide_tool);
+
+ if (remove_guides)
+ {
+ gimp_tool_push_status (tool, display,
+ guide_tool->n_guides > 1 ? _("Remove Guides") :
+ guide_tool->guides[0].guide ? _("Remove Guide") :
+ _("Cancel Guide"));
+ }
+ else
+ {
+ GimpGuideToolGuide *guides[2];
+ gint n_guides = 0;
+ gint i;
+
+ for (i = 0; i < guide_tool->n_guides; i++)
+ {
+ GimpGuideToolGuide *guide = &guide_tool->guides[i];
+
+ if (guide_tool->guides[i].guide)
+ {
+ if (n_guides == 0 || guide->orientation != guides[0]->orientation)
+ {
+ guides[n_guides++] = guide;
+
+ if (n_guides == 2)
+ break;
+ }
+ }
+ }
+
+ if (n_guides == 2 &&
+ guides[0]->orientation == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ GimpGuideToolGuide *temp;
+
+ temp = guides[0];
+ guides[0] = guides[1];
+ guides[1] = temp;
+ }
+
+ if (n_guides == 1)
+ {
+ gimp_tool_push_status_length (tool, display,
+ _("Move Guide: "),
+ SWAP_ORIENT (guides[0]->orientation),
+ guides[0]->position -
+ guides[0]->old_position,
+ NULL);
+ }
+ else if (n_guides == 2)
+ {
+ gimp_tool_push_status_coords (tool, display,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER,
+ _("Move Guides: "),
+ guides[0]->position -
+ guides[0]->old_position,
+ ", ",
+ guides[1]->position -
+ guides[1]->old_position,
+ NULL);
+ }
+ else
+ {
+ gimp_tool_push_status_length (tool, display,
+ _("Add Guide: "),
+ SWAP_ORIENT (guide_tool->guides[0].orientation),
+ guide_tool->guides[0].position,
+ NULL);
+ }
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_guide_tool_start_new (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpOrientationType orientation)
+{
+ g_return_if_fail (GIMP_IS_TOOL (parent_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (orientation != GIMP_ORIENTATION_UNKNOWN);
+
+ gimp_guide_tool_start (parent_tool, display,
+ NULL, orientation);
+}
+
+void
+gimp_guide_tool_start_edit (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpGuide *guide)
+{
+ GList *guides = NULL;
+
+ g_return_if_fail (GIMP_IS_TOOL (parent_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ guides = g_list_append (guides, guide);
+
+ gimp_guide_tool_start (parent_tool, display,
+ guides, GIMP_ORIENTATION_UNKNOWN);
+
+ g_list_free (guides);
+}
+
+void
+gimp_guide_tool_start_edit_many (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GList *guides)
+{
+ g_return_if_fail (GIMP_IS_TOOL (parent_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (guides != NULL);
+
+ gimp_guide_tool_start (parent_tool, display,
+ guides, GIMP_ORIENTATION_UNKNOWN);
+}
diff --git a/app/tools/gimpguidetool.h b/app/tools/gimpguidetool.h
new file mode 100644
index 0000000..fa83b36
--- /dev/null
+++ b/app/tools/gimpguidetool.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_GUIDE_TOOL_H__
+#define __GIMP_GUIDE_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_GUIDE_TOOL (gimp_guide_tool_get_type ())
+#define GIMP_GUIDE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GUIDE_TOOL, GimpGuideTool))
+#define GIMP_GUIDE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GUIDE_TOOL, GimpGuideToolClass))
+#define GIMP_IS_GUIDE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GUIDE_TOOL))
+#define GIMP_IS_GUIDE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GUIDE_TOOL))
+#define GIMP_GUIDE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GUIDE_TOOL, GimpGuideToolClass))
+
+
+typedef struct _GimpGuideToolGuide GimpGuideToolGuide;
+typedef struct _GimpGuideTool GimpGuideTool;
+typedef struct _GimpGuideToolClass GimpGuideToolClass;
+
+struct _GimpGuideToolGuide
+{
+ GimpGuide *guide;
+
+ gint old_position;
+ gint position;
+ GimpOrientationType orientation;
+ gboolean custom;
+};
+
+struct _GimpGuideTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpGuideToolGuide *guides;
+ gint n_guides;
+};
+
+struct _GimpGuideToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+GType gimp_guide_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_guide_tool_start_new (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpOrientationType orientation);
+void gimp_guide_tool_start_edit (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpGuide *guide);
+void gimp_guide_tool_start_edit_many (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GList *guides);
+
+
+#endif /* __GIMP_GUIDE_TOOL_H__ */
diff --git a/app/tools/gimphandletransformoptions.c b/app/tools/gimphandletransformoptions.c
new file mode 100644
index 0000000..bf24419
--- /dev/null
+++ b/app/tools/gimphandletransformoptions.c
@@ -0,0 +1,202 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimphandletransformoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_HANDLE_MODE
+};
+
+
+static void gimp_handle_transform_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_handle_transform_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpHandleTransformOptions, gimp_handle_transform_options,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS)
+
+#define parent_class gimp_handle_transform_options_parent_class
+
+
+static void
+gimp_handle_transform_options_class_init (GimpHandleTransformOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_handle_transform_options_set_property;
+ object_class->get_property = gimp_handle_transform_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_HANDLE_MODE,
+ "handle-mode",
+ _("Handle mode"),
+ _("Handle mode"),
+ GIMP_TYPE_TRANSFORM_HANDLE_MODE,
+ GIMP_HANDLE_MODE_ADD_TRANSFORM,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_handle_transform_options_init (GimpHandleTransformOptions *options)
+{
+}
+
+static void
+gimp_handle_transform_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHandleTransformOptions *options = GIMP_HANDLE_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_HANDLE_MODE:
+ options->handle_mode = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_handle_transform_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHandleTransformOptions *options = GIMP_HANDLE_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_HANDLE_MODE:
+ g_value_set_enum (value, options->handle_mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_handle_transform_options_gui:
+ * @tool_options: a #GimpToolOptions
+ *
+ * Build the Transform Tool Options.
+ *
+ * Return value: a container holding the transform tool options
+ **/
+GtkWidget *
+gimp_handle_transform_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_transform_grid_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *button;
+ gint i;
+
+ frame = gimp_prop_enum_radio_frame_new (config, "handle-mode", NULL,
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* add modifier to name, add tooltip */
+ button = g_object_get_data (G_OBJECT (frame), "radio-button");
+
+ if (GTK_IS_RADIO_BUTTON (button))
+ {
+ GSList *list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+
+ for (i = g_slist_length (list) - 1 ; list; list = list->next, i--)
+ {
+ GdkModifierType shift = gimp_get_extend_selection_mask ();
+ GdkModifierType ctrl = gimp_get_constrain_behavior_mask ();
+ GdkModifierType modifier = 0;
+ gchar *tooltip = "";
+ gchar *tip;
+ gchar *label;
+
+ switch (i)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ modifier = 0;
+ tooltip = _("Add handles and transform the image");
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ modifier = shift;
+ tooltip = _("Move transform handles");
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ modifier = ctrl;
+ tooltip = _("Remove transform handles");
+ break;
+ }
+
+ if (modifier)
+ {
+ label = g_strdup_printf ("%s (%s)",
+ gtk_button_get_label (GTK_BUTTON (list->data)),
+ gimp_get_mod_string (modifier));
+ gtk_button_set_label (GTK_BUTTON (list->data), label);
+ g_free (label);
+
+ tip = g_strdup_printf ("%s (%s)",
+ tooltip, gimp_get_mod_string (modifier));
+ gimp_help_set_help_data (list->data, tip, NULL);
+ g_free (tip);
+ }
+ else
+ {
+ gimp_help_set_help_data (list->data, tooltip, NULL);
+ }
+ }
+ }
+
+ return vbox;
+}
diff --git a/app/tools/gimphandletransformoptions.h b/app/tools/gimphandletransformoptions.h
new file mode 100644
index 0000000..c0587cb
--- /dev/null
+++ b/app/tools/gimphandletransformoptions.h
@@ -0,0 +1,54 @@
+/* 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_HANDLE_TRANSFORM_OPTIONS_H__
+#define __GIMP_HANDLE_TRANSFORM_OPTIONS_H__
+
+
+#include "gimptransformgridoptions.h"
+
+
+#define GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS (gimp_handle_transform_options_get_type ())
+#define GIMP_HANDLE_TRANSFORM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS, GimpHandleTransformOptions))
+#define GIMP_HANDLE_TRANSFORM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS, GimpHandleTransformOptionsClass))
+#define GIMP_IS_HANDLE_TRANSFORM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS))
+#define GIMP_IS_HANDLE_TRANSFORM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS))
+#define GIMP_HANDLE_TRANSFORM_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS, GimpHandleTransformOptionsClass))
+
+
+typedef struct _GimpHandleTransformOptions GimpHandleTransformOptions;
+typedef struct _GimpHandleTransformOptionsClass GimpHandleTransformOptionsClass;
+
+struct _GimpHandleTransformOptions
+{
+ GimpTransformGridOptions parent_instance;
+
+ GimpTransformHandleMode handle_mode;
+};
+
+struct _GimpHandleTransformOptionsClass
+{
+ GimpTransformGridOptionsClass parent_class;
+};
+
+
+GType gimp_handle_transform_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_handle_transform_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_HANDLE_TRANSFORM_OPTIONS_H__ */
diff --git a/app/tools/gimphandletransformtool.c b/app/tools/gimphandletransformtool.c
new file mode 100644
index 0000000..1a20e18
--- /dev/null
+++ b/app/tools/gimphandletransformtool.c
@@ -0,0 +1,384 @@
+/* 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 "tools-types.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolhandlegrid.h"
+#include "display/gimptoolgui.h"
+
+#include "gimphandletransformoptions.h"
+#include "gimphandletransformtool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* the transformation is defined by 8 points:
+ *
+ * 4 points on the original image and 4 corresponding points on the
+ * transformed image. The first N_HANDLES points on the transformed
+ * image are visible as handles.
+ *
+ * For these handles, the constants TRANSFORM_HANDLE_N,
+ * TRANSFORM_HANDLE_S, TRANSFORM_HANDLE_E and TRANSFORM_HANDLE_W are
+ * used. Actually, it makes no sense to name the handles with north,
+ * south, east, and west. But this way, we don't need to define even
+ * more enum constants.
+ */
+
+/* index into trans_info array */
+enum
+{
+ X0,
+ Y0,
+ X1,
+ Y1,
+ X2,
+ Y2,
+ X3,
+ Y3,
+ OX0,
+ OY0,
+ OX1,
+ OY1,
+ OX2,
+ OY2,
+ OX3,
+ OY3,
+ N_HANDLES
+};
+
+
+/* local function prototypes */
+
+static void gimp_handle_transform_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_handle_transform_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+static void gimp_handle_transform_tool_prepare (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_handle_transform_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_handle_transform_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_handle_transform_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void gimp_handle_transform_tool_info_to_points (GimpGenericTransformTool *generic);
+
+
+G_DEFINE_TYPE (GimpHandleTransformTool, gimp_handle_transform_tool,
+ GIMP_TYPE_GENERIC_TRANSFORM_TOOL)
+
+#define parent_class gimp_handle_transform_tool_parent_class
+
+
+void
+gimp_handle_transform_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_HANDLE_TRANSFORM_TOOL,
+ GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS,
+ gimp_handle_transform_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-handle-transform-tool",
+ _("Handle Transform"),
+ _("Handle Transform Tool: "
+ "Deform the layer, selection or path with handles"),
+ N_("_Handle Transform"), "<shift>L",
+ NULL, GIMP_HELP_TOOL_HANDLE_TRANSFORM,
+ GIMP_ICON_TOOL_HANDLE_TRANSFORM,
+ data);
+}
+
+static void
+gimp_handle_transform_tool_class_init (GimpHandleTransformToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+ GimpGenericTransformToolClass *generic_class = GIMP_GENERIC_TRANSFORM_TOOL_CLASS (klass);
+
+ tool_class->modifier_key = gimp_handle_transform_tool_modifier_key;
+
+ tg_class->matrix_to_info = gimp_handle_transform_tool_matrix_to_info;
+ tg_class->prepare = gimp_handle_transform_tool_prepare;
+ tg_class->get_widget = gimp_handle_transform_tool_get_widget;
+ tg_class->update_widget = gimp_handle_transform_tool_update_widget;
+ tg_class->widget_changed = gimp_handle_transform_tool_widget_changed;
+
+ generic_class->info_to_points = gimp_handle_transform_tool_info_to_points;
+
+ tr_class->undo_desc = C_("undo-type", "Handle transform");
+ tr_class->progress_text = _("Handle transformation");
+}
+
+static void
+gimp_handle_transform_tool_init (GimpHandleTransformTool *ht_tool)
+{
+ ht_tool->saved_handle_mode = GIMP_HANDLE_MODE_ADD_TRANSFORM;
+}
+
+static void
+gimp_handle_transform_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tool);
+ GimpHandleTransformOptions *options;
+ GdkModifierType shift = gimp_get_extend_selection_mask ();
+ GdkModifierType ctrl = gimp_get_constrain_behavior_mask ();
+ GimpTransformHandleMode handle_mode;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tool);
+
+ handle_mode = options->handle_mode;
+
+ if (press)
+ {
+ if (key == (state & (shift | ctrl)))
+ {
+ /* first modifier pressed */
+ ht_tool->saved_handle_mode = options->handle_mode;
+ }
+ }
+ else
+ {
+ if (! (state & (shift | ctrl)))
+ {
+ /* last modifier released */
+ handle_mode = ht_tool->saved_handle_mode;
+ }
+ }
+
+ if (state & shift)
+ {
+ handle_mode = GIMP_HANDLE_MODE_MOVE;
+ }
+ else if (state & ctrl)
+ {
+ handle_mode = GIMP_HANDLE_MODE_REMOVE;
+ }
+
+ if (handle_mode != options->handle_mode)
+ {
+ g_object_set (options,
+ "handle-mode", handle_mode,
+ NULL);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press,
+ state, display);
+}
+
+static void
+gimp_handle_transform_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ gimp_matrix3_transform_point (transform,
+ tg_tool->trans_info[OX0],
+ tg_tool->trans_info[OY0],
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_matrix3_transform_point (transform,
+ tg_tool->trans_info[OX1],
+ tg_tool->trans_info[OY1],
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_matrix3_transform_point (transform,
+ tg_tool->trans_info[OX2],
+ tg_tool->trans_info[OY2],
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_matrix3_transform_point (transform,
+ tg_tool->trans_info[OX3],
+ tg_tool->trans_info[OY3],
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+}
+
+static void
+gimp_handle_transform_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->prepare (tg_tool);
+
+ tg_tool->trans_info[X0] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y0] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X1] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y1] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X2] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y2] = (gdouble) tr_tool->y2;
+ tg_tool->trans_info[X3] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y3] = (gdouble) tr_tool->y2;
+ tg_tool->trans_info[OX0] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[OY0] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[OX1] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[OY1] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[OX2] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[OY2] = (gdouble) tr_tool->y2;
+ tg_tool->trans_info[OX3] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[OY3] = (gdouble) tr_tool->y2;
+ tg_tool->trans_info[N_HANDLES] = 0;
+}
+
+static GimpToolWidget *
+gimp_handle_transform_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpHandleTransformOptions *options;
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+
+ widget = gimp_tool_handle_grid_new (shell,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2);
+
+ g_object_set (widget,
+ "n-handles", (gint) tg_tool->trans_info[N_HANDLES],
+ "orig-x1", tg_tool->trans_info[OX0],
+ "orig-y1", tg_tool->trans_info[OY0],
+ "orig-x2", tg_tool->trans_info[OX1],
+ "orig-y2", tg_tool->trans_info[OY1],
+ "orig-x3", tg_tool->trans_info[OX2],
+ "orig-y3", tg_tool->trans_info[OY2],
+ "orig-x4", tg_tool->trans_info[OX3],
+ "orig-y4", tg_tool->trans_info[OY3],
+ "trans-x1", tg_tool->trans_info[X0],
+ "trans-y1", tg_tool->trans_info[Y0],
+ "trans-x2", tg_tool->trans_info[X1],
+ "trans-y2", tg_tool->trans_info[Y1],
+ "trans-x3", tg_tool->trans_info[X2],
+ "trans-y3", tg_tool->trans_info[Y2],
+ "trans-x4", tg_tool->trans_info[X3],
+ "trans-y4", tg_tool->trans_info[Y3],
+ NULL);
+
+ g_object_bind_property (G_OBJECT (options), "handle-mode",
+ G_OBJECT (widget), "handle-mode",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ return widget;
+}
+
+static void
+gimp_handle_transform_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpMatrix3 transform;
+ gboolean transform_valid;
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ transform_valid = gimp_transform_grid_tool_info_to_matrix (tg_tool,
+ &transform);
+
+ g_object_set (tg_tool->widget,
+ "show-guides", transform_valid,
+ "n-handles", (gint) tg_tool->trans_info[N_HANDLES],
+ "orig-x1", tg_tool->trans_info[OX0],
+ "orig-y1", tg_tool->trans_info[OY0],
+ "orig-x2", tg_tool->trans_info[OX1],
+ "orig-y2", tg_tool->trans_info[OY1],
+ "orig-x3", tg_tool->trans_info[OX2],
+ "orig-y3", tg_tool->trans_info[OY2],
+ "orig-x4", tg_tool->trans_info[OX3],
+ "orig-y4", tg_tool->trans_info[OY3],
+ "trans-x1", tg_tool->trans_info[X0],
+ "trans-y1", tg_tool->trans_info[Y0],
+ "trans-x2", tg_tool->trans_info[X1],
+ "trans-y2", tg_tool->trans_info[Y1],
+ "trans-x3", tg_tool->trans_info[X2],
+ "trans-y3", tg_tool->trans_info[Y2],
+ "trans-x4", tg_tool->trans_info[X3],
+ "trans-y4", tg_tool->trans_info[Y3],
+ NULL);
+}
+
+static void
+gimp_handle_transform_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ gint n_handles;
+
+ g_object_get (tg_tool->widget,
+ "n-handles", &n_handles,
+ "orig-x1", &tg_tool->trans_info[OX0],
+ "orig-y1", &tg_tool->trans_info[OY0],
+ "orig-x2", &tg_tool->trans_info[OX1],
+ "orig-y2", &tg_tool->trans_info[OY1],
+ "orig-x3", &tg_tool->trans_info[OX2],
+ "orig-y3", &tg_tool->trans_info[OY2],
+ "orig-x4", &tg_tool->trans_info[OX3],
+ "orig-y4", &tg_tool->trans_info[OY3],
+ "trans-x1", &tg_tool->trans_info[X0],
+ "trans-y1", &tg_tool->trans_info[Y0],
+ "trans-x2", &tg_tool->trans_info[X1],
+ "trans-y2", &tg_tool->trans_info[Y1],
+ "trans-x3", &tg_tool->trans_info[X2],
+ "trans-y3", &tg_tool->trans_info[Y2],
+ "trans-x4", &tg_tool->trans_info[X3],
+ "trans-y4", &tg_tool->trans_info[Y3],
+ NULL);
+
+ tg_tool->trans_info[N_HANDLES] = n_handles;
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+gimp_handle_transform_tool_info_to_points (GimpGenericTransformTool *generic)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (generic);
+
+ generic->input_points[0] = (GimpVector2) {tg_tool->trans_info[OX0],
+ tg_tool->trans_info[OY0]};
+ generic->input_points[1] = (GimpVector2) {tg_tool->trans_info[OX1],
+ tg_tool->trans_info[OY1]};
+ generic->input_points[2] = (GimpVector2) {tg_tool->trans_info[OX2],
+ tg_tool->trans_info[OY2]};
+ generic->input_points[3] = (GimpVector2) {tg_tool->trans_info[OX3],
+ tg_tool->trans_info[OY3]};
+
+ generic->output_points[0] = (GimpVector2) {tg_tool->trans_info[X0],
+ tg_tool->trans_info[Y0]};
+ generic->output_points[1] = (GimpVector2) {tg_tool->trans_info[X1],
+ tg_tool->trans_info[Y1]};
+ generic->output_points[2] = (GimpVector2) {tg_tool->trans_info[X2],
+ tg_tool->trans_info[Y2]};
+ generic->output_points[3] = (GimpVector2) {tg_tool->trans_info[X3],
+ tg_tool->trans_info[Y3]};
+}
diff --git a/app/tools/gimphandletransformtool.h b/app/tools/gimphandletransformtool.h
new file mode 100644
index 0000000..064208b
--- /dev/null
+++ b/app/tools/gimphandletransformtool.h
@@ -0,0 +1,57 @@
+/* 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_HANDLE_TRANSFORM_TOOL_H__
+#define __GIMP_HANDLE_TRANSFORM_TOOL_H__
+
+
+#include "gimpgenerictransformtool.h"
+
+
+#define GIMP_TYPE_HANDLE_TRANSFORM_TOOL (gimp_handle_transform_tool_get_type ())
+#define GIMP_HANDLE_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HANDLE_TRANSFORM_TOOL, GimpHandleTransformTool))
+#define GIMP_HANDLE_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HANDLE_TRANSFORM_TOOL, GimpHandleTransformToolClass))
+#define GIMP_IS_HANDLE_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HANDLE_TRANSFORM_TOOL))
+#define GIMP_IS_HANDLE_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HANDLE_TRANSFORM_TOOL))
+#define GIMP_HANDLE_TRANSFORM_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HANDLE_TRANSFORM_TOOL, GimpHandleTransformToolClass))
+
+#define GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS(t) (GIMP_HANDLE_TRANSFORM_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpHandleTransformTool GimpHandleTransformTool;
+typedef struct _GimpHandleTransformToolClass GimpHandleTransformToolClass;
+
+struct _GimpHandleTransformTool
+{
+ GimpGenericTransformTool parent_instance;
+
+ GimpTransformHandleMode saved_handle_mode;
+};
+
+struct _GimpHandleTransformToolClass
+{
+ GimpGenericTransformToolClass parent_class;
+};
+
+
+void gimp_handle_transform_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_handle_transform_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_HANDLE_TRANSFORM_TOOL_H__ */
diff --git a/app/tools/gimphealtool.c b/app/tools/gimphealtool.c
new file mode 100644
index 0000000..bb43459
--- /dev/null
+++ b/app/tools/gimphealtool.c
@@ -0,0 +1,111 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimpsourceoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "gimphealtool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static GtkWidget * gimp_heal_options_gui (GimpToolOptions *tool_options);
+
+
+G_DEFINE_TYPE (GimpHealTool, gimp_heal_tool, GIMP_TYPE_SOURCE_TOOL)
+
+
+void
+gimp_heal_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_HEAL_TOOL,
+ GIMP_TYPE_SOURCE_OPTIONS,
+ gimp_heal_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK,
+ "gimp-heal-tool",
+ _("Healing"),
+ _("Healing Tool: Heal image irregularities"),
+ N_("_Heal"),
+ "H",
+ NULL,
+ GIMP_HELP_TOOL_HEAL,
+ GIMP_ICON_TOOL_HEAL,
+ data);
+}
+
+static void
+gimp_heal_tool_class_init (GimpHealToolClass *klass)
+{
+}
+
+static void
+gimp_heal_tool_init (GimpHealTool *heal)
+{
+ GimpTool *tool = GIMP_TOOL (heal);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_HEAL);
+
+ paint_tool->status = _("Click to heal");
+ paint_tool->status_ctrl = _("%s to set a new heal source");
+
+ source_tool->status_paint = _("Click to heal");
+ /* Translators: the translation of "Click" must be the first word */
+ source_tool->status_set_source = _("Click to set a new heal source");
+ source_tool->status_set_source_ctrl = _("%s to set a new heal source");
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_heal_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *combo;
+
+ /* the sample merged checkbox */
+ button = gimp_prop_check_button_new (config, "sample-merged",
+ _("Sample merged"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the alignment combo */
+ combo = gimp_prop_enum_combo_box_new (config, "align-mode", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Alignment"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ return vbox;
+}
diff --git a/app/tools/gimphealtool.h b/app/tools/gimphealtool.h
new file mode 100644
index 0000000..fdcecb1
--- /dev/null
+++ b/app/tools/gimphealtool.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 __GIMP_HEAL_TOOL_H__
+#define __GIMP_HEAL_TOOL_H__
+
+
+#include "gimpsourcetool.h"
+
+
+#define GIMP_TYPE_HEAL_TOOL (gimp_heal_tool_get_type ())
+#define GIMP_HEAL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HEAL_TOOL, GimpHealTool))
+#define GIMP_HEAL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HEAL_TOOL, GimpHealToolClass))
+#define GIMP_IS_HEAL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HEAL_TOOL))
+#define GIMP_IS_HEAL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HEAL_TOOL))
+#define GIMP_HEAL_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HEAL_TOOL, GimpHealToolClass))
+
+
+typedef struct _GimpHealTool GimpHealTool;
+typedef struct _GimpHealToolClass GimpHealToolClass;
+
+struct _GimpHealTool
+{
+ GimpSourceTool parent_instance;
+};
+
+struct _GimpHealToolClass
+{
+ GimpSourceToolClass parent_class;
+};
+
+
+void gimp_heal_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_heal_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_HEAL_TOOL_H__ */
diff --git a/app/tools/gimphistogramoptions.c b/app/tools/gimphistogramoptions.c
new file mode 100644
index 0000000..e70ca21
--- /dev/null
+++ b/app/tools/gimphistogramoptions.c
@@ -0,0 +1,113 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "libgimpconfig/gimpconfig.h"
+
+#include "tools-types.h"
+
+#include "gimphistogramoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SCALE
+};
+
+
+static void gimp_histogram_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_histogram_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpHistogramOptions, gimp_histogram_options,
+ GIMP_TYPE_FILTER_OPTIONS)
+
+
+static void
+gimp_histogram_options_class_init (GimpHistogramOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_histogram_options_set_property;
+ object_class->get_property = gimp_histogram_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_SCALE,
+ "histogram-scale",
+ _("Histogram Scale"),
+ NULL,
+ GIMP_TYPE_HISTOGRAM_SCALE,
+ GIMP_HISTOGRAM_SCALE_LINEAR,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_histogram_options_init (GimpHistogramOptions *options)
+{
+}
+
+static void
+gimp_histogram_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHistogramOptions *options = GIMP_HISTOGRAM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SCALE:
+ options->scale = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_histogram_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHistogramOptions *options = GIMP_HISTOGRAM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SCALE:
+ g_value_set_enum (value, options->scale);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
diff --git a/app/tools/gimphistogramoptions.h b/app/tools/gimphistogramoptions.h
new file mode 100644
index 0000000..00606c7
--- /dev/null
+++ b/app/tools/gimphistogramoptions.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_HISTOGRAM_OPTIONS_H__
+#define __GIMP_HISTOGRAM_OPTIONS_H__
+
+
+#include "gimpfilteroptions.h"
+
+
+#define GIMP_TYPE_HISTOGRAM_OPTIONS (gimp_histogram_options_get_type ())
+#define GIMP_HISTOGRAM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HISTOGRAM_OPTIONS, GimpHistogramOptions))
+#define GIMP_HISTOGRAM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HISTOGRAM_OPTIONS, GimpHistogramOptionsClass))
+#define GIMP_IS_HISTOGRAM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HISTOGRAM_OPTIONS))
+#define GIMP_IS_HISTOGRAM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HISTOGRAM_OPTIONS))
+#define GIMP_HISTOGRAM_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HISTOGRAM_OPTIONS, GimpHistogramOptionsClass))
+
+
+typedef struct _GimpHistogramOptions GimpHistogramOptions;
+typedef struct _GimpHistogramOptionsClass GimpHistogramOptionsClass;
+
+struct _GimpHistogramOptions
+{
+ GimpFilterOptions parent_instance;
+
+ GimpHistogramScale scale;
+};
+
+struct _GimpHistogramOptionsClass
+{
+ GimpFilterOptionsClass parent_class;
+};
+
+
+GType gimp_histogram_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_HISTOGRAM_OPTIONS_H__ */
diff --git a/app/tools/gimpinkoptions-gui.c b/app/tools/gimpinkoptions-gui.c
new file mode 100644
index 0000000..70f9e7a
--- /dev/null
+++ b/app/tools/gimpinkoptions-gui.c
@@ -0,0 +1,143 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpconfig-utils.h"
+
+#include "paint/gimpinkoptions.h"
+
+#include "widgets/gimpblobeditor.h"
+#include "widgets/gimppropwidgets.h"
+
+#include "gimpinkoptions-gui.h"
+#include "gimppaintoptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+GtkWidget *
+gimp_ink_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpInkOptions *ink_options = GIMP_INK_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *vbox2;
+ GtkWidget *scale;
+ GtkWidget *blob_box;
+ GtkWidget *hbox;
+ GtkWidget *editor;
+ GtkSizeGroup *size_group;
+
+ /* adjust sliders */
+ frame = gimp_frame_new (_("Adjustment"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* size slider */
+ scale = gimp_prop_spin_scale_new (config, "size", NULL,
+ 1.0, 2.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* angle adjust slider */
+ scale = gimp_prop_spin_scale_new (config, "tilt-angle", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* sens sliders */
+ frame = gimp_frame_new (_("Sensitivity"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* size sens slider */
+ scale = gimp_prop_spin_scale_new (config, "size-sensitivity", NULL,
+ 0.01, 0.1, 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* tilt sens slider */
+ scale = gimp_prop_spin_scale_new (config, "tilt-sensitivity", NULL,
+ 0.01, 0.1, 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* velocity sens slider */
+ scale = gimp_prop_spin_scale_new (config, "vel-sensitivity", NULL,
+ 0.01, 0.1, 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* Blob shape widgets */
+ frame = gimp_frame_new (_("Shape"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), hbox);
+ gtk_widget_show (hbox);
+
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+ /* Blob type radiobuttons */
+ blob_box = gimp_prop_enum_icon_box_new (config, "blob-type",
+ "gimp-shape", 0, 0);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (blob_box),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_box_pack_start (GTK_BOX (hbox), blob_box, FALSE, FALSE, 0);
+ gtk_widget_show (blob_box);
+
+ gtk_size_group_add_widget (size_group, blob_box);
+ g_object_unref (size_group);
+
+ /* Blob editor */
+ frame = gtk_aspect_frame_new (NULL, 0.0, 0.5, 1.0, FALSE);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ gtk_size_group_add_widget (size_group, frame);
+
+ editor = gimp_blob_editor_new (ink_options->blob_type,
+ ink_options->blob_aspect,
+ ink_options->blob_angle);
+ gtk_container_add (GTK_CONTAINER (frame), editor);
+ gtk_widget_show (editor);
+
+ gimp_config_connect (config, G_OBJECT (editor), "blob-type");
+ gimp_config_connect (config, G_OBJECT (editor), "blob-aspect");
+ gimp_config_connect (config, G_OBJECT (editor), "blob-angle");
+
+ return vbox;
+}
diff --git a/app/tools/gimpinkoptions-gui.h b/app/tools/gimpinkoptions-gui.h
new file mode 100644
index 0000000..b449d90
--- /dev/null
+++ b/app/tools/gimpinkoptions-gui.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_INK_OPTIONS_GUI_H__
+#define __GIMP_INK_OPTIONS_GUI_H__
+
+
+GtkWidget * gimp_ink_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_INK_OPTIONS_GUI_H__ */
diff --git a/app/tools/gimpinktool.c b/app/tools/gimpinktool.c
new file mode 100644
index 0000000..4935f7b
--- /dev/null
+++ b/app/tools/gimpinktool.c
@@ -0,0 +1,122 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "paint/gimpinkoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "gimpinkoptions-gui.h"
+#include "gimpinktool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static GimpCanvasItem * gimp_ink_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y);
+static gboolean gimp_ink_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable);
+
+
+G_DEFINE_TYPE (GimpInkTool, gimp_ink_tool, GIMP_TYPE_PAINT_TOOL)
+
+#define parent_class gimp_ink_tool_parent_class
+
+
+void
+gimp_ink_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_INK_TOOL,
+ GIMP_TYPE_INK_OPTIONS,
+ gimp_ink_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND |
+ GIMP_CONTEXT_PROP_MASK_OPACITY |
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE,
+ "gimp-ink-tool",
+ _("Ink"),
+ _("Ink Tool: Calligraphy-style painting"),
+ N_("In_k"), "K",
+ NULL, GIMP_HELP_TOOL_INK,
+ GIMP_ICON_TOOL_INK,
+ data);
+}
+
+static void
+gimp_ink_tool_class_init (GimpInkToolClass *klass)
+{
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ paint_tool_class->get_outline = gimp_ink_tool_get_outline;
+ paint_tool_class->is_alpha_only = gimp_ink_tool_is_alpha_only;
+}
+
+static void
+gimp_ink_tool_init (GimpInkTool *ink_tool)
+{
+ GimpTool *tool = GIMP_TOOL (ink_tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_INK);
+ gimp_tool_control_set_action_size (tool->control,
+ "tools/tools-ink-blob-size-set");
+ gimp_tool_control_set_action_aspect (tool->control,
+ "tools/tools-ink-blob-aspect-set");
+ gimp_tool_control_set_action_angle (tool->control,
+ "tools/tools-ink-blob-angle-set");
+
+ gimp_paint_tool_enable_color_picker (GIMP_PAINT_TOOL (ink_tool),
+ GIMP_COLOR_PICK_TARGET_FOREGROUND);
+}
+
+static GimpCanvasItem *
+gimp_ink_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y)
+{
+ GimpInkOptions *options = GIMP_INK_TOOL_GET_OPTIONS (paint_tool);
+
+ gimp_paint_tool_set_draw_circle (paint_tool, TRUE,
+ options->size);
+
+ return NULL;
+}
+
+static gboolean
+gimp_ink_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable)
+{
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpLayerMode paint_mode = gimp_context_get_paint_mode (context);
+
+ return gimp_layer_mode_is_alpha_only (paint_mode);
+}
diff --git a/app/tools/gimpinktool.h b/app/tools/gimpinktool.h
new file mode 100644
index 0000000..e0076ad
--- /dev/null
+++ b/app/tools/gimpinktool.h
@@ -0,0 +1,55 @@
+/* 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_INK_TOOL_H__
+#define __GIMP_INK_TOOL_H__
+
+
+#include "gimppainttool.h"
+
+
+#define GIMP_TYPE_INK_TOOL (gimp_ink_tool_get_type ())
+#define GIMP_INK_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INK_TOOL, GimpInkTool))
+#define GIMP_INK_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INK_TOOL, GimpInkToolClass))
+#define GIMP_IS_INK_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INK_TOOL))
+#define GIMP_IS_INK_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INK_TOOL))
+#define GIMP_INK_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INK_TOOL, GimpInkToolClass))
+
+#define GIMP_INK_TOOL_GET_OPTIONS(t) (GIMP_INK_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpInkTool GimpInkTool;
+typedef struct _GimpInkToolClass GimpInkToolClass;
+
+struct _GimpInkTool
+{
+ GimpPaintTool parent_instance;
+};
+
+struct _GimpInkToolClass
+{
+ GimpPaintToolClass parent_class;
+};
+
+
+void gimp_ink_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_ink_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_INK_TOOL_H__ */
diff --git a/app/tools/gimpiscissorsoptions.c b/app/tools/gimpiscissorsoptions.c
new file mode 100644
index 0000000..bad0155
--- /dev/null
+++ b/app/tools/gimpiscissorsoptions.c
@@ -0,0 +1,133 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+
+#include "gimpiscissorstool.h"
+#include "gimpiscissorsoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_INTERACTIVE
+};
+
+
+static void gimp_iscissors_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_iscissors_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpIscissorsOptions, gimp_iscissors_options,
+ GIMP_TYPE_SELECTION_OPTIONS)
+
+#define parent_class gimp_iscissors_options_parent_class
+
+
+static void
+gimp_iscissors_options_class_init (GimpIscissorsOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_iscissors_options_set_property;
+ object_class->get_property = gimp_iscissors_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_INTERACTIVE,
+ "interactive",
+ _("Interactive boundary"),
+ _("Display future selection segment "
+ "as you drag a control node"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_iscissors_options_init (GimpIscissorsOptions *options)
+{
+}
+
+static void
+gimp_iscissors_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpIscissorsOptions *options = GIMP_ISCISSORS_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_INTERACTIVE:
+ options->interactive = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_iscissors_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpIscissorsOptions *options = GIMP_ISCISSORS_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_INTERACTIVE:
+ g_value_set_boolean (value, options->interactive);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_iscissors_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_selection_options_gui (tool_options);
+ GtkWidget *button;
+
+ button = gimp_prop_check_button_new (config, "interactive", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ return vbox;
+}
diff --git a/app/tools/gimpiscissorsoptions.h b/app/tools/gimpiscissorsoptions.h
new file mode 100644
index 0000000..a60c251
--- /dev/null
+++ b/app/tools/gimpiscissorsoptions.h
@@ -0,0 +1,49 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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_ISCISSORS_OPTIONS_H__
+#define __GIMP_ISCISSORS_OPTIONS_H__
+
+
+#include "gimpselectionoptions.h"
+
+
+#define GIMP_TYPE_ISCISSORS_OPTIONS (gimp_iscissors_options_get_type ())
+#define GIMP_ISCISSORS_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ISCISSORS_OPTIONS, GimpIscissorsOptions))
+#define GIMP_ISCISSORS_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ISCISSORS_OPTIONS, GimpIscissorsOptionsClass))
+#define GIMP_IS_ISCISSORS_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ISCISSORS_OPTIONS))
+#define GIMP_IS_ISCISSORS_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ISCISSORS_OPTIONS))
+#define GIMP_ISCISSORS_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ISCISSORS_OPTIONS, GimpIscissorsOptionsClass))
+
+
+typedef struct _GimpIscissorsOptions GimpIscissorsOptions;
+typedef struct _GimpToolOptionsClass GimpIscissorsOptionsClass;
+
+struct _GimpIscissorsOptions
+{
+ GimpSelectionOptions parent_instance;
+
+ gboolean interactive;
+};
+
+
+GType gimp_iscissors_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_iscissors_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_ISCISSORS_OPTIONS_H__ */
diff --git a/app/tools/gimpiscissorstool.c b/app/tools/gimpiscissorstool.c
new file mode 100644
index 0000000..9e1706c
--- /dev/null
+++ b/app/tools/gimpiscissorstool.c
@@ -0,0 +1,2188 @@
+/* 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/>.
+ */
+
+/* This tool is based on a paper from SIGGRAPH '95:
+ * "Intelligent Scissors for Image Composition", Eric N. Mortensen and
+ * William A. Barrett, Brigham Young University.
+ *
+ * thanks to Professor D. Forsyth for prompting us to implement this tool. */
+
+/* Personal note: Dr. Barrett, one of the authors of the paper written above
+ * is not only one of the most brilliant people I have ever met, he is an
+ * incredible professor who genuinely cares about his students and wants them
+ * to learn as much as they can about the topic.
+ *
+ * I didn't even notice I was taking a class from the person who wrote the
+ * paper until halfway through the semester.
+ * -- Rockwalrus
+ */
+
+/* The history of this implementation is lonog and varied. It was
+ * originally done by Spencer and Peter, and worked fine in the 0.54
+ * (motif only) release of GIMP. Later revisions (0.99.something
+ * until about 1.1.4) completely changed the algorithm used, until it
+ * bore little resemblance to the one described in the paper above.
+ * The 0.54 version of the algorithm was then forwards ported to 1.1.4
+ * by Austin Donnelly.
+ */
+
+/* Livewire boundary implementation done by Laramie Leavitt */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimpchannel-select.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpscanconvert.h"
+#include "core/gimptempbuf.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+
+#include "gimpiscissorsoptions.h"
+#include "gimpiscissorstool.h"
+#include "gimptilehandleriscissors.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* defines */
+#define GRADIENT_SEARCH 32 /* how far to look when snapping to an edge */
+#define EXTEND_BY 0.2 /* proportion to expand cost map by */
+#define FIXED 5 /* additional fixed size to expand cost map */
+
+#define COST_WIDTH 2 /* number of bytes for each pixel in cost map */
+
+/* weight to give between gradient (_G) and direction (_D) */
+#define OMEGA_D 0.2
+#define OMEGA_G 0.8
+
+/* sentinel to mark seed point in ?cost? map */
+#define SEED_POINT 9
+
+/* Functional defines */
+#define PIXEL_COST(x) ((x) >> 8)
+#define PIXEL_DIR(x) ((x) & 0x000000ff)
+
+
+struct _ISegment
+{
+ gint x1, y1;
+ gint x2, y2;
+ GPtrArray *points;
+};
+
+struct _ICurve
+{
+ GQueue *segments;
+ gboolean first_point;
+ gboolean closed;
+};
+
+
+/* local function prototypes */
+
+static void gimp_iscissors_tool_finalize (GObject *object);
+
+static void gimp_iscissors_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_iscissors_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_iscissors_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_iscissors_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_iscissors_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_iscissors_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_iscissors_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static const gchar * gimp_iscissors_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+static const gchar * gimp_iscissors_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_iscissors_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_iscissors_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+
+static void gimp_iscissors_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_iscissors_tool_push_undo (GimpIscissorsTool *iscissors);
+static void gimp_iscissors_tool_pop_undo (GimpIscissorsTool *iscissors);
+static void gimp_iscissors_tool_free_redo (GimpIscissorsTool *iscissors);
+
+static void gimp_iscissors_tool_halt (GimpIscissorsTool *iscissors,
+ GimpDisplay *display);
+static void gimp_iscissors_tool_commit (GimpIscissorsTool *iscissors,
+ GimpDisplay *display);
+
+static void iscissors_convert (GimpIscissorsTool *iscissors,
+ GimpDisplay *display);
+static GeglBuffer * gradient_map_new (GimpPickable *pickable);
+
+static void find_optimal_path (GeglBuffer *gradient_map,
+ GimpTempBuf *dp_buf,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint xs,
+ gint ys);
+static void find_max_gradient (GimpIscissorsTool *iscissors,
+ GimpPickable *pickable,
+ gint *x,
+ gint *y);
+static void calculate_segment (GimpIscissorsTool *iscissors,
+ ISegment *segment);
+static GimpCanvasItem * iscissors_draw_segment (GimpDrawTool *draw_tool,
+ ISegment *segment);
+
+static gint mouse_over_vertex (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y);
+static gboolean clicked_on_vertex (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y);
+static GList * mouse_over_segment (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y);
+static gboolean clicked_on_segment (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y);
+
+static GPtrArray * plot_pixels (GimpTempBuf *dp_buf,
+ gint x1,
+ gint y1,
+ gint xs,
+ gint ys,
+ gint xe,
+ gint ye);
+
+static ISegment * isegment_new (gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+static ISegment * isegment_copy (ISegment *segment);
+static void isegment_free (ISegment *segment);
+
+static ICurve * icurve_new (void);
+static ICurve * icurve_copy (ICurve *curve);
+static void icurve_clear (ICurve *curve);
+static void icurve_free (ICurve *curve);
+
+static ISegment * icurve_append_segment (ICurve *curve,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+static ISegment * icurve_insert_segment (ICurve *curve,
+ GList *sibling,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+static void icurve_delete_segment (ICurve *curve,
+ ISegment *segment);
+
+static void icurve_close (ICurve *curve);
+
+static GimpScanConvert *
+ icurve_create_scan_convert (ICurve *curve);
+
+
+/* static variables */
+
+/* where to move on a given link direction */
+static const gint move[8][2] =
+{
+ { 1, 0 },
+ { 0, 1 },
+ { -1, 1 },
+ { 1, 1 },
+ { -1, 0 },
+ { 0, -1 },
+ { 1, -1 },
+ { -1, -1 },
+};
+
+/* IE:
+ * '---+---+---`
+ * | 7 | 5 | 6 |
+ * +---+---+---+
+ * | 4 | | 0 |
+ * +---+---+---+
+ * | 2 | 1 | 3 |
+ * `---+---+---'
+ */
+
+static gfloat distance_weights[GRADIENT_SEARCH * GRADIENT_SEARCH];
+
+static gint diagonal_weight[256];
+static gint direction_value[256][4];
+
+
+G_DEFINE_TYPE (GimpIscissorsTool, gimp_iscissors_tool,
+ GIMP_TYPE_SELECTION_TOOL)
+
+#define parent_class gimp_iscissors_tool_parent_class
+
+
+void
+gimp_iscissors_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_ISCISSORS_TOOL,
+ GIMP_TYPE_ISCISSORS_OPTIONS,
+ gimp_iscissors_options_gui,
+ 0,
+ "gimp-iscissors-tool",
+ _("Scissors Select"),
+ _("Scissors Select Tool: Select shapes using intelligent edge-fitting"),
+ N_("Intelligent _Scissors"),
+ "I",
+ NULL, GIMP_HELP_TOOL_ISCISSORS,
+ GIMP_ICON_TOOL_ISCISSORS,
+ data);
+}
+
+static void
+gimp_iscissors_tool_class_init (GimpIscissorsToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+ gint i, j;
+ gint radius;
+
+ object_class->finalize = gimp_iscissors_tool_finalize;
+
+ tool_class->control = gimp_iscissors_tool_control;
+ tool_class->button_press = gimp_iscissors_tool_button_press;
+ tool_class->button_release = gimp_iscissors_tool_button_release;
+ tool_class->motion = gimp_iscissors_tool_motion;
+ tool_class->key_press = gimp_iscissors_tool_key_press;
+ tool_class->oper_update = gimp_iscissors_tool_oper_update;
+ tool_class->cursor_update = gimp_iscissors_tool_cursor_update;
+ tool_class->can_undo = gimp_iscissors_tool_can_undo;
+ tool_class->can_redo = gimp_iscissors_tool_can_redo;
+ tool_class->undo = gimp_iscissors_tool_undo;
+ tool_class->redo = gimp_iscissors_tool_redo;
+
+ draw_tool_class->draw = gimp_iscissors_tool_draw;
+
+ for (i = 0; i < 256; i++)
+ {
+ /* The diagonal weight array */
+ diagonal_weight[i] = (int) (i * G_SQRT2);
+
+ /* The direction value array */
+ direction_value[i][0] = (127 - abs (127 - i)) * 2;
+ direction_value[i][1] = abs (127 - i) * 2;
+ direction_value[i][2] = abs (191 - i) * 2;
+ direction_value[i][3] = abs (63 - i) * 2;
+ }
+
+ /* set the 256th index of the direction_values to the highest cost */
+ direction_value[255][0] = 255;
+ direction_value[255][1] = 255;
+ direction_value[255][2] = 255;
+ direction_value[255][3] = 255;
+
+ /* compute the distance weights */
+ radius = GRADIENT_SEARCH >> 1;
+
+ for (i = 0; i < GRADIENT_SEARCH; i++)
+ for (j = 0; j < GRADIENT_SEARCH; j++)
+ distance_weights[i * GRADIENT_SEARCH + j] =
+ 1.0 / (1 + sqrt (SQR (i - radius) + SQR (j - radius)));
+}
+
+static void
+gimp_iscissors_tool_init (GimpIscissorsTool *iscissors)
+{
+ GimpTool *tool = GIMP_TOOL (iscissors);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE_SIZE |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_ISCISSORS);
+
+ iscissors->op = ISCISSORS_OP_NONE;
+ iscissors->curve = icurve_new ();
+ iscissors->state = NO_ACTION;
+}
+
+static void
+gimp_iscissors_tool_finalize (GObject *object)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (object);
+
+ icurve_free (iscissors->curve);
+ iscissors->curve = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_iscissors_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_iscissors_tool_halt (iscissors, display);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_iscissors_tool_commit (iscissors, display);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_iscissors_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+ GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ ISegment *segment;
+
+ iscissors->x = RINT (coords->x);
+ iscissors->y = RINT (coords->y);
+
+ /* If the tool was being used in another image...reset it */
+ if (display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ gimp_tool_control_activate (tool->control);
+ tool->display = display;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ switch (iscissors->state)
+ {
+ case NO_ACTION:
+ iscissors->state = SEED_PLACEMENT;
+
+ if (! (state & gimp_get_extend_selection_mask ()))
+ find_max_gradient (iscissors, GIMP_PICKABLE (image),
+ &iscissors->x, &iscissors->y);
+
+ iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width (image) - 1);
+ iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1);
+
+ gimp_iscissors_tool_push_undo (iscissors);
+
+ segment = icurve_append_segment (iscissors->curve,
+ iscissors->x,
+ iscissors->y,
+ iscissors->x,
+ iscissors->y);
+
+ /* Initialize the draw tool only on starting the tool */
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+ break;
+
+ default:
+ /* Check if the mouse click occurred on a vertex or the curve itself */
+ if (clicked_on_vertex (iscissors, coords->x, coords->y))
+ {
+ iscissors->state = SEED_ADJUSTMENT;
+
+ /* recalculate both segments */
+ if (iscissors->segment1)
+ {
+ iscissors->segment1->x1 = iscissors->x;
+ iscissors->segment1->y1 = iscissors->y;
+
+ if (options->interactive)
+ calculate_segment (iscissors, iscissors->segment1);
+ }
+
+ if (iscissors->segment2)
+ {
+ iscissors->segment2->x2 = iscissors->x;
+ iscissors->segment2->y2 = iscissors->y;
+
+ if (options->interactive)
+ calculate_segment (iscissors, iscissors->segment2);
+ }
+ }
+ /* If the iscissors is closed, check if the click was inside */
+ else if (iscissors->curve->closed && iscissors->mask &&
+ gimp_pickable_get_opacity_at (GIMP_PICKABLE (iscissors->mask),
+ iscissors->x,
+ iscissors->y))
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ }
+ else if (! iscissors->curve->closed)
+ {
+ /* if we're not closed, we're adding a new point */
+
+ ISegment *last = g_queue_peek_tail (iscissors->curve->segments);
+
+ iscissors->state = SEED_PLACEMENT;
+
+ gimp_iscissors_tool_push_undo (iscissors);
+
+ if (last->x1 == last->x2 &&
+ last->y1 == last->y2)
+ {
+ last->x2 = iscissors->x;
+ last->y2 = iscissors->y;
+
+ if (options->interactive)
+ calculate_segment (iscissors, last);
+ }
+ else
+ {
+ segment = icurve_append_segment (iscissors->curve,
+ last->x2,
+ last->y2,
+ iscissors->x,
+ iscissors->y);
+
+ if (options->interactive)
+ calculate_segment (iscissors, segment);
+ }
+ }
+ break;
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+iscissors_convert (GimpIscissorsTool *iscissors,
+ GimpDisplay *display)
+{
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (iscissors);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpScanConvert *sc;
+
+ sc = icurve_create_scan_convert (iscissors->curve);
+
+ if (iscissors->mask)
+ g_object_unref (iscissors->mask);
+
+ iscissors->mask = gimp_channel_new_mask (image,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+ gimp_scan_convert_render (sc,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (iscissors->mask)),
+ 0, 0, options->antialias);
+
+ gimp_scan_convert_free (sc);
+}
+
+static void
+gimp_iscissors_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+ GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ /* Make sure X didn't skip the button release event -- as it's known
+ * to do
+ */
+ if (iscissors->state == WAITING)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* Progress to the next stage of intelligent selection */
+ switch (iscissors->state)
+ {
+ case SEED_PLACEMENT:
+ /* Add a new segment */
+ if (! iscissors->curve->first_point)
+ {
+ /* Determine if we're connecting to the first point */
+
+ ISegment *segment = g_queue_peek_head (iscissors->curve->segments);
+
+ if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), display,
+ iscissors->x, iscissors->y,
+ GIMP_HANDLE_CIRCLE,
+ segment->x1, segment->y1,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ iscissors->x = segment->x1;
+ iscissors->y = segment->y1;
+
+ icurve_close (iscissors->curve);
+
+ if (! options->interactive)
+ {
+ segment = g_queue_peek_tail (iscissors->curve->segments);
+ calculate_segment (iscissors, segment);
+ }
+
+ gimp_iscissors_tool_free_redo (iscissors);
+ }
+ else
+ {
+ segment = g_queue_peek_tail (iscissors->curve->segments);
+
+ if (segment->x1 != segment->x2 ||
+ segment->y1 != segment->y2)
+ {
+ if (! options->interactive)
+ calculate_segment (iscissors, segment);
+
+ gimp_iscissors_tool_free_redo (iscissors);
+ }
+ else
+ {
+ gimp_iscissors_tool_pop_undo (iscissors);
+ }
+ }
+ }
+ else /* this was our first point */
+ {
+ iscissors->curve->first_point = FALSE;
+
+ gimp_iscissors_tool_free_redo (iscissors);
+ }
+ break;
+
+ case SEED_ADJUSTMENT:
+ if (state & gimp_get_modify_selection_mask ())
+ {
+ if (iscissors->segment1 && iscissors->segment2)
+ {
+ icurve_delete_segment (iscissors->curve,
+ iscissors->segment2);
+
+ calculate_segment (iscissors, iscissors->segment1);
+ }
+ }
+ else
+ {
+ /* recalculate both segments */
+
+ if (iscissors->segment1)
+ {
+ if (! options->interactive)
+ calculate_segment (iscissors, iscissors->segment1);
+ }
+
+ if (iscissors->segment2)
+ {
+ if (! options->interactive)
+ calculate_segment (iscissors, iscissors->segment2);
+ }
+ }
+
+ gimp_iscissors_tool_free_redo (iscissors);
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch (iscissors->state)
+ {
+ case SEED_PLACEMENT:
+ case SEED_ADJUSTMENT:
+ gimp_iscissors_tool_pop_undo (iscissors);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (iscissors->curve->first_point)
+ iscissors->state = NO_ACTION;
+ else
+ iscissors->state = WAITING;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ /* convert the curves into a region */
+ if (iscissors->curve->closed)
+ iscissors_convert (iscissors, display);
+}
+
+static void
+gimp_iscissors_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+ GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ ISegment *segment;
+
+ if (iscissors->state == NO_ACTION)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ iscissors->x = RINT (coords->x);
+ iscissors->y = RINT (coords->y);
+
+ /* Hold the shift key down to disable the auto-edge snap feature */
+ if (! (state & gimp_get_extend_selection_mask ()))
+ find_max_gradient (iscissors, GIMP_PICKABLE (image),
+ &iscissors->x, &iscissors->y);
+
+ iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width (image) - 1);
+ iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1);
+
+ switch (iscissors->state)
+ {
+ case SEED_PLACEMENT:
+ segment = g_queue_peek_tail (iscissors->curve->segments);
+
+ segment->x2 = iscissors->x;
+ segment->y2 = iscissors->y;
+
+ if (iscissors->curve->first_point)
+ {
+ segment->x1 = segment->x2;
+ segment->y1 = segment->y2;
+ }
+ else
+ {
+ if (options->interactive)
+ calculate_segment (iscissors, segment);
+ }
+ break;
+
+ case SEED_ADJUSTMENT:
+ if (iscissors->segment1)
+ {
+ iscissors->segment1->x1 = iscissors->x;
+ iscissors->segment1->y1 = iscissors->y;
+
+ if (options->interactive)
+ calculate_segment (iscissors, iscissors->segment1);
+ }
+
+ if (iscissors->segment2)
+ {
+ iscissors->segment2->x2 = iscissors->x;
+ iscissors->segment2->y2 = iscissors->y;
+
+ if (options->interactive)
+ calculate_segment (iscissors, iscissors->segment2);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_iscissors_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (draw_tool);
+ GimpIscissorsOptions *options = GIMP_ISCISSORS_TOOL_GET_OPTIONS (draw_tool);
+ GimpCanvasItem *item;
+ GList *list;
+
+ /* First, render all segments and lines */
+ if (! iscissors->curve->first_point)
+ {
+ for (list = g_queue_peek_head_link (iscissors->curve->segments);
+ list;
+ list = g_list_next (list))
+ {
+ ISegment *segment = list->data;
+
+ /* plot the segment */
+ item = iscissors_draw_segment (draw_tool, segment);
+
+ /* if this segment is currently being added or adjusted */
+ if ((iscissors->state == SEED_PLACEMENT &&
+ ! list->next)
+ ||
+ (iscissors->state == SEED_ADJUSTMENT &&
+ (segment == iscissors->segment1 ||
+ segment == iscissors->segment2)))
+ {
+ if (! options->interactive)
+ item = gimp_draw_tool_add_line (draw_tool,
+ segment->x1, segment->y1,
+ segment->x2, segment->y2);
+
+ if (item)
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+ }
+ }
+
+ /* Then, render the handles on top of the segments */
+ for (list = g_queue_peek_head_link (iscissors->curve->segments);
+ list;
+ list = g_list_next (list))
+ {
+ ISegment *segment = list->data;
+
+ if (! iscissors->curve->first_point)
+ {
+ gboolean adjustment = (iscissors->state == SEED_ADJUSTMENT &&
+ segment == iscissors->segment1);
+
+ item = gimp_draw_tool_add_handle (draw_tool,
+ adjustment ?
+ GIMP_HANDLE_CROSS :
+ GIMP_HANDLE_FILLED_CIRCLE,
+ segment->x1,
+ segment->y1,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ if (adjustment)
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+
+ /* Draw the last point if the curve is not closed */
+ if (! list->next && ! iscissors->curve->closed)
+ {
+ gboolean placement = (iscissors->state == SEED_PLACEMENT);
+
+ item = gimp_draw_tool_add_handle (draw_tool,
+ placement ?
+ GIMP_HANDLE_CROSS :
+ GIMP_HANDLE_FILLED_CIRCLE,
+ segment->x2,
+ segment->y2,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ if (placement)
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+ }
+}
+
+static GimpCanvasItem *
+iscissors_draw_segment (GimpDrawTool *draw_tool,
+ ISegment *segment)
+{
+ GimpCanvasItem *item;
+ GimpVector2 *points;
+ gpointer *point;
+ gint i, len;
+
+ if (! segment->points)
+ return NULL;
+
+ len = segment->points->len;
+
+ points = g_new (GimpVector2, len);
+
+ for (i = 0, point = segment->points->pdata; i < len; i++, point++)
+ {
+ guint32 coords = GPOINTER_TO_INT (*point);
+
+ points[i].x = (coords & 0x0000ffff);
+ points[i].y = (coords >> 16);
+ }
+
+ item = gimp_draw_tool_add_lines (draw_tool, points, len, NULL, FALSE);
+
+ g_free (points);
+
+ return item;
+}
+
+static void
+gimp_iscissors_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+ /* parent sets a message in the status bar, but it will be replaced here */
+
+ if (mouse_over_vertex (iscissors, coords->x, coords->y) > 1)
+ {
+ GdkModifierType snap_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType remove_mask = gimp_get_modify_selection_mask ();
+
+ if (state & remove_mask)
+ {
+ gimp_tool_replace_status (tool, display,
+ _("Click to remove this point"));
+ iscissors->op = ISCISSORS_OP_REMOVE_POINT;
+ }
+ else
+ {
+ gchar *status =
+ gimp_suggest_modifiers (_("Click-Drag to move this point"),
+ (snap_mask | remove_mask) & ~state,
+ _("%s: disable auto-snap"),
+ _("%s: remove this point"),
+ NULL);
+ gimp_tool_replace_status (tool, display, "%s", status);
+ g_free (status);
+ iscissors->op = ISCISSORS_OP_MOVE_POINT;
+ }
+ }
+ else if (mouse_over_segment (iscissors, coords->x, coords->y))
+ {
+ ISegment *segment = g_queue_peek_head (iscissors->curve->segments);
+
+ if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), display,
+ RINT (coords->x), RINT (coords->y),
+ GIMP_HANDLE_CIRCLE,
+ segment->x1, segment->y1,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ gimp_tool_replace_status (tool, display,
+ _("Click to close the curve"));
+ iscissors->op = ISCISSORS_OP_CONNECT;
+ }
+ else
+ {
+ gimp_tool_replace_status (tool, display,
+ _("Click to add a point on this segment"));
+ iscissors->op = ISCISSORS_OP_ADD_POINT;
+ }
+ }
+ else if (iscissors->curve->closed && iscissors->mask)
+ {
+ if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (iscissors->mask),
+ RINT (coords->x),
+ RINT (coords->y)))
+ {
+ if (proximity)
+ {
+ gimp_tool_replace_status (tool, display,
+ _("Click or press Enter to convert to"
+ " a selection"));
+ }
+ iscissors->op = ISCISSORS_OP_SELECT;
+ }
+ else
+ {
+ if (proximity)
+ {
+ gimp_tool_replace_status (tool, display,
+ _("Press Enter to convert to a"
+ " selection"));
+ }
+ iscissors->op = ISCISSORS_OP_IMPOSSIBLE;
+ }
+ }
+ else
+ {
+ switch (iscissors->state)
+ {
+ case WAITING:
+ if (proximity)
+ {
+ GdkModifierType snap_mask = gimp_get_extend_selection_mask ();
+ gchar *status;
+
+ status = gimp_suggest_modifiers (_("Click or Click-Drag to add a"
+ " point"),
+ snap_mask & ~state,
+ _("%s: disable auto-snap"),
+ NULL, NULL);
+ gimp_tool_replace_status (tool, display, "%s", status);
+ g_free (status);
+ }
+ iscissors->op = ISCISSORS_OP_ADD_POINT;
+ break;
+
+ default:
+ /* if NO_ACTION, keep parent's status bar message (selection tool) */
+ iscissors->op = ISCISSORS_OP_NONE;
+ break;
+ }
+ }
+}
+
+static void
+gimp_iscissors_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ switch (iscissors->op)
+ {
+ case ISCISSORS_OP_SELECT:
+ {
+ GimpSelectionOptions *options;
+
+ options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+
+ /* Do not overwrite the modifiers for add, subtract, intersect */
+ if (options->operation == GIMP_CHANNEL_OP_REPLACE)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_SELECT;
+ }
+ }
+ break;
+
+ case ISCISSORS_OP_MOVE_POINT:
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case ISCISSORS_OP_ADD_POINT:
+ modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ break;
+
+ case ISCISSORS_OP_REMOVE_POINT:
+ modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ break;
+
+ case ISCISSORS_OP_CONNECT:
+ modifier = GIMP_CURSOR_MODIFIER_JOIN;
+ break;
+
+ case ISCISSORS_OP_IMPOSSIBLE:
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ break;
+
+ default:
+ break;
+ }
+
+ if (modifier != GIMP_CURSOR_MODIFIER_NONE)
+ {
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_ISCISSORS,
+ modifier);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords,
+ state, display);
+ }
+}
+
+static gboolean
+gimp_iscissors_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ if (display != tool->display)
+ return FALSE;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ if (! iscissors->curve->closed &&
+ g_queue_peek_tail (iscissors->curve->segments))
+ {
+ ISegment *segment = g_queue_peek_tail (iscissors->curve->segments);
+
+ if (g_queue_get_length (iscissors->curve->segments) > 1)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_iscissors_tool_push_undo (iscissors);
+ icurve_delete_segment (iscissors->curve, segment);
+ gimp_iscissors_tool_free_redo (iscissors);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+ else if (segment->x2 != segment->x1 || segment->y2 != segment->y1)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_iscissors_tool_push_undo (iscissors);
+ segment->x2 = segment->x1;
+ segment->y2 = segment->y1;
+ g_ptr_array_remove_range (segment->points,
+ 0, segment->points->len);
+ gimp_iscissors_tool_free_redo (iscissors);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+ else
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ }
+ return TRUE;
+ }
+ return FALSE;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ if (iscissors->curve->closed && iscissors->mask)
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ return TRUE;
+ }
+ return FALSE;
+
+ case GDK_KEY_Escape:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static const gchar *
+gimp_iscissors_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ if (! iscissors->undo_stack)
+ return NULL;
+
+ return _("Modify Scissors Curve");
+}
+
+static const gchar *
+gimp_iscissors_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ if (! iscissors->redo_stack)
+ return NULL;
+
+ return _("Modify Scissors Curve");
+}
+
+static gboolean
+gimp_iscissors_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ iscissors->redo_stack = g_list_prepend (iscissors->redo_stack,
+ iscissors->curve);
+
+ iscissors->curve = iscissors->undo_stack->data;
+
+ iscissors->undo_stack = g_list_remove (iscissors->undo_stack,
+ iscissors->curve);
+
+ if (! iscissors->undo_stack)
+ {
+ iscissors->state = NO_ACTION;
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ return TRUE;
+}
+
+static gboolean
+gimp_iscissors_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (! iscissors->undo_stack)
+ {
+ iscissors->state = WAITING;
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+ }
+
+ iscissors->undo_stack = g_list_prepend (iscissors->undo_stack,
+ iscissors->curve);
+
+ iscissors->curve = iscissors->redo_stack->data;
+
+ iscissors->redo_stack = g_list_remove (iscissors->redo_stack,
+ iscissors->curve);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ return TRUE;
+}
+
+static void
+gimp_iscissors_tool_push_undo (GimpIscissorsTool *iscissors)
+{
+ iscissors->undo_stack = g_list_prepend (iscissors->undo_stack,
+ icurve_copy (iscissors->curve));
+}
+
+static void
+gimp_iscissors_tool_pop_undo (GimpIscissorsTool *iscissors)
+{
+ icurve_free (iscissors->curve);
+ iscissors->curve = iscissors->undo_stack->data;
+
+ iscissors->undo_stack = g_list_remove (iscissors->undo_stack,
+ iscissors->curve);
+
+ if (! iscissors->undo_stack)
+ {
+ iscissors->state = NO_ACTION;
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (iscissors));
+ }
+}
+
+static void
+gimp_iscissors_tool_free_redo (GimpIscissorsTool *iscissors)
+{
+ g_list_free_full (iscissors->redo_stack,
+ (GDestroyNotify) icurve_free);
+ iscissors->redo_stack = NULL;
+
+ /* update the undo actions / menu items */
+ gimp_image_flush (gimp_display_get_image (GIMP_TOOL (iscissors)->display));
+}
+
+static void
+gimp_iscissors_tool_halt (GimpIscissorsTool *iscissors,
+ GimpDisplay *display)
+{
+ icurve_clear (iscissors->curve);
+
+ iscissors->segment1 = NULL;
+ iscissors->segment2 = NULL;
+ iscissors->state = NO_ACTION;
+
+ if (iscissors->undo_stack)
+ {
+ g_list_free_full (iscissors->undo_stack, (GDestroyNotify) icurve_free);
+ iscissors->undo_stack = NULL;
+ }
+
+ if (iscissors->redo_stack)
+ {
+ g_list_free_full (iscissors->redo_stack, (GDestroyNotify) icurve_free);
+ iscissors->redo_stack = NULL;
+ }
+
+ g_clear_object (&iscissors->gradient_map);
+ g_clear_object (&iscissors->mask);
+}
+
+static void
+gimp_iscissors_tool_commit (GimpIscissorsTool *iscissors,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (iscissors);
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (! iscissors->curve->closed)
+ {
+ ISegment *first = g_queue_peek_head (iscissors->curve->segments);
+ ISegment *last = g_queue_peek_tail (iscissors->curve->segments);
+
+ if (first && last && first != last)
+ {
+ ISegment *segment;
+
+ segment = icurve_append_segment (iscissors->curve,
+ last->x2,
+ last->y2,
+ first->x1,
+ first->y1);
+ icurve_close (iscissors->curve);
+ calculate_segment (iscissors, segment);
+
+ iscissors_convert (iscissors, display);
+ }
+ }
+
+ if (iscissors->curve->closed && iscissors->mask)
+ {
+ gimp_channel_select_channel (gimp_image_get_mask (image),
+ gimp_tool_get_undo_desc (tool),
+ iscissors->mask,
+ 0, 0,
+ options->operation,
+ options->feather,
+ options->feather_radius,
+ options->feather_radius);
+
+ gimp_image_flush (image);
+ }
+}
+
+
+/* XXX need some scan-conversion routines from somewhere. maybe. ? */
+
+static gint
+mouse_over_vertex (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y)
+{
+ GList *list;
+ gint segments_found = 0;
+
+ /* traverse through the list, returning non-zero if the current cursor
+ * position is on an existing curve vertex. Set the segment1 and segment2
+ * variables to the two segments containing the vertex in question
+ */
+
+ iscissors->segment1 = iscissors->segment2 = NULL;
+
+ for (list = g_queue_peek_head_link (iscissors->curve->segments);
+ list;
+ list = g_list_next (list))
+ {
+ ISegment *segment = list->data;
+
+ if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (iscissors),
+ GIMP_TOOL (iscissors)->display,
+ x, y,
+ GIMP_HANDLE_CIRCLE,
+ segment->x1, segment->y1,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ iscissors->segment1 = segment;
+
+ if (segments_found++)
+ return segments_found;
+ }
+ else if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (iscissors),
+ GIMP_TOOL (iscissors)->display,
+ x, y,
+ GIMP_HANDLE_CIRCLE,
+ segment->x2, segment->y2,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER))
+ {
+ iscissors->segment2 = segment;
+
+ if (segments_found++)
+ return segments_found;
+ }
+ }
+
+ return segments_found;
+}
+
+static gboolean
+clicked_on_vertex (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y)
+{
+ gint segments_found = mouse_over_vertex (iscissors, x, y);
+
+ if (segments_found > 1)
+ {
+ gimp_iscissors_tool_push_undo (iscissors);
+
+ return TRUE;
+ }
+
+ /* if only one segment was found, the segments are unconnected, and
+ * the user only wants to move either the first or last point
+ * disallow this for now.
+ */
+ if (segments_found == 1)
+ return FALSE;
+
+ return clicked_on_segment (iscissors, x, y);
+}
+
+
+static GList *
+mouse_over_segment (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y)
+{
+ GList *list;
+
+ /* traverse through the list, returning the curve segment's list element
+ * if the current cursor position is on a curve...
+ */
+ for (list = g_queue_peek_head_link (iscissors->curve->segments);
+ list;
+ list = g_list_next (list))
+ {
+ ISegment *segment = list->data;
+ gpointer *pt;
+ gint len;
+
+ if (! segment->points)
+ continue;
+
+ pt = segment->points->pdata;
+ len = segment->points->len;
+
+ while (len--)
+ {
+ guint32 coords = GPOINTER_TO_INT (*pt);
+ gint tx, ty;
+
+ pt++;
+ tx = coords & 0x0000ffff;
+ ty = coords >> 16;
+
+ /* Is the specified point close enough to the segment? */
+ if (gimp_draw_tool_calc_distance_square (GIMP_DRAW_TOOL (iscissors),
+ GIMP_TOOL (iscissors)->display,
+ tx, ty,
+ x, y) < SQR (GIMP_TOOL_HANDLE_SIZE_CIRCLE / 2))
+ {
+ return list;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+clicked_on_segment (GimpIscissorsTool *iscissors,
+ gdouble x,
+ gdouble y)
+{
+ GList *list = mouse_over_segment (iscissors, x, y);
+
+ /* traverse through the list, getting back the curve segment's list
+ * element if the current cursor position is on a segment...
+ * If this occurs, replace the segment with two new segments,
+ * separated by a new vertex.
+ */
+
+ if (list)
+ {
+ ISegment *segment = list->data;
+ ISegment *new_segment;
+
+ gimp_iscissors_tool_push_undo (iscissors);
+
+ new_segment = icurve_insert_segment (iscissors->curve,
+ list,
+ iscissors->x,
+ iscissors->y,
+ segment->x2,
+ segment->y2);
+
+ iscissors->segment1 = new_segment;
+ iscissors->segment2 = segment;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+calculate_segment (GimpIscissorsTool *iscissors,
+ ISegment *segment)
+{
+ GimpDisplay *display = GIMP_TOOL (iscissors)->display;
+ GimpPickable *pickable = GIMP_PICKABLE (gimp_display_get_image (display));
+ gint width;
+ gint height;
+ gint xs, ys, xe, ye;
+ gint x1, y1, x2, y2;
+ gint ewidth, eheight;
+
+ /* Initialise the gradient map buffer for this pickable if we don't
+ * already have one.
+ */
+ if (! iscissors->gradient_map)
+ iscissors->gradient_map = gradient_map_new (pickable);
+
+ width = gegl_buffer_get_width (iscissors->gradient_map);
+ height = gegl_buffer_get_height (iscissors->gradient_map);
+
+ /* Calculate the lowest cost path from one vertex to the next as specified
+ * by the parameter "segment".
+ * Here are the steps:
+ * 1) Calculate the appropriate working area for this operation
+ * 2) Allocate a temp buf for the dynamic programming array
+ * 3) Run the dynamic programming algorithm to find the optimal path
+ * 4) Translate the optimal path into pixels in the isegment data
+ * structure.
+ */
+
+ /* Get the bounding box */
+ xs = CLAMP (segment->x1, 0, width - 1);
+ ys = CLAMP (segment->y1, 0, height - 1);
+ xe = CLAMP (segment->x2, 0, width - 1);
+ ye = CLAMP (segment->y2, 0, height - 1);
+ x1 = MIN (xs, xe);
+ y1 = MIN (ys, ye);
+ x2 = MAX (xs, xe) + 1; /* +1 because if xe = 199 & xs = 0, x2 - x1, width = 200 */
+ y2 = MAX (ys, ye) + 1;
+
+ /* expand the boundaries past the ending points by
+ * some percentage of width and height. This serves the following purpose:
+ * It gives the algorithm more area to search so better solutions
+ * are found. This is particularly helpful in finding "bumps" which
+ * fall outside the bounding box represented by the start and end
+ * coordinates of the "segment".
+ */
+ ewidth = (x2 - x1) * EXTEND_BY + FIXED;
+ eheight = (y2 - y1) * EXTEND_BY + FIXED;
+
+ if (xe >= xs)
+ x2 += CLAMP (ewidth, 0, width - x2);
+ else
+ x1 -= CLAMP (ewidth, 0, x1);
+
+ if (ye >= ys)
+ y2 += CLAMP (eheight, 0, height - y2);
+ else
+ y1 -= CLAMP (eheight, 0, y1);
+
+ /* blow away any previous points list we might have */
+ if (segment->points)
+ {
+ g_ptr_array_free (segment->points, TRUE);
+ segment->points = NULL;
+ }
+
+ if ((x2 - x1) && (y2 - y1))
+ {
+ /* If the bounding box has width and height... */
+
+ GimpTempBuf *dp_buf; /* dynamic programming buffer */
+ gint dp_width = (x2 - x1);
+ gint dp_height = (y2 - y1);
+
+ dp_buf = gimp_temp_buf_new (dp_width, dp_height,
+ babl_format ("Y u32"));
+
+ /* find the optimal path of pixels from (x1, y1) to (x2, y2) */
+ find_optimal_path (iscissors->gradient_map, dp_buf,
+ x1, y1, x2, y2, xs, ys);
+
+ /* get a list of the pixels in the optimal path */
+ segment->points = plot_pixels (dp_buf, x1, y1, xs, ys, xe, ye);
+
+ gimp_temp_buf_unref (dp_buf);
+ }
+ else if ((x2 - x1) == 0)
+ {
+ /* If the bounding box has no width */
+
+ /* plot a vertical line */
+ gint y = ys;
+ gint dir = (ys > ye) ? -1 : 1;
+
+ segment->points = g_ptr_array_new ();
+ while (y != ye)
+ {
+ g_ptr_array_add (segment->points, GINT_TO_POINTER ((y << 16) + xs));
+ y += dir;
+ }
+ }
+ else if ((y2 - y1) == 0)
+ {
+ /* If the bounding box has no height */
+
+ /* plot a horizontal line */
+ gint x = xs;
+ gint dir = (xs > xe) ? -1 : 1;
+
+ segment->points = g_ptr_array_new ();
+ while (x != xe)
+ {
+ g_ptr_array_add (segment->points, GINT_TO_POINTER ((ys << 16) + x));
+ x += dir;
+ }
+ }
+}
+
+
+/* badly need to get a replacement - this is _way_ too expensive */
+static gboolean
+gradient_map_value (GeglSampler *map_sampler,
+ const GeglRectangle *map_extent,
+ gint x,
+ gint y,
+ guint8 *grad,
+ guint8 *dir)
+{
+ if (x >= map_extent->x &&
+ y >= map_extent->y &&
+ x < map_extent->width &&
+ y < map_extent->height)
+ {
+ guint8 sample[2];
+
+ gegl_sampler_get (map_sampler, x, y, NULL, sample, GEGL_ABYSS_NONE);
+
+ *grad = sample[0];
+ *dir = sample[1];
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gint
+calculate_link (GeglSampler *map_sampler,
+ const GeglRectangle *map_extent,
+ gint x,
+ gint y,
+ guint32 pixel,
+ gint link)
+{
+ gint value = 0;
+ guint8 grad1, dir1, grad2, dir2;
+
+ if (! gradient_map_value (map_sampler, map_extent, x, y, &grad1, &dir1))
+ {
+ grad1 = 0;
+ dir1 = 255;
+ }
+
+ /* Convert the gradient into a cost: large gradients are good, and
+ * so have low cost. */
+ grad1 = 255 - grad1;
+
+ /* calculate the contribution of the gradient magnitude */
+ if (link > 1)
+ value += diagonal_weight[grad1] * OMEGA_G;
+ else
+ value += grad1 * OMEGA_G;
+
+ /* calculate the contribution of the gradient direction */
+ x += (gint8)(pixel & 0xff);
+ y += (gint8)((pixel & 0xff00) >> 8);
+
+ if (! gradient_map_value (map_sampler, map_extent, x, y, &grad2, &dir2))
+ {
+ grad2 = 0;
+ dir2 = 255;
+ }
+
+ value +=
+ (direction_value[dir1][link] + direction_value[dir2][link]) * OMEGA_D;
+
+ return value;
+}
+
+
+static GPtrArray *
+plot_pixels (GimpTempBuf *dp_buf,
+ gint x1,
+ gint y1,
+ gint xs,
+ gint ys,
+ gint xe,
+ gint ye)
+{
+ gint x, y;
+ guint32 coords;
+ gint link;
+ gint width = gimp_temp_buf_get_width (dp_buf);
+ guint *data;
+ GPtrArray *list;
+
+ /* Start the data pointer at the correct location */
+ data = (guint *) gimp_temp_buf_get_data (dp_buf) + (ye - y1) * width + (xe - x1);
+
+ x = xe;
+ y = ye;
+
+ list = g_ptr_array_new ();
+
+ while (TRUE)
+ {
+ coords = (y << 16) + x;
+ g_ptr_array_add (list, GINT_TO_POINTER (coords));
+
+ link = PIXEL_DIR (*data);
+ if (link == SEED_POINT)
+ return list;
+
+ x += move[link][0];
+ y += move[link][1];
+ data += move[link][1] * width + move[link][0];
+ }
+
+ /* won't get here */
+ return NULL;
+}
+
+
+#define PACK(x, y) ((((y) & 0xff) << 8) | ((x) & 0xff))
+#define OFFSET(pixel) ((gint8)((pixel) & 0xff) + \
+ ((gint8)(((pixel) & 0xff00) >> 8)) * \
+ gimp_temp_buf_get_width (dp_buf))
+
+static void
+find_optimal_path (GeglBuffer *gradient_map,
+ GimpTempBuf *dp_buf,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint xs,
+ gint ys)
+{
+ GeglSampler *map_sampler;
+ const GeglRectangle *map_extent;
+ gint i, j, k;
+ gint x, y;
+ gint link;
+ gint linkdir;
+ gint dirx, diry;
+ gint min_cost;
+ gint new_cost;
+ gint offset;
+ gint cum_cost[8];
+ gint link_cost[8];
+ gint pixel_cost[8];
+ guint32 pixel[8];
+ guint32 *data;
+ guint32 *d;
+ gint dp_buf_width = gimp_temp_buf_get_width (dp_buf);
+ gint dp_buf_height = gimp_temp_buf_get_height (dp_buf);
+
+ /* initialize the gradient map sampler and extent */
+ map_sampler = gegl_buffer_sampler_new (gradient_map,
+ gegl_buffer_get_format (gradient_map),
+ GEGL_SAMPLER_NEAREST);
+ map_extent = gegl_buffer_get_extent (gradient_map);
+
+ /* initialize the dynamic programming buffer */
+ data = (guint32 *) gimp_temp_buf_data_clear (dp_buf);
+
+ /* what directions are we filling the array in according to? */
+ dirx = (xs - x1 == 0) ? 1 : -1;
+ diry = (ys - y1 == 0) ? 1 : -1;
+ linkdir = (dirx * diry);
+
+ y = ys;
+
+ for (i = 0; i < dp_buf_height; i++)
+ {
+ x = xs;
+
+ d = data + (y-y1) * dp_buf_width + (x-x1);
+
+ for (j = 0; j < dp_buf_width; j++)
+ {
+ min_cost = G_MAXINT;
+
+ /* pixel[] array encodes how to get to a neighbour, if possible.
+ * 0 means no connection (eg edge).
+ * Rest packed as bottom two bytes: y offset then x offset.
+ * Initially, we assume we can't get anywhere.
+ */
+ for (k = 0; k < 8; k++)
+ pixel[k] = 0;
+
+ /* Find the valid neighboring pixels */
+ /* the previous pixel */
+ if (j)
+ pixel[((dirx == 1) ? 4 : 0)] = PACK (-dirx, 0);
+
+ /* the previous row of pixels */
+ if (i)
+ {
+ pixel[((diry == 1) ? 5 : 1)] = PACK (0, -diry);
+
+ link = (linkdir == 1) ? 3 : 2;
+ if (j)
+ pixel[((diry == 1) ? (link + 4) : link)] = PACK (-dirx, -diry);
+
+ link = (linkdir == 1) ? 2 : 3;
+ if (j != dp_buf_width - 1)
+ pixel[((diry == 1) ? (link + 4) : link)] = PACK (dirx, -diry);
+ }
+
+ /* find the minimum cost of going through each neighbor to reach the
+ * seed point...
+ */
+ link = -1;
+ for (k = 0; k < 8; k ++)
+ if (pixel[k])
+ {
+ link_cost[k] = calculate_link (map_sampler, map_extent,
+ xs + j*dirx, ys + i*diry,
+ pixel [k],
+ ((k > 3) ? k - 4 : k));
+ offset = OFFSET (pixel [k]);
+ pixel_cost[k] = PIXEL_COST (d[offset]);
+ cum_cost[k] = pixel_cost[k] + link_cost[k];
+ if (cum_cost[k] < min_cost)
+ {
+ min_cost = cum_cost[k];
+ link = k;
+ }
+ }
+
+ /* If anything can be done... */
+ if (link >= 0)
+ {
+ /* set the cumulative cost of this pixel and the new direction
+ */
+ *d = (cum_cost[link] << 8) + link;
+
+ /* possibly change the links from the other pixels to this pixel...
+ * these changes occur if a neighboring pixel will receive a lower
+ * cumulative cost by going through this pixel.
+ */
+ for (k = 0; k < 8; k ++)
+ if (pixel[k] && k != link)
+ {
+ /* if the cumulative cost at the neighbor is greater than
+ * the cost through the link to the current pixel, change the
+ * neighbor's link to point to the current pixel.
+ */
+ new_cost = link_cost[k] + cum_cost[link];
+ if (pixel_cost[k] > new_cost)
+ {
+ /* reverse the link direction /--------------------\ */
+ offset = OFFSET (pixel[k]);
+ d[offset] = (new_cost << 8) + ((k > 3) ? k - 4 : k + 4);
+ }
+ }
+ }
+ /* Set the seed point */
+ else if (!i && !j)
+ {
+ *d = SEED_POINT;
+ }
+
+ /* increment the data pointer and the x counter */
+ d += dirx;
+ x += dirx;
+ }
+
+ /* increment the y counter */
+ y += diry;
+ }
+
+ g_object_unref (map_sampler);
+}
+
+static GeglBuffer *
+gradient_map_new (GimpPickable *pickable)
+{
+ GeglBuffer *buffer;
+ GeglTileHandler *handler;
+
+ buffer = gimp_pickable_get_buffer (pickable);
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gegl_buffer_get_width (buffer),
+ gegl_buffer_get_height (buffer)),
+ babl_format_n (babl_type ("u8"), 2));
+
+ handler = gimp_tile_handler_iscissors_new (pickable);
+
+ gimp_tile_handler_validate_assign (GIMP_TILE_HANDLER_VALIDATE (handler),
+ buffer);
+
+ gimp_tile_handler_validate_invalidate (GIMP_TILE_HANDLER_VALIDATE (handler),
+ GEGL_RECTANGLE (0, 0,
+ gegl_buffer_get_width (buffer),
+ gegl_buffer_get_height (buffer)));
+
+ g_object_unref (handler);
+
+ return buffer;
+}
+
+static void
+find_max_gradient (GimpIscissorsTool *iscissors,
+ GimpPickable *pickable,
+ gint *x,
+ gint *y)
+{
+ GeglBufferIterator *iter;
+ GeglRectangle *roi;
+ gint width;
+ gint height;
+ gint radius;
+ gint cx, cy;
+ gint x1, y1, x2, y2;
+ gfloat max_gradient;
+
+ /* Initialise the gradient map buffer for this pickable if we don't
+ * already have one.
+ */
+ if (! iscissors->gradient_map)
+ iscissors->gradient_map = gradient_map_new (pickable);
+
+ width = gegl_buffer_get_width (iscissors->gradient_map);
+ height = gegl_buffer_get_height (iscissors->gradient_map);
+
+ radius = GRADIENT_SEARCH >> 1;
+
+ /* calculate the extent of the search */
+ cx = CLAMP (*x, 0, width);
+ cy = CLAMP (*y, 0, height);
+ x1 = CLAMP (cx - radius, 0, width);
+ y1 = CLAMP (cy - radius, 0, height);
+ x2 = CLAMP (cx + radius, 0, width);
+ y2 = CLAMP (cy + radius, 0, height);
+ /* calculate the factor to multiply the distance from the cursor by */
+
+ max_gradient = 0;
+ *x = cx;
+ *y = cy;
+
+ iter = gegl_buffer_iterator_new (iscissors->gradient_map,
+ GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
+ 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
+ roi = &iter->items[0].roi;
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ guint8 *data = iter->items[0].data;
+ gint endx = roi->x + roi->width;
+ gint endy = roi->y + roi->height;
+ gint i, j;
+
+ for (i = roi->y; i < endy; i++)
+ {
+ const guint8 *gradient = data + 2 * roi->width * (i - roi->y);
+
+ for (j = roi->x; j < endx; j++)
+ {
+ gfloat g = *gradient;
+
+ gradient += COST_WIDTH;
+
+ g *= distance_weights [(i - y1) * GRADIENT_SEARCH + (j - x1)];
+
+ if (g > max_gradient)
+ {
+ max_gradient = g;
+
+ *x = j;
+ *y = i;
+ }
+ }
+ }
+ }
+}
+
+static ISegment *
+isegment_new (gint x1,
+ gint y1,
+ gint x2,
+ gint y2)
+{
+ ISegment *segment = g_slice_new0 (ISegment);
+
+ segment->x1 = x1;
+ segment->y1 = y1;
+ segment->x2 = x2;
+ segment->y2 = y2;
+
+ return segment;
+}
+
+static ISegment *
+isegment_copy (ISegment *segment)
+{
+ ISegment *copy = isegment_new (segment->x1,
+ segment->y1,
+ segment->x2,
+ segment->y2);
+
+ if (segment->points)
+ {
+ gint i;
+
+ copy->points = g_ptr_array_sized_new (segment->points->len);
+
+ for (i = 0; i < segment->points->len; i++)
+ {
+ gpointer value = g_ptr_array_index (segment->points, i);
+
+ g_ptr_array_add (copy->points, value);
+ }
+ }
+
+ return copy;
+}
+
+static void
+isegment_free (ISegment *segment)
+{
+ if (segment->points)
+ g_ptr_array_free (segment->points, TRUE);
+
+ g_slice_free (ISegment, segment);
+}
+
+static ICurve *
+icurve_new (void)
+{
+ ICurve *curve = g_slice_new0 (ICurve);
+
+ curve->segments = g_queue_new ();
+ curve->first_point = TRUE;
+
+ return curve;
+}
+
+static ICurve *
+icurve_copy (ICurve *curve)
+{
+ ICurve *copy = icurve_new ();
+ GList *link;
+
+ for (link = g_queue_peek_head_link (curve->segments);
+ link;
+ link = g_list_next (link))
+ {
+ g_queue_push_tail (copy->segments, isegment_copy (link->data));
+ }
+
+ copy->first_point = curve->first_point;
+ copy->closed = curve->closed;
+
+ return copy;
+}
+
+static void
+icurve_clear (ICurve *curve)
+{
+ while (! g_queue_is_empty (curve->segments))
+ isegment_free (g_queue_pop_head (curve->segments));
+
+ curve->first_point = TRUE;
+ curve->closed = FALSE;
+}
+
+static void
+icurve_free (ICurve *curve)
+{
+ g_queue_free_full (curve->segments, (GDestroyNotify) isegment_free);
+
+ g_slice_free (ICurve, curve);
+}
+
+static ISegment *
+icurve_append_segment (ICurve *curve,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2)
+{
+ ISegment *segment = isegment_new (x1, y1, x2, y2);
+
+ g_queue_push_tail (curve->segments, segment);
+
+ return segment;
+}
+
+static ISegment *
+icurve_insert_segment (ICurve *curve,
+ GList *sibling,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2)
+{
+ ISegment *segment = sibling->data;
+ ISegment *new_segment;
+
+ new_segment = isegment_new (x1, y1, x2, y2);
+
+ segment->x2 = x1;
+ segment->y2 = y1;
+
+ g_queue_insert_after (curve->segments, sibling, new_segment);
+
+ return new_segment;
+}
+
+static void
+icurve_delete_segment (ICurve *curve,
+ ISegment *segment)
+{
+ GList *link = g_queue_find (curve->segments, segment);
+ ISegment *next_segment = NULL;
+
+ if (link->next)
+ next_segment = link->next->data;
+ else if (curve->closed)
+ next_segment = g_queue_peek_head (curve->segments);
+
+ if (next_segment)
+ {
+ next_segment->x1 = segment->x1;
+ next_segment->y1 = segment->y1;
+ }
+
+ g_queue_remove (curve->segments, segment);
+ isegment_free (segment);
+}
+
+static void
+icurve_close (ICurve *curve)
+{
+ ISegment *first = g_queue_peek_head (curve->segments);
+ ISegment *last = g_queue_peek_tail (curve->segments);
+
+ last->x2 = first->x1;
+ last->y2 = first->y1;
+
+ curve->closed = TRUE;
+}
+
+static GimpScanConvert *
+icurve_create_scan_convert (ICurve *curve)
+{
+ GimpScanConvert *sc;
+ GList *list;
+ GimpVector2 *points;
+ guint n_total_points = 0;
+
+ sc = gimp_scan_convert_new ();
+
+ for (list = g_queue_peek_tail_link (curve->segments);
+ list;
+ list = g_list_previous (list))
+ {
+ ISegment *segment = list->data;
+
+ n_total_points += segment->points->len;
+ }
+
+ points = g_new (GimpVector2, n_total_points);
+ n_total_points = 0;
+
+ /* go over the segments in reverse order, adding the points we have */
+ for (list = g_queue_peek_tail_link (curve->segments);
+ list;
+ list = g_list_previous (list))
+ {
+ ISegment *segment = list->data;
+ guint n_points;
+ gint i;
+
+ n_points = segment->points->len;
+
+ for (i = 0; i < n_points; i++)
+ {
+ guint32 packed = GPOINTER_TO_INT (g_ptr_array_index (segment->points,
+ i));
+
+ points[n_total_points + i].x = packed & 0x0000ffff;
+ points[n_total_points + i].y = packed >> 16;
+ }
+
+ n_total_points += n_points;
+ }
+
+ gimp_scan_convert_add_polyline (sc, n_total_points, points, TRUE);
+ g_free (points);
+
+ return sc;
+}
diff --git a/app/tools/gimpiscissorstool.h b/app/tools/gimpiscissorstool.h
new file mode 100644
index 0000000..3cd4c79
--- /dev/null
+++ b/app/tools/gimpiscissorstool.h
@@ -0,0 +1,97 @@
+/* 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_ISCISSORS_TOOL_H__
+#define __GIMP_ISCISSORS_TOOL_H__
+
+
+#include "gimpselectiontool.h"
+
+
+/* The possible states... */
+typedef enum
+{
+ NO_ACTION,
+ SEED_PLACEMENT,
+ SEED_ADJUSTMENT,
+ WAITING
+} IscissorsState;
+
+/* For oper_update & cursor_update */
+typedef enum
+{
+ ISCISSORS_OP_NONE,
+ ISCISSORS_OP_SELECT,
+ ISCISSORS_OP_MOVE_POINT,
+ ISCISSORS_OP_ADD_POINT,
+ ISCISSORS_OP_REMOVE_POINT,
+ ISCISSORS_OP_CONNECT,
+ ISCISSORS_OP_IMPOSSIBLE
+} IscissorsOps;
+
+typedef struct _ISegment ISegment;
+typedef struct _ICurve ICurve;
+
+
+#define GIMP_TYPE_ISCISSORS_TOOL (gimp_iscissors_tool_get_type ())
+#define GIMP_ISCISSORS_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ISCISSORS_TOOL, GimpIscissorsTool))
+#define GIMP_ISCISSORS_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ISCISSORS_TOOL, GimpIscissorsToolClass))
+#define GIMP_IS_ISCISSORS_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ISCISSORS_TOOL))
+#define GIMP_IS_ISCISSORS_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ISCISSORS_TOOL))
+#define GIMP_ISCISSORS_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ISCISSORS_TOOL, GimpIscissorsToolClass))
+
+#define GIMP_ISCISSORS_TOOL_GET_OPTIONS(t) (GIMP_ISCISSORS_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpIscissorsTool GimpIscissorsTool;
+typedef struct _GimpIscissorsToolClass GimpIscissorsToolClass;
+
+struct _GimpIscissorsTool
+{
+ GimpSelectionTool parent_instance;
+
+ IscissorsOps op;
+
+ gint x, y; /* mouse coordinates */
+
+ ISegment *segment1; /* 1st segment connected to current point */
+ ISegment *segment2; /* 2nd segment connected to current point */
+
+ ICurve *curve; /* the curve */
+
+ GList *undo_stack; /* stack of ICurves for undo */
+ GList *redo_stack; /* stack of ICurves for redo */
+
+ IscissorsState state; /* state of iscissors */
+
+ GeglBuffer *gradient_map; /* lazily filled gradient map */
+ GimpChannel *mask; /* selection mask */
+};
+
+struct _GimpIscissorsToolClass
+{
+ GimpSelectionToolClass parent_class;
+};
+
+
+void gimp_iscissors_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_iscissors_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ISCISSORS_TOOL_H__ */
diff --git a/app/tools/gimplevelstool.c b/app/tools/gimplevelstool.c
new file mode 100644
index 0000000..4e97f71
--- /dev/null
+++ b/app/tools/gimplevelstool.c
@@ -0,0 +1,1055 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "operations/gimplevelsconfig.h"
+#include "operations/gimpoperationlevels.h"
+
+#include "core/gimp-gui.h"
+#include "core/gimpasync.h"
+#include "core/gimpcancelable.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdrawable-histogram.h"
+#include "core/gimperror.h"
+#include "core/gimphistogram.h"
+#include "core/gimpimage.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimptriviallycancelablewaitable.h"
+#include "core/gimpwaitable.h"
+
+#include "widgets/gimpcolorbar.h"
+#include "widgets/gimphandlebar.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimphistogramview.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-constructors.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimphistogramoptions.h"
+#include "gimplevelstool.h"
+
+#include "gimp-intl.h"
+
+
+#define PICK_LOW_INPUT (1 << 0)
+#define PICK_GAMMA (1 << 1)
+#define PICK_HIGH_INPUT (1 << 2)
+#define PICK_ALL_CHANNELS (1 << 8)
+
+#define HISTOGRAM_WIDTH 256
+#define GRADIENT_HEIGHT 12
+#define CONTROL_HEIGHT 10
+
+
+/* local function prototypes */
+
+static void gimp_levels_tool_finalize (GObject *object);
+
+static gboolean gimp_levels_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+
+static gchar * gimp_levels_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description);
+static void gimp_levels_tool_dialog (GimpFilterTool *filter_tool);
+static void gimp_levels_tool_reset (GimpFilterTool *filter_tool);
+static void gimp_levels_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec);
+static gboolean gimp_levels_tool_settings_import(GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error);
+static gboolean gimp_levels_tool_settings_export(GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error);
+static void gimp_levels_tool_color_picked (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color);
+
+static void gimp_levels_tool_export_setup (GimpSettingsBox *settings_box,
+ GtkFileChooserDialog *dialog,
+ gboolean export,
+ GimpLevelsTool *tool);
+
+static void levels_update_input_bar (GimpLevelsTool *tool);
+
+static void levels_channel_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool);
+static void levels_channel_reset_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool);
+
+static gboolean levels_menu_sensitivity (gint value,
+ gpointer data);
+
+static void levels_stretch_callback (GtkWidget *widget,
+ GimpLevelsTool *tool);
+static void levels_linear_gamma_changed (GtkAdjustment *adjustment,
+ GimpLevelsTool *tool);
+
+static void levels_to_curves_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool);
+
+
+G_DEFINE_TYPE (GimpLevelsTool, gimp_levels_tool, GIMP_TYPE_FILTER_TOOL)
+
+#define parent_class gimp_levels_tool_parent_class
+
+
+void
+gimp_levels_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_LEVELS_TOOL,
+ GIMP_TYPE_HISTOGRAM_OPTIONS,
+ gimp_color_options_gui,
+ 0,
+ "gimp-levels-tool",
+ _("Levels"),
+ _("Adjust color levels"),
+ N_("_Levels..."), NULL,
+ NULL, GIMP_HELP_TOOL_LEVELS,
+ GIMP_ICON_TOOL_LEVELS,
+ data);
+}
+
+static void
+gimp_levels_tool_class_init (GimpLevelsToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_levels_tool_finalize;
+
+ tool_class->initialize = gimp_levels_tool_initialize;
+
+ filter_tool_class->get_operation = gimp_levels_tool_get_operation;
+ filter_tool_class->dialog = gimp_levels_tool_dialog;
+ filter_tool_class->reset = gimp_levels_tool_reset;
+ filter_tool_class->config_notify = gimp_levels_tool_config_notify;
+ filter_tool_class->settings_import = gimp_levels_tool_settings_import;
+ filter_tool_class->settings_export = gimp_levels_tool_settings_export;
+ filter_tool_class->color_picked = gimp_levels_tool_color_picked;
+}
+
+static void
+gimp_levels_tool_init (GimpLevelsTool *tool)
+{
+}
+
+static void
+gimp_levels_tool_finalize (GObject *object)
+{
+ GimpLevelsTool *tool = GIMP_LEVELS_TOOL (object);
+
+ g_clear_object (&tool->histogram);
+ g_clear_object (&tool->histogram_async);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_levels_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpLevelsTool *l_tool = GIMP_LEVELS_TOOL (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpLevelsConfig *config;
+ gdouble scale_factor;
+ gdouble step_increment;
+ gdouble page_increment;
+ gint digits;
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ return FALSE;
+ }
+
+ config = GIMP_LEVELS_CONFIG (filter_tool->config);
+
+ g_clear_object (&l_tool->histogram);
+ g_clear_object (&l_tool->histogram_async);
+ l_tool->histogram = gimp_histogram_new (config->linear);
+
+ l_tool->histogram_async = gimp_drawable_calculate_histogram_async (
+ drawable, l_tool->histogram, FALSE);
+ gimp_histogram_view_set_histogram (GIMP_HISTOGRAM_VIEW (l_tool->histogram_view),
+ l_tool->histogram);
+
+ if (gimp_drawable_get_component_type (drawable) == GIMP_COMPONENT_TYPE_U8)
+ {
+ scale_factor = 255.0;
+ step_increment = 1.0;
+ page_increment = 8.0;
+ digits = 0;
+ }
+ else
+ {
+ scale_factor = 100;
+ step_increment = 0.01;
+ page_increment = 1.0;
+ digits = 2;
+ }
+
+ gimp_prop_widget_set_factor (l_tool->low_input_spinbutton,
+ scale_factor, step_increment, page_increment,
+ digits);
+ gimp_prop_widget_set_factor (l_tool->high_input_spinbutton,
+ scale_factor, step_increment, page_increment,
+ digits);
+ gimp_prop_widget_set_factor (l_tool->low_output_spinbutton,
+ scale_factor, step_increment, page_increment,
+ digits);
+ gimp_prop_widget_set_factor (l_tool->high_output_spinbutton,
+ scale_factor, step_increment, page_increment,
+ digits);
+
+ gtk_adjustment_configure (l_tool->gamma_linear,
+ scale_factor / 2.0,
+ 0, scale_factor, 0.1, 1.0, 0);
+
+ return TRUE;
+}
+
+static gchar *
+gimp_levels_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description)
+{
+ *description = g_strdup (_("Adjust Color Levels"));
+
+ return g_strdup ("gimp:levels");
+}
+
+
+/*******************/
+/* Levels dialog */
+/*******************/
+
+static GtkWidget *
+gimp_levels_tool_color_picker_new (GimpLevelsTool *tool,
+ guint value)
+{
+ const gchar *icon_name;
+ const gchar *help;
+ gboolean all_channels = (value & PICK_ALL_CHANNELS) != 0;
+
+ switch (value & 0xF)
+ {
+ case PICK_LOW_INPUT:
+ icon_name = GIMP_ICON_COLOR_PICKER_BLACK;
+
+ if (all_channels)
+ help = _("Pick black point for all channels");
+ else
+ help = _("Pick black point for the selected channel");
+ break;
+
+ case PICK_GAMMA:
+ icon_name = GIMP_ICON_COLOR_PICKER_GRAY;
+
+ if (all_channels)
+ help = _("Pick gray point for all channels");
+ else
+ help = _("Pick gray point for the selected channel");
+ break;
+
+ case PICK_HIGH_INPUT:
+ icon_name = GIMP_ICON_COLOR_PICKER_WHITE;
+
+ if (all_channels)
+ help = _("Pick white point for all channels");
+ else
+ help = _("Pick white point for the selected channel");
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return gimp_filter_tool_add_color_picker (GIMP_FILTER_TOOL (tool),
+ GUINT_TO_POINTER (value),
+ icon_name,
+ help,
+ /* pick_abyss = */ FALSE,
+ NULL, NULL);
+}
+
+static void
+gimp_levels_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpLevelsTool *tool = GIMP_LEVELS_TOOL (filter_tool);
+ GimpToolOptions *tool_options = GIMP_TOOL_GET_OPTIONS (filter_tool);
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+ GtkListStore *store;
+ GtkWidget *main_vbox;
+ GtkWidget *frame_vbox;
+ GtkWidget *vbox;
+ GtkWidget *vbox2;
+ GtkWidget *vbox3;
+ GtkWidget *hbox;
+ GtkWidget *hbox2;
+ GtkWidget *label;
+ GtkWidget *main_frame;
+ GtkWidget *frame;
+ GtkWidget *button;
+ GtkWidget *spinbutton;
+ GtkAdjustment *adjustment;
+ GtkWidget *bar;
+ GtkWidget *handle_bar;
+ gint border;
+
+ g_signal_connect (filter_tool->settings_box, "file-dialog-setup",
+ G_CALLBACK (gimp_levels_tool_export_setup),
+ filter_tool);
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ /* The combo box for selecting channels */
+ main_frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (main_vbox), main_frame, TRUE, TRUE, 0);
+ gtk_widget_show (main_frame);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_frame_set_label_widget (GTK_FRAME (main_frame), hbox);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("Cha_nnel:"));
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ store = gimp_enum_store_new_with_range (GIMP_TYPE_HISTOGRAM_CHANNEL,
+ GIMP_HISTOGRAM_VALUE,
+ GIMP_HISTOGRAM_ALPHA);
+ tool->channel_menu =
+ gimp_enum_combo_box_new_with_model (GIMP_ENUM_STORE (store));
+ g_object_unref (store);
+
+ g_object_add_weak_pointer (G_OBJECT (tool->channel_menu),
+ (gpointer) &tool->channel_menu);
+
+ gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (tool->channel_menu),
+ "gimp-channel");
+ gimp_int_combo_box_set_sensitivity (GIMP_INT_COMBO_BOX (tool->channel_menu),
+ levels_menu_sensitivity, filter_tool, NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), tool->channel_menu, FALSE, FALSE, 0);
+ gtk_widget_show (tool->channel_menu);
+
+ g_signal_connect (tool->channel_menu, "changed",
+ G_CALLBACK (levels_channel_callback),
+ tool);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), tool->channel_menu);
+
+ button = gtk_button_new_with_mnemonic (_("R_eset Channel"));
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (levels_channel_reset_callback),
+ tool);
+
+ /* The histogram scale radio buttons */
+ hbox2 = gimp_prop_enum_icon_box_new (G_OBJECT (tool_options),
+ "histogram-scale", "gimp-histogram",
+ 0, 0);
+ gtk_box_pack_end (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ /* The linear/perceptual radio buttons */
+ hbox2 = gimp_prop_boolean_icon_box_new (G_OBJECT (config),
+ "linear",
+ GIMP_ICON_COLOR_SPACE_LINEAR,
+ GIMP_ICON_COLOR_SPACE_PERCEPTUAL,
+ _("Adjust levels in linear light"),
+ _("Adjust levels perceptually"));
+ gtk_box_pack_end (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ frame_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+ gtk_container_add (GTK_CONTAINER (main_frame), frame_vbox);
+ gtk_widget_show (frame_vbox);
+
+ /* Input levels frame */
+ frame = gimp_frame_new (_("Input Levels"));
+ gtk_box_pack_start (GTK_BOX (frame_vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ tool->histogram_view = gimp_histogram_view_new (FALSE);
+
+ g_object_add_weak_pointer (G_OBJECT (tool->histogram_view),
+ (gpointer) &tool->histogram_view);
+
+ gtk_box_pack_start (GTK_BOX (vbox2), tool->histogram_view, TRUE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (tool->histogram_view));
+
+ g_object_bind_property (G_OBJECT (tool_options), "histogram-scale",
+ G_OBJECT (tool->histogram_view), "histogram-scale",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ g_object_get (tool->histogram_view, "border-width", &border, NULL);
+
+ vbox3 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox3), border);
+ gtk_box_pack_start (GTK_BOX (vbox2), vbox3, FALSE, FALSE, 0);
+ gtk_widget_show (vbox3);
+
+ tool->input_bar = g_object_new (GIMP_TYPE_COLOR_BAR, NULL);
+ gtk_widget_set_size_request (tool->input_bar, -1, GRADIENT_HEIGHT / 2);
+ gtk_box_pack_start (GTK_BOX (vbox3), tool->input_bar, FALSE, FALSE, 0);
+ gtk_widget_show (tool->input_bar);
+
+ bar = g_object_new (GIMP_TYPE_COLOR_BAR, NULL);
+ gtk_widget_set_size_request (bar, -1, GRADIENT_HEIGHT / 2);
+ gtk_box_pack_start (GTK_BOX (vbox3), bar, FALSE, FALSE, 0);
+ gtk_widget_show (bar);
+
+ handle_bar = g_object_new (GIMP_TYPE_HANDLE_BAR, NULL);
+ gtk_widget_set_size_request (handle_bar, -1, CONTROL_HEIGHT);
+ gtk_box_pack_start (GTK_BOX (vbox3), handle_bar, FALSE, FALSE, 0);
+ gtk_widget_show (handle_bar);
+
+ gimp_handle_bar_connect_events (GIMP_HANDLE_BAR (handle_bar),
+ tool->input_bar);
+ gimp_handle_bar_connect_events (GIMP_HANDLE_BAR (handle_bar),
+ bar);
+
+ /* Horizontal box for input levels spinbuttons */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ /* low input spin */
+ hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ button = gimp_levels_tool_color_picker_new (tool, PICK_LOW_INPUT);
+ gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ tool->low_input_spinbutton = spinbutton =
+ gimp_prop_spin_button_new (filter_tool->config, "low-input",
+ 0.01, 0.1, 1);
+ gtk_box_pack_start (GTK_BOX (hbox2), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+
+ tool->low_input = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
+ gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 0,
+ tool->low_input);
+
+ /* clamp input toggle */
+ hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (hbox), hbox2, TRUE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ button = gimp_prop_check_button_new (filter_tool->config, "clamp-input",
+ _("Clamp _input"));
+ gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* input gamma spin */
+ spinbutton = gimp_prop_spin_button_new (filter_tool->config, "gamma",
+ 0.01, 0.1, 2);
+ gtk_box_pack_start (GTK_BOX (hbox2), spinbutton, FALSE, FALSE, 0);
+ gimp_help_set_help_data (spinbutton, _("Gamma"), NULL);
+ gtk_widget_show (spinbutton);
+
+ tool->gamma = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
+
+ tool->gamma_linear = GTK_ADJUSTMENT (gtk_adjustment_new (127, 0, 255,
+ 0.1, 1.0, 0.0));
+ g_signal_connect (tool->gamma_linear, "value-changed",
+ G_CALLBACK (levels_linear_gamma_changed),
+ tool);
+
+ gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 1,
+ tool->gamma_linear);
+ g_object_unref (tool->gamma_linear);
+
+ /* high input spin */
+ hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_end (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ button = gimp_levels_tool_color_picker_new (tool, PICK_HIGH_INPUT);
+ gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ spinbutton = gimp_prop_spin_button_new (filter_tool->config, "high-input",
+ 0.01, 0.1, 1);
+ gtk_box_pack_start (GTK_BOX (hbox2), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+ tool->high_input_spinbutton = spinbutton;
+
+ tool->high_input = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
+ gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 2,
+ tool->high_input);
+
+ /* Output levels frame */
+ frame = gimp_frame_new (_("Output Levels"));
+ gtk_box_pack_start (GTK_BOX (frame_vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox2), border);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ tool->output_bar = g_object_new (GIMP_TYPE_COLOR_BAR, NULL);
+ gtk_widget_set_size_request (tool->output_bar, -1, GRADIENT_HEIGHT);
+ gtk_box_pack_start (GTK_BOX (vbox2), tool->output_bar, FALSE, FALSE, 0);
+ gtk_widget_show (tool->output_bar);
+
+ handle_bar = g_object_new (GIMP_TYPE_HANDLE_BAR, NULL);
+ gtk_widget_set_size_request (handle_bar, -1, CONTROL_HEIGHT);
+ gtk_box_pack_start (GTK_BOX (vbox2), handle_bar, FALSE, FALSE, 0);
+ gtk_widget_show (handle_bar);
+
+ gimp_handle_bar_connect_events (GIMP_HANDLE_BAR (handle_bar),
+ tool->output_bar);
+
+ /* Horizontal box for levels spin widgets */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ /* low output spin */
+ tool->low_output_spinbutton = spinbutton =
+ gimp_prop_spin_button_new (filter_tool->config, "low-output",
+ 0.01, 0.1, 1);
+ gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+
+ adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
+ gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 0, adjustment);
+
+ /* clamp output toggle */
+ button = gimp_prop_check_button_new (filter_tool->config, "clamp-output",
+ _("Clamp outpu_t"));
+ gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* high output spin */
+ tool->high_output_spinbutton = spinbutton =
+ gimp_prop_spin_button_new (filter_tool->config, "high-output",
+ 0.01, 0.1, 1);
+ gtk_box_pack_end (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+
+ adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
+ gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 2, adjustment);
+
+ /* all channels frame */
+ main_frame = gimp_frame_new (_("All Channels"));
+ gtk_box_pack_start (GTK_BOX (main_vbox), main_frame, FALSE, FALSE, 0);
+ gtk_widget_show (main_frame);
+
+ frame_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+ gtk_container_add (GTK_CONTAINER (main_frame), frame_vbox);
+ gtk_widget_show (frame_vbox);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (frame_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ button = gtk_button_new_with_mnemonic (_("_Auto Input Levels"));
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gimp_help_set_help_data (button,
+ _("Adjust levels for all channels automatically"),
+ NULL);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (levels_stretch_callback),
+ tool);
+
+ button = gimp_levels_tool_color_picker_new (tool,
+ PICK_HIGH_INPUT |
+ PICK_ALL_CHANNELS);
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_levels_tool_color_picker_new (tool,
+ PICK_GAMMA |
+ PICK_ALL_CHANNELS);
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_levels_tool_color_picker_new (tool,
+ PICK_LOW_INPUT |
+ PICK_ALL_CHANNELS);
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_icon_button_new (GIMP_ICON_TOOL_CURVES,
+ _("Edit these Settings as Curves"));
+ gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (levels_to_curves_callback),
+ tool);
+
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (tool->channel_menu),
+ config->channel);
+}
+
+static void
+gimp_levels_tool_reset (GimpFilterTool *filter_tool)
+{
+ GimpHistogramChannel channel;
+
+ g_object_get (filter_tool->config,
+ "channel", &channel,
+ NULL);
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->reset (filter_tool);
+
+ g_object_set (filter_tool->config,
+ "channel", channel,
+ NULL);
+}
+
+static void
+gimp_levels_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec)
+{
+ GimpLevelsTool *levels_tool = GIMP_LEVELS_TOOL (filter_tool);
+ GimpLevelsConfig *levels_config = GIMP_LEVELS_CONFIG (config);
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->config_notify (filter_tool,
+ config, pspec);
+
+ if (! levels_tool->channel_menu ||
+ ! levels_tool->histogram_view)
+ return;
+
+ if (! strcmp (pspec->name, "linear"))
+ {
+ g_clear_object (&levels_tool->histogram);
+ g_clear_object (&levels_tool->histogram_async);
+ levels_tool->histogram = gimp_histogram_new (levels_config->linear);
+
+ levels_tool->histogram_async = gimp_drawable_calculate_histogram_async (
+ GIMP_TOOL (filter_tool)->drawable, levels_tool->histogram, FALSE);
+ gimp_histogram_view_set_histogram (GIMP_HISTOGRAM_VIEW (levels_tool->histogram_view),
+ levels_tool->histogram);
+ }
+ else if (! strcmp (pspec->name, "channel"))
+ {
+ gimp_histogram_view_set_channel (GIMP_HISTOGRAM_VIEW (levels_tool->histogram_view),
+ levels_config->channel);
+ gimp_color_bar_set_channel (GIMP_COLOR_BAR (levels_tool->output_bar),
+ levels_config->channel);
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (levels_tool->channel_menu),
+ levels_config->channel);
+ }
+ else if (! strcmp (pspec->name, "gamma") ||
+ ! strcmp (pspec->name, "low-input") ||
+ ! strcmp (pspec->name, "high-input"))
+ {
+ gdouble low = gtk_adjustment_get_value (levels_tool->low_input);
+ gdouble high = gtk_adjustment_get_value (levels_tool->high_input);
+ gdouble delta, mid, tmp, value;
+
+ gtk_adjustment_set_lower (levels_tool->high_input, low);
+ gtk_adjustment_set_lower (levels_tool->gamma_linear, low);
+
+ gtk_adjustment_set_upper (levels_tool->low_input, high);
+ gtk_adjustment_set_upper (levels_tool->gamma_linear, high);
+
+ levels_update_input_bar (levels_tool);
+
+ delta = (high - low) / 2.0;
+ mid = low + delta;
+ tmp = log10 (1.0 / levels_config->gamma[levels_config->channel]);
+ value = mid + delta * tmp;
+
+ gtk_adjustment_set_value (levels_tool->gamma_linear, value);
+ }
+}
+
+static gboolean
+gimp_levels_tool_settings_import (GimpFilterTool *filter_tool,
+ GInputStream *input,
+ GError **error)
+{
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+ gchar header[64];
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (input, header, sizeof (header),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (header))
+ {
+ g_prefix_error (error, _("Could not read header: "));
+ return FALSE;
+ }
+
+ g_seekable_seek (G_SEEKABLE (input), 0, G_SEEK_SET, NULL, NULL);
+
+ if (g_str_has_prefix (header, "# GIMP Levels File\n"))
+ return gimp_levels_config_load_cruft (config, input, error);
+
+ return GIMP_FILTER_TOOL_CLASS (parent_class)->settings_import (filter_tool,
+ input,
+ error);
+}
+
+static gboolean
+gimp_levels_tool_settings_export (GimpFilterTool *filter_tool,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpLevelsTool *tool = GIMP_LEVELS_TOOL (filter_tool);
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+
+ if (tool->export_old_format)
+ return gimp_levels_config_save_cruft (config, output, error);
+
+ return GIMP_FILTER_TOOL_CLASS (parent_class)->settings_export (filter_tool,
+ output,
+ error);
+}
+
+static void
+levels_input_adjust_by_color (GimpLevelsConfig *config,
+ guint value,
+ GimpHistogramChannel channel,
+ const GimpRGB *color)
+{
+ switch (value & 0xF)
+ {
+ case PICK_LOW_INPUT:
+ gimp_levels_config_adjust_by_colors (config, channel, color, NULL, NULL);
+ break;
+ case PICK_GAMMA:
+ gimp_levels_config_adjust_by_colors (config, channel, NULL, color, NULL);
+ break;
+ case PICK_HIGH_INPUT:
+ gimp_levels_config_adjust_by_colors (config, channel, NULL, NULL, color);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+gimp_levels_tool_color_picked (GimpFilterTool *color_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (color_tool);
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+ GimpRGB rgb = *color;
+ guint value = GPOINTER_TO_UINT (identifier);
+
+ if (config->linear)
+ babl_process (babl_fish (babl_format ("R'G'B'A double"),
+ babl_format ("RGBA double")),
+ &rgb, &rgb, 1);
+
+ if (value & PICK_ALL_CHANNELS &&
+ gimp_babl_format_get_base_type (sample_format) == GIMP_RGB)
+ {
+ GimpHistogramChannel channel;
+
+ /* first reset the value channel */
+ switch (value & 0xF)
+ {
+ case PICK_LOW_INPUT:
+ config->low_input[GIMP_HISTOGRAM_VALUE] = 0.0;
+ break;
+ case PICK_GAMMA:
+ config->gamma[GIMP_HISTOGRAM_VALUE] = 1.0;
+ break;
+ case PICK_HIGH_INPUT:
+ config->high_input[GIMP_HISTOGRAM_VALUE] = 1.0;
+ break;
+ default:
+ break;
+ }
+
+ /* then adjust all color channels */
+ for (channel = GIMP_HISTOGRAM_RED;
+ channel <= GIMP_HISTOGRAM_BLUE;
+ channel++)
+ {
+ levels_input_adjust_by_color (config, value, channel, &rgb);
+ }
+ }
+ else
+ {
+ levels_input_adjust_by_color (config, value, config->channel, &rgb);
+ }
+}
+
+static void
+gimp_levels_tool_export_setup (GimpSettingsBox *settings_box,
+ GtkFileChooserDialog *dialog,
+ gboolean export,
+ GimpLevelsTool *tool)
+{
+ GtkWidget *button;
+
+ if (! export)
+ return;
+
+ button = gtk_check_button_new_with_mnemonic (_("Use _old levels file format"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+ tool->export_old_format);
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), button);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &tool->export_old_format);
+}
+
+static void
+levels_update_input_bar (GimpLevelsTool *tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+
+ switch (config->channel)
+ {
+ gdouble value;
+
+ case GIMP_HISTOGRAM_VALUE:
+ case GIMP_HISTOGRAM_ALPHA:
+ case GIMP_HISTOGRAM_RGB:
+ case GIMP_HISTOGRAM_LUMINANCE:
+ {
+ guchar v[256];
+ gint i;
+
+ for (i = 0; i < 256; i++)
+ {
+ value = gimp_operation_levels_map_input (config,
+ config->channel,
+ i / 255.0);
+ v[i] = CLAMP (value, 0.0, 1.0) * 255.999;
+ }
+
+ gimp_color_bar_set_buffers (GIMP_COLOR_BAR (tool->input_bar),
+ v, v, v);
+ }
+ break;
+
+ case GIMP_HISTOGRAM_RED:
+ case GIMP_HISTOGRAM_GREEN:
+ case GIMP_HISTOGRAM_BLUE:
+ {
+ guchar r[256];
+ guchar g[256];
+ guchar b[256];
+ gint i;
+
+ for (i = 0; i < 256; i++)
+ {
+ value = gimp_operation_levels_map_input (config,
+ GIMP_HISTOGRAM_RED,
+ i / 255.0);
+ r[i] = CLAMP (value, 0.0, 1.0) * 255.999;
+
+ value = gimp_operation_levels_map_input (config,
+ GIMP_HISTOGRAM_GREEN,
+ i / 255.0);
+ g[i] = CLAMP (value, 0.0, 1.0) * 255.999;
+
+ value = gimp_operation_levels_map_input (config,
+ GIMP_HISTOGRAM_BLUE,
+ i / 255.0);
+ b[i] = CLAMP (value, 0.0, 1.0) * 255.999;
+ }
+
+ gimp_color_bar_set_buffers (GIMP_COLOR_BAR (tool->input_bar),
+ r, g, b);
+ }
+ break;
+ }
+}
+
+static void
+levels_channel_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool)
+{
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+ gint value;
+
+ if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value) &&
+ config->channel != value)
+ {
+ g_object_set (config,
+ "channel", value,
+ NULL);
+ }
+}
+
+static void
+levels_channel_reset_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool)
+{
+ gimp_levels_config_reset_channel (GIMP_LEVELS_CONFIG (filter_tool->config));
+}
+
+static gboolean
+levels_menu_sensitivity (gint value,
+ gpointer data)
+{
+ GimpDrawable *drawable = GIMP_TOOL (data)->drawable;
+ GimpHistogramChannel channel = value;
+
+ if (!drawable)
+ return FALSE;
+
+ switch (channel)
+ {
+ case GIMP_HISTOGRAM_VALUE:
+ return TRUE;
+
+ case GIMP_HISTOGRAM_RED:
+ case GIMP_HISTOGRAM_GREEN:
+ case GIMP_HISTOGRAM_BLUE:
+ return gimp_drawable_is_rgb (drawable);
+
+ case GIMP_HISTOGRAM_ALPHA:
+ return gimp_drawable_has_alpha (drawable);
+
+ case GIMP_HISTOGRAM_RGB:
+ return FALSE;
+
+ case GIMP_HISTOGRAM_LUMINANCE:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static void
+levels_stretch_callback (GtkWidget *widget,
+ GimpLevelsTool *levels_tool)
+{
+ GimpTool *tool = GIMP_TOOL (levels_tool);
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (levels_tool);
+ GimpWaitable *waitable;
+
+ waitable = gimp_trivially_cancelable_waitable_new (
+ GIMP_WAITABLE (levels_tool->histogram_async));
+
+ gimp_wait (tool->tool_info->gimp, waitable, _("Calculating histogram..."));
+
+ g_object_unref (waitable);
+
+ if (gimp_async_is_synced (levels_tool->histogram_async) &&
+ gimp_async_is_finished (levels_tool->histogram_async))
+ {
+ gimp_levels_config_stretch (GIMP_LEVELS_CONFIG (filter_tool->config),
+ levels_tool->histogram,
+ gimp_drawable_is_rgb (tool->drawable));
+ }
+}
+
+static void
+levels_linear_gamma_changed (GtkAdjustment *adjustment,
+ GimpLevelsTool *tool)
+{
+ gdouble low_input = gtk_adjustment_get_value (tool->low_input);
+ gdouble high_input = gtk_adjustment_get_value (tool->high_input);
+ gdouble delta, mid, tmp, value;
+
+ delta = (high_input - low_input) / 2.0;
+
+ if (delta >= 0.5)
+ {
+ mid = low_input + delta;
+ tmp = (gtk_adjustment_get_value (adjustment) - mid) / delta;
+ value = 1.0 / pow (10, tmp);
+
+ /* round the gamma value to the nearest 1/100th */
+ value = floor (value * 100 + 0.5) / 100.0;
+
+ gtk_adjustment_set_value (tool->gamma, value);
+ }
+}
+
+static void
+levels_to_curves_callback (GtkWidget *widget,
+ GimpFilterTool *filter_tool)
+{
+ GimpLevelsConfig *config = GIMP_LEVELS_CONFIG (filter_tool->config);
+ GimpCurvesConfig *curves;
+
+ curves = gimp_levels_config_to_curves_config (config);
+
+ gimp_filter_tool_edit_as (filter_tool,
+ "gimp-curves-tool",
+ GIMP_CONFIG (curves));
+
+ g_object_unref (curves);
+}
diff --git a/app/tools/gimplevelstool.h b/app/tools/gimplevelstool.h
new file mode 100644
index 0000000..b28b9c6
--- /dev/null
+++ b/app/tools/gimplevelstool.h
@@ -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/>.
+ */
+
+#ifndef __GIMP_LEVELS_TOOL_H__
+#define __GIMP_LEVELS_TOOL_H__
+
+
+#include "gimpfiltertool.h"
+
+
+#define GIMP_TYPE_LEVELS_TOOL (gimp_levels_tool_get_type ())
+#define GIMP_LEVELS_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LEVELS_TOOL, GimpLevelsTool))
+#define GIMP_LEVELS_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LEVELS_TOOL, GimpLevelsToolClass))
+#define GIMP_IS_LEVELS_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LEVELS_TOOL))
+#define GIMP_IS_LEVELS_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LEVELS_TOOL))
+#define GIMP_LEVELS_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LEVELS_TOOL, GimpLevelsToolClass))
+
+
+typedef struct _GimpLevelsTool GimpLevelsTool;
+typedef struct _GimpLevelsToolClass GimpLevelsToolClass;
+
+struct _GimpLevelsTool
+{
+ GimpFilterTool parent_instance;
+
+ /* dialog */
+ GimpHistogram *histogram;
+ GimpAsync *histogram_async;
+
+ GtkWidget *channel_menu;
+
+ GtkWidget *histogram_view;
+
+ GtkWidget *input_bar;
+ GtkWidget *low_input_spinbutton;
+ GtkWidget *high_input_spinbutton;
+ GtkWidget *low_output_spinbutton;
+ GtkWidget *high_output_spinbutton;
+ GtkAdjustment *low_input;
+ GtkAdjustment *gamma;
+ GtkAdjustment *gamma_linear;
+ GtkAdjustment *high_input;
+
+ GtkWidget *output_bar;
+
+ /* export dialog */
+ gboolean export_old_format;
+};
+
+struct _GimpLevelsToolClass
+{
+ GimpFilterToolClass parent_class;
+};
+
+
+void gimp_levels_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_levels_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_LEVELS_TOOL_H__ */
diff --git a/app/tools/gimpmagnifyoptions.c b/app/tools/gimpmagnifyoptions.c
new file mode 100644
index 0000000..0ec7074
--- /dev/null
+++ b/app/tools/gimpmagnifyoptions.c
@@ -0,0 +1,202 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpmagnifyoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_AUTO_RESIZE,
+ PROP_ZOOM_TYPE
+};
+
+
+static void gimp_magnify_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_magnify_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_magnify_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_magnify_options_reset (GimpConfig *config);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpMagnifyOptions, gimp_magnify_options,
+ GIMP_TYPE_TOOL_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_magnify_options_config_iface_init))
+
+#define parent_class gimp_magnify_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_magnify_options_class_init (GimpMagnifyOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_magnify_options_set_property;
+ object_class->get_property = gimp_magnify_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_AUTO_RESIZE,
+ "auto-resize",
+ _("Auto-resize window"),
+ _("Resize image window to accommodate "
+ "new zoom level"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ZOOM_TYPE,
+ "zoom-type",
+ _("Direction"),
+ _("Direction of magnification"),
+ GIMP_TYPE_ZOOM_TYPE,
+ GIMP_ZOOM_IN,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_magnify_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ config_iface->reset = gimp_magnify_options_reset;
+}
+
+static void
+gimp_magnify_options_init (GimpMagnifyOptions *options)
+{
+}
+
+static void
+gimp_magnify_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMagnifyOptions *options = GIMP_MAGNIFY_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_AUTO_RESIZE:
+ options->auto_resize = g_value_get_boolean (value);
+ break;
+ case PROP_ZOOM_TYPE:
+ options->zoom_type = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_magnify_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMagnifyOptions *options = GIMP_MAGNIFY_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_AUTO_RESIZE:
+ g_value_set_boolean (value, options->auto_resize);
+ break;
+ case PROP_ZOOM_TYPE:
+ g_value_set_enum (value, options->zoom_type);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_magnify_options_reset (GimpConfig *config)
+{
+ GimpToolOptions *tool_options = GIMP_TOOL_OPTIONS (config);
+ GParamSpec *pspec;
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+ "auto-resize");
+
+ if (pspec)
+ G_PARAM_SPEC_BOOLEAN (pspec)->default_value =
+ GIMP_DISPLAY_CONFIG (tool_options->tool_info->gimp->config)->resize_windows_on_zoom;
+
+ parent_config_iface->reset (config);
+}
+
+GtkWidget *
+gimp_magnify_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *button;
+ gchar *str;
+ GdkModifierType toggle_mask;
+
+ toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ /* the auto_resize toggle button */
+ button = gimp_prop_check_button_new (config, "auto-resize", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* tool toggle */
+ str = g_strdup_printf (_("Direction (%s)"),
+ gimp_get_mod_string (toggle_mask));
+
+ frame = gimp_prop_enum_radio_frame_new (config, "zoom-type",
+ str, 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ g_free (str);
+
+ return vbox;
+}
diff --git a/app/tools/gimpmagnifyoptions.h b/app/tools/gimpmagnifyoptions.h
new file mode 100644
index 0000000..8747e24
--- /dev/null
+++ b/app/tools/gimpmagnifyoptions.h
@@ -0,0 +1,50 @@
+/* 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_MAGNIFY_OPTIONS_H__
+#define __GIMP_MAGNIFY_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_MAGNIFY_OPTIONS (gimp_magnify_options_get_type ())
+#define GIMP_MAGNIFY_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MAGNIFY_OPTIONS, GimpMagnifyOptions))
+#define GIMP_MAGNIFY_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MAGNIFY_OPTIONS, GimpMagnifyOptionsClass))
+#define GIMP_IS_MAGNIFY_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MAGNIFY_OPTIONS))
+#define GIMP_IS_MAGNIFY_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MAGNIFY_OPTIONS))
+#define GIMP_MAGNIFY_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MAGNIFY_OPTIONS, GimpMagnifyOptionsClass))
+
+
+typedef struct _GimpMagnifyOptions GimpMagnifyOptions;
+typedef struct _GimpToolOptionsClass GimpMagnifyOptionsClass;
+
+struct _GimpMagnifyOptions
+{
+ GimpToolOptions parent_instance;
+
+ gboolean auto_resize;
+ GimpZoomType zoom_type;
+};
+
+
+GType gimp_magnify_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_magnify_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_MAGNIFY_OPTIONS_H__ */
diff --git a/app/tools/gimpmagnifytool.c b/app/tools/gimpmagnifytool.c
new file mode 100644
index 0000000..8c5f5d3
--- /dev/null
+++ b/app/tools/gimpmagnifytool.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 "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasrectangle.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-scale.h"
+
+#include "gimpmagnifyoptions.h"
+#include "gimpmagnifytool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_magnify_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_magnify_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_magnify_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_magnify_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_magnify_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_magnify_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_magnify_tool_update_items (GimpMagnifyTool *magnify);
+
+
+G_DEFINE_TYPE (GimpMagnifyTool, gimp_magnify_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_magnify_tool_parent_class
+
+
+void
+gimp_magnify_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_MAGNIFY_TOOL,
+ GIMP_TYPE_MAGNIFY_OPTIONS,
+ gimp_magnify_options_gui,
+ 0,
+ "gimp-zoom-tool",
+ _("Zoom"),
+ _("Zoom Tool: Adjust the zoom level"),
+ N_("_Zoom"), "Z",
+ NULL, GIMP_HELP_TOOL_ZOOM,
+ GIMP_ICON_TOOL_ZOOM,
+ data);
+}
+
+static void
+gimp_magnify_tool_class_init (GimpMagnifyToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->button_press = gimp_magnify_tool_button_press;
+ tool_class->button_release = gimp_magnify_tool_button_release;
+ tool_class->motion = gimp_magnify_tool_motion;
+ tool_class->modifier_key = gimp_magnify_tool_modifier_key;
+ tool_class->cursor_update = gimp_magnify_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_magnify_tool_draw;
+}
+
+static void
+gimp_magnify_tool_init (GimpMagnifyTool *magnify_tool)
+{
+ GimpTool *tool = GIMP_TOOL (magnify_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_ZOOM);
+ gimp_tool_control_set_cursor_modifier (tool->control,
+ GIMP_CURSOR_MODIFIER_PLUS);
+ gimp_tool_control_set_toggle_cursor_modifier (tool->control,
+ GIMP_CURSOR_MODIFIER_MINUS);
+
+ magnify_tool->x = 0;
+ magnify_tool->y = 0;
+ magnify_tool->w = 0;
+ magnify_tool->h = 0;
+}
+
+static void
+gimp_magnify_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpMagnifyTool *magnify = GIMP_MAGNIFY_TOOL (tool);
+
+ magnify->x = coords->x;
+ magnify->y = coords->y;
+ magnify->w = 0;
+ magnify->h = 0;
+
+ gimp_tool_control_activate (tool->control);
+ tool->display = display;
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+}
+
+static void
+gimp_magnify_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpMagnifyTool *magnify = GIMP_MAGNIFY_TOOL (tool);
+ GimpMagnifyOptions *options = GIMP_MAGNIFY_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_control_halt (tool->control);
+
+ switch (release_type)
+ {
+ case GIMP_BUTTON_RELEASE_CLICK:
+ case GIMP_BUTTON_RELEASE_NO_MOTION:
+ gimp_display_shell_scale (shell,
+ options->zoom_type,
+ 0.0,
+ GIMP_ZOOM_FOCUS_POINTER);
+ break;
+
+ case GIMP_BUTTON_RELEASE_NORMAL:
+ {
+ gdouble x, y;
+ gdouble width, height;
+ gboolean resize_window;
+
+ x = (magnify->w < 0) ? magnify->x + magnify->w : magnify->x;
+ y = (magnify->h < 0) ? magnify->y + magnify->h : magnify->y;
+ width = (magnify->w < 0) ? -magnify->w : magnify->w;
+ height = (magnify->h < 0) ? -magnify->h : magnify->h;
+
+ /* Resize windows only in multi-window mode */
+ resize_window = (options->auto_resize &&
+ ! GIMP_GUI_CONFIG (display->config)->single_window_mode);
+
+ gimp_display_shell_scale_to_rectangle (shell,
+ options->zoom_type,
+ x, y, width, height,
+ resize_window);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gimp_magnify_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMagnifyTool *magnify = GIMP_MAGNIFY_TOOL (tool);
+
+ magnify->w = coords->x - magnify->x;
+ magnify->h = coords->y - magnify->y;
+
+ gimp_magnify_tool_update_items (magnify);
+}
+
+static void
+gimp_magnify_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMagnifyOptions *options = GIMP_MAGNIFY_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_toggle_behavior_mask ())
+ {
+ switch (options->zoom_type)
+ {
+ case GIMP_ZOOM_IN:
+ g_object_set (options, "zoom-type", GIMP_ZOOM_OUT, NULL);
+ break;
+
+ case GIMP_ZOOM_OUT:
+ g_object_set (options, "zoom-type", GIMP_ZOOM_IN, NULL);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static void
+gimp_magnify_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMagnifyOptions *options = GIMP_MAGNIFY_TOOL_GET_OPTIONS (tool);
+
+ gimp_tool_control_set_toggled (tool->control,
+ options->zoom_type == GIMP_ZOOM_OUT);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_magnify_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpMagnifyTool *magnify = GIMP_MAGNIFY_TOOL (draw_tool);
+
+ magnify->rectangle =
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ magnify->x,
+ magnify->y,
+ magnify->w,
+ magnify->h);
+}
+
+static void
+gimp_magnify_tool_update_items (GimpMagnifyTool *magnify)
+{
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (magnify)))
+ {
+ gimp_canvas_rectangle_set (magnify->rectangle,
+ magnify->x,
+ magnify->y,
+ magnify->w,
+ magnify->h);
+ }
+}
diff --git a/app/tools/gimpmagnifytool.h b/app/tools/gimpmagnifytool.h
new file mode 100644
index 0000000..505485a
--- /dev/null
+++ b/app/tools/gimpmagnifytool.h
@@ -0,0 +1,60 @@
+/* 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_MAGNIFY_TOOL_H__
+#define __GIMP_MAGNIFY_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_MAGNIFY_TOOL (gimp_magnify_tool_get_type ())
+#define GIMP_MAGNIFY_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MAGNIFY_TOOL, GimpMagnifyTool))
+#define GIMP_MAGNIFY_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MAGNIFY_TOOL, GimpMagnifyToolClass))
+#define GIMP_IS_MAGNIFY_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MAGNIFY_TOOL))
+#define GIMP_IS_MAGNIFY_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MAGNIFY_TOOL))
+#define GIMP_MAGNIFY_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MAGNIFY_TOOL, GimpMagnifyToolClass))
+
+#define GIMP_MAGNIFY_TOOL_GET_OPTIONS(t) (GIMP_MAGNIFY_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpMagnifyTool GimpMagnifyTool;
+typedef struct _GimpMagnifyToolClass GimpMagnifyToolClass;
+
+struct _GimpMagnifyTool
+{
+ GimpDrawTool parent_instance;
+
+ gdouble x, y; /* upper left hand coordinate */
+ gdouble w, h; /* width and height */
+
+ GimpCanvasItem *rectangle;
+};
+
+struct _GimpMagnifyToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_magnify_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_magnify_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MAGNIFY_TOOL_H__ */
diff --git a/app/tools/gimpmeasureoptions.c b/app/tools/gimpmeasureoptions.c
new file mode 100644
index 0000000..4e106a0
--- /dev/null
+++ b/app/tools/gimpmeasureoptions.c
@@ -0,0 +1,183 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmeasuretool.c
+ * Copyright (C) 1999 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpmeasureoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_USE_INFO_WINDOW
+};
+
+
+static void gimp_measure_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_measure_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpMeasureOptions, gimp_measure_options,
+ GIMP_TYPE_TRANSFORM_OPTIONS)
+
+
+static void
+gimp_measure_options_class_init (GimpMeasureOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_measure_options_set_property;
+ object_class->get_property = gimp_measure_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ORIENTATION,
+ "orientation",
+ _("Orientation"),
+ _("Orientation against which the angle is measured"),
+ GIMP_TYPE_COMPASS_ORIENTATION,
+ GIMP_COMPASS_ORIENTATION_AUTO,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_INFO_WINDOW,
+ "use-info-window",
+ _("Use info window"),
+ _("Open a floating dialog to view details "
+ "about measurements"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_measure_options_init (GimpMeasureOptions *options)
+{
+}
+
+static void
+gimp_measure_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMeasureOptions *options = GIMP_MEASURE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ options->orientation = g_value_get_enum (value);
+ break;
+ case PROP_USE_INFO_WINDOW:
+ options->use_info_window = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_measure_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMeasureOptions *options = GIMP_MEASURE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, options->orientation);
+ break;
+ case PROP_USE_INFO_WINDOW:
+ g_value_set_boolean (value, options->use_info_window);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_measure_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpMeasureOptions *options = GIMP_MEASURE_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *button;
+ GtkWidget *vbox2;
+ gchar *str;
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ /* the orientation frame */
+ str = g_strdup_printf (_("Orientation (%s)"),
+ gimp_get_mod_string (toggle_mask));
+ frame = gimp_prop_enum_radio_frame_new (config, "orientation", str, -1, -1);
+ g_free (str);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ /* the use_info_window toggle button */
+ button = gimp_prop_check_button_new (config, "use-info-window", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the straighten frame */
+ frame = gimp_frame_new (_("Straighten"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the transform options */
+ vbox2 = gimp_transform_options_gui (tool_options, FALSE, TRUE, TRUE);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* the straighten button */
+ button = gtk_button_new_with_label (_("Straighten"));
+ gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0);
+ gtk_widget_set_sensitive (button, FALSE);
+ gimp_help_set_help_data (button,
+ _("Rotate the active layer, selection or path "
+ "by the measured angle"),
+ NULL);
+ gtk_widget_show (button);
+
+ options->straighten_button = button;
+
+ return vbox;
+}
diff --git a/app/tools/gimpmeasureoptions.h b/app/tools/gimpmeasureoptions.h
new file mode 100644
index 0000000..79f9f8f
--- /dev/null
+++ b/app/tools/gimpmeasureoptions.h
@@ -0,0 +1,58 @@
+/* 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_MEASURE_OPTIONS_H__
+#define __GIMP_MEASURE_OPTIONS_H__
+
+
+#include "gimptransformoptions.h"
+
+
+#define GIMP_TYPE_MEASURE_OPTIONS (gimp_measure_options_get_type ())
+#define GIMP_MEASURE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MEASURE_OPTIONS, GimpMeasureOptions))
+#define GIMP_MEASURE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MEASURE_OPTIONS, GimpMeasureOptionsClass))
+#define GIMP_IS_MEASURE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MEASURE_OPTIONS))
+#define GIMP_IS_MEASURE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MEASURE_OPTIONS))
+#define GIMP_MEASURE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MEASURE_OPTIONS, GimpMeasureOptionsClass))
+
+
+typedef struct _GimpMeasureOptions GimpMeasureOptions;
+typedef struct _GimpMeasureOptionsClass GimpMeasureOptionsClass;
+
+struct _GimpMeasureOptions
+{
+ GimpTransformOptions parent_instance;
+
+ GimpCompassOrientation orientation;
+ gboolean use_info_window;
+
+ /* options gui */
+ GtkWidget *straighten_button;
+};
+
+struct _GimpMeasureOptionsClass
+{
+ GimpTransformOptionsClass parent_class;
+};
+
+
+GType gimp_measure_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_measure_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_MEASURE_OPTIONS_H__ */
diff --git a/app/tools/gimpmeasuretool.c b/app/tools/gimpmeasuretool.c
new file mode 100644
index 0000000..ade4d8c
--- /dev/null
+++ b/app/tools/gimpmeasuretool.c
@@ -0,0 +1,890 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpimage-undo-push.h"
+#include "core/gimpprogress.h"
+#include "core/gimp-transform-utils.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+#include "display/gimptoolcompass.h"
+#include "display/gimptoolgui.h"
+
+#include "gimpmeasureoptions.h"
+#include "gimpmeasuretool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_measure_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_measure_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_measure_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_measure_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_measure_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_measure_tool_recalc_matrix (GimpTransformTool *tr_tool);
+static gchar * gimp_measure_tool_get_undo_desc (GimpTransformTool *tr_tool);
+
+static void gimp_measure_tool_compass_changed (GimpToolWidget *widget,
+ GimpMeasureTool *measure);
+static void gimp_measure_tool_compass_response(GimpToolWidget *widget,
+ gint response_id,
+ GimpMeasureTool *measure);
+static void gimp_measure_tool_compass_status (GimpToolWidget *widget,
+ const gchar *status,
+ GimpMeasureTool *measure);
+static void gimp_measure_tool_compass_create_guides
+ (GimpToolWidget *widget,
+ gint x,
+ gint y,
+ gboolean horizontal,
+ gboolean vertical,
+ GimpMeasureTool *measure);
+
+static void gimp_measure_tool_start (GimpMeasureTool *measure,
+ GimpDisplay *display,
+ const GimpCoords *coords);
+static void gimp_measure_tool_halt (GimpMeasureTool *measure);
+
+static GimpToolGui * gimp_measure_tool_dialog_new (GimpMeasureTool *measure);
+static void gimp_measure_tool_dialog_update (GimpMeasureTool *measure,
+ GimpDisplay *display);
+
+static void gimp_measure_tool_straighten_button_clicked
+ (GtkWidget *button,
+ GimpMeasureTool *measure);
+
+G_DEFINE_TYPE (GimpMeasureTool, gimp_measure_tool, GIMP_TYPE_TRANSFORM_TOOL)
+
+#define parent_class gimp_measure_tool_parent_class
+
+
+void
+gimp_measure_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_MEASURE_TOOL,
+ GIMP_TYPE_MEASURE_OPTIONS,
+ gimp_measure_options_gui,
+ 0,
+ "gimp-measure-tool",
+ _("Measure"),
+ _("Measure Tool: Measure distances and angles"),
+ N_("_Measure"), "<shift>M",
+ NULL, GIMP_HELP_TOOL_MEASURE,
+ GIMP_ICON_TOOL_MEASURE,
+ data);
+}
+
+static void
+gimp_measure_tool_class_init (GimpMeasureToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_measure_tool_control;
+ tool_class->modifier_key = gimp_measure_tool_modifier_key;
+ tool_class->button_press = gimp_measure_tool_button_press;
+ tool_class->button_release = gimp_measure_tool_button_release;
+ tool_class->motion = gimp_measure_tool_motion;
+
+ tr_class->recalc_matrix = gimp_measure_tool_recalc_matrix;
+ tr_class->get_undo_desc = gimp_measure_tool_get_undo_desc;
+
+ tr_class->undo_desc = C_("undo-type", "Straighten");
+ tr_class->progress_text = _("Straightening");
+}
+
+static void
+gimp_measure_tool_init (GimpMeasureTool *measure)
+{
+ GimpTool *tool = GIMP_TOOL (measure);
+
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER);
+ gimp_tool_control_set_cursor (tool->control,
+ GIMP_CURSOR_CROSSHAIR_SMALL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_MEASURE);
+
+ gimp_draw_tool_set_default_status (GIMP_DRAW_TOOL (tool),
+ _("Click-Drag to create a line"));
+}
+
+static void
+gimp_measure_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpMeasureTool *measure = GIMP_MEASURE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_measure_tool_halt (measure);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_measure_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMeasureOptions *options = GIMP_MEASURE_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_toggle_behavior_mask ())
+ {
+ switch (options->orientation)
+ {
+ case GIMP_COMPASS_ORIENTATION_HORIZONTAL:
+ g_object_set (options,
+ "orientation", GIMP_COMPASS_ORIENTATION_VERTICAL,
+ NULL);
+ break;
+
+ case GIMP_COMPASS_ORIENTATION_VERTICAL:
+ g_object_set (options,
+ "orientation", GIMP_COMPASS_ORIENTATION_HORIZONTAL,
+ NULL);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static void
+gimp_measure_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpMeasureTool *measure = GIMP_MEASURE_TOOL (tool);
+ GimpMeasureOptions *options = GIMP_MEASURE_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (tool->display && display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ if (! measure->widget)
+ {
+ measure->supress_guides = TRUE;
+
+ gimp_measure_tool_start (measure, display, coords);
+
+ gimp_tool_widget_hover (measure->widget, coords, state, TRUE);
+ }
+
+ if (gimp_tool_widget_button_press (measure->widget, coords, time, state,
+ press_type))
+ {
+ measure->grab_widget = measure->widget;
+ }
+
+ /* create the info window if necessary */
+ if (! measure->gui)
+ {
+ if (options->use_info_window ||
+ ! gimp_display_shell_get_show_statusbar (shell))
+ {
+ measure->gui = gimp_measure_tool_dialog_new (measure);
+ g_object_add_weak_pointer (G_OBJECT (measure->gui),
+ (gpointer) &measure->gui);
+ }
+ }
+
+ if (measure->gui)
+ {
+ gimp_tool_gui_set_shell (measure->gui, shell);
+ gimp_tool_gui_set_viewable (measure->gui, GIMP_VIEWABLE (image));
+
+ gimp_measure_tool_dialog_update (measure, display);
+ }
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_measure_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpMeasureTool *measure = GIMP_MEASURE_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (measure->grab_widget)
+ {
+ gimp_tool_widget_button_release (measure->grab_widget,
+ coords, time, state, release_type);
+ measure->grab_widget = NULL;
+ }
+
+ measure->supress_guides = FALSE;
+}
+
+static void
+gimp_measure_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMeasureTool *measure = GIMP_MEASURE_TOOL (tool);
+
+ if (measure->grab_widget)
+ {
+ gimp_tool_widget_motion (measure->grab_widget, coords, time, state);
+ }
+}
+
+static void
+gimp_measure_tool_recalc_matrix (GimpTransformTool *tr_tool)
+{
+ GimpMeasureTool *measure = GIMP_MEASURE_TOOL (tr_tool);
+ gdouble angle;
+
+ if (measure->n_points < 2)
+ {
+ tr_tool->transform_valid = FALSE;
+
+ return;
+ }
+
+ g_object_get (measure->widget,
+ "pixel-angle", &angle,
+ NULL);
+
+ gimp_matrix3_identity (&tr_tool->transform);
+ gimp_transform_matrix_rotate_center (&tr_tool->transform,
+ measure->x[0], measure->y[0],
+ angle);
+
+ tr_tool->transform_valid = TRUE;
+}
+
+static gchar *
+gimp_measure_tool_get_undo_desc (GimpTransformTool *tr_tool)
+{
+ GimpMeasureTool *measure = GIMP_MEASURE_TOOL (tr_tool);
+ GimpCompassOrientation orientation;
+ gdouble angle;
+
+ g_object_get (measure->widget,
+ "effective-orientation", &orientation,
+ "pixel-angle", &angle,
+ NULL);
+
+ angle = gimp_rad_to_deg (fabs (angle));
+
+ switch (orientation)
+ {
+ case GIMP_COMPASS_ORIENTATION_AUTO:
+ return g_strdup_printf (C_("undo-type",
+ "Straighten by %-3.3g°"),
+ angle);
+
+ case GIMP_COMPASS_ORIENTATION_HORIZONTAL:
+ return g_strdup_printf (C_("undo-type",
+ "Straighten Horizontally by %-3.3g°"),
+ angle);
+
+ case GIMP_COMPASS_ORIENTATION_VERTICAL:
+ return g_strdup_printf (C_("undo-type",
+ "Straighten Vertically by %-3.3g°"),
+ angle);
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+static void
+gimp_measure_tool_compass_changed (GimpToolWidget *widget,
+ GimpMeasureTool *measure)
+{
+ GimpMeasureOptions *options = GIMP_MEASURE_TOOL_GET_OPTIONS (measure);
+
+ g_object_get (widget,
+ "n-points", &measure->n_points,
+ "x1", &measure->x[0],
+ "y1", &measure->y[0],
+ "x2", &measure->x[1],
+ "y2", &measure->y[1],
+ "x3", &measure->x[2],
+ "y3", &measure->y[2],
+ NULL);
+
+ gtk_widget_set_sensitive (options->straighten_button, measure->n_points >= 2);
+ gimp_measure_tool_dialog_update (measure, GIMP_TOOL (measure)->display);
+}
+
+static void
+gimp_measure_tool_compass_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpMeasureTool *measure)
+{
+ GimpTool *tool = GIMP_TOOL (measure);
+
+ if (response_id == GIMP_TOOL_WIDGET_RESPONSE_CANCEL)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+}
+
+static void
+gimp_measure_tool_compass_status (GimpToolWidget *widget,
+ const gchar *status,
+ GimpMeasureTool *measure)
+{
+ GimpTool *tool = GIMP_TOOL (measure);
+
+ if (! status)
+ {
+ /* replace status bar hint by distance and angle */
+ gimp_measure_tool_dialog_update (measure, tool->display);
+ }
+}
+
+static void
+gimp_measure_tool_compass_create_guides (GimpToolWidget *widget,
+ gint x,
+ gint y,
+ gboolean horizontal,
+ gboolean vertical,
+ GimpMeasureTool *measure)
+{
+ GimpDisplay *display = GIMP_TOOL (measure)->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (measure->supress_guides)
+ return;
+
+ if (x < 0 || x > gimp_image_get_width (image))
+ vertical = FALSE;
+
+ if (y < 0 || y > gimp_image_get_height (image))
+ horizontal = FALSE;
+
+ if (horizontal || vertical)
+ {
+ if (horizontal && vertical)
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_GUIDE,
+ _("Add Guides"));
+
+ if (horizontal)
+ gimp_image_add_hguide (image, y, TRUE);
+
+ if (vertical)
+ gimp_image_add_vguide (image, x, TRUE);
+
+ if (horizontal && vertical)
+ gimp_image_undo_group_end (image);
+
+ gimp_image_flush (image);
+ }
+}
+
+static void
+gimp_measure_tool_start (GimpMeasureTool *measure,
+ GimpDisplay *display,
+ const GimpCoords *coords)
+{
+ GimpTool *tool = GIMP_TOOL (measure);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpMeasureOptions *options = GIMP_MEASURE_TOOL_GET_OPTIONS (tool);
+
+ measure->n_points = 1;
+ measure->x[0] = coords->x;
+ measure->y[0] = coords->y;
+ measure->x[1] = 0;
+ measure->y[1] = 0;
+ measure->x[2] = 0;
+ measure->y[2] = 0;
+
+ measure->widget = gimp_tool_compass_new (shell,
+ options->orientation,
+ measure->n_points,
+ measure->x[0],
+ measure->y[0],
+ measure->x[1],
+ measure->y[1],
+ measure->x[2],
+ measure->y[2]);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), measure->widget);
+
+ g_object_bind_property (options, "orientation",
+ measure->widget, "orientation",
+ G_BINDING_DEFAULT);
+
+ g_signal_connect (measure->widget, "changed",
+ G_CALLBACK (gimp_measure_tool_compass_changed),
+ measure);
+ g_signal_connect (measure->widget, "response",
+ G_CALLBACK (gimp_measure_tool_compass_response),
+ measure);
+ g_signal_connect (measure->widget, "status",
+ G_CALLBACK (gimp_measure_tool_compass_status),
+ measure);
+ g_signal_connect (measure->widget, "create-guides",
+ G_CALLBACK (gimp_measure_tool_compass_create_guides),
+ measure);
+ g_signal_connect (options->straighten_button, "clicked",
+ G_CALLBACK (gimp_measure_tool_straighten_button_clicked),
+ measure);
+
+ tool->display = display;
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (measure), display);
+}
+
+static void
+gimp_measure_tool_halt (GimpMeasureTool *measure)
+{
+ GimpMeasureOptions *options = GIMP_MEASURE_TOOL_GET_OPTIONS (measure);
+ GimpTool *tool = GIMP_TOOL (measure);
+
+ if (options->straighten_button)
+ {
+ gtk_widget_set_sensitive (options->straighten_button, FALSE);
+
+ g_signal_handlers_disconnect_by_func (
+ options->straighten_button,
+ G_CALLBACK (gimp_measure_tool_straighten_button_clicked),
+ measure);
+ }
+
+ if (tool->display)
+ gimp_tool_pop_status (tool, tool->display);
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (measure)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (measure));
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
+ g_clear_object (&measure->widget);
+
+ g_clear_object (&measure->gui);
+
+ tool->display = NULL;
+}
+
+static void
+gimp_measure_tool_dialog_update (GimpMeasureTool *measure,
+ GimpDisplay *display)
+{
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gint ax, ay;
+ gint bx, by;
+ gint pixel_width;
+ gint pixel_height;
+ gdouble unit_width;
+ gdouble unit_height;
+ gdouble pixel_distance;
+ gdouble unit_distance;
+ gdouble inch_distance;
+ gdouble pixel_angle;
+ gdouble unit_angle;
+ gdouble xres;
+ gdouble yres;
+ gchar format[128];
+ gint unit_distance_digits = 0;
+ gint unit_width_digits;
+ gint unit_height_digits;
+
+ /* calculate distance and angle */
+ ax = measure->x[1] - measure->x[0];
+ ay = measure->y[1] - measure->y[0];
+
+ if (measure->n_points == 3)
+ {
+ bx = measure->x[2] - measure->x[0];
+ by = measure->y[2] - measure->y[0];
+ }
+ else
+ {
+ bx = 0;
+ by = 0;
+ }
+
+ pixel_width = ABS (ax - bx);
+ pixel_height = ABS (ay - by);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ unit_width = gimp_pixels_to_units (pixel_width, shell->unit, xres);
+ unit_height = gimp_pixels_to_units (pixel_height, shell->unit, yres);
+
+ pixel_distance = sqrt (SQR (ax - bx) + SQR (ay - by));
+ inch_distance = sqrt (SQR ((gdouble) (ax - bx) / xres) +
+ SQR ((gdouble) (ay - by) / yres));
+ unit_distance = gimp_unit_get_factor (shell->unit) * inch_distance;
+
+ g_object_get (measure->widget,
+ "pixel-angle", &pixel_angle,
+ "unit-angle", &unit_angle,
+ NULL);
+
+ pixel_angle = fabs (pixel_angle * 180.0 / G_PI);
+ unit_angle = fabs (unit_angle * 180.0 / G_PI);
+
+ /* Compute minimum digits to display accurate values, so that
+ * every pixel shows a different value in unit.
+ */
+ if (inch_distance)
+ unit_distance_digits = gimp_unit_get_scaled_digits (shell->unit,
+ pixel_distance /
+ inch_distance);
+ unit_width_digits = gimp_unit_get_scaled_digits (shell->unit, xres);
+ unit_height_digits = gimp_unit_get_scaled_digits (shell->unit, yres);
+
+ if (shell->unit == GIMP_UNIT_PIXEL)
+ {
+ gimp_tool_replace_status (GIMP_TOOL (measure), display,
+ "%.1f %s, %.2f\302\260 (%d × %d)",
+ pixel_distance, _("pixels"), pixel_angle,
+ pixel_width, pixel_height);
+ }
+ else
+ {
+ g_snprintf (format, sizeof (format),
+ "%%.%df %s, %%.2f\302\260 (%%.%df × %%.%df)",
+ unit_distance_digits,
+ gimp_unit_get_plural (shell->unit),
+ unit_width_digits,
+ unit_height_digits);
+
+ gimp_tool_replace_status (GIMP_TOOL (measure), display, format,
+ unit_distance, unit_angle,
+ unit_width, unit_height);
+ }
+
+ if (measure->gui)
+ {
+ gchar buf[128];
+
+ /* Distance */
+ g_snprintf (buf, sizeof (buf), "%.1f", pixel_distance);
+ gtk_label_set_text (GTK_LABEL (measure->distance_label[0]), buf);
+
+ if (shell->unit != GIMP_UNIT_PIXEL)
+ {
+ g_snprintf (format, sizeof (format), "%%.%df",
+ unit_distance_digits);
+ g_snprintf (buf, sizeof (buf), format, unit_distance);
+ gtk_label_set_text (GTK_LABEL (measure->distance_label[1]), buf);
+
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[0]),
+ gimp_unit_get_plural (shell->unit));
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (measure->distance_label[1]), NULL);
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[0]), NULL);
+ }
+
+ /* Angle */
+ g_snprintf (buf, sizeof (buf), "%.2f", pixel_angle);
+ gtk_label_set_text (GTK_LABEL (measure->angle_label[0]), buf);
+
+ if (fabs (unit_angle - pixel_angle) > 0.01)
+ {
+ g_snprintf (buf, sizeof (buf), "%.2f", unit_angle);
+ gtk_label_set_text (GTK_LABEL (measure->angle_label[1]), buf);
+
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[1]), "\302\260");
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (measure->angle_label[1]), NULL);
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[1]), NULL);
+ }
+
+ /* Width */
+ g_snprintf (buf, sizeof (buf), "%d", pixel_width);
+ gtk_label_set_text (GTK_LABEL (measure->width_label[0]), buf);
+
+ if (shell->unit != GIMP_UNIT_PIXEL)
+ {
+ g_snprintf (format, sizeof (format), "%%.%df",
+ unit_width_digits);
+ g_snprintf (buf, sizeof (buf), format, unit_width);
+ gtk_label_set_text (GTK_LABEL (measure->width_label[1]), buf);
+
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[2]),
+ gimp_unit_get_plural (shell->unit));
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (measure->width_label[1]), NULL);
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[2]), NULL);
+ }
+
+ g_snprintf (buf, sizeof (buf), "%d", pixel_height);
+ gtk_label_set_text (GTK_LABEL (measure->height_label[0]), buf);
+
+ /* Height */
+ if (shell->unit != GIMP_UNIT_PIXEL)
+ {
+ g_snprintf (format, sizeof (format), "%%.%df",
+ unit_height_digits);
+ g_snprintf (buf, sizeof (buf), format, unit_height);
+ gtk_label_set_text (GTK_LABEL (measure->height_label[1]), buf);
+
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[3]),
+ gimp_unit_get_plural (shell->unit));
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (measure->height_label[1]), NULL);
+ gtk_label_set_text (GTK_LABEL (measure->unit_label[3]), NULL);
+ }
+
+ gimp_tool_gui_show (measure->gui);
+ }
+}
+
+static GimpToolGui *
+gimp_measure_tool_dialog_new (GimpMeasureTool *measure)
+{
+ GimpTool *tool = GIMP_TOOL (measure);
+ GimpDisplayShell *shell;
+ GimpToolGui *gui;
+ GtkWidget *table;
+ GtkWidget *label;
+
+ g_return_val_if_fail (tool->display != NULL, NULL);
+
+ shell = gimp_display_get_shell (tool->display);
+
+ gui = gimp_tool_gui_new (tool->tool_info,
+ NULL,
+ _("Measure Distances and Angles"),
+ NULL, NULL,
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ TRUE,
+
+ _("_Close"), GTK_RESPONSE_CLOSE,
+
+ NULL);
+
+ gimp_tool_gui_set_auto_overlay (gui, TRUE);
+ gimp_tool_gui_set_focus_on_map (gui, FALSE);
+
+ g_signal_connect (gui, "response",
+ G_CALLBACK (g_object_unref),
+ NULL);
+
+ table = gtk_table_new (4, 5, TRUE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (gui)), table,
+ TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+
+ label = gtk_label_new (_("Distance:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
+ gtk_widget_show (label);
+
+ measure->distance_label[0] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 0, 1);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (_("pixels"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 3, 0, 1);
+ gtk_widget_show (label);
+
+ measure->distance_label[1] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 3, 4, 0, 1);
+ gtk_widget_show (label);
+
+ measure->unit_label[0] = label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 4, 5, 0, 1);
+ gtk_widget_show (label);
+
+
+ label = gtk_label_new (_("Angle:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 1, 2);
+ gtk_widget_show (label);
+
+ measure->angle_label[0] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 1, 2);
+ gtk_widget_show (label);
+
+ label = gtk_label_new ("\302\260");
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 3, 1, 2);
+ gtk_widget_show (label);
+
+ measure->angle_label[1] = label = gtk_label_new (NULL);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 3, 4, 1, 2);
+ gtk_widget_show (label);
+
+ measure->unit_label[1] = label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 4, 5, 1, 2);
+ gtk_widget_show (label);
+
+
+ label = gtk_label_new (_("Width:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3);
+ gtk_widget_show (label);
+
+ measure->width_label[0] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 2, 3);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (_("pixels"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 3, 2, 3);
+ gtk_widget_show (label);
+
+ measure->width_label[1] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 3, 4, 2, 3);
+ gtk_widget_show (label);
+
+ measure->unit_label[2] = label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 4, 5, 2, 3);
+ gtk_widget_show (label);
+
+
+ label = gtk_label_new (_("Height:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 3, 4);
+ gtk_widget_show (label);
+
+ measure->height_label[0] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (_("pixels"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 3, 3, 4);
+ gtk_widget_show (label);
+
+ measure->height_label[1] = label = gtk_label_new ("0.0");
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 1.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 3, 4, 3, 4);
+ gtk_widget_show (label);
+
+ measure->unit_label[3] = label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach_defaults (GTK_TABLE (table), label, 4, 5, 3, 4);
+ gtk_widget_show (label);
+
+ return gui;
+}
+
+static void
+gimp_measure_tool_straighten_button_clicked (GtkWidget *button,
+ GimpMeasureTool *measure)
+{
+ GimpTool *tool = GIMP_TOOL (measure);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (measure);
+
+ if (gimp_transform_tool_transform (tr_tool, tool->display))
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+}
diff --git a/app/tools/gimpmeasuretool.h b/app/tools/gimpmeasuretool.h
new file mode 100644
index 0000000..ebd42fc
--- /dev/null
+++ b/app/tools/gimpmeasuretool.h
@@ -0,0 +1,71 @@
+/* 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_MEASURE_TOOL_H__
+#define __GIMP_MEASURE_TOOL_H__
+
+
+#include "gimptransformtool.h"
+
+
+#define GIMP_TYPE_MEASURE_TOOL (gimp_measure_tool_get_type ())
+#define GIMP_MEASURE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MEASURE_TOOL, GimpMeasureTool))
+#define GIMP_MEASURE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MEASURE_TOOL, GimpMeasureToolClass))
+#define GIMP_IS_MEASURE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MEASURE_TOOL))
+#define GIMP_IS_MEASURE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MEASURE_TOOL))
+#define GIMP_MEASURE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MEASURE_TOOL, GimpMeasureToolClass))
+
+#define GIMP_MEASURE_TOOL_GET_OPTIONS(t) (GIMP_MEASURE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpMeasureTool GimpMeasureTool;
+typedef struct _GimpMeasureToolClass GimpMeasureToolClass;
+
+struct _GimpMeasureTool
+{
+ GimpTransformTool parent_instance;
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+
+ gboolean supress_guides;
+
+ gint n_points;
+ gint x[3];
+ gint y[3];
+
+ GimpToolGui *gui;
+ GtkWidget *distance_label[2];
+ GtkWidget *angle_label[2];
+ GtkWidget *width_label[2];
+ GtkWidget *height_label[2];
+ GtkWidget *unit_label[4];
+};
+
+struct _GimpMeasureToolClass
+{
+ GimpTransformToolClass parent_class;
+};
+
+
+void gimp_measure_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_measure_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MEASURE_TOOL_H__ */
diff --git a/app/tools/gimpmoveoptions.c b/app/tools/gimpmoveoptions.c
new file mode 100644
index 0000000..86bd4d1
--- /dev/null
+++ b/app/tools/gimpmoveoptions.c
@@ -0,0 +1,227 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpmoveoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_MOVE_TYPE,
+ PROP_MOVE_CURRENT
+};
+
+
+static void gimp_move_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_move_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpMoveOptions, gimp_move_options, GIMP_TYPE_TOOL_OPTIONS)
+
+
+static void
+gimp_move_options_class_init (GimpMoveOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_move_options_set_property;
+ object_class->get_property = gimp_move_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_MOVE_TYPE,
+ "move-type",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_TYPE,
+ GIMP_TRANSFORM_TYPE_LAYER,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MOVE_CURRENT,
+ "move-current",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_move_options_init (GimpMoveOptions *options)
+{
+}
+
+static void
+gimp_move_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMoveOptions *options = GIMP_MOVE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_MOVE_TYPE:
+ options->move_type = g_value_get_enum (value);
+ break;
+ case PROP_MOVE_CURRENT:
+ options->move_current = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_move_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMoveOptions *options = GIMP_MOVE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_MOVE_TYPE:
+ g_value_set_enum (value, options->move_type);
+ break;
+ case PROP_MOVE_CURRENT:
+ g_value_set_boolean (value, options->move_current);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_move_options_notify_type (GimpMoveOptions *move_options,
+ GParamSpec *pspec,
+ GtkWidget *frame)
+{
+ if (move_options->move_type == GIMP_TRANSFORM_TYPE_SELECTION)
+ {
+ gtk_widget_hide (gtk_bin_get_child (GTK_BIN (frame)));
+ gtk_frame_set_label (GTK_FRAME (frame), _("Move selection"));
+ }
+ else
+ {
+ const gchar *false_label = NULL;
+ const gchar *true_label = NULL;
+ GtkWidget *button;
+ GSList *group;
+ gchar *title;
+
+ title = g_strdup_printf (_("Tool Toggle (%s)"),
+ gimp_get_mod_string (gimp_get_extend_selection_mask ()));
+ gtk_frame_set_label (GTK_FRAME (frame), title);
+ g_free (title);
+
+ switch (move_options->move_type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ false_label = _("Pick a layer or guide");
+ true_label = _("Move the active layer");
+ break;
+
+ case GIMP_TRANSFORM_TYPE_PATH:
+ false_label = _("Pick a path");
+ true_label = _("Move the active path");
+ break;
+
+ default: /* GIMP_TRANSFORM_TYPE_SELECTION */
+ g_return_if_reached ();
+ }
+
+ button = g_object_get_data (G_OBJECT (frame), "radio-button");
+
+ group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+ gtk_button_set_label (GTK_BUTTON (group->data), true_label);
+
+ group = g_slist_next (group);
+ gtk_button_set_label (GTK_BUTTON (group->data), false_label);
+
+ gtk_widget_show (gtk_bin_get_child (GTK_BIN (frame)));
+ }
+}
+
+GtkWidget *
+gimp_move_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpMoveOptions *options = GIMP_MOVE_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *hbox;
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *frame;
+ gchar *title;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->type_box = hbox;
+
+ label = gtk_label_new (_("Move:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ box = gimp_prop_enum_icon_box_new (config, "move-type", "gimp",
+ GIMP_TRANSFORM_TYPE_LAYER,
+ GIMP_TRANSFORM_TYPE_PATH);
+ gtk_box_pack_start (GTK_BOX (hbox), box, FALSE, FALSE, 0);
+ gtk_widget_show (box);
+
+ /* tool toggle */
+ title =
+ g_strdup_printf (_("Tool Toggle (%s)"),
+ gimp_get_mod_string (gimp_get_extend_selection_mask ()));
+
+ frame = gimp_prop_boolean_radio_frame_new (config, "move-current",
+ title, "true", "false");
+
+ gimp_move_options_notify_type (GIMP_MOVE_OPTIONS (config), NULL, frame);
+
+ g_signal_connect_object (config, "notify::move-type",
+ G_CALLBACK (gimp_move_options_notify_type),
+ frame, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ g_free (title);
+
+ return vbox;
+}
diff --git a/app/tools/gimpmoveoptions.h b/app/tools/gimpmoveoptions.h
new file mode 100644
index 0000000..bb28683
--- /dev/null
+++ b/app/tools/gimpmoveoptions.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 __GIMP_MOVE_OPTIONS_H__
+#define __GIMP_MOVE_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_MOVE_OPTIONS (gimp_move_options_get_type ())
+#define GIMP_MOVE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MOVE_OPTIONS, GimpMoveOptions))
+#define GIMP_MOVE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MOVE_OPTIONS, GimpMoveOptionsClass))
+#define GIMP_IS_MOVE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MOVE_OPTIONS))
+#define GIMP_IS_MOVE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MOVE_OPTIONS))
+#define GIMP_MOVE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MOVE_OPTIONS, GimpMoveOptionsClass))
+
+
+typedef struct _GimpMoveOptions GimpMoveOptions;
+typedef struct _GimpToolOptionsClass GimpMoveOptionsClass;
+
+struct _GimpMoveOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpTransformType move_type;
+ gboolean move_current;
+
+ /* options gui */
+ GtkWidget *type_box;
+};
+
+
+GType gimp_move_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_move_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_MOVE_OPTIONS_H__ */
diff --git a/app/tools/gimpmovetool.c b/app/tools/gimpmovetool.c
new file mode 100644
index 0000000..8d1d617
--- /dev/null
+++ b/app/tools/gimpmovetool.c
@@ -0,0 +1,665 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimp-cairo.h"
+#include "core/gimp-utils.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimplayer.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimplayermask.h"
+#include "core/gimplayer-floating-selection.h"
+#include "core/gimpundostack.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-appearance.h"
+#include "display/gimpdisplayshell-selection.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimpeditselectiontool.h"
+#include "gimpguidetool.h"
+#include "gimpmoveoptions.h"
+#include "gimpmovetool.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_move_tool_finalize (GObject *object);
+
+static void gimp_move_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_move_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static gboolean gimp_move_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_move_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_move_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_move_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_move_tool_draw (GimpDrawTool *draw_tool);
+
+
+G_DEFINE_TYPE (GimpMoveTool, gimp_move_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_move_tool_parent_class
+
+
+void
+gimp_move_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_MOVE_TOOL,
+ GIMP_TYPE_MOVE_OPTIONS,
+ gimp_move_options_gui,
+ 0,
+ "gimp-move-tool",
+ C_("tool", "Move"),
+ _("Move Tool: Move layers, selections, and other objects"),
+ N_("_Move"), "M",
+ NULL, GIMP_HELP_TOOL_MOVE,
+ GIMP_ICON_TOOL_MOVE,
+ data);
+}
+
+static void
+gimp_move_tool_class_init (GimpMoveToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_move_tool_finalize;
+
+ tool_class->button_press = gimp_move_tool_button_press;
+ tool_class->button_release = gimp_move_tool_button_release;
+ tool_class->key_press = gimp_move_tool_key_press;
+ tool_class->modifier_key = gimp_move_tool_modifier_key;
+ tool_class->oper_update = gimp_move_tool_oper_update;
+ tool_class->cursor_update = gimp_move_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_move_tool_draw;
+}
+
+static void
+gimp_move_tool_init (GimpMoveTool *move_tool)
+{
+ GimpTool *tool = GIMP_TOOL (move_tool);
+
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_MOVE);
+
+ move_tool->floating_layer = NULL;
+ move_tool->guides = NULL;
+
+ move_tool->saved_type = GIMP_TRANSFORM_TYPE_LAYER;
+
+ move_tool->old_active_layer = NULL;
+ move_tool->old_active_vectors = NULL;
+}
+
+static void
+gimp_move_tool_finalize (GObject *object)
+{
+ GimpMoveTool *move = GIMP_MOVE_TOOL (object);
+
+ g_clear_pointer (&move->guides, g_list_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_move_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpMoveTool *move = GIMP_MOVE_TOOL (tool);
+ GimpMoveOptions *options = GIMP_MOVE_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpItem *active_item = NULL;
+ GimpTranslateMode translate_mode = GIMP_TRANSLATE_MODE_MASK;
+ const gchar *null_message = NULL;
+ const gchar *locked_message = NULL;
+
+ tool->display = display;
+
+ move->floating_layer = NULL;
+
+ g_clear_pointer (&move->guides, g_list_free);
+
+ if (! options->move_current)
+ {
+ const gint snap_distance = display->config->snap_distance;
+
+ if (options->move_type == GIMP_TRANSFORM_TYPE_PATH)
+ {
+ GimpVectors *vectors;
+
+ vectors = gimp_image_pick_vectors (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance));
+ if (vectors)
+ {
+ move->old_active_vectors =
+ gimp_image_get_active_vectors (image);
+
+ gimp_image_set_active_vectors (image, vectors);
+ }
+ else
+ {
+ /* no path picked */
+ return;
+ }
+ }
+ else if (options->move_type == GIMP_TRANSFORM_TYPE_LAYER)
+ {
+ GList *guides;
+ GimpLayer *layer;
+
+ if (gimp_display_shell_get_show_guides (shell) &&
+ (guides = gimp_image_pick_guides (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance))))
+ {
+ move->guides = guides;
+
+ gimp_guide_tool_start_edit_many (tool, display, guides);
+
+ return;
+ }
+ else if ((layer = gimp_image_pick_layer (image,
+ coords->x,
+ coords->y,
+ NULL)))
+ {
+ if (gimp_image_get_floating_selection (image) &&
+ ! gimp_layer_is_floating_sel (layer))
+ {
+ /* If there is a floating selection, and this aint it,
+ * use the move tool to anchor it.
+ */
+ move->floating_layer =
+ gimp_image_get_floating_selection (image);
+
+ gimp_tool_control_activate (tool->control);
+
+ return;
+ }
+ else
+ {
+ move->old_active_layer = gimp_image_get_active_layer (image);
+
+ gimp_image_set_active_layer (image, layer);
+ }
+ }
+ else
+ {
+ /* no guide and no layer picked */
+
+ return;
+ }
+ }
+ }
+
+ switch (options->move_type)
+ {
+ case GIMP_TRANSFORM_TYPE_PATH:
+ {
+ active_item = GIMP_ITEM (gimp_image_get_active_vectors (image));
+
+ translate_mode = GIMP_TRANSLATE_MODE_VECTORS;
+
+ if (! active_item)
+ {
+ null_message = _("There is no path to move.");
+ }
+ else if (gimp_item_is_position_locked (active_item))
+ {
+ locked_message = _("The active path's position is locked.");
+ }
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ {
+ active_item = GIMP_ITEM (gimp_image_get_mask (image));
+
+ if (gimp_channel_is_empty (GIMP_CHANNEL (active_item)))
+ active_item = NULL;
+
+ translate_mode = GIMP_TRANSLATE_MODE_MASK;
+
+ if (! active_item)
+ {
+ /* cannot happen, don't translate this message */
+ null_message = "There is no selection to move.";
+ }
+ else if (gimp_item_is_position_locked (active_item))
+ {
+ locked_message = "The selection's position is locked.";
+ }
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ {
+ active_item = GIMP_ITEM (gimp_image_get_active_drawable (image));
+
+ if (! active_item)
+ {
+ null_message = _("There is no layer to move.");
+ }
+ else if (GIMP_IS_LAYER_MASK (active_item))
+ {
+ translate_mode = GIMP_TRANSLATE_MODE_LAYER_MASK;
+
+ if (gimp_item_is_position_locked (active_item))
+ locked_message = _("The active layer's position is locked.");
+ else if (gimp_item_is_content_locked (active_item))
+ locked_message = _("The active layer's pixels are locked.");
+ }
+ else if (GIMP_IS_CHANNEL (active_item))
+ {
+ translate_mode = GIMP_TRANSLATE_MODE_CHANNEL;
+
+ if (gimp_item_is_position_locked (active_item))
+ locked_message = _("The active channel's position is locked.");
+ else if (gimp_item_is_content_locked (active_item))
+ locked_message = _("The active channel's pixels are locked.");
+ }
+ else
+ {
+ translate_mode = GIMP_TRANSLATE_MODE_LAYER;
+
+ if (gimp_item_is_position_locked (active_item))
+ locked_message = _("The active layer's position is locked.");
+ }
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ g_return_if_reached ();
+ }
+
+ if (! active_item)
+ {
+ gimp_tool_message_literal (tool, display, null_message);
+ gimp_widget_blink (options->type_box);
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return;
+ }
+ else if (locked_message)
+ {
+ gimp_tool_message_literal (tool, display, locked_message);
+ gimp_tools_blink_lock_box (display->gimp, active_item);
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return;
+ }
+
+ gimp_tool_control_activate (tool->control);
+
+ gimp_edit_selection_tool_start (tool, display, coords,
+ translate_mode,
+ TRUE);
+}
+
+static void
+gimp_move_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpMoveTool *move = GIMP_MOVE_TOOL (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ gboolean flush = FALSE;
+
+ gimp_tool_control_halt (tool->control);
+
+ if (! config->move_tool_changes_active ||
+ (release_type == GIMP_BUTTON_RELEASE_CANCEL))
+ {
+ if (move->old_active_layer)
+ {
+ gimp_image_set_active_layer (image, move->old_active_layer);
+ move->old_active_layer = NULL;
+
+ flush = TRUE;
+ }
+
+ if (move->old_active_vectors)
+ {
+ gimp_image_set_active_vectors (image, move->old_active_vectors);
+ move->old_active_vectors = NULL;
+
+ flush = TRUE;
+ }
+ }
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ if (move->floating_layer)
+ {
+ floating_sel_anchor (move->floating_layer);
+
+ flush = TRUE;
+ }
+ }
+
+ if (flush)
+ gimp_image_flush (image);
+}
+
+static gboolean
+gimp_move_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpMoveOptions *options = GIMP_MOVE_TOOL_GET_OPTIONS (tool);
+
+ return gimp_edit_selection_tool_translate (tool, kevent,
+ options->move_type,
+ display,
+ options->type_box);
+}
+
+static void
+gimp_move_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMoveTool *move = GIMP_MOVE_TOOL (tool);
+ GimpMoveOptions *options = GIMP_MOVE_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_extend_selection_mask ())
+ {
+ g_object_set (options, "move-current", ! options->move_current, NULL);
+ }
+ else if (key == GDK_MOD1_MASK ||
+ key == gimp_get_toggle_behavior_mask ())
+ {
+ GimpTransformType button_type;
+
+ button_type = options->move_type;
+
+ if (press)
+ {
+ if (key == (state & (GDK_MOD1_MASK |
+ gimp_get_toggle_behavior_mask ())))
+ {
+ /* first modifier pressed */
+
+ move->saved_type = options->move_type;
+ }
+ }
+ else
+ {
+ if (! (state & (GDK_MOD1_MASK |
+ gimp_get_toggle_behavior_mask ())))
+ {
+ /* last modifier released */
+
+ button_type = move->saved_type;
+ }
+ }
+
+ if (state & GDK_MOD1_MASK)
+ {
+ button_type = GIMP_TRANSFORM_TYPE_SELECTION;
+ }
+ else if (state & gimp_get_toggle_behavior_mask ())
+ {
+ button_type = GIMP_TRANSFORM_TYPE_PATH;
+ }
+
+ if (button_type != options->move_type)
+ {
+ g_object_set (options, "move-type", button_type, NULL);
+ }
+ }
+}
+
+static void
+gimp_move_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpMoveTool *move = GIMP_MOVE_TOOL (tool);
+ GimpMoveOptions *options = GIMP_MOVE_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GList *guides = NULL;
+
+ if (options->move_type == GIMP_TRANSFORM_TYPE_LAYER &&
+ ! options->move_current &&
+ gimp_display_shell_get_show_guides (shell) &&
+ proximity)
+ {
+ gint snap_distance = display->config->snap_distance;
+
+ guides = gimp_image_pick_guides (image, coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance));
+ }
+
+ if (gimp_g_list_compare (guides, move->guides))
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (gimp_draw_tool_is_active (draw_tool) &&
+ draw_tool->display != display)
+ gimp_draw_tool_stop (draw_tool);
+
+ g_clear_pointer (&move->guides, g_list_free);
+
+ move->guides = guides;
+
+ if (! gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_start (draw_tool, display);
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+ else
+ {
+ g_list_free (guides);
+ }
+}
+
+static void
+gimp_move_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpMoveOptions *options = GIMP_MOVE_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpCursorType cursor = GIMP_CURSOR_MOUSE;
+ GimpToolCursorType tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+ gint snap_distance = display->config->snap_distance;
+
+ if (options->move_type == GIMP_TRANSFORM_TYPE_PATH)
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_PATHS;
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ if (options->move_current)
+ {
+ GimpItem *item = GIMP_ITEM (gimp_image_get_active_vectors (image));
+
+ if (! item || gimp_item_is_position_locked (item))
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ else
+ {
+ if (gimp_image_pick_vectors (image,
+ coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance)))
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_HAND;
+ }
+ else
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+ }
+ else if (options->move_type == GIMP_TRANSFORM_TYPE_SELECTION)
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_RECT_SELECT;
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ if (gimp_channel_is_empty (gimp_image_get_mask (image)))
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ else if (options->move_current)
+ {
+ GimpItem *item = GIMP_ITEM (gimp_image_get_active_drawable (image));
+
+ if (! item || gimp_item_is_position_locked (item))
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ else
+ {
+ GimpLayer *layer;
+
+ if (gimp_display_shell_get_show_guides (shell) &&
+ gimp_image_pick_guide (image, coords->x, coords->y,
+ FUNSCALEX (shell, snap_distance),
+ FUNSCALEY (shell, snap_distance)))
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_HAND;
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+ else if ((layer = gimp_image_pick_layer (image,
+ coords->x, coords->y,
+ NULL)))
+ {
+ /* if there is a floating selection, and this aint it... */
+ if (gimp_image_get_floating_selection (image) &&
+ ! gimp_layer_is_floating_sel (layer))
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ modifier = GIMP_CURSOR_MODIFIER_ANCHOR;
+ }
+ else if (gimp_item_is_position_locked (GIMP_ITEM (layer)))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ else if (layer != gimp_image_get_active_layer (image))
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_HAND;
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+ }
+ else
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+
+ gimp_tool_control_set_cursor (tool->control, cursor);
+ gimp_tool_control_set_tool_cursor (tool->control, tool_cursor);
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_move_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpMoveTool *move = GIMP_MOVE_TOOL (draw_tool);
+ GList *iter;
+
+ for (iter = move->guides; iter; iter = g_list_next (iter))
+ {
+ GimpGuide *guide = iter->data;
+ GimpCanvasItem *item;
+ GimpGuideStyle style;
+
+ style = gimp_guide_get_style (guide);
+
+ item = gimp_draw_tool_add_guide (draw_tool,
+ gimp_guide_get_orientation (guide),
+ gimp_guide_get_position (guide),
+ style);
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+}
diff --git a/app/tools/gimpmovetool.h b/app/tools/gimpmovetool.h
new file mode 100644
index 0000000..a597e01
--- /dev/null
+++ b/app/tools/gimpmovetool.h
@@ -0,0 +1,63 @@
+/* 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_MOVE_TOOL_H__
+#define __GIMP_MOVE_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_MOVE_TOOL (gimp_move_tool_get_type ())
+#define GIMP_MOVE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MOVE_TOOL, GimpMoveTool))
+#define GIMP_MOVE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MOVE_TOOL, GimpMoveToolClass))
+#define GIMP_IS_MOVE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MOVE_TOOL))
+#define GIMP_IS_MOVE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MOVE_TOOL))
+#define GIMP_MOVE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MOVE_TOOL, GimpMoveToolClass))
+
+#define GIMP_MOVE_TOOL_GET_OPTIONS(t) (GIMP_MOVE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpMoveTool GimpMoveTool;
+typedef struct _GimpMoveToolClass GimpMoveToolClass;
+
+struct _GimpMoveTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpLayer *floating_layer;
+ GList *guides;
+
+ GimpTransformType saved_type;
+
+ GimpLayer *old_active_layer;
+ GimpVectors *old_active_vectors;
+};
+
+struct _GimpMoveToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_move_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_move_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MOVE_TOOL_H__ */
diff --git a/app/tools/gimpmybrushoptions-gui.c b/app/tools/gimpmybrushoptions-gui.c
new file mode 100644
index 0000000..bc26d07
--- /dev/null
+++ b/app/tools/gimpmybrushoptions-gui.c
@@ -0,0 +1,88 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+
+#include "paint/gimpmybrushoptions.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpviewablebox.h"
+
+#include "gimpmybrushoptions-gui.h"
+#include "gimppaintoptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+GtkWidget *
+gimp_mybrush_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *scale;
+
+ /* the brush */
+ button = gimp_prop_mybrush_box_new (NULL, GIMP_CONTEXT (tool_options),
+ _("Brush"), 2,
+ NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* erase mode */
+ scale = gimp_prop_check_button_new (config, "eraser", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* no erasing */
+ scale = gimp_prop_check_button_new (config, "no-erasing", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* radius */
+ scale = gimp_prop_spin_scale_new (config, "radius", NULL,
+ 0.1, 1.0, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* opaque */
+ scale = gimp_prop_spin_scale_new (config, "opaque", NULL,
+ 0.1, 1.0, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* hardness */
+ scale = gimp_prop_spin_scale_new (config, "hardness", NULL,
+ 0.1, 1.0, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return vbox;
+}
diff --git a/app/tools/gimpmybrushoptions-gui.h b/app/tools/gimpmybrushoptions-gui.h
new file mode 100644
index 0000000..f6b7195
--- /dev/null
+++ b/app/tools/gimpmybrushoptions-gui.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_MYBRUSH_OPTIONS_GUI_H__
+#define __GIMP_MYBRUSH_OPTIONS_GUI_H__
+
+
+GtkWidget * gimp_mybrush_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_MYBRUSH_OPTIONS_GUI_H__ */
diff --git a/app/tools/gimpmybrushtool.c b/app/tools/gimpmybrushtool.c
new file mode 100644
index 0000000..4576b89
--- /dev/null
+++ b/app/tools/gimpmybrushtool.c
@@ -0,0 +1,169 @@
+/* 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 "tools-types.h"
+
+#include "paint/gimpmybrushoptions.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpcanvasarc.h"
+
+#include "core/gimp.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "gimpmybrushoptions-gui.h"
+#include "gimpmybrushtool.h"
+#include "gimptoolcontrol.h"
+#include "core/gimpmybrush.h"
+
+#include "gimp-intl.h"
+
+G_DEFINE_TYPE (GimpMybrushTool, gimp_mybrush_tool, GIMP_TYPE_PAINT_TOOL)
+
+#define parent_class gimp_mybrush_tool_parent_class
+
+
+static void gimp_mybrush_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static GimpCanvasItem * gimp_mybrush_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y);
+
+
+void
+gimp_mybrush_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_MYBRUSH_TOOL,
+ GIMP_TYPE_MYBRUSH_OPTIONS,
+ gimp_mybrush_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND |
+ GIMP_CONTEXT_PROP_MASK_OPACITY |
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE |
+ GIMP_CONTEXT_PROP_MASK_MYBRUSH,
+ "gimp-mypaint-brush-tool",
+ _("MyPaint Brush"),
+ _("MyPaint Brush Tool: Use MyPaint brushes in GIMP"),
+ N_("M_yPaint Brush"), "Y",
+ NULL, GIMP_HELP_TOOL_MYPAINT_BRUSH,
+ GIMP_ICON_TOOL_MYPAINT_BRUSH,
+ data);
+}
+
+static void
+gimp_mybrush_tool_class_init (GimpMybrushToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ tool_class->options_notify = gimp_mybrush_tool_options_notify;
+
+ paint_tool_class->get_outline = gimp_mybrush_tool_get_outline;
+}
+
+static void
+gimp_mybrush_tool_init (GimpMybrushTool *mybrush_tool)
+{
+ GimpTool *tool = GIMP_TOOL (mybrush_tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_INK);
+ gimp_tool_control_set_action_size (tool->control,
+ "tools/tools-mypaint-brush-radius-set");
+ gimp_tool_control_set_action_hardness (tool->control,
+ "tools/tools-mypaint-brush-hardness-set");
+
+ gimp_paint_tool_enable_color_picker (GIMP_PAINT_TOOL (mybrush_tool),
+ GIMP_COLOR_PICK_TARGET_FOREGROUND);
+}
+
+static void
+gimp_mybrush_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "radius"))
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+}
+
+static GimpCanvasItem *
+gimp_mybrush_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y)
+{
+ GimpMybrushOptions *options = GIMP_MYBRUSH_TOOL_GET_OPTIONS (paint_tool);
+ GimpMybrush *brush = gimp_context_get_mybrush (GIMP_CONTEXT (options));
+ GimpCanvasItem *item = NULL;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ gdouble radius = exp (options->radius) + 2 * options->radius * gimp_mybrush_get_offset_by_random (brush);
+ radius = MAX (MAX (4 / shell->scale_x, 4 / shell->scale_y), radius);
+
+ item = gimp_mybrush_tool_create_cursor (paint_tool, display, x, y, radius);
+
+ if (! item)
+ {
+ gimp_paint_tool_set_draw_fallback (paint_tool,
+ TRUE, radius);
+ }
+
+ return item;
+}
+
+GimpCanvasItem *
+gimp_mybrush_tool_create_cursor (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ gdouble radius)
+{
+
+ GimpDisplayShell *shell;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_TOOL (paint_tool), NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ shell = gimp_display_get_shell (display);
+
+ /* don't draw the boundary if it becomes too small */
+ if (SCALEX (shell, radius) > 4 &&
+ SCALEY (shell, radius) > 4)
+ {
+ return gimp_canvas_arc_new(shell, x, y, radius, radius, 0.0, 2 * G_PI, FALSE);
+ }
+
+ return NULL;
+}
diff --git a/app/tools/gimpmybrushtool.h b/app/tools/gimpmybrushtool.h
new file mode 100644
index 0000000..9b16024
--- /dev/null
+++ b/app/tools/gimpmybrushtool.h
@@ -0,0 +1,60 @@
+/* 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_MYBRUSH_TOOL_H__
+#define __GIMP_MYBRUSH_TOOL_H__
+
+
+#include "gimppainttool.h"
+
+
+#define GIMP_TYPE_MYBRUSH_TOOL (gimp_mybrush_tool_get_type ())
+#define GIMP_MYBRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MYBRUSH_TOOL, GimpMybrushTool))
+#define GIMP_MYBRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MYBRUSH_TOOL, GimpMybrushToolClass))
+#define GIMP_IS_MYBRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MYBRUSH_TOOL))
+#define GIMP_IS_MYBRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MYBRUSH_TOOL))
+#define GIMP_MYBRUSH_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MYBRUSH_TOOL, GimpMybrushToolClass))
+
+#define GIMP_MYBRUSH_TOOL_GET_OPTIONS(t) (GIMP_MYBRUSH_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpMybrushTool GimpMybrushTool;
+typedef struct _GimpMybrushToolClass GimpMybrushToolClass;
+
+struct _GimpMybrushTool
+{
+ GimpPaintTool parent_instance;
+};
+
+struct _GimpMybrushToolClass
+{
+ GimpPaintToolClass parent_class;
+};
+
+
+void gimp_mybrush_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_mybrush_tool_get_type (void) G_GNUC_CONST;
+
+GimpCanvasItem * gimp_mybrush_tool_create_cursor (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ gdouble radius);
+
+#endif /* __GIMP_MYBRUSH_TOOL_H__ */
diff --git a/app/tools/gimpnpointdeformationoptions.c b/app/tools/gimpnpointdeformationoptions.c
new file mode 100644
index 0000000..9098eb0
--- /dev/null
+++ b/app/tools/gimpnpointdeformationoptions.c
@@ -0,0 +1,264 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpnpointdeformationoptions.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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+
+#include "gimpnpointdeformationoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SQUARE_SIZE,
+ PROP_RIGIDITY,
+ PROP_ASAP_DEFORMATION,
+ PROP_MLS_WEIGHTS,
+ PROP_MLS_WEIGHTS_ALPHA,
+ PROP_MESH_VISIBLE
+};
+
+
+static void gimp_n_point_deformation_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_n_point_deformation_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpNPointDeformationOptions, gimp_n_point_deformation_options,
+ GIMP_TYPE_TOOL_OPTIONS)
+
+#define parent_class gimp_n_point_deformation_options_parent_class
+
+
+static void
+gimp_n_point_deformation_options_class_init (GimpNPointDeformationOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_n_point_deformation_options_set_property;
+ object_class->get_property = gimp_n_point_deformation_options_get_property;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SQUARE_SIZE,
+ "square-size",
+ _("Density"),
+ _("Density"),
+ 5.0, 1000.0, 20.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RIGIDITY,
+ "rigidity",
+ _("Rigidity"),
+ _("Rigidity"),
+ 1.0, 10000.0, 100.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ASAP_DEFORMATION,
+ "asap-deformation",
+ _("Deformation mode"),
+ _("Deformation mode"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MLS_WEIGHTS,
+ "mls-weights",
+ _("Use weights"),
+ _("Use weights"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_MLS_WEIGHTS_ALPHA,
+ "mls-weights-alpha",
+ _("Control points influence"),
+ _("Amount of control points' influence"),
+ 0.1, 2.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MESH_VISIBLE,
+ "mesh-visible",
+ _("Show lattice"),
+ _("Show lattice"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_n_point_deformation_options_init (GimpNPointDeformationOptions *options)
+{
+}
+
+static void
+gimp_n_point_deformation_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpNPointDeformationOptions *options;
+
+ options = GIMP_N_POINT_DEFORMATION_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SQUARE_SIZE:
+ options->square_size = g_value_get_double (value);
+ break;
+ case PROP_RIGIDITY:
+ options->rigidity = g_value_get_double (value);
+ break;
+ case PROP_ASAP_DEFORMATION:
+ options->asap_deformation = g_value_get_boolean (value);
+ break;
+ case PROP_MLS_WEIGHTS:
+ options->mls_weights = g_value_get_boolean (value);
+ break;
+ case PROP_MLS_WEIGHTS_ALPHA:
+ options->mls_weights_alpha = g_value_get_double (value);
+ break;
+ case PROP_MESH_VISIBLE:
+ options->mesh_visible = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_n_point_deformation_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpNPointDeformationOptions *options;
+
+ options = GIMP_N_POINT_DEFORMATION_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SQUARE_SIZE:
+ g_value_set_double (value, options->square_size);
+ break;
+ case PROP_RIGIDITY:
+ g_value_set_double (value, options->rigidity);
+ break;
+ case PROP_ASAP_DEFORMATION:
+ g_value_set_boolean (value, options->asap_deformation);
+ break;
+ case PROP_MLS_WEIGHTS:
+ g_value_set_boolean (value, options->mls_weights);
+ break;
+ case PROP_MLS_WEIGHTS_ALPHA:
+ g_value_set_double (value, options->mls_weights_alpha);
+ break;
+ case PROP_MESH_VISIBLE:
+ g_value_set_boolean (value, options->mesh_visible);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_n_point_deformation_options_gui (GimpToolOptions *tool_options)
+{
+ GimpNPointDeformationOptions *npd_options;
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *widget;
+
+ npd_options = GIMP_N_POINT_DEFORMATION_OPTIONS (tool_options);
+
+ widget = gimp_prop_check_button_new (config, "mesh-visible", NULL);
+ npd_options->check_mesh_visible = widget;
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_spin_scale_new (config, "square-size", NULL,
+ 1.0, 10.0, 0);
+ npd_options->scale_square_size = widget;
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (widget), 10.0, 100.0);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_spin_scale_new (config, "rigidity", NULL,
+ 1.0, 10.0, 0);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (widget), 1.0, 2000.0);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_boolean_radio_frame_new (config, "asap-deformation",
+ NULL,
+ _("Scale"),
+ _("Rigid (Rubber)"));
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_check_button_new (config, "mls-weights", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_spin_scale_new (config, "mls-weights-alpha", NULL,
+ 0.1, 0.1, 1);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (widget), 0.1, 2.0);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (config, "mls-weights",
+ widget, "sensitive",
+ G_BINDING_DEFAULT |
+ G_BINDING_SYNC_CREATE);
+
+ gimp_n_point_deformation_options_set_sensitivity (npd_options, FALSE);
+
+ return vbox;
+}
+
+void
+gimp_n_point_deformation_options_set_sensitivity (GimpNPointDeformationOptions *npd_options,
+ gboolean tool_active)
+{
+ gtk_widget_set_sensitive (npd_options->scale_square_size, ! tool_active);
+ gtk_widget_set_sensitive (npd_options->check_mesh_visible, tool_active);
+}
diff --git a/app/tools/gimpnpointdeformationoptions.h b/app/tools/gimpnpointdeformationoptions.h
new file mode 100644
index 0000000..8e559b0
--- /dev/null
+++ b/app/tools/gimpnpointdeformationoptions.h
@@ -0,0 +1,67 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpnpointdeformationoptions.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_N_POINT_DEFORMATION_OPTIONS_H__
+#define __GIMP_N_POINT_DEFORMATION_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS (gimp_n_point_deformation_options_get_type ())
+#define GIMP_N_POINT_DEFORMATION_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS, GimpNPointDeformationOptions))
+#define GIMP_N_POINT_DEFORMATION_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS, GimpNPointDeformationOptionsClass))
+#define GIMP_IS_N_POINT_DEFORMATION_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS))
+#define GIMP_IS_N_POINT_DEFORMATION_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS))
+#define GIMP_N_POINT_DEFORMATION_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS, GimpNPointDeformationOptionsClass))
+
+
+typedef struct _GimpNPointDeformationOptions GimpNPointDeformationOptions;
+typedef struct _GimpNPointDeformationOptionsClass GimpNPointDeformationOptionsClass;
+
+struct _GimpNPointDeformationOptions
+{
+ GimpToolOptions parent_instance;
+
+ gdouble square_size;
+ gdouble rigidity;
+ gboolean asap_deformation;
+ gboolean mls_weights;
+ gdouble mls_weights_alpha;
+ gboolean mesh_visible;
+
+ GtkWidget *scale_square_size;
+ GtkWidget *check_mesh_visible;
+};
+
+struct _GimpNPointDeformationOptionsClass
+{
+ GimpToolOptionsClass parent_class;
+};
+
+
+GType gimp_n_point_deformation_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_n_point_deformation_options_gui (GimpToolOptions *tool_options);
+
+void gimp_n_point_deformation_options_set_sensitivity (GimpNPointDeformationOptions *npd_options,
+ gboolean tool_active);
+
+
+#endif /* __GIMP_N_POINT_DEFORMATION_OPTIONS_H__ */
diff --git a/app/tools/gimpnpointdeformationtool.c b/app/tools/gimpnpointdeformationtool.c
new file mode 100644
index 0000000..0b653de
--- /dev/null
+++ b/app/tools/gimpnpointdeformationtool.c
@@ -0,0 +1,1020 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpnpointdeformationtool.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 <gegl-plugin.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <npd/npd_common.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h" /* playground */
+
+#include "gegl/gimp-gegl-utils.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpcanvasbufferpreview.h"
+
+#include "gimpnpointdeformationtool.h"
+#include "gimpnpointdeformationoptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+//#define GIMP_NPD_DEBUG
+#define GIMP_NPD_MAXIMUM_DEFORMATION_DELAY 100000 /* 100000 microseconds == 10 FPS */
+#define GIMP_NPD_DRAW_INTERVAL 50 /* 50 milliseconds == 20 FPS */
+
+
+static void gimp_n_point_deformation_tool_start (GimpNPointDeformationTool *npd_tool,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_halt (GimpNPointDeformationTool *npd_tool);
+static void gimp_n_point_deformation_tool_commit (GimpNPointDeformationTool *npd_tool);
+static void gimp_n_point_deformation_tool_set_options (GimpNPointDeformationTool *npd_tool,
+ GimpNPointDeformationOptions *npd_options);
+static void gimp_n_point_deformation_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+static void gimp_n_point_deformation_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static gboolean gimp_n_point_deformation_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_draw (GimpDrawTool *draw_tool);
+static void gimp_n_point_deformation_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_n_point_deformation_tool_clear_selected_points_list
+ (GimpNPointDeformationTool *npd_tool);
+static gboolean gimp_n_point_deformation_tool_add_cp_to_selection (GimpNPointDeformationTool *npd_tool,
+ NPDControlPoint *cp);
+static gboolean gimp_n_point_deformation_tool_is_cp_in_area (NPDControlPoint *cp,
+ gfloat x0,
+ gfloat y0,
+ gfloat x1,
+ gfloat y1,
+ gfloat offset_x,
+ gfloat offset_y,
+ gfloat cp_radius);
+static void gimp_n_point_deformation_tool_remove_cp_from_selection
+ (GimpNPointDeformationTool *npd_tool,
+ NPDControlPoint *cp);
+static gpointer gimp_n_point_deformation_tool_deform_thread_func (gpointer data);
+static gboolean gimp_n_point_deformation_tool_canvas_update_timeout (GimpNPointDeformationTool *npd_tool);
+static void gimp_n_point_deformation_tool_perform_deformation (GimpNPointDeformationTool *npd_tool);
+static void gimp_n_point_deformation_tool_halt_threads (GimpNPointDeformationTool *npd_tool);
+static void gimp_n_point_deformation_tool_apply_deformation (GimpNPointDeformationTool *npd_tool);
+
+#ifdef GIMP_NPD_DEBUG
+#define gimp_npd_debug(x) g_printf x
+#else
+#define gimp_npd_debug(x)
+#endif
+
+G_DEFINE_TYPE (GimpNPointDeformationTool, gimp_n_point_deformation_tool,
+ GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_n_point_deformation_tool_parent_class
+
+
+void
+gimp_n_point_deformation_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ /* we should not know that "data" is a Gimp*, but what the heck this
+ * is experimental playground stuff
+ */
+ if (GIMP_GUI_CONFIG (GIMP (data)->config)->playground_npd_tool)
+ (* callback) (GIMP_TYPE_N_POINT_DEFORMATION_TOOL,
+ GIMP_TYPE_N_POINT_DEFORMATION_OPTIONS,
+ gimp_n_point_deformation_options_gui,
+ 0,
+ "gimp-n-point-deformation-tool",
+ _("N-Point Deformation"),
+ _("N-Point Deformation Tool: Rubber-like deformation of "
+ "image using points"),
+ N_("_N-Point Deformation"), "<shift>N",
+ NULL, GIMP_HELP_TOOL_N_POINT_DEFORMATION,
+ GIMP_ICON_TOOL_N_POINT_DEFORMATION,
+ data);
+}
+
+static void
+gimp_n_point_deformation_tool_class_init (GimpNPointDeformationToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->options_notify = gimp_n_point_deformation_tool_options_notify;
+ tool_class->button_press = gimp_n_point_deformation_tool_button_press;
+ tool_class->button_release = gimp_n_point_deformation_tool_button_release;
+ tool_class->key_press = gimp_n_point_deformation_tool_key_press;
+ tool_class->modifier_key = gimp_n_point_deformation_tool_modifier_key;
+ tool_class->control = gimp_n_point_deformation_tool_control;
+ tool_class->motion = gimp_n_point_deformation_tool_motion;
+ tool_class->oper_update = gimp_n_point_deformation_tool_oper_update;
+ tool_class->cursor_update = gimp_n_point_deformation_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_n_point_deformation_tool_draw;
+}
+
+static void
+gimp_n_point_deformation_tool_init (GimpNPointDeformationTool *npd_tool)
+{
+ GimpTool *tool = GIMP_TOOL (npd_tool);
+
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PERSPECTIVE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_wants_all_key_events (tool->control, TRUE);
+ gimp_tool_control_set_handle_empty_image (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+}
+
+static void
+gimp_n_point_deformation_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_n_point_deformation_tool_halt (npd_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_n_point_deformation_tool_commit (npd_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_n_point_deformation_tool_start (GimpNPointDeformationTool *npd_tool,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (npd_tool);
+ GimpNPointDeformationOptions *npd_options;
+ GimpImage *image;
+ GeglBuffer *source_buffer;
+ GeglBuffer *preview_buffer;
+ NPDModel *model;
+
+ npd_options = GIMP_N_POINT_DEFORMATION_TOOL_GET_OPTIONS (npd_tool);
+
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ image = gimp_display_get_image (display);
+
+ tool->display = display;
+ tool->drawable = gimp_image_get_active_drawable (image);
+
+ npd_tool->active = TRUE;
+
+ /* create GEGL graph */
+ source_buffer = gimp_drawable_get_buffer (tool->drawable);
+
+ preview_buffer = gegl_buffer_new (gegl_buffer_get_extent (source_buffer),
+ babl_format ("cairo-ARGB32"));
+
+ npd_tool->graph = gegl_node_new ();
+
+ npd_tool->source = gegl_node_new_child (npd_tool->graph,
+ "operation", "gegl:buffer-source",
+ "buffer", source_buffer,
+ NULL);
+ npd_tool->npd_node = gegl_node_new_child (npd_tool->graph,
+ "operation", "gegl:npd",
+ NULL);
+ npd_tool->sink = gegl_node_new_child (npd_tool->graph,
+ "operation", "gegl:write-buffer",
+ "buffer", preview_buffer,
+ NULL);
+
+ gegl_node_link_many (npd_tool->source,
+ npd_tool->npd_node,
+ npd_tool->sink,
+ NULL);
+
+ /* initialize some options */
+ g_object_set (G_OBJECT (npd_options), "mesh-visible", TRUE, NULL);
+ gimp_n_point_deformation_options_set_sensitivity (npd_options, TRUE);
+
+ /* compute and get model */
+ gegl_node_process (npd_tool->npd_node);
+ gegl_node_get (npd_tool->npd_node, "model", &model, NULL);
+
+ npd_tool->model = model;
+ npd_tool->preview_buffer = preview_buffer;
+ npd_tool->selected_cp = NULL;
+ npd_tool->hovering_cp = NULL;
+ npd_tool->selected_cps = NULL;
+ npd_tool->rubber_band = FALSE;
+ npd_tool->lattice_points = g_new (GimpVector2,
+ 5 * model->hidden_model->num_of_bones);
+
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable),
+ &npd_tool->offset_x, &npd_tool->offset_y);
+ gimp_npd_debug (("offset: %f %f\n", npd_tool->offset_x, npd_tool->offset_y));
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (npd_tool), display);
+
+ gimp_n_point_deformation_tool_perform_deformation (npd_tool);
+
+ /* hide original image */
+ gimp_item_set_visible (GIMP_ITEM (tool->drawable), FALSE, FALSE);
+ gimp_image_flush (image);
+
+ /* create and start a deformation thread */
+ npd_tool->deform_thread =
+ g_thread_new ("deform thread",
+ (GThreadFunc) gimp_n_point_deformation_tool_deform_thread_func,
+ npd_tool);
+
+ /* create and start canvas update timeout */
+ npd_tool->draw_timeout_id =
+ gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
+ GIMP_NPD_DRAW_INTERVAL,
+ (GSourceFunc) gimp_n_point_deformation_tool_canvas_update_timeout,
+ npd_tool,
+ NULL);
+}
+
+static void
+gimp_n_point_deformation_tool_commit (GimpNPointDeformationTool *npd_tool)
+{
+ GimpTool *tool = GIMP_TOOL (npd_tool);
+
+ if (npd_tool->active)
+ {
+ gimp_n_point_deformation_tool_halt_threads (npd_tool);
+
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+ gimp_n_point_deformation_tool_apply_deformation (npd_tool);
+ gimp_tool_control_pop_preserve (tool->control);
+
+ /* show original/deformed image */
+ gimp_item_set_visible (GIMP_ITEM (tool->drawable), TRUE, FALSE);
+ gimp_image_flush (gimp_display_get_image (tool->display));
+
+ npd_tool->active = FALSE;
+ }
+}
+
+static void
+gimp_n_point_deformation_tool_halt (GimpNPointDeformationTool *npd_tool)
+{
+ GimpTool *tool = GIMP_TOOL (npd_tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (npd_tool);
+ GimpNPointDeformationOptions *npd_options;
+
+ npd_options = GIMP_N_POINT_DEFORMATION_TOOL_GET_OPTIONS (npd_tool);
+
+ if (npd_tool->active)
+ {
+ gimp_n_point_deformation_tool_halt_threads (npd_tool);
+
+ /* show original/deformed image */
+ gimp_item_set_visible (GIMP_ITEM (tool->drawable), TRUE, FALSE);
+ gimp_image_flush (gimp_display_get_image (tool->display));
+
+ /* disable some options */
+ gimp_n_point_deformation_options_set_sensitivity (npd_options, FALSE);
+
+ npd_tool->active = FALSE;
+ }
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_stop (draw_tool);
+
+ gimp_n_point_deformation_tool_clear_selected_points_list (npd_tool);
+
+ g_clear_object (&npd_tool->graph);
+ npd_tool->source = NULL;
+ npd_tool->npd_node = NULL;
+ npd_tool->sink = NULL;
+
+ g_clear_object (&npd_tool->preview_buffer);
+ g_clear_pointer (&npd_tool->lattice_points, g_free);
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+}
+
+static void
+gimp_n_point_deformation_tool_set_options (GimpNPointDeformationTool *npd_tool,
+ GimpNPointDeformationOptions *npd_options)
+{
+ gegl_node_set (npd_tool->npd_node,
+ "square-size", (gint) npd_options->square_size,
+ "rigidity", (gint) npd_options->rigidity,
+ "asap-deformation", npd_options->asap_deformation,
+ "mls-weights", npd_options->mls_weights,
+ "mls-weights-alpha", npd_options->mls_weights_alpha,
+ NULL);
+}
+
+static void
+gimp_n_point_deformation_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+ GimpNPointDeformationOptions *npd_options = GIMP_N_POINT_DEFORMATION_OPTIONS (options);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! npd_tool->active)
+ return;
+
+ gimp_draw_tool_pause (draw_tool);
+
+ gimp_npd_debug (("npd options notify\n"));
+ gimp_n_point_deformation_tool_set_options (npd_tool, npd_options);
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+static gboolean
+gimp_n_point_deformation_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ /* if there is at least one control point, remove last added
+ * control point
+ */
+ if (npd_tool->model &&
+ npd_tool->model->control_points &&
+ npd_tool->model->control_points->len > 0)
+ {
+ GArray *cps = npd_tool->model->control_points;
+ NPDControlPoint *cp = &g_array_index (cps, NPDControlPoint,
+ cps->len - 1);
+
+ gimp_npd_debug (("removing last cp %p\n", cp));
+ gimp_n_point_deformation_tool_remove_cp_from_selection (npd_tool, cp);
+ npd_remove_control_point (npd_tool->model, cp);
+ }
+ break;
+
+ case GDK_KEY_Delete:
+ if (npd_tool->model &&
+ npd_tool->selected_cps)
+ {
+ /* if there is at least one selected control point, remove it */
+ npd_remove_control_points (npd_tool->model, npd_tool->selected_cps);
+ gimp_n_point_deformation_tool_clear_selected_points_list (npd_tool);
+ }
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ break;
+
+ case GDK_KEY_Escape:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_n_point_deformation_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+}
+
+static void
+gimp_n_point_deformation_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_PLUS;
+
+ if (! npd_tool->active)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+ }
+ else if (npd_tool->hovering_cp)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_n_point_deformation_tool_clear_selected_points_list (GimpNPointDeformationTool *npd_tool)
+{
+ if (npd_tool->selected_cps)
+ {
+ g_list_free (npd_tool->selected_cps);
+ npd_tool->selected_cps = NULL;
+ }
+}
+
+static gboolean
+gimp_n_point_deformation_tool_add_cp_to_selection (GimpNPointDeformationTool *npd_tool,
+ NPDControlPoint *cp)
+{
+ if (! g_list_find (npd_tool->selected_cps, cp))
+ {
+ npd_tool->selected_cps = g_list_append (npd_tool->selected_cps, cp);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_n_point_deformation_tool_remove_cp_from_selection (GimpNPointDeformationTool *npd_tool,
+ NPDControlPoint *cp)
+{
+ npd_tool->selected_cps = g_list_remove (npd_tool->selected_cps, cp);
+}
+
+static void
+gimp_n_point_deformation_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+
+ if (display != tool->display)
+ {
+ /* this is the first click on the drawable - just start the tool */
+ gimp_n_point_deformation_tool_start (npd_tool, display);
+ }
+
+ npd_tool->selected_cp = NULL;
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ NPDControlPoint *cp = npd_tool->hovering_cp;
+
+ if (cp)
+ {
+ /* there is a control point at cursor's position */
+ npd_tool->selected_cp = cp;
+
+ if (! g_list_find (npd_tool->selected_cps, cp))
+ {
+ /* control point isn't selected, so we can add it to the
+ * list of selected control points
+ */
+
+ if (! (state & gimp_get_extend_selection_mask ()))
+ {
+ /* <SHIFT> isn't pressed, so this isn't a
+ * multiselection - clear the list of selected
+ * control points
+ */
+ gimp_n_point_deformation_tool_clear_selected_points_list (npd_tool);
+ }
+
+ gimp_n_point_deformation_tool_add_cp_to_selection (npd_tool, cp);
+ }
+ else if (state & gimp_get_extend_selection_mask ())
+ {
+ /* control point is selected and <SHIFT> is pressed -
+ * remove control point from selected points
+ */
+ gimp_n_point_deformation_tool_remove_cp_from_selection (npd_tool,
+ cp);
+ }
+ }
+
+ npd_tool->start_x = coords->x;
+ npd_tool->start_y = coords->y;
+
+ npd_tool->last_x = coords->x;
+ npd_tool->last_y = coords->y;
+ }
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static gboolean
+gimp_n_point_deformation_tool_is_cp_in_area (NPDControlPoint *cp,
+ gfloat x0,
+ gfloat y0,
+ gfloat x1,
+ gfloat y1,
+ gfloat offset_x,
+ gfloat offset_y,
+ gfloat cp_radius)
+{
+ NPDPoint p = cp->point;
+
+ p.x += offset_x;
+ p.y += offset_y;
+
+ return p.x >= x0 - cp_radius && p.x <= x1 + cp_radius &&
+ p.y >= y0 - cp_radius && p.y <= y1 + cp_radius;
+}
+
+static void
+gimp_n_point_deformation_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (npd_tool));
+
+ if (release_type == GIMP_BUTTON_RELEASE_CLICK)
+ {
+ if (! npd_tool->hovering_cp)
+ {
+ NPDPoint p;
+
+ gimp_npd_debug (("cp doesn't exist, adding\n"));
+ p.x = coords->x - npd_tool->offset_x;
+ p.y = coords->y - npd_tool->offset_y;
+
+ npd_add_control_point (npd_tool->model, &p);
+ }
+ }
+ else if (release_type == GIMP_BUTTON_RELEASE_NORMAL)
+ {
+ if (npd_tool->rubber_band)
+ {
+ GArray *cps = npd_tool->model->control_points;
+ gint x0 = MIN (npd_tool->start_x, npd_tool->cursor_x);
+ gint y0 = MIN (npd_tool->start_y, npd_tool->cursor_y);
+ gint x1 = MAX (npd_tool->start_x, npd_tool->cursor_x);
+ gint y1 = MAX (npd_tool->start_y, npd_tool->cursor_y);
+ gint i;
+
+ if (! (state & gimp_get_extend_selection_mask ()))
+ {
+ /* <SHIFT> isn't pressed, so we want a clear selection */
+ gimp_n_point_deformation_tool_clear_selected_points_list (npd_tool);
+ }
+
+ for (i = 0; i < cps->len; i++)
+ {
+ NPDControlPoint *cp = &g_array_index (cps, NPDControlPoint, i);
+
+ if (gimp_n_point_deformation_tool_is_cp_in_area (cp,
+ x0, y0,
+ x1, y1,
+ npd_tool->offset_x,
+ npd_tool->offset_y,
+ npd_tool->cp_scaled_radius))
+ {
+ /* control point is situated in an area defined by
+ * rubber band
+ */
+ gimp_n_point_deformation_tool_add_cp_to_selection (npd_tool,
+ cp);
+ gimp_npd_debug (("%p selected\n", cp));
+ }
+ }
+ }
+ }
+ else if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ gimp_npd_debug (("gimp_button_release_cancel\n"));
+ }
+
+ npd_tool->rubber_band = FALSE;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (npd_tool));
+}
+
+static void
+gimp_n_point_deformation_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (npd_tool->active)
+ {
+ NPDModel *model = npd_tool->model;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ NPDPoint p;
+
+ npd_tool->cp_scaled_radius = model->control_point_radius / shell->scale_x;
+
+ p.x = coords->x - npd_tool->offset_x;
+ p.y = coords->y - npd_tool->offset_y;
+
+ npd_tool->hovering_cp =
+ npd_get_control_point_with_radius_at (model,
+ &p,
+ npd_tool->cp_scaled_radius);
+ }
+
+ npd_tool->cursor_x = coords->x;
+ npd_tool->cursor_y = coords->y;
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+static void
+gimp_n_point_deformation_tool_prepare_lattice (GimpNPointDeformationTool *npd_tool)
+{
+ NPDHiddenModel *hm = npd_tool->model->hidden_model;
+ GimpVector2 *points = npd_tool->lattice_points;
+ gint i, j;
+
+ for (i = 0; i < hm->num_of_bones; i++)
+ {
+ NPDBone *bone = &hm->current_bones[i];
+
+ for (j = 0; j < 4; j++)
+ gimp_vector2_set (&points[5 * i + j], bone->points[j].x, bone->points[j].y);
+ gimp_vector2_set (&points[5 * i + j], bone->points[0].x, bone->points[0].y);
+ }
+}
+
+static void
+gimp_n_point_deformation_tool_draw_lattice (GimpNPointDeformationTool *npd_tool)
+{
+ GimpVector2 *points = npd_tool->lattice_points;
+ gint n_squares = npd_tool->model->hidden_model->num_of_bones;
+ gint i;
+
+ for (i = 0; i < n_squares; i++)
+ gimp_draw_tool_add_lines (GIMP_DRAW_TOOL (npd_tool),
+ &points[5 * i], 5, NULL, FALSE);
+}
+
+static void
+gimp_n_point_deformation_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpNPointDeformationTool *npd_tool;
+ GimpNPointDeformationOptions *npd_options;
+ NPDModel *model;
+ gint x0, y0, x1, y1;
+ gint i;
+
+ npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (draw_tool);
+ npd_options = GIMP_N_POINT_DEFORMATION_TOOL_GET_OPTIONS (npd_tool);
+
+ model = npd_tool->model;
+
+ g_return_if_fail (model != NULL);
+
+ /* draw lattice */
+ if (npd_options->mesh_visible)
+ gimp_n_point_deformation_tool_draw_lattice (npd_tool);
+
+ x0 = MIN (npd_tool->start_x, npd_tool->cursor_x);
+ y0 = MIN (npd_tool->start_y, npd_tool->cursor_y);
+ x1 = MAX (npd_tool->start_x, npd_tool->cursor_x);
+ y1 = MAX (npd_tool->start_y, npd_tool->cursor_y);
+
+ for (i = 0; i < model->control_points->len; i++)
+ {
+ NPDControlPoint *cp = &g_array_index (model->control_points,
+ NPDControlPoint, i);
+ NPDPoint p = cp->point;
+ GimpHandleType handle_type;
+
+ p.x += npd_tool->offset_x;
+ p.y += npd_tool->offset_y;
+
+ handle_type = GIMP_HANDLE_CIRCLE;
+
+ /* check if cursor is hovering over a control point or if a
+ * control point is situated in an area defined by rubber band
+ */
+ if (cp == npd_tool->hovering_cp ||
+ (npd_tool->rubber_band &&
+ gimp_n_point_deformation_tool_is_cp_in_area (cp,
+ x0, y0,
+ x1, y1,
+ npd_tool->offset_x,
+ npd_tool->offset_y,
+ npd_tool->cp_scaled_radius)))
+ {
+ handle_type = GIMP_HANDLE_FILLED_CIRCLE;
+ }
+
+ gimp_draw_tool_add_handle (draw_tool,
+ handle_type,
+ p.x, p.y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ if (g_list_find (npd_tool->selected_cps, cp))
+ {
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_SQUARE,
+ p.x, p.y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+ }
+
+ if (npd_tool->rubber_band)
+ {
+ /* draw a rubber band */
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ x0, y0, x1 - x0, y1 - y0);
+ }
+
+ if (npd_tool->preview_buffer)
+ {
+ GimpCanvasItem *item;
+
+ item = gimp_canvas_buffer_preview_new (gimp_display_get_shell (draw_tool->display),
+ npd_tool->preview_buffer);
+
+ gimp_draw_tool_add_preview (draw_tool, item);
+ g_object_unref (item);
+ }
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+}
+
+static void
+gimp_n_point_deformation_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpNPointDeformationTool *npd_tool = GIMP_N_POINT_DEFORMATION_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (npd_tool->selected_cp)
+ {
+ GList *list;
+ gdouble shift_x = coords->x - npd_tool->last_x;
+ gdouble shift_y = coords->y - npd_tool->last_y;
+
+ for (list = npd_tool->selected_cps; list; list = g_list_next (list))
+ {
+ NPDControlPoint *cp = list->data;
+
+ cp->point.x += shift_x;
+ cp->point.y += shift_y;
+ }
+ }
+ else
+ {
+ /* activate a rubber band selection */
+ npd_tool->rubber_band = TRUE;
+ }
+
+ npd_tool->cursor_x = coords->x;
+ npd_tool->cursor_y = coords->y;
+
+ npd_tool->last_x = coords->x;
+ npd_tool->last_y = coords->y;
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+static gboolean
+gimp_n_point_deformation_tool_canvas_update_timeout (GimpNPointDeformationTool *npd_tool)
+{
+ if (! GIMP_TOOL (npd_tool)->drawable)
+ return FALSE;
+
+ gimp_npd_debug (("canvas update thread\n"));
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL(npd_tool));
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL(npd_tool));
+
+ gimp_npd_debug (("canvas update thread stop\n"));
+
+ return TRUE;
+}
+
+static gpointer
+gimp_n_point_deformation_tool_deform_thread_func (gpointer data)
+{
+ GimpNPointDeformationTool *npd_tool = data;
+ GimpNPointDeformationOptions *npd_options;
+ guint64 start, duration;
+
+ npd_options = GIMP_N_POINT_DEFORMATION_TOOL_GET_OPTIONS (npd_tool);
+
+ npd_tool->deformation_active = TRUE;
+
+ while (npd_tool->deformation_active)
+ {
+ start = g_get_monotonic_time ();
+
+ gimp_n_point_deformation_tool_perform_deformation (npd_tool);
+
+ if (npd_options->mesh_visible)
+ gimp_n_point_deformation_tool_prepare_lattice (npd_tool);
+
+ duration = g_get_monotonic_time () - start;
+ if (duration < GIMP_NPD_MAXIMUM_DEFORMATION_DELAY)
+ {
+ g_usleep (GIMP_NPD_MAXIMUM_DEFORMATION_DELAY - duration);
+ }
+ }
+
+ gimp_npd_debug (("deform thread exit\n"));
+
+ return NULL;
+}
+
+static void
+gimp_n_point_deformation_tool_perform_deformation (GimpNPointDeformationTool *npd_tool)
+{
+ GObject *operation;
+
+ g_object_get (npd_tool->npd_node,
+ "gegl-operation", &operation,
+ NULL);
+ gimp_npd_debug (("gegl_operation_invalidate\n"));
+ gegl_operation_invalidate (GEGL_OPERATION (operation), NULL, FALSE);
+ g_object_unref (operation);
+
+ gimp_npd_debug (("gegl_node_process\n"));
+ gegl_node_process (npd_tool->sink);
+}
+
+static void
+gimp_n_point_deformation_tool_halt_threads (GimpNPointDeformationTool *npd_tool)
+{
+ if (! npd_tool->deformation_active)
+ return;
+
+ gimp_npd_debug (("waiting for deform thread to finish\n"));
+ npd_tool->deformation_active = FALSE;
+
+ /* wait for deformation thread to finish its work */
+ if (npd_tool->deform_thread)
+ {
+ g_thread_join (npd_tool->deform_thread);
+ npd_tool->deform_thread = NULL;
+ }
+
+ /* remove canvas update timeout */
+ if (npd_tool->draw_timeout_id)
+ {
+ g_source_remove (npd_tool->draw_timeout_id);
+ npd_tool->draw_timeout_id = 0;
+ }
+
+ gimp_npd_debug (("finished\n"));
+}
+
+static void
+gimp_n_point_deformation_tool_apply_deformation (GimpNPointDeformationTool *npd_tool)
+{
+ GimpTool *tool = GIMP_TOOL (npd_tool);
+ GimpNPointDeformationOptions *npd_options;
+ GeglBuffer *buffer;
+ GimpImage *image;
+ gint width, height, prev;
+
+ npd_options = GIMP_N_POINT_DEFORMATION_TOOL_GET_OPTIONS (npd_tool);
+
+ image = gimp_display_get_image (tool->display);
+ buffer = gimp_drawable_get_buffer (tool->drawable);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ prev = npd_options->rigidity;
+ npd_options->rigidity = 0;
+ gimp_n_point_deformation_tool_set_options (npd_tool, npd_options);
+ npd_options->rigidity = prev;
+
+ gimp_drawable_push_undo (tool->drawable, _("N-Point Deformation"), NULL,
+ 0, 0, width, height);
+
+
+ gimp_gegl_apply_operation (NULL, NULL, _("N-Point Deformation"),
+ npd_tool->npd_node,
+ gimp_drawable_get_buffer (tool->drawable),
+ NULL, FALSE);
+
+ gimp_drawable_update (tool->drawable,
+ 0, 0, width, height);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+#undef gimp_npd_debug
+#ifdef GIMP_NPD_DEBUG
+#undef GIMP_NPD_DEBUG
+#endif
diff --git a/app/tools/gimpnpointdeformationtool.h b/app/tools/gimpnpointdeformationtool.h
new file mode 100644
index 0000000..5f208ba
--- /dev/null
+++ b/app/tools/gimpnpointdeformationtool.h
@@ -0,0 +1,95 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpnpointdeformationtool.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_N_POINT_DEFORMATION_TOOL_H__
+#define __GIMP_N_POINT_DEFORMATION_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+#include "libgimpmath/gimpmath.h"
+#include <npd/npd_common.h>
+
+
+#define GIMP_TYPE_N_POINT_DEFORMATION_TOOL (gimp_n_point_deformation_tool_get_type ())
+#define GIMP_N_POINT_DEFORMATION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_N_POINT_DEFORMATION_TOOL, GimpNPointDeformationTool))
+#define GIMP_N_POINT_DEFORMATION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_N_POINT_DEFORMATION_TOOL, GimpNPointDeformationToolClass))
+#define GIMP_IS_N_POINT_DEFORMATION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_N_POINT_DEFORMATION_TOOL))
+#define GIMP_IS_N_POINT_DEFORMATION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_N_POINT_DEFORMATION_TOOL))
+#define GIMP_N_POINT_DEFORMATION_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_N_POINT_DEFORMATION_TOOL, GimpNPointDeformationToolClass))
+
+#define GIMP_N_POINT_DEFORMATION_TOOL_GET_OPTIONS(t) (GIMP_N_POINT_DEFORMATION_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpNPointDeformationTool GimpNPointDeformationTool;
+typedef struct _GimpNPointDeformationToolClass GimpNPointDeformationToolClass;
+
+struct _GimpNPointDeformationTool
+{
+ GimpDrawTool parent_instance;
+
+ guint draw_timeout_id;
+ GThread *deform_thread;
+
+ GeglNode *graph;
+ GeglNode *source;
+ GeglNode *npd_node;
+ GeglNode *sink;
+
+ GeglBuffer *preview_buffer;
+
+ NPDModel *model;
+ NPDControlPoint *selected_cp; /* last selected control point */
+ GList *selected_cps; /* list of selected control points */
+ NPDControlPoint *hovering_cp;
+
+ GimpVector2 *lattice_points;
+
+ gdouble start_x;
+ gdouble start_y;
+
+ gdouble last_x;
+ gdouble last_y;
+
+ gdouble cursor_x;
+ gdouble cursor_y;
+
+ gint offset_x;
+ gint offset_y;
+
+ gfloat cp_scaled_radius; /* radius of a control point scaled
+ * according to display shell's scale
+ */
+
+ gboolean active;
+ volatile gboolean deformation_active;
+ gboolean rubber_band;
+};
+
+struct _GimpNPointDeformationToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+void gimp_n_point_deformation_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_n_point_deformation_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_N_POINT_DEFORMATION_TOOL_H__ */
diff --git a/app/tools/gimpoffsettool.c b/app/tools/gimpoffsettool.c
new file mode 100644
index 0000000..8a17fd3
--- /dev/null
+++ b/app/tools/gimpoffsettool.c
@@ -0,0 +1,787 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolgui.h"
+
+#include "gimpoffsettool.h"
+#include "gimpfilteroptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_offset_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_offset_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_offset_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_offset_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_offset_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_offset_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_offset_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static gchar * gimp_offset_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description);
+static void gimp_offset_tool_dialog (GimpFilterTool *filter_tool);
+static void gimp_offset_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec);
+static void gimp_offset_tool_region_changed (GimpFilterTool *filter_tool);
+
+static void gimp_offset_tool_offset_changed (GimpSizeEntry *se,
+ GimpOffsetTool *offset_tool);
+
+static void gimp_offset_tool_half_xy_clicked (GtkButton *button,
+ GimpOffsetTool *offset_tool);
+static void gimp_offset_tool_half_x_clicked (GtkButton *button,
+ GimpOffsetTool *offset_tool);
+static void gimp_offset_tool_half_y_clicked (GtkButton *button,
+ GimpOffsetTool *offset_tool);
+
+static void gimp_offset_tool_edge_behavior_toggled (GtkToggleButton *toggle,
+ GimpOffsetTool *offset_tool);
+
+static void gimp_offset_tool_background_changed (GimpContext *context,
+ const GimpRGB *color,
+ GimpOffsetTool *offset_tool);
+
+static gint gimp_offset_tool_get_width (GimpOffsetTool *offset_tool);
+static gint gimp_offset_tool_get_height (GimpOffsetTool *offset_tool);
+
+static void gimp_offset_tool_update (GimpOffsetTool *offset_tool);
+
+static void gimp_offset_tool_halt (GimpOffsetTool *offset_tool);
+
+
+G_DEFINE_TYPE (GimpOffsetTool, gimp_offset_tool,
+ GIMP_TYPE_FILTER_TOOL)
+
+#define parent_class gimp_offset_tool_parent_class
+
+
+void
+gimp_offset_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_OFFSET_TOOL,
+ GIMP_TYPE_FILTER_OPTIONS, NULL,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-offset-tool",
+ _("Offset"),
+ _("Shift the pixels, optionally wrapping them at the borders"),
+ N_("_Offset..."), NULL,
+ NULL, GIMP_HELP_TOOL_OFFSET,
+ GIMP_ICON_TOOL_OFFSET,
+ data);
+}
+
+static void
+gimp_offset_tool_class_init (GimpOffsetToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ tool_class->initialize = gimp_offset_tool_initialize;
+ tool_class->control = gimp_offset_tool_control;
+ tool_class->button_press = gimp_offset_tool_button_press;
+ tool_class->button_release = gimp_offset_tool_button_release;
+ tool_class->motion = gimp_offset_tool_motion;
+ tool_class->oper_update = gimp_offset_tool_oper_update;
+ tool_class->cursor_update = gimp_offset_tool_cursor_update;
+
+ filter_tool_class->get_operation = gimp_offset_tool_get_operation;
+ filter_tool_class->dialog = gimp_offset_tool_dialog;
+ filter_tool_class->config_notify = gimp_offset_tool_config_notify;
+ filter_tool_class->region_changed = gimp_offset_tool_region_changed;
+}
+
+static void
+gimp_offset_tool_init (GimpOffsetTool *offset_tool)
+{
+ GimpTool *tool = GIMP_TOOL (offset_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+}
+
+static gboolean
+gimp_offset_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpOffsetTool *offset_tool = GIMP_OFFSET_TOOL (tool);
+ GimpContext *context = GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (tool));
+ GimpImage *image;
+ gdouble xres;
+ gdouble yres;
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ return FALSE;
+
+ image = gimp_item_get_image (GIMP_ITEM (tool->drawable));
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_signal_handlers_block_by_func (offset_tool->offset_se,
+ gimp_offset_tool_offset_changed,
+ offset_tool);
+
+ gimp_size_entry_set_resolution (
+ GIMP_SIZE_ENTRY (offset_tool->offset_se), 0,
+ xres, FALSE);
+ gimp_size_entry_set_resolution (
+ GIMP_SIZE_ENTRY (offset_tool->offset_se), 1,
+ yres, FALSE);
+
+ if (GIMP_IS_LAYER (tool->drawable))
+ gimp_tool_gui_set_description (filter_tool->gui, _("Offset Layer"));
+ else if (GIMP_IS_LAYER_MASK (tool->drawable))
+ gimp_tool_gui_set_description (filter_tool->gui, _("Offset Layer Mask"));
+ else if (GIMP_IS_CHANNEL (tool->drawable))
+ gimp_tool_gui_set_description (filter_tool->gui, _("Offset Channel"));
+ else
+ g_warning ("%s: unexpected drawable type", G_STRFUNC);
+
+ gtk_widget_set_sensitive (offset_tool->transparent_radio,
+ gimp_drawable_has_alpha (tool->drawable));
+
+ g_signal_handlers_unblock_by_func (offset_tool->offset_se,
+ gimp_offset_tool_offset_changed,
+ offset_tool);
+
+ gegl_node_set (
+ filter_tool->operation,
+ "context", context,
+ NULL);
+
+ g_signal_connect (context, "background-changed",
+ G_CALLBACK (gimp_offset_tool_background_changed),
+ offset_tool);
+
+ gimp_offset_tool_update (offset_tool);
+
+ return TRUE;
+}
+
+static void
+gimp_offset_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpOffsetTool *offset_tool = GIMP_OFFSET_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_offset_tool_halt (offset_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static gchar *
+gimp_offset_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description)
+{
+ return g_strdup ("gimp:offset");
+}
+
+static void
+gimp_offset_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpOffsetTool *offset_tool = GIMP_OFFSET_TOOL (tool);
+
+ offset_tool->dragging = ! gimp_filter_tool_on_guide (GIMP_FILTER_TOOL (tool),
+ coords, display);
+
+ if (! offset_tool->dragging)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ }
+ else
+ {
+ offset_tool->x = coords->x;
+ offset_tool->y = coords->y;
+
+ g_object_get (GIMP_FILTER_TOOL (tool)->config,
+ "x", &offset_tool->offset_x,
+ "y", &offset_tool->offset_y,
+ NULL);
+
+ tool->display = display;
+
+ gimp_tool_control_activate (tool->control);
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_push_status_coords (tool, display,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER,
+ _("Offset: "),
+ 0,
+ ", ",
+ 0,
+ NULL);
+ }
+}
+
+static void
+gimp_offset_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpOffsetTool *offset_tool = GIMP_OFFSET_TOOL (tool);
+
+ if (! offset_tool->dragging)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+ }
+ else
+ {
+ gimp_tool_control_halt (tool->control);
+
+ offset_tool->dragging = FALSE;
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ g_object_set (GIMP_FILTER_TOOL (tool)->config,
+ "x", offset_tool->offset_x,
+ "y", offset_tool->offset_y,
+ NULL);
+ }
+ }
+}
+
+static void
+gimp_offset_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpOffsetTool *offset_tool = GIMP_OFFSET_TOOL (tool);
+
+ if (! offset_tool->dragging)
+ {
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
+ display);
+ }
+ else
+ {
+ GimpOffsetType type;
+ gint offset_x;
+ gint offset_y;
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+
+ g_object_get (filter_tool->config,
+ "type", &type,
+ NULL);
+
+ offset_x = RINT (coords->x - offset_tool->x);
+ offset_y = RINT (coords->y - offset_tool->y);
+
+ x = offset_tool->offset_x + offset_x;
+ y = offset_tool->offset_y + offset_y;
+
+ width = gimp_offset_tool_get_width (offset_tool);
+ height = gimp_offset_tool_get_height (offset_tool);
+
+ if (type == GIMP_OFFSET_WRAP_AROUND)
+ {
+ x %= MAX (width, 1);
+ y %= MAX (height, 1);
+ }
+ else
+ {
+ x = CLAMP (x, -width, +width);
+ y = CLAMP (y, -height, +height);
+ }
+
+ g_object_set (filter_tool->config,
+ "x", x,
+ "y", y,
+ NULL);
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_push_status_coords (tool, display,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER,
+ _("Offset: "),
+ offset_x,
+ ", ",
+ offset_y,
+ NULL);
+ }
+}
+
+static void
+gimp_offset_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ if (! tool->drawable ||
+ gimp_filter_tool_on_guide (GIMP_FILTER_TOOL (tool),
+ coords, display))
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+ }
+ else
+ {
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_push_status (tool, display, "%s",
+ _("Click-Drag to offset drawable"));
+ }
+}
+
+static void
+gimp_offset_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ if (! tool->drawable ||
+ gimp_filter_tool_on_guide (GIMP_FILTER_TOOL (tool),
+ coords, display))
+ {
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+ }
+ else
+ {
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_MOVE,
+ GIMP_CURSOR_MODIFIER_NONE);
+ }
+}
+
+static void
+gimp_offset_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpOffsetTool *offset_tool = GIMP_OFFSET_TOOL (filter_tool);
+ GtkWidget *main_vbox;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *spinbutton;
+ GtkWidget *frame;
+ GtkAdjustment *adjustment;
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ /* The offset frame */
+ frame = gimp_frame_new (_("Offset"));
+ gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ adjustment = (GtkAdjustment *)
+ gtk_adjustment_new (1, 1, 1, 1, 10, 0);
+ spinbutton = gimp_spin_button_new (adjustment, 1.0, 2);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10);
+
+ offset_tool->offset_se = gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a",
+ TRUE, TRUE, FALSE, 10,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+
+ gtk_table_set_col_spacing (GTK_TABLE (offset_tool->offset_se), 0, 4);
+ gtk_table_set_col_spacing (GTK_TABLE (offset_tool->offset_se), 1, 4);
+ gtk_table_set_row_spacing (GTK_TABLE (offset_tool->offset_se), 0, 2);
+
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (offset_tool->offset_se),
+ GTK_SPIN_BUTTON (spinbutton), NULL);
+ gtk_table_attach_defaults (GTK_TABLE (offset_tool->offset_se), spinbutton,
+ 1, 2, 0, 1);
+ gtk_widget_show (spinbutton);
+
+ gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (offset_tool->offset_se),
+ _("_X:"), 0, 0, 0.0);
+ gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (offset_tool->offset_se),
+ _("_Y:"), 1, 0, 0.0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), offset_tool->offset_se, FALSE, FALSE, 0);
+ gtk_widget_show (offset_tool->offset_se);
+
+ gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (offset_tool->offset_se),
+ GIMP_UNIT_PIXEL);
+
+ g_signal_connect (offset_tool->offset_se, "refval-changed",
+ G_CALLBACK (gimp_offset_tool_offset_changed),
+ offset_tool);
+ g_signal_connect (offset_tool->offset_se, "value-changed",
+ G_CALLBACK (gimp_offset_tool_offset_changed),
+ offset_tool);
+
+ button = gtk_button_new_with_mnemonic (_("By width/_2, height/2"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_offset_tool_half_xy_clicked),
+ offset_tool);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ button = gtk_button_new_with_mnemonic (_("By _width/2"));
+ gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_offset_tool_half_x_clicked),
+ offset_tool);
+
+ button = gtk_button_new_with_mnemonic (_("By _height/2"));
+ gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_offset_tool_half_y_clicked),
+ offset_tool);
+
+ /* The edge behavior frame */
+ frame = gimp_int_radio_group_new (TRUE, _("Edge Behavior"),
+
+ G_CALLBACK (gimp_offset_tool_edge_behavior_toggled),
+ offset_tool,
+
+ GIMP_OFFSET_WRAP_AROUND,
+
+ _("W_rap around"),
+ GIMP_OFFSET_WRAP_AROUND, NULL,
+
+ _("Fill with _background color"),
+ GIMP_OFFSET_BACKGROUND, NULL,
+
+ _("Make _transparent"),
+ GIMP_OFFSET_TRANSPARENT,
+ &offset_tool->transparent_radio,
+ NULL);
+
+ gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+}
+
+static void
+gimp_offset_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec)
+{
+ gimp_offset_tool_update (GIMP_OFFSET_TOOL (filter_tool));
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->config_notify (filter_tool,
+ config, pspec);
+}
+
+static void
+gimp_offset_tool_region_changed (GimpFilterTool *filter_tool)
+{
+ gimp_offset_tool_update (GIMP_OFFSET_TOOL (filter_tool));
+}
+
+static void
+gimp_offset_tool_offset_changed (GimpSizeEntry *se,
+ GimpOffsetTool *offset_tool)
+{
+ g_object_set (GIMP_FILTER_TOOL (offset_tool)->config,
+ "x", (gint) gimp_size_entry_get_refval (se, 0),
+ "y", (gint) gimp_size_entry_get_refval (se, 1),
+ NULL);
+}
+
+static void
+gimp_offset_tool_half_xy_clicked (GtkButton *button,
+ GimpOffsetTool *offset_tool)
+{
+ g_object_set (GIMP_FILTER_TOOL (offset_tool)->config,
+ "x", gimp_offset_tool_get_width (offset_tool) / 2,
+ "y", gimp_offset_tool_get_height (offset_tool) / 2,
+ NULL);
+}
+
+static void
+gimp_offset_tool_half_x_clicked (GtkButton *button,
+ GimpOffsetTool *offset_tool)
+{
+ g_object_set (GIMP_FILTER_TOOL (offset_tool)->config,
+ "x", gimp_offset_tool_get_width (offset_tool) / 2,
+ NULL);
+}
+
+static void
+gimp_offset_tool_half_y_clicked (GtkButton *button,
+ GimpOffsetTool *offset_tool)
+{
+ g_object_set (GIMP_FILTER_TOOL (offset_tool)->config,
+ "y", gimp_offset_tool_get_height (offset_tool) / 2,
+ NULL);
+}
+
+static void
+gimp_offset_tool_edge_behavior_toggled (GtkToggleButton *toggle,
+ GimpOffsetTool *offset_tool)
+{
+ if (gtk_toggle_button_get_active (toggle))
+ {
+ GimpOffsetType type;
+
+ type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toggle),
+ "gimp-item-data"));
+
+ g_object_set (GIMP_FILTER_TOOL (offset_tool)->config,
+ "type", type,
+ NULL);
+ }
+}
+
+static void
+gimp_offset_tool_background_changed (GimpContext *context,
+ const GimpRGB *color,
+ GimpOffsetTool *offset_tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (offset_tool);
+ GimpOffsetType type;
+
+ g_object_get (filter_tool->config,
+ "type", &type,
+ NULL);
+
+ if (type == GIMP_OFFSET_BACKGROUND)
+ {
+ gegl_node_set (filter_tool->operation,
+ "context", context,
+ NULL);
+
+ gimp_drawable_filter_apply (filter_tool->filter, NULL);
+ }
+}
+
+static gint
+gimp_offset_tool_get_width (GimpOffsetTool *offset_tool)
+{
+ GeglRectangle drawable_area;
+ gint drawable_offset_x;
+ gint drawable_offset_y;
+
+ if (gimp_filter_tool_get_drawable_area (GIMP_FILTER_TOOL (offset_tool),
+ &drawable_offset_x,
+ &drawable_offset_y,
+ &drawable_area) &&
+ ! gegl_rectangle_is_empty (&drawable_area))
+ {
+ return drawable_area.width;
+ }
+
+ return 0;
+}
+
+static gint
+gimp_offset_tool_get_height (GimpOffsetTool *offset_tool)
+{
+ GeglRectangle drawable_area;
+ gint drawable_offset_x;
+ gint drawable_offset_y;
+
+ if (gimp_filter_tool_get_drawable_area (GIMP_FILTER_TOOL (offset_tool),
+ &drawable_offset_x,
+ &drawable_offset_y,
+ &drawable_area) &&
+ ! gegl_rectangle_is_empty (&drawable_area))
+ {
+ return drawable_area.height;
+ }
+
+ return 0;
+}
+
+static void
+gimp_offset_tool_update (GimpOffsetTool *offset_tool)
+{
+ GimpTool *tool = GIMP_TOOL (offset_tool);
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (offset_tool);
+ GimpOffsetType orig_type;
+ gint orig_x;
+ gint orig_y;
+ GimpOffsetType type;
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+
+ g_object_get (filter_tool->config,
+ "type", &orig_type,
+ "x", &orig_x,
+ "y", &orig_y,
+ NULL);
+
+ width = gimp_offset_tool_get_width (offset_tool);
+ height = gimp_offset_tool_get_height (offset_tool);
+
+ x = CLAMP (orig_x, -width, +width);
+ y = CLAMP (orig_y, -height, +height);
+
+ type = orig_type;
+
+ if (tool->drawable &&
+ ! gimp_drawable_has_alpha (tool->drawable) &&
+ type == GIMP_OFFSET_TRANSPARENT)
+ {
+ type = GIMP_OFFSET_BACKGROUND;
+ }
+
+ if (x != orig_x ||
+ y != orig_y ||
+ type != orig_type)
+ {
+ g_object_set (filter_tool->config,
+ "type", type,
+ "x", x,
+ "y", y,
+ NULL);
+ }
+
+ if (offset_tool->offset_se)
+ {
+ gint width = gimp_offset_tool_get_width (offset_tool);
+ gint height = gimp_offset_tool_get_height (offset_tool);
+
+ g_signal_handlers_block_by_func (offset_tool->offset_se,
+ gimp_offset_tool_offset_changed,
+ offset_tool);
+
+ gimp_size_entry_set_refval_boundaries (
+ GIMP_SIZE_ENTRY (offset_tool->offset_se), 0,
+ -width, +width);
+ gimp_size_entry_set_refval_boundaries (
+ GIMP_SIZE_ENTRY (offset_tool->offset_se), 1,
+ -height, +height);
+
+ gimp_size_entry_set_size (
+ GIMP_SIZE_ENTRY (offset_tool->offset_se), 0,
+ 0, width);
+ gimp_size_entry_set_size (
+ GIMP_SIZE_ENTRY (offset_tool->offset_se), 1,
+ 0, height);
+
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset_tool->offset_se), 0,
+ x);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset_tool->offset_se), 1,
+ y);
+
+ g_signal_handlers_unblock_by_func (offset_tool->offset_se,
+ gimp_offset_tool_offset_changed,
+ offset_tool);
+ }
+
+ if (offset_tool->transparent_radio)
+ {
+ gimp_int_radio_group_set_active (
+ GTK_RADIO_BUTTON (offset_tool->transparent_radio),
+ type);
+ }
+}
+
+static void
+gimp_offset_tool_halt (GimpOffsetTool *offset_tool)
+{
+ GimpContext *context = GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (offset_tool));
+
+ offset_tool->offset_se = NULL;
+ offset_tool->transparent_radio = NULL;
+
+ g_signal_handlers_disconnect_by_func (
+ context,
+ gimp_offset_tool_background_changed,
+ offset_tool);
+}
diff --git a/app/tools/gimpoffsettool.h b/app/tools/gimpoffsettool.h
new file mode 100644
index 0000000..ff651de
--- /dev/null
+++ b/app/tools/gimpoffsettool.h
@@ -0,0 +1,63 @@
+/* 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_OFFSET_TOOL_H__
+#define __GIMP_OFFSET_TOOL_H__
+
+
+#include "gimpfiltertool.h"
+
+
+#define GIMP_TYPE_OFFSET_TOOL (gimp_offset_tool_get_type ())
+#define GIMP_OFFSET_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OFFSET_TOOL, GimpOffsetTool))
+#define GIMP_OFFSET_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OFFSET_TOOL, GimpOffsetToolClass))
+#define GIMP_IS_OFFSET_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OFFSET_TOOL))
+#define GIMP_IS_OFFSET_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OFFSET_TOOL))
+#define GIMP_OFFSET_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OFFSET_TOOL, GimpOffsetToolClass))
+
+
+typedef struct _GimpOffsetTool GimpOffsetTool;
+typedef struct _GimpOffsetToolClass GimpOffsetToolClass;
+
+struct _GimpOffsetTool
+{
+ GimpFilterTool parent_instance;
+
+ gboolean dragging;
+ gdouble x;
+ gdouble y;
+ gint offset_x;
+ gint offset_y;
+
+ /* dialog */
+ GtkWidget *offset_se;
+ GtkWidget *transparent_radio;
+};
+
+struct _GimpOffsetToolClass
+{
+ GimpFilterToolClass parent_class;
+};
+
+
+void gimp_offset_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_offset_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_OFFSET_TOOL_H__ */
diff --git a/app/tools/gimpoperationtool.c b/app/tools/gimpoperationtool.c
new file mode 100644
index 0000000..73b9a98
--- /dev/null
+++ b/app/tools/gimpoperationtool.c
@@ -0,0 +1,914 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpdrawable.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimplist.h"
+#include "core/gimpparamspecs-duplicate.h"
+#include "core/gimppickable.h"
+#include "core/gimpsettings.h"
+
+#include "widgets/gimpbuffersourcebox.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppickablebutton.h"
+
+#include "propgui/gimppropgui.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolgui.h"
+
+#include "gimpfilteroptions.h"
+#include "gimpoperationtool.h"
+
+#include "gimp-intl.h"
+
+
+typedef struct _AuxInput AuxInput;
+
+struct _AuxInput
+{
+ GimpOperationTool *tool;
+ gchar *pad;
+ GeglNode *node;
+ GtkWidget *box;
+};
+
+
+/* local function prototypes */
+
+static void gimp_operation_tool_finalize (GObject *object);
+
+static gboolean gimp_operation_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_operation_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+
+static gchar * gimp_operation_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description);
+static void gimp_operation_tool_dialog (GimpFilterTool *filter_tool);
+static void gimp_operation_tool_reset (GimpFilterTool *filter_tool);
+static void gimp_operation_tool_set_config (GimpFilterTool *filter_tool,
+ GimpConfig *config);
+static void gimp_operation_tool_region_changed (GimpFilterTool *filter_tool);
+static void gimp_operation_tool_color_picked (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color);
+
+static void gimp_operation_tool_options_box_size_allocate (GtkWidget *options_box,
+ GdkRectangle *allocation,
+ GimpOperationTool *tool);
+
+static void gimp_operation_tool_halt (GimpOperationTool *op_tool);
+static void gimp_operation_tool_commit (GimpOperationTool *op_tool);
+
+static void gimp_operation_tool_sync_op (GimpOperationTool *op_tool,
+ gboolean sync_colors);
+static void gimp_operation_tool_create_gui (GimpOperationTool *tool);
+static void gimp_operation_tool_add_gui (GimpOperationTool *tool);
+
+static AuxInput * gimp_operation_tool_aux_input_new (GimpOperationTool *tool,
+ GeglNode *operation,
+ const gchar *input_pad,
+ const gchar *label);
+static void gimp_operation_tool_aux_input_detach (AuxInput *input);
+static void gimp_operation_tool_aux_input_clear (AuxInput *input);
+static void gimp_operation_tool_aux_input_free (AuxInput *input);
+
+static void gimp_operation_tool_unlink_chains (GimpOperationTool *op_tool);
+static void gimp_operation_tool_relink_chains (GimpOperationTool *op_tool);
+
+
+G_DEFINE_TYPE (GimpOperationTool, gimp_operation_tool,
+ GIMP_TYPE_FILTER_TOOL)
+
+#define parent_class gimp_operation_tool_parent_class
+
+
+void
+gimp_operation_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_OPERATION_TOOL,
+ GIMP_TYPE_FILTER_OPTIONS,
+ gimp_color_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-operation-tool",
+ _("GEGL Operation"),
+ _("Operation Tool: Use an arbitrary GEGL operation"),
+ NULL, NULL,
+ NULL, GIMP_HELP_TOOL_GEGL,
+ GIMP_ICON_GEGL,
+ data);
+}
+
+static void
+gimp_operation_tool_class_init (GimpOperationToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_operation_tool_finalize;
+
+ tool_class->initialize = gimp_operation_tool_initialize;
+ tool_class->control = gimp_operation_tool_control;
+
+ filter_tool_class->get_operation = gimp_operation_tool_get_operation;
+ filter_tool_class->dialog = gimp_operation_tool_dialog;
+ filter_tool_class->reset = gimp_operation_tool_reset;
+ filter_tool_class->set_config = gimp_operation_tool_set_config;
+ filter_tool_class->region_changed = gimp_operation_tool_region_changed;
+ filter_tool_class->color_picked = gimp_operation_tool_color_picked;
+}
+
+static void
+gimp_operation_tool_init (GimpOperationTool *op_tool)
+{
+}
+
+static void
+gimp_operation_tool_finalize (GObject *object)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (object);
+
+ g_clear_pointer (&op_tool->operation, g_free);
+ g_clear_pointer (&op_tool->description, g_free);
+
+ g_list_free_full (op_tool->aux_inputs,
+ (GDestroyNotify) gimp_operation_tool_aux_input_free);
+ op_tool->aux_inputs = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_operation_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ if (GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (tool);
+
+ if (filter_tool->config)
+ {
+ GtkWidget *options_gui;
+
+ gimp_operation_tool_sync_op (op_tool, TRUE);
+ options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
+ if (! options_gui)
+ {
+ gimp_operation_tool_create_gui (op_tool);
+ gimp_operation_tool_add_gui (op_tool);
+ }
+ else
+ {
+ g_object_unref (options_gui);
+ }
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_operation_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_operation_tool_halt (op_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_operation_tool_commit (op_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static gchar *
+gimp_operation_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);
+
+ *description = g_strdup (op_tool->description);
+
+ return g_strdup (op_tool->operation);
+}
+
+static void
+gimp_operation_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);
+ GtkWidget *main_vbox;
+ GtkWidget *options_sw;
+ GtkWidget *options_gui;
+ GtkWidget *options_box;
+ GtkWidget *viewport;
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ /* The options scrolled window */
+ options_sw = gtk_scrolled_window_new (NULL, NULL);
+ g_weak_ref_set (&op_tool->options_sw_ref, options_sw);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (options_sw),
+ GTK_SHADOW_NONE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (options_sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start (GTK_BOX (main_vbox), options_sw,
+ TRUE, TRUE, 0);
+ gtk_widget_show (options_sw);
+
+ viewport = gtk_viewport_new (NULL, NULL);
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+ gtk_container_add (GTK_CONTAINER (options_sw), viewport);
+ gtk_widget_show (viewport);
+
+ /* The options vbox */
+ options_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ g_weak_ref_set (&op_tool->options_box_ref, options_box);
+ gtk_container_add (GTK_CONTAINER (viewport), options_box);
+ gtk_widget_show (options_box);
+
+ g_signal_connect (options_box, "size-allocate",
+ G_CALLBACK (gimp_operation_tool_options_box_size_allocate),
+ op_tool);
+
+ options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
+ if (options_gui)
+ {
+ gimp_operation_tool_add_gui (op_tool);
+ g_object_unref (options_gui);
+ }
+}
+
+static void
+gimp_operation_tool_reset (GimpFilterTool *filter_tool)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);
+
+ gimp_operation_tool_unlink_chains (op_tool);
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->reset (filter_tool);
+
+ if (filter_tool->config && GIMP_TOOL (op_tool)->drawable)
+ gimp_operation_tool_sync_op (op_tool, TRUE);
+
+ gimp_operation_tool_relink_chains (op_tool);
+}
+
+static void
+gimp_operation_tool_set_config (GimpFilterTool *filter_tool,
+ GimpConfig *config)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);
+
+ gimp_operation_tool_unlink_chains (op_tool);
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->set_config (filter_tool, config);
+
+ if (filter_tool->config && GIMP_TOOL (op_tool)->drawable)
+ gimp_operation_tool_sync_op (op_tool, FALSE);
+
+ gimp_operation_tool_relink_chains (op_tool);
+}
+
+static void
+gimp_operation_tool_region_changed (GimpFilterTool *filter_tool)
+{
+ GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);
+
+ /* when the region changes, do we want the operation's on-canvas
+ * controller to move to a new position, or the operation to
+ * change its properties to match the on-canvas controller?
+ *
+ * decided to leave the on-canvas controller where it is and
+ * pretend it has changed, so the operation is updated
+ * accordingly...
+ */
+ if (filter_tool->widget)
+ g_signal_emit_by_name (filter_tool->widget, "changed");
+
+ gimp_operation_tool_sync_op (op_tool, FALSE);
+}
+
+static void
+gimp_operation_tool_color_picked (GimpFilterTool *filter_tool,
+ gpointer identifier,
+ gdouble x,
+ gdouble y,
+ const Babl *sample_format,
+ const GimpRGB *color)
+{
+ gchar **pspecs = g_strsplit (identifier, ":", 2);
+
+ if (pspecs[1])
+ {
+ GObjectClass *object_class = G_OBJECT_GET_CLASS (filter_tool->config);
+ GParamSpec *pspec_x;
+ GParamSpec *pspec_y;
+ gint off_x, off_y;
+ GeglRectangle area;
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ x -= off_x + area.x;
+ y -= off_y + area.y;
+
+ pspec_x = g_object_class_find_property (object_class, pspecs[0]);
+ pspec_y = g_object_class_find_property (object_class, pspecs[1]);
+
+ if (pspec_x && pspec_y &&
+ G_PARAM_SPEC_TYPE (pspec_x) == G_PARAM_SPEC_TYPE (pspec_y))
+ {
+ GValue value_x = G_VALUE_INIT;
+ GValue value_y = G_VALUE_INIT;
+
+ g_value_init (&value_x, G_PARAM_SPEC_VALUE_TYPE (pspec_x));
+ g_value_init (&value_y, G_PARAM_SPEC_VALUE_TYPE (pspec_y));
+
+#define HAS_KEY(p,k,v) gimp_gegl_param_spec_has_key (p, k, v)
+
+ if (HAS_KEY (pspec_x, "unit", "relative-coordinate") &&
+ HAS_KEY (pspec_y, "unit", "relative-coordinate"))
+ {
+ x /= (gdouble) area.width;
+ y /= (gdouble) area.height;
+ }
+
+ if (G_IS_PARAM_SPEC_INT (pspec_x))
+ {
+ g_value_set_int (&value_x, x);
+ g_value_set_int (&value_y, y);
+
+ g_param_value_validate (pspec_x, &value_x);
+ g_param_value_validate (pspec_y, &value_y);
+
+ g_object_set (filter_tool->config,
+ pspecs[0], g_value_get_int (&value_x),
+ pspecs[1], g_value_get_int (&value_y),
+ NULL);
+ }
+ else if (G_IS_PARAM_SPEC_DOUBLE (pspec_x))
+ {
+ g_value_set_double (&value_x, x);
+ g_value_set_double (&value_y, y);
+
+ g_param_value_validate (pspec_x, &value_x);
+ g_param_value_validate (pspec_y, &value_y);
+
+ g_object_set (filter_tool->config,
+ pspecs[0], g_value_get_double (&value_x),
+ pspecs[1], g_value_get_double (&value_y),
+ NULL);
+ }
+ else
+ {
+ g_warning ("%s: unhandled param spec of type %s",
+ G_STRFUNC, G_PARAM_SPEC_TYPE_NAME (pspec_x));
+ }
+
+ g_value_unset (&value_x);
+ g_value_unset (&value_y);
+ }
+ }
+ else
+ {
+ g_object_set (filter_tool->config,
+ pspecs[0], color,
+ NULL);
+ }
+
+ g_strfreev (pspecs);
+}
+
+static void
+gimp_operation_tool_options_box_size_allocate (GtkWidget *options_box,
+ GdkRectangle *allocation,
+ GimpOperationTool *op_tool)
+{
+ GimpTool *tool = GIMP_TOOL (op_tool);
+ GtkWidget *shell;
+ GtkWidget *options_sw;
+ GdkRectangle workarea;
+ GtkRequisition minimum;
+ gint maximum_height;
+
+ shell = GTK_WIDGET (gimp_display_get_shell (tool->display));
+ options_sw = g_weak_ref_get (&op_tool->options_sw_ref);
+
+ g_return_if_fail (options_sw != NULL);
+
+ gdk_screen_get_monitor_workarea (gtk_widget_get_screen (shell),
+ gimp_widget_get_monitor (shell), &workarea);
+
+ maximum_height = workarea.height / 2;
+
+ gtk_widget_size_request (options_box, &minimum);
+
+ if (minimum.height > maximum_height)
+ {
+ GtkWidget *scrollbar;
+
+ minimum.height = maximum_height;
+
+ scrollbar = gtk_scrolled_window_get_vscrollbar (
+ GTK_SCROLLED_WINDOW (options_sw));
+
+ if (scrollbar)
+ {
+ GtkRequisition req;
+
+ gtk_widget_size_request (scrollbar, &req);
+
+ minimum.width += req.width;
+ }
+ }
+
+ gtk_widget_set_size_request (options_sw, minimum.width, minimum.height);
+
+ g_object_unref (options_sw);
+}
+
+static void
+gimp_operation_tool_halt (GimpOperationTool *op_tool)
+{
+ /* don't reset op_tool->operation and op_tool->description so the
+ * tool can be properly restarted by clicking on an image
+ */
+
+ g_list_free_full (op_tool->aux_inputs,
+ (GDestroyNotify) gimp_operation_tool_aux_input_free);
+ op_tool->aux_inputs = NULL;
+}
+
+static void
+gimp_operation_tool_commit (GimpOperationTool *op_tool)
+{
+ /* remove the aux input boxes from the dialog, so they don't get
+ * destroyed when the parent class runs its commit()
+ */
+
+ g_list_foreach (op_tool->aux_inputs,
+ (GFunc) gimp_operation_tool_aux_input_detach, NULL);
+}
+
+static void
+gimp_operation_tool_sync_op (GimpOperationTool *op_tool,
+ gboolean sync_colors)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (op_tool);
+ GimpToolOptions *options = GIMP_TOOL_GET_OPTIONS (op_tool);
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ gint off_x, off_y;
+ GeglRectangle area;
+ gint i;
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (filter_tool->config),
+ &n_pspecs);
+
+ for (i = 0; i < n_pspecs; i++)
+ {
+ GParamSpec *pspec = pspecs[i];
+
+#define HAS_KEY(p,k,v) gimp_gegl_param_spec_has_key (p, k, v)
+
+ if (HAS_KEY (pspec, "role", "output-extent"))
+ {
+ if (HAS_KEY (pspec, "unit", "pixel-coordinate") &&
+ HAS_KEY (pspec, "axis", "x"))
+ {
+ g_object_set (filter_tool->config, pspec->name, 0, NULL);
+ }
+ else if (HAS_KEY (pspec, "unit", "pixel-coordinate") &&
+ HAS_KEY (pspec, "axis", "y"))
+ {
+ g_object_set (filter_tool->config, pspec->name, 0, NULL);
+ }
+ else if (HAS_KEY (pspec, "unit", "pixel-distance") &&
+ HAS_KEY (pspec, "axis", "x"))
+ {
+ g_object_set (filter_tool->config, pspec->name, area.width, NULL);
+ }
+ else if (HAS_KEY (pspec, "unit", "pixel-distance") &&
+ HAS_KEY (pspec, "axis", "y"))
+ {
+ g_object_set (filter_tool->config, pspec->name, area.height, NULL);
+ }
+ }
+ else if (sync_colors)
+ {
+ if (HAS_KEY (pspec, "role", "color-primary"))
+ {
+ GimpRGB color;
+
+ gimp_context_get_foreground (GIMP_CONTEXT (options), &color);
+ g_object_set (filter_tool->config, pspec->name, &color, NULL);
+ }
+ else if (sync_colors && HAS_KEY (pspec, "role", "color-secondary"))
+ {
+ GimpRGB color;
+
+ gimp_context_get_background (GIMP_CONTEXT (options), &color);
+ g_object_set (filter_tool->config, pspec->name, &color, NULL);
+ }
+ }
+ }
+
+ g_free (pspecs);
+}
+
+static void
+gimp_operation_tool_create_gui (GimpOperationTool *op_tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (op_tool);
+ GtkWidget *options_gui;
+ gint off_x, off_y;
+ GeglRectangle area;
+ gchar **input_pads;
+
+ gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);
+
+ options_gui =
+ gimp_prop_gui_new (G_OBJECT (filter_tool->config),
+ G_TYPE_FROM_INSTANCE (filter_tool->config), 0,
+ &area,
+ GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (op_tool)),
+ (GimpCreatePickerFunc) gimp_filter_tool_add_color_picker,
+ (GimpCreateControllerFunc) gimp_filter_tool_add_controller,
+ filter_tool);
+ g_weak_ref_set (&op_tool->options_gui_ref, options_gui);
+
+ input_pads = gegl_node_list_input_pads (filter_tool->operation);
+
+ if (input_pads)
+ {
+ gint i;
+
+ for (i = 0; input_pads[i]; i++)
+ {
+ AuxInput *input;
+ GRegex *regex;
+ gchar *label;
+
+ if (! strcmp (input_pads[i], "input"))
+ continue;
+
+ regex = g_regex_new ("^aux(\\d*)$", 0, 0, NULL);
+
+ g_return_if_fail (regex != NULL);
+
+ /* Translators: don't translate "Aux" */
+ label = g_regex_replace (regex,
+ input_pads[i], -1, 0,
+ _("Aux\\1 Input"),
+ 0, NULL);
+
+ input = gimp_operation_tool_aux_input_new (op_tool,
+ filter_tool->operation,
+ input_pads[i], label);
+
+ op_tool->aux_inputs = g_list_prepend (op_tool->aux_inputs, input);
+
+ g_free (label);
+
+ g_regex_unref (regex);
+ }
+
+ g_strfreev (input_pads);
+ }
+}
+
+static void
+gimp_operation_tool_add_gui (GimpOperationTool *op_tool)
+{
+ GtkSizeGroup *size_group = NULL;
+ GtkWidget *options_gui;
+ GtkWidget *options_box;
+ GList *list;
+
+ options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
+ options_box = g_weak_ref_get (&op_tool->options_box_ref);
+ g_return_if_fail (options_gui && options_box);
+
+ for (list = op_tool->aux_inputs; list; list = g_list_next (list))
+ {
+ AuxInput *input = list->data;
+ GtkWidget *toggle;
+
+ if (! size_group)
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ toggle =
+ gimp_buffer_source_box_get_toggle (GIMP_BUFFER_SOURCE_BOX (input->box));
+
+ gtk_size_group_add_widget (size_group, toggle);
+
+ gtk_box_pack_start (GTK_BOX (options_box), input->box,
+ FALSE, FALSE, 0);
+ gtk_widget_show (input->box);
+ }
+
+ if (size_group)
+ g_object_unref (size_group);
+
+ gtk_box_pack_start (GTK_BOX (options_box), options_gui, TRUE, TRUE, 0);
+ gtk_widget_show (options_gui);
+
+ g_object_unref (options_gui);
+ g_object_unref (options_box);
+}
+
+
+/* aux input utility functions */
+
+static void
+gimp_operation_tool_aux_input_notify (GimpBufferSourceBox *box,
+ const GParamSpec *pspec,
+ AuxInput *input)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (input->tool);
+
+ /* emit "notify" so GimpFilterTool will update its preview
+ *
+ * FIXME: this is a bad hack that should go away once GimpImageMap
+ * and GimpFilterTool are refactored to be more filter-ish.
+ */
+ if (filter_tool->config)
+ g_signal_emit_by_name (filter_tool->config,
+ "notify", NULL);
+}
+
+static AuxInput *
+gimp_operation_tool_aux_input_new (GimpOperationTool *op_tool,
+ GeglNode *operation,
+ const gchar *input_pad,
+ const gchar *label)
+{
+ AuxInput *input = g_slice_new (AuxInput);
+ GimpContext *context;
+
+ input->tool = op_tool;
+ input->pad = g_strdup (input_pad);
+ input->node = gegl_node_new_child (NULL,
+ "operation", "gegl:buffer-source",
+ NULL);
+
+ gegl_node_connect_to (input->node, "output",
+ operation, input_pad);
+
+ context = GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (op_tool));
+
+ input->box = gimp_buffer_source_box_new (context, input->node, label);
+
+ /* make AuxInput owner of the box */
+ g_object_ref_sink (input->box);
+
+ g_signal_connect (input->box, "notify::pickable",
+ G_CALLBACK (gimp_operation_tool_aux_input_notify),
+ input);
+ g_signal_connect (input->box, "notify::enabled",
+ G_CALLBACK (gimp_operation_tool_aux_input_notify),
+ input);
+
+ return input;
+}
+
+static void
+gimp_operation_tool_aux_input_detach (AuxInput *input)
+{
+ GtkWidget *parent = gtk_widget_get_parent (input->box);
+
+ if (parent)
+ gtk_container_remove (GTK_CONTAINER (parent), input->box);
+}
+
+static void
+gimp_operation_tool_aux_input_clear (AuxInput *input)
+{
+ gimp_operation_tool_aux_input_detach (input);
+
+ g_object_set (input->box,
+ "pickable", NULL,
+ NULL);
+}
+
+static void
+gimp_operation_tool_aux_input_free (AuxInput *input)
+{
+ gimp_operation_tool_aux_input_clear (input);
+
+ g_free (input->pad);
+ g_object_unref (input->node);
+ g_object_unref (input->box);
+
+ g_slice_free (AuxInput, input);
+}
+
+static void
+gimp_operation_tool_unlink_chains (GimpOperationTool *op_tool)
+{
+ GObject *options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
+ GList *chains;
+
+ g_return_if_fail (options_gui != NULL);
+
+ chains = g_object_get_data (options_gui, "chains");
+
+ while (chains)
+ {
+ GimpChainButton *chain = chains->data;
+ gboolean active;
+
+ active = gimp_chain_button_get_active (chain);
+
+ g_object_set_data (G_OBJECT (chain), "was-active",
+ GINT_TO_POINTER (active));
+
+ if (active)
+ {
+ gimp_chain_button_set_active (chain, FALSE);
+ }
+
+ chains = chains->next;
+ }
+
+ g_object_unref (options_gui);
+}
+
+static void
+gimp_operation_tool_relink_chains (GimpOperationTool *op_tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (op_tool);
+ GObject *options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
+ GList *chains;
+
+ g_return_if_fail (options_gui != NULL);
+
+ chains = g_object_get_data (options_gui, "chains");
+
+ while (chains)
+ {
+ GimpChainButton *chain = chains->data;
+
+ if (g_object_get_data (G_OBJECT (chain), "was-active"))
+ {
+ const gchar *name_x = g_object_get_data (chains->data, "x-property");
+ const gchar *name_y = g_object_get_data (chains->data, "y-property");
+ const gchar *names[2] = { name_x, name_y };
+ GValue values[2] = { G_VALUE_INIT, G_VALUE_INIT };
+ GValue double_x = G_VALUE_INIT;
+ GValue double_y = G_VALUE_INIT;
+
+ g_object_getv (filter_tool->config, 2, names, values);
+
+ g_value_init (&double_x, G_TYPE_DOUBLE);
+ g_value_init (&double_y, G_TYPE_DOUBLE);
+
+ if (g_value_transform (&values[0], &double_x) &&
+ g_value_transform (&values[1], &double_y) &&
+ g_value_get_double (&double_x) ==
+ g_value_get_double (&double_y))
+ {
+ gimp_chain_button_set_active (chain, TRUE);
+ }
+
+ g_value_unset (&double_x);
+ g_value_unset (&double_y);
+ g_value_unset (&values[0]);
+ g_value_unset (&values[1]);
+
+ g_object_set_data (G_OBJECT (chain), "was-active", NULL);
+ }
+
+ chains = chains->next;
+ }
+
+ g_object_unref (options_gui);
+}
+
+
+/* public functions */
+
+void
+gimp_operation_tool_set_operation (GimpOperationTool *op_tool,
+ const gchar *operation,
+ const gchar *title,
+ const gchar *description,
+ const gchar *undo_desc,
+ const gchar *icon_name,
+ const gchar *help_id)
+{
+ GimpTool *tool;
+ GimpFilterTool *filter_tool;
+ GtkWidget *options_gui;
+
+ g_return_if_fail (GIMP_IS_OPERATION_TOOL (op_tool));
+
+ tool = GIMP_TOOL (op_tool);
+ filter_tool = GIMP_FILTER_TOOL (op_tool);
+
+ g_free (op_tool->operation);
+ g_free (op_tool->description);
+
+ op_tool->operation = g_strdup (operation);
+ op_tool->description = g_strdup (description);
+
+ gimp_tool_set_label (tool, title);
+ gimp_tool_set_undo_desc (tool, undo_desc);
+ gimp_tool_set_icon_name (tool, icon_name);
+ gimp_tool_set_help_id (tool, help_id);
+
+ g_list_free_full (op_tool->aux_inputs,
+ (GDestroyNotify) gimp_operation_tool_aux_input_free);
+ op_tool->aux_inputs = NULL;
+
+ gimp_filter_tool_set_widget (filter_tool, NULL);
+
+ options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
+ if (options_gui)
+ {
+ gimp_filter_tool_disable_color_picking (filter_tool);
+ g_object_unref (options_gui);
+ gtk_widget_destroy (options_gui);
+ }
+
+ if (! operation)
+ return;
+
+ gimp_filter_tool_get_operation (filter_tool);
+
+ if (tool->drawable)
+ gimp_operation_tool_sync_op (op_tool, TRUE);
+
+ if (filter_tool->config && tool->display)
+ {
+ GtkWidget *options_box;
+
+ gimp_operation_tool_create_gui (op_tool);
+
+ options_box = g_weak_ref_get (&op_tool->options_box_ref);
+ if (options_box)
+ {
+ gimp_operation_tool_add_gui (op_tool);
+ g_object_unref (options_box);
+ }
+ }
+}
diff --git a/app/tools/gimpoperationtool.h b/app/tools/gimpoperationtool.h
new file mode 100644
index 0000000..c35c8ab
--- /dev/null
+++ b/app/tools/gimpoperationtool.h
@@ -0,0 +1,71 @@
+/* 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_OPERATION_TOOL_H__
+#define __GIMP_OPERATION_TOOL_H__
+
+
+#include "gimpfiltertool.h"
+
+
+#define GIMP_TYPE_OPERATION_TOOL (gimp_operation_tool_get_type ())
+#define GIMP_OPERATION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OPERATION_TOOL, GimpOperationTool))
+#define GIMP_OPERATION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OPERATION_TOOL, GimpOperationToolClass))
+#define GIMP_IS_OPERATION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OPERATION_TOOL))
+#define GIMP_IS_OPERATION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OPERATION_TOOL))
+#define GIMP_OPERATION_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OPERATION_TOOL, GimpOperationToolClass))
+
+
+typedef struct _GimpOperationTool GimpOperationTool;
+typedef struct _GimpOperationToolClass GimpOperationToolClass;
+
+struct _GimpOperationTool
+{
+ GimpFilterTool parent_instance;
+
+ gchar *operation;
+ gchar *description;
+
+ GList *aux_inputs;
+
+ /* dialog */
+ GWeakRef options_sw_ref;
+ GWeakRef options_box_ref;
+ GWeakRef options_gui_ref;
+};
+
+struct _GimpOperationToolClass
+{
+ GimpFilterToolClass parent_class;
+};
+
+
+void gimp_operation_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_operation_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_operation_tool_set_operation (GimpOperationTool *op_tool,
+ const gchar *operation,
+ const gchar *title,
+ const gchar *description,
+ const gchar *undo_desc,
+ const gchar *icon_name,
+ const gchar *help_id);
+
+
+#endif /* __GIMP_OPERATION_TOOL_H__ */
diff --git a/app/tools/gimppaintbrushtool.c b/app/tools/gimppaintbrushtool.c
new file mode 100644
index 0000000..a8aefe8
--- /dev/null
+++ b/app/tools/gimppaintbrushtool.c
@@ -0,0 +1,94 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "gimppaintbrushtool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_paintbrush_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable);
+
+
+G_DEFINE_TYPE (GimpPaintbrushTool, gimp_paintbrush_tool, GIMP_TYPE_BRUSH_TOOL)
+
+
+void
+gimp_paintbrush_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_PAINTBRUSH_TOOL,
+ GIMP_TYPE_PAINT_OPTIONS,
+ gimp_paint_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT,
+ "gimp-paintbrush-tool",
+ _("Paintbrush"),
+ _("Paintbrush Tool: Paint smooth strokes using a brush"),
+ N_("_Paintbrush"), "P",
+ NULL, GIMP_HELP_TOOL_PAINTBRUSH,
+ GIMP_ICON_TOOL_PAINTBRUSH,
+ data);
+}
+
+static void
+gimp_paintbrush_tool_class_init (GimpPaintbrushToolClass *klass)
+{
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ paint_tool_class->is_alpha_only = gimp_paintbrush_tool_is_alpha_only;
+}
+
+static void
+gimp_paintbrush_tool_init (GimpPaintbrushTool *paintbrush)
+{
+ GimpTool *tool = GIMP_TOOL (paintbrush);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PAINTBRUSH);
+
+ gimp_paint_tool_enable_color_picker (GIMP_PAINT_TOOL (paintbrush),
+ GIMP_COLOR_PICK_TARGET_FOREGROUND);
+}
+
+static gboolean
+gimp_paintbrush_tool_is_alpha_only (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable)
+{
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpLayerMode paint_mode = gimp_context_get_paint_mode (context);
+
+ return gimp_layer_mode_is_alpha_only (paint_mode);
+}
diff --git a/app/tools/gimppaintbrushtool.h b/app/tools/gimppaintbrushtool.h
new file mode 100644
index 0000000..9014b6e
--- /dev/null
+++ b/app/tools/gimppaintbrushtool.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 __GIMP_PAINTBRUSH_TOOL_H__
+#define __GIMP_PAINTBRUSH_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+
+
+#define GIMP_TYPE_PAINTBRUSH_TOOL (gimp_paintbrush_tool_get_type ())
+#define GIMP_PAINTBRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINTBRUSH_TOOL, GimpPaintbrushTool))
+#define GIMP_PAINTBRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINTBRUSH_TOOL, GimpPaintbrushToolClass))
+#define GIMP_IS_PAINTBRUSH_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINTBRUSH_TOOL))
+#define GIMP_IS_PAINTBRUSH_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINTBRUSH_TOOL))
+#define GIMP_PAINTBRUSH_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINTBRUSH_TOOL, GimpPaintbrushToolClass))
+
+
+typedef struct _GimpPaintbrushTool GimpPaintbrushTool;
+typedef struct _GimpPaintbrushToolClass GimpPaintbrushToolClass;
+
+struct _GimpPaintbrushTool
+{
+ GimpBrushTool parent_instance;
+};
+
+struct _GimpPaintbrushToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+void gimp_paintbrush_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_paintbrush_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PAINTBRUSH_TOOL_H__ */
diff --git a/app/tools/gimppaintoptions-gui.c b/app/tools/gimppaintoptions-gui.c
new file mode 100644
index 0000000..211270f
--- /dev/null
+++ b/app/tools/gimppaintoptions-gui.c
@@ -0,0 +1,582 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimptoolinfo.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "widgets/gimplayermodebox.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpviewablebox.h"
+#include "widgets/gimpwidgets-constructors.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpairbrushtool.h"
+#include "gimpclonetool.h"
+#include "gimpconvolvetool.h"
+#include "gimpdodgeburntool.h"
+#include "gimperasertool.h"
+#include "gimphealtool.h"
+#include "gimpinktool.h"
+#include "gimpmybrushtool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimppenciltool.h"
+#include "gimpperspectiveclonetool.h"
+#include "gimpsmudgetool.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_paint_options_gui_reset_size (GtkWidget *button,
+ GimpPaintOptions *paint_options);
+static void gimp_paint_options_gui_reset_aspect_ratio
+ (GtkWidget *button,
+ GimpPaintOptions *paint_options);
+static void gimp_paint_options_gui_reset_angle (GtkWidget *button,
+ GimpPaintOptions *paint_options);
+static void gimp_paint_options_gui_reset_spacing
+ (GtkWidget *button,
+ GimpPaintOptions *paint_options);
+static void gimp_paint_options_gui_reset_hardness
+ (GtkWidget *button,
+ GimpPaintOptions *paint_options);
+static void gimp_paint_options_gui_reset_force (GtkWidget *button,
+ GimpPaintOptions *paint_options);
+
+static GtkWidget * dynamics_options_gui (GimpPaintOptions *paint_options,
+ GType tool_type);
+static GtkWidget * jitter_options_gui (GimpPaintOptions *paint_options,
+ GType tool_type);
+static GtkWidget * smoothing_options_gui (GimpPaintOptions *paint_options,
+ GType tool_type);
+
+static GtkWidget * gimp_paint_options_gui_scale_with_buttons
+ (GObject *config,
+ gchar *prop_name,
+ gchar *link_prop_name,
+ gchar *reset_tooltip,
+ gdouble step_increment,
+ gdouble page_increment,
+ gint digits,
+ gdouble scale_min,
+ gdouble scale_max,
+ gdouble factor,
+ gdouble gamma,
+ GCallback reset_callback,
+ GtkSizeGroup *link_group);
+
+
+/* public functions */
+
+GtkWidget *
+gimp_paint_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpPaintOptions *options = GIMP_PAINT_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *menu;
+ GtkWidget *scale;
+ GType tool_type;
+
+ tool_type = tool_options->tool_info->tool_type;
+
+ /* the paint mode menu */
+ menu = gimp_prop_layer_mode_box_new (config, "paint-mode",
+ GIMP_LAYER_MODE_CONTEXT_PAINT);
+ gimp_layer_mode_box_set_label (GIMP_LAYER_MODE_BOX (menu), _("Mode"));
+ gimp_layer_mode_box_set_ellipsize (GIMP_LAYER_MODE_BOX (menu),
+ PANGO_ELLIPSIZE_END);
+ gtk_box_pack_start (GTK_BOX (vbox), menu, FALSE, FALSE, 0);
+ gtk_widget_show (menu);
+
+ g_object_set_data (G_OBJECT (vbox),
+ "gimp-paint-options-gui-paint-mode-box", menu);
+
+ if (tool_type == GIMP_TYPE_ERASER_TOOL ||
+ tool_type == GIMP_TYPE_CONVOLVE_TOOL ||
+ tool_type == GIMP_TYPE_DODGE_BURN_TOOL ||
+ tool_type == GIMP_TYPE_HEAL_TOOL ||
+ tool_type == GIMP_TYPE_MYBRUSH_TOOL ||
+ tool_type == GIMP_TYPE_SMUDGE_TOOL)
+ {
+ gtk_widget_set_sensitive (menu, FALSE);
+ }
+
+ /* the opacity scale */
+ scale = gimp_prop_spin_scale_new (config, "opacity", NULL,
+ 0.01, 0.1, 0);
+ gimp_prop_widget_set_factor (scale, 100.0, 0.0, 0.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* temp debug foo, disabled in stable */
+ if (FALSE &&
+ g_type_is_a (tool_type, GIMP_TYPE_PAINT_TOOL) &&
+ tool_type != GIMP_TYPE_MYBRUSH_TOOL)
+ {
+ GtkWidget *button;
+
+ button = gimp_prop_check_button_new (config, "use-applicator", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+ }
+
+ /* the brush */
+ if (g_type_is_a (tool_type, GIMP_TYPE_BRUSH_TOOL))
+ {
+ GtkSizeGroup *link_group;
+ GtkWidget *button;
+ GtkWidget *frame;
+ GtkWidget *hbox;
+
+ button = gimp_prop_brush_box_new (NULL, GIMP_CONTEXT (tool_options),
+ _("Brush"), 2,
+ "brush-view-type", "brush-view-size",
+ "gimp-brush-editor",
+ _("Edit this brush"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ link_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ hbox = gimp_paint_options_gui_scale_with_buttons
+ (config, "brush-size", "brush-link-size",
+ _("Reset size to brush's native size"),
+ 1.0, 10.0, 2, 1.0, 1000.0, 1.0, 1.7,
+ G_CALLBACK (gimp_paint_options_gui_reset_size), link_group);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ hbox = gimp_paint_options_gui_scale_with_buttons
+ (config, "brush-aspect-ratio", "brush-link-aspect-ratio",
+ _("Reset aspect ratio to brush's native aspect ratio"),
+ 0.1, 1.0, 2, -20.0, 20.0, 1.0, 1.0,
+ G_CALLBACK (gimp_paint_options_gui_reset_aspect_ratio), link_group);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ hbox = gimp_paint_options_gui_scale_with_buttons
+ (config, "brush-angle", "brush-link-angle",
+ _("Reset angle to brush's native angle"),
+ 0.1, 1.0, 2, -180.0, 180.0, 1.0, 1.0,
+ G_CALLBACK (gimp_paint_options_gui_reset_angle), link_group);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ hbox = gimp_paint_options_gui_scale_with_buttons
+ (config, "brush-spacing", "brush-link-spacing",
+ _("Reset spacing to brush's native spacing"),
+ 0.1, 1.0, 1, 1.0, 200.0, 100.0, 1.7,
+ G_CALLBACK (gimp_paint_options_gui_reset_spacing), link_group);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ hbox = gimp_paint_options_gui_scale_with_buttons
+ (config, "brush-hardness", "brush-link-hardness",
+ _("Reset hardness to brush's native hardness"),
+ 0.1, 1.0, 1, 0.0, 100.0, 100.0, 1.0,
+ G_CALLBACK (gimp_paint_options_gui_reset_hardness), link_group);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ hbox = gimp_paint_options_gui_scale_with_buttons
+ (config, "brush-force", NULL,
+ _("Reset force to default"),
+ 0.1, 1.0, 1, 0.0, 100.0, 100.0, 1.0,
+ G_CALLBACK (gimp_paint_options_gui_reset_force), link_group);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ if (tool_type == GIMP_TYPE_PENCIL_TOOL)
+ gtk_widget_set_sensitive (hbox, FALSE);
+
+ g_object_unref (link_group);
+
+ button = gimp_prop_dynamics_box_new (NULL, GIMP_CONTEXT (tool_options),
+ _("Dynamics"), 2,
+ "dynamics-view-type",
+ "dynamics-view-size",
+ "gimp-dynamics-editor",
+ _("Edit this dynamics"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ frame = dynamics_options_gui (options, tool_type);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ frame = jitter_options_gui (options, tool_type);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+ }
+
+ /* the "smooth stroke" options */
+ if (g_type_is_a (tool_type, GIMP_TYPE_PAINT_TOOL))
+ {
+ GtkWidget *frame;
+
+ frame = smoothing_options_gui (options, tool_type);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+ }
+
+ /* the "Lock brush to view" toggle */
+ if (g_type_is_a (tool_type, GIMP_TYPE_BRUSH_TOOL))
+ {
+ GtkWidget *button;
+
+ button = gimp_prop_check_button_new (config, "brush-lock-to-view", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+ }
+
+ /* the "incremental" toggle */
+ if (tool_type == GIMP_TYPE_PENCIL_TOOL ||
+ tool_type == GIMP_TYPE_PAINTBRUSH_TOOL ||
+ tool_type == GIMP_TYPE_ERASER_TOOL ||
+ tool_type == GIMP_TYPE_DODGE_BURN_TOOL)
+ {
+ GtkWidget *button;
+
+ button = gimp_prop_enum_check_button_new (config, "application-mode",
+ NULL,
+ GIMP_PAINT_CONSTANT,
+ GIMP_PAINT_INCREMENTAL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+ }
+
+ /* the "hard edge" toggle */
+ if (tool_type == GIMP_TYPE_ERASER_TOOL ||
+ tool_type == GIMP_TYPE_CLONE_TOOL ||
+ tool_type == GIMP_TYPE_HEAL_TOOL ||
+ tool_type == GIMP_TYPE_PERSPECTIVE_CLONE_TOOL ||
+ tool_type == GIMP_TYPE_CONVOLVE_TOOL ||
+ tool_type == GIMP_TYPE_DODGE_BURN_TOOL ||
+ tool_type == GIMP_TYPE_SMUDGE_TOOL)
+ {
+ GtkWidget *button;
+
+ button = gimp_prop_check_button_new (config, "hard", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+ }
+
+ return vbox;
+}
+
+GtkWidget *
+gimp_paint_options_gui_get_paint_mode_box (GtkWidget *options_gui)
+{
+ return g_object_get_data (G_OBJECT (options_gui),
+ "gimp-paint-options-gui-paint-mode-box");
+}
+
+
+/* private functions */
+
+static GtkWidget *
+dynamics_options_gui (GimpPaintOptions *paint_options,
+ GType tool_type)
+{
+ GObject *config = G_OBJECT (paint_options);
+ GtkWidget *frame;
+ GtkWidget *inner_frame;
+ GtkWidget *scale;
+ GtkWidget *menu;
+ GtkWidget *combo;
+ GtkWidget *checkbox;
+ GtkWidget *vbox;
+ GtkWidget *inner_vbox;
+ GtkWidget *hbox;
+ GtkWidget *box;
+
+ frame = gimp_prop_expander_new (config, "dynamics-expanded", NULL);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ inner_frame = gimp_frame_new (_("Fade Options"));
+ gtk_box_pack_start (GTK_BOX (vbox), inner_frame, FALSE, FALSE, 0);
+ gtk_widget_show (inner_frame);
+
+ inner_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (inner_frame), inner_vbox);
+ gtk_widget_show (inner_vbox);
+
+ /* the fade-out scale & unitmenu */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ scale = gimp_prop_spin_scale_new (config, "fade-length", NULL,
+ 1.0, 50.0, 0);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 1000.0);
+ gtk_box_pack_start (GTK_BOX (hbox), scale, TRUE, TRUE, 0);
+ gtk_widget_show (scale);
+
+ menu = gimp_prop_unit_combo_box_new (config, "fade-unit");
+ gtk_box_pack_start (GTK_BOX (hbox), menu, FALSE, FALSE, 0);
+ gtk_widget_show (menu);
+
+#if 0
+ /* FIXME pixel digits */
+ g_object_set_data (G_OBJECT (menu), "set_digits", spinbutton);
+ gimp_unit_menu_set_pixel_digits (GIMP_UNIT_MENU (menu), 0);
+#endif
+
+ /* the repeat type */
+ combo = gimp_prop_enum_combo_box_new (config, "fade-repeat", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Repeat"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ checkbox = gimp_prop_check_button_new (config, "fade-reverse", NULL);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), checkbox, FALSE, FALSE, 0);
+ gtk_widget_show (checkbox);
+
+ /* Color UI */
+ if (g_type_is_a (tool_type, GIMP_TYPE_PAINTBRUSH_TOOL) ||
+ tool_type == GIMP_TYPE_SMUDGE_TOOL)
+ {
+ inner_frame = gimp_frame_new (_("Color Options"));
+ gtk_box_pack_start (GTK_BOX (vbox), inner_frame, FALSE, FALSE, 0);
+ gtk_widget_show (inner_frame);
+
+ inner_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (inner_frame), inner_vbox);
+ gtk_widget_show (inner_vbox);
+
+ box = gimp_prop_gradient_box_new (NULL, GIMP_CONTEXT (config),
+ _("Gradient"), 2,
+ "gradient-view-type",
+ "gradient-view-size",
+ "gradient-reverse",
+ "gradient-blend-color-space",
+ "gimp-gradient-editor",
+ _("Edit this gradient"));
+ gtk_box_pack_start (GTK_BOX (inner_vbox), box, FALSE, FALSE, 0);
+ gtk_widget_show (box);
+
+ /* the blend color space */
+ combo = gimp_prop_enum_combo_box_new (config, "gradient-blend-color-space",
+ 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo),
+ _("Blend Color Space"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (inner_vbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+ }
+
+ return frame;
+}
+
+static GtkWidget *
+jitter_options_gui (GimpPaintOptions *paint_options,
+ GType tool_type)
+{
+ GObject *config = G_OBJECT (paint_options);
+ GtkWidget *frame;
+ GtkWidget *scale;
+
+ scale = gimp_prop_spin_scale_new (config, "jitter-amount", NULL,
+ 0.01, 1.0, 2);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 0.0, 5.0);
+
+ frame = gimp_prop_expanding_frame_new (config, "use-jitter", NULL,
+ scale, NULL);
+
+ return frame;
+}
+
+static GtkWidget *
+smoothing_options_gui (GimpPaintOptions *paint_options,
+ GType tool_type)
+{
+ GObject *config = G_OBJECT (paint_options);
+ GtkWidget *frame;
+ GtkWidget *vbox;
+ GtkWidget *scale;
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+ frame = gimp_prop_expanding_frame_new (config, "use-smoothing", NULL,
+ vbox, NULL);
+
+ scale = gimp_prop_spin_scale_new (config, "smoothing-quality", NULL,
+ 1, 10, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ scale = gimp_prop_spin_scale_new (config, "smoothing-factor", NULL,
+ 1, 10, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return frame;
+}
+
+static void
+gimp_paint_options_gui_reset_size (GtkWidget *button,
+ GimpPaintOptions *paint_options)
+{
+ GimpBrush *brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ gimp_paint_options_set_default_brush_size (paint_options, brush);
+}
+
+static void
+gimp_paint_options_gui_reset_aspect_ratio (GtkWidget *button,
+ GimpPaintOptions *paint_options)
+{
+ GimpBrush *brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ gimp_paint_options_set_default_brush_aspect_ratio (paint_options, brush);
+}
+
+static void
+gimp_paint_options_gui_reset_angle (GtkWidget *button,
+ GimpPaintOptions *paint_options)
+{
+ GimpBrush *brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ gimp_paint_options_set_default_brush_angle (paint_options, brush);
+}
+
+static void
+gimp_paint_options_gui_reset_spacing (GtkWidget *button,
+ GimpPaintOptions *paint_options)
+{
+ GimpBrush *brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ gimp_paint_options_set_default_brush_spacing (paint_options, brush);
+}
+
+static void
+gimp_paint_options_gui_reset_hardness (GtkWidget *button,
+ GimpPaintOptions *paint_options)
+{
+ GimpBrush *brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ gimp_paint_options_set_default_brush_hardness (paint_options, brush);
+}
+
+static void
+gimp_paint_options_gui_reset_force (GtkWidget *button,
+ GimpPaintOptions *paint_options)
+{
+ g_object_set (paint_options,
+ "brush-force", 0.5,
+ NULL);
+}
+
+static GtkWidget *
+gimp_paint_options_gui_scale_with_buttons (GObject *config,
+ gchar *prop_name,
+ gchar *link_prop_name,
+ gchar *reset_tooltip,
+ gdouble step_increment,
+ gdouble page_increment,
+ gint digits,
+ gdouble scale_min,
+ gdouble scale_max,
+ gdouble factor,
+ gdouble gamma,
+ GCallback reset_callback,
+ GtkSizeGroup *link_group)
+{
+ GtkWidget *scale;
+ GtkWidget *hbox;
+ GtkWidget *button;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+
+ scale = gimp_prop_spin_scale_new (config, prop_name, NULL,
+ step_increment, page_increment, digits);
+ gimp_spin_scale_set_constrain_drag (GIMP_SPIN_SCALE (scale), TRUE);
+
+ gimp_prop_widget_set_factor (scale, factor,
+ step_increment, page_increment, digits);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale),
+ scale_min, scale_max);
+ gimp_spin_scale_set_gamma (GIMP_SPIN_SCALE (scale), gamma);
+ gtk_box_pack_start (GTK_BOX (hbox), scale, TRUE, TRUE, 0);
+ gtk_widget_show (scale);
+
+ button = gimp_icon_button_new (GIMP_ICON_RESET, NULL);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_image_set_from_icon_name (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (button))),
+ GIMP_ICON_RESET, GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ reset_callback,
+ config);
+
+ gimp_help_set_help_data (button,
+ reset_tooltip, NULL);
+
+ if (link_prop_name)
+ {
+ GtkWidget *image;
+
+ button = gtk_toggle_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+
+ image = gtk_image_new_from_icon_name (GIMP_ICON_LINKED,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show (image);
+
+ g_object_bind_property (config, link_prop_name,
+ button, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ }
+ else
+ {
+ button = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ }
+
+ gtk_size_group_add_widget (link_group, button);
+
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (button,
+ _("Link to brush default"), NULL);
+
+ return hbox;
+}
diff --git a/app/tools/gimppaintoptions-gui.h b/app/tools/gimppaintoptions-gui.h
new file mode 100644
index 0000000..d747bbe
--- /dev/null
+++ b/app/tools/gimppaintoptions-gui.h
@@ -0,0 +1,27 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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_PAINT_OPTIONS_GUI_H__
+#define __GIMP_PAINT_OPTIONS_GUI_H__
+
+
+GtkWidget * gimp_paint_options_gui (GimpToolOptions *tool_options);
+
+GtkWidget * gimp_paint_options_gui_get_paint_mode_box (GtkWidget *options_gui);
+
+
+#endif /* __GIMP_PAINT_OPTIONS_GUI_H__ */
diff --git a/app/tools/gimppainttool-paint.c b/app/tools/gimppainttool-paint.c
new file mode 100644
index 0000000..88cd526
--- /dev/null
+++ b/app/tools/gimppainttool-paint.c
@@ -0,0 +1,540 @@
+/* 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 "tools-types.h"
+
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimpprojection.h"
+
+#include "paint/gimppaintcore.h"
+#include "paint/gimppaintoptions.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-utils.h"
+
+#include "gimppainttool.h"
+#include "gimppainttool-paint.h"
+
+
+#define DISPLAY_UPDATE_INTERVAL 10000 /* microseconds */
+
+
+#define PAINT_FINISH NULL
+
+
+typedef struct
+{
+ GimpPaintTool *paint_tool;
+ GimpPaintToolPaintFunc func;
+ union
+ {
+ gpointer data;
+ gboolean *finished;
+ };
+} PaintItem;
+
+typedef struct
+{
+ GimpCoords coords;
+ guint32 time;
+} InterpolateData;
+
+
+/* local function prototypes */
+
+static gboolean gimp_paint_tool_paint_use_thread (GimpPaintTool *paint_tool);
+static gpointer gimp_paint_tool_paint_thread (gpointer data);
+
+static gboolean gimp_paint_tool_paint_timeout (GimpPaintTool *paint_tool);
+
+static void gimp_paint_tool_paint_interpolate (GimpPaintTool *paint_tool,
+ InterpolateData *data);
+
+
+/* static variables */
+
+static GThread *paint_thread;
+
+static GMutex paint_mutex;
+static GCond paint_cond;
+
+static GQueue paint_queue = G_QUEUE_INIT;
+static GMutex paint_queue_mutex;
+static GCond paint_queue_cond;
+
+static guint paint_timeout_id;
+static volatile gboolean paint_timeout_pending;
+
+
+/* private functions */
+
+
+static gboolean
+gimp_paint_tool_paint_use_thread (GimpPaintTool *paint_tool)
+{
+ if (! paint_tool->draw_line)
+ {
+ if (! paint_thread)
+ {
+ static gint use_paint_thread = -1;
+
+ if (use_paint_thread < 0)
+ use_paint_thread = g_getenv ("GIMP_NO_PAINT_THREAD") == NULL;
+
+ if (use_paint_thread)
+ {
+ paint_thread = g_thread_new ("paint",
+ gimp_paint_tool_paint_thread, NULL);
+ }
+ }
+
+ return paint_thread != NULL;
+ }
+
+ return FALSE;
+}
+
+static gpointer
+gimp_paint_tool_paint_thread (gpointer data)
+{
+ g_mutex_lock (&paint_queue_mutex);
+
+ while (TRUE)
+ {
+ PaintItem *item;
+
+ while (! (item = g_queue_pop_head (&paint_queue)))
+ g_cond_wait (&paint_queue_cond, &paint_queue_mutex);
+
+ if (item->func == PAINT_FINISH)
+ {
+ *item->finished = TRUE;
+ g_cond_signal (&paint_queue_cond);
+ }
+ else
+ {
+ g_mutex_unlock (&paint_queue_mutex);
+ g_mutex_lock (&paint_mutex);
+
+ while (paint_timeout_pending)
+ g_cond_wait (&paint_cond, &paint_mutex);
+
+ item->func (item->paint_tool, item->data);
+
+ g_mutex_unlock (&paint_mutex);
+ g_mutex_lock (&paint_queue_mutex);
+ }
+
+ g_slice_free (PaintItem, item);
+ }
+
+ g_mutex_unlock (&paint_queue_mutex);
+
+ return NULL;
+}
+
+static gboolean
+gimp_paint_tool_paint_timeout (GimpPaintTool *paint_tool)
+{
+ GimpPaintCore *core = paint_tool->core;
+ GimpDrawable *drawable = paint_tool->drawable;
+ gboolean update;
+
+ paint_timeout_pending = TRUE;
+
+ g_mutex_lock (&paint_mutex);
+
+ paint_tool->paint_x = core->last_paint.x;
+ paint_tool->paint_y = core->last_paint.y;
+
+ update = gimp_drawable_flush_paint (drawable);
+
+ if (update && GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_flush)
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_flush (paint_tool);
+
+ paint_timeout_pending = FALSE;
+ g_cond_signal (&paint_cond);
+
+ g_mutex_unlock (&paint_mutex);
+
+ if (update)
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (paint_tool);
+ GimpDisplay *display = paint_tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (paint_tool->snap_brush)
+ gimp_draw_tool_pause (draw_tool);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (display);
+
+ if (paint_tool->snap_brush)
+ gimp_draw_tool_resume (draw_tool);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gimp_paint_tool_paint_interpolate (GimpPaintTool *paint_tool,
+ InterpolateData *data)
+{
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ GimpPaintCore *core = paint_tool->core;
+ GimpDrawable *drawable = paint_tool->drawable;
+
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &data->coords, data->time);
+
+ g_slice_free (InterpolateData, data);
+}
+
+
+/* public functions */
+
+
+gboolean
+gimp_paint_tool_paint_start (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ const GimpCoords *coords,
+ guint32 time,
+ gboolean constrain,
+ GError **error)
+{
+ GimpTool *tool;
+ GimpPaintOptions *paint_options;
+ GimpPaintCore *core;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GimpCoords curr_coords;
+ gint off_x, off_y;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_TOOL (paint_tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (paint_tool->display == NULL, FALSE);
+
+ tool = GIMP_TOOL (paint_tool);
+ paint_tool = GIMP_PAINT_TOOL (paint_tool);
+ paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ core = paint_tool->core;
+ shell = gimp_display_get_shell (display);
+ image = gimp_display_get_image (display);
+ drawable = gimp_image_get_active_drawable (image);
+
+ curr_coords = *coords;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ curr_coords.x -= off_x;
+ curr_coords.y -= off_y;
+
+ paint_tool->paint_x = curr_coords.x;
+ paint_tool->paint_y = curr_coords.y;
+
+ /* If we use a separate paint thread, enter paint mode before starting the
+ * paint core
+ */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ gimp_drawable_start_paint (drawable);
+
+ /* Prepare to start the paint core */
+ if (GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_prepare)
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_prepare (paint_tool, display);
+
+ /* Start the paint core */
+ if (! gimp_paint_core_start (core,
+ drawable, paint_options, &curr_coords,
+ error))
+ {
+ gimp_drawable_end_paint (drawable);
+
+ return FALSE;
+ }
+
+ paint_tool->display = display;
+ paint_tool->drawable = drawable;
+
+ if ((display != tool->display) || ! paint_tool->draw_line)
+ {
+ /* If this is a new display, reset the "last stroke's endpoint"
+ * because there is none
+ */
+ if (display != tool->display)
+ core->start_coords = core->cur_coords;
+
+ core->last_coords = core->cur_coords;
+
+ core->distance = 0.0;
+ core->pixel_dist = 0.0;
+ }
+ else if (paint_tool->draw_line)
+ {
+ gdouble offset_angle;
+ gdouble xres, yres;
+
+ gimp_display_shell_get_constrained_line_params (shell,
+ &offset_angle,
+ &xres, &yres);
+
+ /* If shift is down and this is not the first paint
+ * stroke, then draw a line from the last coords to the pointer
+ */
+ gimp_paint_core_round_line (core, paint_options,
+ constrain, offset_angle, xres, yres);
+ }
+
+ /* Notify subclasses */
+ if (gimp_paint_tool_paint_use_thread (paint_tool) &&
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_start)
+ {
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_start (paint_tool);
+ }
+
+ /* Let the specific painting function initialize itself */
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_INIT, time);
+
+ /* Paint to the image */
+ if (paint_tool->draw_line)
+ {
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &core->cur_coords, time);
+ }
+ else
+ {
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+ }
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (display);
+
+ /* Start the display update timeout */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ {
+ paint_timeout_id = g_timeout_add_full (
+ G_PRIORITY_HIGH_IDLE,
+ DISPLAY_UPDATE_INTERVAL / 1000,
+ (GSourceFunc) gimp_paint_tool_paint_timeout,
+ paint_tool, NULL);
+ }
+
+ return TRUE;
+}
+
+void
+gimp_paint_tool_paint_end (GimpPaintTool *paint_tool,
+ guint32 time,
+ gboolean cancel)
+{
+ GimpPaintOptions *paint_options;
+ GimpPaintCore *core;
+ GimpDrawable *drawable;
+
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (paint_tool));
+ g_return_if_fail (paint_tool->display != NULL);
+
+ paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ core = paint_tool->core;
+ drawable = paint_tool->drawable;
+
+ /* Process remaining paint items */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ {
+ PaintItem *item;
+ gboolean finished = FALSE;
+ guint64 end_time;
+
+ g_return_if_fail (gimp_paint_tool_paint_is_active (paint_tool));
+
+ g_source_remove (paint_timeout_id);
+ paint_timeout_id = 0;
+
+ item = g_slice_new (PaintItem);
+
+ item->paint_tool = paint_tool;
+ item->func = PAINT_FINISH;
+ item->finished = &finished;
+
+ g_mutex_lock (&paint_queue_mutex);
+
+ g_queue_push_tail (&paint_queue, item);
+ g_cond_signal (&paint_queue_cond);
+
+ end_time = g_get_monotonic_time () + DISPLAY_UPDATE_INTERVAL;
+
+ while (! finished)
+ {
+ if (! g_cond_wait_until (&paint_queue_cond, &paint_queue_mutex,
+ end_time))
+ {
+ g_mutex_unlock (&paint_queue_mutex);
+
+ gimp_paint_tool_paint_timeout (paint_tool);
+
+ g_mutex_lock (&paint_queue_mutex);
+
+ end_time = g_get_monotonic_time () + DISPLAY_UPDATE_INTERVAL;
+ }
+ }
+
+ g_mutex_unlock (&paint_queue_mutex);
+ }
+
+ /* Let the specific painting function finish up */
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_FINISH, time);
+
+ if (cancel)
+ gimp_paint_core_cancel (core, drawable);
+ else
+ gimp_paint_core_finish (core, drawable, TRUE);
+
+ /* Notify subclasses */
+ if (gimp_paint_tool_paint_use_thread (paint_tool) &&
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_end)
+ {
+ GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->paint_end (paint_tool);
+ }
+
+ /* Exit paint mode */
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ gimp_drawable_end_paint (drawable);
+
+ paint_tool->display = NULL;
+ paint_tool->drawable = NULL;
+}
+
+gboolean
+gimp_paint_tool_paint_is_active (GimpPaintTool *paint_tool)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_TOOL (paint_tool), FALSE);
+
+ return paint_tool->drawable != NULL &&
+ gimp_drawable_is_painting (paint_tool->drawable);
+}
+
+void
+gimp_paint_tool_paint_push (GimpPaintTool *paint_tool,
+ GimpPaintToolPaintFunc func,
+ gpointer data)
+{
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (paint_tool));
+ g_return_if_fail (func != NULL);
+
+ if (gimp_paint_tool_paint_use_thread (paint_tool))
+ {
+ PaintItem *item;
+
+ g_return_if_fail (gimp_paint_tool_paint_is_active (paint_tool));
+
+ /* Push an item to the queue, to be processed by the paint thread */
+
+ item = g_slice_new (PaintItem);
+
+ item->paint_tool = paint_tool;
+ item->func = func;
+ item->data = data;
+
+ g_mutex_lock (&paint_queue_mutex);
+
+ g_queue_push_tail (&paint_queue, item);
+ g_cond_signal (&paint_queue_cond);
+
+ g_mutex_unlock (&paint_queue_mutex);
+ }
+ else
+ {
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (paint_tool);
+ GimpDisplay *display = paint_tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ /* Paint directly */
+
+ gimp_draw_tool_pause (draw_tool);
+
+ func (paint_tool, data);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (display);
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+}
+
+void
+gimp_paint_tool_paint_motion (GimpPaintTool *paint_tool,
+ const GimpCoords *coords,
+ guint32 time)
+{
+ GimpPaintOptions *paint_options;
+ GimpPaintCore *core;
+ GimpDrawable *drawable;
+ InterpolateData *data;
+ gint off_x, off_y;
+
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (paint_tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (paint_tool->display != NULL);
+
+ paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (paint_tool);
+ core = paint_tool->core;
+ drawable = paint_tool->drawable;
+
+ data = g_slice_new (InterpolateData);
+
+ data->coords = *coords;
+ data->time = time;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ data->coords.x -= off_x;
+ data->coords.y -= off_y;
+
+ paint_tool->cursor_x = data->coords.x;
+ paint_tool->cursor_y = data->coords.y;
+
+ gimp_paint_core_smooth_coords (core, paint_options, &data->coords);
+
+ /* Don't paint while the Shift key is pressed for line drawing */
+ if (paint_tool->draw_line)
+ {
+ gimp_paint_core_set_current_coords (core, &data->coords);
+
+ g_slice_free (InterpolateData, data);
+
+ return;
+ }
+
+ gimp_paint_tool_paint_push (
+ paint_tool,
+ (GimpPaintToolPaintFunc) gimp_paint_tool_paint_interpolate,
+ data);
+}
diff --git a/app/tools/gimppainttool-paint.h b/app/tools/gimppainttool-paint.h
new file mode 100644
index 0000000..b3a6b03
--- /dev/null
+++ b/app/tools/gimppainttool-paint.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_PAINT_TOOL_PAINT_H__
+#define __GIMP_PAINT_TOOL_PAINT_H__
+
+
+typedef void (* GimpPaintToolPaintFunc) (GimpPaintTool *tool,
+ gpointer data);
+
+
+
+gboolean gimp_paint_tool_paint_start (GimpPaintTool *tool,
+ GimpDisplay *display,
+ const GimpCoords *coords,
+ guint32 time,
+ gboolean constrain,
+ GError **error);
+void gimp_paint_tool_paint_end (GimpPaintTool *tool,
+ guint32 time,
+ gboolean cancel);
+
+gboolean gimp_paint_tool_paint_is_active (GimpPaintTool *tool);
+
+void gimp_paint_tool_paint_push (GimpPaintTool *tool,
+ GimpPaintToolPaintFunc func,
+ gpointer data);
+
+void gimp_paint_tool_paint_motion (GimpPaintTool *tool,
+ const GimpCoords *coords,
+ guint32 time);
+
+
+#endif /* __GIMP_PAINT_TOOL_PAINT_H__ */
diff --git a/app/tools/gimppainttool.c b/app/tools/gimppainttool.c
new file mode 100644
index 0000000..48eb215
--- /dev/null
+++ b/app/tools/gimppainttool.c
@@ -0,0 +1,991 @@
+/* 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 "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimp-utils.h"
+#include "core/gimpdrawable.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer.h"
+#include "core/gimppaintinfo.h"
+#include "core/gimpprojection.h"
+#include "core/gimptoolinfo.h"
+
+#include "paint/gimppaintcore.h"
+#include "paint/gimppaintoptions.h"
+
+#include "widgets/gimpdevices.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-selection.h"
+#include "display/gimpdisplayshell-utils.h"
+
+#include "gimpcoloroptions.h"
+#include "gimppaintoptions-gui.h"
+#include "gimppainttool.h"
+#include "gimppainttool-paint.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_paint_tool_constructed (GObject *object);
+static void gimp_paint_tool_finalize (GObject *object);
+
+static void gimp_paint_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_paint_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_paint_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_paint_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_paint_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_paint_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_paint_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+
+static void gimp_paint_tool_draw (GimpDrawTool *draw_tool);
+
+static void
+ gimp_paint_tool_real_paint_prepare (GimpPaintTool *paint_tool,
+ GimpDisplay *display);
+
+static GimpCanvasItem *
+ gimp_paint_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y);
+
+static gboolean gimp_paint_tool_check_alpha (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable,
+ GimpDisplay *display,
+ GError **error);
+
+static void gimp_paint_tool_hard_notify (GimpPaintOptions *options,
+ const GParamSpec *pspec,
+ GimpPaintTool *paint_tool);
+static void gimp_paint_tool_cursor_notify (GimpDisplayConfig *config,
+ GParamSpec *pspec,
+ GimpPaintTool *paint_tool);
+
+
+G_DEFINE_TYPE (GimpPaintTool, gimp_paint_tool, GIMP_TYPE_COLOR_TOOL)
+
+#define parent_class gimp_paint_tool_parent_class
+
+
+static void
+gimp_paint_tool_class_init (GimpPaintToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_paint_tool_constructed;
+ object_class->finalize = gimp_paint_tool_finalize;
+
+ tool_class->control = gimp_paint_tool_control;
+ tool_class->button_press = gimp_paint_tool_button_press;
+ tool_class->button_release = gimp_paint_tool_button_release;
+ tool_class->motion = gimp_paint_tool_motion;
+ tool_class->modifier_key = gimp_paint_tool_modifier_key;
+ tool_class->cursor_update = gimp_paint_tool_cursor_update;
+ tool_class->oper_update = gimp_paint_tool_oper_update;
+
+ draw_tool_class->draw = gimp_paint_tool_draw;
+
+ klass->paint_prepare = gimp_paint_tool_real_paint_prepare;
+}
+
+static void
+gimp_paint_tool_init (GimpPaintTool *paint_tool)
+{
+ GimpTool *tool = GIMP_TOOL (paint_tool);
+
+ gimp_tool_control_set_motion_mode (tool->control, GIMP_MOTION_MODE_EXACT);
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_action_opacity (tool->control,
+ "context/context-opacity-set");
+
+ paint_tool->active = TRUE;
+ paint_tool->pick_colors = FALSE;
+ paint_tool->draw_line = FALSE;
+
+ paint_tool->show_cursor = TRUE;
+ paint_tool->draw_brush = TRUE;
+ paint_tool->snap_brush = FALSE;
+ paint_tool->draw_fallback = FALSE;
+ paint_tool->fallback_size = 0.0;
+ paint_tool->draw_circle = FALSE;
+ paint_tool->circle_size = 0.0;
+
+ paint_tool->status = _("Click to paint");
+ paint_tool->status_line = _("Click to draw the line");
+ paint_tool->status_ctrl = _("%s to pick a color");
+
+ paint_tool->core = NULL;
+}
+
+static void
+gimp_paint_tool_constructed (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (object);
+ GimpPaintOptions *options = GIMP_PAINT_TOOL_GET_OPTIONS (tool);
+ GimpDisplayConfig *display_config;
+ GimpPaintInfo *paint_info;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_TOOL_INFO (tool->tool_info));
+ gimp_assert (GIMP_IS_PAINT_INFO (tool->tool_info->paint_info));
+
+ display_config = GIMP_DISPLAY_CONFIG (tool->tool_info->gimp->config);
+
+ paint_info = tool->tool_info->paint_info;
+
+ gimp_assert (g_type_is_a (paint_info->paint_type, GIMP_TYPE_PAINT_CORE));
+
+ paint_tool->core = g_object_new (paint_info->paint_type,
+ "undo-desc", paint_info->blurb,
+ NULL);
+
+ g_signal_connect_object (options, "notify::hard",
+ G_CALLBACK (gimp_paint_tool_hard_notify),
+ paint_tool, 0);
+
+ gimp_paint_tool_hard_notify (options, NULL, paint_tool);
+
+ paint_tool->show_cursor = display_config->show_paint_tool_cursor;
+ paint_tool->draw_brush = display_config->show_brush_outline;
+ paint_tool->snap_brush = display_config->snap_brush_outline;
+
+ g_signal_connect_object (display_config, "notify::show-paint-tool-cursor",
+ G_CALLBACK (gimp_paint_tool_cursor_notify),
+ paint_tool, 0);
+ g_signal_connect_object (display_config, "notify::show-brush-outline",
+ G_CALLBACK (gimp_paint_tool_cursor_notify),
+ paint_tool, 0);
+ g_signal_connect_object (display_config, "notify::snap-brush-outline",
+ G_CALLBACK (gimp_paint_tool_cursor_notify),
+ paint_tool, 0);
+}
+
+static void
+gimp_paint_tool_finalize (GObject *object)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (object);
+
+ if (paint_tool->core)
+ {
+ g_object_unref (paint_tool->core);
+ paint_tool->core = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_paint_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_paint_core_cleanup (paint_tool->core);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_paint_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpPaintOptions *options = GIMP_PAINT_TOOL_GET_OPTIONS (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ gboolean constrain;
+ GError *error = NULL;
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+ return;
+ }
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ gimp_tool_message_literal (tool, display,
+ _("Cannot paint on layer groups."));
+ return;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The active layer's pixels are locked."));
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ return;
+ }
+
+ if (! gimp_paint_tool_check_alpha (paint_tool, drawable, display, &error))
+ {
+ GtkWidget *options_gui;
+ GtkWidget *mode_box;
+
+ gimp_tool_message_literal (tool, display, error->message);
+
+ options_gui = gimp_tools_get_tool_options_gui (
+ GIMP_TOOL_OPTIONS (options));
+ mode_box = gimp_paint_options_gui_get_paint_mode_box (options_gui);
+
+ if (gtk_widget_is_sensitive (mode_box))
+ gimp_widget_blink (mode_box);
+
+ g_clear_error (&error);
+
+ return;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The active layer is not visible."));
+ return;
+ }
+
+ if (gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_stop (draw_tool);
+
+ if (tool->display &&
+ tool->display != display &&
+ gimp_display_get_image (tool->display) == image)
+ {
+ /* if this is a different display, but the same image, HACK around
+ * in tool internals AFTER stopping the current draw_tool, so
+ * straight line drawing works across different views of the
+ * same image.
+ */
+
+ tool->display = display;
+ }
+
+ constrain = (state & gimp_get_constrain_behavior_mask ()) != 0;
+
+ if (! gimp_paint_tool_paint_start (paint_tool,
+ display, coords, time, constrain,
+ &error))
+ {
+ gimp_tool_message_literal (tool, display, error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ /* pause the current selection */
+ gimp_display_shell_selection_pause (shell);
+
+ gimp_draw_tool_start (draw_tool, display);
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_paint_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gboolean cancel;
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time,
+ state, release_type,
+ display);
+ return;
+ }
+
+ cancel = (release_type == GIMP_BUTTON_RELEASE_CANCEL);
+
+ gimp_paint_tool_paint_end (paint_tool, time, cancel);
+
+ gimp_tool_control_halt (tool->control);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ /* resume the current selection */
+ gimp_display_shell_selection_resume (shell);
+
+ gimp_image_flush (image);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_paint_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display);
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ return;
+
+ if (! paint_tool->snap_brush)
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_paint_tool_paint_motion (paint_tool, coords, time);
+
+ if (! paint_tool->snap_brush)
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_paint_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (paint_tool->pick_colors && ! paint_tool->draw_line)
+ {
+ if ((state & gimp_get_all_modifiers_mask ()) ==
+ gimp_get_constrain_behavior_mask ())
+ {
+ if (! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GimpToolInfo *info = gimp_get_tool_info (display->gimp,
+ "gimp-color-picker-tool");
+
+ if (GIMP_IS_TOOL_INFO (info))
+ {
+ if (gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_stop (draw_tool);
+
+ gimp_color_tool_enable (GIMP_COLOR_TOOL (tool),
+ GIMP_COLOR_OPTIONS (info->tool_options));
+
+ switch (GIMP_COLOR_TOOL (tool)->pick_target)
+ {
+ case GIMP_COLOR_PICK_TARGET_FOREGROUND:
+ gimp_tool_push_status (tool, display,
+ _("Click in any image to pick the "
+ "foreground color"));
+ break;
+
+ case GIMP_COLOR_PICK_TARGET_BACKGROUND:
+ gimp_tool_push_status (tool, display,
+ _("Click in any image to pick the "
+ "background color"));
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ gimp_tool_pop_status (tool, display);
+ gimp_color_tool_disable (GIMP_COLOR_TOOL (tool));
+ }
+ }
+ }
+}
+
+static void
+gimp_paint_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpCursorModifier modifier;
+ GimpCursorModifier toggle_modifier;
+ GimpCursorModifier old_modifier;
+ GimpCursorModifier old_toggle_modifier;
+
+ modifier = tool->control->cursor_modifier;
+ toggle_modifier = tool->control->toggle_cursor_modifier;
+
+ old_modifier = modifier;
+ old_toggle_modifier = toggle_modifier;
+
+ if (! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
+ gimp_item_is_content_locked (GIMP_ITEM (drawable)) ||
+ ! gimp_paint_tool_check_alpha (paint_tool, drawable, display, NULL) ||
+ ! (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
+ config->edit_non_visible))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ toggle_modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+
+ if (! paint_tool->show_cursor &&
+ modifier != GIMP_CURSOR_MODIFIER_BAD)
+ {
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_NONE,
+ GIMP_TOOL_CURSOR_NONE,
+ GIMP_CURSOR_MODIFIER_NONE);
+ return;
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control,
+ modifier);
+ gimp_tool_control_set_toggle_cursor_modifier (tool->control,
+ toggle_modifier);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+
+ /* reset old stuff here so we are not interfering with the modifiers
+ * set by our subclasses
+ */
+ gimp_tool_control_set_cursor_modifier (tool->control,
+ old_modifier);
+ gimp_tool_control_set_toggle_cursor_modifier (tool->control,
+ old_toggle_modifier);
+}
+
+static void
+gimp_paint_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ GimpPaintOptions *paint_options = GIMP_PAINT_TOOL_GET_OPTIONS (tool);
+ GimpPaintCore *core = paint_tool->core;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+ return;
+ }
+
+ gimp_draw_tool_pause (draw_tool);
+
+ if (gimp_draw_tool_is_active (draw_tool) &&
+ draw_tool->display != display)
+ gimp_draw_tool_stop (draw_tool);
+
+ gimp_tool_pop_status (tool, display);
+
+ if (tool->display &&
+ tool->display != display &&
+ gimp_display_get_image (tool->display) == image)
+ {
+ /* if this is a different display, but the same image, HACK around
+ * in tool internals AFTER stopping the current draw_tool, so
+ * straight line drawing works across different views of the
+ * same image.
+ */
+
+ tool->display = display;
+ }
+
+ if (drawable && proximity)
+ {
+ gchar *status;
+ gboolean constrain_mask = gimp_get_constrain_behavior_mask ();
+ gint off_x, off_y;
+
+ core->cur_coords = *coords;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ core->cur_coords.x -= off_x;
+ core->cur_coords.y -= off_y;
+
+ if (display == tool->display && (state & GIMP_PAINT_TOOL_LINE_MASK))
+ {
+ /* If shift is down and this is not the first paint stroke,
+ * draw a line.
+ */
+ gchar *status_help;
+ gdouble offset_angle;
+ gdouble xres, yres;
+
+ gimp_display_shell_get_constrained_line_params (shell,
+ &offset_angle,
+ &xres, &yres);
+
+ gimp_paint_core_round_line (core, paint_options,
+ (state & constrain_mask) != 0,
+ offset_angle, xres, yres);
+
+ status_help = gimp_suggest_modifiers (paint_tool->status_line,
+ constrain_mask & ~state,
+ NULL,
+ _("%s for constrained angles"),
+ NULL);
+
+ status = gimp_display_shell_get_line_status (shell, status_help,
+ ". ",
+ core->last_coords.x,
+ core->last_coords.y,
+ core->cur_coords.x,
+ core->cur_coords.y);
+ g_free (status_help);
+ paint_tool->draw_line = TRUE;
+ }
+ else
+ {
+ GdkModifierType modifiers = 0;
+
+ /* HACK: A paint tool may set status_ctrl to NULL to indicate that
+ * it ignores the Ctrl modifier (temporarily or permanently), so
+ * it should not be suggested. This is different from how
+ * gimp_suggest_modifiers() would interpret this parameter.
+ */
+ if (paint_tool->status_ctrl != NULL)
+ modifiers |= constrain_mask;
+
+ /* suggest drawing lines only after the first point is set
+ */
+ if (display == tool->display)
+ modifiers |= GIMP_PAINT_TOOL_LINE_MASK;
+
+ status = gimp_suggest_modifiers (paint_tool->status,
+ modifiers & ~state,
+ _("%s for a straight line"),
+ paint_tool->status_ctrl,
+ NULL);
+ paint_tool->draw_line = FALSE;
+ }
+ gimp_tool_push_status (tool, display, "%s", status);
+ g_free (status);
+
+ paint_tool->cursor_x = core->cur_coords.x;
+ paint_tool->cursor_y = core->cur_coords.y;
+
+ if (! gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_start (draw_tool, display);
+ }
+ else if (gimp_draw_tool_is_active (draw_tool))
+ {
+ gimp_draw_tool_stop (draw_tool);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+static void
+gimp_paint_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (draw_tool);
+
+
+ if (paint_tool->active &&
+ ! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (draw_tool)))
+ {
+ GimpPaintCore *core = paint_tool->core;
+ GimpImage *image = gimp_display_get_image (draw_tool->display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpCanvasItem *outline = NULL;
+ gboolean line_drawn = FALSE;
+ gdouble cur_x, cur_y;
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ if (gimp_paint_tool_paint_is_active (paint_tool) &&
+ paint_tool->snap_brush)
+ {
+ cur_x = paint_tool->paint_x + off_x;
+ cur_y = paint_tool->paint_y + off_y;
+ }
+ else
+ {
+ cur_x = paint_tool->cursor_x + off_x;
+ cur_y = paint_tool->cursor_y + off_y;
+
+ if (paint_tool->draw_line &&
+ ! gimp_tool_control_is_active (GIMP_TOOL (draw_tool)->control))
+ {
+ GimpCanvasGroup *group;
+ gdouble last_x, last_y;
+
+ last_x = core->last_coords.x + off_x;
+ last_y = core->last_coords.y + off_y;
+
+ group = gimp_draw_tool_add_stroke_group (draw_tool);
+ gimp_draw_tool_push_group (draw_tool, group);
+
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CIRCLE,
+ last_x, last_y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ gimp_draw_tool_add_line (draw_tool,
+ last_x, last_y,
+ cur_x, cur_y);
+
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CIRCLE,
+ cur_x, cur_y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ gimp_draw_tool_pop_group (draw_tool);
+
+ line_drawn = TRUE;
+ }
+ }
+
+ gimp_paint_tool_set_draw_fallback (paint_tool, FALSE, 0.0);
+
+ if (paint_tool->draw_brush)
+ outline = gimp_paint_tool_get_outline (paint_tool,
+ draw_tool->display,
+ cur_x, cur_y);
+
+ if (outline)
+ {
+ gimp_draw_tool_add_item (draw_tool, outline);
+ g_object_unref (outline);
+ }
+ else if (paint_tool->draw_fallback)
+ {
+ /* Lets make a sensible fallback cursor
+ *
+ * Sensible cursor is
+ * * crossed to indicate draw point
+ * * reactive to options alterations
+ * * not a full circle that would be in the way
+ */
+ gint size = (gint) paint_tool->fallback_size;
+
+#define TICKMARK_ANGLE 48
+#define ROTATION_ANGLE G_PI / 4
+
+ /* marks for indicating full size */
+ gimp_draw_tool_add_arc (draw_tool,
+ FALSE,
+ cur_x - (size / 2.0),
+ cur_y - (size / 2.0),
+ size, size,
+ ROTATION_ANGLE - (2.0 * G_PI) / (TICKMARK_ANGLE * 2),
+ (2.0 * G_PI) / TICKMARK_ANGLE);
+
+ gimp_draw_tool_add_arc (draw_tool,
+ FALSE,
+ cur_x - (size / 2.0),
+ cur_y - (size / 2.0),
+ size, size,
+ ROTATION_ANGLE + G_PI / 2 - (2.0 * G_PI) / (TICKMARK_ANGLE * 2),
+ (2.0 * G_PI) / TICKMARK_ANGLE);
+
+ gimp_draw_tool_add_arc (draw_tool,
+ FALSE,
+ cur_x - (size / 2.0),
+ cur_y - (size / 2.0),
+ size, size,
+ ROTATION_ANGLE + G_PI - (2.0 * G_PI) / (TICKMARK_ANGLE * 2),
+ (2.0 * G_PI) / TICKMARK_ANGLE);
+
+ gimp_draw_tool_add_arc (draw_tool,
+ FALSE,
+ cur_x - (size / 2.0),
+ cur_y - (size / 2.0),
+ size, size,
+ ROTATION_ANGLE + 3 * G_PI / 2 - (2.0 * G_PI) / (TICKMARK_ANGLE * 2),
+ (2.0 * G_PI) / TICKMARK_ANGLE);
+ }
+ else if (paint_tool->draw_circle)
+ {
+ gint size = (gint) paint_tool->circle_size;
+
+ /* draw an indicatory circle */
+ gimp_draw_tool_add_arc (draw_tool,
+ FALSE,
+ cur_x - (size / 2.0),
+ cur_y - (size / 2.0),
+ size, size,
+ 0.0, (2.0 * G_PI));
+ }
+
+ if (! outline &&
+ ! line_drawn &&
+ ! paint_tool->show_cursor &&
+ ! paint_tool->draw_circle)
+ {
+ /* don't leave the user without any indication and draw
+ * a fallback crosshair
+ */
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CROSSHAIR,
+ cur_x, cur_y,
+ GIMP_TOOL_HANDLE_SIZE_CROSSHAIR,
+ GIMP_TOOL_HANDLE_SIZE_CROSSHAIR,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+ }
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+}
+
+static void
+gimp_paint_tool_real_paint_prepare (GimpPaintTool *paint_tool,
+ GimpDisplay *display)
+{
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ gimp_paint_core_set_show_all (paint_tool->core, shell->show_all);
+}
+
+static GimpCanvasItem *
+gimp_paint_tool_get_outline (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y)
+{
+ if (GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->get_outline)
+ return GIMP_PAINT_TOOL_GET_CLASS (paint_tool)->get_outline (paint_tool,
+ display, x, y);
+
+ return NULL;
+}
+
+static gboolean
+gimp_paint_tool_check_alpha (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpPaintToolClass *klass = GIMP_PAINT_TOOL_GET_CLASS (paint_tool);
+
+ if (klass->is_alpha_only && klass->is_alpha_only (paint_tool, drawable))
+ {
+ if (! gimp_drawable_has_alpha (drawable))
+ {
+ g_set_error_literal (
+ error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer does not have an alpha channel."));
+
+ return FALSE;
+ }
+
+ if (GIMP_IS_LAYER (drawable) &&
+ gimp_layer_get_lock_alpha (GIMP_LAYER (drawable)))
+ {
+ g_set_error_literal (
+ error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer's alpha channel is locked."));
+
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_paint_tool_hard_notify (GimpPaintOptions *options,
+ const GParamSpec *pspec,
+ GimpPaintTool *paint_tool)
+{
+ if (paint_tool->active)
+ {
+ GimpTool *tool = GIMP_TOOL (paint_tool);
+
+ gimp_tool_control_set_precision (tool->control,
+ options->hard ?
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER :
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ }
+}
+
+static void
+gimp_paint_tool_cursor_notify (GimpDisplayConfig *config,
+ GParamSpec *pspec,
+ GimpPaintTool *paint_tool)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (paint_tool));
+
+ paint_tool->show_cursor = config->show_paint_tool_cursor;
+ paint_tool->draw_brush = config->show_brush_outline;
+ paint_tool->snap_brush = config->snap_brush_outline;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (paint_tool));
+}
+
+void
+gimp_paint_tool_set_active (GimpPaintTool *tool,
+ gboolean active)
+{
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (tool));
+
+ if (active != tool->active)
+ {
+ GimpPaintOptions *options = GIMP_PAINT_TOOL_GET_OPTIONS (tool);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ tool->active = active;
+
+ if (active)
+ gimp_paint_tool_hard_notify (options, NULL, tool);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+}
+
+/**
+ * gimp_paint_tool_enable_color_picker:
+ * @tool: a #GimpPaintTool
+ * @target: the #GimpColorPickTarget to set
+ *
+ * This is a convenience function used from the init method of paint
+ * tools that want the color picking functionality. The @mode that is
+ * set here is used to decide what cursor modifier to draw and if the
+ * picked color goes to the foreground or background color.
+ **/
+void
+gimp_paint_tool_enable_color_picker (GimpPaintTool *tool,
+ GimpColorPickTarget target)
+{
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (tool));
+
+ tool->pick_colors = TRUE;
+
+ GIMP_COLOR_TOOL (tool)->pick_target = target;
+}
+
+void
+gimp_paint_tool_set_draw_fallback (GimpPaintTool *tool,
+ gboolean draw_fallback,
+ gint fallback_size)
+{
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (tool));
+
+ tool->draw_fallback = draw_fallback;
+ tool->fallback_size = fallback_size;
+}
+
+void
+gimp_paint_tool_set_draw_circle (GimpPaintTool *tool,
+ gboolean draw_circle,
+ gint circle_size)
+{
+ g_return_if_fail (GIMP_IS_PAINT_TOOL (tool));
+
+ tool->draw_circle = draw_circle;
+ tool->circle_size = circle_size;
+}
diff --git a/app/tools/gimppainttool.h b/app/tools/gimppainttool.h
new file mode 100644
index 0000000..faaf9c7
--- /dev/null
+++ b/app/tools/gimppainttool.h
@@ -0,0 +1,109 @@
+/* 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_PAINT_TOOL_H__
+#define __GIMP_PAINT_TOOL_H__
+
+
+#include "gimpcolortool.h"
+
+
+#define GIMP_PAINT_TOOL_LINE_MASK (gimp_get_extend_selection_mask ())
+
+
+#define GIMP_TYPE_PAINT_TOOL (gimp_paint_tool_get_type ())
+#define GIMP_PAINT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINT_TOOL, GimpPaintTool))
+#define GIMP_PAINT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINT_TOOL, GimpPaintToolClass))
+#define GIMP_IS_PAINT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINT_TOOL))
+#define GIMP_IS_PAINT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINT_TOOL))
+#define GIMP_PAINT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINT_TOOL, GimpPaintToolClass))
+
+#define GIMP_PAINT_TOOL_GET_OPTIONS(t) (GIMP_PAINT_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpPaintToolClass GimpPaintToolClass;
+
+struct _GimpPaintTool
+{
+ GimpColorTool parent_instance;
+
+ gboolean active;
+ gboolean pick_colors; /* pick color if ctrl is pressed */
+ gboolean draw_line;
+
+ gboolean show_cursor;
+ gboolean draw_brush;
+ gboolean snap_brush;
+ gboolean draw_fallback;
+ gint fallback_size;
+ gboolean draw_circle;
+ gint circle_size;
+
+ const gchar *status; /* status message */
+ const gchar *status_line; /* status message when drawing a line */
+ const gchar *status_ctrl; /* additional message for the ctrl modifier */
+
+ GimpPaintCore *core;
+
+ GimpDisplay *display;
+ GimpDrawable *drawable;
+
+ gdouble cursor_x;
+ gdouble cursor_y;
+
+ gdouble paint_x;
+ gdouble paint_y;
+};
+
+struct _GimpPaintToolClass
+{
+ GimpColorToolClass parent_class;
+
+ void (* paint_prepare) (GimpPaintTool *paint_tool,
+ GimpDisplay *display);
+ void (* paint_start) (GimpPaintTool *paint_tool);
+ void (* paint_end) (GimpPaintTool *paint_tool);
+ void (* paint_flush) (GimpPaintTool *paint_tool);
+
+ GimpCanvasItem * (* get_outline) (GimpPaintTool *paint_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y);
+
+ gboolean (* is_alpha_only) (GimpPaintTool *paint_tool,
+ GimpDrawable *drawable);
+};
+
+
+GType gimp_paint_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_paint_tool_set_active (GimpPaintTool *tool,
+ gboolean active);
+
+void gimp_paint_tool_enable_color_picker (GimpPaintTool *tool,
+ GimpColorPickTarget target);
+
+void gimp_paint_tool_set_draw_fallback (GimpPaintTool *tool,
+ gboolean draw_fallback,
+ gint fallback_size);
+
+void gimp_paint_tool_set_draw_circle (GimpPaintTool *tool,
+ gboolean draw_circle,
+ gint circle_size);
+
+
+#endif /* __GIMP_PAINT_TOOL_H__ */
diff --git a/app/tools/gimppenciltool.c b/app/tools/gimppenciltool.c
new file mode 100644
index 0000000..70dd1ce
--- /dev/null
+++ b/app/tools/gimppenciltool.c
@@ -0,0 +1,70 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimppenciloptions.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "gimppenciltool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+G_DEFINE_TYPE (GimpPencilTool, gimp_pencil_tool, GIMP_TYPE_PAINTBRUSH_TOOL)
+
+
+void
+gimp_pencil_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_PENCIL_TOOL,
+ GIMP_TYPE_PENCIL_OPTIONS,
+ gimp_paint_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT,
+ "gimp-pencil-tool",
+ _("Pencil"),
+ _("Pencil Tool: Hard edge painting using a brush"),
+ N_("Pe_ncil"), "N",
+ NULL, GIMP_HELP_TOOL_PENCIL,
+ GIMP_ICON_TOOL_PENCIL,
+ data);
+}
+
+static void
+gimp_pencil_tool_class_init (GimpPencilToolClass *klass)
+{
+}
+
+static void
+gimp_pencil_tool_init (GimpPencilTool *pencil)
+{
+ GimpTool *tool = GIMP_TOOL (pencil);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_PENCIL);
+}
diff --git a/app/tools/gimppenciltool.h b/app/tools/gimppenciltool.h
new file mode 100644
index 0000000..2e9b316
--- /dev/null
+++ b/app/tools/gimppenciltool.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 __GIMP_PENCIL_TOOL_H__
+#define __GIMP_PENCIL_TOOL_H__
+
+
+#include "gimppaintbrushtool.h"
+
+
+#define GIMP_TYPE_PENCIL_TOOL (gimp_pencil_tool_get_type ())
+#define GIMP_PENCIL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PENCIL_TOOL, GimpPencilTool))
+#define GIMP_PENCIL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PENCIL_TOOL, GimpPencilToolClass))
+#define GIMP_IS_PENCIL_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PENCIL_TOOL))
+#define GIMP_IS_PENCIL_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PENCIL_TOOL))
+#define GIMP_PENCIL_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PENCIL_TOOL, GimpPencilToolClass))
+
+
+typedef struct _GimpPencilTool GimpPencilTool;
+typedef struct _GimpPencilToolClass GimpPencilToolClass;
+
+struct _GimpPencilTool
+{
+ GimpPaintbrushTool parent_instance;
+};
+
+struct _GimpPencilToolClass
+{
+ GimpPaintbrushToolClass parent_class;
+};
+
+
+void gimp_pencil_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_pencil_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PENCIL_TOOL_H__ */
diff --git a/app/tools/gimpperspectiveclonetool.c b/app/tools/gimpperspectiveclonetool.c
new file mode 100644
index 0000000..ff57634
--- /dev/null
+++ b/app/tools/gimpperspectiveclonetool.c
@@ -0,0 +1,913 @@
+/* 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 "tools-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimpimage.h"
+
+#include "paint/gimpperspectiveclone.h"
+#include "paint/gimpperspectivecloneoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpviewablebox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasgroup.h"
+#include "display/gimpdisplay.h"
+#include "display/gimptooltransformgrid.h"
+
+#include "gimpperspectiveclonetool.h"
+#include "gimpcloneoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* index into trans_info array */
+enum
+{
+ X0,
+ Y0,
+ X1,
+ Y1,
+ X2,
+ Y2,
+ X3,
+ Y3,
+ PIVOT_X,
+ PIVOT_Y
+};
+
+
+static void gimp_perspective_clone_tool_constructed (GObject *object);
+
+static gboolean gimp_perspective_clone_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+
+static gboolean gimp_perspective_clone_tool_has_display (GimpTool *tool,
+ GimpDisplay *display);
+static GimpDisplay *
+ gimp_perspective_clone_tool_has_image (GimpTool *tool,
+ GimpImage *image);
+static void gimp_perspective_clone_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_perspective_clone_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_perspective_clone_tool_halt (GimpPerspectiveCloneTool *clone_tool);
+static void gimp_perspective_clone_tool_bounds (GimpPerspectiveCloneTool *clone_tool,
+ GimpDisplay *display);
+static void gimp_perspective_clone_tool_prepare (GimpPerspectiveCloneTool *clone_tool);
+static void gimp_perspective_clone_tool_recalc_matrix (GimpPerspectiveCloneTool *clone_tool,
+ GimpToolWidget *widget);
+
+static void gimp_perspective_clone_tool_widget_changed (GimpToolWidget *widget,
+ GimpPerspectiveCloneTool *clone_tool);
+static void gimp_perspective_clone_tool_widget_status (GimpToolWidget *widget,
+ const gchar *status,
+ GimpPerspectiveCloneTool *clone_tool);
+
+static GtkWidget *
+ gimp_perspective_clone_options_gui (GimpToolOptions *tool_options);
+
+
+G_DEFINE_TYPE (GimpPerspectiveCloneTool, gimp_perspective_clone_tool,
+ GIMP_TYPE_BRUSH_TOOL)
+
+#define parent_class gimp_perspective_clone_tool_parent_class
+
+
+void
+gimp_perspective_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_PERSPECTIVE_CLONE_TOOL,
+ GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS,
+ gimp_perspective_clone_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_PATTERN,
+ "gimp-perspective-clone-tool",
+ _("Perspective Clone"),
+ _("Perspective Clone Tool: Clone from an image source "
+ "after applying a perspective transformation"),
+ N_("_Perspective Clone"), NULL,
+ NULL, GIMP_HELP_TOOL_PERSPECTIVE_CLONE,
+ GIMP_ICON_TOOL_PERSPECTIVE_CLONE,
+ data);
+}
+
+static void
+gimp_perspective_clone_tool_class_init (GimpPerspectiveCloneToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_perspective_clone_tool_constructed;
+
+ tool_class->initialize = gimp_perspective_clone_tool_initialize;
+ tool_class->has_display = gimp_perspective_clone_tool_has_display;
+ tool_class->has_image = gimp_perspective_clone_tool_has_image;
+ tool_class->control = gimp_perspective_clone_tool_control;
+ tool_class->button_press = gimp_perspective_clone_tool_button_press;
+ tool_class->button_release = gimp_perspective_clone_tool_button_release;
+ tool_class->motion = gimp_perspective_clone_tool_motion;
+ tool_class->modifier_key = gimp_perspective_clone_tool_modifier_key;
+ tool_class->cursor_update = gimp_perspective_clone_tool_cursor_update;
+ tool_class->oper_update = gimp_perspective_clone_tool_oper_update;
+ tool_class->options_notify = gimp_perspective_clone_tool_options_notify;
+
+ draw_tool_class->draw = gimp_perspective_clone_tool_draw;
+}
+
+static void
+gimp_perspective_clone_tool_init (GimpPerspectiveCloneTool *clone_tool)
+{
+ GimpTool *tool = GIMP_TOOL (clone_tool);
+
+ gimp_tool_control_set_action_object_2 (tool->control,
+ "context/context-pattern-select-set");
+
+ gimp_matrix3_identity (&clone_tool->transform);
+}
+
+static void
+gimp_perspective_clone_tool_constructed (GObject *object)
+{
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ gimp_paint_tool_set_active (GIMP_PAINT_TOOL (object), FALSE);
+}
+
+static gboolean
+gimp_perspective_clone_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ return FALSE;
+ }
+
+ if (display != tool->display)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ gint i;
+
+ tool->display = display;
+ tool->drawable = gimp_image_get_active_drawable (image);
+
+ /* Find the transform bounds initializing */
+ gimp_perspective_clone_tool_bounds (clone_tool, display);
+
+ gimp_perspective_clone_tool_prepare (clone_tool);
+
+ /* Recalculate the transform tool */
+ gimp_perspective_clone_tool_recalc_matrix (clone_tool, NULL);
+
+ clone_tool->widget =
+ gimp_tool_transform_grid_new (shell,
+ &clone_tool->transform,
+ clone_tool->x1,
+ clone_tool->y1,
+ clone_tool->x2,
+ clone_tool->y2);
+
+ g_object_set (clone_tool->widget,
+ "pivot-x", (clone_tool->x1 + clone_tool->x2) / 2.0,
+ "pivot-y", (clone_tool->y1 + clone_tool->y2) / 2.0,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-corner-handles", TRUE,
+ "use-perspective-handles", TRUE,
+ "use-side-handles", TRUE,
+ "use-shear-handles", TRUE,
+ "use-pivot-handle", TRUE,
+ NULL);
+
+ g_signal_connect (clone_tool->widget, "changed",
+ G_CALLBACK (gimp_perspective_clone_tool_widget_changed),
+ clone_tool);
+ g_signal_connect (clone_tool->widget, "status",
+ G_CALLBACK (gimp_perspective_clone_tool_widget_status),
+ clone_tool);
+
+ /* start drawing the bounding box and handles... */
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+
+ /* Save the current transformation info */
+ for (i = 0; i < TRANS_INFO_SIZE; i++)
+ clone_tool->old_trans_info[i] = clone_tool->trans_info[i];
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_perspective_clone_tool_has_display (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+
+ return (display == clone_tool->src_display ||
+ GIMP_TOOL_CLASS (parent_class)->has_display (tool, display));
+}
+
+static GimpDisplay *
+gimp_perspective_clone_tool_has_image (GimpTool *tool,
+ GimpImage *image)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpDisplay *display;
+
+ display = GIMP_TOOL_CLASS (parent_class)->has_image (tool, image);
+
+ if (! display && clone_tool->src_display)
+ {
+ if (image && gimp_display_get_image (clone_tool->src_display) == image)
+ display = clone_tool->src_display;
+
+ /* NULL image means any display */
+ if (! image)
+ display = clone_tool->src_display;
+ }
+
+ return display;
+}
+
+static void
+gimp_perspective_clone_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_perspective_clone_tool_halt (clone_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_perspective_clone_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (paint_tool->core);
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (clone);
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ {
+ if (clone_tool->widget)
+ {
+ gimp_tool_widget_hover (clone_tool->widget, coords, state, TRUE);
+
+ if (gimp_tool_widget_button_press (clone_tool->widget, coords,
+ time, state, press_type))
+ {
+ clone_tool->grab_widget = clone_tool->widget;
+ }
+ }
+
+ gimp_tool_control_activate (tool->control);
+ }
+ else
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+ gdouble nnx, nny;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if ((state & (toggle_mask | extend_mask)) == toggle_mask)
+ {
+ source_core->set_source = TRUE;
+
+ clone_tool->src_display = display;
+ }
+ else
+ {
+ source_core->set_source = FALSE;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+
+ /* Set the coordinates for the reference cross */
+ gimp_perspective_clone_get_source_point (clone,
+ coords->x, coords->y,
+ &nnx, &nny);
+
+ clone_tool->src_x = floor (nnx);
+ clone_tool->src_y = floor (nny);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+}
+
+static void
+gimp_perspective_clone_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ switch (options->clone_mode)
+ {
+ case GIMP_PERSPECTIVE_CLONE_MODE_ADJUST:
+ gimp_tool_control_halt (tool->control);
+
+ if (clone_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (clone_tool->grab_widget,
+ coords, time, state, release_type);
+ clone_tool->grab_widget = NULL;
+ }
+ break;
+
+ case GIMP_PERSPECTIVE_CLONE_MODE_PAINT:
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
+ release_type, display);
+ break;
+ }
+}
+
+static void
+gimp_perspective_clone_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (paint_tool->core);
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ {
+ if (clone_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (clone_tool->grab_widget, coords, time, state);
+ }
+ }
+ else if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_PAINT)
+ {
+ gdouble nnx, nny;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
+ display);
+
+ /* Set the coordinates for the reference cross */
+ gimp_perspective_clone_get_source_point (clone,
+ coords->x, coords->y,
+ &nnx, &nny);
+
+ clone_tool->src_x = floor (nnx);
+ clone_tool->src_y = floor (nny);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+}
+
+static void
+gimp_perspective_clone_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_PAINT &&
+ key == gimp_get_toggle_behavior_mask ())
+ {
+ if (press)
+ {
+ clone_tool->saved_precision =
+ gimp_tool_control_get_precision (tool->control);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+ }
+ else
+ {
+ gimp_tool_control_set_precision (tool->control,
+ clone_tool->saved_precision);
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
+ display);
+}
+
+static void
+gimp_perspective_clone_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPerspectiveCloneOptions *options;
+ GimpImage *image;
+ GimpToolClass *tool_class;
+ GimpCursorType cursor = GIMP_CURSOR_MOUSE;
+ GimpToolCursorType tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ image = gimp_display_get_image (display);
+
+ if (gimp_image_coords_in_active_pickable (image, coords,
+ FALSE, FALSE, TRUE))
+ {
+ cursor = GIMP_CURSOR_MOUSE;
+ }
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ {
+ if (clone_tool->widget)
+ {
+ if (display == tool->display)
+ {
+ gimp_tool_widget_get_cursor (clone_tool->widget,
+ coords, state,
+ &cursor, &tool_cursor, &modifier);
+ }
+ }
+ }
+ else
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if ((state & (toggle_mask | extend_mask)) == toggle_mask)
+ {
+ cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ }
+ else if (! GIMP_SOURCE_CORE (GIMP_PAINT_TOOL (tool)->core)->src_drawable)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+
+ tool_cursor = GIMP_TOOL_CURSOR_CLONE;
+ }
+
+ gimp_tool_control_set_cursor (tool->control, cursor);
+ gimp_tool_control_set_tool_cursor (tool->control, tool_cursor);
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ /* If we are in adjust mode, skip the GimpBrushClass when chaining up.
+ * This ensures that the cursor will be set regardless of
+ * GimpBrushTool::show_cursor (see bug #354933).
+ */
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ tool_class = GIMP_TOOL_CLASS (g_type_class_peek_parent (parent_class));
+ else
+ tool_class = GIMP_TOOL_CLASS (parent_class);
+
+ tool_class->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_perspective_clone_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ {
+ if (clone_tool->widget)
+ {
+ if (display == tool->display)
+ {
+ gimp_tool_widget_hover (clone_tool->widget, coords, state,
+ proximity);
+ }
+ }
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+
+ if (proximity)
+ {
+ GimpPaintCore *core = GIMP_PAINT_TOOL (tool)->core;
+ GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (core);
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (core);
+
+ if (source_core->src_drawable == NULL)
+ {
+ gimp_tool_replace_status (tool, display,
+ _("Ctrl-Click to set a clone source"));
+ }
+ else
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ clone_tool->src_x = source_core->src_x;
+ clone_tool->src_y = source_core->src_y;
+
+ if (! source_core->first_stroke)
+ {
+ if (GIMP_SOURCE_OPTIONS (options)->align_mode ==
+ GIMP_SOURCE_ALIGN_YES)
+ {
+ gdouble nnx, nny;
+
+ /* Set the coordinates for the reference cross */
+ gimp_perspective_clone_get_source_point (clone,
+ coords->x,
+ coords->y,
+ &nnx, &nny);
+
+ clone_tool->src_x = floor (nnx);
+ clone_tool->src_y = floor (nny);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+ }
+ }
+}
+
+static void
+gimp_perspective_clone_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (tool);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpPerspectiveCloneOptions *clone_options;
+
+ clone_options = GIMP_PERSPECTIVE_CLONE_OPTIONS (options);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "clone-mode"))
+ {
+ GimpPerspectiveClone *clone;
+
+ clone = GIMP_PERSPECTIVE_CLONE (GIMP_PAINT_TOOL (tool)->core);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (clone_tool));
+
+ if (clone_options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_PAINT)
+ {
+ gimp_perspective_clone_set_transform (clone, &clone_tool->transform);
+
+ gimp_paint_tool_set_active (paint_tool, TRUE);
+ }
+ else
+ {
+ gimp_paint_tool_set_active (paint_tool, FALSE);
+
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+
+ /* start drawing the bounding box and handles... */
+ if (tool->display &&
+ ! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (clone_tool)))
+ {
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (clone_tool), tool->display);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (clone_tool));
+ }
+}
+
+static void
+gimp_perspective_clone_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpTool *tool = GIMP_TOOL (draw_tool);
+ GimpPerspectiveCloneTool *clone_tool = GIMP_PERSPECTIVE_CLONE_TOOL (draw_tool);
+ GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (GIMP_PAINT_TOOL (tool)->core);
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (clone);
+ GimpPerspectiveCloneOptions *options;
+
+ options = GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS (tool);
+
+ if (options->clone_mode == GIMP_PERSPECTIVE_CLONE_MODE_ADJUST)
+ {
+ if (clone_tool->widget)
+ {
+ GimpCanvasItem *item = gimp_tool_widget_get_item (clone_tool->widget);
+
+ gimp_draw_tool_add_item (draw_tool, item);
+ }
+ }
+ else
+ {
+ GimpCanvasGroup *stroke_group;
+
+ stroke_group = gimp_draw_tool_add_stroke_group (draw_tool);
+
+ /* draw the bounding box */
+ gimp_draw_tool_push_group (draw_tool, stroke_group);
+
+ gimp_draw_tool_add_line (draw_tool,
+ clone_tool->trans_info[X0],
+ clone_tool->trans_info[Y0],
+ clone_tool->trans_info[X1],
+ clone_tool->trans_info[Y1]);
+ gimp_draw_tool_add_line (draw_tool,
+ clone_tool->trans_info[X1],
+ clone_tool->trans_info[Y1],
+ clone_tool->trans_info[X3],
+ clone_tool->trans_info[Y3]);
+ gimp_draw_tool_add_line (draw_tool,
+ clone_tool->trans_info[X2],
+ clone_tool->trans_info[Y2],
+ clone_tool->trans_info[X3],
+ clone_tool->trans_info[Y3]);
+ gimp_draw_tool_add_line (draw_tool,
+ clone_tool->trans_info[X2],
+ clone_tool->trans_info[Y2],
+ clone_tool->trans_info[X0],
+ clone_tool->trans_info[Y0]);
+
+ gimp_draw_tool_pop_group (draw_tool);
+ }
+
+ if (source_core->src_drawable && clone_tool->src_display)
+ {
+ GimpDisplay *tmp_display;
+
+ tmp_display = draw_tool->display;
+ draw_tool->display = clone_tool->src_display;
+
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CROSS,
+ clone_tool->src_x + 0.5,
+ clone_tool->src_y + 0.5,
+ GIMP_TOOL_HANDLE_SIZE_CROSS,
+ GIMP_TOOL_HANDLE_SIZE_CROSS,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ draw_tool->display = tmp_display;
+ }
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+}
+
+static void
+gimp_perspective_clone_tool_halt (GimpPerspectiveCloneTool *clone_tool)
+{
+ GimpTool *tool = GIMP_TOOL (clone_tool);
+
+ clone_tool->src_display = NULL;
+
+ g_object_set (GIMP_PAINT_TOOL (tool)->core,
+ "src-drawable", NULL,
+ NULL);
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ g_clear_object (&clone_tool->widget);
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+}
+
+static void
+gimp_perspective_clone_tool_bounds (GimpPerspectiveCloneTool *clone_tool,
+ GimpDisplay *display)
+{
+ GimpImage *image = gimp_display_get_image (display);
+
+ clone_tool->x1 = 0;
+ clone_tool->y1 = 0;
+ clone_tool->x2 = gimp_image_get_width (image);
+ clone_tool->y2 = gimp_image_get_height (image);
+}
+
+static void
+gimp_perspective_clone_tool_prepare (GimpPerspectiveCloneTool *clone_tool)
+{
+ clone_tool->trans_info[PIVOT_X] = (gdouble) (clone_tool->x1 + clone_tool->x2) / 2.0;
+ clone_tool->trans_info[PIVOT_Y] = (gdouble) (clone_tool->y1 + clone_tool->y2) / 2.0;
+
+ clone_tool->trans_info[X0] = clone_tool->x1;
+ clone_tool->trans_info[Y0] = clone_tool->y1;
+ clone_tool->trans_info[X1] = clone_tool->x2;
+ clone_tool->trans_info[Y1] = clone_tool->y1;
+ clone_tool->trans_info[X2] = clone_tool->x1;
+ clone_tool->trans_info[Y2] = clone_tool->y2;
+ clone_tool->trans_info[X3] = clone_tool->x2;
+ clone_tool->trans_info[Y3] = clone_tool->y2;
+}
+
+static void
+gimp_perspective_clone_tool_recalc_matrix (GimpPerspectiveCloneTool *clone_tool,
+ GimpToolWidget *widget)
+{
+ gimp_matrix3_identity (&clone_tool->transform);
+ gimp_transform_matrix_perspective (&clone_tool->transform,
+ clone_tool->x1,
+ clone_tool->y1,
+ clone_tool->x2 - clone_tool->x1,
+ clone_tool->y2 - clone_tool->y1,
+ clone_tool->trans_info[X0],
+ clone_tool->trans_info[Y0],
+ clone_tool->trans_info[X1],
+ clone_tool->trans_info[Y1],
+ clone_tool->trans_info[X2],
+ clone_tool->trans_info[Y2],
+ clone_tool->trans_info[X3],
+ clone_tool->trans_info[Y3]);
+
+ if (widget)
+ g_object_set (widget,
+ "transform", &clone_tool->transform,
+ "x1", (gdouble) clone_tool->x1,
+ "y1", (gdouble) clone_tool->y1,
+ "x2", (gdouble) clone_tool->x2,
+ "y2", (gdouble) clone_tool->y2,
+ "pivot-x", clone_tool->trans_info[PIVOT_X],
+ "pivot-y", clone_tool->trans_info[PIVOT_Y],
+ NULL);
+}
+
+static void
+gimp_perspective_clone_tool_widget_changed (GimpToolWidget *widget,
+ GimpPerspectiveCloneTool *clone_tool)
+{
+ GimpMatrix3 *transform;
+
+ g_object_get (widget,
+ "transform", &transform,
+ "pivot-x", &clone_tool->trans_info[PIVOT_X],
+ "pivot-y", &clone_tool->trans_info[PIVOT_Y],
+ NULL);
+
+ gimp_matrix3_transform_point (transform,
+ clone_tool->x1, clone_tool->y1,
+ &clone_tool->trans_info[X0],
+ &clone_tool->trans_info[Y0]);
+ gimp_matrix3_transform_point (transform,
+ clone_tool->x2, clone_tool->y1,
+ &clone_tool->trans_info[X1],
+ &clone_tool->trans_info[Y1]);
+ gimp_matrix3_transform_point (transform,
+ clone_tool->x1, clone_tool->y2,
+ &clone_tool->trans_info[X2],
+ &clone_tool->trans_info[Y2]);
+ gimp_matrix3_transform_point (transform,
+ clone_tool->x2, clone_tool->y2,
+ &clone_tool->trans_info[X3],
+ &clone_tool->trans_info[Y3]);
+
+ g_free (transform);
+
+ gimp_perspective_clone_tool_recalc_matrix (clone_tool, NULL);
+}
+
+static void
+gimp_perspective_clone_tool_widget_status (GimpToolWidget *widget,
+ const gchar *status,
+ GimpPerspectiveCloneTool *clone_tool)
+{
+ GimpTool *tool = GIMP_TOOL (clone_tool);
+
+ if (status)
+ {
+ gimp_tool_replace_status (tool, tool->display, "%s", status);
+ }
+ else
+ {
+ gimp_tool_pop_status (tool, tool->display);
+ }
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_perspective_clone_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_clone_options_gui (tool_options);
+ GtkWidget *mode;
+
+ /* radio buttons to set if you are modifying the perspective plane
+ * or painting
+ */
+ mode = gimp_prop_enum_radio_box_new (config, "clone-mode", 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), mode, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (vbox), mode, 0);
+ gtk_widget_show (mode);
+
+ return vbox;
+}
diff --git a/app/tools/gimpperspectiveclonetool.h b/app/tools/gimpperspectiveclonetool.h
new file mode 100644
index 0000000..d0c4697
--- /dev/null
+++ b/app/tools/gimpperspectiveclonetool.h
@@ -0,0 +1,72 @@
+/* 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_PERSPECTIVE_CLONE_TOOL_H__
+#define __GIMP_PERSPECTIVE_CLONE_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+#include "gimptransformtool.h" /* for TransInfo */
+
+
+#define GIMP_TYPE_PERSPECTIVE_CLONE_TOOL (gimp_perspective_clone_tool_get_type ())
+#define GIMP_PERSPECTIVE_CLONE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PERSPECTIVE_CLONE_TOOL, GimpPerspectiveCloneTool))
+#define GIMP_PERSPECTIVE_CLONE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PERSPECTIVE_CLONE_TOOL, GimpPerspectiveCloneToolClass))
+#define GIMP_IS_PERSPECTIVE_CLONE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PERSPECTIVE_CLONE_TOOL))
+#define GIMP_IS_PERSPECTIVE_CLONE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PERSPECTIVE_CLONE_TOOL))
+#define GIMP_PERSPECTIVE_CLONE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PERSPECTIVE_CLONE_TOOL, GimpPerspectiveCloneToolClass))
+
+#define GIMP_PERSPECTIVE_CLONE_TOOL_GET_OPTIONS(t) (GIMP_PERSPECTIVE_CLONE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpPerspectiveCloneTool GimpPerspectiveCloneTool;
+typedef struct _GimpPerspectiveCloneToolClass GimpPerspectiveCloneToolClass;
+
+struct _GimpPerspectiveCloneTool
+{
+ GimpBrushTool parent_instance;
+
+ GimpDisplay *src_display;
+ gint src_x;
+ gint src_y;
+
+ GimpMatrix3 transform; /* transformation matrix */
+ TransInfo trans_info; /* transformation info */
+ TransInfo old_trans_info; /* for cancelling a drag operation */
+
+ gint x1, y1; /* upper left hand coordinate */
+ gint x2, y2; /* lower right hand coords */
+
+ GimpCursorPrecision saved_precision;
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+};
+
+struct _GimpPerspectiveCloneToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+void gimp_perspective_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_perspective_clone_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PERSPECTIVE_CLONE_TOOL_H__ */
diff --git a/app/tools/gimpperspectivetool.c b/app/tools/gimpperspectivetool.c
new file mode 100644
index 0000000..c6f7a5d
--- /dev/null
+++ b/app/tools/gimpperspectivetool.c
@@ -0,0 +1,292 @@
+/* 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 "tools-types.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptooltransformgrid.h"
+
+#include "gimpperspectivetool.h"
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+
+#include "gimp-intl.h"
+
+
+/* index into trans_info array */
+enum
+{
+ X0,
+ Y0,
+ X1,
+ Y1,
+ X2,
+ Y2,
+ X3,
+ Y3
+};
+
+
+/* local function prototypes */
+
+static void gimp_perspective_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+static void gimp_perspective_tool_prepare (GimpTransformGridTool *tg_tool);
+static void gimp_perspective_tool_readjust (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_perspective_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_perspective_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_perspective_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void gimp_perspective_tool_info_to_points (GimpGenericTransformTool *generic);
+
+
+G_DEFINE_TYPE (GimpPerspectiveTool, gimp_perspective_tool,
+ GIMP_TYPE_GENERIC_TRANSFORM_TOOL)
+
+#define parent_class gimp_perspective_tool_parent_class
+
+
+void
+gimp_perspective_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_PERSPECTIVE_TOOL,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS,
+ gimp_transform_grid_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-perspective-tool",
+ _("Perspective"),
+ _("Perspective Tool: "
+ "Change perspective of the layer, selection or path"),
+ N_("_Perspective"), "<shift>P",
+ NULL, GIMP_HELP_TOOL_PERSPECTIVE,
+ GIMP_ICON_TOOL_PERSPECTIVE,
+ data);
+}
+
+static void
+gimp_perspective_tool_class_init (GimpPerspectiveToolClass *klass)
+{
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+ GimpGenericTransformToolClass *generic_class = GIMP_GENERIC_TRANSFORM_TOOL_CLASS (klass);
+
+ tg_class->matrix_to_info = gimp_perspective_tool_matrix_to_info;
+ tg_class->prepare = gimp_perspective_tool_prepare;
+ tg_class->readjust = gimp_perspective_tool_readjust;
+ tg_class->get_widget = gimp_perspective_tool_get_widget;
+ tg_class->update_widget = gimp_perspective_tool_update_widget;
+ tg_class->widget_changed = gimp_perspective_tool_widget_changed;
+
+ generic_class->info_to_points = gimp_perspective_tool_info_to_points;
+
+ tr_class->undo_desc = C_("undo-type", "Perspective");
+ tr_class->progress_text = _("Perspective transformation");
+}
+
+static void
+gimp_perspective_tool_init (GimpPerspectiveTool *perspective_tool)
+{
+ GimpTool *tool = GIMP_TOOL (perspective_tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PERSPECTIVE);
+}
+
+static void
+gimp_perspective_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2,
+ tr_tool->y1,
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1,
+ tr_tool->y2,
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2,
+ tr_tool->y2,
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+}
+
+static void
+gimp_perspective_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->prepare (tg_tool);
+
+ tg_tool->trans_info[X0] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y0] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X1] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y1] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X2] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y2] = (gdouble) tr_tool->y2;
+ tg_tool->trans_info[X3] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y3] = (gdouble) tr_tool->y2;
+}
+
+static void
+gimp_perspective_tool_readjust (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ gdouble x;
+ gdouble y;
+ gdouble r;
+
+ x = shell->disp_width / 2.0;
+ y = shell->disp_height / 2.0;
+ r = MAX (MIN (x, y) / G_SQRT2 -
+ GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE / 2.0,
+ GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE / 2.0);
+
+ gimp_display_shell_untransform_xy_f (shell,
+ x - r, y - r,
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_display_shell_untransform_xy_f (shell,
+ x + r, y - r,
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_display_shell_untransform_xy_f (shell,
+ x - r, y + r,
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_display_shell_untransform_xy_f (shell,
+ x + r, y + r,
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+}
+
+static GimpToolWidget *
+gimp_perspective_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+
+ widget = gimp_tool_transform_grid_new (shell,
+ &tr_tool->transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2);
+
+ g_object_set (widget,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_PERSPECTIVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_PERSPECTIVE,
+ "use-perspective-handles", TRUE,
+ "use-center-handle", TRUE,
+ NULL);
+
+ return widget;
+}
+
+static void
+gimp_perspective_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ g_object_set (tg_tool->widget,
+ "x1", (gdouble) tr_tool->x1,
+ "y1", (gdouble) tr_tool->y1,
+ "x2", (gdouble) tr_tool->x2,
+ "y2", (gdouble) tr_tool->y2,
+ NULL);
+}
+
+static void
+gimp_perspective_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpMatrix3 *transform;
+
+ g_object_get (tg_tool->widget,
+ "transform", &transform,
+ NULL);
+
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1, tr_tool->y1,
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2, tr_tool->y1,
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1, tr_tool->y2,
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2, tr_tool->y2,
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+
+ g_free (transform);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+gimp_perspective_tool_info_to_points (GimpGenericTransformTool *generic)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (generic);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (generic);
+
+ generic->input_points[0] = (GimpVector2) {tr_tool->x1, tr_tool->y1};
+ generic->input_points[1] = (GimpVector2) {tr_tool->x2, tr_tool->y1};
+ generic->input_points[2] = (GimpVector2) {tr_tool->x1, tr_tool->y2};
+ generic->input_points[3] = (GimpVector2) {tr_tool->x2, tr_tool->y2};
+
+ generic->output_points[0] = (GimpVector2) {tg_tool->trans_info[X0],
+ tg_tool->trans_info[Y0]};
+ generic->output_points[1] = (GimpVector2) {tg_tool->trans_info[X1],
+ tg_tool->trans_info[Y1]};
+ generic->output_points[2] = (GimpVector2) {tg_tool->trans_info[X2],
+ tg_tool->trans_info[Y2]};
+ generic->output_points[3] = (GimpVector2) {tg_tool->trans_info[X3],
+ tg_tool->trans_info[Y3]};
+}
diff --git a/app/tools/gimpperspectivetool.h b/app/tools/gimpperspectivetool.h
new file mode 100644
index 0000000..b037747
--- /dev/null
+++ b/app/tools/gimpperspectivetool.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 __GIMP_PERSPECTIVE_TOOL_H__
+#define __GIMP_PERSPECTIVE_TOOL_H__
+
+
+#include "gimpgenerictransformtool.h"
+
+
+#define GIMP_TYPE_PERSPECTIVE_TOOL (gimp_perspective_tool_get_type ())
+#define GIMP_PERSPECTIVE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PERSPECTIVE_TOOL, GimpPerspectiveTool))
+#define GIMP_PERSPECTIVE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PERSPECTIVE_TOOL, GimpPerspectiveToolClass))
+#define GIMP_IS_PERSPECTIVE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PERSPECTIVE_TOOL))
+#define GIMP_IS_PERSPECTIVE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PERSPECTIVE_TOOL))
+#define GIMP_PERSPECTIVE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PERSPECTIVE_TOOL, GimpPerspectiveToolClass))
+
+
+typedef struct _GimpPerspectiveTool GimpPerspectiveTool;
+typedef struct _GimpPerspectiveToolClass GimpPerspectiveToolClass;
+
+struct _GimpPerspectiveTool
+{
+ GimpGenericTransformTool parent_instance;
+};
+
+struct _GimpPerspectiveToolClass
+{
+ GimpGenericTransformToolClass parent_class;
+};
+
+
+void gimp_perspective_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_perspective_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PERSPECTIVE_TOOL_H__ */
diff --git a/app/tools/gimppolygonselecttool.c b/app/tools/gimppolygonselecttool.c
new file mode 100644
index 0000000..878e226
--- /dev/null
+++ b/app/tools/gimppolygonselecttool.c
@@ -0,0 +1,555 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * Major improvement to support polygonal segments
+ * Copyright (C) 2008 Martin Nordholts
+ *
+ * This program is polygon software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Polygon 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 "tools-types.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimpchannel-select.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer-floating-selection.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolpolygon.h"
+
+#include "gimppolygonselecttool.h"
+#include "gimpselectionoptions.h"
+#include "gimptoolcontrol.h"
+
+
+struct _GimpPolygonSelectToolPrivate
+{
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+
+ gboolean pending_response;
+ gint pending_response_id;
+};
+
+
+/* local function prototypes */
+
+static void gimp_polygon_select_tool_finalize (GObject *object);
+
+static void gimp_polygon_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_polygon_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_polygon_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_polygon_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_polygon_select_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_polygon_select_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_polygon_select_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_polygon_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_polygon_select_tool_real_confirm (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display);
+
+static void gimp_polygon_select_tool_polygon_change_complete (GimpToolWidget *polygon,
+ GimpPolygonSelectTool *poly_sel);
+static void gimp_polygon_select_tool_polygon_response (GimpToolWidget *polygon,
+ gint response_id,
+ GimpPolygonSelectTool *poly_sel);
+
+static void gimp_polygon_select_tool_start (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpPolygonSelectTool, gimp_polygon_select_tool,
+ GIMP_TYPE_SELECTION_TOOL)
+
+#define parent_class gimp_polygon_select_tool_parent_class
+
+
+/* private functions */
+
+static void
+gimp_polygon_select_tool_class_init (GimpPolygonSelectToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_polygon_select_tool_finalize;
+
+ tool_class->control = gimp_polygon_select_tool_control;
+ tool_class->button_press = gimp_polygon_select_tool_button_press;
+ tool_class->button_release = gimp_polygon_select_tool_button_release;
+ tool_class->motion = gimp_polygon_select_tool_motion;
+ tool_class->key_press = gimp_polygon_select_tool_key_press;
+ tool_class->modifier_key = gimp_polygon_select_tool_modifier_key;
+ tool_class->oper_update = gimp_polygon_select_tool_oper_update;
+ tool_class->cursor_update = gimp_polygon_select_tool_cursor_update;
+
+ klass->change_complete = NULL;
+ klass->confirm = gimp_polygon_select_tool_real_confirm;
+}
+
+static void
+gimp_polygon_select_tool_init (GimpPolygonSelectTool *poly_sel)
+{
+ GimpTool *tool = GIMP_TOOL (poly_sel);
+ GimpSelectionTool *sel_tool = GIMP_SELECTION_TOOL (tool);
+
+ poly_sel->priv = gimp_polygon_select_tool_get_instance_private (poly_sel);
+
+ gimp_tool_control_set_motion_mode (tool->control,
+ GIMP_MOTION_MODE_EXACT);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_wants_double_click (tool->control, TRUE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+
+ sel_tool->allow_move = FALSE;
+}
+
+static void
+gimp_polygon_select_tool_finalize (GObject *object)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (object);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ g_clear_object (&priv->widget);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_polygon_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_polygon_select_tool_halt (poly_sel);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_polygon_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ if (tool->display && tool->display != display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+
+ if (! priv->widget) /* not tool->display, we have a subclass */
+ {
+ /* First of all handle delegation to the selection mask edit logic
+ * if appropriate.
+ */
+ if (gimp_selection_tool_start_edit (GIMP_SELECTION_TOOL (poly_sel),
+ display, coords))
+ {
+ return;
+ }
+
+ gimp_polygon_select_tool_start (poly_sel, display);
+
+ gimp_tool_widget_hover (priv->widget, coords, state, TRUE);
+ }
+
+ if (gimp_tool_widget_button_press (priv->widget, coords, time, state,
+ press_type))
+ {
+ priv->grab_widget = priv->widget;
+ }
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_polygon_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+ GimpImage *image = gimp_display_get_image (display);
+
+ gimp_tool_control_halt (tool->control);
+
+ switch (release_type)
+ {
+ case GIMP_BUTTON_RELEASE_CLICK:
+ case GIMP_BUTTON_RELEASE_NO_MOTION:
+ /* If there is a floating selection, anchor it */
+ if (gimp_image_get_floating_selection (image))
+ {
+ floating_sel_anchor (gimp_image_get_floating_selection (image));
+
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ return;
+ }
+
+ /* fallthru */
+
+ default:
+ if (priv->grab_widget)
+ {
+ gimp_tool_widget_button_release (priv->grab_widget,
+ coords, time, state, release_type);
+ priv->grab_widget = NULL;
+ }
+ }
+
+ if (priv->pending_response)
+ {
+ gimp_polygon_select_tool_polygon_response (priv->widget,
+ priv->pending_response_id,
+ poly_sel);
+
+ priv->pending_response = FALSE;
+ }
+}
+
+static void
+gimp_polygon_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ if (priv->grab_widget)
+ {
+ gimp_tool_widget_motion (priv->grab_widget, coords, time, state);
+ }
+}
+
+static gboolean
+gimp_polygon_select_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ if (priv->widget && display == tool->display)
+ {
+ return gimp_tool_widget_key_press (priv->widget, kevent);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_polygon_select_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ if (priv->widget && display == tool->display)
+ {
+ gimp_tool_widget_hover_modifier (priv->widget, key, press, state);
+
+ /* let GimpSelectTool handle alt+<mod> */
+ if (! (state & GDK_MOD1_MASK))
+ {
+ /* otherwise, shift/ctrl are handled by the widget */
+ state &= ~(gimp_get_extend_selection_mask () |
+ gimp_get_modify_selection_mask ());
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
+ display);
+}
+
+static void
+gimp_polygon_select_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ if (priv->widget && display == tool->display)
+ {
+ gimp_tool_widget_hover (priv->widget, coords, state, proximity);
+
+ /* let GimpSelectTool handle alt+<mod> */
+ if (! (state & GDK_MOD1_MASK))
+ {
+ /* otherwise, shift/ctrl are handled by the widget */
+ state &= ~(gimp_get_extend_selection_mask () |
+ gimp_get_modify_selection_mask ());
+ }
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+}
+
+static void
+gimp_polygon_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPolygonSelectTool *poly_sel = GIMP_POLYGON_SELECT_TOOL (tool);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ if (tool->display)
+ {
+ if (priv->widget && display == tool->display)
+ {
+ gimp_tool_widget_get_cursor (priv->widget, coords, state,
+ NULL, NULL, &modifier);
+
+ /* let GimpSelectTool handle alt+<mod> */
+ if (! (state & GDK_MOD1_MASK))
+ {
+ /* otherwise, shift/ctrl are handled by the widget */
+ state &= ~(gimp_get_extend_selection_mask () |
+ gimp_get_modify_selection_mask ());
+ }
+ }
+
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ modifier);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+}
+
+static void
+gimp_polygon_select_tool_real_confirm (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display)
+{
+ gimp_tool_control (GIMP_TOOL (poly_sel), GIMP_TOOL_ACTION_COMMIT, display);
+}
+
+static void
+gimp_polygon_select_tool_polygon_change_complete (GimpToolWidget *polygon,
+ GimpPolygonSelectTool *poly_sel)
+{
+ if (GIMP_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->change_complete)
+ {
+ GIMP_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->change_complete (
+ poly_sel, GIMP_TOOL (poly_sel)->display);
+ }
+}
+
+static void
+gimp_polygon_select_tool_polygon_response (GimpToolWidget *polygon,
+ gint response_id,
+ GimpPolygonSelectTool *poly_sel)
+{
+ GimpTool *tool = GIMP_TOOL (poly_sel);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+
+ /* if we're in the middle of a click, defer the response to the
+ * button_release() event
+ */
+ if (gimp_tool_control_is_active (tool->control))
+ {
+ priv->pending_response = TRUE;
+ priv->pending_response_id = response_id;
+
+ return;
+ }
+
+ switch (response_id)
+ {
+ case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
+ /* don't gimp_tool_control(COMMIT) here because we don't always
+ * want to HALT the tool after committing because we have a
+ * subclass, we do that in the default implementation of
+ * confirm().
+ */
+ if (GIMP_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->confirm)
+ {
+ GIMP_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->confirm (
+ poly_sel, tool->display);
+ }
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ break;
+ }
+}
+
+static void
+gimp_polygon_select_tool_start (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (poly_sel);
+ GimpPolygonSelectToolPrivate *priv = poly_sel->priv;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ tool->display = display;
+
+ priv->widget = gimp_tool_polygon_new (shell);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), priv->widget);
+
+ g_signal_connect (priv->widget, "change-complete",
+ G_CALLBACK (gimp_polygon_select_tool_polygon_change_complete),
+ poly_sel);
+ g_signal_connect (priv->widget, "response",
+ G_CALLBACK (gimp_polygon_select_tool_polygon_response),
+ poly_sel);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+}
+
+
+/* public functions */
+
+gboolean
+gimp_polygon_select_tool_is_closed (GimpPolygonSelectTool *poly_sel)
+{
+ GimpPolygonSelectToolPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_POLYGON_SELECT_TOOL (poly_sel), FALSE);
+
+ priv = poly_sel->priv;
+
+ if (priv->widget)
+ return gimp_tool_polygon_is_closed (GIMP_TOOL_POLYGON (priv->widget));
+
+ return FALSE;
+}
+
+void
+gimp_polygon_select_tool_get_points (GimpPolygonSelectTool *poly_sel,
+ const GimpVector2 **points,
+ gint *n_points)
+{
+ GimpPolygonSelectToolPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_POLYGON_SELECT_TOOL (poly_sel));
+
+ priv = poly_sel->priv;
+
+ if (priv->widget)
+ {
+ gimp_tool_polygon_get_points (GIMP_TOOL_POLYGON (priv->widget),
+ points, n_points);
+ }
+ else
+ {
+ if (points) *points = NULL;
+ if (n_points) *n_points = 0;
+ }
+}
+
+
+/* protected functions */
+
+gboolean
+gimp_polygon_select_tool_is_grabbed (GimpPolygonSelectTool *poly_sel)
+{
+ GimpPolygonSelectToolPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_POLYGON_SELECT_TOOL (poly_sel), FALSE);
+
+ priv = poly_sel->priv;
+
+ return priv->grab_widget != NULL;
+}
+
+void
+gimp_polygon_select_tool_halt (GimpPolygonSelectTool *poly_sel)
+{
+ GimpPolygonSelectToolPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_POLYGON_SELECT_TOOL (poly_sel));
+
+ priv = poly_sel->priv;
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (poly_sel), NULL);
+ g_clear_object (&priv->widget);
+}
diff --git a/app/tools/gimppolygonselecttool.h b/app/tools/gimppolygonselecttool.h
new file mode 100644
index 0000000..13de063
--- /dev/null
+++ b/app/tools/gimppolygonselecttool.h
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is polygon software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Polygon 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_POLYGON_SELECT_TOOL_H__
+#define __GIMP_POLYGON_SELECT_TOOL_H__
+
+
+#include "gimpselectiontool.h"
+
+
+#define GIMP_TYPE_POLYGON_SELECT_TOOL (gimp_polygon_select_tool_get_type ())
+#define GIMP_POLYGON_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_POLYGON_SELECT_TOOL, GimpPolygonSelectTool))
+#define GIMP_POLYGON_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_POLYGON_SELECT_TOOL, GimpPolygonSelectToolClass))
+#define GIMP_IS_POLYGON_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_POLYGON_SELECT_TOOL))
+#define GIMP_IS_POLYGON_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_POLYGON_SELECT_TOOL))
+#define GIMP_POLYGON_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_POLYGON_SELECT_TOOL, GimpPolygonSelectToolClass))
+
+
+typedef struct _GimpPolygonSelectTool GimpPolygonSelectTool;
+typedef struct _GimpPolygonSelectToolPrivate GimpPolygonSelectToolPrivate;
+typedef struct _GimpPolygonSelectToolClass GimpPolygonSelectToolClass;
+
+struct _GimpPolygonSelectTool
+{
+ GimpSelectionTool parent_instance;
+
+ GimpPolygonSelectToolPrivate *priv;
+};
+
+struct _GimpPolygonSelectToolClass
+{
+ GimpSelectionToolClass parent_class;
+
+ /* virtual functions */
+ void (* change_complete) (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display);
+ void (* confirm) (GimpPolygonSelectTool *poly_sel,
+ GimpDisplay *display);
+};
+
+
+GType gimp_polygon_select_tool_get_type (void) G_GNUC_CONST;
+
+gboolean gimp_polygon_select_tool_is_closed (GimpPolygonSelectTool *poly_sel);
+void gimp_polygon_select_tool_get_points (GimpPolygonSelectTool *poly_sel,
+ const GimpVector2 **points,
+ gint *n_points);
+
+/* protected functions */
+gboolean gimp_polygon_select_tool_is_grabbed (GimpPolygonSelectTool *poly_sel);
+
+void gimp_polygon_select_tool_halt (GimpPolygonSelectTool *poly_sel);
+
+
+#endif /* __GIMP_POLYGON_SELECT_TOOL_H__ */
diff --git a/app/tools/gimprectangleoptions.c b/app/tools/gimprectangleoptions.c
new file mode 100644
index 0000000..aff6870
--- /dev/null
+++ b/app/tools/gimprectangleoptions.c
@@ -0,0 +1,1266 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpimage.h"
+#include "core/gimptooloptions.h"
+
+#include "widgets/gimppropwidgets.h"
+
+#include "gimprectangleoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+#define SB_WIDTH 5
+
+
+enum
+{
+ COLUMN_LEFT_NUMBER,
+ COLUMN_RIGHT_NUMBER,
+ COLUMN_TEXT,
+ N_COLUMNS
+};
+
+
+/* local function prototypes */
+
+static void gimp_rectangle_options_fixed_rule_changed (GtkWidget *combo_box,
+ GimpRectangleOptionsPrivate *private);
+
+static void gimp_rectangle_options_string_current_updates (GimpNumberPairEntry *entry,
+ GParamSpec *param,
+ GimpRectangleOptions *rectangle_options);
+static void gimp_rectangle_options_setup_ratio_completion (GimpRectangleOptions *rectangle_options,
+ GtkWidget *entry,
+ GtkListStore *history);
+
+static gboolean gimp_number_pair_entry_history_select (GtkEntryCompletion *completion,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GimpNumberPairEntry *entry);
+
+static void gimp_number_pair_entry_history_add (GtkWidget *entry,
+ GtkTreeModel *model);
+
+
+G_DEFINE_INTERFACE (GimpRectangleOptions, gimp_rectangle_options, GIMP_TYPE_TOOL_OPTIONS)
+
+
+static void
+gimp_rectangle_options_default_init (GimpRectangleOptionsInterface *iface)
+{
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("auto-shrink",
+ NULL,
+ _("Automatically shrink to the nearest "
+ "rectangular shape in a layer"),
+ FALSE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("shrink-merged",
+ _("Shrink merged"),
+ _("Use all visible layers when shrinking "
+ "the selection"),
+ FALSE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_enum ("guide",
+ NULL,
+ _("Composition guides such as rule of thirds"),
+ GIMP_TYPE_GUIDES_TYPE,
+ GIMP_GUIDES_NONE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("x",
+ NULL,
+ _("X coordinate of top left corner"),
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("y",
+ NULL,
+ _("Y coordinate of top left corner"),
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("width",
+ NULL,
+ _("Width of selection"),
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("height",
+ NULL,
+ _("Height of selection"),
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ gimp_param_spec_unit ("position-unit",
+ NULL,
+ _("Unit of top left corner coordinate"),
+ TRUE, TRUE,
+ GIMP_UNIT_PIXEL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ gimp_param_spec_unit ("size-unit",
+ NULL,
+ _("Unit of selection size"),
+ TRUE, TRUE,
+ GIMP_UNIT_PIXEL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("fixed-rule-active",
+ NULL,
+ _("Enable lock of aspect ratio, "
+ "width, height or size"),
+ FALSE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_enum ("fixed-rule",
+ NULL,
+ _("Choose what has to be locked"),
+ GIMP_TYPE_RECTANGLE_FIXED_RULE,
+ GIMP_RECTANGLE_FIXED_ASPECT,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("desired-fixed-width",
+ NULL,
+ _("Custom fixed width"),
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("desired-fixed-height",
+ NULL,
+ _("Custom fixed height"),
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("desired-fixed-size-width",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("desired-fixed-size-height",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("default-fixed-size-width",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_PARAM_READWRITE |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("default-fixed-size-height",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 100.0,
+ GIMP_PARAM_READWRITE |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("overridden-fixed-size",
+ NULL, NULL,
+ FALSE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("aspect-numerator",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 1.0,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("aspect-denominator",
+ NULL, NULL,
+ 0.0, GIMP_MAX_IMAGE_SIZE,
+ 1.0,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("default-aspect-numerator",
+ NULL, NULL,
+ 0.001, GIMP_MAX_IMAGE_SIZE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_double ("default-aspect-denominator",
+ NULL, NULL,
+ 0.001, GIMP_MAX_IMAGE_SIZE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("overridden-fixed-aspect",
+ NULL, NULL,
+ FALSE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("use-string-current",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ gimp_param_spec_unit ("fixed-unit",
+ NULL,
+ _("Unit of fixed width, height or size"),
+ TRUE, TRUE,
+ GIMP_UNIT_PIXEL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_boolean ("fixed-center",
+ _("Expand from center"),
+ _("Expand selection from center outwards"),
+ FALSE,
+ GIMP_CONFIG_PARAM_FLAGS |
+ GIMP_PARAM_STATIC_STRINGS));
+}
+
+static void
+gimp_rectangle_options_private_finalize (GimpRectangleOptionsPrivate *private)
+{
+ g_clear_object (&private->aspect_history);
+ g_clear_object (&private->size_history);
+
+ g_slice_free (GimpRectangleOptionsPrivate, private);
+}
+
+GimpRectangleOptionsPrivate *
+gimp_rectangle_options_get_private (GimpRectangleOptions *options)
+{
+ GimpRectangleOptionsPrivate *private;
+
+ static GQuark private_key = 0;
+
+ g_return_val_if_fail (GIMP_IS_RECTANGLE_OPTIONS (options), NULL);
+
+ if (! private_key)
+ private_key = g_quark_from_static_string ("gimp-rectangle-options-private");
+
+ private = g_object_get_qdata (G_OBJECT (options), private_key);
+
+ if (! private)
+ {
+ private = g_slice_new0 (GimpRectangleOptionsPrivate);
+
+ private->aspect_history = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_DOUBLE,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING);
+
+ private->size_history = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_DOUBLE,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING);
+
+ g_object_set_qdata_full (G_OBJECT (options), private_key, private,
+ (GDestroyNotify) gimp_rectangle_options_private_finalize);
+ }
+
+ return private;
+}
+
+/**
+ * gimp_rectangle_options_install_properties:
+ * @klass: the class structure for a type deriving from #GObject
+ *
+ * Installs the necessary properties for a class implementing
+ * #GimpRectangleOptions. A #GimpRectangleOptionsProp property is installed
+ * for each property, using the values from the #GimpRectangleOptionsProp
+ * enumeration. The caller must make sure itself that the enumeration
+ * values don't collide with some other property values they
+ * are using (that's what %GIMP_RECTANGLE_OPTIONS_PROP_LAST is good for).
+ **/
+void
+gimp_rectangle_options_install_properties (GObjectClass *klass)
+{
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_AUTO_SHRINK,
+ "auto-shrink");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_SHRINK_MERGED,
+ "shrink-merged");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_GUIDE,
+ "guide");
+
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_X,
+ "x");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_Y,
+ "y");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_WIDTH,
+ "width");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_HEIGHT,
+ "height");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_POSITION_UNIT,
+ "position-unit");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_SIZE_UNIT,
+ "size-unit");
+
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE_ACTIVE,
+ "fixed-rule-active");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE,
+ "fixed-rule");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_WIDTH,
+ "desired-fixed-width");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_HEIGHT,
+ "desired-fixed-height");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_WIDTH,
+ "desired-fixed-size-width");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_HEIGHT,
+ "desired-fixed-size-height");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_WIDTH,
+ "default-fixed-size-width");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_HEIGHT,
+ "default-fixed-size-height");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_SIZE,
+ "overridden-fixed-size");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_NUMERATOR,
+ "aspect-numerator");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_DENOMINATOR,
+ "aspect-denominator");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_NUMERATOR,
+ "default-aspect-numerator");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_DENOMINATOR,
+ "default-aspect-denominator");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_ASPECT,
+ "overridden-fixed-aspect");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_USE_STRING_CURRENT,
+ "use-string-current");
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_UNIT,
+ "fixed-unit");
+
+ g_object_class_override_property (klass,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_CENTER,
+ "fixed-center");
+}
+
+void
+gimp_rectangle_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpRectangleOptions *options = GIMP_RECTANGLE_OPTIONS (object);
+ GimpRectangleOptionsPrivate *private;
+
+ private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (options);
+
+ switch (property_id)
+ {
+ case GIMP_RECTANGLE_OPTIONS_PROP_AUTO_SHRINK:
+ private->auto_shrink = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_SHRINK_MERGED:
+ private->shrink_merged = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT:
+ private->highlight = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT_OPACITY:
+ private->highlight_opacity = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_GUIDE:
+ private->guide = g_value_get_enum (value);
+ break;
+
+ case GIMP_RECTANGLE_OPTIONS_PROP_X:
+ private->x = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_Y:
+ private->y = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_WIDTH:
+ private->width = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_HEIGHT:
+ private->height = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_POSITION_UNIT:
+ private->position_unit = g_value_get_int (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_SIZE_UNIT:
+ private->size_unit = g_value_get_int (value);
+ break;
+
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE_ACTIVE:
+ private->fixed_rule_active = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE:
+ private->fixed_rule = g_value_get_enum (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_WIDTH:
+ private->desired_fixed_width = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_HEIGHT:
+ private->desired_fixed_height = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_WIDTH:
+ private->desired_fixed_size_width = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_HEIGHT:
+ private->desired_fixed_size_height = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_WIDTH:
+ private->default_fixed_size_width = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_HEIGHT:
+ private->default_fixed_size_height = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_SIZE:
+ private->overridden_fixed_size = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_NUMERATOR:
+ private->aspect_numerator = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_DENOMINATOR:
+ private->aspect_denominator = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_NUMERATOR:
+ private->default_aspect_numerator = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_DENOMINATOR:
+ private->default_aspect_denominator = g_value_get_double (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_ASPECT:
+ private->overridden_fixed_aspect = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_USE_STRING_CURRENT:
+ private->use_string_current = g_value_get_boolean (value);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_UNIT:
+ private->fixed_unit = g_value_get_int (value);
+ break;
+
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_CENTER:
+ private->fixed_center = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+void
+gimp_rectangle_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpRectangleOptions *options = GIMP_RECTANGLE_OPTIONS (object);
+ GimpRectangleOptionsPrivate *private;
+
+ private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (options);
+
+ switch (property_id)
+ {
+ case GIMP_RECTANGLE_OPTIONS_PROP_AUTO_SHRINK:
+ g_value_set_boolean (value, private->auto_shrink);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_SHRINK_MERGED:
+ g_value_set_boolean (value, private->shrink_merged);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT:
+ g_value_set_boolean (value, private->highlight);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT_OPACITY:
+ g_value_set_double (value, private->highlight_opacity);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_GUIDE:
+ g_value_set_enum (value, private->guide);
+ break;
+
+ case GIMP_RECTANGLE_OPTIONS_PROP_X:
+ g_value_set_double (value, private->x);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_Y:
+ g_value_set_double (value, private->y);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_WIDTH:
+ g_value_set_double (value, private->width);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_HEIGHT:
+ g_value_set_double (value, private->height);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_POSITION_UNIT:
+ g_value_set_int (value, private->position_unit);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_SIZE_UNIT:
+ g_value_set_int (value, private->size_unit);
+ break;
+
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE_ACTIVE:
+ g_value_set_boolean (value, private->fixed_rule_active);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE:
+ g_value_set_enum (value, private->fixed_rule);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_WIDTH:
+ g_value_set_double (value, private->desired_fixed_width);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_HEIGHT:
+ g_value_set_double (value, private->desired_fixed_height);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_WIDTH:
+ g_value_set_double (value, private->desired_fixed_size_width);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_HEIGHT:
+ g_value_set_double (value, private->desired_fixed_size_height);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_WIDTH:
+ g_value_set_double (value, private->default_fixed_size_width);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_HEIGHT:
+ g_value_set_double (value, private->default_fixed_size_height);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_SIZE:
+ g_value_set_boolean (value, private->overridden_fixed_size);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_NUMERATOR:
+ g_value_set_double (value, private->aspect_numerator);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_DENOMINATOR:
+ g_value_set_double (value, private->aspect_denominator);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_NUMERATOR:
+ g_value_set_double (value, private->default_aspect_numerator);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_DENOMINATOR:
+ g_value_set_double (value, private->default_aspect_denominator);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_ASPECT:
+ g_value_set_boolean (value, private->overridden_fixed_aspect);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_USE_STRING_CURRENT:
+ g_value_set_boolean (value, private->use_string_current);
+ break;
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_UNIT:
+ g_value_set_int (value, private->fixed_unit);
+ break;
+
+ case GIMP_RECTANGLE_OPTIONS_PROP_FIXED_CENTER:
+ g_value_set_boolean (value, private->fixed_center);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_rectangle_options_get_size_entry:
+ * @rectangle_options:
+ *
+ * Returns: GtkEntry used to enter desired size of rectangle. For
+ * testing purposes.
+ **/
+GtkWidget *
+gimp_rectangle_options_get_size_entry (GimpRectangleOptions *rectangle_options)
+{
+ GimpRectangleOptionsPrivate *private;
+
+ private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (rectangle_options);
+
+ return private->size_entry;
+}
+
+/**
+ * gimp_rectangle_options_fixed_rule_changed:
+ * @combo_box:
+ * @private:
+ *
+ * Updates tool options widgets depending on current fixed rule state.
+ */
+static void
+gimp_rectangle_options_fixed_rule_changed (GtkWidget *combo_box,
+ GimpRectangleOptionsPrivate *private)
+{
+ /* Setup sensitivity for Width and Height entries */
+
+ gtk_widget_set_sensitive (gimp_size_entry_get_help_widget (
+ GIMP_SIZE_ENTRY (private->size_entry), 0),
+ ! (private->fixed_rule_active &&
+ (private->fixed_rule ==
+ GIMP_RECTANGLE_FIXED_WIDTH ||
+ private->fixed_rule ==
+ GIMP_RECTANGLE_FIXED_SIZE)));
+
+ gtk_widget_set_sensitive (gimp_size_entry_get_help_widget (
+ GIMP_SIZE_ENTRY (private->size_entry), 1),
+ ! (private->fixed_rule_active &&
+ (private->fixed_rule ==
+ GIMP_RECTANGLE_FIXED_HEIGHT ||
+ private->fixed_rule ==
+ GIMP_RECTANGLE_FIXED_SIZE)));
+
+ /* Setup current fixed rule entries */
+
+ gtk_widget_hide (private->fixed_width_entry);
+ gtk_widget_hide (private->fixed_height_entry);
+ gtk_widget_hide (private->fixed_aspect_hbox);
+ gtk_widget_hide (private->fixed_size_hbox);
+
+ switch (private->fixed_rule)
+ {
+ case GIMP_RECTANGLE_FIXED_ASPECT:
+ gtk_widget_show (private->fixed_aspect_hbox);
+ break;
+
+ case GIMP_RECTANGLE_FIXED_WIDTH:
+ gtk_widget_show (private->fixed_width_entry);
+ break;
+
+ case GIMP_RECTANGLE_FIXED_HEIGHT:
+ gtk_widget_show (private->fixed_height_entry);
+ break;
+
+ case GIMP_RECTANGLE_FIXED_SIZE:
+ gtk_widget_show (private->fixed_size_hbox);
+ break;
+ }
+}
+
+static void
+gimp_rectangle_options_string_current_updates (GimpNumberPairEntry *entry,
+ GParamSpec *param,
+ GimpRectangleOptions *rectangle_options)
+{
+ GimpRectangleOptionsPrivate *private;
+ gboolean user_override;
+
+ private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (rectangle_options);
+
+ user_override = gimp_number_pair_entry_get_user_override (entry);
+
+ gimp_number_pair_entry_set_default_text (entry,
+ private->use_string_current ?
+ /* Current, as in what is currently in use. */
+ _("Current") : NULL);
+
+ gtk_widget_set_sensitive (private->aspect_button_box,
+ ! private->use_string_current || user_override);
+}
+
+static GtkWidget *
+gimp_rectangle_options_prop_dimension_frame_new (GObject *config,
+ const gchar *x_property_name,
+ const gchar *y_property_name,
+ const gchar *unit_property_name,
+ const gchar *table_label,
+ GtkWidget **entry)
+{
+ GimpUnit unit_value;
+ GtkWidget *frame;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *menu;
+ GtkWidget *spinbutton;
+ GtkAdjustment *adjustment;
+
+ g_object_get (config,
+ unit_property_name, &unit_value,
+ NULL);
+
+ frame = gimp_frame_new (NULL);
+
+ /* title */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), hbox);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new (table_label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ menu = gimp_prop_unit_combo_box_new (config, unit_property_name);
+ gtk_box_pack_end (GTK_BOX (hbox), menu, FALSE, FALSE, 0);
+ gtk_widget_show (menu);
+
+ /* content */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), hbox);
+ gtk_widget_show (hbox);
+
+ adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0);
+ spinbutton = gimp_spin_button_new (adjustment, 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), SB_WIDTH);
+
+ *entry = gimp_size_entry_new (1, unit_value, "%a", TRUE, TRUE, FALSE,
+ SB_WIDTH, GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gtk_table_set_col_spacings (GTK_TABLE (*entry), 0);
+ gimp_size_entry_show_unit_menu (GIMP_SIZE_ENTRY (*entry), FALSE);
+ gtk_box_pack_end (GTK_BOX (hbox), *entry, TRUE, TRUE, 0);
+ gtk_widget_show (*entry);
+
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (*entry),
+ GTK_SPIN_BUTTON (spinbutton), NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), spinbutton, TRUE, TRUE, 0);
+ gtk_widget_show (spinbutton);
+
+ gimp_prop_coordinates_connect (config,
+ x_property_name, y_property_name,
+ unit_property_name,
+ *entry, NULL, 300, 300);
+
+ return frame;
+}
+
+GtkWidget *
+gimp_rectangle_options_gui (GimpToolOptions *tool_options)
+{
+ GimpRectangleOptionsPrivate *private;
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *combo;
+ GtkWidget *frame;
+
+ private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (tool_options);
+
+ /* Fixed Center */
+ button = gimp_prop_check_button_new (config, "fixed-center", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* Rectangle fixed-rules (e.g. aspect or width). */
+ {
+ GtkWidget *vbox2;
+ GtkWidget *hbox;
+ GtkWidget *entry;
+ GtkSizeGroup *size_group;
+ GList *children;
+
+ frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* Setup frame title widgets */
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), hbox);
+ gtk_widget_show (hbox);
+
+ button = gimp_prop_check_button_new (config, "fixed-rule-active", NULL);
+ gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (button)));
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (gimp_rectangle_options_fixed_rule_changed),
+ private);
+
+ combo = gimp_prop_enum_combo_box_new (config, "fixed-rule", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Fixed"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_rectangle_options_fixed_rule_changed),
+ private);
+
+ /* Setup frame content */
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+ /* Fixed aspect entry/buttons */
+ private->fixed_aspect_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), private->fixed_aspect_hbox,
+ FALSE, FALSE, 0);
+ gtk_size_group_add_widget (size_group, private->fixed_aspect_hbox);
+ g_object_unref (size_group);
+ /* don't show */
+
+ g_object_add_weak_pointer (G_OBJECT (private->fixed_aspect_hbox),
+ (gpointer) &private->fixed_aspect_hbox);
+
+ entry = gimp_prop_number_pair_entry_new (config,
+ "aspect-numerator",
+ "aspect-denominator",
+ "default-aspect-numerator",
+ "default-aspect-denominator",
+ "overridden-fixed-aspect",
+ FALSE, TRUE,
+ ":/" "xX*",
+ TRUE,
+ 0.001, GIMP_MAX_IMAGE_SIZE);
+ gtk_box_pack_start (GTK_BOX (private->fixed_aspect_hbox), entry,
+ TRUE, TRUE, 0);
+ gtk_widget_show (entry);
+
+ g_signal_connect (entry, "notify::user-override",
+ G_CALLBACK (gimp_rectangle_options_string_current_updates),
+ config);
+ g_signal_connect_swapped (config, "notify::use-string-current",
+ G_CALLBACK (gimp_rectangle_options_string_current_updates),
+ entry);
+
+ gimp_rectangle_options_setup_ratio_completion (GIMP_RECTANGLE_OPTIONS (tool_options),
+ entry,
+ private->aspect_history);
+
+ private->aspect_button_box =
+ gimp_prop_enum_icon_box_new (G_OBJECT (entry),
+ "aspect", "gimp", -1, -1);
+ gtk_box_pack_start (GTK_BOX (private->fixed_aspect_hbox),
+ private->aspect_button_box, FALSE, FALSE, 0);
+ gtk_widget_show (private->aspect_button_box);
+
+ g_object_add_weak_pointer (G_OBJECT (private->aspect_button_box),
+ (gpointer) &private->aspect_button_box);
+
+ /* hide "square" */
+ children =
+ gtk_container_get_children (GTK_CONTAINER (private->aspect_button_box));
+ gtk_widget_hide (children->data);
+ g_list_free (children);
+
+ /* Fixed width entry */
+ private->fixed_width_entry =
+ gimp_prop_size_entry_new (config,
+ "desired-fixed-width", TRUE, "fixed-unit", "%a",
+ GIMP_SIZE_ENTRY_UPDATE_SIZE, 300);
+ gtk_box_pack_start (GTK_BOX (vbox2), private->fixed_width_entry,
+ FALSE, FALSE, 0);
+ gtk_size_group_add_widget (size_group, private->fixed_width_entry);
+ /* don't show */
+
+ g_object_add_weak_pointer (G_OBJECT (private->fixed_width_entry),
+ (gpointer) &private->fixed_width_entry);
+
+ /* Fixed height entry */
+ private->fixed_height_entry =
+ gimp_prop_size_entry_new (config,
+ "desired-fixed-height", TRUE, "fixed-unit", "%a",
+ GIMP_SIZE_ENTRY_UPDATE_SIZE, 300);
+ gtk_box_pack_start (GTK_BOX (vbox2), private->fixed_height_entry,
+ FALSE, FALSE, 0);
+ gtk_size_group_add_widget (size_group, private->fixed_height_entry);
+ /* don't show */
+
+ g_object_add_weak_pointer (G_OBJECT (private->fixed_height_entry),
+ (gpointer) &private->fixed_height_entry);
+
+ /* Fixed size entry */
+ private->fixed_size_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), private->fixed_size_hbox,
+ FALSE, FALSE, 0);
+ gtk_size_group_add_widget (size_group, private->fixed_size_hbox);
+ /* don't show */
+
+ g_object_add_weak_pointer (G_OBJECT (private->fixed_size_hbox),
+ (gpointer) &private->fixed_size_hbox);
+
+ entry = gimp_prop_number_pair_entry_new (config,
+ "desired-fixed-size-width",
+ "desired-fixed-size-height",
+ "default-fixed-size-width",
+ "default-fixed-size-height",
+ "overridden-fixed-size",
+ TRUE, FALSE,
+ "xX*" ":/",
+ FALSE,
+ 1, GIMP_MAX_IMAGE_SIZE);
+ gtk_box_pack_start (GTK_BOX (private->fixed_size_hbox), entry,
+ TRUE, TRUE, 0);
+ gtk_widget_show (entry);
+
+ gimp_rectangle_options_setup_ratio_completion (GIMP_RECTANGLE_OPTIONS (tool_options),
+ entry,
+ private->size_history);
+
+ private->size_button_box =
+ gimp_prop_enum_icon_box_new (G_OBJECT (entry),
+ "aspect", "gimp", -1, -1);
+ gtk_box_pack_start (GTK_BOX (private->fixed_size_hbox),
+ private->size_button_box, FALSE, FALSE, 0);
+ gtk_widget_show (private->size_button_box);
+
+ /* hide "square" */
+ children =
+ gtk_container_get_children (GTK_CONTAINER (private->size_button_box));
+ gtk_widget_hide (children->data);
+ g_list_free (children);
+ }
+
+ /* X, Y */
+ frame = gimp_rectangle_options_prop_dimension_frame_new (config,
+ "x", "y",
+ "position-unit",
+ _("Position:"),
+ &private->position_entry);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* Width, Height */
+ frame = gimp_rectangle_options_prop_dimension_frame_new (config,
+ "width", "height",
+ "size-unit",
+ _("Size:"),
+ &private->size_entry);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the Highlight frame */
+ {
+ GtkWidget *scale;
+
+ scale = gimp_prop_spin_scale_new (config, "highlight-opacity", NULL,
+ 0.01, 0.1, 0);
+ gimp_prop_widget_set_factor (scale, 100.0, 0.0, 0.0, 1);
+
+ frame = gimp_prop_expanding_frame_new (config, "highlight", NULL,
+ scale, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+ }
+
+ /* Guide */
+ combo = gimp_prop_enum_combo_box_new (config, "guide",
+ GIMP_GUIDES_NONE,
+ GIMP_GUIDES_DIAGONALS);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ /* Auto Shrink */
+ private->auto_shrink_button = gtk_button_new_with_label (_("Auto Shrink"));
+ gtk_box_pack_start (GTK_BOX (vbox), private->auto_shrink_button,
+ FALSE, FALSE, 0);
+ gtk_widget_set_sensitive (private->auto_shrink_button, FALSE);
+ gtk_widget_show (private->auto_shrink_button);
+
+ g_object_add_weak_pointer (G_OBJECT (private->auto_shrink_button),
+ (gpointer) &private->auto_shrink_button);
+
+ button = gimp_prop_check_button_new (config, "shrink-merged", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* Setup initial fixed rule widgets */
+ gimp_rectangle_options_fixed_rule_changed (NULL, private);
+
+ return vbox;
+}
+
+void
+gimp_rectangle_options_connect (GimpRectangleOptions *options,
+ GimpImage *image,
+ GCallback shrink_callback,
+ gpointer shrink_object)
+{
+ GimpRectangleOptionsPrivate *options_private;
+ gdouble xres;
+ gdouble yres;
+
+ g_return_if_fail (GIMP_IS_RECTANGLE_OPTIONS (options));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (shrink_callback != NULL);
+ g_return_if_fail (shrink_object != NULL);
+
+ options_private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (options);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ if (options_private->fixed_width_entry)
+ {
+ GtkWidget *entry = options_private->fixed_width_entry;
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 0, xres, FALSE);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (entry), 0,
+ 0, gimp_image_get_width (image));
+ }
+
+ if (options_private->fixed_height_entry)
+ {
+ GtkWidget *entry = options_private->fixed_height_entry;
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 0, yres, FALSE);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (entry), 0,
+ 0, gimp_image_get_height (image));
+ }
+
+ if (options_private->position_entry)
+ {
+ GtkWidget *entry = options_private->position_entry;
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 0, xres, FALSE);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (entry), 0,
+ 0, gimp_image_get_width (image));
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 1, yres, FALSE);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (entry), 1,
+ 0, gimp_image_get_height (image));
+ }
+
+ if (options_private->size_entry)
+ {
+ GtkWidget *entry = options_private->size_entry;
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 0, xres, FALSE);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (entry), 0,
+ 0, gimp_image_get_width (image));
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 1, yres, FALSE);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (entry), 1,
+ 0, gimp_image_get_height (image));
+ }
+
+ if (options_private->auto_shrink_button)
+ {
+ g_signal_connect_swapped (options_private->auto_shrink_button, "clicked",
+ shrink_callback,
+ shrink_object);
+
+ gtk_widget_set_sensitive (options_private->auto_shrink_button, TRUE);
+ }
+}
+
+void
+gimp_rectangle_options_disconnect (GimpRectangleOptions *options,
+ GCallback shrink_callback,
+ gpointer shrink_object)
+{
+ GimpRectangleOptionsPrivate *options_private;
+
+ g_return_if_fail (GIMP_IS_RECTANGLE_OPTIONS (options));
+ g_return_if_fail (shrink_callback != NULL);
+ g_return_if_fail (shrink_object != NULL);
+
+ options_private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (options);
+
+ if (options_private->auto_shrink_button)
+ {
+ gtk_widget_set_sensitive (options_private->auto_shrink_button, FALSE);
+
+ g_signal_handlers_disconnect_by_func (options_private->auto_shrink_button,
+ shrink_callback,
+ shrink_object);
+ }
+}
+
+/**
+ * gimp_rectangle_options_fixed_rule_active:
+ * @rectangle_options:
+ * @fixed_rule:
+ *
+ * Return value: %TRUE if @fixed_rule is active, %FALSE otherwise.
+ */
+gboolean
+gimp_rectangle_options_fixed_rule_active (GimpRectangleOptions *rectangle_options,
+ GimpRectangleFixedRule fixed_rule)
+{
+ GimpRectangleOptionsPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_RECTANGLE_OPTIONS (rectangle_options), FALSE);
+
+ private = GIMP_RECTANGLE_OPTIONS_GET_PRIVATE (rectangle_options);
+
+ return private->fixed_rule_active &&
+ private->fixed_rule == fixed_rule;
+}
+
+static void
+gimp_rectangle_options_setup_ratio_completion (GimpRectangleOptions *rectangle_options,
+ GtkWidget *entry,
+ GtkListStore *history)
+{
+ GtkEntryCompletion *completion;
+
+ completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION,
+ "model", history,
+ "inline-completion", TRUE,
+ NULL);
+
+ gtk_entry_completion_set_text_column (completion, COLUMN_TEXT);
+ gtk_entry_set_completion (GTK_ENTRY (entry), completion);
+ g_object_unref (completion);
+
+ g_signal_connect (entry, "ratio-changed",
+ G_CALLBACK (gimp_number_pair_entry_history_add),
+ history);
+
+ g_signal_connect (completion, "match-selected",
+ G_CALLBACK (gimp_number_pair_entry_history_select),
+ entry);
+}
+
+static gboolean
+gimp_number_pair_entry_history_select (GtkEntryCompletion *completion,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GimpNumberPairEntry *entry)
+{
+ gdouble left_number;
+ gdouble right_number;
+
+ gtk_tree_model_get (model, iter,
+ COLUMN_LEFT_NUMBER, &left_number,
+ COLUMN_RIGHT_NUMBER, &right_number,
+ -1);
+
+ gimp_number_pair_entry_set_values (entry, left_number, right_number);
+
+ return TRUE;
+}
+
+static void
+gimp_number_pair_entry_history_add (GtkWidget *entry,
+ GtkTreeModel *model)
+{
+ GValue value = G_VALUE_INIT;
+ GtkTreeIter iter;
+ gboolean iter_valid;
+ gdouble left_number;
+ gdouble right_number;
+ const gchar *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ gimp_number_pair_entry_get_values (GIMP_NUMBER_PAIR_ENTRY (entry),
+ &left_number,
+ &right_number);
+
+ for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
+ iter_valid;
+ iter_valid = gtk_tree_model_iter_next (model, &iter))
+ {
+ gtk_tree_model_get_value (model, &iter, COLUMN_TEXT, &value);
+
+ if (strcmp (text, g_value_get_string (&value)) == 0)
+ {
+ g_value_unset (&value);
+ break;
+ }
+
+ g_value_unset (&value);
+ }
+
+ if (iter_valid)
+ {
+ gtk_list_store_move_after (GTK_LIST_STORE (model), &iter, NULL);
+ }
+ else
+ {
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ COLUMN_LEFT_NUMBER, left_number,
+ COLUMN_RIGHT_NUMBER, right_number,
+ COLUMN_TEXT, text,
+ -1);
+
+ /* FIXME: limit the size of the history */
+ }
+}
diff --git a/app/tools/gimprectangleoptions.h b/app/tools/gimprectangleoptions.h
new file mode 100644
index 0000000..e3d011c
--- /dev/null
+++ b/app/tools/gimprectangleoptions.h
@@ -0,0 +1,181 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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_RECTANGLE_OPTIONS_H__
+#define __GIMP_RECTANGLE_OPTIONS_H__
+
+
+typedef enum
+{
+ GIMP_RECTANGLE_OPTIONS_PROP_0,
+
+ GIMP_RECTANGLE_OPTIONS_PROP_AUTO_SHRINK,
+ GIMP_RECTANGLE_OPTIONS_PROP_SHRINK_MERGED,
+ GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT,
+ GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT_OPACITY,
+ GIMP_RECTANGLE_OPTIONS_PROP_GUIDE,
+
+ GIMP_RECTANGLE_OPTIONS_PROP_X,
+ GIMP_RECTANGLE_OPTIONS_PROP_Y,
+ GIMP_RECTANGLE_OPTIONS_PROP_WIDTH,
+ GIMP_RECTANGLE_OPTIONS_PROP_HEIGHT,
+ GIMP_RECTANGLE_OPTIONS_PROP_POSITION_UNIT,
+ GIMP_RECTANGLE_OPTIONS_PROP_SIZE_UNIT,
+
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE_ACTIVE,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_RULE,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_WIDTH,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_HEIGHT,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_WIDTH,
+ GIMP_RECTANGLE_OPTIONS_PROP_DESIRED_FIXED_SIZE_HEIGHT,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_WIDTH,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_FIXED_SIZE_HEIGHT,
+ GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_SIZE,
+ GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_NUMERATOR,
+ GIMP_RECTANGLE_OPTIONS_PROP_ASPECT_DENOMINATOR,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_NUMERATOR,
+ GIMP_RECTANGLE_OPTIONS_PROP_DEFAULT_ASPECT_DENOMINATOR,
+ GIMP_RECTANGLE_OPTIONS_PROP_OVERRIDDEN_FIXED_ASPECT,
+ GIMP_RECTANGLE_OPTIONS_PROP_USE_STRING_CURRENT,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_UNIT,
+ GIMP_RECTANGLE_OPTIONS_PROP_FIXED_CENTER,
+
+ GIMP_RECTANGLE_OPTIONS_PROP_LAST = GIMP_RECTANGLE_OPTIONS_PROP_FIXED_CENTER
+} GimpRectangleOptionsProp;
+
+
+#define GIMP_TYPE_RECTANGLE_OPTIONS (gimp_rectangle_options_get_type ())
+#define GIMP_IS_RECTANGLE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_RECTANGLE_OPTIONS))
+#define GIMP_RECTANGLE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RECTANGLE_OPTIONS, GimpRectangleOptions))
+#define GIMP_RECTANGLE_OPTIONS_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_RECTANGLE_OPTIONS, GimpRectangleOptionsInterface))
+
+#define GIMP_RECTANGLE_OPTIONS_GET_PRIVATE(obj) (gimp_rectangle_options_get_private (GIMP_RECTANGLE_OPTIONS (obj)))
+
+
+typedef struct _GimpRectangleOptions GimpRectangleOptions;
+typedef struct _GimpRectangleOptionsInterface GimpRectangleOptionsInterface;
+typedef struct _GimpRectangleOptionsPrivate GimpRectangleOptionsPrivate;
+
+struct _GimpRectangleOptionsInterface
+{
+ GTypeInterface base_iface;
+};
+
+struct _GimpRectangleOptionsPrivate
+{
+ gboolean auto_shrink;
+ gboolean shrink_merged;
+
+ gboolean highlight;
+ gdouble highlight_opacity;
+ GimpGuidesType guide;
+
+ gdouble x;
+ gdouble y;
+ gdouble width;
+ gdouble height;
+
+ GimpUnit position_unit;
+ GimpUnit size_unit;
+
+ 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 default_fixed_size_width;
+ gdouble default_fixed_size_height;
+ gboolean overridden_fixed_size;
+
+ gdouble aspect_numerator;
+ gdouble aspect_denominator;
+
+ gdouble default_aspect_numerator;
+ gdouble default_aspect_denominator;
+ gboolean overridden_fixed_aspect;
+
+ gboolean fixed_center;
+
+ /* This gboolean is not part of the actual rectangle tool options,
+ * and should be refactored out along with the pointers to widgets.
+ */
+ gboolean use_string_current;
+
+ GimpUnit fixed_unit;
+
+ /* options gui */
+
+ GtkWidget *auto_shrink_button;
+
+ GtkWidget *fixed_width_entry;
+ GtkWidget *fixed_height_entry;
+
+ GtkWidget *fixed_aspect_hbox;
+ GtkWidget *aspect_button_box;
+ GtkListStore *aspect_history;
+
+ GtkWidget *fixed_size_hbox;
+ GtkWidget *size_button_box;
+ GtkListStore *size_history;
+
+ GtkWidget *position_entry;
+ GtkWidget *size_entry;
+};
+
+
+GType gimp_rectangle_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_rectangle_options_gui (GimpToolOptions *tool_options);
+
+void gimp_rectangle_options_connect (GimpRectangleOptions *options,
+ GimpImage *image,
+ GCallback shrink_callback,
+ gpointer shrink_object);
+void gimp_rectangle_options_disconnect (GimpRectangleOptions *options,
+ GCallback shrink_callback,
+ gpointer shrink_object);
+
+gboolean gimp_rectangle_options_fixed_rule_active (GimpRectangleOptions *rectangle_options,
+ GimpRectangleFixedRule fixed_rule);
+
+GimpRectangleOptionsPrivate *
+ gimp_rectangle_options_get_private (GimpRectangleOptions *options);
+
+
+/* convenience functions */
+
+void gimp_rectangle_options_install_properties (GObjectClass *klass);
+void gimp_rectangle_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+void gimp_rectangle_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+/* testing helper functions */
+
+GtkWidget * gimp_rectangle_options_get_size_entry (GimpRectangleOptions *rectangle_options);
+
+
+#endif /* __GIMP_RECTANGLE_OPTIONS_H__ */
diff --git a/app/tools/gimprectangleselectoptions.c b/app/tools/gimprectangleselectoptions.c
new file mode 100644
index 0000000..ea8f0da
--- /dev/null
+++ b/app/tools/gimprectangleselectoptions.c
@@ -0,0 +1,203 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+
+#include "gimprectangleoptions.h"
+#include "gimprectangleselectoptions.h"
+#include "gimprectangleselecttool.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_ROUND_CORNERS = GIMP_RECTANGLE_OPTIONS_PROP_LAST + 1,
+ PROP_CORNER_RADIUS
+};
+
+
+static void gimp_rectangle_select_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_rectangle_select_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpRectangleSelectOptions,
+ gimp_rectangle_select_options,
+ GIMP_TYPE_SELECTION_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_RECTANGLE_OPTIONS,
+ NULL))
+
+
+static void
+gimp_rectangle_select_options_class_init (GimpRectangleSelectOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_rectangle_select_options_set_property;
+ object_class->get_property = gimp_rectangle_select_options_get_property;
+
+ /* The 'highlight' property is defined here because we want different
+ * default values for the Crop and the Rectangle Select tools.
+ */
+ GIMP_CONFIG_PROP_BOOLEAN (object_class,
+ GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT,
+ "highlight",
+ _("Highlight"),
+ _("Dim everything outside selection"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class,
+ GIMP_RECTANGLE_OPTIONS_PROP_HIGHLIGHT_OPACITY,
+ "highlight-opacity",
+ _("Highlight opacity"),
+ _("How much to dim everything outside selection"),
+ 0.0, 1.0, 0.5,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ROUND_CORNERS,
+ "round-corners",
+ _("Rounded corners"),
+ _("Round corners of selection"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_CORNER_RADIUS,
+ "corner-radius",
+ _("Radius"),
+ _("Radius of rounding in pixels"),
+ 0.0, 10000.0, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ gimp_rectangle_options_install_properties (object_class);
+}
+
+static void
+gimp_rectangle_select_options_init (GimpRectangleSelectOptions *options)
+{
+}
+
+static void
+gimp_rectangle_select_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpRectangleSelectOptions *options = GIMP_RECTANGLE_SELECT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_ROUND_CORNERS:
+ options->round_corners = g_value_get_boolean (value);
+ break;
+
+ case PROP_CORNER_RADIUS:
+ options->corner_radius = g_value_get_double (value);
+ break;
+
+ default:
+ gimp_rectangle_options_set_property (object, property_id, value, pspec);
+ break;
+ }
+}
+
+static void
+gimp_rectangle_select_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpRectangleSelectOptions *options = GIMP_RECTANGLE_SELECT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_ROUND_CORNERS:
+ g_value_set_boolean (value, options->round_corners);
+ break;
+
+ case PROP_CORNER_RADIUS:
+ g_value_set_double (value, options->corner_radius);
+ break;
+
+ default:
+ gimp_rectangle_options_get_property (object, property_id, value, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_rectangle_select_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_selection_options_gui (tool_options);
+
+ /* the round corners frame */
+ if (tool_options->tool_info->tool_type == GIMP_TYPE_RECTANGLE_SELECT_TOOL)
+ {
+ GtkWidget *frame;
+ GtkWidget *scale;
+ GtkWidget *toggle;
+
+ scale = gimp_prop_spin_scale_new (config, "corner-radius", NULL,
+ 1.0, 10.0, 1);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale),
+ 0.0, 1000.0);
+ gimp_spin_scale_set_gamma (GIMP_SPIN_SCALE (scale), 1.7);
+
+ frame = gimp_prop_expanding_frame_new (config, "round-corners", NULL,
+ scale, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ toggle = GIMP_SELECTION_OPTIONS (tool_options)->antialias_toggle;
+
+ g_object_bind_property (config, "round-corners",
+ toggle, "sensitive",
+ G_BINDING_SYNC_CREATE);
+ }
+
+ /* the rectangle options */
+ {
+ GtkWidget *vbox_rectangle;
+
+ vbox_rectangle = gimp_rectangle_options_gui (tool_options);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox_rectangle, FALSE, FALSE, 0);
+ gtk_widget_show (vbox_rectangle);
+ }
+
+ return vbox;
+}
diff --git a/app/tools/gimprectangleselectoptions.h b/app/tools/gimprectangleselectoptions.h
new file mode 100644
index 0000000..cf56f8f
--- /dev/null
+++ b/app/tools/gimprectangleselectoptions.h
@@ -0,0 +1,50 @@
+/* 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_RECTANGLE_SELECT_OPTIONS_H__
+#define __GIMP_RECTANGLE_SELECT_OPTIONS_H__
+
+
+#include "gimpselectionoptions.h"
+
+
+#define GIMP_TYPE_RECTANGLE_SELECT_OPTIONS (gimp_rectangle_select_options_get_type ())
+#define GIMP_RECTANGLE_SELECT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RECTANGLE_SELECT_OPTIONS, GimpRectangleSelectOptions))
+#define GIMP_RECTANGLE_SELECT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_RECTANGLE_SELECT_OPTIONS, GimpRectangleSelectOptionsClass))
+#define GIMP_IS_RECTANGLE_SELECT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_RECTANGLE_SELECT_OPTIONS))
+#define GIMP_IS_RECTANGLE_SELECT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_RECTANGLE_SELECT_OPTIONS))
+#define GIMP_RECTANGLE_SELECT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_RECTANGLE_SELECT_OPTIONS, GimpRectangleSelectOptionsClass))
+
+
+typedef struct _GimpRectangleSelectOptions GimpRectangleSelectOptions;
+typedef struct _GimpToolOptionsClass GimpRectangleSelectOptionsClass;
+
+struct _GimpRectangleSelectOptions
+{
+ GimpSelectionOptions parent_instance;
+
+ gboolean round_corners;
+ gdouble corner_radius;
+};
+
+
+GType gimp_rectangle_select_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_rectangle_select_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_RECTANGLE_SELECT_OPTIONS_H__ */
diff --git a/app/tools/gimprectangleselecttool.c b/app/tools/gimprectangleselecttool.c
new file mode 100644
index 0000000..65f596d
--- /dev/null
+++ b/app/tools/gimprectangleselecttool.c
@@ -0,0 +1,918 @@
+/* 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 "tools-types.h"
+
+#include "core/gimpchannel-select.h"
+#include "core/gimpchannel.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer-floating-selection.h"
+#include "core/gimppickable.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolrectangle.h"
+
+#include "gimpeditselectiontool.h"
+#include "gimprectangleoptions.h"
+#include "gimprectangleselecttool.h"
+#include "gimprectangleselectoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+struct _GimpRectangleSelectToolPrivate
+{
+ GimpChannelOps operation; /* remember for use when modifying */
+ gboolean use_saved_op; /* use operation or get from options */
+
+ gdouble press_x;
+ gdouble press_y;
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+ GList *bindings;
+};
+
+
+static void gimp_rectangle_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_rectangle_select_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static gboolean gimp_rectangle_select_tool_select (GimpRectangleSelectTool *rect_tool,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+static void gimp_rectangle_select_tool_real_select (GimpRectangleSelectTool *rect_tool,
+ GimpChannelOps operation,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+static void gimp_rectangle_select_tool_rectangle_response
+ (GimpToolWidget *widget,
+ gint response_id,
+ GimpRectangleSelectTool *rect_tool);
+static void gimp_rectangle_select_tool_rectangle_change_complete
+ (GimpToolWidget *widget,
+ GimpRectangleSelectTool *rect_tool);
+
+static void gimp_rectangle_select_tool_start (GimpRectangleSelectTool *rect_tool,
+ GimpDisplay *display);
+static void gimp_rectangle_select_tool_commit (GimpRectangleSelectTool *rect_tool);
+static void gimp_rectangle_select_tool_halt (GimpRectangleSelectTool *rect_tool);
+
+static GimpChannelOps
+ gimp_rectangle_select_tool_get_operation (GimpRectangleSelectTool *rect_tool);
+static void gimp_rectangle_select_tool_update_option_defaults
+ (GimpRectangleSelectTool *rect_tool,
+ gboolean ignore_pending);
+static void gimp_rectangle_select_tool_update (GimpRectangleSelectTool *rect_tool);
+static void gimp_rectangle_select_tool_auto_shrink (GimpRectangleSelectTool *rect_tool);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpRectangleSelectTool, gimp_rectangle_select_tool,
+ GIMP_TYPE_SELECTION_TOOL)
+
+#define parent_class gimp_rectangle_select_tool_parent_class
+
+
+void
+gimp_rectangle_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_RECTANGLE_SELECT_TOOL,
+ GIMP_TYPE_RECTANGLE_SELECT_OPTIONS,
+ gimp_rectangle_select_options_gui,
+ 0,
+ "gimp-rect-select-tool",
+ _("Rectangle Select"),
+ _("Rectangle Select Tool: Select a rectangular region"),
+ N_("_Rectangle Select"), "R",
+ NULL, GIMP_HELP_TOOL_RECT_SELECT,
+ GIMP_ICON_TOOL_RECT_SELECT,
+ data);
+}
+
+static void
+gimp_rectangle_select_tool_class_init (GimpRectangleSelectToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_rectangle_select_tool_control;
+ tool_class->button_press = gimp_rectangle_select_tool_button_press;
+ tool_class->button_release = gimp_rectangle_select_tool_button_release;
+ tool_class->motion = gimp_rectangle_select_tool_motion;
+ tool_class->key_press = gimp_rectangle_select_tool_key_press;
+ tool_class->oper_update = gimp_rectangle_select_tool_oper_update;
+ tool_class->cursor_update = gimp_rectangle_select_tool_cursor_update;
+ tool_class->options_notify = gimp_rectangle_select_tool_options_notify;
+
+ klass->select = gimp_rectangle_select_tool_real_select;
+}
+
+static void
+gimp_rectangle_select_tool_init (GimpRectangleSelectTool *rect_tool)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+
+ rect_tool->private =
+ gimp_rectangle_select_tool_get_instance_private (rect_tool);
+
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_RECT_SELECT);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE_SIZE |
+ GIMP_DIRTY_SELECTION);
+ gimp_tool_control_set_dirty_action (tool->control,
+ GIMP_TOOL_ACTION_COMMIT);
+}
+
+static void
+gimp_rectangle_select_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_rectangle_select_tool_halt (rect_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_rectangle_select_tool_commit (rect_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_rectangle_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+ GimpRectangleSelectToolPrivate *private = rect_tool->private;
+ GimpRectangleFunction function;
+
+ if (tool->display && display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+
+ if (gimp_selection_tool_start_edit (GIMP_SELECTION_TOOL (tool),
+ display, coords))
+ {
+ /* In some cases we want to finish the rectangle select tool
+ * and hand over responsibility to the selection tool
+ */
+
+ gboolean zero_rect = FALSE;
+
+ if (private->widget)
+ {
+ gdouble x1, y1, x2, y2;
+
+ gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (private->widget),
+ &x1, &y1, &x2, &y2);
+ if (x1 == x2 && y1 == y2)
+ zero_rect = TRUE;
+ }
+
+ /* Don't commit a zero-size rectangle, it would look like a
+ * click to commit() and that could anchor the floating
+ * selection or do other evil things. Instead, simply cancel a
+ * zero-size rectangle. See bug #796073.
+ */
+ if (zero_rect)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ else
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+
+ gimp_rectangle_select_tool_update_option_defaults (rect_tool, TRUE);
+ return;
+ }
+
+ if (! tool->display)
+ {
+ gimp_rectangle_select_tool_start (rect_tool, display);
+
+ gimp_tool_widget_hover (private->widget, coords, state, TRUE);
+
+ /* HACK: force CREATING on a newly created rectangle; otherwise,
+ * the above binding of properties would cause the rectangle to
+ * start with the size from tool options.
+ */
+ gimp_tool_rectangle_set_function (GIMP_TOOL_RECTANGLE (private->widget),
+ GIMP_TOOL_RECTANGLE_CREATING);
+ }
+
+ /* if the shift or ctrl keys are down, we don't want to adjust, we
+ * want to create a new rectangle, regardless of pointer loc
+ */
+ if (state & (gimp_get_extend_selection_mask () |
+ gimp_get_modify_selection_mask ()))
+ {
+ gimp_tool_rectangle_set_function (GIMP_TOOL_RECTANGLE (private->widget),
+ GIMP_TOOL_RECTANGLE_CREATING);
+ }
+
+ if (gimp_tool_widget_button_press (private->widget, coords, time, state,
+ press_type))
+ {
+ private->grab_widget = private->widget;
+ }
+
+ private->press_x = coords->x;
+ private->press_y = coords->y;
+
+ /* if we have an existing rectangle in the current display, then
+ * we have already "executed", and need to undo at this point,
+ * unless the user has done something in the meantime
+ */
+ function =
+ gimp_tool_rectangle_get_function (GIMP_TOOL_RECTANGLE (private->widget));
+
+ if (function == GIMP_TOOL_RECTANGLE_CREATING)
+ private->use_saved_op = FALSE;
+
+ gimp_selection_tool_start_change (
+ GIMP_SELECTION_TOOL (tool),
+ function == GIMP_TOOL_RECTANGLE_CREATING,
+ gimp_rectangle_select_tool_get_operation (rect_tool));
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_rectangle_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+
+ gimp_tool_control_halt (tool->control);
+
+ gimp_selection_tool_end_change (GIMP_SELECTION_TOOL (tool),
+ /* if the user has not moved the mouse,
+ * cancel the change
+ */
+ release_type == GIMP_BUTTON_RELEASE_CLICK ||
+ release_type == GIMP_BUTTON_RELEASE_CANCEL);
+
+ gimp_tool_pop_status (tool, display);
+
+ if (priv->grab_widget)
+ {
+ gimp_tool_widget_button_release (priv->grab_widget,
+ coords, time, state, release_type);
+ priv->grab_widget = NULL;
+ }
+}
+
+static void
+gimp_rectangle_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+
+ if (priv->grab_widget)
+ {
+ gimp_tool_widget_motion (priv->grab_widget, coords, time, state);
+ }
+}
+
+static gboolean
+gimp_rectangle_select_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+
+ if (priv->widget && display == tool->display)
+ {
+ if (gimp_tool_widget_key_press (priv->widget, kevent))
+ return TRUE;
+ }
+
+ return gimp_edit_selection_tool_key_press (tool, kevent, display);
+}
+
+static void
+gimp_rectangle_select_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+
+ if (priv->widget && display == tool->display)
+ {
+ gimp_tool_widget_hover (priv->widget, coords, state, proximity);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+}
+
+static void
+gimp_rectangle_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpRectangleSelectTool *rect_tool = GIMP_RECTANGLE_SELECT_TOOL (tool);
+ GimpRectangleSelectToolPrivate *private = rect_tool->private;
+ GimpCursorType cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ if (private->widget && display == tool->display)
+ {
+ gimp_tool_widget_get_cursor (private->widget, coords, state,
+ &cursor, NULL, &modifier);
+ }
+
+ gimp_tool_control_set_cursor (tool->control, cursor);
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ /* override the previous if shift or ctrl are down */
+ if (state & (gimp_get_extend_selection_mask () |
+ gimp_get_modify_selection_mask ()))
+ {
+ gimp_tool_control_set_cursor (tool->control,
+ GIMP_CURSOR_CROSSHAIR_SMALL);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_rectangle_select_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ if (! strcmp (pspec->name, "antialias") ||
+ ! strcmp (pspec->name, "feather") ||
+ ! strcmp (pspec->name, "feather-radius") ||
+ ! strcmp (pspec->name, "round-corners") ||
+ ! strcmp (pspec->name, "corner-radius"))
+ {
+ gimp_rectangle_select_tool_update (GIMP_RECTANGLE_SELECT_TOOL (tool));
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+}
+
+static gboolean
+gimp_rectangle_select_tool_select (GimpRectangleSelectTool *rect_tool,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+ gboolean rectangle_exists;
+ GimpChannelOps operation;
+
+ gimp_tool_pop_status (tool, tool->display);
+
+ rectangle_exists = (x <= gimp_image_get_width (image) &&
+ y <= gimp_image_get_height (image) &&
+ x + w >= 0 &&
+ y + h >= 0 &&
+ w > 0 &&
+ h > 0);
+
+ operation = gimp_rectangle_select_tool_get_operation (rect_tool);
+
+ /* if rectangle exists, turn it into a selection */
+ if (rectangle_exists)
+ {
+ gimp_selection_tool_start_change (GIMP_SELECTION_TOOL (rect_tool),
+ FALSE,
+ operation);
+
+ GIMP_RECTANGLE_SELECT_TOOL_GET_CLASS (rect_tool)->select (rect_tool,
+ operation,
+ x, y, w, h);
+
+ gimp_selection_tool_end_change (GIMP_SELECTION_TOOL (rect_tool),
+ FALSE);
+ }
+
+ return rectangle_exists;
+}
+
+static void
+gimp_rectangle_select_tool_real_select (GimpRectangleSelectTool *rect_tool,
+ GimpChannelOps operation,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpRectangleSelectOptions *rect_options;
+ GimpChannel *channel;
+
+ rect_options = GIMP_RECTANGLE_SELECT_TOOL_GET_OPTIONS (tool);
+
+ channel = gimp_image_get_mask (gimp_display_get_image (tool->display));
+
+ if (rect_options->round_corners)
+ {
+ /* To prevent elliptification of the rectangle,
+ * we must cap the corner radius.
+ */
+ gdouble max = MIN (w / 2.0, h / 2.0);
+ gdouble radius = MIN (rect_options->corner_radius, max);
+
+ gimp_channel_select_round_rect (channel,
+ x, y, w, h,
+ radius, radius,
+ operation,
+ options->antialias,
+ options->feather,
+ options->feather_radius,
+ options->feather_radius,
+ TRUE);
+ }
+ else
+ {
+ gimp_channel_select_rectangle (channel,
+ x, y, w, h,
+ operation,
+ options->feather,
+ options->feather_radius,
+ options->feather_radius,
+ TRUE);
+ }
+}
+
+static void
+gimp_rectangle_select_tool_rectangle_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpRectangleSelectTool *rect_tool)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+
+ switch (response_id)
+ {
+ case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
+ {
+ gdouble x1, y1, x2, y2;
+
+ gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (widget),
+ &x1, &y1, &x2, &y2);
+ if (x1 == x2 && y1 == y2)
+ {
+ /* if there are no extents, we got here because of a
+ * click, call commit() directly because we might want to
+ * reconfigure the rectangle and continue, instead of
+ * HALTing it like calling COMMIT would do
+ */
+ gimp_rectangle_select_tool_commit (rect_tool);
+
+ gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (widget),
+ &x1, &y1, &x2, &y2);
+ if (x1 == x2 && y1 == y2)
+ {
+ /* if there still is no rectangle after the
+ * tool_commit(), the click was outside the selection
+ * and we HALT to get rid of a zero-size tool widget.
+ */
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ }
+ }
+ else
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
+ }
+ }
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ break;
+ }
+}
+
+static void
+gimp_rectangle_select_tool_rectangle_change_complete (GimpToolWidget *widget,
+ GimpRectangleSelectTool *rect_tool)
+{
+ gimp_rectangle_select_tool_update (rect_tool);
+}
+
+static void
+gimp_rectangle_select_tool_start (GimpRectangleSelectTool *rect_tool,
+ GimpDisplay *display)
+{
+ static const gchar *properties[] =
+ {
+ "highlight",
+ "highlight-opacity",
+ "guide",
+ "round-corners",
+ "corner-radius",
+ "x",
+ "y",
+ "width",
+ "height",
+ "fixed-rule-active",
+ "fixed-rule",
+ "desired-fixed-width",
+ "desired-fixed-height",
+ "desired-fixed-size-width",
+ "desired-fixed-size-height",
+ "aspect-numerator",
+ "aspect-denominator",
+ "fixed-center"
+ };
+
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpRectangleSelectToolPrivate *private = rect_tool->private;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpRectangleSelectOptions *options;
+ GimpToolWidget *widget;
+ gboolean draw_ellipse;
+ gint i;
+
+ options = GIMP_RECTANGLE_SELECT_TOOL_GET_OPTIONS (rect_tool);
+
+ tool->display = display;
+
+ private->widget = widget = gimp_tool_rectangle_new (shell);
+
+ draw_ellipse = GIMP_RECTANGLE_SELECT_TOOL_GET_CLASS (rect_tool)->draw_ellipse;
+
+ g_object_set (widget,
+ "draw-ellipse", draw_ellipse,
+ "status-title", draw_ellipse ? _("Ellipse: ") : _("Rectangle: "),
+ NULL);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), widget);
+
+ for (i = 0; i < G_N_ELEMENTS (properties); i++)
+ {
+ GBinding *binding =
+ g_object_bind_property (G_OBJECT (options), properties[i],
+ G_OBJECT (widget), properties[i],
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ private->bindings = g_list_prepend (private->bindings, binding);
+ }
+
+ gimp_rectangle_options_connect (GIMP_RECTANGLE_OPTIONS (options),
+ gimp_display_get_image (shell->display),
+ G_CALLBACK (gimp_rectangle_select_tool_auto_shrink),
+ rect_tool);
+
+ g_signal_connect (widget, "response",
+ G_CALLBACK (gimp_rectangle_select_tool_rectangle_response),
+ rect_tool);
+ g_signal_connect (widget, "change-complete",
+ G_CALLBACK (gimp_rectangle_select_tool_rectangle_change_complete),
+ rect_tool);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+}
+
+/* This function is called if the user clicks and releases the left
+ * button without moving it. There are the things we might want
+ * to do here:
+ * 1) If there is an existing rectangle and we are inside it, we
+ * convert it into a selection.
+ * 2) If there is an existing rectangle and we are outside it, we
+ * clear it.
+ * 3) If there is no rectangle and there is a floating selection,
+ * we anchor it.
+ * 4) If there is no rectangle and we are inside the selection, we
+ * create a rectangle from the selection bounds.
+ * 5) If there is no rectangle and we are outside the selection,
+ * we clear the selection.
+ */
+static void
+gimp_rectangle_select_tool_commit (GimpRectangleSelectTool *rect_tool)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+
+ if (priv->widget)
+ {
+ gdouble x1, y1, x2, y2;
+ gint w, h;
+
+ gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (priv->widget),
+ &x1, &y1, &x2, &y2);
+ w = x2 - x1;
+ h = y2 - y1;
+
+ if (w == 0 && h == 0)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+ GimpChannel *selection = gimp_image_get_mask (image);
+ gint press_x;
+ gint press_y;
+
+ if (gimp_image_get_floating_selection (image))
+ {
+ floating_sel_anchor (gimp_image_get_floating_selection (image));
+ gimp_image_flush (image);
+
+ return;
+ }
+
+ press_x = ROUND (priv->press_x);
+ press_y = ROUND (priv->press_y);
+
+ /* if the click was inside the marching ants */
+ if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (selection),
+ press_x, press_y) > 0.5)
+ {
+ gint x, y, w, h;
+
+ if (gimp_item_bounds (GIMP_ITEM (selection), &x, &y, &w, &h))
+ {
+ g_object_set (priv->widget,
+ "x1", (gdouble) x,
+ "y1", (gdouble) y,
+ "x2", (gdouble) (x + w),
+ "y2", (gdouble) (y + h),
+ NULL);
+ }
+
+ gimp_rectangle_select_tool_update (rect_tool);
+ }
+ else
+ {
+ GimpChannelOps operation;
+
+ /* prevent this change from halting the tool */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ /* We can conceptually think of a click outside of the
+ * selection as adding a 0px selection. Behave intuitively
+ * for the current selection mode
+ */
+ operation = gimp_rectangle_select_tool_get_operation (rect_tool);
+
+ switch (operation)
+ {
+ case GIMP_CHANNEL_OP_REPLACE:
+ case GIMP_CHANNEL_OP_INTERSECT:
+ gimp_channel_clear (selection, NULL, TRUE);
+ gimp_image_flush (image);
+ break;
+
+ case GIMP_CHANNEL_OP_ADD:
+ case GIMP_CHANNEL_OP_SUBTRACT:
+ default:
+ /* Do nothing */
+ break;
+ }
+
+ gimp_tool_control_pop_preserve (tool->control);
+ }
+ }
+
+ gimp_rectangle_select_tool_update_option_defaults (rect_tool, FALSE);
+ }
+}
+
+static void
+gimp_rectangle_select_tool_halt (GimpRectangleSelectTool *rect_tool)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+ GimpRectangleSelectOptions *options;
+
+ options = GIMP_RECTANGLE_SELECT_TOOL_GET_OPTIONS (rect_tool);
+
+ if (tool->display)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ gimp_display_shell_set_highlight (shell, NULL, 0.0);
+
+ gimp_rectangle_options_disconnect (GIMP_RECTANGLE_OPTIONS (options),
+ G_CALLBACK (gimp_rectangle_select_tool_auto_shrink),
+ rect_tool);
+ }
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ /* disconnect bindings manually so they are really gone *now*, we
+ * might be in the middle of a signal emission that keeps the
+ * widget and its bindings alive.
+ */
+ g_list_free_full (priv->bindings, (GDestroyNotify) g_object_unref);
+ priv->bindings = NULL;
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
+ g_clear_object (&priv->widget);
+
+ tool->display = NULL;
+
+ gimp_rectangle_select_tool_update_option_defaults (rect_tool, TRUE);
+}
+
+static GimpChannelOps
+gimp_rectangle_select_tool_get_operation (GimpRectangleSelectTool *rect_tool)
+{
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+ GimpSelectionOptions *options;
+
+ options = GIMP_SELECTION_TOOL_GET_OPTIONS (rect_tool);
+
+ if (priv->use_saved_op)
+ return priv->operation;
+ else
+ return options->operation;
+}
+
+/**
+ * gimp_rectangle_select_tool_update_option_defaults:
+ * @crop_tool:
+ * @ignore_pending: %TRUE to ignore any pending crop rectangle.
+ *
+ * Sets the default Fixed: Aspect ratio and Fixed: Size option
+ * properties.
+ */
+static void
+gimp_rectangle_select_tool_update_option_defaults (GimpRectangleSelectTool *rect_tool,
+ gboolean ignore_pending)
+{
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpRectangleOptions *rect_options;
+
+ rect_options = GIMP_RECTANGLE_OPTIONS (gimp_tool_get_options (tool));
+
+ if (priv->widget && ! ignore_pending)
+ {
+ /* There is a pending rectangle and we should not ignore it, so
+ * set default Fixed: Size to the same as the current pending
+ * rectangle width/height.
+ */
+
+ gimp_tool_rectangle_pending_size_set (GIMP_TOOL_RECTANGLE (priv->widget),
+ G_OBJECT (rect_options),
+ "default-aspect-numerator",
+ "default-aspect-denominator");
+
+ g_object_set (G_OBJECT (rect_options),
+ "use-string-current", TRUE,
+ NULL);
+ }
+ else
+ {
+ g_object_set (G_OBJECT (rect_options),
+ "default-aspect-numerator", 1.0,
+ "default-aspect-denominator", 1.0,
+ NULL);
+
+ g_object_set (G_OBJECT (rect_options),
+ "use-string-current", FALSE,
+ NULL);
+ }
+}
+
+static void
+gimp_rectangle_select_tool_update (GimpRectangleSelectTool *rect_tool)
+{
+ GimpTool *tool = GIMP_TOOL (rect_tool);
+ GimpRectangleSelectToolPrivate *priv = rect_tool->private;
+
+ /* prevent change in selection from halting the tool */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ if (tool->display && ! gimp_tool_control_is_active (tool->control))
+ {
+ gdouble x1, y1, x2, y2;
+
+ gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (priv->widget),
+ &x1, &y1, &x2, &y2);
+
+ gimp_rectangle_select_tool_select (rect_tool,
+ x1, y1, x2 - x1, y2 - y1);
+
+ if (! priv->use_saved_op)
+ {
+ GimpSelectionOptions *options;
+
+ options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+
+ /* remember the operation now in case we modify the rectangle */
+ priv->operation = options->operation;
+ priv->use_saved_op = TRUE;
+ }
+ }
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_rectangle_select_tool_update_option_defaults (rect_tool, FALSE);
+}
+
+static void
+gimp_rectangle_select_tool_auto_shrink (GimpRectangleSelectTool *rect_tool)
+{
+ GimpRectangleSelectToolPrivate *private = rect_tool->private;
+ gboolean shrink_merged;
+
+ g_object_get (gimp_tool_get_options (GIMP_TOOL (rect_tool)),
+ "shrink-merged", &shrink_merged,
+ NULL);
+
+ gimp_tool_rectangle_auto_shrink (GIMP_TOOL_RECTANGLE (private->widget),
+ shrink_merged);
+}
diff --git a/app/tools/gimprectangleselecttool.h b/app/tools/gimprectangleselecttool.h
new file mode 100644
index 0000000..eb4d9c3
--- /dev/null
+++ b/app/tools/gimprectangleselecttool.h
@@ -0,0 +1,67 @@
+/* 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_RECTANGLE_SELECT_TOOL_H__
+#define __GIMP_RECTANGLE_SELECT_TOOL_H__
+
+
+#include "gimpselectiontool.h"
+
+
+#define GIMP_TYPE_RECTANGLE_SELECT_TOOL (gimp_rectangle_select_tool_get_type ())
+#define GIMP_RECTANGLE_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RECTANGLE_SELECT_TOOL, GimpRectangleSelectTool))
+#define GIMP_RECTANGLE_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_RECTANGLE_SELECT_TOOL, GimpRectangleSelectToolClass))
+#define GIMP_IS_RECTANGLE_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_RECTANGLE_SELECT_TOOL))
+#define GIMP_IS_RECTANGLE_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_RECTANGLE_SELECT_TOOL))
+#define GIMP_RECTANGLE_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_RECTANGLE_SELECT_TOOL, GimpRectangleSelectToolClass))
+
+#define GIMP_RECTANGLE_SELECT_TOOL_GET_OPTIONS(t) (GIMP_RECTANGLE_SELECT_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpRectangleSelectTool GimpRectangleSelectTool;
+typedef struct _GimpRectangleSelectToolPrivate GimpRectangleSelectToolPrivate;
+typedef struct _GimpRectangleSelectToolClass GimpRectangleSelectToolClass;
+
+struct _GimpRectangleSelectTool
+{
+ GimpSelectionTool parent_instance;
+
+ GimpRectangleSelectToolPrivate *private;
+};
+
+struct _GimpRectangleSelectToolClass
+{
+ GimpSelectionToolClass parent_class;
+
+ void (* select) (GimpRectangleSelectTool *rect_select,
+ GimpChannelOps operation,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+ gboolean draw_ellipse;
+};
+
+
+void gimp_rectangle_select_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_rectangle_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_RECTANGLE_SELECT_TOOL_H__ */
diff --git a/app/tools/gimpregionselectoptions.c b/app/tools/gimpregionselectoptions.c
new file mode 100644
index 0000000..351c08f
--- /dev/null
+++ b/app/tools/gimpregionselectoptions.c
@@ -0,0 +1,290 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpregionselectoptions.h"
+#include "gimpregionselecttool.h"
+#include "gimpfuzzyselecttool.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SELECT_TRANSPARENT,
+ PROP_SAMPLE_MERGED,
+ PROP_DIAGONAL_NEIGHBORS,
+ PROP_THRESHOLD,
+ PROP_SELECT_CRITERION,
+ PROP_DRAW_MASK
+};
+
+
+static void gimp_region_select_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_region_select_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_region_select_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_region_select_options_reset (GimpConfig *config);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpRegionSelectOptions, gimp_region_select_options,
+ GIMP_TYPE_SELECTION_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_region_select_options_config_iface_init))
+
+#define parent_class gimp_region_select_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_region_select_options_class_init (GimpRegionSelectOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_region_select_options_set_property;
+ object_class->get_property = gimp_region_select_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SELECT_TRANSPARENT,
+ "select-transparent",
+ _("Select transparent areas"),
+ _("Allow completely transparent regions "
+ "to be selected"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
+ "sample-merged",
+ _("Sample merged"),
+ _("Base selection on all visible layers"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DIAGONAL_NEIGHBORS,
+ "diagonal-neighbors",
+ _("Diagonal neighbors"),
+ _("Treat diagonally neighboring pixels as "
+ "connected"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_THRESHOLD,
+ "threshold",
+ _("Threshold"),
+ _("Maximum color difference"),
+ 0.0, 255.0, 15.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_SELECT_CRITERION,
+ "select-criterion",
+ _("Select by"),
+ _("Selection criterion"),
+ GIMP_TYPE_SELECT_CRITERION,
+ GIMP_SELECT_CRITERION_COMPOSITE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DRAW_MASK,
+ "draw-mask",
+ _("Draw mask"),
+ _("Draw the selected region's mask"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_region_select_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ config_iface->reset = gimp_region_select_options_reset;
+}
+
+static void
+gimp_region_select_options_init (GimpRegionSelectOptions *options)
+{
+}
+
+static void
+gimp_region_select_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SELECT_TRANSPARENT:
+ options->select_transparent = g_value_get_boolean (value);
+ break;
+
+ case PROP_SAMPLE_MERGED:
+ options->sample_merged = g_value_get_boolean (value);
+ break;
+
+ case PROP_DIAGONAL_NEIGHBORS:
+ options->diagonal_neighbors = g_value_get_boolean (value);
+ break;
+
+ case PROP_THRESHOLD:
+ options->threshold = g_value_get_double (value);
+ break;
+
+ case PROP_SELECT_CRITERION:
+ options->select_criterion = g_value_get_enum (value);
+ break;
+
+ case PROP_DRAW_MASK:
+ options->draw_mask = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_region_select_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_SELECT_TRANSPARENT:
+ g_value_set_boolean (value, options->select_transparent);
+ break;
+
+ case PROP_SAMPLE_MERGED:
+ g_value_set_boolean (value, options->sample_merged);
+ break;
+
+ case PROP_DIAGONAL_NEIGHBORS:
+ g_value_set_boolean (value, options->diagonal_neighbors);
+ break;
+
+ case PROP_THRESHOLD:
+ g_value_set_double (value, options->threshold);
+ break;
+
+ case PROP_SELECT_CRITERION:
+ g_value_set_enum (value, options->select_criterion);
+ break;
+
+ case PROP_DRAW_MASK:
+ g_value_set_boolean (value, options->draw_mask);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_region_select_options_reset (GimpConfig *config)
+{
+ GimpToolOptions *tool_options = GIMP_TOOL_OPTIONS (config);
+ GParamSpec *pspec;
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+ "threshold");
+
+ if (pspec)
+ G_PARAM_SPEC_DOUBLE (pspec)->default_value =
+ tool_options->tool_info->gimp->config->default_threshold;
+
+ parent_config_iface->reset (config);
+}
+
+GtkWidget *
+gimp_region_select_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_selection_options_gui (tool_options);
+ GtkWidget *button;
+ GtkWidget *scale;
+ GtkWidget *combo;
+ GType tool_type;
+
+ tool_type = tool_options->tool_info->tool_type;
+
+ /* the select transparent areas toggle */
+ button = gimp_prop_check_button_new (config, "select-transparent", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the sample merged toggle */
+ button = gimp_prop_check_button_new (config, "sample-merged", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the diagonal neighbors toggle */
+ if (tool_type == GIMP_TYPE_FUZZY_SELECT_TOOL)
+ {
+ button = gimp_prop_check_button_new (config, "diagonal-neighbors", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+ }
+
+ /* the threshold scale */
+ scale = gimp_prop_spin_scale_new (config, "threshold", NULL,
+ 1.0, 16.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ /* the select criterion combo */
+ combo = gimp_prop_enum_combo_box_new (config, "select-criterion", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Select by"));
+ gtk_box_pack_start (GTK_BOX (vbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ /* the show mask toggle */
+ button = gimp_prop_check_button_new (config, "draw-mask", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ return vbox;
+}
diff --git a/app/tools/gimpregionselectoptions.h b/app/tools/gimpregionselectoptions.h
new file mode 100644
index 0000000..24b8b42
--- /dev/null
+++ b/app/tools/gimpregionselectoptions.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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_REGION_SELECT_OPTIONS_H__
+#define __GIMP_REGION_SELECT_OPTIONS_H__
+
+
+#include "gimpselectionoptions.h"
+
+
+#define GIMP_TYPE_REGION_SELECT_OPTIONS (gimp_region_select_options_get_type ())
+#define GIMP_REGION_SELECT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_REGION_SELECT_OPTIONS, GimpRegionSelectOptions))
+#define GIMP_REGION_SELECT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_REGION_SELECT_OPTIONS, GimpRegionSelectOptionsClass))
+#define GIMP_IS_REGION_SELECT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_REGION_SELECT_OPTIONS))
+#define GIMP_IS_REGION_SELECT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_REGION_SELECT_OPTIONS))
+#define GIMP_REGION_SELECT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_REGION_SELECT_OPTIONS, GimpRegionSelectOptionsClass))
+
+
+typedef struct _GimpRegionSelectOptions GimpRegionSelectOptions;
+typedef struct _GimpToolOptionsClass GimpRegionSelectOptionsClass;
+
+struct _GimpRegionSelectOptions
+{
+ GimpSelectionOptions parent_instance;
+
+ gboolean select_transparent;
+ gboolean sample_merged;
+ gboolean diagonal_neighbors;
+ gdouble threshold;
+ GimpSelectCriterion select_criterion;
+ gboolean draw_mask;
+};
+
+
+GType gimp_region_select_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_region_select_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_REGION_SELECT_OPTIONS_H__ */
diff --git a/app/tools/gimpregionselecttool.c b/app/tools/gimpregionselecttool.c
new file mode 100644
index 0000000..fe08533
--- /dev/null
+++ b/app/tools/gimpregionselecttool.c
@@ -0,0 +1,386 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpregionselecttool.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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpboundary.h"
+#include "core/gimpchannel.h"
+#include "core/gimpchannel-select.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer-floating-selection.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-cursor.h"
+
+#include "gimpregionselectoptions.h"
+#include "gimpregionselecttool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_region_select_tool_finalize (GObject *object);
+
+static void gimp_region_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_region_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_region_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_region_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_region_select_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_region_select_tool_get_mask (GimpRegionSelectTool *region_sel,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE (GimpRegionSelectTool, gimp_region_select_tool,
+ GIMP_TYPE_SELECTION_TOOL)
+
+#define parent_class gimp_region_select_tool_parent_class
+
+
+static void
+gimp_region_select_tool_class_init (GimpRegionSelectToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_region_select_tool_finalize;
+
+ tool_class->button_press = gimp_region_select_tool_button_press;
+ tool_class->button_release = gimp_region_select_tool_button_release;
+ tool_class->motion = gimp_region_select_tool_motion;
+ tool_class->cursor_update = gimp_region_select_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_region_select_tool_draw;
+}
+
+static void
+gimp_region_select_tool_init (GimpRegionSelectTool *region_select)
+{
+ GimpTool *tool = GIMP_TOOL (region_select);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+
+ region_select->x = 0;
+ region_select->y = 0;
+ region_select->saved_threshold = 0.0;
+
+ region_select->region_mask = NULL;
+ region_select->segs = NULL;
+ region_select->n_segs = 0;
+}
+
+static void
+gimp_region_select_tool_finalize (GObject *object)
+{
+ GimpRegionSelectTool *region_sel = GIMP_REGION_SELECT_TOOL (object);
+
+ g_clear_object (&region_sel->region_mask);
+
+ g_clear_pointer (&region_sel->segs, g_free);
+ region_sel->n_segs = 0;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_region_select_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpRegionSelectTool *region_sel = GIMP_REGION_SELECT_TOOL (tool);
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
+
+ region_sel->x = coords->x;
+ region_sel->y = coords->y;
+ region_sel->saved_threshold = options->threshold;
+
+ if (gimp_selection_tool_start_edit (GIMP_SELECTION_TOOL (region_sel),
+ display, coords))
+ {
+ return;
+ }
+
+ gimp_tool_control_activate (tool->control);
+ tool->display = display;
+
+ gimp_tool_push_status (tool, display,
+ _("Move the mouse to change threshold"));
+
+ gimp_region_select_tool_get_mask (region_sel, display);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+}
+
+static void
+gimp_region_select_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpRegionSelectTool *region_sel = GIMP_REGION_SELECT_TOOL (tool);
+ GimpSelectionOptions *sel_options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_control_halt (tool->control);
+
+ if (options->draw_mask)
+ gimp_display_shell_set_mask (gimp_display_get_shell (display),
+ NULL, 0, 0, NULL, FALSE);
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ if (GIMP_SELECTION_TOOL (tool)->function == SELECTION_ANCHOR)
+ {
+ if (gimp_image_get_floating_selection (image))
+ {
+ /* If there is a floating selection, anchor it */
+ floating_sel_anchor (gimp_image_get_floating_selection (image));
+ }
+ else
+ {
+ /* Otherwise, clear the selection mask */
+ gimp_channel_clear (gimp_image_get_mask (image), NULL, TRUE);
+ }
+
+ gimp_image_flush (image);
+ }
+ else if (region_sel->region_mask)
+ {
+ gint off_x = 0;
+ gint off_y = 0;
+
+ if (! options->sample_merged)
+ {
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+ }
+
+ gimp_channel_select_buffer (gimp_image_get_mask (image),
+ GIMP_REGION_SELECT_TOOL_GET_CLASS (tool)->undo_desc,
+ region_sel->region_mask,
+ off_x,
+ off_y,
+ sel_options->operation,
+ sel_options->feather,
+ sel_options->feather_radius,
+ sel_options->feather_radius);
+
+
+ gimp_image_flush (image);
+ }
+ }
+
+ g_clear_object (&region_sel->region_mask);
+
+ g_clear_pointer (&region_sel->segs, g_free);
+ region_sel->n_segs = 0;
+
+ /* Restore the original threshold */
+ g_object_set (options,
+ "threshold", region_sel->saved_threshold,
+ NULL);
+}
+
+static void
+gimp_region_select_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpRegionSelectTool *region_sel = GIMP_REGION_SELECT_TOOL (tool);
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
+ gint diff_x, diff_y;
+ gdouble diff;
+
+ static guint32 last_time = 0;
+
+ /* don't let the events come in too fast, ignore below a delay of 100 ms */
+ if (time - last_time < 100)
+ return;
+
+ last_time = time;
+
+ diff_x = coords->x - region_sel->x;
+ diff_y = coords->y - region_sel->y;
+
+ diff = ((ABS (diff_x) > ABS (diff_y)) ? diff_x : diff_y) / 2.0;
+
+ g_object_set (options,
+ "threshold", CLAMP (region_sel->saved_threshold + diff, 0, 255),
+ NULL);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_region_select_tool_get_mask (region_sel, display);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_region_select_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (tool);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (! gimp_image_coords_in_active_pickable (image, coords,
+ FALSE, options->sample_merged,
+ FALSE))
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_region_select_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpRegionSelectTool *region_sel = GIMP_REGION_SELECT_TOOL (draw_tool);
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (draw_tool);
+
+ if (! options->draw_mask && region_sel->region_mask)
+ {
+ if (! region_sel->segs)
+ {
+ /* calculate and allocate a new segment array which represents
+ * the boundary of the contiguous region
+ */
+ region_sel->segs = gimp_boundary_find (region_sel->region_mask, NULL,
+ babl_format ("Y float"),
+ GIMP_BOUNDARY_WITHIN_BOUNDS,
+ 0, 0,
+ gegl_buffer_get_width (region_sel->region_mask),
+ gegl_buffer_get_height (region_sel->region_mask),
+ GIMP_BOUNDARY_HALF_WAY,
+ &region_sel->n_segs);
+
+ }
+
+ if (region_sel->segs)
+ {
+ gint off_x = 0;
+ gint off_y = 0;
+
+ if (! options->sample_merged)
+ {
+ GimpImage *image = gimp_display_get_image (draw_tool->display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+ }
+
+ gimp_draw_tool_add_boundary (draw_tool,
+ region_sel->segs,
+ region_sel->n_segs,
+ NULL,
+ off_x, off_y);
+ }
+ }
+}
+
+static void
+gimp_region_select_tool_get_mask (GimpRegionSelectTool *region_sel,
+ GimpDisplay *display)
+{
+ GimpRegionSelectOptions *options = GIMP_REGION_SELECT_TOOL_GET_OPTIONS (region_sel);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ gimp_display_shell_set_override_cursor (shell, (GimpCursorType) GDK_WATCH);
+
+ g_clear_pointer (&region_sel->segs, g_free);
+ region_sel->n_segs = 0;
+
+ if (region_sel->region_mask)
+ g_object_unref (region_sel->region_mask);
+
+ region_sel->region_mask =
+ GIMP_REGION_SELECT_TOOL_GET_CLASS (region_sel)->get_mask (region_sel,
+ display);
+
+ if (options->draw_mask)
+ {
+ if (region_sel->region_mask)
+ {
+ GimpRGB color = { 1.0, 0.0, 1.0, 1.0 };
+ gint off_x = 0;
+ gint off_y = 0;
+
+ if (! options->sample_merged)
+ {
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+ }
+
+ gimp_display_shell_set_mask (shell, region_sel->region_mask,
+ off_x, off_y, &color, FALSE);
+ }
+ else
+ {
+ gimp_display_shell_set_mask (shell, NULL, 0, 0, NULL, FALSE);
+ }
+ }
+
+ gimp_display_shell_unset_override_cursor (shell);
+}
diff --git a/app/tools/gimpregionselecttool.h b/app/tools/gimpregionselecttool.h
new file mode 100644
index 0000000..63c2551
--- /dev/null
+++ b/app/tools/gimpregionselecttool.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpregionselecttool.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_REGION_SELECT_TOOL_H__
+#define __GIMP_REGION_SELECT_TOOL_H__
+
+
+#include "gimpselectiontool.h"
+
+
+#define GIMP_TYPE_REGION_SELECT_TOOL (gimp_region_select_tool_get_type ())
+#define GIMP_REGION_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_REGION_SELECT_TOOL, GimpRegionSelectTool))
+#define GIMP_REGION_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_REGION_SELECT_TOOL, GimpRegionSelectToolClass))
+#define GIMP_IS_REGION_SELECT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_REGION_SELECT_TOOL))
+#define GIMP_IS_REGION_SELECT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_REGION_SELECT_TOOL))
+#define GIMP_REGION_SELECT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_REGION_SELECT_TOOL, GimpRegionSelectToolClass))
+
+#define GIMP_REGION_SELECT_TOOL_GET_OPTIONS(t) (GIMP_REGION_SELECT_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpRegionSelectTool GimpRegionSelectTool;
+typedef struct _GimpRegionSelectToolClass GimpRegionSelectToolClass;
+
+struct _GimpRegionSelectTool
+{
+ GimpSelectionTool parent_instance;
+
+ gint x, y;
+ gdouble saved_threshold;
+
+ GeglBuffer *region_mask;
+ GimpBoundSeg *segs;
+ gint n_segs;
+};
+
+struct _GimpRegionSelectToolClass
+{
+ GimpSelectionToolClass parent_class;
+
+ const gchar * undo_desc;
+
+ GeglBuffer * (* get_mask) (GimpRegionSelectTool *region_tool,
+ GimpDisplay *display);
+};
+
+
+GType gimp_region_select_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_REGION_SELECT_TOOL_H__ */
diff --git a/app/tools/gimprotatetool.c b/app/tools/gimprotatetool.c
new file mode 100644
index 0000000..b7959eb
--- /dev/null
+++ b/app/tools/gimprotatetool.c
@@ -0,0 +1,537 @@
+/* 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 "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppivotselector.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptoolrotategrid.h"
+
+#include "gimprotatetool.h"
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+
+#include "gimp-intl.h"
+
+
+/* index into trans_info array */
+enum
+{
+ ANGLE,
+ PIVOT_X,
+ PIVOT_Y
+};
+
+
+#define SB_WIDTH 8
+#define EPSILON 1e-6
+
+
+/* local function prototypes */
+
+static gboolean gimp_rotate_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+
+static gboolean gimp_rotate_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+static void gimp_rotate_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+static gchar * gimp_rotate_tool_get_undo_desc (GimpTransformGridTool *tg_tool);
+static void gimp_rotate_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_rotate_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_rotate_tool_prepare (GimpTransformGridTool *tg_tool);
+static void gimp_rotate_tool_readjust (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_rotate_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_rotate_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_rotate_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void rotate_angle_changed (GtkAdjustment *adj,
+ GimpTransformGridTool *tg_tool);
+static void rotate_center_changed (GtkWidget *entry,
+ GimpTransformGridTool *tg_tool);
+static void rotate_pivot_changed (GimpPivotSelector *selector,
+ GimpTransformGridTool *tg_tool);
+
+
+G_DEFINE_TYPE (GimpRotateTool, gimp_rotate_tool, GIMP_TYPE_TRANSFORM_GRID_TOOL)
+
+#define parent_class gimp_rotate_tool_parent_class
+
+
+void
+gimp_rotate_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_ROTATE_TOOL,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS,
+ gimp_transform_grid_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-rotate-tool",
+ _("Rotate"),
+ _("Rotate Tool: Rotate the layer, selection or path"),
+ N_("_Rotate"), "<shift>R",
+ NULL, GIMP_HELP_TOOL_ROTATE,
+ GIMP_ICON_TOOL_ROTATE,
+ data);
+}
+
+static void
+gimp_rotate_tool_class_init (GimpRotateToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+
+ tool_class->key_press = gimp_rotate_tool_key_press;
+
+ tg_class->info_to_matrix = gimp_rotate_tool_info_to_matrix;
+ tg_class->matrix_to_info = gimp_rotate_tool_matrix_to_info;
+ tg_class->get_undo_desc = gimp_rotate_tool_get_undo_desc;
+ tg_class->dialog = gimp_rotate_tool_dialog;
+ tg_class->dialog_update = gimp_rotate_tool_dialog_update;
+ tg_class->prepare = gimp_rotate_tool_prepare;
+ tg_class->readjust = gimp_rotate_tool_readjust;
+ tg_class->get_widget = gimp_rotate_tool_get_widget;
+ tg_class->update_widget = gimp_rotate_tool_update_widget;
+ tg_class->widget_changed = gimp_rotate_tool_widget_changed;
+
+ tr_class->undo_desc = C_("undo-type", "Rotate");
+ tr_class->progress_text = _("Rotating");
+ tg_class->ok_button_label = _("R_otate");
+}
+
+static void
+gimp_rotate_tool_init (GimpRotateTool *rotate_tool)
+{
+ GimpTool *tool = GIMP_TOOL (rotate_tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_ROTATE);
+}
+
+static gboolean
+gimp_rotate_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (display == draw_tool->display)
+ {
+ GimpRotateTool *rotate = GIMP_ROTATE_TOOL (tool);
+ GtkSpinButton *angle_spin = GTK_SPIN_BUTTON (rotate->angle_spin_button);
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ gtk_spin_button_spin (angle_spin, GTK_SPIN_STEP_FORWARD, 0.0);
+ return TRUE;
+
+ case GDK_KEY_Down:
+ gtk_spin_button_spin (angle_spin, GTK_SPIN_STEP_BACKWARD, 0.0);
+ return TRUE;
+
+ case GDK_KEY_Right:
+ gtk_spin_button_spin (angle_spin, GTK_SPIN_PAGE_FORWARD, 0.0);
+ return TRUE;
+
+ case GDK_KEY_Left:
+ gtk_spin_button_spin (angle_spin, GTK_SPIN_PAGE_BACKWARD, 0.0);
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ return GIMP_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
+}
+
+static gboolean
+gimp_rotate_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ gimp_matrix3_identity (transform);
+ gimp_transform_matrix_rotate_center (transform,
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ tg_tool->trans_info[ANGLE]);
+
+ return TRUE;
+}
+
+static void
+gimp_rotate_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ gdouble c;
+ gdouble s;
+ gdouble x;
+ gdouble y;
+ gdouble q;
+
+ c = transform->coeff[0][0];
+ s = transform->coeff[1][0];
+ x = transform->coeff[0][2];
+ y = transform->coeff[1][2];
+
+ tg_tool->trans_info[ANGLE] = atan2 (s, c);
+
+ q = 2.0 * (1.0 - transform->coeff[0][0]);
+
+ if (fabs (q) > EPSILON)
+ {
+ tg_tool->trans_info[PIVOT_X] = ((1.0 - c) * x - s * y) / q;
+ tg_tool->trans_info[PIVOT_Y] = (s * x + (1.0 - c) * y) / q;
+ }
+ else
+ {
+ GimpMatrix3 transfer;
+
+ gimp_transform_grid_tool_info_to_matrix (tg_tool, &transfer);
+ gimp_matrix3_invert (&transfer);
+ gimp_matrix3_mult (transform, &transfer);
+
+ gimp_matrix3_transform_point (&transfer,
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ &tg_tool->trans_info[PIVOT_X],
+ &tg_tool->trans_info[PIVOT_Y]);
+ }
+}
+
+static gchar *
+gimp_rotate_tool_get_undo_desc (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ gdouble center_x;
+ gdouble center_y;
+
+ center_x = (tr_tool->x1 + tr_tool->x2) / 2.0;
+ center_y = (tr_tool->y1 + tr_tool->y2) / 2.0;
+
+ if (fabs (tg_tool->trans_info[PIVOT_X] - center_x) <= EPSILON &&
+ fabs (tg_tool->trans_info[PIVOT_Y] - center_y) <= EPSILON)
+ {
+ return g_strdup_printf (C_("undo-type",
+ "Rotate by %-3.3g°"),
+ gimp_rad_to_deg (tg_tool->trans_info[ANGLE]));
+ }
+ else
+ {
+ return g_strdup_printf (C_("undo-type",
+ "Rotate by %-3.3g° around (%g, %g)"),
+ gimp_rad_to_deg (tg_tool->trans_info[ANGLE]),
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y]);
+ }
+}
+
+static void
+gimp_rotate_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+ GimpRotateTool *rotate = GIMP_ROTATE_TOOL (tg_tool);
+ GtkWidget *table;
+ GtkWidget *button;
+ GtkWidget *scale;
+ GtkAdjustment *adj;
+
+ table = gtk_table_new (4, 3, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacing (GTK_TABLE (table), 1, 6);
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tg_tool->gui)), table,
+ FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ rotate->angle_adj = (GtkAdjustment *)
+ gtk_adjustment_new (0, -180, 180, 0.1, 15, 0);
+ button = gimp_spin_button_new (rotate->angle_adj, 1.0, 2);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (button), TRUE);
+ gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (button), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (button), SB_WIDTH);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, _("_Angle:"),
+ 0.0, 0.5, button, 1, TRUE);
+ rotate->angle_spin_button = button;
+
+ g_signal_connect (rotate->angle_adj, "value-changed",
+ G_CALLBACK (rotate_angle_changed),
+ tg_tool);
+
+ scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, rotate->angle_adj);
+ gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
+ gtk_table_attach (GTK_TABLE (table), scale, 1, 3, 1, 2,
+ GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (scale);
+
+ adj = (GtkAdjustment *) gtk_adjustment_new (0, -1, 1, 1, 10, 0);
+ button = gimp_spin_button_new (adj, 1.0, 2);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (button), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (button), SB_WIDTH);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, _("Center _X:"),
+ 0.0, 0.5, button, 1, TRUE);
+
+ rotate->sizeentry = gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a",
+ TRUE, TRUE, FALSE, SB_WIDTH,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (rotate->sizeentry),
+ GTK_SPIN_BUTTON (button), NULL);
+ gimp_size_entry_set_pixel_digits (GIMP_SIZE_ENTRY (rotate->sizeentry), 2);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, _("Center _Y:"),
+ 0.0, 0.5, rotate->sizeentry, 1, TRUE);
+
+ g_signal_connect (rotate->sizeentry, "value-changed",
+ G_CALLBACK (rotate_center_changed),
+ tg_tool);
+
+ rotate->pivot_selector = gimp_pivot_selector_new (0.0, 0.0, 0.0, 0.0);
+ gtk_table_attach (GTK_TABLE (table), rotate->pivot_selector, 2, 3, 2, 4,
+ GTK_SHRINK, GTK_SHRINK, 0, 0);
+ gtk_widget_show (rotate->pivot_selector);
+
+ g_signal_connect (rotate->pivot_selector, "changed",
+ G_CALLBACK (rotate_pivot_changed),
+ tg_tool);
+}
+
+static void
+gimp_rotate_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ GimpRotateTool *rotate = GIMP_ROTATE_TOOL (tg_tool);
+
+ gtk_adjustment_set_value (rotate->angle_adj,
+ gimp_rad_to_deg (tg_tool->trans_info[ANGLE]));
+
+ g_signal_handlers_block_by_func (rotate->sizeentry,
+ rotate_center_changed,
+ tg_tool);
+
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (rotate->sizeentry), 0,
+ tg_tool->trans_info[PIVOT_X]);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (rotate->sizeentry), 1,
+ tg_tool->trans_info[PIVOT_Y]);
+
+ g_signal_handlers_unblock_by_func (rotate->sizeentry,
+ rotate_center_changed,
+ tg_tool);
+
+ g_signal_handlers_block_by_func (rotate->pivot_selector,
+ rotate_pivot_changed,
+ tg_tool);
+
+ gimp_pivot_selector_set_position (
+ GIMP_PIVOT_SELECTOR (rotate->pivot_selector),
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y]);
+
+ g_signal_handlers_unblock_by_func (rotate->pivot_selector,
+ rotate_pivot_changed,
+ tg_tool);
+}
+
+static void
+gimp_rotate_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpRotateTool *rotate = GIMP_ROTATE_TOOL (tg_tool);
+ GimpDisplay *display = GIMP_TOOL (tg_tool)->display;
+ GimpImage *image = gimp_display_get_image (display);
+ gdouble xres;
+ gdouble yres;
+
+ tg_tool->trans_info[ANGLE] = 0.0;
+ tg_tool->trans_info[PIVOT_X] = (gdouble) (tr_tool->x1 + tr_tool->x2) / 2.0;
+ tg_tool->trans_info[PIVOT_Y] = (gdouble) (tr_tool->y1 + tr_tool->y2) / 2.0;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_signal_handlers_block_by_func (rotate->sizeentry,
+ rotate_center_changed,
+ tg_tool);
+
+ gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (rotate->sizeentry),
+ gimp_display_get_shell (display)->unit);
+
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (rotate->sizeentry), 0,
+ xres, FALSE);
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (rotate->sizeentry), 1,
+ yres, FALSE);
+
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (rotate->sizeentry), 0,
+ -65536,
+ 65536 +
+ gimp_image_get_width (image));
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (rotate->sizeentry), 1,
+ -65536,
+ 65536 +
+ gimp_image_get_height (image));
+
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (rotate->sizeentry), 0,
+ tr_tool->x1, tr_tool->x2);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (rotate->sizeentry), 1,
+ tr_tool->y1, tr_tool->y2);
+
+ g_signal_handlers_unblock_by_func (rotate->sizeentry,
+ rotate_center_changed,
+ tg_tool);
+
+ gimp_pivot_selector_set_bounds (GIMP_PIVOT_SELECTOR (rotate->pivot_selector),
+ tr_tool->x1, tr_tool->y1,
+ tr_tool->x2, tr_tool->y2);
+}
+
+static void
+gimp_rotate_tool_readjust (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ tg_tool->trans_info[ANGLE] = -gimp_deg_to_rad (shell->rotate_angle);
+
+ if (tg_tool->trans_info[ANGLE] <= -G_PI)
+ tg_tool->trans_info[ANGLE] += 2.0 * G_PI;
+
+ gimp_display_shell_untransform_xy_f (shell,
+ shell->disp_width / 2.0,
+ shell->disp_height / 2.0,
+ &tg_tool->trans_info[PIVOT_X],
+ &tg_tool->trans_info[PIVOT_Y]);
+}
+
+static GimpToolWidget *
+gimp_rotate_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+
+ widget = gimp_tool_rotate_grid_new (shell,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2,
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ tg_tool->trans_info[ANGLE]);
+
+ g_object_set (widget,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-pivot-handle", TRUE,
+ NULL);
+
+ return widget;
+}
+
+static void
+gimp_rotate_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ g_object_set (tg_tool->widget,
+ "angle", tg_tool->trans_info[ANGLE],
+ "pivot-x", tg_tool->trans_info[PIVOT_X],
+ "pivot-y", tg_tool->trans_info[PIVOT_Y],
+ NULL);
+}
+
+static void
+gimp_rotate_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ g_object_get (tg_tool->widget,
+ "angle", &tg_tool->trans_info[ANGLE],
+ "pivot-x", &tg_tool->trans_info[PIVOT_X],
+ "pivot-y", &tg_tool->trans_info[PIVOT_Y],
+ NULL);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+rotate_angle_changed (GtkAdjustment *adj,
+ GimpTransformGridTool *tg_tool)
+{
+ gdouble value = gimp_deg_to_rad (gtk_adjustment_get_value (adj));
+
+ if (fabs (value - tg_tool->trans_info[ANGLE]) > EPSILON)
+ {
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ tg_tool->trans_info[ANGLE] = value;
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+}
+
+static void
+rotate_center_changed (GtkWidget *widget,
+ GimpTransformGridTool *tg_tool)
+{
+ gdouble px = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0);
+ gdouble py = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1);
+
+ if ((px != tg_tool->trans_info[PIVOT_X]) ||
+ (py != tg_tool->trans_info[PIVOT_Y]))
+ {
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ tg_tool->trans_info[PIVOT_X] = px;
+ tg_tool->trans_info[PIVOT_Y] = py;
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+}
+
+static void
+rotate_pivot_changed (GimpPivotSelector *selector,
+ GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ gimp_pivot_selector_get_position (selector,
+ &tg_tool->trans_info[PIVOT_X],
+ &tg_tool->trans_info[PIVOT_Y]);
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+}
diff --git a/app/tools/gimprotatetool.h b/app/tools/gimprotatetool.h
new file mode 100644
index 0000000..5a09ca2
--- /dev/null
+++ b/app/tools/gimprotatetool.h
@@ -0,0 +1,58 @@
+/* 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_ROTATE_TOOL_H__
+#define __GIMP_ROTATE_TOOL_H__
+
+
+#include "gimptransformgridtool.h"
+
+
+#define GIMP_TYPE_ROTATE_TOOL (gimp_rotate_tool_get_type ())
+#define GIMP_ROTATE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ROTATE_TOOL, GimpRotateTool))
+#define GIMP_ROTATE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ROTATE_TOOL, GimpRotateToolClass))
+#define GIMP_IS_ROTATE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ROTATE_TOOL))
+#define GIMP_IS_ROTATE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ROTATE_TOOL))
+#define GIMP_ROTATE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ROTATE_TOOL, GimpRotateToolClass))
+
+
+typedef struct _GimpRotateTool GimpRotateTool;
+typedef struct _GimpRotateToolClass GimpRotateToolClass;
+
+struct _GimpRotateTool
+{
+ GimpTransformGridTool parent_instance;
+
+ GtkAdjustment *angle_adj;
+ GtkWidget *angle_spin_button;
+ GtkWidget *sizeentry;
+ GtkWidget *pivot_selector;
+};
+
+struct _GimpRotateToolClass
+{
+ GimpTransformGridToolClass parent_class;
+};
+
+
+void gimp_rotate_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_rotate_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ROTATE_TOOL_H__ */
diff --git a/app/tools/gimpsamplepointtool.c b/app/tools/gimpsamplepointtool.c
new file mode 100644
index 0000000..8d0c153
--- /dev/null
+++ b/app/tools/gimpsamplepointtool.c
@@ -0,0 +1,373 @@
+/* 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 "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpsamplepoint.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-sample-points.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-selection.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimpsamplepointtool.h"
+#include "gimptoolcontrol.h"
+#include "tool_manager.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_sample_point_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_sample_point_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_sample_point_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_sample_point_tool_start (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpSamplePoint *sample_point);
+
+
+G_DEFINE_TYPE (GimpSamplePointTool, gimp_sample_point_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_sample_point_tool_parent_class
+
+
+static void
+gimp_sample_point_tool_class_init (GimpSamplePointToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->button_release = gimp_sample_point_tool_button_release;
+ tool_class->motion = gimp_sample_point_tool_motion;
+
+ draw_tool_class->draw = gimp_sample_point_tool_draw;
+}
+
+static void
+gimp_sample_point_tool_init (GimpSamplePointTool *sp_tool)
+{
+ GimpTool *tool = GIMP_TOOL (sp_tool);
+
+ gimp_tool_control_set_snap_to (tool->control, FALSE);
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_MOVE);
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+
+ sp_tool->sample_point = NULL;
+ sp_tool->sample_point_x = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+ sp_tool->sample_point_y = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+}
+
+static void
+gimp_sample_point_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpSamplePointTool *sp_tool = GIMP_SAMPLE_POINT_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+
+ gimp_tool_pop_status (tool, display);
+
+ gimp_tool_control_halt (tool->control);
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ gint width = gimp_image_get_width (image);
+ gint height = gimp_image_get_height (image);
+
+ if (sp_tool->sample_point_x == GIMP_SAMPLE_POINT_POSITION_UNDEFINED ||
+ sp_tool->sample_point_x < 0 ||
+ sp_tool->sample_point_x >= width ||
+ sp_tool->sample_point_y == GIMP_SAMPLE_POINT_POSITION_UNDEFINED ||
+ sp_tool->sample_point_y < 0 ||
+ sp_tool->sample_point_y >= height)
+ {
+ if (sp_tool->sample_point)
+ {
+ gimp_image_remove_sample_point (image,
+ sp_tool->sample_point, TRUE);
+ sp_tool->sample_point = NULL;
+ }
+ }
+ else
+ {
+ if (sp_tool->sample_point)
+ {
+ gimp_image_move_sample_point (image,
+ sp_tool->sample_point,
+ sp_tool->sample_point_x,
+ sp_tool->sample_point_y,
+ TRUE);
+ }
+ else
+ {
+ sp_tool->sample_point =
+ gimp_image_add_sample_point_at_pos (image,
+ sp_tool->sample_point_x,
+ sp_tool->sample_point_y,
+ TRUE);
+ }
+ }
+
+ gimp_image_flush (image);
+ }
+
+ gimp_display_shell_selection_resume (shell);
+
+ sp_tool->sample_point_x = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+ sp_tool->sample_point_y = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+
+ tool_manager_pop_tool (display->gimp);
+ g_object_unref (sp_tool);
+
+ {
+ GimpTool *active_tool = tool_manager_get_active (display->gimp);
+
+ if (GIMP_IS_DRAW_TOOL (active_tool))
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (active_tool));
+
+ tool_manager_oper_update_active (display->gimp, coords, state,
+ TRUE, display);
+ tool_manager_cursor_update_active (display->gimp, coords, state,
+ display);
+
+ if (GIMP_IS_DRAW_TOOL (active_tool))
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (active_tool));
+ }
+}
+
+static void
+gimp_sample_point_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+
+{
+ GimpSamplePointTool *sp_tool = GIMP_SAMPLE_POINT_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ gboolean delete_point = FALSE;
+ gint tx, ty;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ gimp_display_shell_transform_xy (shell,
+ coords->x, coords->y,
+ &tx, &ty);
+
+ if (tx < 0 || tx >= shell->disp_width ||
+ ty < 0 || ty >= shell->disp_height)
+ {
+ sp_tool->sample_point_x = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+ sp_tool->sample_point_y = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+
+ delete_point = TRUE;
+ }
+ else
+ {
+ GimpImage *image = gimp_display_get_image (display);
+ gint width = gimp_image_get_width (image);
+ gint height = gimp_image_get_height (image);
+
+ sp_tool->sample_point_x = floor (coords->x);
+ sp_tool->sample_point_y = floor (coords->y);
+
+ if (sp_tool->sample_point_x < 0 ||
+ sp_tool->sample_point_x >= height ||
+ sp_tool->sample_point_y < 0 ||
+ sp_tool->sample_point_y >= width)
+ {
+ delete_point = TRUE;
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_pop_status (tool, display);
+
+ if (delete_point)
+ {
+ gimp_tool_push_status (tool, display,
+ sp_tool->sample_point ?
+ _("Remove Sample Point") :
+ _("Cancel Sample Point"));
+ }
+ else if (sp_tool->sample_point)
+ {
+ gimp_tool_push_status_coords (tool, display,
+ gimp_tool_control_get_precision (tool->control),
+ _("Move Sample Point: "),
+ sp_tool->sample_point_x -
+ sp_tool->sample_point_old_x,
+ ", ",
+ sp_tool->sample_point_y -
+ sp_tool->sample_point_old_y,
+ NULL);
+ }
+ else
+ {
+ gimp_tool_push_status_coords (tool, display,
+ gimp_tool_control_get_precision (tool->control),
+ _("Add Sample Point: "),
+ sp_tool->sample_point_x,
+ ", ",
+ sp_tool->sample_point_y,
+ NULL);
+ }
+}
+
+static void
+gimp_sample_point_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpSamplePointTool *sp_tool = GIMP_SAMPLE_POINT_TOOL (draw_tool);
+
+ if (sp_tool->sample_point_x != GIMP_SAMPLE_POINT_POSITION_UNDEFINED &&
+ sp_tool->sample_point_y != GIMP_SAMPLE_POINT_POSITION_UNDEFINED)
+ {
+ gimp_draw_tool_add_crosshair (draw_tool,
+ sp_tool->sample_point_x,
+ sp_tool->sample_point_y);
+ }
+}
+
+static void
+gimp_sample_point_tool_start (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpSamplePoint *sample_point)
+{
+ GimpSamplePointTool *sp_tool;
+ GimpTool *tool;
+
+ sp_tool = g_object_new (GIMP_TYPE_SAMPLE_POINT_TOOL,
+ "tool-info", parent_tool->tool_info,
+ NULL);
+
+ tool = GIMP_TOOL (sp_tool);
+
+ gimp_display_shell_selection_pause (gimp_display_get_shell (display));
+
+ if (sample_point)
+ {
+ sp_tool->sample_point = sample_point;
+
+ gimp_sample_point_get_position (sample_point,
+ &sp_tool->sample_point_old_x,
+ &sp_tool->sample_point_old_y);
+
+ sp_tool->sample_point_x = sp_tool->sample_point_old_x;
+ sp_tool->sample_point_y = sp_tool->sample_point_old_y;
+ }
+ else
+ {
+ sp_tool->sample_point = NULL;
+ sp_tool->sample_point_old_x = 0;
+ sp_tool->sample_point_old_y = 0;
+ sp_tool->sample_point_x = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+ sp_tool->sample_point_y = GIMP_SAMPLE_POINT_POSITION_UNDEFINED;
+ }
+
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_MOUSE,
+ GIMP_TOOL_CURSOR_COLOR_PICKER,
+ GIMP_CURSOR_MODIFIER_MOVE);
+
+ tool_manager_push_tool (display->gimp, tool);
+
+ tool->display = display;
+ gimp_tool_control_activate (tool->control);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (sp_tool), display);
+
+ if (sp_tool->sample_point)
+ {
+ gimp_tool_push_status_coords (tool, display,
+ gimp_tool_control_get_precision (tool->control),
+ _("Move Sample Point: "),
+ sp_tool->sample_point_x -
+ sp_tool->sample_point_old_x,
+ ", ",
+ sp_tool->sample_point_y -
+ sp_tool->sample_point_old_y,
+ NULL);
+ }
+ else
+ {
+ gimp_tool_push_status_coords (tool, display,
+ gimp_tool_control_get_precision (tool->control),
+ _("Add Sample Point: "),
+ sp_tool->sample_point_x,
+ ", ",
+ sp_tool->sample_point_y,
+ NULL);
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_sample_point_tool_start_new (GimpTool *parent_tool,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (parent_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ gimp_sample_point_tool_start (parent_tool, display, NULL);
+}
+
+void
+gimp_sample_point_tool_start_edit (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpSamplePoint *sample_point)
+{
+ g_return_if_fail (GIMP_IS_TOOL (parent_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ gimp_sample_point_tool_start (parent_tool, display, sample_point);
+}
diff --git a/app/tools/gimpsamplepointtool.h b/app/tools/gimpsamplepointtool.h
new file mode 100644
index 0000000..1e5a3b2
--- /dev/null
+++ b/app/tools/gimpsamplepointtool.h
@@ -0,0 +1,62 @@
+/* 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_SAMPLE_POINT_TOOL_H__
+#define __GIMP_SAMPLE_POINT_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_SAMPLE_POINT_TOOL (gimp_sample_point_tool_get_type ())
+#define GIMP_SAMPLE_POINT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SAMPLE_POINT_TOOL, GimpSamplePointTool))
+#define GIMP_SAMPLE_POINT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SAMPLE_POINT_TOOL, GimpSamplePointToolClass))
+#define GIMP_IS_SAMPLE_POINT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SAMPLE_POINT_TOOL))
+#define GIMP_IS_SAMPLE_POINT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SAMPLE_POINT_TOOL))
+#define GIMP_SAMPLE_POINT_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SAMPLE_POINT_TOOL, GimpSamplePointToolClass))
+
+
+typedef struct _GimpSamplePointTool GimpSamplePointTool;
+typedef struct _GimpSamplePointToolClass GimpSamplePointToolClass;
+
+struct _GimpSamplePointTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpSamplePoint *sample_point;
+ gint sample_point_old_x;
+ gint sample_point_old_y;
+ gint sample_point_x;
+ gint sample_point_y;
+};
+
+struct _GimpSamplePointToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+GType gimp_sample_point_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_sample_point_tool_start_new (GimpTool *parent_tool,
+ GimpDisplay *display);
+void gimp_sample_point_tool_start_edit (GimpTool *parent_tool,
+ GimpDisplay *display,
+ GimpSamplePoint *sample_point);
+
+
+#endif /* __GIMP_SAMPLE_POINT_TOOL_H__ */
diff --git a/app/tools/gimpscaletool.c b/app/tools/gimpscaletool.c
new file mode 100644
index 0000000..b7fcd96
--- /dev/null
+++ b/app/tools/gimpscaletool.c
@@ -0,0 +1,474 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpsizebox.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptooltransformgrid.h"
+
+#include "gimpscaletool.h"
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 1e-6
+
+
+/* index into trans_info array */
+enum
+{
+ X0,
+ Y0,
+ X1,
+ Y1
+};
+
+
+/* local function prototypes */
+
+static gboolean gimp_scale_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+static void gimp_scale_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+static gchar * gimp_scale_tool_get_undo_desc (GimpTransformGridTool *tg_tool);
+static void gimp_scale_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_scale_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_scale_tool_prepare (GimpTransformGridTool *tg_tool);
+static void gimp_scale_tool_readjust (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_scale_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_scale_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_scale_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void gimp_scale_tool_size_notify (GtkWidget *box,
+ GParamSpec *pspec,
+ GimpTransformGridTool *tg_tool);
+
+
+G_DEFINE_TYPE (GimpScaleTool, gimp_scale_tool, GIMP_TYPE_TRANSFORM_GRID_TOOL)
+
+#define parent_class gimp_scale_tool_parent_class
+
+
+void
+gimp_scale_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_SCALE_TOOL,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS,
+ gimp_transform_grid_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-scale-tool",
+ _("Scale"),
+ _("Scale Tool: Scale the layer, selection or path"),
+ N_("_Scale"), "<shift>S",
+ NULL, GIMP_HELP_TOOL_SCALE,
+ GIMP_ICON_TOOL_SCALE,
+ data);
+}
+
+static void
+gimp_scale_tool_class_init (GimpScaleToolClass *klass)
+{
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+
+ tg_class->info_to_matrix = gimp_scale_tool_info_to_matrix;
+ tg_class->matrix_to_info = gimp_scale_tool_matrix_to_info;
+ tg_class->get_undo_desc = gimp_scale_tool_get_undo_desc;
+ tg_class->dialog = gimp_scale_tool_dialog;
+ tg_class->dialog_update = gimp_scale_tool_dialog_update;
+ tg_class->prepare = gimp_scale_tool_prepare;
+ tg_class->readjust = gimp_scale_tool_readjust;
+ tg_class->get_widget = gimp_scale_tool_get_widget;
+ tg_class->update_widget = gimp_scale_tool_update_widget;
+ tg_class->widget_changed = gimp_scale_tool_widget_changed;
+
+ tr_class->undo_desc = C_("undo-type", "Scale");
+ tr_class->progress_text = _("Scaling");
+ tg_class->ok_button_label = _("_Scale");
+}
+
+static void
+gimp_scale_tool_init (GimpScaleTool *scale_tool)
+{
+ GimpTool *tool = GIMP_TOOL (scale_tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_RESIZE);
+}
+
+static gboolean
+gimp_scale_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ gimp_matrix3_identity (transform);
+ gimp_transform_matrix_scale (transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2 - tr_tool->x1,
+ tr_tool->y2 - tr_tool->y1,
+ tg_tool->trans_info[X0],
+ tg_tool->trans_info[Y0],
+ tg_tool->trans_info[X1] - tg_tool->trans_info[X0],
+ tg_tool->trans_info[Y1] - tg_tool->trans_info[Y0]);
+
+ return TRUE;
+}
+
+static void
+gimp_scale_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ gdouble x;
+ gdouble y;
+ gdouble w;
+ gdouble h;
+
+ x = transform->coeff[0][2];
+ y = transform->coeff[1][2];
+ w = transform->coeff[0][0];
+ h = transform->coeff[1][1];
+
+ tg_tool->trans_info[X0] = x + w * tr_tool->x1;
+ tg_tool->trans_info[Y0] = y + h * tr_tool->y1;
+ tg_tool->trans_info[X1] = tg_tool->trans_info[X0] +
+ w * (tr_tool->x2 - tr_tool->x1);
+ tg_tool->trans_info[Y1] = tg_tool->trans_info[Y0] +
+ h * (tr_tool->y2 - tr_tool->y1);
+}
+
+static gchar *
+gimp_scale_tool_get_undo_desc (GimpTransformGridTool *tg_tool)
+{
+ gint width;
+ gint height;
+
+ width = ROUND (tg_tool->trans_info[X1] - tg_tool->trans_info[X0]);
+ height = ROUND (tg_tool->trans_info[Y1] - tg_tool->trans_info[Y0]);
+
+ return g_strdup_printf (C_("undo-type", "Scale to %d x %d"),
+ width, height);
+}
+
+static void
+gimp_scale_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+}
+
+static void
+gimp_scale_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ gint width;
+ gint height;
+
+ width = ROUND (tg_tool->trans_info[X1] - tg_tool->trans_info[X0]);
+ height = ROUND (tg_tool->trans_info[Y1] - tg_tool->trans_info[Y0]);
+
+ g_object_set (GIMP_SCALE_TOOL (tg_tool)->box,
+ "width", width,
+ "height", height,
+ "keep-aspect", options->constrain_scale,
+ NULL);
+}
+
+static void
+gimp_scale_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpScaleTool *scale = GIMP_SCALE_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpDisplay *display = GIMP_TOOL (tg_tool)->display;
+ gdouble xres;
+ gdouble yres;
+
+ tg_tool->trans_info[X0] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y0] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X1] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y1] = (gdouble) tr_tool->y2;
+
+ gimp_image_get_resolution (gimp_display_get_image (display),
+ &xres, &yres);
+
+ if (scale->box)
+ {
+ g_signal_handlers_disconnect_by_func (scale->box,
+ gimp_scale_tool_size_notify,
+ tg_tool);
+ gtk_widget_destroy (scale->box);
+ }
+
+ /* Need to create a new GimpSizeBox widget because the initial
+ * width and height is what counts as 100%.
+ */
+ scale->box =
+ g_object_new (GIMP_TYPE_SIZE_BOX,
+ "width", tr_tool->x2 - tr_tool->x1,
+ "height", tr_tool->y2 - tr_tool->y1,
+ "keep-aspect", options->constrain_scale,
+ "unit", gimp_display_get_shell (display)->unit,
+ "xresolution", xres,
+ "yresolution", yres,
+ NULL);
+
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tg_tool->gui)),
+ scale->box, FALSE, FALSE, 0);
+ gtk_widget_show (scale->box);
+
+ g_signal_connect (scale->box, "notify",
+ G_CALLBACK (gimp_scale_tool_size_notify),
+ tg_tool);
+}
+
+static void
+gimp_scale_tool_readjust (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ gdouble x;
+ gdouble y;
+ gdouble r;
+
+ x = shell->disp_width / 2.0;
+ y = shell->disp_height / 2.0;
+ r = MAX (MIN (x, y) / G_SQRT2 -
+ GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE / 2.0,
+ GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE / 2.0);
+
+ gimp_display_shell_untransform_xy_f (shell,
+ x, y,
+ &x, &y);
+
+ tg_tool->trans_info[X0] = RINT (x - FUNSCALEX (shell, r));
+ tg_tool->trans_info[Y0] = RINT (y - FUNSCALEY (shell, r));
+ tg_tool->trans_info[X1] = RINT (x + FUNSCALEX (shell, r));
+ tg_tool->trans_info[Y1] = RINT (y + FUNSCALEY (shell, r));
+}
+
+static GimpToolWidget *
+gimp_scale_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+
+ widget = gimp_tool_transform_grid_new (shell,
+ &tr_tool->transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2);
+
+ g_object_set (widget,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_SCALE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_SCALE,
+ "use-corner-handles", TRUE,
+ "use-side-handles", TRUE,
+ "use-center-handle", TRUE,
+ NULL);
+
+ return widget;
+}
+
+static void
+gimp_scale_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ g_object_set (
+ tg_tool->widget,
+ "x1", (gdouble) tr_tool->x1,
+ "y1", (gdouble) tr_tool->y1,
+ "x2", (gdouble) tr_tool->x2,
+ "y2", (gdouble) tr_tool->y2,
+ NULL);
+}
+
+static void
+gimp_scale_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpMatrix3 *transform;
+ gdouble x0, y0;
+ gdouble x1, y1;
+ gint width, height;
+
+ g_object_get (tg_tool->widget,
+ "transform", &transform,
+ NULL);
+
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1, tr_tool->y1,
+ &x0, &y0);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2, tr_tool->y2,
+ &x1, &y1);
+
+ g_free (transform);
+
+ width = ROUND (x1 - x0);
+ height = ROUND (y1 - y0);
+
+ if (width > 0)
+ {
+ tg_tool->trans_info[X0] = x0;
+ tg_tool->trans_info[X1] = x1;
+ }
+ else if (fabs (x0 - tg_tool->trans_info[X0]) < EPSILON)
+ {
+ tg_tool->trans_info[X1] = tg_tool->trans_info[X0] + 1.0;
+ }
+ else if (fabs (x1 - tg_tool->trans_info[X1]) < EPSILON)
+ {
+ tg_tool->trans_info[X0] = tg_tool->trans_info[X1] - 1.0;
+ }
+ else
+ {
+ tg_tool->trans_info[X0] = (x0 + x1) / 2.0 - 0.5;
+ tg_tool->trans_info[X1] = (x0 + x1) / 2.0 + 0.5;
+ }
+
+ if (height > 0)
+ {
+ tg_tool->trans_info[Y0] = y0;
+ tg_tool->trans_info[Y1] = y1;
+ }
+ else if (fabs (y0 - tg_tool->trans_info[Y0]) < EPSILON)
+ {
+ tg_tool->trans_info[Y1] = tg_tool->trans_info[Y0] + 1.0;
+ }
+ else if (fabs (y1 - tg_tool->trans_info[Y1]) < EPSILON)
+ {
+ tg_tool->trans_info[Y0] = tg_tool->trans_info[Y1] - 1.0;
+ }
+ else
+ {
+ tg_tool->trans_info[Y0] = (y0 + y1) / 2.0 - 0.5;
+ tg_tool->trans_info[Y1] = (y0 + y1) / 2.0 + 0.5;
+ }
+
+ if (width <= 0 || height <= 0)
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+gimp_scale_tool_size_notify (GtkWidget *box,
+ GParamSpec *pspec,
+ GimpTransformGridTool *tg_tool)
+{
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+
+ if (! strcmp (pspec->name, "width") ||
+ ! strcmp (pspec->name, "height"))
+ {
+ gint width;
+ gint height;
+ gint old_width;
+ gint old_height;
+
+ g_object_get (box,
+ "width", &width,
+ "height", &height,
+ NULL);
+
+ old_width = ROUND (tg_tool->trans_info[X1] - tg_tool->trans_info[X0]);
+ old_height = ROUND (tg_tool->trans_info[Y1] - tg_tool->trans_info[Y0]);
+
+ if ((width != old_width) || (height != old_height))
+ {
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ if (options->frompivot_scale)
+ {
+ gdouble center_x;
+ gdouble center_y;
+
+ center_x = (tg_tool->trans_info[X0] +
+ tg_tool->trans_info[X1]) / 2.0;
+ center_y = (tg_tool->trans_info[Y0] +
+ tg_tool->trans_info[Y1]) / 2.0;
+
+ tg_tool->trans_info[X0] = center_x - width / 2.0;
+ tg_tool->trans_info[Y0] = center_y - height / 2.0;
+ tg_tool->trans_info[X1] = center_x + width / 2.0;
+ tg_tool->trans_info[Y1] = center_y + height / 2.0;
+ }
+ else
+ {
+ tg_tool->trans_info[X1] = tg_tool->trans_info[X0] + width;
+ tg_tool->trans_info[Y1] = tg_tool->trans_info[Y0] + height;
+ }
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+ }
+ else if (! strcmp (pspec->name, "keep-aspect"))
+ {
+ gboolean constrain;
+
+ g_object_get (box,
+ "keep-aspect", &constrain,
+ NULL);
+
+ if (constrain != options->constrain_scale)
+ {
+ gint width;
+ gint height;
+
+ g_object_get (box,
+ "width", &width,
+ "height", &height,
+ NULL);
+
+ g_object_set (options,
+ "constrain-scale", constrain,
+ NULL);
+ }
+ }
+}
diff --git a/app/tools/gimpscaletool.h b/app/tools/gimpscaletool.h
new file mode 100644
index 0000000..e3c4b19
--- /dev/null
+++ b/app/tools/gimpscaletool.h
@@ -0,0 +1,54 @@
+/* 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_SCALE_TOOL_H__
+#define __GIMP_SCALE_TOOL_H__
+
+
+#include "gimptransformgridtool.h"
+
+
+#define GIMP_TYPE_SCALE_TOOL (gimp_scale_tool_get_type ())
+#define GIMP_SCALE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SCALE_TOOL, GimpScaleTool))
+#define GIMP_SCALE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SCALE_TOOL, GimpScaleToolClass))
+#define GIMP_IS_SCALE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SCALE_TOOL))
+#define GIMP_SCALE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SCALE_TOOL, GimpScaleToolClass))
+
+
+typedef struct _GimpScaleTool GimpScaleTool;
+typedef struct _GimpScaleToolClass GimpScaleToolClass;
+
+struct _GimpScaleTool
+{
+ GimpTransformGridTool parent_instance;
+
+ GtkWidget *box;
+};
+
+struct _GimpScaleToolClass
+{
+ GimpTransformGridToolClass parent_class;
+};
+
+
+void gimp_scale_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_scale_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SCALE_TOOL_H__ */
diff --git a/app/tools/gimpseamlesscloneoptions.c b/app/tools/gimpseamlesscloneoptions.c
new file mode 100644
index 0000000..cb2c557
--- /dev/null
+++ b/app/tools/gimpseamlesscloneoptions.c
@@ -0,0 +1,138 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpseamlesscloneoptions.c
+ * Copyright (C) 2011 Barak Itkin <lightningismyname@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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+
+#include "gimpseamlesscloneoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_MAX_REFINE_SCALE,
+};
+
+
+static void gimp_seamless_clone_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_seamless_clone_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpSeamlessCloneOptions, gimp_seamless_clone_options,
+ GIMP_TYPE_TOOL_OPTIONS)
+
+#define parent_class gimp_seamless_clone_options_parent_class
+
+
+static void
+gimp_seamless_clone_options_class_init (GimpSeamlessCloneOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_seamless_clone_options_set_property;
+ object_class->get_property = gimp_seamless_clone_options_get_property;
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_MAX_REFINE_SCALE,
+ "max-refine-scale",
+ _("Refinement scale"),
+ _("Maximal scale of refinement points to be "
+ "used for the interpolation mesh"),
+ 0, 50, 5,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_seamless_clone_options_init (GimpSeamlessCloneOptions *options)
+{
+}
+
+static void
+gimp_seamless_clone_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSeamlessCloneOptions *options = GIMP_SEAMLESS_CLONE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_MAX_REFINE_SCALE:
+ options->max_refine_scale = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_seamless_clone_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSeamlessCloneOptions *options = GIMP_SEAMLESS_CLONE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_MAX_REFINE_SCALE:
+ g_value_set_int (value, options->max_refine_scale);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_seamless_clone_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *scale;
+
+ scale = gimp_prop_spin_scale_new (config, "max-refine-scale", NULL,
+ 1.0, 10.0, 0);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 0.0, 50.0);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return vbox;
+}
diff --git a/app/tools/gimpseamlesscloneoptions.h b/app/tools/gimpseamlesscloneoptions.h
new file mode 100644
index 0000000..872aa5d
--- /dev/null
+++ b/app/tools/gimpseamlesscloneoptions.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpseamlesscloneoptions.h
+ * Copyright (C) 2011 Barak Itkin <lightningismyname@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_SEAMLESS_CLONE_OPTIONS_H__
+#define __GIMP_SEAMLESS_CLONE_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_SEAMLESS_CLONE_OPTIONS (gimp_seamless_clone_options_get_type ())
+#define GIMP_SEAMLESS_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SEAMLESS_CLONE_OPTIONS, GimpSeamlessCloneOptions))
+#define GIMP_SEAMLESS_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SEAMLESS_CLONE_OPTIONS, GimpSeamlessCloneOptionsClass))
+#define GIMP_IS_SEAMLESS_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SEAMLESS_CLONE_OPTIONS))
+#define GIMP_IS_SEAMLESS_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SEAMLESS_CLONE_OPTIONS))
+#define GIMP_SEAMLESS_CLONE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SEAMLESS_CLONE_OPTIONS, GimpSeamlessCloneOptionsClass))
+
+
+typedef struct _GimpSeamlessCloneOptions GimpSeamlessCloneOptions;
+typedef struct _GimpSeamlessCloneOptionsClass GimpSeamlessCloneOptionsClass;
+
+struct _GimpSeamlessCloneOptions
+{
+ GimpToolOptions parent_instance;
+
+ gint max_refine_scale;
+ gboolean temp;
+};
+
+struct _GimpSeamlessCloneOptionsClass
+{
+ GimpToolOptionsClass parent_class;
+};
+
+
+GType gimp_seamless_clone_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_seamless_clone_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_SEAMLESS_CLONE_OPTIONS_H__ */
diff --git a/app/tools/gimpseamlessclonetool.c b/app/tools/gimpseamlessclonetool.c
new file mode 100644
index 0000000..79a9780
--- /dev/null
+++ b/app/tools/gimpseamlessclonetool.c
@@ -0,0 +1,845 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpseamlessclonetool.c
+ * Copyright (C) 2011 Barak Itkin <lightningismyname@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 <gegl-plugin.h> /* gegl_operation_invalidate() */
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h" /* playground */
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimpbuffer.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpclipboard.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimpseamlessclonetool.h"
+#include "gimpseamlesscloneoptions.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+#define SC_DEBUG TRUE
+
+#ifdef SC_DEBUG
+#define sc_debug_fstart() g_debug ("%s::start", __FUNCTION__);
+#define sc_debug_fend() g_debug ("%s::end", __FUNCTION__);
+#else
+#define sc_debug_fstart()
+#define sc_debug_fend()
+#endif
+
+#define gimp_seamless_clone_tool_is_in_paste(sc,x0,y0) \
+ ( ((sc)->xoff <= (x0) && (x0) < (sc)->xoff + (sc)->width) \
+ && ((sc)->yoff <= (y0) && (y0) < (sc)->yoff + (sc)->height)) \
+
+#define gimp_seamless_clone_tool_is_in_paste_c(sc,coords) \
+ gimp_seamless_clone_tool_is_in_paste((sc),(coords)->x,(coords)->y)
+
+
+/* init ----------> preprocess
+ * | |
+ * | |
+ * | |
+ * | v
+ * | render(wait, motion)
+ * | / |
+ * | _____/ |
+ * | _____/ |
+ * v v v
+ * quit <---------- commit
+ *
+ * Begin at INIT state
+ *
+ * INIT: Wait for click on canvas
+ * have a paste ? -> PREPROCESS : -> QUIT
+ *
+ * PREPROCESS: Do the preprocessing
+ * -> RENDER
+ *
+ * RENDER: Interact and wait for quit signal
+ * commit quit ? -> COMMIT : -> QUIT
+ *
+ * COMMIT: Commit the changes
+ * -> QUIT
+ *
+ * QUIT: Invoked by sending a ACTION_HALT to the tool_control
+ * Free resources
+ */
+enum
+{
+ SC_STATE_INIT,
+ SC_STATE_PREPROCESS,
+ SC_STATE_RENDER_WAIT,
+ SC_STATE_RENDER_MOTION,
+ SC_STATE_COMMIT,
+ SC_STATE_QUIT
+};
+
+
+static void gimp_seamless_clone_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+
+static void gimp_seamless_clone_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_seamless_clone_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_seamless_clone_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_seamless_clone_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_seamless_clone_tool_start (GimpSeamlessCloneTool *sc,
+ GimpDisplay *display);
+
+static void gimp_seamless_clone_tool_stop (GimpSeamlessCloneTool *sc,
+ gboolean display_change_only);
+
+static void gimp_seamless_clone_tool_commit (GimpSeamlessCloneTool *sc);
+
+static void gimp_seamless_clone_tool_create_render_node (GimpSeamlessCloneTool *sc);
+static gboolean gimp_seamless_clone_tool_render_node_update (GimpSeamlessCloneTool *sc);
+static void gimp_seamless_clone_tool_create_filter (GimpSeamlessCloneTool *sc,
+ GimpDrawable *drawable);
+static void gimp_seamless_clone_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+static void gimp_seamless_clone_tool_filter_update (GimpSeamlessCloneTool *sc);
+
+
+G_DEFINE_TYPE (GimpSeamlessCloneTool, gimp_seamless_clone_tool,
+ GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_seamless_clone_tool_parent_class
+
+
+void
+gimp_seamless_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ /* we should not know that "data" is a Gimp*, but what the heck this
+ * is experimental playground stuff
+ */
+ if (GIMP_GUI_CONFIG (GIMP (data)->config)->playground_seamless_clone_tool)
+ (* callback) (GIMP_TYPE_SEAMLESS_CLONE_TOOL,
+ GIMP_TYPE_SEAMLESS_CLONE_OPTIONS,
+ gimp_seamless_clone_options_gui,
+ 0,
+ "gimp-seamless-clone-tool",
+ _("Seamless Clone"),
+ _("Seamless Clone: Seamlessly paste one image into another"),
+ N_("_Seamless Clone"), NULL,
+ NULL, GIMP_HELP_TOOL_SEAMLESS_CLONE,
+ GIMP_ICON_TOOL_SEAMLESS_CLONE,
+ data);
+}
+
+static void
+gimp_seamless_clone_tool_class_init (GimpSeamlessCloneToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_seamless_clone_tool_control;
+ tool_class->button_press = gimp_seamless_clone_tool_button_press;
+ tool_class->button_release = gimp_seamless_clone_tool_button_release;
+ tool_class->motion = gimp_seamless_clone_tool_motion;
+ tool_class->key_press = gimp_seamless_clone_tool_key_press;
+ tool_class->oper_update = gimp_seamless_clone_tool_oper_update;
+ tool_class->cursor_update = gimp_seamless_clone_tool_cursor_update;
+ tool_class->options_notify = gimp_seamless_clone_tool_options_notify;
+
+ draw_tool_class->draw = gimp_seamless_clone_tool_draw;
+}
+
+static void
+gimp_seamless_clone_tool_init (GimpSeamlessCloneTool *self)
+{
+ GimpTool *tool = GIMP_TOOL (self);
+
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION);
+
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_MOVE);
+
+ self->tool_state = SC_STATE_INIT;
+}
+
+static void
+gimp_seamless_clone_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ if (tool->display)
+ gimp_seamless_clone_tool_stop (sc, FALSE);
+
+ /* TODO: If we have any tool options that should be reset, here is
+ * a good place to do so.
+ */
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_seamless_clone_tool_commit (sc);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+/**
+ * gimp_seamless_clone_tool_start:
+ * @sc: The GimpSeamlessCloneTool to initialize for usage on the given
+ * display
+ * @display: The display to initialize the tool for
+ *
+ * A utility function to initialize a tool for working on a given
+ * display. At the beginning of each function, we can check if the event's
+ * display is the same as the tool's one, and if not call this. This is
+ * not required by the gimptool interface or anything like that, but
+ * this is a convenient way to do all the initialization work in one
+ * place, and this is how the base class (GimpDrawTool) does that
+ */
+static void
+gimp_seamless_clone_tool_start (GimpSeamlessCloneTool *sc,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (sc);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ /* First handle the paste - we need to make sure we have one in order
+ * to do anything else.
+ */
+ if (sc->paste == NULL)
+ {
+ GimpBuffer *buffer = gimp_clipboard_get_buffer (tool->tool_info->gimp);
+
+ if (! buffer)
+ {
+ gimp_tool_push_status (tool, display,
+ "%s",
+ _("There is no image data in the clipboard to paste."));
+ return;
+ }
+
+ sc->paste = gimp_gegl_buffer_dup (gimp_buffer_get_buffer (buffer));
+ g_object_unref (buffer);
+
+ sc->width = gegl_buffer_get_width (sc->paste);
+ sc->height = gegl_buffer_get_height (sc->paste);
+ }
+
+ /* Free resources which are relevant only for the previous display */
+ gimp_seamless_clone_tool_stop (sc, TRUE);
+
+ tool->display = display;
+
+ gimp_seamless_clone_tool_create_filter (sc, drawable);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (sc), display);
+
+ sc->tool_state = SC_STATE_RENDER_WAIT;
+}
+
+
+/**
+ * gimp_seamless_clone_tool_stop:
+ * @sc: The seamless clone tool whose resources should be freed
+ * @display_change_only: Mark that the only reason for this call was a
+ * switch of the working display.
+ *
+ * This function frees any resources associated with the seamless clone
+ * tool, including caches, gegl graphs, and anything the tool created.
+ * Afterwards, it initializes all the relevant pointers to some initial
+ * value (usually NULL) like the init function does.
+ *
+ * Note that for seamless cloning, no change needs to be done when
+ * switching to a different display, except for clearing the image map.
+ * So for that, we provide a boolean parameter to specify that the only
+ * change was one of the display
+ */
+static void
+gimp_seamless_clone_tool_stop (GimpSeamlessCloneTool *sc,
+ gboolean display_change_only)
+{
+ /* See if we actually have any reason to stop */
+ if (sc->tool_state == SC_STATE_INIT)
+ return;
+
+ if (! display_change_only)
+ {
+ sc->tool_state = SC_STATE_INIT;
+
+ g_clear_object (&sc->paste);
+ g_clear_object (&sc->render_node);
+ sc->sc_node = NULL;
+ }
+
+ /* This should always happen, even when we just switch a display */
+ if (sc->filter)
+ {
+ gimp_drawable_filter_abort (sc->filter);
+ g_clear_object (&sc->filter);
+
+ if (GIMP_TOOL (sc)->display)
+ gimp_image_flush (gimp_display_get_image (GIMP_TOOL (sc)->display));
+ }
+
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (sc));
+}
+
+static void
+gimp_seamless_clone_tool_commit (GimpSeamlessCloneTool *sc)
+{
+ GimpTool *tool = GIMP_TOOL (sc);
+
+ if (sc->filter)
+ {
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_commit (sc->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&sc->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+}
+
+static void
+gimp_seamless_clone_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ if (display != tool->display)
+ {
+ gimp_seamless_clone_tool_start (sc, display);
+
+ /* Center the paste on the mouse */
+ sc->xoff = (gint) coords->x - sc->width / 2;
+ sc->yoff = (gint) coords->y - sc->height / 2;
+ }
+
+ if (sc->tool_state == SC_STATE_RENDER_WAIT &&
+ gimp_seamless_clone_tool_is_in_paste_c (sc, coords))
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (sc));
+
+ /* Record previous location, in case the user cancels the
+ * movement
+ */
+ sc->xoff_p = sc->xoff;
+ sc->yoff_p = sc->yoff;
+
+ /* Record the mouse location, so that the dragging offset can be
+ * calculated
+ */
+ sc->xclick = coords->x;
+ sc->yclick = coords->y;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+
+ sc->tool_state = SC_STATE_RENDER_MOTION;
+
+ /* In order to receive motion events from the current click, we must
+ * activate the tool control
+ */
+ gimp_tool_control_activate (tool->control);
+ }
+}
+
+void
+gimp_seamless_clone_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ /* There is nothing to do, unless we were actually moving a paste */
+ if (sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (sc));
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ sc->xoff = sc->xoff_p;
+ sc->yoff = sc->yoff_p;
+ }
+ else
+ {
+ sc->xoff = sc->xoff_p + (gint) (coords->x - sc->xclick);
+ sc->yoff = sc->yoff_p + (gint) (coords->y - sc->yclick);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+
+ sc->tool_state = SC_STATE_RENDER_WAIT;
+ }
+}
+
+static void
+gimp_seamless_clone_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ if (sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (sc));
+
+ sc->xoff = sc->xoff_p + (gint) (coords->x - sc->xclick);
+ sc->yoff = sc->yoff_p + (gint) (coords->y - sc->yclick);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+ }
+}
+
+static gboolean
+gimp_seamless_clone_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sct = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ if (sct->tool_state == SC_STATE_RENDER_MOTION ||
+ sct->tool_state == SC_STATE_RENDER_WAIT)
+ {
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_tool_control_set_preserve (tool->control, TRUE);
+
+ /* TODO: there may be issues with committing the image map
+ * result after some changes were made and the preview
+ * was scrolled. We can fix these by either invalidating
+ * the area which is a union of the previous paste
+ * rectangle each time (in the update function) or by
+ * invalidating and re-rendering all now (expensive and
+ * perhaps useless */
+ gimp_drawable_filter_commit (sct->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&sct->filter);
+
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+
+ gimp_image_flush (gimp_display_get_image (display));
+
+ gimp_seamless_clone_tool_control (tool, GIMP_TOOL_ACTION_HALT,
+ display);
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_seamless_clone_tool_control (tool, GIMP_TOOL_ACTION_HALT,
+ display);
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_seamless_clone_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ /* TODO: Modify data here */
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+/* Mouse cursor policy:
+ * - Always use the move cursor
+ * - While dragging the paste, use a move modified
+ * - Else, While hovering above it, display no modifier
+ * - Else, display a "bad" modifier
+ */
+static void
+gimp_seamless_clone_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_BAD;
+
+ /* Only update if the tool is actually active on some display */
+ if (tool->display)
+ {
+ if (sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+ else if (sc->tool_state == SC_STATE_RENDER_WAIT &&
+ gimp_seamless_clone_tool_is_in_paste_c (sc, coords))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_seamless_clone_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! tool->display)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (! strcmp (pspec->name, "max-refine-scale"))
+ {
+ if (gimp_seamless_clone_tool_render_node_update (sc))
+ {
+ gimp_seamless_clone_tool_filter_update (sc);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_seamless_clone_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpSeamlessCloneTool *sc = GIMP_SEAMLESS_CLONE_TOOL (draw_tool);
+
+ if (sc->tool_state == SC_STATE_RENDER_WAIT ||
+ sc->tool_state == SC_STATE_RENDER_MOTION)
+ {
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ sc->xoff, sc->yoff, sc->width, sc->height);
+ }
+}
+
+/**
+ * gimp_seamless_clone_tool_create_render_node:
+ * @sc: The GimpSeamlessCloneTool to initialize
+ *
+ * This function creates a Gegl node graph of the composition which is
+ * needed to render the drawable. The graph should have an "input" pad
+ * which will receive the drawable on which the preview is applied, and
+ * it should also have an "output" pad to which the final result will be
+ * rendered
+ */
+static void
+gimp_seamless_clone_tool_create_render_node (GimpSeamlessCloneTool *sc)
+{
+ /* Here is a textual description of the graph we are going to create:
+ *
+ * <input> <- drawable
+ * +--+--------------------------+
+ * | |output |
+ * | | |
+ * | | <buffer-source> <- paste |
+ * | | |output |
+ * | | | |
+ * | |input |aux |
+ * |<seamless-paste-render> |
+ * | |output |
+ * | | |
+ * | |input |
+ * +----+------------------------+
+ * <output>
+ */
+ GimpSeamlessCloneOptions *options = GIMP_SEAMLESS_CLONE_TOOL_GET_OPTIONS (sc);
+ GeglNode *node;
+ GeglNode *op, *paste, *overlay;
+ GeglNode *input, *output;
+
+ node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (node, "input");
+ output = gegl_node_get_output_proxy (node, "output");
+
+ paste = gegl_node_new_child (node,
+ "operation", "gegl:buffer-source",
+ "buffer", sc->paste,
+ NULL);
+
+ op = gegl_node_new_child (node,
+ "operation", "gegl:seamless-clone",
+ "max-refine-scale", options->max_refine_scale,
+ NULL);
+
+ overlay = gegl_node_new_child (node,
+ "operation", "svg:dst-over",
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ op, "input");
+
+ gegl_node_connect_to (paste, "output",
+ op, "aux");
+
+ gegl_node_connect_to (op, "output",
+ overlay, "input");
+
+ gegl_node_connect_to (input, "output",
+ overlay, "aux");
+
+ gegl_node_connect_to (overlay, "output",
+ output, "input");
+
+ sc->render_node = node;
+ sc->sc_node = op;
+}
+
+/* gimp_seamless_clone_tool_render_node_update:
+ * sc: the Seamless Clone tool whose render has to be updated.
+ *
+ * Returns: TRUE if any property changed.
+ */
+static gboolean
+gimp_seamless_clone_tool_render_node_update (GimpSeamlessCloneTool *sc)
+{
+ static gint rendered__max_refine_scale = -1;
+ static gint rendered_xoff = G_MAXINT;
+ static gint rendered_yoff = G_MAXINT;
+
+ GimpSeamlessCloneOptions *options = GIMP_SEAMLESS_CLONE_TOOL_GET_OPTIONS (sc);
+ GimpDrawable *bg = GIMP_TOOL (sc)->drawable;
+ gint off_x, off_y;
+
+ /* All properties stay the same. No need to update. */
+ if (rendered__max_refine_scale == options->max_refine_scale &&
+ rendered_xoff == sc->xoff &&
+ rendered_yoff == sc->yoff)
+ return FALSE;
+
+ gimp_item_get_offset (GIMP_ITEM (bg), &off_x, &off_y);
+
+ gegl_node_set (sc->sc_node,
+ "xoff", (gint) sc->xoff - off_x,
+ "yoff", (gint) sc->yoff - off_y,
+ "max-refine-scale", (gint) options->max_refine_scale,
+ NULL);
+
+ rendered__max_refine_scale = options->max_refine_scale;
+ rendered_xoff = sc->xoff;
+ rendered_yoff = sc->yoff;
+
+ return TRUE;
+}
+
+static void
+gimp_seamless_clone_tool_create_filter (GimpSeamlessCloneTool *sc,
+ GimpDrawable *drawable)
+{
+ if (! sc->render_node)
+ gimp_seamless_clone_tool_create_render_node (sc);
+
+ sc->filter = gimp_drawable_filter_new (drawable,
+ _("Seamless Clone"),
+ sc->render_node,
+ GIMP_ICON_TOOL_SEAMLESS_CLONE);
+
+ gimp_drawable_filter_set_region (sc->filter, GIMP_FILTER_REGION_DRAWABLE);
+
+ g_signal_connect (sc->filter, "flush",
+ G_CALLBACK (gimp_seamless_clone_tool_filter_flush),
+ sc);
+}
+
+static void
+gimp_seamless_clone_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_seamless_clone_tool_filter_update (GimpSeamlessCloneTool *sc)
+{
+ GimpTool *tool = GIMP_TOOL (sc);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpItem *item = GIMP_ITEM (tool->drawable);
+ gint x, y;
+ gint w, h;
+ gint off_x, off_y;
+ GeglRectangle visible;
+ GeglOperation *op = NULL;
+
+ GimpProgress *progress;
+ GeglNode *output;
+ GeglProcessor *processor;
+ gdouble value;
+
+ progress = gimp_progress_start (GIMP_PROGRESS (sc), FALSE,
+ _("Cloning the foreground object"));
+
+ /* Find out at which x,y is the top left corner of the currently
+ * displayed part */
+ gimp_display_shell_untransform_viewport (shell, ! shell->show_all,
+ &x, &y, &w, &h);
+
+ /* Find out where is our drawable positioned */
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ /* Create a rectangle from the intersection of the currently displayed
+ * part with the drawable */
+ gimp_rectangle_intersect (x, y, w, h,
+ off_x,
+ off_y,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &visible.x,
+ &visible.y,
+ &visible.width,
+ &visible.height);
+
+ /* Since the filter_apply function receives a rectangle describing
+ * where it should update the preview, and since that rectangle should
+ * be relative to the drawable's location, we now offset back by the
+ * drawable's offsetts. */
+ visible.x -= off_x;
+ visible.y -= off_y;
+
+ g_object_get (sc->sc_node, "gegl-operation", &op, NULL);
+ /* If any cache of the visible area was present, clear it!
+ * We need to clear the cache in the sc_node, since that is
+ * where the previous paste was located
+ */
+ gegl_operation_invalidate (op, &visible, TRUE);
+ g_object_unref (op);
+
+ /* Now update the image map and show this area */
+ gimp_drawable_filter_apply (sc->filter, NULL);
+
+ /* Show update progress. */
+ output = gegl_node_get_output_proxy (sc->render_node, "output");
+ processor = gegl_node_new_processor (output, NULL);
+
+ while (gegl_processor_work (processor, &value))
+ {
+ if (progress)
+ gimp_progress_set_value (progress, value);
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ g_object_unref (processor);
+}
diff --git a/app/tools/gimpseamlessclonetool.h b/app/tools/gimpseamlessclonetool.h
new file mode 100644
index 0000000..d2c85a3
--- /dev/null
+++ b/app/tools/gimpseamlessclonetool.h
@@ -0,0 +1,86 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpseamlessclonetool.h
+ * Copyright (C) 2011 Barak Itkin <lightningismyname@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_SEAMLESS_CLONE_TOOL_H__
+#define __GIMP_SEAMLESS_CLONE_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_SEAMLESS_CLONE_TOOL (gimp_seamless_clone_tool_get_type ())
+#define GIMP_SEAMLESS_CLONE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SEAMLESS_CLONE_TOOL, GimpSeamlessCloneTool))
+#define GIMP_SEAMLESS_CLONE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SEAMLESS_CLONE_TOOL, GimpSeamlessCloneToolClass))
+#define GIMP_IS_SEAMLESS_CLONE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SEAMLESS_CLONE_TOOL))
+#define GIMP_IS_SEAMLESS_CLONE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SEAMLESS_CLONE_TOOL))
+#define GIMP_SEAMLESS_CLONE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SEAMLESS_CLONE_TOOL, GimpSeamlessCloneToolClass))
+
+#define GIMP_SEAMLESS_CLONE_TOOL_GET_OPTIONS(t) (GIMP_SEAMLESS_CLONE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpSeamlessCloneTool GimpSeamlessCloneTool;
+typedef struct _GimpSeamlessCloneToolClass GimpSeamlessCloneToolClass;
+
+struct _GimpSeamlessCloneTool
+{
+ GimpDrawTool parent_instance;
+
+ GeglBuffer *paste; /* A buffer containing the original
+ * paste that will be used in the
+ * rendering process */
+
+ GeglNode *render_node; /* The parent of the Gegl graph that
+ * renders the seamless cloning */
+
+ GeglNode *sc_node; /* A Gegl node to do the seamless
+ * cloning live with translation of
+ * the paste */
+
+ gint tool_state; /* The current state in the tool's
+ * state machine */
+
+ GimpDrawableFilter *filter; /* The filter object which renders
+ * the live preview, and commits it
+ * when at the end */
+
+ gint width, height; /* The width and height of the paste.
+ * Needed for mouse hit detection */
+
+ gint xoff, yoff; /* The current offset of the paste */
+ gint xoff_p, yoff_p; /* The previous offset of the paste */
+
+ gdouble xclick, yclick; /* The image location of the last
+ * mouse click. To be used when the
+ * mouse is in motion, to recalculate
+ * the xoff and yoff values */
+};
+
+struct _GimpSeamlessCloneToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_seamless_clone_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_seamless_clone_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SEAMLESS_CLONE_TOOL_H__ */
diff --git a/app/tools/gimpselectionoptions.c b/app/tools/gimpselectionoptions.c
new file mode 100644
index 0000000..4599386
--- /dev/null
+++ b/app/tools/gimpselectionoptions.c
@@ -0,0 +1,292 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpselectionoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_OPERATION,
+ PROP_ANTIALIAS,
+ PROP_FEATHER,
+ PROP_FEATHER_RADIUS
+};
+
+
+static void gimp_selection_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_selection_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpSelectionOptions, gimp_selection_options,
+ GIMP_TYPE_TOOL_OPTIONS)
+
+#define parent_class gimp_selection_options_parent_class
+
+
+static void
+gimp_selection_options_class_init (GimpSelectionOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_selection_options_set_property;
+ object_class->get_property = gimp_selection_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_OPERATION,
+ "operation",
+ NULL, NULL,
+ GIMP_TYPE_CHANNEL_OPS,
+ GIMP_CHANNEL_OP_REPLACE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ANTIALIAS,
+ "antialias",
+ _("Antialiasing"),
+ _("Smooth edges"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FEATHER,
+ "feather",
+ _("Feather edges"),
+ _("Enable feathering of selection edges"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FEATHER_RADIUS,
+ "feather-radius",
+ _("Radius"),
+ _("Radius of feathering"),
+ 0.0, 100.0, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_selection_options_init (GimpSelectionOptions *options)
+{
+}
+
+static void
+gimp_selection_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSelectionOptions *options = GIMP_SELECTION_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_OPERATION:
+ options->operation = g_value_get_enum (value);
+ break;
+
+ case PROP_ANTIALIAS:
+ options->antialias = g_value_get_boolean (value);
+ break;
+
+ case PROP_FEATHER:
+ options->feather = g_value_get_boolean (value);
+ break;
+
+ case PROP_FEATHER_RADIUS:
+ options->feather_radius = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_selection_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSelectionOptions *options = GIMP_SELECTION_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_OPERATION:
+ g_value_set_enum (value, options->operation);
+ break;
+
+ case PROP_ANTIALIAS:
+ g_value_set_boolean (value, options->antialias);
+ break;
+
+ case PROP_FEATHER:
+ g_value_set_boolean (value, options->feather);
+ break;
+
+ case PROP_FEATHER_RADIUS:
+ g_value_set_double (value, options->feather_radius);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static const gchar *
+gimp_selection_options_get_modifiers (GimpChannelOps operation)
+{
+ GdkModifierType extend_mask;
+ GdkModifierType modify_mask;
+ GdkModifierType modifiers = 0;
+
+ extend_mask = gimp_get_extend_selection_mask ();
+ modify_mask = gimp_get_modify_selection_mask ();
+
+ switch (operation)
+ {
+ case GIMP_CHANNEL_OP_ADD:
+ modifiers = extend_mask;
+ break;
+
+ case GIMP_CHANNEL_OP_SUBTRACT:
+ modifiers = modify_mask;
+ break;
+
+ case GIMP_CHANNEL_OP_REPLACE:
+ modifiers = 0;
+ break;
+
+ case GIMP_CHANNEL_OP_INTERSECT:
+ modifiers = extend_mask | modify_mask;
+ break;
+ }
+
+ return gimp_get_mod_string (modifiers);
+}
+
+GtkWidget *
+gimp_selection_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpSelectionOptions *options = GIMP_SELECTION_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *button;
+
+ /* the selection operation radio buttons */
+ {
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *box;
+ GList *children;
+ GList *list;
+ gint i;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->mode_box = hbox;
+
+ label = gtk_label_new (_("Mode:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ box = gimp_prop_enum_icon_box_new (config, "operation",
+ "gimp-selection", 0, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), box, FALSE, FALSE, 0);
+ gtk_widget_show (box);
+
+ children = gtk_container_get_children (GTK_CONTAINER (box));
+
+ /* add modifier keys to the tooltips */
+ for (list = children, i = 0; list; list = list->next, i++)
+ {
+ GtkWidget *button = list->data;
+ const gchar *modifier = gimp_selection_options_get_modifiers (i);
+ gchar *tooltip;
+
+ if (! modifier)
+ continue;
+
+ tooltip = gtk_widget_get_tooltip_text (button);
+
+ if (tooltip)
+ {
+ gchar *tip = g_strdup_printf ("%s <b>%s</b>", tooltip, modifier);
+
+ gimp_help_set_help_data_with_markup (button, tip, NULL);
+
+ g_free (tip);
+ g_free (tooltip);
+ }
+ else
+ {
+ gimp_help_set_help_data (button, modifier, NULL);
+ }
+ }
+
+ /* move GIMP_CHANNEL_OP_REPLACE to the front */
+ gtk_box_reorder_child (GTK_BOX (box),
+ GTK_WIDGET (children->next->next->data), 0);
+
+ g_list_free (children);
+ }
+
+ /* the antialias toggle button */
+ button = gimp_prop_check_button_new (config, "antialias", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ options->antialias_toggle = button;
+
+ /* the feather frame */
+ {
+ GtkWidget *frame;
+ GtkWidget *scale;
+
+ /* the feather radius scale */
+ scale = gimp_prop_spin_scale_new (config, "feather-radius", NULL,
+ 1.0, 10.0, 1);
+
+ frame = gimp_prop_expanding_frame_new (config, "feather", NULL,
+ scale, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+ }
+
+ return vbox;
+}
diff --git a/app/tools/gimpselectionoptions.h b/app/tools/gimpselectionoptions.h
new file mode 100644
index 0000000..9a2107d
--- /dev/null
+++ b/app/tools/gimpselectionoptions.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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_SELECTION_OPTIONS_H__
+#define __GIMP_SELECTION_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_SELECTION_OPTIONS (gimp_selection_options_get_type ())
+#define GIMP_SELECTION_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SELECTION_OPTIONS, GimpSelectionOptions))
+#define GIMP_SELECTION_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SELECTION_OPTIONS, GimpSelectionOptionsClass))
+#define GIMP_IS_SELECTION_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SELECTION_OPTIONS))
+#define GIMP_IS_SELECTION_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SELECTION_OPTIONS))
+#define GIMP_SELECTION_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SELECTION_OPTIONS, GimpSelectionOptionsClass))
+
+
+typedef struct _GimpSelectionOptions GimpSelectionOptions;
+typedef struct _GimpToolOptionsClass GimpSelectionOptionsClass;
+
+struct _GimpSelectionOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpChannelOps operation;
+ gboolean antialias;
+ gboolean feather;
+ gdouble feather_radius;
+
+ /* options gui */
+ GtkWidget *mode_box;
+ GtkWidget *antialias_toggle;
+};
+
+
+GType gimp_selection_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_selection_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_SELECTION_OPTIONS_H__ */
diff --git a/app/tools/gimpselectiontool.c b/app/tools/gimpselectiontool.c
new file mode 100644
index 0000000..c1cec9c
--- /dev/null
+++ b/app/tools/gimpselectiontool.c
@@ -0,0 +1,830 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimppickable.h"
+#include "core/gimpundostack.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell-appearance.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpeditselectiontool.h"
+#include "gimpselectiontool.h"
+#include "gimpselectionoptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_selection_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_selection_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_selection_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_selection_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static gboolean gimp_selection_tool_real_have_selection (GimpSelectionTool *sel_tool,
+ GimpDisplay *display);
+
+static void gimp_selection_tool_commit (GimpSelectionTool *sel_tool);
+static void gimp_selection_tool_halt (GimpSelectionTool *sel_tool,
+ GimpDisplay *display);
+
+static gboolean gimp_selection_tool_check (GimpSelectionTool *sel_tool,
+ GimpDisplay *display,
+ GError **error);
+
+static gboolean gimp_selection_tool_have_selection (GimpSelectionTool *sel_tool,
+ GimpDisplay *display);
+
+static void gimp_selection_tool_set_undo_ptr (GimpUndo **undo_ptr,
+ GimpUndo *undo);
+
+
+G_DEFINE_TYPE (GimpSelectionTool, gimp_selection_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_selection_tool_parent_class
+
+
+static void
+gimp_selection_tool_class_init (GimpSelectionToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_selection_tool_control;
+ tool_class->modifier_key = gimp_selection_tool_modifier_key;
+ tool_class->key_press = gimp_edit_selection_tool_key_press;
+ tool_class->oper_update = gimp_selection_tool_oper_update;
+ tool_class->cursor_update = gimp_selection_tool_cursor_update;
+
+ klass->have_selection = gimp_selection_tool_real_have_selection;
+}
+
+static void
+gimp_selection_tool_init (GimpSelectionTool *selection_tool)
+{
+ selection_tool->function = SELECTION_SELECT;
+ selection_tool->saved_operation = GIMP_CHANNEL_OP_REPLACE;
+
+ selection_tool->saved_show_selection = FALSE;
+ selection_tool->undo = NULL;
+ selection_tool->redo = NULL;
+ selection_tool->idle_id = 0;
+
+ selection_tool->allow_move = TRUE;
+}
+
+static void
+gimp_selection_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_selection_tool_halt (selection_tool, display);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_selection_tool_commit (selection_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_selection_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GdkModifierType extend_mask;
+ GdkModifierType modify_mask;
+
+ extend_mask = gimp_get_extend_selection_mask ();
+ modify_mask = gimp_get_modify_selection_mask ();
+
+ if (key == extend_mask ||
+ key == modify_mask ||
+ key == GDK_MOD1_MASK)
+ {
+ GimpChannelOps button_op = options->operation;
+
+ state &= extend_mask |
+ modify_mask |
+ GDK_MOD1_MASK;
+
+ if (press)
+ {
+ if (key == state ||
+ /* GimpPolygonSelectTool may mask-out part of the state, which
+ * can cause the wrong mode to be restored on release if we don't
+ * init saved_operation here.
+ *
+ * see issue #4992.
+ */
+ ! state)
+ {
+ /* first modifier pressed */
+
+ selection_tool->saved_operation = options->operation;
+ }
+ }
+ else
+ {
+ if (! state)
+ {
+ /* last modifier released */
+
+ button_op = selection_tool->saved_operation;
+ }
+ }
+
+ if (state & GDK_MOD1_MASK)
+ {
+ /* if alt is down, pretend that neither
+ * shift nor control are down
+ */
+ button_op = selection_tool->saved_operation;
+ }
+ else if (state & (extend_mask |
+ modify_mask))
+ {
+ /* else get the operation from the modifier state, but only
+ * if there is actually a modifier pressed, so we don't
+ * override the "last modifier released" assignment above
+ */
+ button_op = gimp_modifiers_to_channel_op (state);
+ }
+
+ if (button_op != options->operation)
+ {
+ g_object_set (options, "operation", button_op, NULL);
+ }
+ }
+}
+
+static void
+gimp_selection_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GimpLayer *layer;
+ GimpLayer *floating_sel;
+ GdkModifierType extend_mask;
+ GdkModifierType modify_mask;
+ gboolean have_selection;
+ gboolean move_layer = FALSE;
+ gboolean move_floating_sel = FALSE;
+
+ image = gimp_display_get_image (display);
+ drawable = gimp_image_get_active_drawable (image);
+ layer = gimp_image_pick_layer (image, coords->x, coords->y, NULL);
+ floating_sel = gimp_image_get_floating_selection (image);
+
+ extend_mask = gimp_get_extend_selection_mask ();
+ modify_mask = gimp_get_modify_selection_mask ();
+
+ have_selection = gimp_selection_tool_have_selection (selection_tool, display);
+
+ if (drawable)
+ {
+ if (floating_sel)
+ {
+ if (layer == floating_sel)
+ move_floating_sel = TRUE;
+ }
+ else if (have_selection &&
+ gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ NULL, NULL, NULL, NULL))
+ {
+ move_layer = TRUE;
+ }
+ }
+
+ selection_tool->function = SELECTION_SELECT;
+
+ if (selection_tool->allow_move &&
+ (state & GDK_MOD1_MASK) && (state & modify_mask) && move_layer)
+ {
+ /* move the selection */
+ selection_tool->function = SELECTION_MOVE;
+ }
+ else if (selection_tool->allow_move &&
+ (state & GDK_MOD1_MASK) && (state & extend_mask) && move_layer)
+ {
+ /* move a copy of the selection */
+ selection_tool->function = SELECTION_MOVE_COPY;
+ }
+ else if (selection_tool->allow_move &&
+ (state & GDK_MOD1_MASK) && have_selection)
+ {
+ /* move the selection mask */
+ selection_tool->function = SELECTION_MOVE_MASK;
+ }
+ else if (selection_tool->allow_move &&
+ ! (state & (extend_mask | modify_mask)) &&
+ move_floating_sel)
+ {
+ /* move the selection */
+ selection_tool->function = SELECTION_MOVE;
+ }
+ else if ((state & modify_mask) || (state & extend_mask))
+ {
+ /* select */
+ selection_tool->function = SELECTION_SELECT;
+ }
+ else if (floating_sel)
+ {
+ /* anchor the selection */
+ selection_tool->function = SELECTION_ANCHOR;
+ }
+
+ gimp_tool_pop_status (tool, display);
+
+ if (proximity)
+ {
+ const gchar *status = NULL;
+ gboolean free_status = FALSE;
+ GdkModifierType modifiers = (extend_mask | modify_mask);
+
+ if (have_selection)
+ modifiers |= GDK_MOD1_MASK;
+
+ switch (selection_tool->function)
+ {
+ case SELECTION_SELECT:
+ switch (options->operation)
+ {
+ case GIMP_CHANNEL_OP_REPLACE:
+ if (have_selection)
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to replace the "
+ "current selection"),
+ modifiers & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ }
+ else
+ {
+ status = _("Click-Drag to create a new selection");
+ }
+ break;
+
+ case GIMP_CHANNEL_OP_ADD:
+ status = gimp_suggest_modifiers (_("Click-Drag to add to the "
+ "current selection"),
+ modifiers
+ & ~(state | extend_mask),
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case GIMP_CHANNEL_OP_SUBTRACT:
+ status = gimp_suggest_modifiers (_("Click-Drag to subtract from the "
+ "current selection"),
+ modifiers
+ & ~(state | modify_mask),
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case GIMP_CHANNEL_OP_INTERSECT:
+ status = gimp_suggest_modifiers (_("Click-Drag to intersect with "
+ "the current selection"),
+ modifiers & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+ }
+ break;
+
+ case SELECTION_MOVE_MASK:
+ status = gimp_suggest_modifiers (_("Click-Drag to move the "
+ "selection mask"),
+ modifiers & ~state,
+ NULL, NULL, NULL);
+ free_status = TRUE;
+ break;
+
+ case SELECTION_MOVE:
+ status = _("Click-Drag to move the selected pixels");
+ break;
+
+ case SELECTION_MOVE_COPY:
+ status = _("Click-Drag to move a copy of the selected pixels");
+ break;
+
+ case SELECTION_ANCHOR:
+ status = _("Click to anchor the floating selection");
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ if (status)
+ gimp_tool_push_status (tool, display, "%s", status);
+
+ if (free_status)
+ g_free ((gchar *) status);
+ }
+}
+
+static void
+gimp_selection_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
+ GimpSelectionOptions *options;
+ GimpToolCursorType tool_cursor;
+ GimpCursorModifier modifier;
+
+ options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
+
+ tool_cursor = gimp_tool_control_get_tool_cursor (tool->control);
+ modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ switch (selection_tool->function)
+ {
+ case SELECTION_SELECT:
+ switch (options->operation)
+ {
+ case GIMP_CHANNEL_OP_REPLACE:
+ break;
+ case GIMP_CHANNEL_OP_ADD:
+ modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ break;
+ case GIMP_CHANNEL_OP_SUBTRACT:
+ modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ break;
+ case GIMP_CHANNEL_OP_INTERSECT:
+ modifier = GIMP_CURSOR_MODIFIER_INTERSECT;
+ break;
+ }
+ break;
+
+ case SELECTION_MOVE_MASK:
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+
+ case SELECTION_MOVE:
+ case SELECTION_MOVE_COPY:
+ tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ break;
+
+ case SELECTION_ANCHOR:
+ modifier = GIMP_CURSOR_MODIFIER_ANCHOR;
+ break;
+ }
+
+ /* our subclass might have set a BAD modifier, in which case we leave it
+ * there, since it's more important than what we have to say.
+ */
+ if (gimp_tool_control_get_cursor_modifier (tool->control) ==
+ GIMP_CURSOR_MODIFIER_BAD ||
+ ! gimp_selection_tool_check (selection_tool, display, NULL))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ tool_cursor,
+ modifier);
+}
+
+static gboolean
+gimp_selection_tool_real_have_selection (GimpSelectionTool *sel_tool,
+ GimpDisplay *display)
+{
+ GimpImage *image = gimp_display_get_image (display);
+ GimpChannel *selection = gimp_image_get_mask (image);
+
+ return ! gimp_channel_is_empty (selection);
+}
+
+static void
+gimp_selection_tool_commit (GimpSelectionTool *sel_tool)
+{
+ /* make sure gimp_selection_tool_halt() doesn't undo the change, if any */
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, NULL);
+}
+
+static void
+gimp_selection_tool_halt (GimpSelectionTool *sel_tool,
+ GimpDisplay *display)
+{
+ g_warn_if_fail (sel_tool->change_count == 0);
+
+ if (display)
+ {
+ GimpTool *tool = GIMP_TOOL (sel_tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpUndoStack *undo_stack = gimp_image_get_undo_stack (image);
+ GimpUndo *undo = gimp_undo_stack_peek (undo_stack);
+
+ /* if we have an existing selection in the current display, then
+ * we have already "executed", and need to undo at this point,
+ * unless the user has done something in the meantime
+ */
+ if (undo && sel_tool->undo == undo)
+ {
+ /* prevent this change from halting the tool */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_image_undo (image);
+ gimp_image_flush (image);
+
+ gimp_tool_control_pop_preserve (tool->control);
+ }
+
+ /* reset the automatic undo/redo mechanism */
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, NULL);
+ gimp_selection_tool_set_undo_ptr (&sel_tool->redo, NULL);
+ }
+}
+
+static gboolean
+gimp_selection_tool_check (GimpSelectionTool *sel_tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (sel_tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ switch (sel_tool->function)
+ {
+ case SELECTION_SELECT:
+ switch (options->operation)
+ {
+ case GIMP_CHANNEL_OP_ADD:
+ case GIMP_CHANNEL_OP_REPLACE:
+ break;
+
+ case GIMP_CHANNEL_OP_SUBTRACT:
+ if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ NULL, NULL, NULL, NULL))
+ {
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot subtract from an empty selection."));
+
+ return FALSE;
+ }
+ break;
+
+ case GIMP_CHANNEL_OP_INTERSECT:
+ if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ NULL, NULL, NULL, NULL))
+ {
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot intersect with an empty selection."));
+
+ return FALSE;
+ }
+ break;
+ }
+ break;
+
+ case SELECTION_MOVE:
+ case SELECTION_MOVE_COPY:
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot modify the pixels of layer groups."));
+
+ return FALSE;
+ }
+ else if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer's pixels are locked."));
+
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+
+ return FALSE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_selection_tool_have_selection (GimpSelectionTool *sel_tool,
+ GimpDisplay *display)
+{
+ return GIMP_SELECTION_TOOL_GET_CLASS (sel_tool)->have_selection (sel_tool,
+ display);
+}
+
+static void
+gimp_selection_tool_set_undo_ptr (GimpUndo **undo_ptr,
+ GimpUndo *undo)
+{
+ if (*undo_ptr)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (*undo_ptr),
+ (gpointer *) undo_ptr);
+ }
+
+ *undo_ptr = undo;
+
+ if (*undo_ptr)
+ {
+ g_object_add_weak_pointer (G_OBJECT (*undo_ptr),
+ (gpointer *) undo_ptr);
+ }
+}
+
+
+/* public functions */
+
+gboolean
+gimp_selection_tool_start_edit (GimpSelectionTool *sel_tool,
+ GimpDisplay *display,
+ const GimpCoords *coords)
+{
+ GimpTool *tool;
+ GimpSelectionOptions *options;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_SELECTION_TOOL (sel_tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+
+ tool = GIMP_TOOL (sel_tool);
+ options = GIMP_SELECTION_TOOL_GET_OPTIONS (sel_tool);
+
+ g_return_val_if_fail (gimp_tool_control_is_active (tool->control) == FALSE,
+ FALSE);
+
+ if (! gimp_selection_tool_check (sel_tool, display, &error))
+ {
+ gimp_tool_message_literal (tool, display, error->message);
+
+ gimp_widget_blink (options->mode_box);
+
+ g_clear_error (&error);
+
+ return TRUE;
+ }
+
+ switch (sel_tool->function)
+ {
+ case SELECTION_MOVE_MASK:
+ gimp_edit_selection_tool_start (tool, display, coords,
+ GIMP_TRANSLATE_MODE_MASK, FALSE);
+ return TRUE;
+
+ case SELECTION_MOVE:
+ case SELECTION_MOVE_COPY:
+ {
+ GimpTranslateMode edit_mode;
+
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+
+ if (sel_tool->function == SELECTION_MOVE)
+ edit_mode = GIMP_TRANSLATE_MODE_MASK_TO_LAYER;
+ else
+ edit_mode = GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER;
+
+ gimp_edit_selection_tool_start (tool, display, coords,
+ edit_mode, FALSE);
+
+ return TRUE;
+ }
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_selection_tool_idle (GimpSelectionTool *sel_tool)
+{
+ GimpTool *tool = GIMP_TOOL (sel_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ gimp_display_shell_set_show_selection (shell, FALSE);
+
+ sel_tool->idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+gimp_selection_tool_start_change (GimpSelectionTool *sel_tool,
+ gboolean create,
+ GimpChannelOps operation)
+{
+ GimpTool *tool;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpUndoStack *undo_stack;
+
+ g_return_if_fail (GIMP_IS_SELECTION_TOOL (sel_tool));
+
+ tool = GIMP_TOOL (sel_tool);
+
+ g_return_if_fail (tool->display != NULL);
+
+ if (sel_tool->change_count++ > 0)
+ return;
+
+ shell = gimp_display_get_shell (tool->display);
+ image = gimp_display_get_image (tool->display);
+ undo_stack = gimp_image_get_undo_stack (image);
+
+ sel_tool->saved_show_selection =
+ gimp_display_shell_get_show_selection (shell);
+
+ if (create)
+ {
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, NULL);
+ }
+ else
+ {
+ GimpUndoStack *redo_stack = gimp_image_get_redo_stack (image);
+ GimpUndo *undo;
+
+ undo = gimp_undo_stack_peek (undo_stack);
+
+ if (undo && undo == sel_tool->undo)
+ {
+ /* prevent this change from halting the tool */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_image_undo (image);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, NULL);
+
+ /* we will need to redo if the user cancels or executes */
+ gimp_selection_tool_set_undo_ptr (
+ &sel_tool->redo,
+ gimp_undo_stack_peek (redo_stack));
+ }
+
+ /* if the operation is "Replace", turn off the marching ants,
+ * because they are confusing ...
+ */
+ if (operation == GIMP_CHANNEL_OP_REPLACE)
+ {
+ /* ... however, do this in an idle function, to avoid unnecessarily
+ * restarting the selection if we don't visit the main loop between
+ * the start_change() and end_change() calls.
+ */
+ sel_tool->idle_id = g_idle_add_full (
+ G_PRIORITY_HIGH_IDLE,
+ (GSourceFunc) gimp_selection_tool_idle,
+ sel_tool, NULL);
+ }
+ }
+
+ gimp_selection_tool_set_undo_ptr (
+ &sel_tool->undo,
+ gimp_undo_stack_peek (undo_stack));
+}
+
+void
+gimp_selection_tool_end_change (GimpSelectionTool *sel_tool,
+ gboolean cancel)
+{
+ GimpTool *tool;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ GimpUndoStack *undo_stack;
+
+ g_return_if_fail (GIMP_IS_SELECTION_TOOL (sel_tool));
+ g_return_if_fail (sel_tool->change_count > 0);
+
+ tool = GIMP_TOOL (sel_tool);
+
+ g_return_if_fail (tool->display != NULL);
+
+ if (--sel_tool->change_count > 0)
+ return;
+
+ shell = gimp_display_get_shell (tool->display);
+ image = gimp_display_get_image (tool->display);
+ undo_stack = gimp_image_get_undo_stack (image);
+
+ if (cancel)
+ {
+ GimpUndoStack *redo_stack = gimp_image_get_redo_stack (image);
+ GimpUndo *redo = gimp_undo_stack_peek (redo_stack);
+
+ if (redo && redo == sel_tool->redo)
+ {
+ /* prevent this from halting the tool */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_image_redo (image);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_selection_tool_set_undo_ptr (
+ &sel_tool->undo,
+ gimp_undo_stack_peek (undo_stack));
+ }
+ else
+ {
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, NULL);
+ }
+ }
+ else
+ {
+ GimpUndo *undo = gimp_undo_stack_peek (undo_stack);
+
+ /* save the undo that we got when executing, but only if
+ * we actually selected something
+ */
+ if (undo && undo != sel_tool->undo)
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, undo);
+ else
+ gimp_selection_tool_set_undo_ptr (&sel_tool->undo, NULL);
+ }
+
+ gimp_selection_tool_set_undo_ptr (&sel_tool->redo, NULL);
+
+ if (sel_tool->idle_id)
+ {
+ g_source_remove (sel_tool->idle_id);
+ sel_tool->idle_id = 0;
+ }
+ else
+ {
+ gimp_display_shell_set_show_selection (shell,
+ sel_tool->saved_show_selection);
+ }
+
+ gimp_image_flush (image);
+}
diff --git a/app/tools/gimpselectiontool.h b/app/tools/gimpselectiontool.h
new file mode 100644
index 0000000..0776056
--- /dev/null
+++ b/app/tools/gimpselectiontool.h
@@ -0,0 +1,78 @@
+/* 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_SELECTION_TOOL_H__
+#define __GIMP_SELECTION_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_SELECTION_TOOL (gimp_selection_tool_get_type ())
+#define GIMP_SELECTION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SELECTION_TOOL, GimpSelectionTool))
+#define GIMP_SELECTION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SELECTION_TOOL, GimpSelectionToolClass))
+#define GIMP_IS_SELECTION_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SELECTION_TOOL))
+#define GIMP_IS_SELECTION_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SELECTION_TOOL))
+#define GIMP_SELECTION_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SELECTION_TOOL, GimpSelectionToolClass))
+
+#define GIMP_SELECTION_TOOL_GET_OPTIONS(t) (GIMP_SELECTION_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpSelectionTool GimpSelectionTool;
+typedef struct _GimpSelectionToolClass GimpSelectionToolClass;
+
+struct _GimpSelectionTool
+{
+ GimpDrawTool parent_instance;
+
+ SelectFunction function; /* selection function */
+ GimpChannelOps saved_operation; /* saved tool options state */
+
+ gint change_count;
+ gboolean saved_show_selection;
+ GimpUndo *undo;
+ GimpUndo *redo;
+ gint idle_id;
+
+ gboolean allow_move;
+};
+
+struct _GimpSelectionToolClass
+{
+ GimpDrawToolClass parent_class;
+
+ /* virtual functions */
+ gboolean (* have_selection) (GimpSelectionTool *sel_tool,
+ GimpDisplay *display);
+};
+
+
+GType gimp_selection_tool_get_type (void) G_GNUC_CONST;
+
+/* protected function */
+gboolean gimp_selection_tool_start_edit (GimpSelectionTool *sel_tool,
+ GimpDisplay *display,
+ const GimpCoords *coords);
+
+void gimp_selection_tool_start_change (GimpSelectionTool *sel_tool,
+ gboolean create,
+ GimpChannelOps operation);
+void gimp_selection_tool_end_change (GimpSelectionTool *sel_tool,
+ gboolean cancel);
+
+
+#endif /* __GIMP_SELECTION_TOOL_H__ */
diff --git a/app/tools/gimpsheartool.c b/app/tools/gimpsheartool.c
new file mode 100644
index 0000000..9d76adf
--- /dev/null
+++ b/app/tools/gimpsheartool.c
@@ -0,0 +1,331 @@
+/* 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 "tools-types.h"
+
+#include "core/gimp-transform-utils.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpspinscale.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptoolsheargrid.h"
+
+#include "gimpsheartool.h"
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+
+#include "gimp-intl.h"
+
+
+/* index into trans_info array */
+enum
+{
+ ORIENTATION,
+ SHEAR_X,
+ SHEAR_Y
+};
+
+
+#define SB_WIDTH 10
+
+
+/* local function prototypes */
+
+static gboolean gimp_shear_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+static gchar * gimp_shear_tool_get_undo_desc (GimpTransformGridTool *tg_tool);
+static void gimp_shear_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_shear_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_shear_tool_prepare (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_shear_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_shear_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_shear_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void shear_x_mag_changed (GtkAdjustment *adj,
+ GimpTransformGridTool *tg_tool);
+static void shear_y_mag_changed (GtkAdjustment *adj,
+ GimpTransformGridTool *tg_tool);
+
+
+G_DEFINE_TYPE (GimpShearTool, gimp_shear_tool, GIMP_TYPE_TRANSFORM_GRID_TOOL)
+
+#define parent_class gimp_shear_tool_parent_class
+
+
+void
+gimp_shear_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_SHEAR_TOOL,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS,
+ gimp_transform_grid_options_gui,
+ 0,
+ "gimp-shear-tool",
+ _("Shear"),
+ _("Shear Tool: Shear the layer, selection or path"),
+ N_("S_hear"), "<shift>H",
+ NULL, GIMP_HELP_TOOL_SHEAR,
+ GIMP_ICON_TOOL_SHEAR,
+ data);
+}
+
+static void
+gimp_shear_tool_class_init (GimpShearToolClass *klass)
+{
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+
+ tg_class->info_to_matrix = gimp_shear_tool_info_to_matrix;
+ tg_class->get_undo_desc = gimp_shear_tool_get_undo_desc;
+ tg_class->dialog = gimp_shear_tool_dialog;
+ tg_class->dialog_update = gimp_shear_tool_dialog_update;
+ tg_class->prepare = gimp_shear_tool_prepare;
+ tg_class->get_widget = gimp_shear_tool_get_widget;
+ tg_class->update_widget = gimp_shear_tool_update_widget;
+ tg_class->widget_changed = gimp_shear_tool_widget_changed;
+
+ tr_class->progress_text = C_("undo-type", "Shear");
+ tr_class->progress_text = _("Shearing");
+ tg_class->ok_button_label = _("_Shear");
+}
+
+static void
+gimp_shear_tool_init (GimpShearTool *shear_tool)
+{
+ GimpTool *tool = GIMP_TOOL (shear_tool);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_SHEAR);
+}
+
+static gboolean
+gimp_shear_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ gdouble amount;
+
+ if (tg_tool->trans_info[SHEAR_X] == 0.0 &&
+ tg_tool->trans_info[SHEAR_Y] == 0.0)
+ {
+ tg_tool->trans_info[ORIENTATION] = GIMP_ORIENTATION_UNKNOWN;
+ }
+
+ if (tg_tool->trans_info[ORIENTATION] == GIMP_ORIENTATION_HORIZONTAL)
+ amount = tg_tool->trans_info[SHEAR_X];
+ else
+ amount = tg_tool->trans_info[SHEAR_Y];
+
+ gimp_matrix3_identity (transform);
+ gimp_transform_matrix_shear (transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2 - tr_tool->x1,
+ tr_tool->y2 - tr_tool->y1,
+ tg_tool->trans_info[ORIENTATION],
+ amount);
+
+ return TRUE;
+}
+
+static gchar *
+gimp_shear_tool_get_undo_desc (GimpTransformGridTool *tg_tool)
+{
+ gdouble x = tg_tool->trans_info[SHEAR_X];
+ gdouble y = tg_tool->trans_info[SHEAR_Y];
+
+ switch ((gint) tg_tool->trans_info[ORIENTATION])
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ return g_strdup_printf (C_("undo-type", "Shear horizontally by %-3.3g"),
+ x);
+
+ case GIMP_ORIENTATION_VERTICAL:
+ return g_strdup_printf (C_("undo-type", "Shear vertically by %-3.3g"),
+ y);
+
+ default:
+ /* e.g. user entered numbers but no notification callback */
+ return g_strdup_printf (C_("undo-type", "Shear horizontally by %-3.3g, vertically by %-3.3g"),
+ x, y);
+ }
+}
+
+static void
+gimp_shear_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+ GimpShearTool *shear = GIMP_SHEAR_TOOL (tg_tool);
+ GtkWidget *vbox;
+ GtkWidget *scale;
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tg_tool->gui)), vbox,
+ FALSE, FALSE, 0);
+ gtk_widget_show (vbox);
+
+ shear->x_adj = (GtkAdjustment *)
+ gtk_adjustment_new (0, -65536, 65536, 1, 10, 0);
+ scale = gimp_spin_scale_new (shear->x_adj, _("Shear magnitude _X"), 0);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), -1000, 1000);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ g_signal_connect (shear->x_adj, "value-changed",
+ G_CALLBACK (shear_x_mag_changed),
+ tg_tool);
+
+ shear->y_adj = (GtkAdjustment *)
+ gtk_adjustment_new (0, -65536, 65536, 1, 10, 0);
+ scale = gimp_spin_scale_new (shear->y_adj, _("Shear magnitude _Y"), 0);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), -1000, 1000);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ g_signal_connect (shear->y_adj, "value-changed",
+ G_CALLBACK (shear_y_mag_changed),
+ tg_tool);
+}
+
+static void
+gimp_shear_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ GimpShearTool *shear = GIMP_SHEAR_TOOL (tg_tool);
+
+ gtk_adjustment_set_value (shear->x_adj, tg_tool->trans_info[SHEAR_X]);
+ gtk_adjustment_set_value (shear->y_adj, tg_tool->trans_info[SHEAR_Y]);
+}
+
+static void
+gimp_shear_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ tg_tool->trans_info[ORIENTATION] = GIMP_ORIENTATION_UNKNOWN;
+ tg_tool->trans_info[SHEAR_X] = 0.0;
+ tg_tool->trans_info[SHEAR_Y] = 0.0;
+}
+
+static GimpToolWidget *
+gimp_shear_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+
+ widget = gimp_tool_shear_grid_new (shell,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2,
+ tg_tool->trans_info[ORIENTATION],
+ tg_tool->trans_info[SHEAR_X],
+ tg_tool->trans_info[SHEAR_Y]);
+
+ g_object_set (widget,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_SHEAR,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_SHEAR,
+ "frompivot-shear", TRUE,
+ NULL);
+
+ return widget;
+}
+
+static void
+gimp_shear_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ g_object_set (tg_tool->widget,
+ "x1", (gdouble) tr_tool->x1,
+ "y1", (gdouble) tr_tool->y1,
+ "x2", (gdouble) tr_tool->x2,
+ "y2", (gdouble) tr_tool->y2,
+ "orientation", (gint) tg_tool->trans_info[ORIENTATION],
+ "shear-x", tg_tool->trans_info[SHEAR_X],
+ "shear-y", tg_tool->trans_info[SHEAR_Y],
+ NULL);
+}
+
+static void
+gimp_shear_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ GimpOrientationType orientation;
+
+ g_object_get (tg_tool->widget,
+ "orientation", &orientation,
+ "shear-x", &tg_tool->trans_info[SHEAR_X],
+ "shear-y", &tg_tool->trans_info[SHEAR_Y],
+ NULL);
+
+ tg_tool->trans_info[ORIENTATION] = orientation;
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+shear_x_mag_changed (GtkAdjustment *adj,
+ GimpTransformGridTool *tg_tool)
+{
+ gdouble value = gtk_adjustment_get_value (adj);
+
+ if (value != tg_tool->trans_info[SHEAR_X])
+ {
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ tg_tool->trans_info[ORIENTATION] = GIMP_ORIENTATION_HORIZONTAL;
+
+ tg_tool->trans_info[SHEAR_X] = value;
+ tg_tool->trans_info[SHEAR_Y] = 0.0; /* can only shear in one axis */
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+}
+
+static void
+shear_y_mag_changed (GtkAdjustment *adj,
+ GimpTransformGridTool *tg_tool)
+{
+ gdouble value = gtk_adjustment_get_value (adj);
+
+ if (value != tg_tool->trans_info[SHEAR_Y])
+ {
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ tg_tool->trans_info[ORIENTATION] = GIMP_ORIENTATION_VERTICAL;
+
+ tg_tool->trans_info[SHEAR_Y] = value;
+ tg_tool->trans_info[SHEAR_X] = 0.0; /* can only shear in one axis */
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+}
diff --git a/app/tools/gimpsheartool.h b/app/tools/gimpsheartool.h
new file mode 100644
index 0000000..0ab6d25
--- /dev/null
+++ b/app/tools/gimpsheartool.h
@@ -0,0 +1,56 @@
+/* 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_SHEAR_TOOL_H__
+#define __GIMP_SHEAR_TOOL_H__
+
+
+#include "gimptransformgridtool.h"
+
+
+#define GIMP_TYPE_SHEAR_TOOL (gimp_shear_tool_get_type ())
+#define GIMP_SHEAR_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SHEAR_TOOL, GimpShearTool))
+#define GIMP_SHEAR_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SHEAR_TOOL, GimpShearToolClass))
+#define GIMP_IS_SHEAR_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SHEAR_TOOL))
+#define GIMP_IS_SHEAR_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SHEAR_TOOL))
+#define GIMP_SHEAR_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SHEAR_TOOL, GimpShearToolClass))
+
+
+typedef struct _GimpShearTool GimpShearTool;
+typedef struct _GimpShearToolClass GimpShearToolClass;
+
+struct _GimpShearTool
+{
+ GimpTransformGridTool parent_instance;
+
+ GtkAdjustment *x_adj;
+ GtkAdjustment *y_adj;
+};
+
+struct _GimpShearToolClass
+{
+ GimpTransformGridToolClass parent_class;
+};
+
+
+void gimp_shear_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_shear_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SHEAR_TOOL_H__ */
diff --git a/app/tools/gimpsmudgetool.c b/app/tools/gimpsmudgetool.c
new file mode 100644
index 0000000..d6c5f65
--- /dev/null
+++ b/app/tools/gimpsmudgetool.c
@@ -0,0 +1,115 @@
+/* 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "paint/gimpsmudgeoptions.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppropwidgets.h"
+
+#include "gimpsmudgetool.h"
+#include "gimppaintoptions-gui.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static GtkWidget * gimp_smudge_options_gui (GimpToolOptions *tool_options);
+
+
+G_DEFINE_TYPE (GimpSmudgeTool, gimp_smudge_tool, GIMP_TYPE_BRUSH_TOOL)
+
+
+void
+gimp_smudge_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_SMUDGE_TOOL,
+ GIMP_TYPE_SMUDGE_OPTIONS,
+ gimp_smudge_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT,
+ "gimp-smudge-tool",
+ _("Smudge"),
+ _("Smudge Tool: Smudge selectively using a brush"),
+ N_("_Smudge"), "S",
+ NULL, GIMP_HELP_TOOL_SMUDGE,
+ GIMP_ICON_TOOL_SMUDGE,
+ data);
+}
+
+static void
+gimp_smudge_tool_class_init (GimpSmudgeToolClass *klass)
+{
+}
+
+static void
+gimp_smudge_tool_init (GimpSmudgeTool *smudge)
+{
+ GimpTool *tool = GIMP_TOOL (smudge);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (smudge);
+
+ gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_SMUDGE);
+
+ gimp_paint_tool_enable_color_picker (GIMP_PAINT_TOOL (smudge),
+ GIMP_COLOR_PICK_TARGET_FOREGROUND);
+
+ paint_tool->status = _("Click to smudge");
+ paint_tool->status_line = _("Click to smudge the line");
+ paint_tool->status_ctrl = NULL;
+}
+
+
+/* tool options stuff */
+
+static GtkWidget *
+gimp_smudge_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_paint_options_gui (tool_options);
+ GtkWidget *scale;
+ GtkWidget *button;
+
+ button = gimp_prop_check_button_new (config, "no-erasing", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_prop_check_button_new (config, "sample-merged", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the rate scale */
+ scale = gimp_prop_spin_scale_new (config, "rate", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ scale = gimp_prop_spin_scale_new (config, "flow", NULL,
+ 1.0, 10.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ return vbox;
+}
diff --git a/app/tools/gimpsmudgetool.h b/app/tools/gimpsmudgetool.h
new file mode 100644
index 0000000..ab5f977
--- /dev/null
+++ b/app/tools/gimpsmudgetool.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 __GIMP_SMUDGE_TOOL_H__
+#define __GIMP_SMUDGE_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+
+
+#define GIMP_TYPE_SMUDGE_TOOL (gimp_smudge_tool_get_type ())
+#define GIMP_SMUDGE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SMUDGE_TOOL, GimpSmudgeTool))
+#define GIMP_SMUDGE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SMUDGE_TOOL, GimpSmudgeToolClass))
+#define GIMP_IS_SMUDGE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SMUDGE_TOOL))
+#define GIMP_IS_SMUDGE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SMUDGE_TOOL))
+#define GIMP_SMUDGE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SMUDGE_TOOL, GimpSmudgeToolClass))
+
+
+typedef struct _GimpSmudgeTool GimpSmudgeTool;
+typedef struct _GimpSmudgeToolClass GimpSmudgeToolClass;
+
+struct _GimpSmudgeTool
+{
+ GimpBrushTool parent_instance;
+};
+
+struct _GimpSmudgeToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+void gimp_smudge_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_smudge_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SMUDGE_TOOL_H__ */
diff --git a/app/tools/gimpsourcetool.c b/app/tools/gimpsourcetool.c
new file mode 100644
index 0000000..9d9f725
--- /dev/null
+++ b/app/tools/gimpsourcetool.c
@@ -0,0 +1,519 @@
+/* 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 "tools-types.h"
+
+#include "core/gimpchannel.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+
+#include "paint/gimpsourcecore.h"
+#include "paint/gimpsourceoptions.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvashandle.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-items.h"
+
+#include "gimpsourcetool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_source_tool_has_display (GimpTool *tool,
+ GimpDisplay *display);
+static GimpDisplay * gimp_source_tool_has_image (GimpTool *tool,
+ GimpImage *image);
+static void gimp_source_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_source_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_source_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_source_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_source_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_source_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+
+static void gimp_source_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_source_tool_paint_prepare (GimpPaintTool *paint_tool,
+ GimpDisplay *display);
+
+static void gimp_source_tool_set_src_display (GimpSourceTool *source_tool,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE (GimpSourceTool, gimp_source_tool, GIMP_TYPE_BRUSH_TOOL)
+
+#define parent_class gimp_source_tool_parent_class
+
+
+static void
+gimp_source_tool_class_init (GimpSourceToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+ GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass);
+
+ tool_class->has_display = gimp_source_tool_has_display;
+ tool_class->has_image = gimp_source_tool_has_image;
+ tool_class->control = gimp_source_tool_control;
+ tool_class->button_press = gimp_source_tool_button_press;
+ tool_class->motion = gimp_source_tool_motion;
+ tool_class->modifier_key = gimp_source_tool_modifier_key;
+ tool_class->oper_update = gimp_source_tool_oper_update;
+ tool_class->cursor_update = gimp_source_tool_cursor_update;
+
+ draw_tool_class->draw = gimp_source_tool_draw;
+
+ paint_tool_class->paint_prepare = gimp_source_tool_paint_prepare;
+}
+
+static void
+gimp_source_tool_init (GimpSourceTool *source)
+{
+ source->show_source_outline = TRUE;
+}
+
+static gboolean
+gimp_source_tool_has_display (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+
+ return (display == source_tool->src_display ||
+ GIMP_TOOL_CLASS (parent_class)->has_display (tool, display));
+}
+
+static GimpDisplay *
+gimp_source_tool_has_image (GimpTool *tool,
+ GimpImage *image)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+ GimpDisplay *display;
+
+ display = GIMP_TOOL_CLASS (parent_class)->has_image (tool, image);
+
+ if (! display && source_tool->src_display)
+ {
+ if (image && gimp_display_get_image (source_tool->src_display) == image)
+ display = source_tool->src_display;
+
+ /* NULL image means any display */
+ if (! image)
+ display = source_tool->src_display;
+ }
+
+ return display;
+}
+
+static void
+gimp_source_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_source_tool_set_src_display (source_tool, NULL);
+ g_object_set (GIMP_PAINT_TOOL (tool)->core,
+ "src-drawable", NULL,
+ NULL);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_source_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+ GimpSourceCore *source = GIMP_SOURCE_CORE (paint_tool->core);
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if ((state & (toggle_mask | extend_mask)) == toggle_mask)
+ {
+ source->set_source = TRUE;
+
+ gimp_source_tool_set_src_display (source_tool, display);
+ }
+ else
+ {
+ source->set_source = FALSE;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+ press_type, display);
+
+ source_tool->src_x = source->src_x;
+ source_tool->src_y = source->src_y;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_source_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceCore *source = GIMP_SOURCE_CORE (paint_tool->core);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display);
+
+ source_tool->src_x = source->src_x;
+ source_tool->src_y = source->src_y;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_source_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceOptions *options = GIMP_SOURCE_TOOL_GET_OPTIONS (tool);
+
+ if (gimp_source_core_use_source (GIMP_SOURCE_CORE (paint_tool->core),
+ options) &&
+ key == gimp_get_toggle_behavior_mask ())
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (press)
+ {
+ paint_tool->status = source_tool->status_set_source;
+
+ source_tool->show_source_outline = FALSE;
+
+ source_tool->saved_precision =
+ gimp_tool_control_get_precision (tool->control);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+ }
+ else
+ {
+ paint_tool->status = source_tool->status_paint;
+
+ source_tool->show_source_outline = TRUE;
+
+ gimp_tool_control_set_precision (tool->control,
+ source_tool->saved_precision);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
+ display);
+}
+
+static void
+gimp_source_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceOptions *options = GIMP_SOURCE_TOOL_GET_OPTIONS (tool);
+ GimpCursorType cursor = GIMP_CURSOR_MOUSE;
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ if (gimp_source_core_use_source (GIMP_SOURCE_CORE (paint_tool->core),
+ options))
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if ((state & (toggle_mask | extend_mask)) == toggle_mask)
+ {
+ cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ }
+ else if (! GIMP_SOURCE_CORE (GIMP_PAINT_TOOL (tool)->core)->src_drawable)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+
+ gimp_tool_control_set_cursor (tool->control, cursor);
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_source_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpPaintTool *paint_tool = GIMP_PAINT_TOOL (tool);
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (tool);
+ GimpSourceOptions *options = GIMP_SOURCE_TOOL_GET_OPTIONS (tool);
+ GimpSourceCore *source;
+
+ source = GIMP_SOURCE_CORE (GIMP_PAINT_TOOL (tool)->core);
+
+ if (proximity)
+ {
+ if (gimp_source_core_use_source (source, options))
+ paint_tool->status_ctrl = source_tool->status_set_source_ctrl;
+ else
+ paint_tool->status_ctrl = NULL;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
+ display);
+
+ if (gimp_source_core_use_source (source, options))
+ {
+ if (source->src_drawable == NULL)
+ {
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ if (state & toggle_mask)
+ {
+ gimp_tool_replace_status (tool, display, "%s",
+ source_tool->status_set_source);
+ }
+ else
+ {
+ gimp_tool_replace_status (tool, display, "%s-%s",
+ gimp_get_mod_string (toggle_mask),
+ source_tool->status_set_source);
+ }
+ }
+ else
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ source_tool->src_x = source->src_x;
+ source_tool->src_y = source->src_y;
+
+ if (! source->first_stroke)
+ {
+ switch (options->align_mode)
+ {
+ case GIMP_SOURCE_ALIGN_YES:
+ source_tool->src_x = floor (coords->x) + source->offset_x;
+ source_tool->src_y = floor (coords->y) + source->offset_y;
+ break;
+
+ case GIMP_SOURCE_ALIGN_REGISTERED:
+ source_tool->src_x = floor (coords->x);
+ source_tool->src_y = floor (coords->y);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+ }
+}
+
+static void
+gimp_source_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (draw_tool);
+ GimpSourceOptions *options = GIMP_SOURCE_TOOL_GET_OPTIONS (draw_tool);
+ GimpSourceCore *source;
+
+ source = GIMP_SOURCE_CORE (GIMP_PAINT_TOOL (draw_tool)->core);
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+
+ if (gimp_source_core_use_source (source, options) &&
+ source->src_drawable && source_tool->src_display)
+ {
+ GimpDisplayShell *src_shell;
+ gint off_x;
+ gint off_y;
+ gdouble src_x;
+ gdouble src_y;
+
+ src_shell = gimp_display_get_shell (source_tool->src_display);
+
+ gimp_item_get_offset (GIMP_ITEM (source->src_drawable), &off_x, &off_y);
+
+ src_x = source_tool->src_x + off_x + 0.5;
+ src_y = source_tool->src_y + off_y + 0.5;
+
+ if (source_tool->src_outline)
+ {
+ gimp_display_shell_remove_tool_item (src_shell,
+ source_tool->src_outline);
+ source_tool->src_outline = NULL;
+ }
+
+ if (source_tool->show_source_outline)
+ {
+ source_tool->src_outline =
+ gimp_brush_tool_create_outline (GIMP_BRUSH_TOOL (source_tool),
+ source_tool->src_display,
+ src_x, src_y);
+
+ if (source_tool->src_outline)
+ {
+ gimp_display_shell_add_tool_item (src_shell,
+ source_tool->src_outline);
+ g_object_unref (source_tool->src_outline);
+ }
+ }
+
+ if (source_tool->src_outline)
+ {
+ if (source_tool->src_handle)
+ {
+ gimp_display_shell_remove_tool_item (src_shell,
+ source_tool->src_handle);
+ source_tool->src_handle = NULL;
+ }
+ }
+ else
+ {
+ if (! source_tool->src_handle)
+ {
+ source_tool->src_handle =
+ gimp_canvas_handle_new (src_shell,
+ GIMP_HANDLE_CROSS,
+ GIMP_HANDLE_ANCHOR_CENTER,
+ src_x, src_y,
+ GIMP_TOOL_HANDLE_SIZE_CROSS,
+ GIMP_TOOL_HANDLE_SIZE_CROSS);
+ gimp_display_shell_add_tool_item (src_shell,
+ source_tool->src_handle);
+ g_object_unref (source_tool->src_handle);
+ }
+ else
+ {
+ gimp_canvas_handle_set_position (source_tool->src_handle,
+ src_x, src_y);
+ }
+ }
+ }
+}
+
+static void
+gimp_source_tool_paint_prepare (GimpPaintTool *paint_tool,
+ GimpDisplay *display)
+{
+ GimpSourceTool *source_tool = GIMP_SOURCE_TOOL (paint_tool);
+
+ if (GIMP_PAINT_TOOL_CLASS (parent_class)->paint_prepare)
+ GIMP_PAINT_TOOL_CLASS (parent_class)->paint_prepare (paint_tool, display);
+
+ if (source_tool->src_display)
+ {
+ GimpDisplayShell *src_shell;
+
+ src_shell = gimp_display_get_shell (source_tool->src_display);
+
+ gimp_paint_core_set_show_all (paint_tool->core, src_shell->show_all);
+ }
+}
+
+static void
+gimp_source_tool_set_src_display (GimpSourceTool *source_tool,
+ GimpDisplay *display)
+{
+ if (source_tool->src_display != display)
+ {
+ if (source_tool->src_display)
+ {
+ GimpDisplayShell *src_shell;
+
+ src_shell = gimp_display_get_shell (source_tool->src_display);
+
+ if (source_tool->src_handle)
+ {
+ gimp_display_shell_remove_tool_item (src_shell,
+ source_tool->src_handle);
+ source_tool->src_handle = NULL;
+ }
+
+ if (source_tool->src_outline)
+ {
+ gimp_display_shell_remove_tool_item (src_shell,
+ source_tool->src_outline);
+ source_tool->src_outline = NULL;
+ }
+ }
+
+ source_tool->src_display = display;
+ }
+}
diff --git a/app/tools/gimpsourcetool.h b/app/tools/gimpsourcetool.h
new file mode 100644
index 0000000..c573791
--- /dev/null
+++ b/app/tools/gimpsourcetool.h
@@ -0,0 +1,66 @@
+/* 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_SOURCE_TOOL_H__
+#define __GIMP_SOURCE_TOOL_H__
+
+
+#include "gimpbrushtool.h"
+
+
+#define GIMP_TYPE_SOURCE_TOOL (gimp_source_tool_get_type ())
+#define GIMP_SOURCE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SOURCE_TOOL, GimpSourceTool))
+#define GIMP_SOURCE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SOURCE_TOOL, GimpSourceToolClass))
+#define GIMP_IS_SOURCE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SOURCE_TOOL))
+#define GIMP_IS_SOURCE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SOURCE_TOOL))
+#define GIMP_SOURCE_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SOURCE_TOOL, GimpSourceToolClass))
+
+#define GIMP_SOURCE_TOOL_GET_OPTIONS(t) (GIMP_SOURCE_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpSourceTool GimpSourceTool;
+typedef struct _GimpSourceToolClass GimpSourceToolClass;
+
+struct _GimpSourceTool
+{
+ GimpBrushTool parent_instance;
+
+ GimpDisplay *src_display;
+ gint src_x;
+ gint src_y;
+
+ gboolean show_source_outline;
+ GimpCursorPrecision saved_precision;
+
+ GimpCanvasItem *src_handle;
+ GimpCanvasItem *src_outline;
+
+ const gchar *status_paint;
+ const gchar *status_set_source;
+ const gchar *status_set_source_ctrl;
+};
+
+struct _GimpSourceToolClass
+{
+ GimpBrushToolClass parent_class;
+};
+
+
+GType gimp_source_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SOURCE_TOOL_H__ */
diff --git a/app/tools/gimptextoptions.c b/app/tools/gimptextoptions.c
new file mode 100644
index 0000000..70f2dc4
--- /dev/null
+++ b/app/tools/gimptextoptions.c
@@ -0,0 +1,743 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpconfig-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimpdatafactory.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimpviewable.h"
+
+#include "text/gimptext.h"
+
+#include "widgets/gimpcolorpanel.h"
+#include "widgets/gimpmenufactory.h"
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimptextbuffer.h"
+#include "widgets/gimptexteditor.h"
+#include "widgets/gimpviewablebox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimptextoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_FONT_SIZE,
+ PROP_UNIT,
+ PROP_ANTIALIAS,
+ PROP_HINT_STYLE,
+ PROP_LANGUAGE,
+ PROP_BASE_DIR,
+ PROP_JUSTIFICATION,
+ PROP_INDENTATION,
+ PROP_LINE_SPACING,
+ PROP_LETTER_SPACING,
+ PROP_BOX_MODE,
+
+ PROP_USE_EDITOR,
+
+ PROP_FONT_VIEW_TYPE,
+ PROP_FONT_VIEW_SIZE
+};
+
+
+static void gimp_text_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_text_options_finalize (GObject *object);
+static void gimp_text_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_text_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_text_options_reset (GimpConfig *config);
+
+static void gimp_text_options_notify_font (GimpContext *context,
+ GParamSpec *pspec,
+ GimpText *text);
+static void gimp_text_options_notify_text_font (GimpText *text,
+ GParamSpec *pspec,
+ GimpContext *context);
+static void gimp_text_options_notify_color (GimpContext *context,
+ GParamSpec *pspec,
+ GimpText *text);
+static void gimp_text_options_notify_text_color (GimpText *text,
+ GParamSpec *pspec,
+ GimpContext *context);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpTextOptions, gimp_text_options,
+ GIMP_TYPE_TOOL_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_text_options_config_iface_init))
+
+#define parent_class gimp_text_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_text_options_class_init (GimpTextOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_text_options_finalize;
+ object_class->set_property = gimp_text_options_set_property;
+ object_class->get_property = gimp_text_options_get_property;
+
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_UNIT,
+ "font-size-unit",
+ _("Unit"),
+ _("Font size unit"),
+ TRUE, FALSE, GIMP_UNIT_PIXEL,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FONT_SIZE,
+ "font-size",
+ _("Font size"),
+ _("Font size"),
+ 0.0, 8192.0, 62.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ANTIALIAS,
+ "antialias",
+ _("Antialiasing"),
+ NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_HINT_STYLE,
+ "hint-style",
+ _("Hinting"),
+ _("Hinting alters the font outline to "
+ "produce a crisp bitmap at small "
+ "sizes"),
+ GIMP_TYPE_TEXT_HINT_STYLE,
+ GIMP_TEXT_HINT_STYLE_MEDIUM,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_LANGUAGE,
+ "language",
+ _("Language"),
+ _("The text language may have an effect "
+ "on the way the text is rendered."),
+ (const gchar *) gtk_get_default_language (),
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_BASE_DIR,
+ "base-direction",
+ NULL, NULL,
+ GIMP_TYPE_TEXT_DIRECTION,
+ GIMP_TEXT_DIRECTION_LTR,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_JUSTIFICATION,
+ "justify",
+ _("Justify"),
+ _("Text alignment"),
+ GIMP_TYPE_TEXT_JUSTIFICATION,
+ GIMP_TEXT_JUSTIFY_LEFT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_INDENTATION,
+ "indent",
+ _("Indentation"),
+ _("Indentation of the first line"),
+ -8192.0, 8192.0, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_DEFAULTS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LINE_SPACING,
+ "line-spacing",
+ _("Line spacing"),
+ _("Adjust line spacing"),
+ -8192.0, 8192.0, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_DEFAULTS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LETTER_SPACING,
+ "letter-spacing",
+ _("Letter spacing"),
+ _("Adjust letter spacing"),
+ -8192.0, 8192.0, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_DEFAULTS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_BOX_MODE,
+ "box-mode",
+ _("Box"),
+ _("Whether text flows into rectangular shape or "
+ "moves into a new line when you press Enter"),
+ GIMP_TYPE_TEXT_BOX_MODE,
+ GIMP_TEXT_BOX_DYNAMIC,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_EDITOR,
+ "use-editor",
+ _("Use editor"),
+ _("Use an external editor window for text entry"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FONT_VIEW_TYPE,
+ "font-view-type",
+ NULL, NULL,
+ GIMP_TYPE_VIEW_TYPE,
+ GIMP_VIEW_TYPE_LIST,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_FONT_VIEW_SIZE,
+ "font-view-size",
+ NULL, NULL,
+ GIMP_VIEW_SIZE_TINY,
+ GIMP_VIEWABLE_MAX_BUTTON_SIZE,
+ GIMP_VIEW_SIZE_SMALL,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_text_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ config_iface->reset = gimp_text_options_reset;
+}
+
+static void
+gimp_text_options_init (GimpTextOptions *options)
+{
+ options->size_entry = NULL;
+}
+
+static void
+gimp_text_options_finalize (GObject *object)
+{
+ GimpTextOptions *options = GIMP_TEXT_OPTIONS (object);
+
+ g_clear_pointer (&options->language, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_text_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTextOptions *options = GIMP_TEXT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_FONT_SIZE:
+ g_value_set_double (value, options->font_size);
+ break;
+ case PROP_UNIT:
+ g_value_set_int (value, options->unit);
+ break;
+ case PROP_ANTIALIAS:
+ g_value_set_boolean (value, options->antialias);
+ break;
+ case PROP_HINT_STYLE:
+ g_value_set_enum (value, options->hint_style);
+ break;
+ case PROP_LANGUAGE:
+ g_value_set_string (value, options->language);
+ break;
+ case PROP_BASE_DIR:
+ g_value_set_enum (value, options->base_dir);
+ break;
+ case PROP_JUSTIFICATION:
+ g_value_set_enum (value, options->justify);
+ break;
+ case PROP_INDENTATION:
+ g_value_set_double (value, options->indent);
+ break;
+ case PROP_LINE_SPACING:
+ g_value_set_double (value, options->line_spacing);
+ break;
+ case PROP_LETTER_SPACING:
+ g_value_set_double (value, options->letter_spacing);
+ break;
+ case PROP_BOX_MODE:
+ g_value_set_enum (value, options->box_mode);
+ break;
+
+ case PROP_USE_EDITOR:
+ g_value_set_boolean (value, options->use_editor);
+ break;
+
+ case PROP_FONT_VIEW_TYPE:
+ g_value_set_enum (value, options->font_view_type);
+ break;
+ case PROP_FONT_VIEW_SIZE:
+ g_value_set_int (value, options->font_view_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_text_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTextOptions *options = GIMP_TEXT_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_FONT_SIZE:
+ options->font_size = g_value_get_double (value);
+ break;
+ case PROP_UNIT:
+ options->unit = g_value_get_int (value);
+ break;
+ case PROP_ANTIALIAS:
+ options->antialias = g_value_get_boolean (value);
+ break;
+ case PROP_HINT_STYLE:
+ options->hint_style = g_value_get_enum (value);
+ break;
+ case PROP_BASE_DIR:
+ options->base_dir = g_value_get_enum (value);
+ break;
+ case PROP_LANGUAGE:
+ g_free (options->language);
+ options->language = g_value_dup_string (value);
+ break;
+ case PROP_JUSTIFICATION:
+ options->justify = g_value_get_enum (value);
+ break;
+ case PROP_INDENTATION:
+ options->indent = g_value_get_double (value);
+ break;
+ case PROP_LINE_SPACING:
+ options->line_spacing = g_value_get_double (value);
+ break;
+ case PROP_LETTER_SPACING:
+ options->letter_spacing = g_value_get_double (value);
+ break;
+ case PROP_BOX_MODE:
+ options->box_mode = g_value_get_enum (value);
+ break;
+
+ case PROP_USE_EDITOR:
+ options->use_editor = g_value_get_boolean (value);
+ break;
+
+ case PROP_FONT_VIEW_TYPE:
+ options->font_view_type = g_value_get_enum (value);
+ break;
+ case PROP_FONT_VIEW_SIZE:
+ options->font_view_size = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_text_options_reset (GimpConfig *config)
+{
+ GObject *object = G_OBJECT (config);
+
+ /* implement reset() ourselves because the default impl would
+ * reset *all* properties, including all rectangle properties
+ * of the text box
+ */
+
+ /* context */
+ gimp_config_reset_property (object, "font");
+ gimp_config_reset_property (object, "foreground");
+
+ /* text options */
+ gimp_config_reset_property (object, "font-size");
+ gimp_config_reset_property (object, "font-size-unit");
+ gimp_config_reset_property (object, "antialias");
+ gimp_config_reset_property (object, "hint-style");
+ gimp_config_reset_property (object, "language");
+ gimp_config_reset_property (object, "base-direction");
+ gimp_config_reset_property (object, "justify");
+ gimp_config_reset_property (object, "indent");
+ gimp_config_reset_property (object, "line-spacing");
+ gimp_config_reset_property (object, "letter-spacing");
+ gimp_config_reset_property (object, "box-mode");
+ gimp_config_reset_property (object, "use-editor");
+}
+
+static void
+gimp_text_options_notify_font (GimpContext *context,
+ GParamSpec *pspec,
+ GimpText *text)
+{
+ g_signal_handlers_block_by_func (text,
+ gimp_text_options_notify_text_font,
+ context);
+
+ g_object_set (text, "font", gimp_context_get_font_name (context), NULL);
+
+ g_signal_handlers_unblock_by_func (text,
+ gimp_text_options_notify_text_font,
+ context);
+}
+
+static void
+gimp_text_options_notify_text_font (GimpText *text,
+ GParamSpec *pspec,
+ GimpContext *context)
+{
+ g_signal_handlers_block_by_func (context,
+ gimp_text_options_notify_font, text);
+
+ gimp_context_set_font_name (context, text->font);
+
+ g_signal_handlers_unblock_by_func (context,
+ gimp_text_options_notify_font, text);
+}
+
+static void
+gimp_text_options_notify_color (GimpContext *context,
+ GParamSpec *pspec,
+ GimpText *text)
+{
+ GimpRGB color;
+
+ gimp_context_get_foreground (context, &color);
+
+ g_signal_handlers_block_by_func (text,
+ gimp_text_options_notify_text_color,
+ context);
+
+ g_object_set (text, "color", &color, NULL);
+
+ g_signal_handlers_unblock_by_func (text,
+ gimp_text_options_notify_text_color,
+ context);
+}
+
+static void
+gimp_text_options_notify_text_color (GimpText *text,
+ GParamSpec *pspec,
+ GimpContext *context)
+{
+ g_signal_handlers_block_by_func (context,
+ gimp_text_options_notify_color, text);
+
+ gimp_context_set_foreground (context, &text->color);
+
+ g_signal_handlers_unblock_by_func (context,
+ gimp_text_options_notify_color, text);
+}
+
+/* This function could live in gimptexttool.c also.
+ * But it takes some bloat out of that file...
+ */
+void
+gimp_text_options_connect_text (GimpTextOptions *options,
+ GimpText *text)
+{
+ GimpContext *context;
+ GimpRGB color;
+
+ g_return_if_fail (GIMP_IS_TEXT_OPTIONS (options));
+ g_return_if_fail (GIMP_IS_TEXT (text));
+
+ context = GIMP_CONTEXT (options);
+
+ gimp_context_get_foreground (context, &color);
+
+ gimp_config_sync (G_OBJECT (options), G_OBJECT (text), 0);
+
+ g_object_set (text,
+ "color", &color,
+ "font", gimp_context_get_font_name (context),
+ NULL);
+
+ gimp_config_connect (G_OBJECT (options), G_OBJECT (text), NULL);
+
+ g_signal_connect_object (options, "notify::font",
+ G_CALLBACK (gimp_text_options_notify_font),
+ text, 0);
+ g_signal_connect_object (text, "notify::font",
+ G_CALLBACK (gimp_text_options_notify_text_font),
+ options, 0);
+
+ g_signal_connect_object (options, "notify::foreground",
+ G_CALLBACK (gimp_text_options_notify_color),
+ text, 0);
+ g_signal_connect_object (text, "notify::color",
+ G_CALLBACK (gimp_text_options_notify_text_color),
+ options, 0);
+}
+
+GtkWidget *
+gimp_text_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpTextOptions *options = GIMP_TEXT_OPTIONS (tool_options);
+ GtkWidget *main_vbox = gimp_tool_options_gui (tool_options);
+ GimpAsyncSet *async_set;
+ GtkWidget *options_vbox;
+ GtkWidget *table;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *entry;
+ GtkWidget *box;
+ GtkWidget *spinbutton;
+ GtkWidget *combo;
+ GtkSizeGroup *size_group;
+ gint row = 0;
+
+ async_set =
+ gimp_data_factory_get_async_set (tool_options->tool_info->gimp->font_factory);
+
+ box = gimp_busy_box_new (_("Loading fonts (this may take a while...)"));
+ gtk_container_set_border_width (GTK_CONTAINER (box), 8);
+ gtk_box_pack_start (GTK_BOX (main_vbox), box, FALSE, FALSE, 0);
+
+ g_object_bind_property (async_set, "empty",
+ box, "visible",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ options_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL,
+ gtk_box_get_spacing (GTK_BOX (main_vbox)));
+ gtk_box_pack_start (GTK_BOX (main_vbox), options_vbox, FALSE, FALSE, 0);
+ gtk_widget_show (options_vbox);
+
+ g_object_bind_property (async_set, "empty",
+ options_vbox, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ hbox = gimp_prop_font_box_new (NULL, GIMP_CONTEXT (tool_options),
+ _("Font"), 2,
+ "font-view-type", "font-view-size");
+ gtk_box_pack_start (GTK_BOX (options_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ table = gtk_table_new (1, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_box_pack_start (GTK_BOX (options_vbox), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ entry = gimp_prop_size_entry_new (config,
+ "font-size", FALSE, "font-size-unit", "%p",
+ GIMP_SIZE_ENTRY_UPDATE_SIZE, 72.0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Size:"), 0.0, 0.5,
+ entry, 2, FALSE);
+
+ options->size_entry = entry;
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_box_pack_start (GTK_BOX (options_vbox), vbox, FALSE, FALSE, 0);
+ gtk_widget_show (vbox);
+
+ button = gimp_prop_check_button_new (config, "use-editor", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_prop_check_button_new (config, "antialias", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ table = gtk_table_new (6, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_box_pack_start (GTK_BOX (options_vbox), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ row = 0;
+
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ button = gimp_prop_enum_combo_box_new (config, "hint-style", -1, -1);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Hinting:"), 0.0, 0.5,
+ button, 1, TRUE);
+ gtk_size_group_add_widget (size_group, button);
+
+ button = gimp_prop_color_button_new (config, "foreground", _("Text Color"),
+ 40, 24, GIMP_COLOR_AREA_FLAT);
+ gimp_color_panel_set_context (GIMP_COLOR_PANEL (button),
+ GIMP_CONTEXT (options));
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Color:"), 0.0, 0.5,
+ button, 1, TRUE);
+ gtk_size_group_add_widget (size_group, button);
+
+ box = gimp_prop_enum_icon_box_new (config, "justify", "format-justify", 0, 0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Justify:"), 0.0, 0.5,
+ box, 2, TRUE);
+ gtk_size_group_add_widget (size_group, box);
+ g_object_unref (size_group);
+
+ spinbutton = gimp_prop_spin_button_new (config, "indent", 1.0, 10.0, 1);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 5);
+ gimp_table_attach_icon (GTK_TABLE (table), row++,
+ GIMP_ICON_FORMAT_INDENT_MORE,
+ spinbutton, 1, TRUE);
+
+ spinbutton = gimp_prop_spin_button_new (config, "line-spacing", 1.0, 10.0, 1);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 5);
+ gimp_table_attach_icon (GTK_TABLE (table), row++,
+ GIMP_ICON_FORMAT_TEXT_SPACING_LINE,
+ spinbutton, 1, TRUE);
+
+ spinbutton = gimp_prop_spin_button_new (config,
+ "letter-spacing", 1.0, 10.0, 1);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 5);
+ gimp_table_attach_icon (GTK_TABLE (table), row++,
+ GIMP_ICON_FORMAT_TEXT_SPACING_LETTER,
+ spinbutton, 1, TRUE);
+
+ combo = gimp_prop_enum_combo_box_new (config, "box-mode", 0, 0);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
+ _("Box:"), 0.0, 0.5,
+ combo, 1, TRUE);
+
+ /* Only add the language entry if the iso-codes package is available. */
+
+#ifdef HAVE_ISO_CODES
+ {
+ GtkWidget *label;
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_box_pack_start (GTK_BOX (options_vbox), vbox, FALSE, FALSE, 0);
+ gtk_widget_show (vbox);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new (_("Language:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ entry = gimp_prop_language_entry_new (config, "language");
+ gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0);
+ gtk_widget_show (entry);
+ }
+#endif
+
+ return main_vbox;
+}
+
+static void
+gimp_text_options_editor_dir_changed (GimpTextEditor *editor,
+ GimpTextOptions *options)
+{
+ g_object_set (options,
+ "base-direction", editor->base_dir,
+ NULL);
+}
+
+static void
+gimp_text_options_editor_notify_dir (GimpTextOptions *options,
+ GParamSpec *pspec,
+ GimpTextEditor *editor)
+{
+ GimpTextDirection dir;
+
+ g_object_get (options,
+ "base-direction", &dir,
+ NULL);
+
+ gimp_text_editor_set_direction (editor, dir);
+}
+
+static void
+gimp_text_options_editor_notify_font (GimpTextOptions *options,
+ GParamSpec *pspec,
+ GimpTextEditor *editor)
+{
+ const gchar *font_name;
+
+ font_name = gimp_context_get_font_name (GIMP_CONTEXT (options));
+
+ gimp_text_editor_set_font_name (editor, font_name);
+}
+
+GtkWidget *
+gimp_text_options_editor_new (GtkWindow *parent,
+ Gimp *gimp,
+ GimpTextOptions *options,
+ GimpMenuFactory *menu_factory,
+ const gchar *title,
+ GimpText *text,
+ GimpTextBuffer *text_buffer,
+ gdouble xres,
+ gdouble yres)
+{
+ GtkWidget *editor;
+ const gchar *font_name;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_TEXT_OPTIONS (options), NULL);
+ g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL);
+ g_return_val_if_fail (title != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_TEXT (text), NULL);
+ g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (text_buffer), NULL);
+
+ editor = gimp_text_editor_new (title, parent, gimp, menu_factory,
+ text, text_buffer, xres, yres);
+
+ font_name = gimp_context_get_font_name (GIMP_CONTEXT (options));
+
+ gimp_text_editor_set_direction (GIMP_TEXT_EDITOR (editor),
+ options->base_dir);
+ gimp_text_editor_set_font_name (GIMP_TEXT_EDITOR (editor),
+ font_name);
+
+ g_signal_connect_object (editor, "dir-changed",
+ G_CALLBACK (gimp_text_options_editor_dir_changed),
+ options, 0);
+ g_signal_connect_object (options, "notify::base-direction",
+ G_CALLBACK (gimp_text_options_editor_notify_dir),
+ editor, 0);
+ g_signal_connect_object (options, "notify::font",
+ G_CALLBACK (gimp_text_options_editor_notify_font),
+ editor, 0);
+
+ return editor;
+}
diff --git a/app/tools/gimptextoptions.h b/app/tools/gimptextoptions.h
new file mode 100644
index 0000000..d80c21f
--- /dev/null
+++ b/app/tools/gimptextoptions.h
@@ -0,0 +1,80 @@
+/* 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_TEXT_OPTIONS_H__
+#define __GIMP_TEXT_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_TEXT_OPTIONS (gimp_text_options_get_type ())
+#define GIMP_TEXT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_OPTIONS, GimpTextOptions))
+#define GIMP_TEXT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEXT_OPTIONS, GimpTextOptionsClass))
+#define GIMP_IS_TEXT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_OPTIONS))
+#define GIMP_IS_TEXT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEXT_OPTIONS))
+#define GIMP_TEXT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TEXT_OPTIONS, GimpTextOptionsClass))
+
+
+typedef struct _GimpTextOptions GimpTextOptions;
+typedef struct _GimpToolOptionsClass GimpTextOptionsClass;
+
+struct _GimpTextOptions
+{
+ GimpToolOptions tool_options;
+
+ GimpUnit unit;
+ gdouble font_size;
+ gboolean antialias;
+ GimpTextHintStyle hint_style;
+ gchar *language;
+ GimpTextDirection base_dir;
+ GimpTextJustification justify;
+ gdouble indent;
+ gdouble line_spacing;
+ gdouble letter_spacing;
+ GimpTextBoxMode box_mode;
+
+ GimpViewType font_view_type;
+ GimpViewSize font_view_size;
+
+ gboolean use_editor;
+
+ /* options gui */
+ GtkWidget *size_entry;
+};
+
+
+GType gimp_text_options_get_type (void) G_GNUC_CONST;
+
+void gimp_text_options_connect_text (GimpTextOptions *options,
+ GimpText *text);
+
+GtkWidget * gimp_text_options_gui (GimpToolOptions *tool_options);
+
+GtkWidget * gimp_text_options_editor_new (GtkWindow *parent,
+ Gimp *gimp,
+ GimpTextOptions *options,
+ GimpMenuFactory *menu_factory,
+ const gchar *title,
+ GimpText *text,
+ GimpTextBuffer *text_buffer,
+ gdouble xres,
+ gdouble yres);
+
+
+#endif /* __GIMP_TEXT_OPTIONS_H__ */
diff --git a/app/tools/gimptexttool-editor.c b/app/tools/gimptexttool-editor.c
new file mode 100644
index 0000000..62ee118
--- /dev/null
+++ b/app/tools/gimptexttool-editor.c
@@ -0,0 +1,1864 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextTool
+ * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org>
+ * Daniel Eddeland <danedde@svn.gnome.org>
+ * 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 <gdk/gdkkeysyms.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpdatafactory.h"
+#include "core/gimpimage.h"
+#include "core/gimptoolinfo.h"
+
+#include "text/gimptext.h"
+#include "text/gimptextlayout.h"
+
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpdockcontainer.h"
+#include "widgets/gimpoverlaybox.h"
+#include "widgets/gimpoverlayframe.h"
+#include "widgets/gimptextbuffer.h"
+#include "widgets/gimptexteditor.h"
+#include "widgets/gimptextproxy.h"
+#include "widgets/gimptextstyleeditor.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimptextoptions.h"
+#include "gimptexttool.h"
+#include "gimptexttool-editor.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_text_tool_ensure_proxy (GimpTextTool *text_tool);
+static void gimp_text_tool_move_cursor (GimpTextTool *text_tool,
+ GtkMovementStep step,
+ gint count,
+ gboolean extend_selection);
+static void gimp_text_tool_insert_at_cursor (GimpTextTool *text_tool,
+ const gchar *str);
+static void gimp_text_tool_delete_from_cursor (GimpTextTool *text_tool,
+ GtkDeleteType type,
+ gint count);
+static void gimp_text_tool_backspace (GimpTextTool *text_tool);
+static void gimp_text_tool_toggle_overwrite (GimpTextTool *text_tool);
+static void gimp_text_tool_select_all (GimpTextTool *text_tool,
+ gboolean select);
+static void gimp_text_tool_change_size (GimpTextTool *text_tool,
+ gdouble amount);
+static void gimp_text_tool_change_baseline (GimpTextTool *text_tool,
+ gdouble amount);
+static void gimp_text_tool_change_kerning (GimpTextTool *text_tool,
+ gdouble amount);
+
+static void gimp_text_tool_options_notify (GimpTextOptions *options,
+ GParamSpec *pspec,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_editor_dialog (GimpTextTool *text_tool);
+static void gimp_text_tool_editor_destroy (GtkWidget *dialog,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_enter_text (GimpTextTool *text_tool,
+ const gchar *str);
+static void gimp_text_tool_xy_to_iter (GimpTextTool *text_tool,
+ gdouble x,
+ gdouble y,
+ GtkTextIter *iter);
+
+static void gimp_text_tool_im_preedit_start (GtkIMContext *context,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_im_preedit_end (GtkIMContext *context,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_im_preedit_changed (GtkIMContext *context,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_im_commit (GtkIMContext *context,
+ const gchar *str,
+ GimpTextTool *text_tool);
+static gboolean gimp_text_tool_im_retrieve_surrounding
+ (GtkIMContext *context,
+ GimpTextTool *text_tool);
+static gboolean gimp_text_tool_im_delete_surrounding
+ (GtkIMContext *context,
+ gint offset,
+ gint n_chars,
+ GimpTextTool *text_tool);
+
+static void gimp_text_tool_im_delete_preedit (GimpTextTool *text_tool);
+
+static void gimp_text_tool_editor_copy_selection_to_clipboard
+ (GimpTextTool *text_tool);
+
+static void gimp_text_tool_fix_position (GimpTextTool *text_tool,
+ gdouble *x,
+ gdouble *y);
+
+static void gimp_text_tool_convert_gdkkeyevent (GimpTextTool *text_tool,
+ GdkEventKey *kevent);
+
+
+/* public functions */
+
+void
+gimp_text_tool_editor_init (GimpTextTool *text_tool)
+{
+ text_tool->im_context = gtk_im_multicontext_new ();
+ text_tool->needs_im_reset = FALSE;
+
+ text_tool->preedit_string = NULL;
+ text_tool->preedit_cursor = 0;
+ text_tool->overwrite_mode = FALSE;
+ text_tool->x_pos = -1;
+
+ g_signal_connect (text_tool->im_context, "preedit-start",
+ G_CALLBACK (gimp_text_tool_im_preedit_start),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "preedit-end",
+ G_CALLBACK (gimp_text_tool_im_preedit_end),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "preedit-changed",
+ G_CALLBACK (gimp_text_tool_im_preedit_changed),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "commit",
+ G_CALLBACK (gimp_text_tool_im_commit),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "retrieve-surrounding",
+ G_CALLBACK (gimp_text_tool_im_retrieve_surrounding),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "delete-surrounding",
+ G_CALLBACK (gimp_text_tool_im_delete_surrounding),
+ text_tool);
+}
+
+void
+gimp_text_tool_editor_finalize (GimpTextTool *text_tool)
+{
+ if (text_tool->im_context)
+ {
+ g_object_unref (text_tool->im_context);
+ text_tool->im_context = NULL;
+ }
+}
+
+void
+gimp_text_tool_editor_start (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ gtk_im_context_set_client_window (text_tool->im_context,
+ gtk_widget_get_window (shell->canvas));
+
+ text_tool->needs_im_reset = TRUE;
+ gimp_text_tool_reset_im_context (text_tool);
+
+ gtk_im_context_focus_in (text_tool->im_context);
+
+ if (options->use_editor)
+ gimp_text_tool_editor_dialog (text_tool);
+
+ g_signal_connect (options, "notify::use-editor",
+ G_CALLBACK (gimp_text_tool_options_notify),
+ text_tool);
+
+ if (! text_tool->style_overlay)
+ {
+ Gimp *gimp = GIMP_CONTEXT (options)->gimp;
+ GimpContainer *fonts;
+ gdouble xres = 1.0;
+ gdouble yres = 1.0;
+
+ text_tool->style_overlay = gimp_overlay_frame_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (text_tool->style_overlay),
+ 4);
+ gimp_display_shell_add_overlay (shell,
+ text_tool->style_overlay,
+ 0, 0,
+ GIMP_HANDLE_ANCHOR_CENTER, 0, 0);
+ gimp_overlay_box_set_child_opacity (GIMP_OVERLAY_BOX (shell->canvas),
+ text_tool->style_overlay, 0.7);
+
+ if (text_tool->image)
+ gimp_image_get_resolution (text_tool->image, &xres, &yres);
+
+ fonts = gimp_data_factory_get_container (gimp->font_factory);
+
+ text_tool->style_editor = gimp_text_style_editor_new (gimp,
+ text_tool->proxy,
+ text_tool->buffer,
+ fonts,
+ xres, yres);
+ gtk_container_add (GTK_CONTAINER (text_tool->style_overlay),
+ text_tool->style_editor);
+ gtk_widget_show (text_tool->style_editor);
+ }
+
+ gimp_text_tool_editor_position (text_tool);
+ gtk_widget_show (text_tool->style_overlay);
+}
+
+void
+gimp_text_tool_editor_position (GimpTextTool *text_tool)
+{
+ if (text_tool->style_overlay)
+ {
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GtkRequisition requisition;
+ gdouble x, y;
+
+ gtk_widget_size_request (text_tool->style_overlay, &requisition);
+
+ g_object_get (text_tool->widget,
+ "x1", &x,
+ "y1", &y,
+ NULL);
+
+ gimp_display_shell_move_overlay (shell,
+ text_tool->style_overlay,
+ x, y,
+ GIMP_HANDLE_ANCHOR_SOUTH_WEST, 4, 12);
+
+ if (text_tool->image)
+ {
+ gdouble xres, yres;
+
+ gimp_image_get_resolution (text_tool->image, &xres, &yres);
+
+ g_object_set (text_tool->style_editor,
+ "resolution-x", xres,
+ "resolution-y", yres,
+ NULL);
+ }
+ }
+}
+
+void
+gimp_text_tool_editor_halt (GimpTextTool *text_tool)
+{
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+
+ if (text_tool->style_overlay)
+ {
+ gtk_widget_destroy (text_tool->style_overlay);
+ text_tool->style_overlay = NULL;
+ text_tool->style_editor = NULL;
+ }
+
+ g_signal_handlers_disconnect_by_func (options,
+ gimp_text_tool_options_notify,
+ text_tool);
+
+ if (text_tool->editor_dialog)
+ {
+ g_signal_handlers_disconnect_by_func (text_tool->editor_dialog,
+ gimp_text_tool_editor_destroy,
+ text_tool);
+ gtk_widget_destroy (text_tool->editor_dialog);
+ }
+
+ if (text_tool->proxy_text_view)
+ {
+ gtk_widget_destroy (text_tool->offscreen_window);
+ text_tool->offscreen_window = NULL;
+ text_tool->proxy_text_view = NULL;
+ }
+
+ text_tool->needs_im_reset = TRUE;
+ gimp_text_tool_reset_im_context (text_tool);
+
+ gtk_im_context_focus_out (text_tool->im_context);
+
+ gtk_im_context_set_client_window (text_tool->im_context, NULL);
+}
+
+void
+gimp_text_tool_editor_button_press (GimpTextTool *text_tool,
+ gdouble x,
+ gdouble y,
+ GimpButtonPressType press_type)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter cursor;
+ GtkTextIter selection;
+
+ gimp_text_tool_xy_to_iter (text_tool, x, y, &cursor);
+
+ selection = cursor;
+
+ text_tool->select_start_iter = cursor;
+ text_tool->select_words = FALSE;
+ text_tool->select_lines = FALSE;
+
+ switch (press_type)
+ {
+ GtkTextIter start, end;
+
+ case GIMP_BUTTON_PRESS_NORMAL:
+ if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end) ||
+ gtk_text_iter_compare (&start, &cursor))
+ gtk_text_buffer_place_cursor (buffer, &cursor);
+ break;
+
+ case GIMP_BUTTON_PRESS_DOUBLE:
+ text_tool->select_words = TRUE;
+
+ if (! gtk_text_iter_starts_word (&cursor))
+ gtk_text_iter_backward_visible_word_starts (&cursor, 1);
+
+ if (! gtk_text_iter_ends_word (&selection) &&
+ ! gtk_text_iter_forward_visible_word_ends (&selection, 1))
+ gtk_text_iter_forward_to_line_end (&selection);
+
+ gtk_text_buffer_select_range (buffer, &cursor, &selection);
+ break;
+
+ case GIMP_BUTTON_PRESS_TRIPLE:
+ text_tool->select_lines = TRUE;
+
+ gtk_text_iter_set_line_offset (&cursor, 0);
+ gtk_text_iter_forward_to_line_end (&selection);
+
+ gtk_text_buffer_select_range (buffer, &cursor, &selection);
+ break;
+ }
+}
+
+void
+gimp_text_tool_editor_button_release (GimpTextTool *text_tool)
+{
+ gimp_text_tool_editor_copy_selection_to_clipboard (text_tool);
+}
+
+void
+gimp_text_tool_editor_motion (GimpTextTool *text_tool,
+ gdouble x,
+ gdouble y)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter old_cursor;
+ GtkTextIter old_selection;
+ GtkTextIter cursor;
+ GtkTextIter selection;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &old_cursor,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_get_iter_at_mark (buffer, &old_selection,
+ gtk_text_buffer_get_selection_bound (buffer));
+
+ gimp_text_tool_xy_to_iter (text_tool, x, y, &cursor);
+ selection = text_tool->select_start_iter;
+
+ if (text_tool->select_words ||
+ text_tool->select_lines)
+ {
+ GtkTextIter start;
+ GtkTextIter end;
+
+ if (gtk_text_iter_compare (&cursor, &selection) < 0)
+ {
+ start = cursor;
+ end = selection;
+ }
+ else
+ {
+ start = selection;
+ end = cursor;
+ }
+
+ if (text_tool->select_words)
+ {
+ if (! gtk_text_iter_starts_word (&start))
+ gtk_text_iter_backward_visible_word_starts (&start, 1);
+
+ if (! gtk_text_iter_ends_word (&end) &&
+ ! gtk_text_iter_forward_visible_word_ends (&end, 1))
+ gtk_text_iter_forward_to_line_end (&end);
+ }
+ else if (text_tool->select_lines)
+ {
+ gtk_text_iter_set_line_offset (&start, 0);
+ gtk_text_iter_forward_to_line_end (&end);
+ }
+
+ if (gtk_text_iter_compare (&cursor, &selection) < 0)
+ {
+ cursor = start;
+ selection = end;
+ }
+ else
+ {
+ selection = start;
+ cursor = end;
+ }
+ }
+
+ if (! gtk_text_iter_equal (&cursor, &old_cursor) ||
+ ! gtk_text_iter_equal (&selection, &old_selection))
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
+
+ gtk_text_buffer_select_range (buffer, &cursor, &selection);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
+ }
+}
+
+gboolean
+gimp_text_tool_editor_key_press (GimpTextTool *text_tool,
+ GdkEventKey *kevent)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter cursor;
+ GtkTextIter selection;
+ gboolean retval = TRUE;
+
+ if (! gtk_widget_has_focus (shell->canvas))
+ {
+ /* The focus is in the floating style editor, and the event
+ * was not handled there, focus the canvas.
+ */
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Tab:
+ case GDK_KEY_KP_Tab:
+ case GDK_KEY_ISO_Left_Tab:
+ case GDK_KEY_Escape:
+ gtk_widget_grab_focus (shell->canvas);
+ return TRUE;
+
+ default:
+ break;
+ }
+ }
+
+ if (gtk_im_context_filter_keypress (text_tool->im_context, kevent))
+ {
+ text_tool->needs_im_reset = TRUE;
+ text_tool->x_pos = -1;
+
+ return TRUE;
+ }
+
+ gimp_text_tool_convert_gdkkeyevent (text_tool, kevent);
+
+ gimp_text_tool_ensure_proxy (text_tool);
+
+ if (gtk_bindings_activate_event (GTK_OBJECT (text_tool->proxy_text_view),
+ kevent))
+ {
+ GIMP_LOG (TEXT_EDITING, "binding handled event");
+
+ return TRUE;
+ }
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_get_iter_at_mark (buffer, &selection,
+ gtk_text_buffer_get_selection_bound (buffer));
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_text_tool_reset_im_context (text_tool);
+ gimp_text_tool_enter_text (text_tool, "\n");
+ break;
+
+ case GDK_KEY_Tab:
+ case GDK_KEY_KP_Tab:
+ case GDK_KEY_ISO_Left_Tab:
+ gimp_text_tool_reset_im_context (text_tool);
+ gimp_text_tool_enter_text (text_tool, "\t");
+ break;
+
+ case GDK_KEY_Escape:
+ gimp_tool_control (GIMP_TOOL (text_tool), GIMP_TOOL_ACTION_HALT,
+ GIMP_TOOL (text_tool)->display);
+ break;
+
+ default:
+ retval = FALSE;
+ }
+
+ text_tool->x_pos = -1;
+
+ return retval;
+}
+
+gboolean
+gimp_text_tool_editor_key_release (GimpTextTool *text_tool,
+ GdkEventKey *kevent)
+{
+ if (gtk_im_context_filter_keypress (text_tool->im_context, kevent))
+ {
+ text_tool->needs_im_reset = TRUE;
+
+ return TRUE;
+ }
+
+ gimp_text_tool_convert_gdkkeyevent (text_tool, kevent);
+
+ gimp_text_tool_ensure_proxy (text_tool);
+
+ if (gtk_bindings_activate_event (GTK_OBJECT (text_tool->proxy_text_view),
+ kevent))
+ {
+ GIMP_LOG (TEXT_EDITING, "binding handled event");
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_text_tool_reset_im_context (GimpTextTool *text_tool)
+{
+ if (text_tool->needs_im_reset)
+ {
+ text_tool->needs_im_reset = FALSE;
+ gtk_im_context_reset (text_tool->im_context);
+ }
+}
+
+void
+gimp_text_tool_abort_im_context (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ text_tool->needs_im_reset = TRUE;
+ gimp_text_tool_reset_im_context (text_tool);
+
+ /* Making sure preedit text is removed. */
+ gimp_text_tool_im_delete_preedit (text_tool);
+
+ /* the following lines seem to be the only way of really getting
+ * rid of any ongoing preedit state, please somebody tell me
+ * a clean way... mitch
+ */
+
+ gtk_im_context_focus_out (text_tool->im_context);
+ gtk_im_context_set_client_window (text_tool->im_context, NULL);
+
+ g_object_unref (text_tool->im_context);
+ text_tool->im_context = gtk_im_multicontext_new ();
+ gtk_im_context_set_client_window (text_tool->im_context,
+ gtk_widget_get_window (shell->canvas));
+ gtk_im_context_focus_in (text_tool->im_context);
+ g_signal_connect (text_tool->im_context, "preedit-start",
+ G_CALLBACK (gimp_text_tool_im_preedit_start),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "preedit-end",
+ G_CALLBACK (gimp_text_tool_im_preedit_end),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "preedit-changed",
+ G_CALLBACK (gimp_text_tool_im_preedit_changed),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "commit",
+ G_CALLBACK (gimp_text_tool_im_commit),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "retrieve-surrounding",
+ G_CALLBACK (gimp_text_tool_im_retrieve_surrounding),
+ text_tool);
+ g_signal_connect (text_tool->im_context, "delete-surrounding",
+ G_CALLBACK (gimp_text_tool_im_delete_surrounding),
+ text_tool);
+}
+
+void
+gimp_text_tool_editor_get_cursor_rect (GimpTextTool *text_tool,
+ gboolean overwrite,
+ PangoRectangle *cursor_rect)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ PangoLayout *layout;
+ PangoContext *context;
+ gint offset_x;
+ gint offset_y;
+ GtkTextIter cursor;
+ gint cursor_index;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+ g_return_if_fail (cursor_rect != NULL);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+ cursor_index = gimp_text_buffer_get_iter_index (text_tool->buffer, &cursor,
+ TRUE);
+
+ gimp_text_tool_ensure_layout (text_tool);
+
+ layout = gimp_text_layout_get_pango_layout (text_tool->layout);
+
+ context = pango_layout_get_context (layout);
+
+ gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
+
+ if (overwrite)
+ {
+ pango_layout_index_to_pos (layout, cursor_index, cursor_rect);
+
+ /* pango_layout_index_to_pos() returns wrong position, if gravity is west
+ * and cursor is at end of line. Avoid this behavior. (pango 1.42.1)
+ */
+ if (pango_context_get_base_gravity (context) == PANGO_GRAVITY_WEST &&
+ cursor_rect->width == 0)
+ pango_layout_get_cursor_pos (layout, cursor_index, cursor_rect, NULL);
+ }
+ else
+ pango_layout_get_cursor_pos (layout, cursor_index, cursor_rect, NULL);
+
+ gimp_text_layout_transform_rect (text_tool->layout, cursor_rect);
+
+ switch (gimp_text_tool_get_direction (text_tool))
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ cursor_rect->x = PANGO_PIXELS (cursor_rect->x) + offset_x;
+ cursor_rect->y = PANGO_PIXELS (cursor_rect->y) + offset_y;
+ cursor_rect->width = PANGO_PIXELS (cursor_rect->width);
+ cursor_rect->height = PANGO_PIXELS (cursor_rect->height);
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ {
+ gint temp, width, height;
+
+ gimp_text_layout_get_size (text_tool->layout, &width, &height);
+
+ temp = cursor_rect->x;
+ cursor_rect->x = width - PANGO_PIXELS (cursor_rect->y) + offset_x;
+ cursor_rect->y = PANGO_PIXELS (temp) + offset_y;
+
+ temp = cursor_rect->width;
+ cursor_rect->width = PANGO_PIXELS (cursor_rect->height);
+ cursor_rect->height = PANGO_PIXELS (temp);
+ }
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ {
+ gint temp, width, height;
+
+ gimp_text_layout_get_size (text_tool->layout, &width, &height);
+
+ temp = cursor_rect->x;
+ cursor_rect->x = PANGO_PIXELS (cursor_rect->y) + offset_x;
+ cursor_rect->y = height - PANGO_PIXELS (temp) + offset_y;
+
+ temp = cursor_rect->width;
+ cursor_rect->width = PANGO_PIXELS (cursor_rect->height);
+ cursor_rect->height = PANGO_PIXELS (temp);
+ }
+ break;
+ }
+}
+
+void
+gimp_text_tool_editor_update_im_cursor (GimpTextTool *text_tool)
+{
+ GimpDisplayShell *shell;
+ PangoRectangle rect = { 0, };
+ gdouble off_x, off_y;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+
+ shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
+
+ if (text_tool->text)
+ gimp_text_tool_editor_get_cursor_rect (text_tool,
+ text_tool->overwrite_mode,
+ &rect);
+
+ g_object_get (text_tool->widget,
+ "x1", &off_x,
+ "y1", &off_y,
+ NULL);
+
+ rect.x += off_x;
+ rect.y += off_y;
+
+ gimp_display_shell_transform_xy (shell, rect.x, rect.y, &rect.x, &rect.y);
+
+ gtk_im_context_set_cursor_location (text_tool->im_context,
+ (GdkRectangle *) &rect);
+}
+
+
+/* private functions */
+
+static void
+gimp_text_tool_ensure_proxy (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+
+ if (text_tool->offscreen_window &&
+ gtk_widget_get_screen (text_tool->offscreen_window) !=
+ gtk_widget_get_screen (GTK_WIDGET (shell)))
+ {
+ gtk_window_set_screen (GTK_WINDOW (text_tool->offscreen_window),
+ gtk_widget_get_screen (GTK_WIDGET (shell)));
+ gtk_window_move (GTK_WINDOW (text_tool->offscreen_window), -200, -200);
+ }
+ else if (! text_tool->offscreen_window)
+ {
+ text_tool->offscreen_window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_screen (GTK_WINDOW (text_tool->offscreen_window),
+ gtk_widget_get_screen (GTK_WIDGET (shell)));
+ gtk_window_move (GTK_WINDOW (text_tool->offscreen_window), -200, -200);
+ gtk_widget_show (text_tool->offscreen_window);
+
+ text_tool->proxy_text_view = gimp_text_proxy_new ();
+ gtk_container_add (GTK_CONTAINER (text_tool->offscreen_window),
+ text_tool->proxy_text_view);
+ gtk_widget_show (text_tool->proxy_text_view);
+
+ g_signal_connect_swapped (text_tool->proxy_text_view, "move-cursor",
+ G_CALLBACK (gimp_text_tool_move_cursor),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "insert-at-cursor",
+ G_CALLBACK (gimp_text_tool_insert_at_cursor),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "delete-from-cursor",
+ G_CALLBACK (gimp_text_tool_delete_from_cursor),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "backspace",
+ G_CALLBACK (gimp_text_tool_backspace),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "cut-clipboard",
+ G_CALLBACK (gimp_text_tool_cut_clipboard),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "copy-clipboard",
+ G_CALLBACK (gimp_text_tool_copy_clipboard),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "paste-clipboard",
+ G_CALLBACK (gimp_text_tool_paste_clipboard),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "toggle-overwrite",
+ G_CALLBACK (gimp_text_tool_toggle_overwrite),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "select-all",
+ G_CALLBACK (gimp_text_tool_select_all),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "change-size",
+ G_CALLBACK (gimp_text_tool_change_size),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "change-baseline",
+ G_CALLBACK (gimp_text_tool_change_baseline),
+ text_tool);
+ g_signal_connect_swapped (text_tool->proxy_text_view, "change-kerning",
+ G_CALLBACK (gimp_text_tool_change_kerning),
+ text_tool);
+ }
+}
+
+static void
+gimp_text_tool_move_cursor (GimpTextTool *text_tool,
+ GtkMovementStep step,
+ gint count,
+ gboolean extend_selection)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter cursor;
+ GtkTextIter selection;
+ GtkTextIter *sel_start;
+ gboolean cancel_selection = FALSE;
+ gint x_pos = -1;
+
+ if (text_tool->pending)
+ {
+ /* If there are any pending text commits, there would be
+ * inconsistencies between the text_tool->buffer and layout.
+ * This could result in crashes. See bug 751333.
+ * Therefore we apply them first.
+ */
+ gimp_text_tool_apply (text_tool, TRUE);
+ }
+ GIMP_LOG (TEXT_EDITING, "%s count = %d, select = %s",
+ g_enum_get_value (g_type_class_ref (GTK_TYPE_MOVEMENT_STEP),
+ step)->value_name,
+ count,
+ extend_selection ? "TRUE" : "FALSE");
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_get_iter_at_mark (buffer, &selection,
+ gtk_text_buffer_get_selection_bound (buffer));
+
+ if (extend_selection)
+ {
+ sel_start = &selection;
+ }
+ else
+ {
+ /* when there is a selection, moving the cursor without
+ * extending it should move the cursor to the end of the
+ * selection that is in moving direction
+ */
+ if (count > 0)
+ gtk_text_iter_order (&selection, &cursor);
+ else
+ gtk_text_iter_order (&cursor, &selection);
+
+ sel_start = &cursor;
+
+ /* if we actually have a selection, just move *to* the beginning/end
+ * of the selection and not *from* there on LOGICAL_POSITIONS
+ * and VISUAL_POSITIONS movement
+ */
+ if (! gtk_text_iter_equal (&cursor, &selection))
+ cancel_selection = TRUE;
+ }
+
+ switch (step)
+ {
+ case GTK_MOVEMENT_LOGICAL_POSITIONS:
+ if (! cancel_selection)
+ gtk_text_iter_forward_visible_cursor_positions (&cursor, count);
+ break;
+
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ if (! cancel_selection)
+ {
+ PangoLayout *layout;
+ const gchar *text;
+
+ if (! gimp_text_tool_ensure_layout (text_tool))
+ break;
+
+ layout = gimp_text_layout_get_pango_layout (text_tool->layout);
+ text = pango_layout_get_text (layout);
+
+ while (count != 0)
+ {
+ const gunichar word_joiner = 8288; /*g_utf8_get_char(WORD_JOINER);*/
+ gint index;
+ gint trailing = 0;
+ gint new_index;
+
+ index = gimp_text_buffer_get_iter_index (text_tool->buffer,
+ &cursor, TRUE);
+
+ if (count > 0)
+ {
+ if (g_utf8_get_char (text + index) == word_joiner)
+ pango_layout_move_cursor_visually (layout, TRUE,
+ index, 0, 1,
+ &new_index, &trailing);
+ else
+ new_index = index;
+
+ pango_layout_move_cursor_visually (layout, TRUE,
+ new_index, trailing, 1,
+ &new_index, &trailing);
+ count--;
+ }
+ else
+ {
+ pango_layout_move_cursor_visually (layout, TRUE,
+ index, 0, -1,
+ &new_index, &trailing);
+
+ if (new_index != -1 && new_index != G_MAXINT &&
+ g_utf8_get_char (text + new_index) == word_joiner)
+ {
+ pango_layout_move_cursor_visually (layout, TRUE,
+ new_index, trailing, -1,
+ &new_index, &trailing);
+ }
+
+ count++;
+ }
+
+ if (new_index != G_MAXINT && new_index != -1)
+ index = new_index;
+ else
+ break;
+
+ gimp_text_buffer_get_iter_at_index (text_tool->buffer,
+ &cursor, index, TRUE);
+ gtk_text_iter_forward_chars (&cursor, trailing);
+ }
+ }
+ break;
+
+ case GTK_MOVEMENT_WORDS:
+ if (count < 0)
+ {
+ gtk_text_iter_backward_visible_word_starts (&cursor, -count);
+ }
+ else if (count > 0)
+ {
+ if (! gtk_text_iter_forward_visible_word_ends (&cursor, count))
+ gtk_text_iter_forward_to_line_end (&cursor);
+ }
+ break;
+
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ {
+ GtkTextIter start;
+ GtkTextIter end;
+ gint cursor_index;
+ PangoLayout *layout;
+ PangoLayoutLine *layout_line;
+ PangoLayoutIter *layout_iter;
+ PangoRectangle logical;
+ gint line;
+ gint trailing;
+ gint i;
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+
+ cursor_index = gimp_text_buffer_get_iter_index (text_tool->buffer,
+ &cursor, TRUE);
+
+ if (! gimp_text_tool_ensure_layout (text_tool))
+ break;
+
+ layout = gimp_text_layout_get_pango_layout (text_tool->layout);
+
+ pango_layout_index_to_line_x (layout, cursor_index, FALSE,
+ &line, &x_pos);
+
+ layout_iter = pango_layout_get_iter (layout);
+ for (i = 0; i < line; i++)
+ pango_layout_iter_next_line (layout_iter);
+
+ pango_layout_iter_get_line_extents (layout_iter, NULL, &logical);
+
+ x_pos += logical.x;
+
+ pango_layout_iter_free (layout_iter);
+
+ /* try to go to the remembered x_pos if it exists *and* we are at
+ * the beginning or at the end of the current line
+ */
+ if (text_tool->x_pos != -1 && (x_pos <= logical.x ||
+ x_pos >= logical.x + logical.width))
+ x_pos = text_tool->x_pos;
+
+ line += count;
+
+ if (line < 0)
+ {
+ cursor = start;
+ break;
+ }
+ else if (line >= pango_layout_get_line_count (layout))
+ {
+ cursor = end;
+ break;
+ }
+
+ layout_iter = pango_layout_get_iter (layout);
+ for (i = 0; i < line; i++)
+ pango_layout_iter_next_line (layout_iter);
+
+ layout_line = pango_layout_iter_get_line_readonly (layout_iter);
+ pango_layout_iter_get_line_extents (layout_iter, NULL, &logical);
+
+ pango_layout_iter_free (layout_iter);
+
+ pango_layout_line_x_to_index (layout_line, x_pos - logical.x,
+ &cursor_index, &trailing);
+
+ gimp_text_buffer_get_iter_at_index (text_tool->buffer, &cursor,
+ cursor_index, TRUE);
+
+ while (trailing--)
+ gtk_text_iter_forward_char (&cursor);
+ }
+ break;
+
+ case GTK_MOVEMENT_PAGES: /* well... */
+ case GTK_MOVEMENT_BUFFER_ENDS:
+ if (count < 0)
+ {
+ gtk_text_buffer_get_start_iter (buffer, &cursor);
+ }
+ else if (count > 0)
+ {
+ gtk_text_buffer_get_end_iter (buffer, &cursor);
+ }
+ break;
+
+ case GTK_MOVEMENT_PARAGRAPH_ENDS:
+ if (count < 0)
+ {
+ gtk_text_iter_set_line_offset (&cursor, 0);
+ }
+ else if (count > 0)
+ {
+ if (! gtk_text_iter_ends_line (&cursor))
+ gtk_text_iter_forward_to_line_end (&cursor);
+ }
+ break;
+
+ case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+ if (count < 0)
+ {
+ gtk_text_iter_set_line_offset (&cursor, 0);
+ }
+ else if (count > 0)
+ {
+ if (! gtk_text_iter_ends_line (&cursor))
+ gtk_text_iter_forward_to_line_end (&cursor);
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ text_tool->x_pos = x_pos;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
+
+ gimp_text_tool_reset_im_context (text_tool);
+
+ gtk_text_buffer_select_range (buffer, &cursor, sel_start);
+ gimp_text_tool_editor_copy_selection_to_clipboard (text_tool);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
+}
+
+static void
+gimp_text_tool_insert_at_cursor (GimpTextTool *text_tool,
+ const gchar *str)
+{
+ gimp_text_buffer_insert (text_tool->buffer, str);
+}
+
+static gboolean
+is_whitespace (gunichar ch,
+ gpointer user_data)
+{
+ return (ch == ' ' || ch == '\t');
+}
+
+static gboolean
+is_not_whitespace (gunichar ch,
+ gpointer user_data)
+{
+ return ! is_whitespace (ch, user_data);
+}
+
+static gboolean
+find_whitepace_region (const GtkTextIter *center,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ *start = *center;
+ *end = *center;
+
+ if (gtk_text_iter_backward_find_char (start, is_not_whitespace, NULL, NULL))
+ gtk_text_iter_forward_char (start); /* we want the first whitespace... */
+
+ if (is_whitespace (gtk_text_iter_get_char (end), NULL))
+ gtk_text_iter_forward_find_char (end, is_not_whitespace, NULL, NULL);
+
+ return ! gtk_text_iter_equal (start, end);
+}
+
+static void
+gimp_text_tool_delete_from_cursor (GimpTextTool *text_tool,
+ GtkDeleteType type,
+ gint count)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter cursor;
+ GtkTextIter end;
+
+ GIMP_LOG (TEXT_EDITING, "%s count = %d",
+ g_enum_get_value (g_type_class_ref (GTK_TYPE_DELETE_TYPE),
+ type)->value_name,
+ count);
+
+ gimp_text_tool_reset_im_context (text_tool);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+ end = cursor;
+
+ switch (type)
+ {
+ case GTK_DELETE_CHARS:
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ return;
+ }
+ else
+ {
+ gtk_text_iter_forward_cursor_positions (&end, count);
+ }
+ break;
+
+ case GTK_DELETE_WORD_ENDS:
+ if (count < 0)
+ {
+ if (! gtk_text_iter_starts_word (&cursor))
+ gtk_text_iter_backward_visible_word_starts (&cursor, 1);
+ }
+ else if (count > 0)
+ {
+ if (! gtk_text_iter_ends_word (&end) &&
+ ! gtk_text_iter_forward_visible_word_ends (&end, 1))
+ gtk_text_iter_forward_to_line_end (&end);
+ }
+ break;
+
+ case GTK_DELETE_WORDS:
+ if (! gtk_text_iter_starts_word (&cursor))
+ gtk_text_iter_backward_visible_word_starts (&cursor, 1);
+
+ if (! gtk_text_iter_ends_word (&end) &&
+ ! gtk_text_iter_forward_visible_word_ends (&end, 1))
+ gtk_text_iter_forward_to_line_end (&end);
+ break;
+
+ case GTK_DELETE_DISPLAY_LINES:
+ break;
+
+ case GTK_DELETE_DISPLAY_LINE_ENDS:
+ break;
+
+ case GTK_DELETE_PARAGRAPH_ENDS:
+ if (count < 0)
+ {
+ gtk_text_iter_set_line_offset (&cursor, 0);
+ }
+ else if (count > 0)
+ {
+ if (! gtk_text_iter_ends_line (&end))
+ gtk_text_iter_forward_to_line_end (&end);
+ else
+ gtk_text_iter_forward_cursor_positions (&end, 1);
+ }
+ break;
+
+ case GTK_DELETE_PARAGRAPHS:
+ break;
+
+ case GTK_DELETE_WHITESPACE:
+ find_whitepace_region (&cursor, &cursor, &end);
+ break;
+ }
+
+ if (! gtk_text_iter_equal (&cursor, &end))
+ {
+ gtk_text_buffer_delete_interactive (buffer, &cursor, &end, TRUE);
+ }
+}
+
+static void
+gimp_text_tool_backspace (GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+
+ gimp_text_tool_reset_im_context (text_tool);
+
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ }
+ else
+ {
+ GtkTextIter cursor;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+
+ gtk_text_buffer_backspace (buffer, &cursor, TRUE, TRUE);
+ }
+}
+
+static void
+gimp_text_tool_toggle_overwrite (GimpTextTool *text_tool)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
+
+ text_tool->overwrite_mode = ! text_tool->overwrite_mode;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
+}
+
+static void
+gimp_text_tool_select_all (GimpTextTool *text_tool,
+ gboolean select)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
+
+ if (select)
+ {
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ gtk_text_buffer_select_range (buffer, &start, &end);
+ }
+ else
+ {
+ GtkTextIter cursor;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &cursor);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
+}
+
+static void
+gimp_text_tool_change_size (GimpTextTool *text_tool,
+ gdouble amount)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter start;
+ GtkTextIter end;
+
+ if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+ {
+ return;
+ }
+
+ gtk_text_iter_order (&start, &end);
+ gimp_text_buffer_change_size (text_tool->buffer, &start, &end,
+ amount * PANGO_SCALE);
+}
+
+static void
+gimp_text_tool_change_baseline (GimpTextTool *text_tool,
+ gdouble amount)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter start;
+ GtkTextIter end;
+
+ if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+ {
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_get_end_iter (buffer, &end);
+ }
+
+ gtk_text_iter_order (&start, &end);
+ gimp_text_buffer_change_baseline (text_tool->buffer, &start, &end,
+ amount * PANGO_SCALE);
+}
+
+static void
+gimp_text_tool_change_kerning (GimpTextTool *text_tool,
+ gdouble amount)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter start;
+ GtkTextIter end;
+
+ if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+ {
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ gtk_text_buffer_get_insert (buffer));
+ end = start;
+ gtk_text_iter_forward_char (&end);
+ }
+
+ gtk_text_iter_order (&start, &end);
+ gimp_text_buffer_change_kerning (text_tool->buffer, &start, &end,
+ amount * PANGO_SCALE);
+}
+
+static void
+gimp_text_tool_options_notify (GimpTextOptions *options,
+ GParamSpec *pspec,
+ GimpTextTool *text_tool)
+{
+ const gchar *param_name = g_param_spec_get_name (pspec);
+
+ if (! strcmp (param_name, "use-editor"))
+ {
+ if (options->use_editor)
+ {
+ gimp_text_tool_editor_dialog (text_tool);
+ }
+ else
+ {
+ if (text_tool->editor_dialog)
+ gtk_widget_destroy (text_tool->editor_dialog);
+ }
+ }
+}
+
+static void
+gimp_text_tool_editor_dialog (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpImageWindow *image_window;
+ GimpDialogFactory *dialog_factory;
+ GtkWindow *parent = NULL;
+ gdouble xres = 1.0;
+ gdouble yres = 1.0;
+
+ if (text_tool->editor_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (text_tool->editor_dialog));
+ return;
+ }
+
+ image_window = gimp_display_shell_get_window (shell);
+ dialog_factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window));
+
+ if (text_tool->image)
+ gimp_image_get_resolution (text_tool->image, &xres, &yres);
+
+ text_tool->editor_dialog =
+ gimp_text_options_editor_new (parent, tool->tool_info->gimp, options,
+ gimp_dialog_factory_get_menu_factory (dialog_factory),
+ _("GIMP Text Editor"),
+ text_tool->proxy, text_tool->buffer,
+ xres, yres);
+
+ g_object_add_weak_pointer (G_OBJECT (text_tool->editor_dialog),
+ (gpointer) &text_tool->editor_dialog);
+
+ gimp_dialog_factory_add_foreign (dialog_factory,
+ "gimp-text-tool-dialog",
+ text_tool->editor_dialog,
+ gtk_widget_get_screen (GTK_WIDGET (image_window)),
+ gimp_widget_get_monitor (GTK_WIDGET (image_window)));
+
+ g_signal_connect (text_tool->editor_dialog, "destroy",
+ G_CALLBACK (gimp_text_tool_editor_destroy),
+ text_tool);
+
+ gtk_widget_show (text_tool->editor_dialog);
+}
+
+static void
+gimp_text_tool_editor_destroy (GtkWidget *dialog,
+ GimpTextTool *text_tool)
+{
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+
+ g_object_set (options,
+ "use-editor", FALSE,
+ NULL);
+}
+
+static void
+gimp_text_tool_enter_text (GimpTextTool *text_tool,
+ const gchar *str)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GList *insert_tags = NULL;
+ GList *remove_tags = NULL;
+ gboolean had_selection;
+
+ had_selection = gtk_text_buffer_get_has_selection (buffer);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ if (had_selection && text_tool->style_editor)
+ insert_tags = gimp_text_style_editor_list_tags (GIMP_TEXT_STYLE_EDITOR (text_tool->style_editor),
+ &remove_tags);
+
+ gimp_text_tool_delete_selection (text_tool);
+
+ if (! had_selection && text_tool->overwrite_mode && strcmp (str, "\n"))
+ {
+ GtkTextIter cursor;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+
+ if (! gtk_text_iter_ends_line (&cursor))
+ gimp_text_tool_delete_from_cursor (text_tool, GTK_DELETE_CHARS, 1);
+ }
+
+ if (had_selection && text_tool->style_editor)
+ gimp_text_buffer_set_insert_tags (text_tool->buffer,
+ insert_tags, remove_tags);
+
+ gimp_text_buffer_insert (text_tool->buffer, str);
+
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+static void
+gimp_text_tool_xy_to_iter (GimpTextTool *text_tool,
+ gdouble x,
+ gdouble y,
+ GtkTextIter *iter)
+{
+ PangoLayout *layout;
+ gint offset_x;
+ gint offset_y;
+ gint index;
+ gint trailing;
+
+ gimp_text_tool_ensure_layout (text_tool);
+
+ gimp_text_layout_untransform_point (text_tool->layout, &x, &y);
+
+ gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
+ x -= offset_x;
+ y -= offset_y;
+
+ layout = gimp_text_layout_get_pango_layout (text_tool->layout);
+
+ gimp_text_tool_fix_position (text_tool, &x, &y);
+
+ pango_layout_xy_to_index (layout,
+ x * PANGO_SCALE,
+ y * PANGO_SCALE,
+ &index, &trailing);
+
+ gimp_text_buffer_get_iter_at_index (text_tool->buffer, iter, index, TRUE);
+
+ if (trailing)
+ gtk_text_iter_forward_char (iter);
+}
+
+static void
+gimp_text_tool_im_preedit_start (GtkIMContext *context,
+ GimpTextTool *text_tool)
+{
+ GIMP_LOG (TEXT_EDITING, "preedit start");
+
+ text_tool->preedit_active = TRUE;
+}
+
+static void
+gimp_text_tool_im_preedit_end (GtkIMContext *context,
+ GimpTextTool *text_tool)
+{
+ gimp_text_tool_delete_selection (text_tool);
+
+ text_tool->preedit_active = FALSE;
+
+ GIMP_LOG (TEXT_EDITING, "preedit end");
+}
+
+static void
+gimp_text_tool_im_preedit_changed (GtkIMContext *context,
+ GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ PangoAttrList *attrs;
+
+ GIMP_LOG (TEXT_EDITING, "preedit changed");
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ gimp_text_tool_im_delete_preedit (text_tool);
+
+ gimp_text_tool_delete_selection (text_tool);
+
+ gtk_im_context_get_preedit_string (context,
+ &text_tool->preedit_string, &attrs,
+ &text_tool->preedit_cursor);
+
+ if (text_tool->preedit_string && *text_tool->preedit_string)
+ {
+ PangoAttrIterator *attr_iter;
+ GtkTextIter iter;
+ gint i;
+
+ /* Save the preedit start position. */
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+ gtk_text_buffer_get_insert (buffer));
+ text_tool->preedit_start = gtk_text_buffer_create_mark (buffer,
+ "preedit-start",
+ &iter, TRUE);
+
+ /* Loop through chunks of preedit text with different attributes. */
+ attr_iter = pango_attr_list_get_iterator (attrs);
+ do
+ {
+ gint attr_start;
+ gint attr_end;
+
+ pango_attr_iterator_range (attr_iter, &attr_start, &attr_end);
+ if (attr_start < strlen (text_tool->preedit_string))
+ {
+ GSList *attrs_pos;
+ GtkTextMark *start_mark;
+ GtkTextIter start;
+ GtkTextIter end;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ gtk_text_buffer_get_insert (buffer));
+ start_mark = gtk_text_buffer_create_mark (buffer,
+ NULL,
+ &start, TRUE);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ /* Insert the preedit chunk at current cursor position. */
+ gtk_text_buffer_insert_at_cursor (GTK_TEXT_BUFFER (text_tool->buffer),
+ text_tool->preedit_string + attr_start,
+ attr_end - attr_start);
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ start_mark);
+ gtk_text_buffer_delete_mark (buffer, start_mark);
+ gtk_text_buffer_get_iter_at_mark (buffer, &end,
+ gtk_text_buffer_get_insert (buffer));
+
+ /* Apply text attributes to preedit text. */
+ attrs_pos = pango_attr_iterator_get_attrs (attr_iter);
+ while (attrs_pos)
+ {
+ PangoAttribute *attr = attrs_pos->data;
+
+ if (attr)
+ {
+ switch (attr->klass->type)
+ {
+ case PANGO_ATTR_UNDERLINE:
+ gtk_text_buffer_apply_tag (buffer,
+ text_tool->buffer->preedit_underline_tag,
+ &start, &end);
+ break;
+ case PANGO_ATTR_BACKGROUND:
+ case PANGO_ATTR_FOREGROUND:
+ {
+ PangoAttrColor *color_attr = (PangoAttrColor *) attr;
+ GimpRGB color;
+
+ color.r = (gdouble) color_attr->color.red / 65535.0;
+ color.g = (gdouble) color_attr->color.green / 65535.0;
+ color.b = (gdouble) color_attr->color.blue / 65535.0;
+
+ if (attr->klass->type == PANGO_ATTR_BACKGROUND)
+ {
+ gimp_text_buffer_set_preedit_bg_color (text_tool->buffer,
+ &start, &end,
+ &color);
+ }
+ else
+ {
+ gimp_text_buffer_set_preedit_color (text_tool->buffer,
+ &start, &end,
+ &color);
+ }
+ }
+ break;
+ default:
+ /* Unsupported tags. */
+ break;
+ }
+ }
+
+ attrs_pos = attrs_pos->next;
+ }
+
+ gtk_text_buffer_end_user_action (buffer);
+ }
+ }
+ while (pango_attr_iterator_next (attr_iter));
+
+ /* Save the preedit end position. */
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+ gtk_text_buffer_get_insert (buffer));
+ text_tool->preedit_end = gtk_text_buffer_create_mark (buffer,
+ "preedit-end",
+ &iter, FALSE);
+
+ /* Move the cursor to the expected location. */
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, text_tool->preedit_start);
+ for (i = 0; i < text_tool->preedit_cursor; i++)
+ gtk_text_iter_forward_char (&iter);
+ gtk_text_buffer_place_cursor (buffer, &iter);
+
+ pango_attr_iterator_destroy (attr_iter);
+ }
+
+ pango_attr_list_unref (attrs);
+
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+static void
+gimp_text_tool_im_commit (GtkIMContext *context,
+ const gchar *str,
+ GimpTextTool *text_tool)
+{
+ gboolean preedit_active = text_tool->preedit_active;
+
+ gimp_text_tool_im_delete_preedit (text_tool);
+
+ /* Some IMEs would emit a preedit-commit before preedit-end.
+ * To keep undo consistency, we fake and end then immediate restart of
+ * preediting.
+ */
+ if (preedit_active)
+ gimp_text_tool_im_preedit_end (context, text_tool);
+
+ gimp_text_tool_enter_text (text_tool, str);
+
+ if (preedit_active)
+ gimp_text_tool_im_preedit_start (context, text_tool);
+}
+
+static gboolean
+gimp_text_tool_im_retrieve_surrounding (GtkIMContext *context,
+ GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter start;
+ GtkTextIter end;
+ gint pos;
+ gchar *text;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ gtk_text_buffer_get_insert (buffer));
+ end = start;
+
+ pos = gtk_text_iter_get_line_index (&start);
+ gtk_text_iter_set_line_offset (&start, 0);
+ gtk_text_iter_forward_to_line_end (&end);
+
+ text = gtk_text_iter_get_slice (&start, &end);
+ gtk_im_context_set_surrounding (context, text, -1, pos);
+ g_free (text);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_text_tool_im_delete_surrounding (GtkIMContext *context,
+ gint offset,
+ gint n_chars,
+ GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter start;
+ GtkTextIter end;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ gtk_text_buffer_get_insert (buffer));
+ end = start;
+
+ gtk_text_iter_forward_chars (&start, offset);
+ gtk_text_iter_forward_chars (&end, offset + n_chars);
+
+ gtk_text_buffer_delete_interactive (buffer, &start, &end, TRUE);
+
+ return TRUE;
+}
+
+static void
+gimp_text_tool_im_delete_preedit (GimpTextTool *text_tool)
+{
+ if (text_tool->preedit_string)
+ {
+ if (*text_tool->preedit_string)
+ {
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GtkTextIter start;
+ GtkTextIter end;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &start,
+ text_tool->preedit_start);
+ gtk_text_buffer_get_iter_at_mark (buffer, &end,
+ text_tool->preedit_end);
+
+ gtk_text_buffer_delete_interactive (buffer, &start, &end, TRUE);
+
+ gtk_text_buffer_delete_mark (buffer, text_tool->preedit_start);
+ gtk_text_buffer_delete_mark (buffer, text_tool->preedit_end);
+ text_tool->preedit_start = NULL;
+ text_tool->preedit_end = NULL;
+ }
+
+ g_clear_pointer (&text_tool->preedit_string, g_free);
+ }
+}
+
+static void
+gimp_text_tool_editor_copy_selection_to_clipboard (GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+
+ if (! text_tool->editor_dialog &&
+ gtk_text_buffer_get_has_selection (buffer))
+ {
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
+ GDK_SELECTION_PRIMARY);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+ }
+}
+
+static void
+gimp_text_tool_fix_position (GimpTextTool *text_tool,
+ gdouble *x,
+ gdouble *y)
+{
+ gint temp, width, height;
+
+ gimp_text_layout_get_size (text_tool->layout, &width, &height);
+ switch (gimp_text_tool_get_direction(text_tool))
+ {
+ case GIMP_TEXT_DIRECTION_RTL:
+ case GIMP_TEXT_DIRECTION_LTR:
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ temp = width - *x;
+ *x = *y;
+ *y = temp;
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ temp = *x;
+ *x = height - *y;
+ *y = temp;
+ break;
+ }
+}
+
+static void
+gimp_text_tool_convert_gdkkeyevent (GimpTextTool *text_tool,
+ GdkEventKey *kevent)
+{
+ switch (gimp_text_tool_get_direction (text_tool))
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ break;
+
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+#ifdef _WIN32
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ kevent->hardware_keycode = 0x25;/* VK_LEFT */
+ kevent->keyval = GDK_KEY_Left;
+ break;
+ case GDK_KEY_Down:
+ kevent->hardware_keycode = 0x27;/* VK_RIGHT */
+ kevent->keyval = GDK_KEY_Right;
+ break;
+ case GDK_KEY_Left:
+ kevent->hardware_keycode = 0x28;/* VK_DOWN */
+ kevent->keyval = GDK_KEY_Down;
+ break;
+ case GDK_KEY_Right:
+ kevent->hardware_keycode = 0x26;/* VK_UP */
+ kevent->keyval = GDK_KEY_Up;
+ break;
+ }
+#else
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ kevent->hardware_keycode = 113;
+ kevent->keyval = GDK_KEY_Left;
+ break;
+ case GDK_KEY_Down:
+ kevent->hardware_keycode = 114;
+ kevent->keyval = GDK_KEY_Right;
+ break;
+ case GDK_KEY_Left:
+ kevent->hardware_keycode = 116;
+ kevent->keyval = GDK_KEY_Down;
+ break;
+ case GDK_KEY_Right:
+ kevent->hardware_keycode = 111;
+ kevent->keyval = GDK_KEY_Up;
+ break;
+ }
+#endif
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+#ifdef _WIN32
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ kevent->hardware_keycode = 0x26;/* VK_UP */
+ kevent->keyval = GDK_KEY_Up;
+ break;
+ case GDK_KEY_Down:
+ kevent->hardware_keycode = 0x28;/* VK_DOWN */
+ kevent->keyval = GDK_KEY_Down;
+ break;
+ case GDK_KEY_Left:
+ kevent->hardware_keycode = 0x25;/* VK_LEFT */
+ kevent->keyval = GDK_KEY_Left;
+ break;
+ case GDK_KEY_Right:
+ kevent->hardware_keycode = 0x27;/* VK_RIGHT */
+ kevent->keyval = GDK_KEY_Right;
+ break;
+ }
+#else
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ kevent->hardware_keycode = 114;
+ kevent->keyval = GDK_KEY_Right;
+ break;
+ case GDK_KEY_Down:
+ kevent->hardware_keycode = 113;
+ kevent->keyval = GDK_KEY_Left;
+ break;
+ case GDK_KEY_Left:
+ kevent->hardware_keycode = 111;
+ kevent->keyval = GDK_KEY_Up;
+ break;
+ case GDK_KEY_Right:
+ kevent->hardware_keycode = 116;
+ kevent->keyval = GDK_KEY_Down;
+ break;
+ }
+#endif
+ break;
+ }
+}
diff --git a/app/tools/gimptexttool-editor.h b/app/tools/gimptexttool-editor.h
new file mode 100644
index 0000000..e8e7d7d
--- /dev/null
+++ b/app/tools/gimptexttool-editor.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextTool
+ * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org>
+ * Daniel Eddeland <danedde@svn.gnome.org>
+ * 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_TEXT_TOOL_EDITOR_H__
+#define __GIMP_TEXT_TOOL_EDITOR_H__
+
+
+void gimp_text_tool_editor_init (GimpTextTool *text_tool);
+void gimp_text_tool_editor_finalize (GimpTextTool *text_tool);
+
+void gimp_text_tool_editor_start (GimpTextTool *text_tool);
+void gimp_text_tool_editor_position (GimpTextTool *text_tool);
+void gimp_text_tool_editor_halt (GimpTextTool *text_tool);
+
+void gimp_text_tool_editor_button_press (GimpTextTool *text_tool,
+ gdouble x,
+ gdouble y,
+ GimpButtonPressType press_type);
+void gimp_text_tool_editor_button_release (GimpTextTool *text_tool);
+void gimp_text_tool_editor_motion (GimpTextTool *text_tool,
+ gdouble x,
+ gdouble y);
+gboolean gimp_text_tool_editor_key_press (GimpTextTool *text_tool,
+ GdkEventKey *kevent);
+gboolean gimp_text_tool_editor_key_release (GimpTextTool *text_tool,
+ GdkEventKey *kevent);
+
+void gimp_text_tool_reset_im_context (GimpTextTool *text_tool);
+void gimp_text_tool_abort_im_context (GimpTextTool *text_tool);
+
+void gimp_text_tool_editor_get_cursor_rect (GimpTextTool *text_tool,
+ gboolean overwrite,
+ PangoRectangle *cursor_rect);
+void gimp_text_tool_editor_update_im_cursor (GimpTextTool *text_tool);
+
+
+#endif /* __GIMP_TEXT_TOOL_EDITOR_H__ */
diff --git a/app/tools/gimptexttool.c b/app/tools/gimptexttool.c
new file mode 100644
index 0000000..45023ab
--- /dev/null
+++ b/app/tools/gimptexttool.c
@@ -0,0 +1,2388 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextTool
+ * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org>
+ * Daniel Eddeland <danedde@svn.gnome.org>
+ * 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpasyncset.h"
+#include "core/gimpcontext.h"
+#include "core/gimpdatafactory.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimp-palettes.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpimage-undo-push.h"
+#include "core/gimplayer-floating-selection.h"
+#include "core/gimpmarshal.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimpundostack.h"
+
+#include "text/gimptext.h"
+#include "text/gimptext-vectors.h"
+#include "text/gimptextlayer.h"
+#include "text/gimptextlayout.h"
+#include "text/gimptextundo.h"
+
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpvectors-warp.h"
+
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpdockcontainer.h"
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpmenufactory.h"
+#include "widgets/gimptextbuffer.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpviewabledialog.h"
+
+#include "display/gimpcanvasgroup.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolrectangle.h"
+
+#include "gimptextoptions.h"
+#include "gimptexttool.h"
+#include "gimptexttool-editor.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+#define TEXT_UNDO_TIMEOUT 3
+
+
+/* local function prototypes */
+
+static void gimp_text_tool_constructed (GObject *object);
+static void gimp_text_tool_finalize (GObject *object);
+
+static void gimp_text_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_text_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_text_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_text_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_text_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static gboolean gimp_text_tool_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_text_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_text_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static GimpUIManager * gimp_text_tool_get_popup (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path);
+
+static void gimp_text_tool_draw (GimpDrawTool *draw_tool);
+static void gimp_text_tool_draw_selection (GimpDrawTool *draw_tool);
+
+static gboolean gimp_text_tool_start (GimpTextTool *text_tool,
+ GimpDisplay *display,
+ GimpLayer *layer,
+ GError **error);
+static void gimp_text_tool_halt (GimpTextTool *text_tool);
+
+static void gimp_text_tool_frame_item (GimpTextTool *text_tool);
+
+static void gimp_text_tool_rectangle_response
+ (GimpToolRectangle *rectangle,
+ gint response_id,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_rectangle_change_complete
+ (GimpToolRectangle *rectangle,
+ GimpTextTool *text_tool);
+
+static void gimp_text_tool_connect (GimpTextTool *text_tool,
+ GimpTextLayer *layer,
+ GimpText *text);
+
+static void gimp_text_tool_layer_notify (GimpTextLayer *layer,
+ const GParamSpec *pspec,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_proxy_notify (GimpText *text,
+ const GParamSpec *pspec,
+ GimpTextTool *text_tool);
+
+static void gimp_text_tool_text_notify (GimpText *text,
+ const GParamSpec *pspec,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_text_changed (GimpText *text,
+ GimpTextTool *text_tool);
+
+static void
+ gimp_text_tool_fonts_async_set_empty_notify (GimpAsyncSet *async_set,
+ GParamSpec *pspec,
+ GimpTextTool *text_tool);
+
+static void gimp_text_tool_apply_list (GimpTextTool *text_tool,
+ GList *pspecs);
+
+static void gimp_text_tool_create_layer (GimpTextTool *text_tool,
+ GimpText *text);
+
+static void gimp_text_tool_layer_changed (GimpImage *image,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_set_image (GimpTextTool *text_tool,
+ GimpImage *image);
+static gboolean gimp_text_tool_set_drawable (GimpTextTool *text_tool,
+ GimpDrawable *drawable,
+ gboolean confirm);
+
+static void gimp_text_tool_block_drawing (GimpTextTool *text_tool);
+static void gimp_text_tool_unblock_drawing (GimpTextTool *text_tool);
+
+static void gimp_text_tool_buffer_begin_edit (GimpTextBuffer *buffer,
+ GimpTextTool *text_tool);
+static void gimp_text_tool_buffer_end_edit (GimpTextBuffer *buffer,
+ GimpTextTool *text_tool);
+
+static void gimp_text_tool_buffer_color_applied
+ (GimpTextBuffer *buffer,
+ const GimpRGB *color,
+ GimpTextTool *text_tool);
+
+
+G_DEFINE_TYPE (GimpTextTool, gimp_text_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_text_tool_parent_class
+
+
+void
+gimp_text_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_TEXT_TOOL,
+ GIMP_TYPE_TEXT_OPTIONS,
+ gimp_text_options_gui,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_FONT |
+ GIMP_CONTEXT_PROP_MASK_PALETTE /* for the color popup's palette tab */,
+ "gimp-text-tool",
+ _("Text"),
+ _("Text Tool: Create or edit text layers"),
+ N_("Te_xt"), "T",
+ NULL, GIMP_HELP_TOOL_TEXT,
+ GIMP_ICON_TOOL_TEXT,
+ data);
+}
+
+static void
+gimp_text_tool_class_init (GimpTextToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_text_tool_constructed;
+ object_class->finalize = gimp_text_tool_finalize;
+
+ tool_class->control = gimp_text_tool_control;
+ tool_class->button_press = gimp_text_tool_button_press;
+ tool_class->motion = gimp_text_tool_motion;
+ tool_class->button_release = gimp_text_tool_button_release;
+ tool_class->key_press = gimp_text_tool_key_press;
+ tool_class->key_release = gimp_text_tool_key_release;
+ tool_class->oper_update = gimp_text_tool_oper_update;
+ tool_class->cursor_update = gimp_text_tool_cursor_update;
+ tool_class->get_popup = gimp_text_tool_get_popup;
+
+ draw_tool_class->draw = gimp_text_tool_draw;
+}
+
+static void
+gimp_text_tool_init (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+
+ text_tool->buffer = gimp_text_buffer_new ();
+
+ g_signal_connect (text_tool->buffer, "begin-user-action",
+ G_CALLBACK (gimp_text_tool_buffer_begin_edit),
+ text_tool);
+ g_signal_connect (text_tool->buffer, "end-user-action",
+ G_CALLBACK (gimp_text_tool_buffer_end_edit),
+ text_tool);
+ g_signal_connect (text_tool->buffer, "color-applied",
+ G_CALLBACK (gimp_text_tool_buffer_color_applied),
+ text_tool);
+
+ text_tool->handle_rectangle_change_complete = TRUE;
+
+ gimp_text_tool_editor_init (text_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_wants_double_click (tool->control, TRUE);
+ gimp_tool_control_set_wants_triple_click (tool->control, TRUE);
+ gimp_tool_control_set_wants_all_key_events (tool->control, TRUE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_PIXEL_BORDER);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_TEXT);
+ gimp_tool_control_set_action_object_1 (tool->control,
+ "context/context-font-select-set");
+}
+
+static void
+gimp_text_tool_constructed (GObject *object)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (object);
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpAsyncSet *async_set;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ text_tool->proxy = g_object_new (GIMP_TYPE_TEXT, NULL);
+
+ gimp_text_options_connect_text (options, text_tool->proxy);
+
+ g_signal_connect_object (text_tool->proxy, "notify",
+ G_CALLBACK (gimp_text_tool_proxy_notify),
+ text_tool, 0);
+
+ async_set =
+ gimp_data_factory_get_async_set (tool->tool_info->gimp->font_factory);
+
+ g_signal_connect_object (async_set,
+ "notify::empty",
+ G_CALLBACK (gimp_text_tool_fonts_async_set_empty_notify),
+ text_tool, 0);
+}
+
+static void
+gimp_text_tool_finalize (GObject *object)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (object);
+
+ g_clear_object (&text_tool->proxy);
+ g_clear_object (&text_tool->buffer);
+ g_clear_object (&text_tool->ui_manager);
+
+ gimp_text_tool_editor_finalize (text_tool);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_text_tool_remove_empty_text_layer (GimpTextTool *text_tool)
+{
+ GimpTextLayer *text_layer = text_tool->layer;
+
+ if (text_layer && text_layer->auto_rename)
+ {
+ GimpText *text = gimp_text_layer_get_text (text_layer);
+
+ if (text && text->box_mode == GIMP_TEXT_BOX_DYNAMIC &&
+ (! text->text || text->text[0] == '\0') &&
+ (! text->markup || text->markup[0] == '\0'))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (text_layer));
+
+ if (text_tool->image == image)
+ g_signal_handlers_block_by_func (image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+
+ gimp_image_remove_layer (image, GIMP_LAYER (text_layer), TRUE, NULL);
+ gimp_image_flush (image);
+
+ if (text_tool->image == image)
+ g_signal_handlers_unblock_by_func (image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+ }
+ }
+}
+
+static void
+gimp_text_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_text_tool_halt (text_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_text_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpText *text = text_tool->text;
+ GimpToolRectangle *rectangle;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (tool->display && tool->display != display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ if (! text_tool->widget)
+ {
+ GError *error = NULL;
+
+ if (! gimp_text_tool_start (text_tool, display, NULL, &error))
+ {
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ gimp_tool_message_literal (tool, display, error->message);
+
+ g_clear_error (&error);
+
+ return;
+ }
+
+ gimp_tool_widget_hover (text_tool->widget, coords, state, TRUE);
+
+ /* HACK: force CREATING on a newly created rectangle; otherwise,
+ * the above binding of properties would cause the rectangle to
+ * start with the size from tool options.
+ */
+ gimp_tool_rectangle_set_function (GIMP_TOOL_RECTANGLE (text_tool->widget),
+ GIMP_TOOL_RECTANGLE_CREATING);
+ }
+
+ rectangle = GIMP_TOOL_RECTANGLE (text_tool->widget);
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ gimp_tool_control_activate (tool->control);
+
+ /* clicking anywhere while a preedit is going on aborts the
+ * preedit, this is ugly but at least leaves everything in
+ * a consistent state
+ */
+ if (text_tool->preedit_active)
+ gimp_text_tool_abort_im_context (text_tool);
+ else
+ gimp_text_tool_reset_im_context (text_tool);
+
+ text_tool->selecting = FALSE;
+
+ if (gimp_tool_rectangle_point_in_rectangle (rectangle,
+ coords->x,
+ coords->y) &&
+ ! text_tool->moving)
+ {
+ gimp_tool_rectangle_set_function (rectangle,
+ GIMP_TOOL_RECTANGLE_DEAD);
+ }
+ else if (gimp_tool_widget_button_press (text_tool->widget, coords,
+ time, state, press_type))
+ {
+ text_tool->grab_widget = text_tool->widget;
+ }
+
+ /* bail out now if the user user clicked on a handle of an
+ * existing rectangle, but not inside an existing framed layer
+ */
+ if (gimp_tool_rectangle_get_function (rectangle) !=
+ GIMP_TOOL_RECTANGLE_CREATING)
+ {
+ if (text_tool->layer)
+ {
+ GimpItem *item = GIMP_ITEM (text_tool->layer);
+ gdouble x = coords->x - gimp_item_get_offset_x (item);
+ gdouble y = coords->y - gimp_item_get_offset_y (item);
+
+ if (x < 0 || x >= gimp_item_get_width (item) ||
+ y < 0 || y >= gimp_item_get_height (item))
+ {
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ return;
+ }
+ }
+ else
+ {
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ return;
+ }
+ }
+
+ /* if the the click is not related to the currently edited text
+ * layer in any way, try to pick a text layer
+ */
+ if (! text_tool->moving &&
+ gimp_tool_rectangle_get_function (rectangle) ==
+ GIMP_TOOL_RECTANGLE_CREATING)
+ {
+ GimpTextLayer *text_layer;
+
+ text_layer = gimp_image_pick_text_layer (image, coords->x, coords->y);
+
+ if (text_layer && text_layer != text_tool->layer)
+ {
+ if (text_tool->image == image)
+ g_signal_handlers_block_by_func (image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+
+ gimp_image_set_active_layer (image, GIMP_LAYER (text_layer));
+
+ if (text_tool->image == image)
+ g_signal_handlers_unblock_by_func (image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+ }
+ }
+ }
+
+ if (gimp_image_coords_in_active_pickable (image, coords, FALSE, FALSE, FALSE))
+ {
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpItem *item = GIMP_ITEM (drawable);
+ gdouble x = coords->x - gimp_item_get_offset_x (item);
+ gdouble y = coords->y - gimp_item_get_offset_y (item);
+
+ /* did the user click on a text layer? */
+ if (gimp_text_tool_set_drawable (text_tool, drawable, TRUE))
+ {
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ /* if we clicked on a text layer while the tool was idle
+ * (didn't show a rectangle), frame the layer and switch to
+ * selecting instead of drawing a new rectangle
+ */
+ if (gimp_tool_rectangle_get_function (rectangle) ==
+ GIMP_TOOL_RECTANGLE_CREATING)
+ {
+ gimp_tool_rectangle_set_function (rectangle,
+ GIMP_TOOL_RECTANGLE_DEAD);
+
+ gimp_text_tool_frame_item (text_tool);
+ }
+
+ if (text_tool->text && text_tool->text != text)
+ {
+ gimp_text_tool_editor_start (text_tool);
+ }
+ }
+
+ if (text_tool->text && ! text_tool->moving)
+ {
+ text_tool->selecting = TRUE;
+
+ gimp_text_tool_editor_button_press (text_tool, x, y, press_type);
+ }
+ else
+ {
+ text_tool->selecting = FALSE;
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ return;
+ }
+ }
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ /* create a new text layer */
+ text_tool->text_box_fixed = FALSE;
+
+ /* make sure the text tool has an image, even if the user didn't click
+ * inside the active drawable, in particular, so that the text style
+ * editor picks the correct resolution.
+ */
+ gimp_text_tool_set_image (text_tool, image);
+
+ gimp_text_tool_connect (text_tool, NULL, NULL);
+ gimp_text_tool_editor_start (text_tool);
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_text_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (text_tool->widget);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (text_tool->selecting)
+ {
+ /* we are in a selection process (user has initially clicked on
+ * an existing text layer), so finish the selection process and
+ * ignore rectangle-change-complete.
+ */
+
+ /* need to block "end-user-action" on the text buffer, because
+ * GtkTextBuffer considers copying text to the clipboard an
+ * undo-relevant user action, which is clearly a bug, but what
+ * can we do...
+ */
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+
+ gimp_text_tool_editor_button_release (text_tool);
+
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+
+ text_tool->selecting = FALSE;
+
+ text_tool->handle_rectangle_change_complete = FALSE;
+
+ /* there is no cancelling of selections yet */
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ release_type = GIMP_BUTTON_RELEASE_NORMAL;
+ }
+ else if (text_tool->moving)
+ {
+ /* the user has moved the text layer with Alt-drag, fall
+ * through and let rectangle-change-complete do its job of
+ * setting text layer's new position.
+ */
+ }
+ else if (gimp_tool_rectangle_get_function (rectangle) ==
+ GIMP_TOOL_RECTANGLE_DEAD)
+ {
+ /* the user clicked in dead space (like between the corner and
+ * edge handles, so completely ignore that.
+ */
+
+ text_tool->handle_rectangle_change_complete = FALSE;
+ }
+ else if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* user has canceled the rectangle resizing, fall through
+ * and let the rectangle handle restoring the previous size
+ */
+ }
+ else
+ {
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ /* otherwise the user has clicked outside of any text layer in
+ * order to create a new text, fall through and let
+ * rectangle-change-complete do its job of setting the new text
+ * layer's size.
+ */
+
+ g_object_get (rectangle,
+ "x1", &x1,
+ "y1", &y1,
+ "x2", &x2,
+ "y2", &y2,
+ NULL);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CLICK ||
+ (x2 - x1) < 3 ||
+ (y2 - y1) < 3)
+ {
+ /* unless the rectangle is unreasonably small to hold any
+ * real text (the user has eitherjust clicked or just made
+ * a rectangle of a few pixels), so set the text box to
+ * dynamic and ignore rectangle-change-complete.
+ */
+
+ g_object_set (text_tool->proxy,
+ "box-mode", GIMP_TEXT_BOX_DYNAMIC,
+ NULL);
+
+ text_tool->handle_rectangle_change_complete = FALSE;
+ }
+ }
+
+ if (text_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (text_tool->grab_widget,
+ coords, time, state, release_type);
+ text_tool->grab_widget = NULL;
+ }
+
+ text_tool->handle_rectangle_change_complete = TRUE;
+}
+
+static void
+gimp_text_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+
+ if (! text_tool->selecting)
+ {
+ if (text_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (text_tool->grab_widget,
+ coords, time, state);
+ }
+ }
+ else
+ {
+ GimpItem *item = GIMP_ITEM (text_tool->layer);
+ gdouble x = coords->x - gimp_item_get_offset_x (item);
+ gdouble y = coords->y - gimp_item_get_offset_y (item);
+
+ gimp_text_tool_editor_motion (text_tool, x, y);
+ }
+}
+
+static gboolean
+gimp_text_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+
+ if (display == tool->display)
+ return gimp_text_tool_editor_key_press (text_tool, kevent);
+
+ return FALSE;
+}
+
+static gboolean
+gimp_text_tool_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+
+ if (display == tool->display)
+ return gimp_text_tool_editor_key_release (text_tool, kevent);
+
+ return FALSE;
+}
+
+static void
+gimp_text_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (text_tool->widget);
+
+ GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
+ proximity, display);
+
+ text_tool->moving = (text_tool->widget &&
+ gimp_tool_rectangle_get_function (rectangle) ==
+ GIMP_TOOL_RECTANGLE_MOVING &&
+ (state & GDK_MOD1_MASK));
+}
+
+static void
+gimp_text_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (text_tool->widget);
+
+ if (rectangle && tool->display == display)
+ {
+ if (gimp_tool_rectangle_point_in_rectangle (rectangle,
+ coords->x,
+ coords->y) &&
+ ! text_tool->moving)
+ {
+ gimp_tool_set_cursor (tool, display,
+ (GimpCursorType) GDK_XTERM,
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_NONE);
+ }
+ else
+ {
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+ }
+ }
+ else
+ {
+ GimpAsyncSet *async_set;
+
+ async_set =
+ gimp_data_factory_get_async_set (tool->tool_info->gimp->font_factory);
+
+ if (gimp_async_set_is_empty (async_set))
+ {
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
+ display);
+ }
+ else
+ {
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ }
+ }
+}
+
+static GimpUIManager *
+gimp_text_tool_get_popup (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
+ GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (text_tool->widget);
+
+ if (rectangle &&
+ gimp_tool_rectangle_point_in_rectangle (rectangle,
+ coords->x,
+ coords->y))
+ {
+ if (! text_tool->ui_manager)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpImageWindow *image_window;
+ GimpDialogFactory *dialog_factory;
+ GtkWidget *im_menu;
+ GList *children;
+
+ image_window = gimp_display_shell_get_window (shell);
+ dialog_factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window));
+
+ text_tool->ui_manager =
+ gimp_menu_factory_manager_new (gimp_dialog_factory_get_menu_factory (dialog_factory),
+ "<TextTool>",
+ text_tool, FALSE);
+
+ im_menu = gimp_ui_manager_get_widget (text_tool->ui_manager,
+ "/text-tool-popup/text-tool-input-methods-menu");
+
+ if (GTK_IS_MENU_ITEM (im_menu))
+ im_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (im_menu));
+
+ /* hide the generated "empty" item */
+ children = gtk_container_get_children (GTK_CONTAINER (im_menu));
+ while (children)
+ {
+ gtk_widget_hide (children->data);
+ children = g_list_remove (children, children->data);
+ }
+
+ gtk_im_multicontext_append_menuitems (GTK_IM_MULTICONTEXT (text_tool->im_context),
+ GTK_MENU_SHELL (im_menu));
+ }
+
+ gimp_ui_manager_update (text_tool->ui_manager, text_tool);
+
+ *ui_path = "/text-tool-popup";
+
+ return text_tool->ui_manager;
+ }
+
+ return NULL;
+}
+
+static void
+gimp_text_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (draw_tool);
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+
+ if (! text_tool->text ||
+ ! text_tool->layer ||
+ ! text_tool->layer->text)
+ {
+ gimp_text_tool_editor_update_im_cursor (text_tool);
+
+ return;
+ }
+
+ gimp_text_tool_ensure_layout (text_tool);
+
+ if (gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (text_tool->buffer)))
+ {
+ /* If the text buffer has a selection, highlight the selected letters */
+
+ gimp_text_tool_draw_selection (draw_tool);
+ }
+ else
+ {
+ /* If the text buffer has no selection, draw the text cursor */
+
+ GimpCanvasItem *item;
+ PangoRectangle cursor_rect;
+ gint off_x, off_y;
+ gboolean overwrite;
+ GimpTextDirection direction;
+
+ gimp_text_tool_editor_get_cursor_rect (text_tool,
+ text_tool->overwrite_mode,
+ &cursor_rect);
+
+ gimp_item_get_offset (GIMP_ITEM (text_tool->layer), &off_x, &off_y);
+ cursor_rect.x += off_x;
+ cursor_rect.y += off_y;
+
+ overwrite = text_tool->overwrite_mode && cursor_rect.width != 0;
+
+ direction = gimp_text_tool_get_direction (text_tool);
+
+ item = gimp_draw_tool_add_text_cursor (draw_tool, &cursor_rect,
+ overwrite, direction);
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+
+ gimp_text_tool_editor_update_im_cursor (text_tool);
+}
+
+static void
+gimp_text_tool_draw_selection (GimpDrawTool *draw_tool)
+{
+ GimpTextTool *text_tool = GIMP_TEXT_TOOL (draw_tool);
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+ GimpCanvasGroup *group;
+ PangoLayout *layout;
+ gint offset_x;
+ gint offset_y;
+ gint width;
+ gint height;
+ gint off_x, off_y;
+ PangoLayoutIter *iter;
+ GtkTextIter sel_start, sel_end;
+ gint min, max;
+ gint i;
+ GimpTextDirection direction;
+
+ group = gimp_draw_tool_add_stroke_group (draw_tool);
+ gimp_canvas_item_set_highlight (GIMP_CANVAS_ITEM (group), TRUE);
+
+ gtk_text_buffer_get_selection_bounds (buffer, &sel_start, &sel_end);
+
+ min = gimp_text_buffer_get_iter_index (text_tool->buffer, &sel_start, TRUE);
+ max = gimp_text_buffer_get_iter_index (text_tool->buffer, &sel_end, TRUE);
+
+ layout = gimp_text_layout_get_pango_layout (text_tool->layout);
+
+ gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
+
+ gimp_text_layout_get_size (text_tool->layout, &width, &height);
+
+ gimp_item_get_offset (GIMP_ITEM (text_tool->layer), &off_x, &off_y);
+ offset_x += off_x;
+ offset_y += off_y;
+
+ direction = gimp_text_tool_get_direction (text_tool);
+
+ iter = pango_layout_get_iter (layout);
+
+ gimp_draw_tool_push_group (draw_tool, group);
+
+ do
+ {
+ if (! pango_layout_iter_get_run (iter))
+ continue;
+
+ i = pango_layout_iter_get_index (iter);
+
+ if (i >= min && i < max)
+ {
+ PangoRectangle rect;
+ gint ytop, ybottom;
+
+ pango_layout_iter_get_char_extents (iter, &rect);
+ pango_layout_iter_get_line_yrange (iter, &ytop, &ybottom);
+
+ rect.y = ytop;
+ rect.height = ybottom - ytop;
+
+ pango_extents_to_pixels (&rect, NULL);
+
+ gimp_text_layout_transform_rect (text_tool->layout, &rect);
+
+ switch (direction)
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ rect.x += offset_x;
+ rect.y += offset_y;
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ rect.x, rect.y,
+ rect.width, rect.height);
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_RTL:
+ case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
+ rect.y = offset_x - rect.y + width;
+ rect.x = offset_y + rect.x;
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ rect.y, rect.x,
+ -rect.height, rect.width);
+ break;
+ case GIMP_TEXT_DIRECTION_TTB_LTR:
+ case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
+ rect.y = offset_x + rect.y;
+ rect.x = offset_y - rect.x + height;
+ gimp_draw_tool_add_rectangle (draw_tool, FALSE,
+ rect.y, rect.x,
+ rect.height, -rect.width);
+ break;
+ }
+ }
+ }
+ while (pango_layout_iter_next_char (iter));
+
+ gimp_draw_tool_pop_group (draw_tool);
+
+ pango_layout_iter_free (iter);
+}
+
+static gboolean
+gimp_text_tool_start (GimpTextTool *text_tool,
+ GimpDisplay *display,
+ GimpLayer *layer,
+ GError **error)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpToolWidget *widget;
+ GimpAsyncSet *async_set;
+
+ async_set =
+ gimp_data_factory_get_async_set (tool->tool_info->gimp->font_factory);
+
+ if (! gimp_async_set_is_empty (async_set))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Fonts are still loading"));
+
+ return FALSE;
+ }
+
+ tool->display = display;
+
+ text_tool->widget = widget = gimp_tool_rectangle_new (shell);
+
+ g_object_set (widget,
+ "force-narrow-mode", TRUE,
+ "status-title", _("Text box: "),
+ NULL);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), widget);
+
+ g_signal_connect (widget, "response",
+ G_CALLBACK (gimp_text_tool_rectangle_response),
+ text_tool);
+ g_signal_connect (widget, "change-complete",
+ G_CALLBACK (gimp_text_tool_rectangle_change_complete),
+ text_tool);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+
+ if (layer)
+ {
+ gimp_text_tool_frame_item (text_tool);
+ gimp_text_tool_editor_start (text_tool);
+ gimp_text_tool_editor_position (text_tool);
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_text_tool_halt (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+
+ gimp_text_tool_editor_halt (text_tool);
+ gimp_text_tool_clear_layout (text_tool);
+ gimp_text_tool_set_drawable (text_tool, NULL, FALSE);
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
+ g_clear_object (&text_tool->widget);
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+}
+
+static void
+gimp_text_tool_frame_item (GimpTextTool *text_tool)
+{
+ g_return_if_fail (GIMP_IS_LAYER (text_tool->layer));
+
+ text_tool->handle_rectangle_change_complete = FALSE;
+
+ gimp_tool_rectangle_frame_item (GIMP_TOOL_RECTANGLE (text_tool->widget),
+ GIMP_ITEM (text_tool->layer));
+
+ text_tool->handle_rectangle_change_complete = TRUE;
+}
+
+static void
+gimp_text_tool_rectangle_response (GimpToolRectangle *rectangle,
+ gint response_id,
+ GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+
+ /* this happens when a newly created rectangle gets canceled,
+ * we have to shut down the tool
+ */
+ if (response_id == GIMP_TOOL_WIDGET_RESPONSE_CANCEL)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+}
+
+static void
+gimp_text_tool_rectangle_change_complete (GimpToolRectangle *rectangle,
+ GimpTextTool *text_tool)
+{
+ gimp_text_tool_editor_position (text_tool);
+
+ if (text_tool->handle_rectangle_change_complete)
+ {
+ GimpItem *item = GIMP_ITEM (text_tool->layer);
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ if (! item)
+ {
+ /* we can't set properties for the text layer, because it
+ * isn't created until some text has been inserted, so we
+ * need to make a special note that will remind us what to
+ * do when we actually create the layer
+ */
+ text_tool->text_box_fixed = TRUE;
+
+ return;
+ }
+
+ g_object_get (rectangle,
+ "x1", &x1,
+ "y1", &y1,
+ "x2", &x2,
+ "y2", &y2,
+ NULL);
+
+ if ((x2 - x1) != gimp_item_get_width (item) ||
+ (y2 - y1) != gimp_item_get_height (item))
+ {
+ GimpUnit box_unit = text_tool->proxy->box_unit;
+ gdouble xres, yres;
+ gboolean push_undo = TRUE;
+ GimpUndo *undo;
+
+ gimp_image_get_resolution (text_tool->image, &xres, &yres);
+
+ g_object_set (text_tool->proxy,
+ "box-mode", GIMP_TEXT_BOX_FIXED,
+ "box-width", gimp_pixels_to_units (x2 - x1,
+ box_unit, xres),
+ "box-height", gimp_pixels_to_units (y2 - y1,
+ box_unit, yres),
+ NULL);
+
+ undo = gimp_image_undo_can_compress (text_tool->image,
+ GIMP_TYPE_UNDO_STACK,
+ GIMP_UNDO_GROUP_TEXT);
+
+ if (undo &&
+ gimp_undo_get_age (undo) <= TEXT_UNDO_TIMEOUT &&
+ g_object_get_data (G_OBJECT (undo), "reshape-text-layer") == (gpointer) item)
+ push_undo = FALSE;
+
+ if (push_undo)
+ {
+ gimp_image_undo_group_start (text_tool->image, GIMP_UNDO_GROUP_TEXT,
+ _("Reshape Text Layer"));
+
+ undo = gimp_image_undo_can_compress (text_tool->image, GIMP_TYPE_UNDO_STACK,
+ GIMP_UNDO_GROUP_TEXT);
+
+ if (undo)
+ g_object_set_data (G_OBJECT (undo), "reshape-text-layer",
+ (gpointer) item);
+ }
+
+ gimp_text_tool_block_drawing (text_tool);
+
+ gimp_item_translate (item,
+ x1 - gimp_item_get_offset_x (item),
+ y1 - gimp_item_get_offset_y (item),
+ push_undo);
+ gimp_text_tool_apply (text_tool, push_undo);
+
+ gimp_text_tool_unblock_drawing (text_tool);
+
+ if (push_undo)
+ gimp_image_undo_group_end (text_tool->image);
+ }
+ else if (x1 != gimp_item_get_offset_x (item) ||
+ y1 != gimp_item_get_offset_y (item))
+ {
+ gimp_text_tool_block_drawing (text_tool);
+
+ gimp_text_tool_apply (text_tool, TRUE);
+
+ gimp_item_translate (item,
+ x1 - gimp_item_get_offset_x (item),
+ y1 - gimp_item_get_offset_y (item),
+ TRUE);
+
+ gimp_text_tool_unblock_drawing (text_tool);
+
+ gimp_image_flush (text_tool->image);
+ }
+ }
+}
+
+static void
+gimp_text_tool_connect (GimpTextTool *text_tool,
+ GimpTextLayer *layer,
+ GimpText *text)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+
+ g_return_if_fail (text == NULL || (layer != NULL && layer->text == text));
+
+ if (text_tool->text != text)
+ {
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (tool);
+
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+
+ if (text_tool->text)
+ {
+ g_signal_handlers_disconnect_by_func (text_tool->text,
+ gimp_text_tool_text_notify,
+ text_tool);
+ g_signal_handlers_disconnect_by_func (text_tool->text,
+ gimp_text_tool_text_changed,
+ text_tool);
+
+ if (text_tool->pending)
+ gimp_text_tool_apply (text_tool, TRUE);
+
+ g_clear_object (&text_tool->text);
+
+ g_object_set (text_tool->proxy,
+ "text", NULL,
+ "markup", NULL,
+ NULL);
+ gimp_text_buffer_set_text (text_tool->buffer, NULL);
+
+ gimp_text_tool_clear_layout (text_tool);
+ }
+
+ gimp_context_define_property (GIMP_CONTEXT (options),
+ GIMP_CONTEXT_PROP_FOREGROUND,
+ text != NULL);
+
+ if (text)
+ {
+ if (text->unit != text_tool->proxy->unit)
+ gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (options->size_entry),
+ text->unit);
+
+ gimp_config_sync (G_OBJECT (text), G_OBJECT (text_tool->proxy), 0);
+
+ if (text->markup)
+ gimp_text_buffer_set_markup (text_tool->buffer, text->markup);
+ else
+ gimp_text_buffer_set_text (text_tool->buffer, text->text);
+
+ gimp_text_tool_clear_layout (text_tool);
+
+ text_tool->text = g_object_ref (text);
+
+ g_signal_connect (text, "notify",
+ G_CALLBACK (gimp_text_tool_text_notify),
+ text_tool);
+ g_signal_connect (text, "changed",
+ G_CALLBACK (gimp_text_tool_text_changed),
+ text_tool);
+ }
+
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+ }
+
+ if (text_tool->layer != layer)
+ {
+ if (text_tool->layer)
+ {
+ g_signal_handlers_disconnect_by_func (text_tool->layer,
+ gimp_text_tool_layer_notify,
+ text_tool);
+
+ /* don't try to remove the layer if it is not attached,
+ * which can happen if we got here because the layer was
+ * somehow deleted from the image (like by the user in the
+ * layers dialog).
+ */
+ if (gimp_item_is_attached (GIMP_ITEM (text_tool->layer)))
+ gimp_text_tool_remove_empty_text_layer (text_tool);
+ }
+
+ text_tool->layer = layer;
+
+ if (layer)
+ {
+ g_signal_connect_object (text_tool->layer, "notify",
+ G_CALLBACK (gimp_text_tool_layer_notify),
+ text_tool, 0);
+ }
+ }
+}
+
+static void
+gimp_text_tool_layer_notify (GimpTextLayer *layer,
+ const GParamSpec *pspec,
+ GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+
+ if (! strcmp (pspec->name, "modified"))
+ {
+ if (layer->modified)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ }
+ else if (! strcmp (pspec->name, "text"))
+ {
+ if (! layer->text)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ }
+ else if (! strcmp (pspec->name, "offset-x") ||
+ ! strcmp (pspec->name, "offset-y"))
+ {
+ if (gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ gimp_text_tool_block_drawing (text_tool);
+
+ gimp_text_tool_frame_item (text_tool);
+
+ gimp_text_tool_unblock_drawing (text_tool);
+ }
+ }
+}
+
+static gboolean
+gimp_text_tool_apply_idle (GimpTextTool *text_tool)
+{
+ text_tool->idle_id = 0;
+
+ gimp_text_tool_apply (text_tool, TRUE);
+
+ gimp_text_tool_unblock_drawing (text_tool);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gimp_text_tool_proxy_notify (GimpText *text,
+ const GParamSpec *pspec,
+ GimpTextTool *text_tool)
+{
+ if (! text_tool->text)
+ return;
+
+ if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
+ pspec->owner_type == GIMP_TYPE_TEXT)
+ {
+ if (text_tool->preedit_active)
+ {
+ /* if there is a preedit going on, don't queue pending
+ * changes to be idle-applied with undo; instead, flush the
+ * pending queue (happens only when preedit starts), and
+ * apply the changes to text_tool->text directly. Preedit
+ * will *always* end by removing the preedit string, and if
+ * the preedit was committed, it will insert the resulting
+ * text, which will not trigger this if() any more.
+ */
+
+ GList *list = NULL;
+
+ /* if there are pending changes, apply them before applying
+ * preedit stuff directly (bypassing undo)
+ */
+ if (text_tool->pending)
+ {
+ gimp_text_tool_block_drawing (text_tool);
+ gimp_text_tool_apply (text_tool, TRUE);
+ gimp_text_tool_unblock_drawing (text_tool);
+ }
+
+ gimp_text_tool_block_drawing (text_tool);
+
+ list = g_list_append (list, (gpointer) pspec);
+ gimp_text_tool_apply_list (text_tool, list);
+ g_list_free (list);
+
+ gimp_text_tool_frame_item (text_tool);
+
+ gimp_image_flush (gimp_item_get_image (GIMP_ITEM (text_tool->layer)));
+
+ gimp_text_tool_unblock_drawing (text_tool);
+ }
+ else
+ {
+ /* else queue the property change for normal processing,
+ * including undo
+ */
+
+ text_tool->pending = g_list_append (text_tool->pending,
+ (gpointer) pspec);
+
+ if (! text_tool->idle_id)
+ {
+ gimp_text_tool_block_drawing (text_tool);
+
+ text_tool->idle_id =
+ g_idle_add_full (G_PRIORITY_LOW,
+ (GSourceFunc) gimp_text_tool_apply_idle,
+ text_tool,
+ NULL);
+ }
+ }
+ }
+}
+
+static void
+gimp_text_tool_text_notify (GimpText *text,
+ const GParamSpec *pspec,
+ GimpTextTool *text_tool)
+{
+ g_return_if_fail (text == text_tool->text);
+
+ /* an undo cancels all preedit operations */
+ if (text_tool->preedit_active)
+ gimp_text_tool_abort_im_context (text_tool);
+
+ gimp_text_tool_block_drawing (text_tool);
+
+ if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
+ {
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, pspec->value_type);
+
+ g_object_get_property (G_OBJECT (text), pspec->name, &value);
+
+ g_signal_handlers_block_by_func (text_tool->proxy,
+ gimp_text_tool_proxy_notify,
+ text_tool);
+
+ g_object_set_property (G_OBJECT (text_tool->proxy), pspec->name, &value);
+
+ g_signal_handlers_unblock_by_func (text_tool->proxy,
+ gimp_text_tool_proxy_notify,
+ text_tool);
+
+ g_value_unset (&value);
+ }
+
+ /* if the text has changed, (probably because of an undo), we put
+ * the new text into the text buffer
+ */
+ if (strcmp (pspec->name, "text") == 0 ||
+ strcmp (pspec->name, "markup") == 0)
+ {
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+
+ if (text->markup)
+ gimp_text_buffer_set_markup (text_tool->buffer, text->markup);
+ else
+ gimp_text_buffer_set_text (text_tool->buffer, text->text);
+
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+ }
+
+ gimp_text_tool_unblock_drawing (text_tool);
+}
+
+static void
+gimp_text_tool_text_changed (GimpText *text,
+ GimpTextTool *text_tool)
+{
+ gimp_text_tool_block_drawing (text_tool);
+
+ /* we need to redraw the rectangle in any case because whatever
+ * changes to the text can change its size
+ */
+ gimp_text_tool_frame_item (text_tool);
+
+ gimp_text_tool_unblock_drawing (text_tool);
+}
+
+static void
+gimp_text_tool_fonts_async_set_empty_notify (GimpAsyncSet *async_set,
+ GParamSpec *pspec,
+ GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+
+ if (! gimp_async_set_is_empty (async_set) && tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+}
+
+static void
+gimp_text_tool_apply_list (GimpTextTool *text_tool,
+ GList *pspecs)
+{
+ GObject *src = G_OBJECT (text_tool->proxy);
+ GObject *dest = G_OBJECT (text_tool->text);
+ GList *list;
+
+ g_signal_handlers_block_by_func (dest,
+ gimp_text_tool_text_notify,
+ text_tool);
+ g_signal_handlers_block_by_func (dest,
+ gimp_text_tool_text_changed,
+ text_tool);
+
+ g_object_freeze_notify (dest);
+
+ for (list = pspecs; list; list = g_list_next (list))
+ {
+ const GParamSpec *pspec;
+ GValue value = G_VALUE_INIT;
+
+ /* look ahead and compress changes */
+ if (list->next && list->next->data == list->data)
+ continue;
+
+ pspec = list->data;
+
+ g_value_init (&value, pspec->value_type);
+
+ g_object_get_property (src, pspec->name, &value);
+ g_object_set_property (dest, pspec->name, &value);
+
+ g_value_unset (&value);
+ }
+
+ g_object_thaw_notify (dest);
+
+ g_signal_handlers_unblock_by_func (dest,
+ gimp_text_tool_text_notify,
+ text_tool);
+ g_signal_handlers_unblock_by_func (dest,
+ gimp_text_tool_text_changed,
+ text_tool);
+}
+
+static void
+gimp_text_tool_create_layer (GimpTextTool *text_tool,
+ GimpText *text)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+ GimpLayer *layer;
+ gdouble x1, y1;
+ gdouble x2, y2;
+
+ gimp_text_tool_block_drawing (text_tool);
+
+ if (text)
+ {
+ text = gimp_config_duplicate (GIMP_CONFIG (text));
+ }
+ else
+ {
+ gchar *string;
+
+ if (gimp_text_buffer_has_markup (text_tool->buffer))
+ {
+ string = gimp_text_buffer_get_markup (text_tool->buffer);
+
+ g_object_set (text_tool->proxy,
+ "markup", string,
+ "box-mode", GIMP_TEXT_BOX_DYNAMIC,
+ NULL);
+ }
+ else
+ {
+ string = gimp_text_buffer_get_text (text_tool->buffer);
+
+ g_object_set (text_tool->proxy,
+ "text", string,
+ "box-mode", GIMP_TEXT_BOX_DYNAMIC,
+ NULL);
+ }
+
+ g_free (string);
+
+ text = gimp_config_duplicate (GIMP_CONFIG (text_tool->proxy));
+ }
+
+ layer = gimp_text_layer_new (image, text);
+
+ g_object_unref (text);
+
+ if (! layer)
+ {
+ gimp_text_tool_unblock_drawing (text_tool);
+ return;
+ }
+
+ gimp_text_tool_connect (text_tool, GIMP_TEXT_LAYER (layer), text);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TEXT,
+ _("Add Text Layer"));
+
+ if (gimp_image_get_floating_selection (image))
+ {
+ g_signal_handlers_block_by_func (image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+
+ floating_sel_anchor (gimp_image_get_floating_selection (image));
+
+ g_signal_handlers_unblock_by_func (image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+ }
+
+ g_object_get (text_tool->widget,
+ "x1", &x1,
+ "y1", &y1,
+ "x2", &x2,
+ "y2", &y2,
+ NULL);
+
+ if (text_tool->text_box_fixed == FALSE)
+ {
+ if (text_tool->text &&
+ (text_tool->text->base_dir == GIMP_TEXT_DIRECTION_TTB_RTL ||
+ text_tool->text->base_dir == GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT))
+ {
+ x1 -= gimp_item_get_width (GIMP_ITEM (layer));
+ }
+ }
+ gimp_item_set_offset (GIMP_ITEM (layer), x1, y1);
+
+ gimp_image_add_layer (image, layer,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ if (text_tool->text_box_fixed)
+ {
+ GimpUnit box_unit = text_tool->proxy->box_unit;
+ gdouble xres, yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ g_object_set (text_tool->proxy,
+ "box-mode", GIMP_TEXT_BOX_FIXED,
+ "box-width", gimp_pixels_to_units (x2 - x1,
+ box_unit, xres),
+ "box-height", gimp_pixels_to_units (y2 - y1,
+ box_unit, yres),
+ NULL);
+
+ gimp_text_tool_apply (text_tool, TRUE); /* unblocks drawing */
+ }
+ else
+ {
+ gimp_text_tool_frame_item (text_tool);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ gimp_image_flush (image);
+
+ gimp_text_tool_set_drawable (text_tool, GIMP_DRAWABLE (layer), FALSE);
+
+ gimp_text_tool_unblock_drawing (text_tool);
+}
+
+#define RESPONSE_NEW 1
+
+static void
+gimp_text_tool_confirm_response (GtkWidget *widget,
+ gint response_id,
+ GimpTextTool *text_tool)
+{
+ GimpTextLayer *layer = text_tool->layer;
+
+ gtk_widget_destroy (widget);
+
+ if (layer && layer->text)
+ {
+ switch (response_id)
+ {
+ case RESPONSE_NEW:
+ gimp_text_tool_create_layer (text_tool, layer->text);
+ break;
+
+ case GTK_RESPONSE_ACCEPT:
+ gimp_text_tool_connect (text_tool, layer, layer->text);
+
+ /* cause the text layer to be rerendered */
+ g_object_notify (G_OBJECT (text_tool->proxy), "markup");
+
+ gimp_text_tool_editor_start (text_tool);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static void
+gimp_text_tool_confirm_dialog (GimpTextTool *text_tool)
+{
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+ GtkWidget *label;
+
+ g_return_if_fail (text_tool->layer != NULL);
+
+ if (text_tool->confirm_dialog)
+ {
+ gtk_window_present (GTK_WINDOW (text_tool->confirm_dialog));
+ return;
+ }
+
+ dialog = gimp_viewable_dialog_new (GIMP_VIEWABLE (text_tool->layer),
+ GIMP_CONTEXT (gimp_tool_get_options (tool)),
+ _("Confirm Text Editing"),
+ "gimp-text-tool-confirm",
+ GIMP_ICON_LAYER_TEXT_LAYER,
+ _("Confirm Text Editing"),
+ GTK_WIDGET (shell),
+ gimp_standard_help_func, NULL,
+
+ _("Create _New Layer"), RESPONSE_NEW,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Edit"), GTK_RESPONSE_ACCEPT,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ RESPONSE_NEW,
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gimp_text_tool_confirm_response),
+ text_tool);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ vbox, FALSE, FALSE, 0);
+ gtk_widget_show (vbox);
+
+ label = gtk_label_new (_("The layer you selected is a text layer but "
+ "it has been modified using other tools. "
+ "Editing the layer with the text tool will "
+ "discard these modifications."
+ "\n\n"
+ "You can edit the layer or create a new "
+ "text layer from its text attributes."));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ gtk_widget_show (dialog);
+
+ text_tool->confirm_dialog = dialog;
+ g_signal_connect_swapped (dialog, "destroy",
+ G_CALLBACK (g_nullify_pointer),
+ &text_tool->confirm_dialog);
+}
+
+static void
+gimp_text_tool_layer_changed (GimpImage *image,
+ GimpTextTool *text_tool)
+{
+ GimpLayer *layer = gimp_image_get_active_layer (image);
+
+ if (layer != GIMP_LAYER (text_tool->layer))
+ {
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpDisplay *display = tool->display;
+
+ if (display)
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ if (gimp_text_tool_set_drawable (text_tool, GIMP_DRAWABLE (layer),
+ FALSE) &&
+ GIMP_LAYER (text_tool->layer) == layer)
+ {
+ GError *error = NULL;
+
+ if (! gimp_text_tool_start (text_tool, display, layer, &error))
+ {
+ gimp_text_tool_set_drawable (text_tool, NULL, FALSE);
+
+ gimp_tool_message_literal (tool, display, error->message);
+
+ g_clear_error (&error);
+
+ return;
+ }
+ }
+ }
+ }
+}
+
+static void
+gimp_text_tool_set_image (GimpTextTool *text_tool,
+ GimpImage *image)
+{
+ if (text_tool->image == image)
+ return;
+
+ if (text_tool->image)
+ {
+ g_signal_handlers_disconnect_by_func (text_tool->image,
+ gimp_text_tool_layer_changed,
+ text_tool);
+
+ g_object_remove_weak_pointer (G_OBJECT (text_tool->image),
+ (gpointer) &text_tool->image);
+ }
+
+ text_tool->image = image;
+
+ if (image)
+ {
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+ gdouble xres;
+ gdouble yres;
+
+ g_object_add_weak_pointer (G_OBJECT (text_tool->image),
+ (gpointer) &text_tool->image);
+
+ g_signal_connect_object (text_tool->image, "active-layer-changed",
+ G_CALLBACK (gimp_text_tool_layer_changed),
+ text_tool, 0);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+ gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (options->size_entry), 0,
+ yres, FALSE);
+ }
+}
+
+static gboolean
+gimp_text_tool_set_drawable (GimpTextTool *text_tool,
+ GimpDrawable *drawable,
+ gboolean confirm)
+{
+ GimpImage *image = NULL;
+
+ if (text_tool->confirm_dialog)
+ gtk_widget_destroy (text_tool->confirm_dialog);
+
+ if (drawable)
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ gimp_text_tool_set_image (text_tool, image);
+
+ if (GIMP_IS_TEXT_LAYER (drawable) && GIMP_TEXT_LAYER (drawable)->text)
+ {
+ GimpTextLayer *layer = GIMP_TEXT_LAYER (drawable);
+
+ if (layer == text_tool->layer && layer->text == text_tool->text)
+ return TRUE;
+
+ if (layer->modified)
+ {
+ if (confirm)
+ {
+ gimp_text_tool_connect (text_tool, layer, NULL);
+ gimp_text_tool_confirm_dialog (text_tool);
+ return TRUE;
+ }
+ }
+ else
+ {
+ gimp_text_tool_connect (text_tool, layer, layer->text);
+ return TRUE;
+ }
+ }
+
+ gimp_text_tool_connect (text_tool, NULL, NULL);
+
+ return FALSE;
+}
+
+static void
+gimp_text_tool_block_drawing (GimpTextTool *text_tool)
+{
+ if (text_tool->drawing_blocked == 0)
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
+
+ gimp_text_tool_clear_layout (text_tool);
+ }
+
+ text_tool->drawing_blocked++;
+}
+
+static void
+gimp_text_tool_unblock_drawing (GimpTextTool *text_tool)
+{
+ g_return_if_fail (text_tool->drawing_blocked > 0);
+
+ text_tool->drawing_blocked--;
+
+ if (text_tool->drawing_blocked == 0)
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
+}
+
+static void
+gimp_text_tool_buffer_begin_edit (GimpTextBuffer *buffer,
+ GimpTextTool *text_tool)
+{
+ gimp_text_tool_block_drawing (text_tool);
+}
+
+static void
+gimp_text_tool_buffer_end_edit (GimpTextBuffer *buffer,
+ GimpTextTool *text_tool)
+{
+ if (text_tool->text)
+ {
+ gchar *string;
+
+ if (gimp_text_buffer_has_markup (buffer))
+ {
+ string = gimp_text_buffer_get_markup (buffer);
+
+ g_object_set (text_tool->proxy,
+ "markup", string,
+ NULL);
+ }
+ else
+ {
+ string = gimp_text_buffer_get_text (buffer);
+
+ g_object_set (text_tool->proxy,
+ "text", string,
+ NULL);
+ }
+
+ g_free (string);
+ }
+ else
+ {
+ gimp_text_tool_create_layer (text_tool, NULL);
+ }
+
+ gimp_text_tool_unblock_drawing (text_tool);
+}
+
+static void
+gimp_text_tool_buffer_color_applied (GimpTextBuffer *buffer,
+ const GimpRGB *color,
+ GimpTextTool *text_tool)
+{
+ gimp_palettes_add_color_history (GIMP_TOOL (text_tool)->tool_info->gimp,
+ color);
+}
+
+
+/* public functions */
+
+void
+gimp_text_tool_clear_layout (GimpTextTool *text_tool)
+{
+ g_clear_object (&text_tool->layout);
+}
+
+gboolean
+gimp_text_tool_ensure_layout (GimpTextTool *text_tool)
+{
+ if (! text_tool->layout && text_tool->text)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (text_tool->layer));
+ gdouble xres;
+ gdouble yres;
+ GError *error = NULL;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ text_tool->layout = gimp_text_layout_new (text_tool->layer->text,
+ xres, yres, &error);
+ if (error)
+ {
+ gimp_message_literal (image->gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_error_free (error);
+ }
+ }
+
+ return text_tool->layout != NULL;
+}
+
+void
+gimp_text_tool_apply (GimpTextTool *text_tool,
+ gboolean push_undo)
+{
+ const GParamSpec *pspec = NULL;
+ GimpImage *image;
+ GimpTextLayer *layer;
+ GList *list;
+ gboolean undo_group = FALSE;
+
+ if (text_tool->idle_id)
+ {
+ g_source_remove (text_tool->idle_id);
+ text_tool->idle_id = 0;
+
+ gimp_text_tool_unblock_drawing (text_tool);
+ }
+
+ g_return_if_fail (text_tool->text != NULL);
+ g_return_if_fail (text_tool->layer != NULL);
+
+ layer = text_tool->layer;
+ image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ g_return_if_fail (layer->text == text_tool->text);
+
+ /* Walk over the list of changes and figure out if we are changing
+ * a single property or need to push a full text undo.
+ */
+ for (list = text_tool->pending;
+ list && list->next && list->next->data == list->data;
+ list = list->next)
+ /* do nothing */;
+
+ if (g_list_length (list) == 1)
+ pspec = list->data;
+
+ /* If we are changing a single property, we don't need to push
+ * an undo if all of the following is true:
+ * - the redo stack is empty
+ * - the last item on the undo stack is a text undo
+ * - the last undo changed the same text property on the same layer
+ * - the last undo happened less than TEXT_UNDO_TIMEOUT seconds ago
+ */
+ if (pspec)
+ {
+ GimpUndo *undo = gimp_image_undo_can_compress (image, GIMP_TYPE_TEXT_UNDO,
+ GIMP_UNDO_TEXT_LAYER);
+
+ if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer))
+ {
+ GimpTextUndo *text_undo = GIMP_TEXT_UNDO (undo);
+
+ if (text_undo->pspec == pspec)
+ {
+ if (gimp_undo_get_age (undo) < TEXT_UNDO_TIMEOUT)
+ {
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpContext *context;
+
+ context = GIMP_CONTEXT (gimp_tool_get_options (tool));
+
+ push_undo = FALSE;
+ gimp_undo_reset_age (undo);
+ gimp_undo_refresh_preview (undo, context);
+ }
+ }
+ }
+ }
+
+ if (push_undo)
+ {
+ if (layer->modified)
+ {
+ undo_group = TRUE;
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TEXT, NULL);
+
+ gimp_image_undo_push_text_layer_modified (image, NULL, layer);
+
+ /* see comment in gimp_text_layer_set() */
+ gimp_image_undo_push_drawable_mod (image, NULL,
+ GIMP_DRAWABLE (layer), TRUE);
+ }
+
+ gimp_image_undo_push_text_layer (image, NULL, layer, pspec);
+ }
+
+ gimp_text_tool_apply_list (text_tool, list);
+
+ g_list_free (text_tool->pending);
+ text_tool->pending = NULL;
+
+ if (push_undo)
+ {
+ g_object_set (layer, "modified", FALSE, NULL);
+
+ if (undo_group)
+ gimp_image_undo_group_end (image);
+ }
+
+ gimp_text_tool_frame_item (text_tool);
+
+ gimp_image_flush (image);
+}
+
+gboolean
+gimp_text_tool_set_layer (GimpTextTool *text_tool,
+ GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_TEXT_TOOL (text_tool), FALSE);
+ g_return_val_if_fail (layer == NULL || GIMP_IS_LAYER (layer), FALSE);
+
+ if (layer == GIMP_LAYER (text_tool->layer))
+ return TRUE;
+
+ /* FIXME this function works, and I have no clue why: first we set
+ * the drawable, then we HALT the tool and start() it without
+ * re-setting the drawable. Why this works perfectly anyway when
+ * double clicking a text layer in the layers dialog... no idea.
+ */
+ if (gimp_text_tool_set_drawable (text_tool, GIMP_DRAWABLE (layer), TRUE))
+ {
+ GimpTool *tool = GIMP_TOOL (text_tool);
+ GimpItem *item = GIMP_ITEM (layer);
+ GimpContext *context;
+ GimpDisplay *display;
+
+ context = gimp_get_user_context (tool->tool_info->gimp);
+ display = gimp_context_get_display (context);
+
+ if (! display ||
+ gimp_display_get_image (display) != gimp_item_get_image (item))
+ {
+ GList *list;
+
+ display = NULL;
+
+ for (list = gimp_get_display_iter (tool->tool_info->gimp);
+ list;
+ list = g_list_next (list))
+ {
+ display = list->data;
+
+ if (gimp_display_get_image (display) == gimp_item_get_image (item))
+ {
+ gimp_context_set_display (context, display);
+ break;
+ }
+
+ display = NULL;
+ }
+ }
+
+ if (tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ if (display)
+ {
+ GError *error = NULL;
+
+ if (! gimp_text_tool_start (text_tool, display, layer, &error))
+ {
+ gimp_text_tool_set_drawable (text_tool, NULL, FALSE);
+
+ gimp_tool_message_literal (tool, display, error->message);
+
+ g_clear_error (&error);
+
+ return FALSE;
+ }
+
+ tool->drawable = GIMP_DRAWABLE (layer);
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_text_tool_get_has_text_selection (GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+
+ return gtk_text_buffer_get_has_selection (buffer);
+}
+
+void
+gimp_text_tool_delete_selection (GimpTextTool *text_tool)
+{
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
+
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ }
+}
+
+void
+gimp_text_tool_cut_clipboard (GimpTextTool *text_tool)
+{
+ GimpDisplayShell *shell;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+
+ shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
+ GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_cut_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
+ clipboard, TRUE);
+}
+
+void
+gimp_text_tool_copy_clipboard (GimpTextTool *text_tool)
+{
+ GimpDisplayShell *shell;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+
+ shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
+ GDK_SELECTION_CLIPBOARD);
+
+ /* need to block "end-user-action" on the text buffer, because
+ * GtkTextBuffer considers copying text to the clipboard an
+ * undo-relevant user action, which is clearly a bug, but what
+ * can we do...
+ */
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+ g_signal_handlers_block_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+
+ gtk_text_buffer_copy_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
+ clipboard);
+
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_end_edit,
+ text_tool);
+ g_signal_handlers_unblock_by_func (text_tool->buffer,
+ gimp_text_tool_buffer_begin_edit,
+ text_tool);
+}
+
+void
+gimp_text_tool_paste_clipboard (GimpTextTool *text_tool)
+{
+ GimpDisplayShell *shell;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+
+ shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
+
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
+ GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_paste_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
+ clipboard, NULL, TRUE);
+}
+
+void
+gimp_text_tool_create_vectors (GimpTextTool *text_tool)
+{
+ GimpVectors *vectors;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+
+ if (! text_tool->text || ! text_tool->image)
+ return;
+
+ vectors = gimp_text_vectors_new (text_tool->image, text_tool->text);
+
+ if (text_tool->layer)
+ {
+ gint x, y;
+
+ gimp_item_get_offset (GIMP_ITEM (text_tool->layer), &x, &y);
+ gimp_item_translate (GIMP_ITEM (vectors), x, y, FALSE);
+ }
+
+ gimp_image_add_vectors (text_tool->image, vectors,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ gimp_image_flush (text_tool->image);
+}
+
+void
+gimp_text_tool_create_vectors_warped (GimpTextTool *text_tool)
+{
+ GimpVectors *vectors0;
+ GimpVectors *vectors;
+ gdouble box_width;
+ gdouble box_height;
+ GimpTextDirection dir;
+ gdouble offset = 0.0;
+
+ g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+
+ if (! text_tool->text || ! text_tool->image || ! text_tool->layer)
+ return;
+
+ box_width = gimp_item_get_width (GIMP_ITEM (text_tool->layer));
+ box_height = gimp_item_get_height (GIMP_ITEM (text_tool->layer));
+
+ vectors0 = gimp_image_get_active_vectors (text_tool->image);
+ if (! vectors0)
+ return;
+
+ vectors = gimp_text_vectors_new (text_tool->image, text_tool->text);
+
+ offset = 0;
+ dir = gimp_text_tool_get_direction (text_tool);
+ switch (dir)
+ {
+ case GIMP_TEXT_DIRECTION_LTR:
+ case GIMP_TEXT_DIRECTION_RTL:
+ offset = 0.5 * box_height;
+ 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:
+ {
+ GimpStroke *stroke = NULL;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ gimp_stroke_rotate (stroke, 0, 0, 270);
+ gimp_stroke_translate (stroke, 0, box_width);
+ }
+ }
+ offset = 0.5 * box_width;
+ break;
+ }
+
+ gimp_vectors_warp_vectors (vectors0, vectors, offset);
+
+ gimp_item_set_visible (GIMP_ITEM (vectors), TRUE, FALSE);
+
+ gimp_image_add_vectors (text_tool->image, vectors,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+
+ gimp_image_flush (text_tool->image);
+}
+
+GimpTextDirection
+gimp_text_tool_get_direction (GimpTextTool *text_tool)
+{
+ GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
+ return options->base_dir;
+}
diff --git a/app/tools/gimptexttool.h b/app/tools/gimptexttool.h
new file mode 100644
index 0000000..b939d18
--- /dev/null
+++ b/app/tools/gimptexttool.h
@@ -0,0 +1,132 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextTool
+ * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org>
+ * Daniel Eddeland <danedde@svn.gnome.org>
+ * 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_TEXT_TOOL_H__
+#define __GIMP_TEXT_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_TEXT_TOOL (gimp_text_tool_get_type ())
+#define GIMP_TEXT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_TOOL, GimpTextTool))
+#define GIMP_IS_TEXT_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_TOOL))
+#define GIMP_TEXT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEXT_TOOL, GimpTextToolClass))
+#define GIMP_IS_TEXT_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEXT_TOOL))
+
+#define GIMP_TEXT_TOOL_GET_OPTIONS(t) (GIMP_TEXT_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpTextTool GimpTextTool;
+typedef struct _GimpTextToolClass GimpTextToolClass;
+
+struct _GimpTextTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpText *proxy;
+ GList *pending;
+ guint idle_id;
+
+ gboolean moving;
+
+ GimpTextBuffer *buffer;
+
+ GimpText *text;
+ GimpTextLayer *layer;
+ GimpImage *image;
+
+ GtkWidget *confirm_dialog;
+ GimpUIManager *ui_manager;
+
+ gboolean handle_rectangle_change_complete;
+ gboolean text_box_fixed;
+
+ GimpTextLayout *layout;
+ gint drawing_blocked;
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+
+ /* text editor state: */
+
+ GtkWidget *style_overlay;
+ GtkWidget *style_editor;
+
+ gboolean selecting;
+ GtkTextIter select_start_iter;
+ gboolean select_words;
+ gboolean select_lines;
+
+ GtkIMContext *im_context;
+ gboolean needs_im_reset;
+
+ gboolean preedit_active;
+ gchar *preedit_string;
+ gint preedit_cursor;
+ GtkTextMark *preedit_start;
+ GtkTextMark *preedit_end;
+
+ gboolean overwrite_mode;
+ gint x_pos;
+
+ GtkWidget *offscreen_window;
+ GtkWidget *proxy_text_view;
+
+ GtkWidget *editor_dialog;
+};
+
+struct _GimpTextToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_text_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_text_tool_get_type (void) G_GNUC_CONST;
+
+gboolean gimp_text_tool_set_layer (GimpTextTool *text_tool,
+ GimpLayer *layer);
+
+gboolean gimp_text_tool_get_has_text_selection (GimpTextTool *text_tool);
+
+void gimp_text_tool_delete_selection (GimpTextTool *text_tool);
+void gimp_text_tool_cut_clipboard (GimpTextTool *text_tool);
+void gimp_text_tool_copy_clipboard (GimpTextTool *text_tool);
+void gimp_text_tool_paste_clipboard (GimpTextTool *text_tool);
+
+void gimp_text_tool_create_vectors (GimpTextTool *text_tool);
+void gimp_text_tool_create_vectors_warped (GimpTextTool *text_tool);
+
+GimpTextDirection
+ gimp_text_tool_get_direction (GimpTextTool *text_tool);
+
+/* only for the text editor */
+void gimp_text_tool_clear_layout (GimpTextTool *text_tool);
+gboolean gimp_text_tool_ensure_layout (GimpTextTool *text_tool);
+void gimp_text_tool_apply (GimpTextTool *text_tool,
+ gboolean push_undo);
+
+
+#endif /* __GIMP_TEXT_TOOL_H__ */
diff --git a/app/tools/gimpthresholdtool.c b/app/tools/gimpthresholdtool.c
new file mode 100644
index 0000000..3b21d72
--- /dev/null
+++ b/app/tools/gimpthresholdtool.c
@@ -0,0 +1,436 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp-gui.h"
+#include "core/gimpasync.h"
+#include "core/gimpcancelable.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdrawable-histogram.h"
+#include "core/gimphistogram.h"
+#include "core/gimpimage.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimptriviallycancelablewaitable.h"
+#include "core/gimpwaitable.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimphistogrambox.h"
+#include "widgets/gimphistogramview.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimphistogramoptions.h"
+#include "gimpthresholdtool.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_threshold_tool_finalize (GObject *object);
+
+static gboolean gimp_threshold_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+
+static gchar * gimp_threshold_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description);
+static void gimp_threshold_tool_dialog (GimpFilterTool *filter_tool);
+static void gimp_threshold_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec);
+
+static gboolean gimp_threshold_tool_channel_sensitive
+ (gint value,
+ gpointer data);
+static void gimp_threshold_tool_histogram_range (GimpHistogramView *view,
+ gint start,
+ gint end,
+ GimpThresholdTool *t_tool);
+static void gimp_threshold_tool_auto_clicked (GtkWidget *button,
+ GimpThresholdTool *t_tool);
+
+
+G_DEFINE_TYPE (GimpThresholdTool, gimp_threshold_tool,
+ GIMP_TYPE_FILTER_TOOL)
+
+#define parent_class gimp_threshold_tool_parent_class
+
+
+void
+gimp_threshold_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_THRESHOLD_TOOL,
+ GIMP_TYPE_HISTOGRAM_OPTIONS,
+ NULL,
+ 0,
+ "gimp-threshold-tool",
+ _("Threshold"),
+ _("Reduce image to two colors using a threshold"),
+ N_("_Threshold..."), NULL,
+ NULL, GIMP_HELP_TOOL_THRESHOLD,
+ GIMP_ICON_TOOL_THRESHOLD,
+ data);
+}
+
+static void
+gimp_threshold_tool_class_init (GimpThresholdToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_threshold_tool_finalize;
+
+ tool_class->initialize = gimp_threshold_tool_initialize;
+
+ filter_tool_class->get_operation = gimp_threshold_tool_get_operation;
+ filter_tool_class->dialog = gimp_threshold_tool_dialog;
+ filter_tool_class->config_notify = gimp_threshold_tool_config_notify;
+}
+
+static void
+gimp_threshold_tool_init (GimpThresholdTool *t_tool)
+{
+ t_tool->histogram = gimp_histogram_new (FALSE);
+}
+
+static void
+gimp_threshold_tool_finalize (GObject *object)
+{
+ GimpThresholdTool *t_tool = GIMP_THRESHOLD_TOOL (object);
+
+ g_clear_object (&t_tool->histogram);
+ g_clear_object (&t_tool->histogram_async);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_threshold_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpThresholdTool *t_tool = GIMP_THRESHOLD_TOOL (tool);
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ gdouble low;
+ gdouble high;
+ gint n_bins;
+
+ if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
+ {
+ return FALSE;
+ }
+
+ g_clear_object (&t_tool->histogram_async);
+
+ g_object_get (filter_tool->config,
+ "low", &low,
+ "high", &high,
+ NULL);
+
+ /* this is a hack to make sure that
+ * 'gimp_histogram_n_bins (t_tool->histogram)' returns the correct value for
+ * 'drawable' before the asynchronous calculation of its histogram is
+ * finished.
+ */
+ {
+ GeglBuffer *temp;
+
+ temp = gegl_buffer_new (GEGL_RECTANGLE (0, 0, 1, 1),
+ gimp_drawable_get_format (drawable));
+
+ gimp_histogram_calculate (t_tool->histogram,
+ temp, GEGL_RECTANGLE (0, 0, 1, 1),
+ NULL, NULL);
+
+ g_object_unref (temp);
+ }
+
+ n_bins = gimp_histogram_n_bins (t_tool->histogram);
+
+ t_tool->histogram_async = gimp_drawable_calculate_histogram_async (
+ drawable, t_tool->histogram, FALSE);
+ gimp_histogram_view_set_histogram (t_tool->histogram_box->view,
+ t_tool->histogram);
+
+ gimp_histogram_view_set_range (t_tool->histogram_box->view,
+ low * (n_bins - 0.0001),
+ high * (n_bins - 0.0001));
+
+ return TRUE;
+}
+
+static gchar *
+gimp_threshold_tool_get_operation (GimpFilterTool *filter_tool,
+ gchar **description)
+{
+ *description = g_strdup (_("Apply Threshold"));
+
+ return g_strdup ("gimp:threshold");
+}
+
+
+/**********************/
+/* Threshold dialog */
+/**********************/
+
+static void
+gimp_threshold_tool_dialog (GimpFilterTool *filter_tool)
+{
+ GimpThresholdTool *t_tool = GIMP_THRESHOLD_TOOL (filter_tool);
+ GimpToolOptions *tool_options = GIMP_TOOL_GET_OPTIONS (filter_tool);
+ GtkWidget *main_vbox;
+ GtkWidget *main_frame;
+ GtkWidget *frame_vbox;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *hbox2;
+ GtkWidget *box;
+ GtkWidget *button;
+ GimpHistogramChannel channel;
+
+ main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);
+
+ main_frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (main_vbox), main_frame, TRUE, TRUE, 0);
+ gtk_widget_show (main_frame);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_frame_set_label_widget (GTK_FRAME (main_frame), hbox);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("Cha_nnel:"));
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ t_tool->channel_menu = gimp_prop_enum_combo_box_new (filter_tool->config,
+ "channel", -1, -1);
+ gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (t_tool->channel_menu),
+ "gimp-channel");
+
+ gimp_int_combo_box_set_sensitivity (GIMP_INT_COMBO_BOX (t_tool->channel_menu),
+ gimp_threshold_tool_channel_sensitive,
+ filter_tool, NULL);
+
+ gtk_box_pack_start (GTK_BOX (hbox), t_tool->channel_menu, FALSE, FALSE, 0);
+ gtk_widget_show (t_tool->channel_menu);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), t_tool->channel_menu);
+
+ hbox2 = gimp_prop_enum_icon_box_new (G_OBJECT (tool_options),
+ "histogram-scale", "gimp-histogram",
+ 0, 0);
+ gtk_box_pack_end (GTK_BOX (hbox), hbox2, FALSE, FALSE, 0);
+ gtk_widget_show (hbox2);
+
+ frame_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+ gtk_container_add (GTK_CONTAINER (main_frame), frame_vbox);
+ gtk_widget_show (frame_vbox);
+
+ box = gimp_histogram_box_new ();
+ gtk_box_pack_start (GTK_BOX (frame_vbox), box, TRUE, TRUE, 0);
+ gtk_widget_show (box);
+
+ t_tool->histogram_box = GIMP_HISTOGRAM_BOX (box);
+
+ g_object_get (filter_tool->config,
+ "channel", &channel,
+ NULL);
+
+ gimp_histogram_view_set_channel (t_tool->histogram_box->view, channel);
+
+ g_signal_connect (t_tool->histogram_box->view, "range-changed",
+ G_CALLBACK (gimp_threshold_tool_histogram_range),
+ t_tool);
+
+ g_object_bind_property (G_OBJECT (tool_options), "histogram-scale",
+ G_OBJECT (t_tool->histogram_box->view), "histogram-scale",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (frame_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ button = gtk_button_new_with_mnemonic (_("_Auto"));
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gimp_help_set_help_data (button, _("Automatically adjust to optimal "
+ "binarization threshold"), NULL);
+ gtk_widget_show (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_threshold_tool_auto_clicked),
+ t_tool);
+}
+
+static void
+gimp_threshold_tool_config_notify (GimpFilterTool *filter_tool,
+ GimpConfig *config,
+ const GParamSpec *pspec)
+{
+ GimpThresholdTool *t_tool = GIMP_THRESHOLD_TOOL (filter_tool);
+
+ GIMP_FILTER_TOOL_CLASS (parent_class)->config_notify (filter_tool,
+ config, pspec);
+
+ if (! t_tool->histogram_box)
+ return;
+
+ if (! strcmp (pspec->name, "channel"))
+ {
+ GimpHistogramChannel channel;
+
+ g_object_get (config,
+ "channel", &channel,
+ NULL);
+
+ gimp_histogram_view_set_channel (t_tool->histogram_box->view,
+ channel);
+ }
+ else if (! strcmp (pspec->name, "low") ||
+ ! strcmp (pspec->name, "high"))
+ {
+ gdouble low;
+ gdouble high;
+ gint n_bins;
+
+ g_object_get (config,
+ "low", &low,
+ "high", &high,
+ NULL);
+
+ n_bins = gimp_histogram_n_bins (t_tool->histogram);
+
+ gimp_histogram_view_set_range (t_tool->histogram_box->view,
+ low * (n_bins - 0.0001),
+ high * (n_bins - 0.0001));
+ }
+}
+
+static gboolean
+gimp_threshold_tool_channel_sensitive (gint value,
+ gpointer data)
+{
+ GimpDrawable *drawable = GIMP_TOOL (data)->drawable;
+ GimpHistogramChannel channel = value;
+
+ if (!drawable)
+ return FALSE;
+
+ switch (channel)
+ {
+ case GIMP_HISTOGRAM_VALUE:
+ return TRUE;
+
+ case GIMP_HISTOGRAM_RED:
+ case GIMP_HISTOGRAM_GREEN:
+ case GIMP_HISTOGRAM_BLUE:
+ return gimp_drawable_is_rgb (drawable);
+
+ case GIMP_HISTOGRAM_ALPHA:
+ return gimp_drawable_has_alpha (drawable);
+
+ case GIMP_HISTOGRAM_RGB:
+ return gimp_drawable_is_rgb (drawable);
+
+ case GIMP_HISTOGRAM_LUMINANCE:
+ return gimp_drawable_is_rgb (drawable);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_threshold_tool_histogram_range (GimpHistogramView *widget,
+ gint start,
+ gint end,
+ GimpThresholdTool *t_tool)
+{
+ GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (t_tool);
+ gint n_bins = gimp_histogram_n_bins (t_tool->histogram);
+ gdouble low = (gdouble) start / (n_bins - 1);
+ gdouble high = (gdouble) end / (n_bins - 1);
+ gdouble config_low;
+ gdouble config_high;
+
+ g_object_get (filter_tool->config,
+ "low", &config_low,
+ "high", &config_high,
+ NULL);
+
+ if (low != config_low ||
+ high != config_high)
+ {
+ g_object_set (filter_tool->config,
+ "low", low,
+ "high", high,
+ NULL);
+ }
+}
+
+static void
+gimp_threshold_tool_auto_clicked (GtkWidget *button,
+ GimpThresholdTool *t_tool)
+{
+ GimpTool *tool = GIMP_TOOL (t_tool);
+ GimpWaitable *waitable;
+
+ waitable = gimp_trivially_cancelable_waitable_new (
+ GIMP_WAITABLE (t_tool->histogram_async));
+
+ gimp_wait (tool->tool_info->gimp, waitable, _("Calculating histogram..."));
+
+ g_object_unref (waitable);
+
+ if (gimp_async_is_synced (t_tool->histogram_async) &&
+ gimp_async_is_finished (t_tool->histogram_async))
+ {
+ GimpHistogramChannel channel;
+ gint n_bins;
+ gdouble low;
+
+ g_object_get (GIMP_FILTER_TOOL (t_tool)->config,
+ "channel", &channel,
+ NULL);
+
+ n_bins = gimp_histogram_n_bins (t_tool->histogram);
+
+ low = gimp_histogram_get_threshold (t_tool->histogram,
+ channel,
+ 0, n_bins - 1);
+
+ gimp_histogram_view_set_range (t_tool->histogram_box->view,
+ low, n_bins - 1);
+ }
+}
diff --git a/app/tools/gimpthresholdtool.h b/app/tools/gimpthresholdtool.h
new file mode 100644
index 0000000..95f80eb
--- /dev/null
+++ b/app/tools/gimpthresholdtool.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_THRESHOLD_TOOL_H__
+#define __GIMP_THRESHOLD_TOOL_H__
+
+
+#include "gimpfiltertool.h"
+
+
+#define GIMP_TYPE_THRESHOLD_TOOL (gimp_threshold_tool_get_type ())
+#define GIMP_THRESHOLD_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_THRESHOLD_TOOL, GimpThresholdTool))
+#define GIMP_THRESHOLD_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_THRESHOLD_TOOL, GimpThresholdToolClass))
+#define GIMP_IS_THRESHOLD_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_THRESHOLD_TOOL))
+#define GIMP_IS_THRESHOLD_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_THRESHOLD_TOOL))
+#define GIMP_THRESHOLD_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_THRESHOLD_TOOL, GimpThresholdToolClass))
+
+
+typedef struct _GimpThresholdTool GimpThresholdTool;
+typedef struct _GimpThresholdToolClass GimpThresholdToolClass;
+
+struct _GimpThresholdTool
+{
+ GimpFilterTool parent_instance;
+
+ /* dialog */
+ GimpHistogram *histogram;
+ GimpAsync *histogram_async;
+ GtkWidget *channel_menu;
+ GimpHistogramBox *histogram_box;
+};
+
+struct _GimpThresholdToolClass
+{
+ GimpFilterToolClass parent_class;
+};
+
+
+void gimp_threshold_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_threshold_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_THRESHOLD_TOOL_H__ */
diff --git a/app/tools/gimptilehandleriscissors.c b/app/tools/gimptilehandleriscissors.c
new file mode 100644
index 0000000..63e434a
--- /dev/null
+++ b/app/tools/gimptilehandleriscissors.c
@@ -0,0 +1,331 @@
+/* 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 <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "tools-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "core/gimppickable.h"
+
+#include "gimptilehandleriscissors.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PICKABLE
+};
+
+
+static void gimp_tile_handler_iscissors_finalize (GObject *object);
+static void gimp_tile_handler_iscissors_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tile_handler_iscissors_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tile_handler_iscissors_validate (GimpTileHandlerValidate *validate,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer dest_buf,
+ gint dest_stride);
+
+
+G_DEFINE_TYPE (GimpTileHandlerIscissors, gimp_tile_handler_iscissors,
+ GIMP_TYPE_TILE_HANDLER_VALIDATE)
+
+#define parent_class gimp_tile_handler_iscissors_parent_class
+
+
+static void
+gimp_tile_handler_iscissors_class_init (GimpTileHandlerIscissorsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpTileHandlerValidateClass *validate_class;
+
+ validate_class = GIMP_TILE_HANDLER_VALIDATE_CLASS (klass);
+
+ object_class->finalize = gimp_tile_handler_iscissors_finalize;
+ object_class->set_property = gimp_tile_handler_iscissors_set_property;
+ object_class->get_property = gimp_tile_handler_iscissors_get_property;
+
+ validate_class->validate = gimp_tile_handler_iscissors_validate;
+
+ g_object_class_install_property (object_class, PROP_PICKABLE,
+ g_param_spec_object ("pickable", NULL, NULL,
+ GIMP_TYPE_PICKABLE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_tile_handler_iscissors_init (GimpTileHandlerIscissors *iscissors)
+{
+}
+
+static void
+gimp_tile_handler_iscissors_finalize (GObject *object)
+{
+ GimpTileHandlerIscissors *iscissors = GIMP_TILE_HANDLER_ISCISSORS (object);
+
+ if (iscissors->pickable)
+ {
+ g_object_unref (iscissors->pickable);
+ iscissors->pickable = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tile_handler_iscissors_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTileHandlerIscissors *iscissors = GIMP_TILE_HANDLER_ISCISSORS (object);
+
+ switch (property_id)
+ {
+ case PROP_PICKABLE:
+ iscissors->pickable = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tile_handler_iscissors_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTileHandlerIscissors *iscissors = GIMP_TILE_HANDLER_ISCISSORS (object);
+
+ switch (property_id)
+ {
+ case PROP_PICKABLE:
+ g_value_set_object (value, iscissors->pickable);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static const gfloat horz_deriv[9] =
+{
+ 1, 0, -1,
+ 2, 0, -2,
+ 1, 0, -1,
+};
+
+static const gfloat vert_deriv[9] =
+{
+ 1, 2, 1,
+ 0, 0, 0,
+ -1, -2, -1,
+};
+
+static const gfloat blur_32[9] =
+{
+ 1, 1, 1,
+ 1, 24, 1,
+ 1, 1, 1,
+};
+
+#define MAX_GRADIENT 179.606 /* == sqrt (127^2 + 127^2) */
+#define MIN_GRADIENT 63 /* gradients < this are directionless */
+#define COST_WIDTH 2 /* number of bytes for each pixel in cost map */
+
+static void
+gimp_tile_handler_iscissors_validate (GimpTileHandlerValidate *validate,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer dest_buf,
+ gint dest_stride)
+{
+ GimpTileHandlerIscissors *iscissors = GIMP_TILE_HANDLER_ISCISSORS (validate);
+ GeglBuffer *src;
+ GeglBuffer *temp0;
+ GeglBuffer *temp1;
+ GeglBuffer *temp2;
+ gint stride1;
+ gint stride2;
+ gint i, j;
+
+ /* temporary convolution buffers -- */
+ guchar *maxgrad_conv1;
+ guchar *maxgrad_conv2;
+
+#if 0
+ g_printerr ("validating at %d %d %d %d\n",
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+#endif
+
+ gimp_pickable_flush (iscissors->pickable);
+
+ src = gimp_pickable_get_buffer (iscissors->pickable);
+
+ temp0 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ rect->width,
+ rect->height),
+ babl_format ("R'G'B'A u8"));
+
+ temp1 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ rect->width,
+ rect->height),
+ babl_format ("R'G'B'A u8"));
+
+ temp2 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ rect->width,
+ rect->height),
+ babl_format ("R'G'B'A u8"));
+
+ /* XXX tile edges? */
+
+ /* Blur the source to get rid of noise */
+ gimp_gegl_convolve (src, rect,
+ temp0, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
+ blur_32, 3, 32, GIMP_NORMAL_CONVOL, FALSE);
+
+ /* Use this blurred region as the new source */
+
+ /* Get the horizontal derivative */
+ gimp_gegl_convolve (temp0, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
+ temp1, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
+ horz_deriv, 3, 1, GIMP_NEGATIVE_CONVOL, FALSE);
+
+ /* Get the vertical derivative */
+ gimp_gegl_convolve (temp0, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
+ temp2, GEGL_RECTANGLE (0, 0, rect->width, rect->height),
+ vert_deriv, 3, 1, GIMP_NEGATIVE_CONVOL, FALSE);
+
+ maxgrad_conv1 =
+ (guchar *) gegl_buffer_linear_open (temp1,
+ GEGL_RECTANGLE (0, 0,
+ rect->width,
+ rect->height),
+ &stride1, NULL);
+
+ maxgrad_conv2 =
+ (guchar *) gegl_buffer_linear_open (temp2,
+ GEGL_RECTANGLE (0, 0,
+ rect->width,
+ rect->height),
+ &stride2, NULL);
+
+ /* calculate overall gradient */
+
+ for (i = 0; i < rect->height; i++)
+ {
+ const guint8 *datah = maxgrad_conv1 + stride1 * i;
+ const guint8 *datav = maxgrad_conv2 + stride2 * i;
+ guint8 *gradmap = (guint8 *) dest_buf + dest_stride * i;
+
+ for (j = 0; j < rect->width; j++)
+ {
+ gint8 hmax = datah[0] - 128;
+ gint8 vmax = datav[0] - 128;
+ gfloat gradient;
+ gint b;
+
+ for (b = 1; b < 4; b++)
+ {
+ if (abs (datah[b] - 128) > abs (hmax))
+ hmax = datah[b] - 128;
+
+ if (abs (datav[b] - 128) > abs (vmax))
+ vmax = datav[b] - 128;
+ }
+
+ if (i == 0 || j == 0 || i == rect->height - 1 || j == rect->width - 1)
+ {
+ gradmap[j * COST_WIDTH + 0] = 0;
+ gradmap[j * COST_WIDTH + 1] = 255;
+ goto contin;
+ }
+
+ /* 1 byte absolute magnitude first */
+ gradient = sqrt (SQR (hmax) + SQR (vmax));
+ gradmap[j * COST_WIDTH] = gradient * 255 / MAX_GRADIENT;
+
+ /* then 1 byte direction */
+ if (gradient > MIN_GRADIENT)
+ {
+ gfloat direction;
+
+ if (! hmax)
+ direction = (vmax > 0) ? G_PI_2 : -G_PI_2;
+ else
+ direction = atan ((gdouble) vmax / (gdouble) hmax);
+
+ /* Scale the direction from between 0 and 254,
+ * corresponding to -PI/2, PI/2 255 is reserved for
+ * directionless pixels
+ */
+ gradmap[j * COST_WIDTH + 1] =
+ (guint8) (254 * (direction + G_PI_2) / G_PI);
+ }
+ else
+ {
+ gradmap[j * COST_WIDTH + 1] = 255; /* reserved for weak gradient */
+ }
+
+ contin:
+ datah += 4;
+ datav += 4;
+ }
+ }
+
+ gegl_buffer_linear_close (temp1, maxgrad_conv1);
+ gegl_buffer_linear_close (temp2, maxgrad_conv2);
+
+ g_object_unref (temp0);
+ g_object_unref (temp1);
+ g_object_unref (temp2);
+}
+
+GeglTileHandler *
+gimp_tile_handler_iscissors_new (GimpPickable *pickable)
+{
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ return g_object_new (GIMP_TYPE_TILE_HANDLER_ISCISSORS,
+ "whole-tile", TRUE,
+ "pickable", pickable,
+ NULL);
+}
diff --git a/app/tools/gimptilehandleriscissors.h b/app/tools/gimptilehandleriscissors.h
new file mode 100644
index 0000000..a2c1916
--- /dev/null
+++ b/app/tools/gimptilehandleriscissors.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_TILE_HANDLER_ISCISSORS_H__
+#define __GIMP_TILE_HANDLER_ISCISSORS_H__
+
+
+#include "gegl/gimptilehandlervalidate.h"
+
+
+/***
+ * GimpTileHandlerIscissors is a GeglTileHandler that renders the
+ * Iscissors tool's gradmap.
+ */
+
+#define GIMP_TYPE_TILE_HANDLER_ISCISSORS (gimp_tile_handler_iscissors_get_type ())
+#define GIMP_TILE_HANDLER_ISCISSORS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TILE_HANDLER_ISCISSORS, GimpTileHandlerIscissors))
+#define GIMP_TILE_HANDLER_ISCISSORS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TILE_HANDLER_ISCISSORS, GimpTileHandlerIscissorsClass))
+#define GIMP_IS_TILE_HANDLER_ISCISSORS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TILE_HANDLER_ISCISSORS))
+#define GIMP_IS_TILE_HANDLER_ISCISSORS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TILE_HANDLER_ISCISSORS))
+#define GIMP_TILE_HANDLER_ISCISSORS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TILE_HANDLER_ISCISSORS, GimpTileHandlerIscissorsClass))
+
+
+typedef struct _GimpTileHandlerIscissors GimpTileHandlerIscissors;
+typedef struct _GimpTileHandlerIscissorsClass GimpTileHandlerIscissorsClass;
+
+struct _GimpTileHandlerIscissors
+{
+ GimpTileHandlerValidate parent_instance;
+
+ GimpPickable *pickable;
+};
+
+struct _GimpTileHandlerIscissorsClass
+{
+ GimpTileHandlerValidateClass parent_class;
+};
+
+
+GType gimp_tile_handler_iscissors_get_type (void) G_GNUC_CONST;
+
+GeglTileHandler * gimp_tile_handler_iscissors_new (GimpPickable *pickable);
+
+
+#endif /* __GIMP_TILE_HANDLER_ISCISSORS_H__ */
diff --git a/app/tools/gimptool-progress.c b/app/tools/gimptool-progress.c
new file mode 100644
index 0000000..09b0a9d
--- /dev/null
+++ b/app/tools/gimptool-progress.c
@@ -0,0 +1,260 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptool-progress.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 <gdk/gdkkeysyms.h>
+
+#include "tools-types.h"
+
+#include "core/gimpprogress.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasprogress.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-items.h"
+#include "display/gimpdisplayshell-transform.h"
+
+#include "gimptool.h"
+#include "gimptool-progress.h"
+
+
+/* local function prototypes */
+
+static GimpProgress * gimp_tool_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+static void gimp_tool_progress_end (GimpProgress *progress);
+static gboolean gimp_tool_progress_is_active (GimpProgress *progress);
+static void gimp_tool_progress_set_text (GimpProgress *progress,
+ const gchar *message);
+static void gimp_tool_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+static gdouble gimp_tool_progress_get_value (GimpProgress *progress);
+static void gimp_tool_progress_pulse (GimpProgress *progress);
+static gboolean gimp_tool_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+
+
+/* public functions */
+
+void
+gimp_tool_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_tool_progress_start;
+ iface->end = gimp_tool_progress_end;
+ iface->is_active = gimp_tool_progress_is_active;
+ iface->set_text = gimp_tool_progress_set_text;
+ iface->set_value = gimp_tool_progress_set_value;
+ iface->get_value = gimp_tool_progress_get_value;
+ iface->pulse = gimp_tool_progress_pulse;
+ iface->message = gimp_tool_progress_message;
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_tool_progress_button_press (GtkWidget *widget,
+ const GdkEventButton *bevent,
+ GimpTool *tool)
+{
+ if (bevent->type == GDK_BUTTON_PRESS &&
+ bevent->button == 1)
+ {
+ GtkWidget *event_widget;
+ GimpDisplayShell *shell;
+
+ event_widget = gtk_get_event_widget ((GdkEvent *) bevent);
+ shell = gimp_display_get_shell (tool->progress_display);
+
+ if (shell->canvas == event_widget)
+ {
+ gint x, y;
+
+ gimp_display_shell_unzoom_xy (shell, bevent->x, bevent->y,
+ &x, &y, FALSE);
+
+ if (gimp_canvas_item_hit (tool->progress, x, y))
+ {
+ gimp_progress_cancel (GIMP_PROGRESS (tool));
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_tool_progress_key_press (GtkWidget *widget,
+ const GdkEventKey *kevent,
+ GimpTool *tool)
+{
+ if (kevent->keyval == GDK_KEY_Escape)
+ {
+ gimp_progress_cancel (GIMP_PROGRESS (tool));
+ }
+
+ return TRUE;
+}
+
+static GimpProgress *
+gimp_tool_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ GimpTool *tool = GIMP_TOOL (progress);
+ GimpDisplayShell *shell;
+ gint x, y;
+
+ g_return_val_if_fail (GIMP_IS_DISPLAY (tool->display), NULL);
+ g_return_val_if_fail (tool->progress == NULL, NULL);
+
+ shell = gimp_display_get_shell (tool->display);
+
+ x = shell->disp_width / 2;
+ y = shell->disp_height / 2;
+
+ gimp_display_shell_unzoom_xy (shell, x, y, &x, &y, FALSE);
+
+ tool->progress = gimp_canvas_progress_new (shell,
+ GIMP_HANDLE_ANCHOR_CENTER,
+ x, y);
+ gimp_display_shell_add_unrotated_item (shell, tool->progress);
+ g_object_unref (tool->progress);
+
+ gimp_progress_start (GIMP_PROGRESS (tool->progress), FALSE,
+ "%s", message);
+ gimp_widget_flush_expose (shell->canvas);
+
+ tool->progress_display = tool->display;
+
+ if (cancellable)
+ {
+ tool->progress_grab_widget = gtk_invisible_new ();
+ gtk_widget_show (tool->progress_grab_widget);
+ gtk_grab_add (tool->progress_grab_widget);
+
+ g_signal_connect (tool->progress_grab_widget, "button-press-event",
+ G_CALLBACK (gimp_tool_progress_button_press),
+ tool);
+ g_signal_connect (tool->progress_grab_widget, "key-press-event",
+ G_CALLBACK (gimp_tool_progress_key_press),
+ tool);
+ }
+
+ return progress;
+}
+
+static void
+gimp_tool_progress_end (GimpProgress *progress)
+{
+ GimpTool *tool = GIMP_TOOL (progress);
+
+ if (tool->progress)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->progress_display);
+
+ gimp_progress_end (GIMP_PROGRESS (tool->progress));
+ gimp_display_shell_remove_unrotated_item (shell, tool->progress);
+
+ tool->progress = NULL;
+ tool->progress_display = NULL;
+
+ if (tool->progress_grab_widget)
+ {
+ gtk_grab_remove (tool->progress_grab_widget);
+ gtk_widget_destroy (tool->progress_grab_widget);
+ tool->progress_grab_widget = NULL;
+ }
+ }
+}
+
+static gboolean
+gimp_tool_progress_is_active (GimpProgress *progress)
+{
+ GimpTool *tool = GIMP_TOOL (progress);
+
+ return tool->progress != NULL;
+}
+
+static void
+gimp_tool_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpTool *tool = GIMP_TOOL (progress);
+
+ if (tool->progress)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->progress_display);
+
+ gimp_progress_set_text_literal (GIMP_PROGRESS (tool->progress), message);
+ gimp_widget_flush_expose (shell->canvas);
+ }
+}
+
+static void
+gimp_tool_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpTool *tool = GIMP_TOOL (progress);
+
+ if (tool->progress)
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->progress_display);
+
+ gimp_progress_set_value (GIMP_PROGRESS (tool->progress), percentage);
+ gimp_widget_flush_expose (shell->canvas);
+ }
+}
+
+static gdouble
+gimp_tool_progress_get_value (GimpProgress *progress)
+{
+ GimpTool *tool = GIMP_TOOL (progress);
+
+ if (tool->progress)
+ return gimp_progress_get_value (GIMP_PROGRESS (tool->progress));
+
+ return 0.0;
+}
+
+static void
+gimp_tool_progress_pulse (GimpProgress *progress)
+{
+}
+
+static gboolean
+gimp_tool_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ return FALSE;
+}
diff --git a/app/tools/gimptool-progress.h b/app/tools/gimptool-progress.h
new file mode 100644
index 0000000..7a34db6
--- /dev/null
+++ b/app/tools/gimptool-progress.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptool-progress.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_TOOL_PROGRESS_H__
+#define __GIMP_TOOL_PROGRESS_H__
+
+
+void gimp_tool_progress_iface_init (GimpProgressInterface *iface);
+
+
+#endif /* __GIMP_TOOL_PROGRESS */
diff --git a/app/tools/gimptool.c b/app/tools/gimptool.c
new file mode 100644
index 0000000..53981c1
--- /dev/null
+++ b/app/tools/gimptool.c
@@ -0,0 +1,1512 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis and others
+ *
+ * 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 "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpimage.h"
+#include "core/gimpprogress.h"
+#include "core/gimptoolinfo.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-cursor.h"
+#include "display/gimpstatusbar.h"
+
+#include "gimptool.h"
+#include "gimptool-progress.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+/* #define DEBUG_ACTIVE_STATE 1 */
+
+
+enum
+{
+ PROP_0,
+ PROP_TOOL_INFO
+};
+
+
+static void gimp_tool_constructed (GObject *object);
+static void gimp_tool_dispose (GObject *object);
+static void gimp_tool_finalize (GObject *object);
+static void gimp_tool_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gboolean gimp_tool_real_has_display (GimpTool *tool,
+ GimpDisplay *display);
+static GimpDisplay * gimp_tool_real_has_image (GimpTool *tool,
+ GimpImage *image);
+static gboolean gimp_tool_real_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_tool_real_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_tool_real_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_tool_real_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_tool_real_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_tool_real_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static gboolean gimp_tool_real_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_tool_real_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_tool_real_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_tool_real_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_tool_real_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static const gchar * gimp_tool_real_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+static const gchar * gimp_tool_real_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_tool_real_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_tool_real_redo (GimpTool *tool,
+ GimpDisplay *display);
+static GimpUIManager * gimp_tool_real_get_popup (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path);
+static void gimp_tool_real_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_tool_options_notify (GimpToolOptions *options,
+ const GParamSpec *pspec,
+ GimpTool *tool);
+static void gimp_tool_clear_status (GimpTool *tool);
+static void gimp_tool_release (GimpTool *tool);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpTool, gimp_tool, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
+ gimp_tool_progress_iface_init))
+
+#define parent_class gimp_tool_parent_class
+
+static gint global_tool_ID = 1;
+
+
+static void
+gimp_tool_class_init (GimpToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_tool_constructed;
+ object_class->dispose = gimp_tool_dispose;
+ object_class->finalize = gimp_tool_finalize;
+ object_class->set_property = gimp_tool_set_property;
+ object_class->get_property = gimp_tool_get_property;
+
+ klass->has_display = gimp_tool_real_has_display;
+ klass->has_image = gimp_tool_real_has_image;
+ klass->initialize = gimp_tool_real_initialize;
+ klass->control = gimp_tool_real_control;
+ klass->button_press = gimp_tool_real_button_press;
+ klass->button_release = gimp_tool_real_button_release;
+ klass->motion = gimp_tool_real_motion;
+ klass->key_press = gimp_tool_real_key_press;
+ klass->key_release = gimp_tool_real_key_release;
+ klass->modifier_key = gimp_tool_real_modifier_key;
+ klass->active_modifier_key = gimp_tool_real_active_modifier_key;
+ klass->oper_update = gimp_tool_real_oper_update;
+ klass->cursor_update = gimp_tool_real_cursor_update;
+ klass->can_undo = gimp_tool_real_can_undo;
+ klass->can_redo = gimp_tool_real_can_redo;
+ klass->undo = gimp_tool_real_undo;
+ klass->redo = gimp_tool_real_redo;
+ klass->get_popup = gimp_tool_real_get_popup;
+ klass->options_notify = gimp_tool_real_options_notify;
+
+ g_object_class_install_property (object_class, PROP_TOOL_INFO,
+ g_param_spec_object ("tool-info",
+ NULL, NULL,
+ GIMP_TYPE_TOOL_INFO,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_tool_init (GimpTool *tool)
+{
+ tool->tool_info = NULL;
+ tool->ID = global_tool_ID++;
+ tool->control = g_object_new (GIMP_TYPE_TOOL_CONTROL, NULL);
+ tool->display = NULL;
+ tool->drawable = NULL;
+ tool->focus_display = NULL;
+ tool->modifier_state = 0;
+ tool->active_modifier_state = 0;
+ tool->button_press_state = 0;
+}
+
+static void
+gimp_tool_constructed (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_TOOL_INFO (tool->tool_info));
+
+ g_signal_connect_object (gimp_tool_get_options (tool), "notify",
+ G_CALLBACK (gimp_tool_options_notify),
+ tool, 0);
+}
+
+static void
+gimp_tool_dispose (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_tool_finalize (GObject *object)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+
+ g_clear_object (&tool->tool_info);
+ g_clear_object (&tool->control);
+ g_clear_pointer (&tool->label, g_free);
+ g_clear_pointer (&tool->undo_desc, g_free);
+ g_clear_pointer (&tool->icon_name, g_free);
+ g_clear_pointer (&tool->help_id, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+
+ switch (property_id)
+ {
+ case PROP_TOOL_INFO:
+ tool->tool_info = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTool *tool = GIMP_TOOL (object);
+
+ switch (property_id)
+ {
+ case PROP_TOOL_INFO:
+ g_value_set_object (value, tool->tool_info);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* standard member functions */
+
+static gboolean
+gimp_tool_real_has_display (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return (display == tool->display ||
+ g_list_find (tool->status_displays, display));
+}
+
+static GimpDisplay *
+gimp_tool_real_has_image (GimpTool *tool,
+ GimpImage *image)
+{
+ if (tool->display)
+ {
+ if (image && gimp_display_get_image (tool->display) == image)
+ return tool->display;
+
+ /* NULL image means any display */
+ if (! image)
+ return tool->display;
+ }
+
+ return NULL;
+}
+
+static gboolean
+gimp_tool_real_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ return TRUE;
+}
+
+static void
+gimp_tool_real_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ tool->display = NULL;
+ tool->drawable = NULL;
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+}
+
+static void
+gimp_tool_real_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+ {
+ GimpImage *image = gimp_display_get_image (display);
+
+ tool->display = display;
+ tool->drawable = gimp_image_get_active_drawable (image);
+
+ gimp_tool_control_activate (tool->control);
+ }
+}
+
+static void
+gimp_tool_real_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ gimp_tool_control_halt (tool->control);
+}
+
+static void
+gimp_tool_real_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+}
+
+static gboolean
+gimp_tool_real_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ return FALSE;
+}
+
+static gboolean
+gimp_tool_real_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ return FALSE;
+}
+
+static void
+gimp_tool_real_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+}
+
+static void
+gimp_tool_real_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+}
+
+static void
+gimp_tool_real_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+}
+
+static void
+gimp_tool_real_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ gimp_tool_control_get_cursor_modifier (tool->control));
+}
+
+static const gchar *
+gimp_tool_real_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return NULL;
+}
+
+static const gchar *
+gimp_tool_real_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return NULL;
+}
+
+static gboolean
+gimp_tool_real_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return FALSE;
+}
+
+static gboolean
+gimp_tool_real_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ return FALSE;
+}
+
+static GimpUIManager *
+gimp_tool_real_get_popup (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path)
+{
+ *ui_path = NULL;
+
+ return NULL;
+}
+
+static void
+gimp_tool_real_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+}
+
+
+/* public functions */
+
+GimpToolOptions *
+gimp_tool_get_options (GimpTool *tool)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+ g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool->tool_info), NULL);
+
+ return tool->tool_info->tool_options;
+}
+
+void
+gimp_tool_set_label (GimpTool *tool,
+ const gchar *label)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ g_free (tool->label);
+ tool->label = g_strdup (label);
+}
+
+const gchar *
+gimp_tool_get_label (GimpTool *tool)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+
+ if (tool->label)
+ return tool->label;
+
+ return tool->tool_info->label;
+}
+
+void
+gimp_tool_set_undo_desc (GimpTool *tool,
+ const gchar *undo_desc)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ g_free (tool->undo_desc);
+ tool->undo_desc = g_strdup (undo_desc);
+}
+
+const gchar *
+gimp_tool_get_undo_desc (GimpTool *tool)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+
+ if (tool->undo_desc)
+ return tool->undo_desc;
+
+ return tool->tool_info->label;
+}
+
+void
+gimp_tool_set_icon_name (GimpTool *tool,
+ const gchar *icon_name)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ g_free (tool->icon_name);
+ tool->icon_name = g_strdup (icon_name);
+}
+
+const gchar *
+gimp_tool_get_icon_name (GimpTool *tool)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+
+ if (tool->icon_name)
+ return tool->icon_name;
+
+ return gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool->tool_info));
+}
+
+void
+gimp_tool_set_help_id (GimpTool *tool,
+ const gchar *help_id)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ g_free (tool->help_id);
+ tool->help_id = g_strdup (help_id);
+}
+
+const gchar *
+gimp_tool_get_help_id (GimpTool *tool)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+
+ if (tool->help_id)
+ return tool->help_id;
+
+ return tool->tool_info->help_id;
+}
+
+gboolean
+gimp_tool_has_display (GimpTool *tool,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ return GIMP_TOOL_GET_CLASS (tool)->has_display (tool, display);
+}
+
+GimpDisplay *
+gimp_tool_has_image (GimpTool *tool,
+ GimpImage *image)
+{
+ GimpDisplay *display;
+
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+ g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL);
+
+ display = GIMP_TOOL_GET_CLASS (tool)->has_image (tool, image);
+
+ /* check status displays last because they don't affect the tool
+ * itself (unlike tool->display or draw_tool->display)
+ */
+ if (! display && tool->status_displays)
+ {
+ GList *list;
+
+ for (list = tool->status_displays; list; list = g_list_next (list))
+ {
+ GimpDisplay *status_display = list->data;
+
+ if (gimp_display_get_image (status_display) == image)
+ return status_display;
+ }
+
+ /* NULL image means any display */
+ if (! image)
+ return tool->status_displays->data;
+ }
+
+ return display;
+}
+
+gboolean
+gimp_tool_initialize (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GError *error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ if (! GIMP_TOOL_GET_CLASS (tool)->initialize (tool, display, &error))
+ {
+ if (error)
+ {
+ gimp_tool_message_literal (tool, display, error->message);
+ g_clear_error (&error);
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+gimp_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ g_object_ref (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ if (! gimp_tool_control_is_paused (tool->control))
+ GIMP_TOOL_GET_CLASS (tool)->control (tool, action, display);
+
+ gimp_tool_control_pause (tool->control);
+ break;
+
+ case GIMP_TOOL_ACTION_RESUME:
+ if (gimp_tool_control_is_paused (tool->control))
+ {
+ gimp_tool_control_resume (tool->control);
+
+ if (! gimp_tool_control_is_paused (tool->control))
+ GIMP_TOOL_GET_CLASS (tool)->control (tool, action, display);
+ }
+ else
+ {
+ g_warning ("gimp_tool_control: unable to RESUME tool with "
+ "tool->control->paused_count == 0");
+ }
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_tool_release (tool);
+
+ GIMP_TOOL_GET_CLASS (tool)->control (tool, action, display);
+
+ /* always HALT after COMMIT here and not in each tool individually;
+ * some tools interact with their subclasses (e.g. filter tool
+ * and operation tool), and it's essential that COMMIT runs
+ * through the entire class hierarchy before HALT
+ */
+ action = GIMP_TOOL_ACTION_HALT;
+
+ /* pass through */
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_tool_release (tool);
+
+ GIMP_TOOL_GET_CLASS (tool)->control (tool, action, display);
+
+ if (gimp_tool_control_is_active (tool->control))
+ gimp_tool_control_halt (tool->control);
+
+ gimp_tool_clear_status (tool);
+ break;
+ }
+
+ g_object_unref (tool);
+}
+
+void
+gimp_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ GIMP_TOOL_GET_CLASS (tool)->button_press (tool, coords, time, state,
+ press_type, display);
+
+ if (press_type == GIMP_BUTTON_PRESS_NORMAL &&
+ gimp_tool_control_is_active (tool->control))
+ {
+ tool->button_press_state = state;
+ tool->active_modifier_state = state;
+
+ tool->last_pointer_coords = *coords;
+ tool->last_pointer_time = time - g_get_monotonic_time () / 1000;
+ tool->last_pointer_state = state;
+
+ if (gimp_tool_control_get_wants_click (tool->control))
+ {
+ tool->in_click_distance = TRUE;
+ tool->got_motion_event = FALSE;
+ tool->button_press_coords = *coords;
+ tool->button_press_time = time;
+ }
+ else
+ {
+ tool->in_click_distance = FALSE;
+ }
+ }
+}
+
+static gboolean
+gimp_tool_check_click_distance (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GimpDisplay *display)
+{
+ GimpDisplayShell *shell;
+ gint double_click_time;
+ gint double_click_distance;
+
+ if (! tool->in_click_distance)
+ return FALSE;
+
+ shell = gimp_display_get_shell (display);
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (shell)),
+ "gtk-double-click-time", &double_click_time,
+ "gtk-double-click-distance", &double_click_distance,
+ NULL);
+
+ if ((time - tool->button_press_time) > double_click_time)
+ {
+ tool->in_click_distance = FALSE;
+ }
+ else
+ {
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ gdouble dx;
+ gdouble dy;
+
+ dx = SCALEX (shell, tool->button_press_coords.x - coords->x);
+ dy = SCALEY (shell, tool->button_press_coords.y - coords->y);
+
+ if ((SQR (dx) + SQR (dy)) > SQR (double_click_distance))
+ {
+ tool->in_click_distance = FALSE;
+ }
+ }
+
+ return tool->in_click_distance;
+}
+
+void
+gimp_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpButtonReleaseType release_type = GIMP_BUTTON_RELEASE_NORMAL;
+ GimpCoords my_coords;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == TRUE);
+
+ g_object_ref (tool);
+
+ tool->last_pointer_state = 0;
+
+ my_coords = *coords;
+
+ if (state & GDK_BUTTON3_MASK)
+ {
+ release_type = GIMP_BUTTON_RELEASE_CANCEL;
+ }
+ else if (gimp_tool_control_get_wants_click (tool->control))
+ {
+ if (gimp_tool_check_click_distance (tool, coords, time, display))
+ {
+ release_type = GIMP_BUTTON_RELEASE_CLICK;
+ my_coords = tool->button_press_coords;
+
+ if (tool->got_motion_event)
+ {
+ /* if there has been a motion() since button_press(),
+ * synthesize a motion() back to the recorded press
+ * coordinates
+ */
+ GIMP_TOOL_GET_CLASS (tool)->motion (tool, &my_coords, time,
+ state & GDK_BUTTON1_MASK,
+ display);
+ }
+ }
+ else if (! tool->got_motion_event)
+ {
+ release_type = GIMP_BUTTON_RELEASE_NO_MOTION;
+ }
+ }
+
+ GIMP_TOOL_GET_CLASS (tool)->button_release (tool, &my_coords, time, state,
+ release_type, display);
+
+ g_warn_if_fail (gimp_tool_control_is_active (tool->control) == FALSE);
+
+ if (tool->active_modifier_state != 0 &&
+ gimp_tool_control_get_active_modifiers (tool->control) !=
+ GIMP_TOOL_ACTIVE_MODIFIERS_SAME)
+ {
+ gimp_tool_control_activate (tool->control);
+
+ gimp_tool_set_active_modifier_state (tool, 0, display);
+
+ gimp_tool_control_halt (tool->control);
+ }
+
+ tool->button_press_state = 0;
+ tool->active_modifier_state = 0;
+
+ g_object_unref (tool);
+}
+
+void
+gimp_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == TRUE);
+
+ tool->got_motion_event = TRUE;
+
+ tool->last_pointer_coords = *coords;
+ tool->last_pointer_time = time - g_get_monotonic_time () / 1000;
+ tool->last_pointer_state = state;
+
+ GIMP_TOOL_GET_CLASS (tool)->motion (tool, coords, time, state, display);
+}
+
+void
+gimp_tool_set_focus_display (GimpTool *tool,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (display == NULL || GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == FALSE);
+
+ GIMP_LOG (TOOL_FOCUS, "tool: %p focus_display: %p tool->focus_display: %p",
+ tool, display, tool->focus_display);
+
+ if (display != tool->focus_display)
+ {
+ if (tool->focus_display)
+ {
+ if (tool->active_modifier_state != 0)
+ {
+ gimp_tool_control_activate (tool->control);
+
+ gimp_tool_set_active_modifier_state (tool, 0, tool->focus_display);
+
+ gimp_tool_control_halt (tool->control);
+ }
+
+ if (tool->modifier_state != 0)
+ gimp_tool_set_modifier_state (tool, 0, tool->focus_display);
+ }
+
+ tool->focus_display = display;
+ }
+}
+
+gboolean
+gimp_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+ g_return_val_if_fail (display == tool->focus_display, FALSE);
+ g_return_val_if_fail (gimp_tool_control_is_active (tool->control) == FALSE,
+ FALSE);
+
+ return GIMP_TOOL_GET_CLASS (tool)->key_press (tool, kevent, display);
+}
+
+gboolean
+gimp_tool_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+ g_return_val_if_fail (display == tool->focus_display, FALSE);
+ g_return_val_if_fail (gimp_tool_control_is_active (tool->control) == FALSE,
+ FALSE);
+
+ return GIMP_TOOL_GET_CLASS (tool)->key_release (tool, kevent, display);
+}
+
+static void
+gimp_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (display == tool->focus_display);
+
+ GIMP_TOOL_GET_CLASS (tool)->modifier_key (tool, key, press, state, display);
+}
+
+static void
+gimp_tool_active_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (display == tool->focus_display);
+
+ GIMP_TOOL_GET_CLASS (tool)->active_modifier_key (tool, key, press, state,
+ display);
+}
+
+static gboolean
+state_changed (GdkModifierType old_state,
+ GdkModifierType new_state,
+ GdkModifierType modifier,
+ gboolean *press)
+{
+ if ((old_state & modifier) != (new_state & modifier))
+ {
+ *press = (new_state & modifier) ? TRUE : FALSE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_tool_set_modifier_state (GimpTool *tool,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ gboolean press;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == FALSE);
+
+ GIMP_LOG (TOOL_FOCUS, "tool: %p display: %p tool->focus_display: %p",
+ tool, display, tool->focus_display);
+
+ g_return_if_fail (display == tool->focus_display);
+
+ if (state_changed (tool->modifier_state, state, GDK_SHIFT_MASK, &press))
+ {
+ gimp_tool_modifier_key (tool, GDK_SHIFT_MASK,
+ press, state,
+ display);
+ }
+
+ if (state_changed (tool->modifier_state, state, GDK_CONTROL_MASK, &press))
+ {
+ gimp_tool_modifier_key (tool, GDK_CONTROL_MASK,
+ press, state,
+ display);
+ }
+
+ if (state_changed (tool->modifier_state, state, GDK_MOD1_MASK, &press))
+ {
+ gimp_tool_modifier_key (tool, GDK_MOD1_MASK,
+ press, state,
+ display);
+ }
+
+ if (state_changed (tool->modifier_state, state, GDK_MOD2_MASK, &press))
+ {
+ gimp_tool_modifier_key (tool, GDK_MOD2_MASK,
+ press, state,
+ display);
+ }
+
+ tool->modifier_state = state;
+}
+
+void
+gimp_tool_set_active_modifier_state (GimpTool *tool,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpToolActiveModifiers active_modifiers;
+ gboolean press;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == TRUE);
+
+ GIMP_LOG (TOOL_FOCUS, "tool: %p display: %p tool->focus_display: %p",
+ tool, display, tool->focus_display);
+
+ g_return_if_fail (display == tool->focus_display);
+
+ active_modifiers = gimp_tool_control_get_active_modifiers (tool->control);
+
+ if (state_changed (tool->active_modifier_state, state, GDK_SHIFT_MASK,
+ &press))
+ {
+#ifdef DEBUG_ACTIVE_STATE
+ g_printerr ("%s: SHIFT %s\n", G_STRFUNC,
+ press ? "pressed" : "released");
+#endif
+
+ switch (active_modifiers)
+ {
+ case GIMP_TOOL_ACTIVE_MODIFIERS_OFF:
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SAME:
+ gimp_tool_modifier_key (tool, GDK_SHIFT_MASK,
+ press, state,
+ display);
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE:
+ if (! press && (tool->button_press_state & GDK_SHIFT_MASK))
+ {
+ tool->button_press_state &= ~GDK_SHIFT_MASK;
+ }
+ else
+ {
+ gimp_tool_active_modifier_key (tool, GDK_SHIFT_MASK,
+ press, state,
+ display);
+ }
+ break;
+ }
+ }
+
+ if (state_changed (tool->active_modifier_state, state, GDK_CONTROL_MASK,
+ &press))
+ {
+#ifdef DEBUG_ACTIVE_STATE
+ g_printerr ("%s: CONTROL %s\n", G_STRFUNC,
+ press ? "pressed" : "released");
+#endif
+
+ switch (active_modifiers)
+ {
+ case GIMP_TOOL_ACTIVE_MODIFIERS_OFF:
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SAME:
+ gimp_tool_modifier_key (tool, GDK_CONTROL_MASK,
+ press, state,
+ display);
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE:
+ if (! press && (tool->button_press_state & GDK_CONTROL_MASK))
+ {
+ tool->button_press_state &= ~GDK_CONTROL_MASK;
+ }
+ else
+ {
+ gimp_tool_active_modifier_key (tool, GDK_CONTROL_MASK,
+ press, state,
+ display);
+ }
+ break;
+ }
+ }
+
+ if (state_changed (tool->active_modifier_state, state, GDK_MOD1_MASK,
+ &press))
+ {
+#ifdef DEBUG_ACTIVE_STATE
+ g_printerr ("%s: ALT %s\n", G_STRFUNC,
+ press ? "pressed" : "released");
+#endif
+
+ switch (active_modifiers)
+ {
+ case GIMP_TOOL_ACTIVE_MODIFIERS_OFF:
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SAME:
+ gimp_tool_modifier_key (tool, GDK_MOD1_MASK,
+ press, state,
+ display);
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE:
+ if (! press && (tool->button_press_state & GDK_MOD1_MASK))
+ {
+ tool->button_press_state &= ~GDK_MOD1_MASK;
+ }
+ else
+ {
+ gimp_tool_active_modifier_key (tool, GDK_MOD1_MASK,
+ press, state,
+ display);
+ }
+ break;
+ }
+ }
+
+ if (state_changed (tool->active_modifier_state, state, GDK_MOD2_MASK,
+ &press))
+ {
+#ifdef DEBUG_ACTIVE_STATE
+ g_printerr ("%s: MOD2 %s\n", G_STRFUNC,
+ press ? "pressed" : "released");
+#endif
+
+ switch (active_modifiers)
+ {
+ case GIMP_TOOL_ACTIVE_MODIFIERS_OFF:
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SAME:
+ gimp_tool_modifier_key (tool, GDK_MOD2_MASK,
+ press, state,
+ display);
+ break;
+
+ case GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE:
+ if (! press && (tool->button_press_state & GDK_MOD2_MASK))
+ {
+ tool->button_press_state &= ~GDK_MOD2_MASK;
+ }
+ else
+ {
+ gimp_tool_active_modifier_key (tool, GDK_MOD2_MASK,
+ press, state,
+ display);
+ }
+ break;
+ }
+ }
+
+ tool->active_modifier_state = state;
+
+ if (active_modifiers == GIMP_TOOL_ACTIVE_MODIFIERS_SAME)
+ tool->modifier_state = state;
+}
+
+void
+gimp_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == FALSE);
+
+ GIMP_TOOL_GET_CLASS (tool)->oper_update (tool, coords, state, proximity,
+ display);
+
+ if (G_UNLIKELY (gimp_image_is_empty (gimp_display_get_image (display)) &&
+ ! gimp_tool_control_get_handle_empty_image (tool->control)))
+ {
+ gimp_tool_replace_status (tool, display,
+ "%s",
+ _("Can't work on an empty image, "
+ "add a layer first"));
+ }
+}
+
+void
+gimp_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (coords != NULL);
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (gimp_tool_control_is_active (tool->control) == FALSE);
+
+ GIMP_TOOL_GET_CLASS (tool)->cursor_update (tool, coords, state, display);
+}
+
+const gchar *
+gimp_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ if (display == tool->display)
+ return GIMP_TOOL_GET_CLASS (tool)->can_undo (tool, display);
+
+ return NULL;
+}
+
+const gchar *
+gimp_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ if (display == tool->display)
+ return GIMP_TOOL_GET_CLASS (tool)->can_redo (tool, display);
+
+ return NULL;
+}
+
+gboolean
+gimp_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ if (gimp_tool_can_undo (tool, display))
+ return GIMP_TOOL_GET_CLASS (tool)->undo (tool, display);
+
+ return FALSE;
+}
+
+gboolean
+gimp_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ if (gimp_tool_can_redo (tool, display))
+ return GIMP_TOOL_GET_CLASS (tool)->redo (tool, display);
+
+ return FALSE;
+}
+
+GimpUIManager *
+gimp_tool_get_popup (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL (tool), NULL);
+ g_return_val_if_fail (coords != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+ g_return_val_if_fail (ui_path != NULL, NULL);
+
+ return GIMP_TOOL_GET_CLASS (tool)->get_popup (tool, coords, state, display,
+ ui_path);
+}
+
+void
+gimp_tool_push_status (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *format,
+ ...)
+{
+ GimpDisplayShell *shell;
+ const gchar *icon_name;
+ va_list args;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (format != NULL);
+
+ shell = gimp_display_get_shell (display);
+
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool->tool_info));
+
+ va_start (args, format);
+
+ gimp_statusbar_push_valist (gimp_display_shell_get_statusbar (shell),
+ G_OBJECT_TYPE_NAME (tool), icon_name,
+ format, args);
+
+ va_end (args);
+
+ tool->status_displays = g_list_remove (tool->status_displays, display);
+ tool->status_displays = g_list_prepend (tool->status_displays, display);
+}
+
+void
+gimp_tool_push_status_coords (GimpTool *tool,
+ GimpDisplay *display,
+ GimpCursorPrecision precision,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help)
+{
+ GimpDisplayShell *shell;
+ const gchar *icon_name;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ shell = gimp_display_get_shell (display);
+
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool->tool_info));
+
+ gimp_statusbar_push_coords (gimp_display_shell_get_statusbar (shell),
+ G_OBJECT_TYPE_NAME (tool), icon_name,
+ precision, title, x, separator, y,
+ help);
+
+ tool->status_displays = g_list_remove (tool->status_displays, display);
+ tool->status_displays = g_list_prepend (tool->status_displays, display);
+}
+
+void
+gimp_tool_push_status_length (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *title,
+ GimpOrientationType axis,
+ gdouble value,
+ const gchar *help)
+{
+ GimpDisplayShell *shell;
+ const gchar *icon_name;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ shell = gimp_display_get_shell (display);
+
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool->tool_info));
+
+ gimp_statusbar_push_length (gimp_display_shell_get_statusbar (shell),
+ G_OBJECT_TYPE_NAME (tool), icon_name,
+ title, axis, value, help);
+
+ tool->status_displays = g_list_remove (tool->status_displays, display);
+ tool->status_displays = g_list_prepend (tool->status_displays, display);
+}
+
+void
+gimp_tool_replace_status (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *format,
+ ...)
+{
+ GimpDisplayShell *shell;
+ const gchar *icon_name;
+ va_list args;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (format != NULL);
+
+ shell = gimp_display_get_shell (display);
+
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool->tool_info));
+
+ va_start (args, format);
+
+ gimp_statusbar_replace_valist (gimp_display_shell_get_statusbar (shell),
+ G_OBJECT_TYPE_NAME (tool), icon_name,
+ format, args);
+
+ va_end (args);
+
+ tool->status_displays = g_list_remove (tool->status_displays, display);
+ tool->status_displays = g_list_prepend (tool->status_displays, display);
+}
+
+void
+gimp_tool_pop_status (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpDisplayShell *shell;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ shell = gimp_display_get_shell (display);
+
+ gimp_statusbar_pop (gimp_display_shell_get_statusbar (shell),
+ G_OBJECT_TYPE_NAME (tool));
+
+ tool->status_displays = g_list_remove (tool->status_displays, display);
+}
+
+void
+gimp_tool_message (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+
+ gimp_message_valist (display->gimp, G_OBJECT (display),
+ GIMP_MESSAGE_WARNING, format, args);
+
+ va_end (args);
+}
+
+void
+gimp_tool_message_literal (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *message)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+ g_return_if_fail (message != NULL);
+
+ gimp_message_literal (display->gimp, G_OBJECT (display),
+ GIMP_MESSAGE_WARNING, message);
+}
+
+void
+gimp_tool_set_cursor (GimpTool *tool,
+ GimpDisplay *display,
+ GimpCursorType cursor,
+ GimpToolCursorType tool_cursor,
+ GimpCursorModifier modifier)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ gimp_display_shell_set_cursor (gimp_display_get_shell (display),
+ cursor, tool_cursor, modifier);
+}
+
+
+/* private functions */
+
+static void
+gimp_tool_options_notify (GimpToolOptions *options,
+ const GParamSpec *pspec,
+ GimpTool *tool)
+{
+ GIMP_TOOL_GET_CLASS (tool)->options_notify (tool, options, pspec);
+}
+
+static void
+gimp_tool_clear_status (GimpTool *tool)
+{
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ while (tool->status_displays)
+ gimp_tool_pop_status (tool, tool->status_displays->data);
+}
+
+static void
+gimp_tool_release (GimpTool *tool)
+{
+ if (tool->last_pointer_state &&
+ gimp_tool_control_is_active (tool->control))
+ {
+ gimp_tool_button_release (
+ tool,
+ &tool->last_pointer_coords,
+ tool->last_pointer_time + g_get_monotonic_time () / 1000,
+ tool->last_pointer_state,
+ tool->display);
+ }
+}
diff --git a/app/tools/gimptool.h b/app/tools/gimptool.h
new file mode 100644
index 0000000..c7943fd
--- /dev/null
+++ b/app/tools/gimptool.h
@@ -0,0 +1,299 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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_H__
+#define __GIMP_TOOL_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_TOOL (gimp_tool_get_type ())
+#define GIMP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL, GimpTool))
+#define GIMP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL, GimpToolClass))
+#define GIMP_IS_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL))
+#define GIMP_IS_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL))
+#define GIMP_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL, GimpToolClass))
+
+#define GIMP_TOOL_GET_OPTIONS(t) (gimp_tool_get_options (GIMP_TOOL (t)))
+
+
+typedef struct _GimpToolClass GimpToolClass;
+
+struct _GimpTool
+{
+ GimpObject parent_instance;
+
+ GimpToolInfo *tool_info;
+
+ gchar *label;
+ gchar *undo_desc;
+ gchar *icon_name;
+ gchar *help_id;
+
+ gint ID; /* unique tool ID */
+
+ GimpToolControl *control;
+
+ GimpDisplay *display; /* pointer to currently active display */
+ GimpDrawable *drawable; /* pointer to the tool's current drawable */
+
+ /* private state of gimp_tool_set_focus_display() and
+ * gimp_tool_set_[active_]modifier_state()
+ */
+ GimpDisplay *focus_display;
+ GdkModifierType modifier_state;
+ GdkModifierType button_press_state;
+ GdkModifierType active_modifier_state;
+
+ /* private state for synthesizing button_release() events
+ */
+ GimpCoords last_pointer_coords;
+ guint32 last_pointer_time;
+ GdkModifierType last_pointer_state;
+
+ /* private state for click detection
+ */
+ gboolean in_click_distance;
+ gboolean got_motion_event;
+ GimpCoords button_press_coords;
+ guint32 button_press_time;
+
+ /* private list of displays which have a status message from this tool
+ */
+ GList *status_displays;
+
+ /* on-canvas progress */
+ GimpCanvasItem *progress;
+ GimpDisplay *progress_display;
+ GtkWidget *progress_grab_widget;
+};
+
+struct _GimpToolClass
+{
+ GimpObjectClass parent_class;
+
+ /* virtual functions */
+
+ gboolean (* has_display) (GimpTool *tool,
+ GimpDisplay *display);
+ GimpDisplay * (* has_image) (GimpTool *tool,
+ GimpImage *image);
+
+ gboolean (* initialize) (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+ void (* control) (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+
+ void (* button_press) (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+ void (* button_release) (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+ void (* motion) (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+ gboolean (* key_press) (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+ gboolean (* key_release) (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+ void (* modifier_key) (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+ void (* active_modifier_key) (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+ void (* oper_update) (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+ void (* cursor_update) (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+ const gchar * (* can_undo) (GimpTool *tool,
+ GimpDisplay *display);
+ const gchar * (* can_redo) (GimpTool *tool,
+ GimpDisplay *display);
+ gboolean (* undo) (GimpTool *tool,
+ GimpDisplay *display);
+ gboolean (* redo) (GimpTool *tool,
+ GimpDisplay *display);
+
+ GimpUIManager * (* get_popup) (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path);
+
+ void (* options_notify) (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+};
+
+
+GType gimp_tool_get_type (void) G_GNUC_CONST;
+
+GimpToolOptions * gimp_tool_get_options (GimpTool *tool);
+
+void gimp_tool_set_label (GimpTool *tool,
+ const gchar *label);
+const gchar * gimp_tool_get_label (GimpTool *tool);
+
+void gimp_tool_set_undo_desc (GimpTool *tool,
+ const gchar *undo_desc);
+const gchar * gimp_tool_get_undo_desc (GimpTool *tool);
+
+void gimp_tool_set_icon_name (GimpTool *tool,
+ const gchar *icon_name);
+const gchar * gimp_tool_get_icon_name (GimpTool *tool);
+
+void gimp_tool_set_help_id (GimpTool *tool,
+ const gchar *help_id);
+const gchar * gimp_tool_get_help_id (GimpTool *tool);
+
+gboolean gimp_tool_has_display (GimpTool *tool,
+ GimpDisplay *display);
+GimpDisplay * gimp_tool_has_image (GimpTool *tool,
+ GimpImage *image);
+
+gboolean gimp_tool_initialize (GimpTool *tool,
+ GimpDisplay *display);
+void gimp_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+
+void gimp_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+void gimp_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+void gimp_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+gboolean gimp_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+gboolean gimp_tool_key_release (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+
+void gimp_tool_set_focus_display (GimpTool *tool,
+ GimpDisplay *display);
+void gimp_tool_set_modifier_state (GimpTool *tool,
+ GdkModifierType state,
+ GimpDisplay *display);
+void gimp_tool_set_active_modifier_state (GimpTool *tool,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+void gimp_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+void gimp_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+const gchar * gimp_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+const gchar * gimp_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+gboolean gimp_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+gboolean gimp_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+
+GimpUIManager * gimp_tool_get_popup (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path);
+
+void gimp_tool_push_status (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(3,4);
+void gimp_tool_push_status_coords (GimpTool *tool,
+ GimpDisplay *display,
+ GimpCursorPrecision precision,
+ const gchar *title,
+ gdouble x,
+ const gchar *separator,
+ gdouble y,
+ const gchar *help);
+void gimp_tool_push_status_length (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *title,
+ GimpOrientationType axis,
+ gdouble value,
+ const gchar *help);
+void gimp_tool_replace_status (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(3,4);
+void gimp_tool_pop_status (GimpTool *tool,
+ GimpDisplay *display);
+
+void gimp_tool_message (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *format,
+ ...) G_GNUC_PRINTF(3,4);
+void gimp_tool_message_literal (GimpTool *tool,
+ GimpDisplay *display,
+ const gchar *message);
+
+void gimp_tool_set_cursor (GimpTool *tool,
+ GimpDisplay *display,
+ GimpCursorType cursor,
+ GimpToolCursorType tool_cursor,
+ GimpCursorModifier modifier);
+
+
+#endif /* __GIMP_TOOL_H__ */
diff --git a/app/tools/gimptoolcontrol.c b/app/tools/gimptoolcontrol.c
new file mode 100644
index 0000000..d8b1386
--- /dev/null
+++ b/app/tools/gimptoolcontrol.c
@@ -0,0 +1,727 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis and others
+ *
+ * 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 "tools-types.h"
+
+#include "gimptoolcontrol.h"
+
+
+static void gimp_tool_control_finalize (GObject *object);
+
+
+G_DEFINE_TYPE (GimpToolControl, gimp_tool_control, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_tool_control_parent_class
+
+
+static void
+gimp_tool_control_class_init (GimpToolControlClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_tool_control_finalize;
+}
+
+static void
+gimp_tool_control_init (GimpToolControl *control)
+{
+ control->active = FALSE;
+ control->paused_count = 0;
+
+ control->preserve = TRUE;
+ control->scroll_lock = FALSE;
+ control->handle_empty_image = FALSE;
+
+ control->dirty_mask = GIMP_DIRTY_NONE;
+ control->dirty_action = GIMP_TOOL_ACTION_HALT;
+ control->motion_mode = GIMP_MOTION_MODE_COMPRESS;
+
+ control->auto_snap_to = TRUE;
+ control->snap_offset_x = 0;
+ control->snap_offset_y = 0;
+ control->snap_width = 0;
+ control->snap_height = 0;
+
+ control->precision = GIMP_CURSOR_PRECISION_PIXEL_CENTER;
+
+ control->toggled = FALSE;
+
+ control->wants_click = FALSE;
+ control->wants_double_click = FALSE;
+ control->wants_triple_click = FALSE;
+ control->wants_all_key_events = FALSE;
+
+ control->active_modifiers = GIMP_TOOL_ACTIVE_MODIFIERS_OFF;
+
+ control->cursor = GIMP_CURSOR_MOUSE;
+ control->tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ control->cursor_modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ control->toggle_cursor = -1;
+ control->toggle_tool_cursor = -1;
+ control->toggle_cursor_modifier = -1;
+}
+
+static void
+gimp_tool_control_finalize (GObject *object)
+{
+ GimpToolControl *control = GIMP_TOOL_CONTROL (object);
+
+ g_slist_free (control->preserve_stack);
+
+ g_free (control->action_opacity);
+ g_free (control->action_size);
+ g_free (control->action_aspect);
+ g_free (control->action_angle);
+ g_free (control->action_spacing);
+ g_free (control->action_hardness);
+ g_free (control->action_force);
+ g_free (control->action_object_1);
+ g_free (control->action_object_2);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+/* public functions */
+
+void
+gimp_tool_control_activate (GimpToolControl *control)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+ g_return_if_fail (control->active == FALSE);
+
+ control->active = TRUE;
+}
+
+void
+gimp_tool_control_halt (GimpToolControl *control)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+ g_return_if_fail (control->active == TRUE);
+
+ control->active = FALSE;
+}
+
+gboolean
+gimp_tool_control_is_active (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->active;
+}
+
+void
+gimp_tool_control_pause (GimpToolControl *control)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->paused_count++;
+}
+
+void
+gimp_tool_control_resume (GimpToolControl *control)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+ g_return_if_fail (control->paused_count > 0);
+
+ control->paused_count--;
+}
+
+gboolean
+gimp_tool_control_is_paused (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->paused_count > 0;
+}
+
+void
+gimp_tool_control_set_preserve (GimpToolControl *control,
+ gboolean preserve)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->preserve = preserve ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_preserve (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->preserve;
+}
+
+void
+gimp_tool_control_push_preserve (GimpToolControl *control,
+ gboolean preserve)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->preserve_stack =
+ g_slist_prepend (control->preserve_stack,
+ GINT_TO_POINTER (control->preserve));
+
+ control->preserve = preserve ? TRUE : FALSE;
+}
+
+void
+gimp_tool_control_pop_preserve (GimpToolControl *control)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+ g_return_if_fail (control->preserve_stack != NULL);
+
+ control->preserve = GPOINTER_TO_INT (control->preserve_stack->data);
+
+ control->preserve_stack = g_slist_delete_link (control->preserve_stack,
+ control->preserve_stack);
+}
+
+void
+gimp_tool_control_set_scroll_lock (GimpToolControl *control,
+ gboolean scroll_lock)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->scroll_lock = scroll_lock ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_scroll_lock (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->scroll_lock;
+}
+
+void
+gimp_tool_control_set_handle_empty_image (GimpToolControl *control,
+ gboolean handle_empty)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->handle_empty_image = handle_empty ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_handle_empty_image (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->handle_empty_image;
+}
+
+void
+gimp_tool_control_set_dirty_mask (GimpToolControl *control,
+ GimpDirtyMask dirty_mask)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->dirty_mask = dirty_mask;
+}
+
+GimpDirtyMask
+gimp_tool_control_get_dirty_mask (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), GIMP_DIRTY_NONE);
+
+ return control->dirty_mask;
+}
+
+void
+gimp_tool_control_set_dirty_action (GimpToolControl *control,
+ GimpToolAction action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->dirty_action = action;
+}
+
+GimpToolAction
+gimp_tool_control_get_dirty_action (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), GIMP_TOOL_ACTION_HALT);
+
+ return control->dirty_action;
+}
+
+void
+gimp_tool_control_set_motion_mode (GimpToolControl *control,
+ GimpMotionMode motion_mode)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->motion_mode = motion_mode;
+}
+
+GimpMotionMode
+gimp_tool_control_get_motion_mode (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), GIMP_MOTION_MODE_EXACT);
+
+ return control->motion_mode;
+}
+
+void
+gimp_tool_control_set_snap_to (GimpToolControl *control,
+ gboolean snap_to)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->auto_snap_to = snap_to ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_snap_to (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->auto_snap_to;
+}
+
+void
+gimp_tool_control_set_wants_click (GimpToolControl *control,
+ gboolean wants_click)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->wants_click = wants_click ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_wants_click (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->wants_click;
+}
+
+void
+gimp_tool_control_set_wants_double_click (GimpToolControl *control,
+ gboolean wants_double_click)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->wants_double_click = wants_double_click ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_wants_double_click (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->wants_double_click;
+}
+
+void
+gimp_tool_control_set_wants_triple_click (GimpToolControl *control,
+ gboolean wants_triple_click)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->wants_triple_click = wants_triple_click ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_wants_triple_click (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->wants_triple_click;
+}
+
+void
+gimp_tool_control_set_wants_all_key_events (GimpToolControl *control,
+ gboolean wants_key_events)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->wants_all_key_events = wants_key_events ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_wants_all_key_events (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->wants_all_key_events;
+}
+
+void
+gimp_tool_control_set_active_modifiers (GimpToolControl *control,
+ GimpToolActiveModifiers active_modifiers)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->active_modifiers = active_modifiers;
+}
+
+GimpToolActiveModifiers
+gimp_tool_control_get_active_modifiers (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control),
+ GIMP_TOOL_ACTIVE_MODIFIERS_OFF);
+
+ return control->active_modifiers;
+}
+
+void
+gimp_tool_control_set_snap_offsets (GimpToolControl *control,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->snap_offset_x = offset_x;
+ control->snap_offset_y = offset_y;
+ control->snap_width = width;
+ control->snap_height = height;
+}
+
+void
+gimp_tool_control_get_snap_offsets (GimpToolControl *control,
+ gint *offset_x,
+ gint *offset_y,
+ gint *width,
+ gint *height)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (offset_x) *offset_x = control->snap_offset_x;
+ if (offset_y) *offset_y = control->snap_offset_y;
+ if (width) *width = control->snap_width;
+ if (height) *height = control->snap_height;
+}
+
+void
+gimp_tool_control_set_precision (GimpToolControl *control,
+ GimpCursorPrecision precision)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->precision = precision;
+}
+
+GimpCursorPrecision
+gimp_tool_control_get_precision (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control),
+ GIMP_CURSOR_PRECISION_PIXEL_CENTER);
+
+ return control->precision;
+}
+
+void
+gimp_tool_control_set_toggled (GimpToolControl *control,
+ gboolean toggled)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->toggled = toggled ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_control_get_toggled (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ return control->toggled;
+}
+
+void
+gimp_tool_control_set_cursor (GimpToolControl *control,
+ GimpCursorType cursor)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->cursor = cursor;
+}
+
+void
+gimp_tool_control_set_tool_cursor (GimpToolControl *control,
+ GimpToolCursorType cursor)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->tool_cursor = cursor;
+}
+
+void
+gimp_tool_control_set_cursor_modifier (GimpToolControl *control,
+ GimpCursorModifier modifier)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->cursor_modifier = modifier;
+}
+
+void
+gimp_tool_control_set_toggle_cursor (GimpToolControl *control,
+ GimpCursorType cursor)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->toggle_cursor = cursor;
+}
+
+void
+gimp_tool_control_set_toggle_tool_cursor (GimpToolControl *control,
+ GimpToolCursorType cursor)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->toggle_tool_cursor = cursor;
+}
+
+void
+gimp_tool_control_set_toggle_cursor_modifier (GimpToolControl *control,
+ GimpCursorModifier modifier)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ control->toggle_cursor_modifier = modifier;
+}
+
+GimpCursorType
+gimp_tool_control_get_cursor (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ if (control->toggled && control->toggle_cursor != -1)
+ return control->toggle_cursor;
+
+ return control->cursor;
+}
+
+GimpToolCursorType
+gimp_tool_control_get_tool_cursor (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ if (control->toggled && control->toggle_tool_cursor != -1)
+ return control->toggle_tool_cursor;
+
+ return control->tool_cursor;
+}
+
+GimpCursorModifier
+gimp_tool_control_get_cursor_modifier (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), FALSE);
+
+ if (control->toggled && control->toggle_cursor_modifier != -1)
+ return control->toggle_cursor_modifier;
+
+ return control->cursor_modifier;
+}
+
+void
+gimp_tool_control_set_action_opacity (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_opacity)
+ {
+ g_free (control->action_opacity);
+ control->action_opacity = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_opacity (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_opacity;
+}
+
+void
+gimp_tool_control_set_action_size (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_size)
+ {
+ g_free (control->action_size);
+ control->action_size = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_size (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_size;
+}
+
+void
+gimp_tool_control_set_action_aspect (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_aspect)
+ {
+ g_free (control->action_aspect);
+ control->action_aspect = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_aspect (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_aspect;
+}
+
+void
+gimp_tool_control_set_action_angle (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_angle)
+ {
+ g_free (control->action_angle);
+ control->action_angle = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_angle (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_angle;
+}
+
+void
+gimp_tool_control_set_action_spacing (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_spacing)
+ {
+ g_free (control->action_spacing);
+ control->action_spacing = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_spacing (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_spacing;
+}
+
+void
+gimp_tool_control_set_action_hardness (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_hardness)
+ {
+ g_free (control->action_hardness);
+ control->action_hardness = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_hardness (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_hardness;
+}
+
+void
+gimp_tool_control_set_action_force (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_force)
+ {
+ g_free (control->action_force);
+ control->action_force = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_force (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_force;
+}
+
+void
+gimp_tool_control_set_action_object_1 (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_object_1)
+ {
+ g_free (control->action_object_1);
+ control->action_object_1 = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_object_1 (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_object_1;
+}
+
+void
+gimp_tool_control_set_action_object_2 (GimpToolControl *control,
+ const gchar *action)
+{
+ g_return_if_fail (GIMP_IS_TOOL_CONTROL (control));
+
+ if (action != control->action_object_2)
+ {
+ g_free (control->action_object_2);
+ control->action_object_2 = g_strdup (action);
+ }
+}
+
+const gchar *
+gimp_tool_control_get_action_object_2 (GimpToolControl *control)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_CONTROL (control), NULL);
+
+ return control->action_object_2;
+}
diff --git a/app/tools/gimptoolcontrol.h b/app/tools/gimptoolcontrol.h
new file mode 100644
index 0000000..8551806
--- /dev/null
+++ b/app/tools/gimptoolcontrol.h
@@ -0,0 +1,254 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis and others
+ *
+ * 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_CONTROL_H__
+#define __GIMP_TOOL_CONTROL_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_TOOL_CONTROL (gimp_tool_control_get_type ())
+#define GIMP_TOOL_CONTROL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_CONTROL, GimpToolControl))
+#define GIMP_TOOL_CONTROL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_CONTROL, GimpToolControlClass))
+#define GIMP_IS_TOOL_CONTROL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_CONTROL))
+#define GIMP_IS_TOOL_CONTROL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_CONTROL))
+#define GIMP_TOOL_CONTROL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_CONTROL, GimpToolControlClass))
+
+
+typedef struct _GimpToolControlClass GimpToolControlClass;
+
+
+struct _GimpToolControl
+{
+ GimpObject parent_instance;
+
+ gboolean active; /* state of tool activity */
+ gint paused_count; /* paused control count */
+
+ gboolean preserve; /* Preserve this tool across *
+ * drawable changes */
+ GSList *preserve_stack; /* for push/pop preserve */
+
+ gboolean scroll_lock; /* allow scrolling or not */
+ gboolean handle_empty_image; /* invoke the tool on images *
+ * without active drawable */
+ GimpDirtyMask dirty_mask; /* if preserve is FALSE, stop *
+ * the tool on these events */
+ GimpToolAction dirty_action; /* use this action to stop the *
+ * tool when one of the dirty *
+ * events occurs */
+ GimpMotionMode motion_mode; /* how to process motion events *
+ * before they go to the tool */
+ gboolean auto_snap_to; /* snap to guides automatically */
+ gint snap_offset_x;
+ gint snap_offset_y;
+ gint snap_width;
+ gint snap_height;
+
+ GimpCursorPrecision precision;
+
+ gboolean wants_click; /* wants click detection */
+ gboolean wants_double_click;
+ gboolean wants_triple_click;
+ gboolean wants_all_key_events;
+
+ GimpToolActiveModifiers active_modifiers;
+
+ gboolean toggled;
+
+ GimpCursorType cursor;
+ GimpToolCursorType tool_cursor;
+ GimpCursorModifier cursor_modifier;
+
+ GimpCursorType toggle_cursor;
+ GimpToolCursorType toggle_tool_cursor;
+ GimpCursorModifier toggle_cursor_modifier;
+
+ gchar *action_opacity;
+ gchar *action_size;
+ gchar *action_aspect;
+ gchar *action_angle;
+ gchar *action_spacing;
+ gchar *action_hardness;
+ gchar *action_force;
+ gchar *action_object_1;
+ gchar *action_object_2;
+};
+
+struct _GimpToolControlClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_tool_control_get_type (void) G_GNUC_CONST;
+
+void gimp_tool_control_activate (GimpToolControl *control);
+void gimp_tool_control_halt (GimpToolControl *control);
+gboolean gimp_tool_control_is_active (GimpToolControl *control);
+
+void gimp_tool_control_pause (GimpToolControl *control);
+void gimp_tool_control_resume (GimpToolControl *control);
+gboolean gimp_tool_control_is_paused (GimpToolControl *control);
+
+void gimp_tool_control_set_preserve (GimpToolControl *control,
+ gboolean preserve);
+gboolean gimp_tool_control_get_preserve (GimpToolControl *control);
+
+void gimp_tool_control_push_preserve (GimpToolControl *control,
+ gboolean preserve);
+void gimp_tool_control_pop_preserve (GimpToolControl *control);
+
+void gimp_tool_control_set_scroll_lock (GimpToolControl *control,
+ gboolean scroll_lock);
+gboolean gimp_tool_control_get_scroll_lock (GimpToolControl *control);
+
+void gimp_tool_control_set_handle_empty_image
+ (GimpToolControl *control,
+ gboolean handle_empty);
+gboolean gimp_tool_control_get_handle_empty_image
+ (GimpToolControl *control);
+
+void gimp_tool_control_set_dirty_mask (GimpToolControl *control,
+ GimpDirtyMask dirty_mask);
+GimpDirtyMask gimp_tool_control_get_dirty_mask (GimpToolControl *control);
+
+void gimp_tool_control_set_dirty_action (GimpToolControl *control,
+ GimpToolAction action);
+GimpToolAction gimp_tool_control_get_dirty_action (GimpToolControl *control);
+
+void gimp_tool_control_set_motion_mode (GimpToolControl *control,
+ GimpMotionMode motion_mode);
+GimpMotionMode gimp_tool_control_get_motion_mode (GimpToolControl *control);
+
+void gimp_tool_control_set_snap_to (GimpToolControl *control,
+ gboolean snap_to);
+gboolean gimp_tool_control_get_snap_to (GimpToolControl *control);
+
+void gimp_tool_control_set_wants_click (GimpToolControl *control,
+ gboolean wants_click);
+gboolean gimp_tool_control_get_wants_click (GimpToolControl *control);
+
+void gimp_tool_control_set_wants_double_click
+ (GimpToolControl *control,
+ gboolean wants_double_click);
+gboolean gimp_tool_control_get_wants_double_click
+ (GimpToolControl *control);
+
+void gimp_tool_control_set_wants_triple_click
+ (GimpToolControl *control,
+ gboolean wants_double_click);
+gboolean gimp_tool_control_get_wants_triple_click
+ (GimpToolControl *control);
+
+void gimp_tool_control_set_wants_all_key_events
+ (GimpToolControl *control,
+ gboolean wants_key_events);
+gboolean gimp_tool_control_get_wants_all_key_events
+ (GimpToolControl *control);
+
+void gimp_tool_control_set_active_modifiers
+ (GimpToolControl *control,
+ GimpToolActiveModifiers active_modifiers);
+GimpToolActiveModifiers
+ gimp_tool_control_get_active_modifiers
+ (GimpToolControl *control);
+
+void gimp_tool_control_set_snap_offsets (GimpToolControl *control,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height);
+void gimp_tool_control_get_snap_offsets (GimpToolControl *control,
+ gint *offset_x,
+ gint *offset_y,
+ gint *width,
+ gint *height);
+
+void gimp_tool_control_set_precision (GimpToolControl *control,
+ GimpCursorPrecision precision);
+GimpCursorPrecision
+ gimp_tool_control_get_precision (GimpToolControl *control);
+
+void gimp_tool_control_set_toggled (GimpToolControl *control,
+ gboolean toggled);
+gboolean gimp_tool_control_get_toggled (GimpToolControl *control);
+
+void gimp_tool_control_set_cursor (GimpToolControl *control,
+ GimpCursorType cursor);
+void gimp_tool_control_set_tool_cursor (GimpToolControl *control,
+ GimpToolCursorType cursor);
+void gimp_tool_control_set_cursor_modifier
+ (GimpToolControl *control,
+ GimpCursorModifier modifier);
+void gimp_tool_control_set_toggle_cursor (GimpToolControl *control,
+ GimpCursorType cursor);
+void gimp_tool_control_set_toggle_tool_cursor
+ (GimpToolControl *control,
+ GimpToolCursorType cursor);
+void gimp_tool_control_set_toggle_cursor_modifier
+ (GimpToolControl *control,
+ GimpCursorModifier modifier);
+
+GimpCursorType
+ gimp_tool_control_get_cursor (GimpToolControl *control);
+
+GimpToolCursorType
+ gimp_tool_control_get_tool_cursor (GimpToolControl *control);
+
+GimpCursorModifier
+ gimp_tool_control_get_cursor_modifier (GimpToolControl *control);
+
+void gimp_tool_control_set_action_opacity (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_opacity (GimpToolControl *control);
+
+void gimp_tool_control_set_action_size (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_size (GimpToolControl *control);
+
+void gimp_tool_control_set_action_aspect (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_aspect (GimpToolControl *control);
+
+void gimp_tool_control_set_action_angle (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_angle (GimpToolControl *control);
+
+void gimp_tool_control_set_action_spacing (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_spacing (GimpToolControl *control);
+
+void gimp_tool_control_set_action_hardness (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_hardness (GimpToolControl *control);
+
+void gimp_tool_control_set_action_force (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_force (GimpToolControl *control);
+
+void gimp_tool_control_set_action_object_1 (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_object_1 (GimpToolControl *control);
+
+void gimp_tool_control_set_action_object_2 (GimpToolControl *control,
+ const gchar *action);
+const gchar * gimp_tool_control_get_action_object_2 (GimpToolControl *control);
+
+
+#endif /* __GIMP_TOOL_CONTROL_H__ */
diff --git a/app/tools/gimptooloptions-gui.c b/app/tools/gimptooloptions-gui.c
new file mode 100644
index 0000000..7e0ef3e
--- /dev/null
+++ b/app/tools/gimptooloptions-gui.c
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimptooloptions.h"
+
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+GtkWidget *
+gimp_tool_options_gui (GimpToolOptions *tool_options)
+{
+ GtkWidget *vbox;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_OPTIONS (tool_options), NULL);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
+
+ return vbox;
+}
+
+GtkWidget *
+gimp_tool_options_empty_gui (GimpToolOptions *tool_options)
+{
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *label;
+
+ label = gtk_label_new (_("This tool has\nno options."));
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 6);
+ gtk_widget_show (label);
+
+ return vbox;
+}
diff --git a/app/tools/gimptooloptions-gui.h b/app/tools/gimptooloptions-gui.h
new file mode 100644
index 0000000..5847e92
--- /dev/null
+++ b/app/tools/gimptooloptions-gui.h
@@ -0,0 +1,26 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 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_TOOL_OPTIONS_GUI_H__
+#define __GIMP_TOOL_OPTIONS_GUI_H__
+
+
+GtkWidget * gimp_tool_options_gui (GimpToolOptions *tool_options);
+GtkWidget * gimp_tool_options_empty_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_TOOL_OPTIONS_GUI_H__ */
diff --git a/app/tools/gimptools-utils.c b/app/tools/gimptools-utils.c
new file mode 100644
index 0000000..27fe0b6
--- /dev/null
+++ b/app/tools/gimptools-utils.c
@@ -0,0 +1,80 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis and others
+ *
+ * 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 "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpchannel.h"
+#include "core/gimplayer.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "widgets/gimpdialogfactory.h"
+#include "widgets/gimpitemtreeview.h"
+#include "widgets/gimpwidgets-utils.h"
+#include "widgets/gimpwindowstrategy.h"
+
+#include "gimptools-utils.h"
+
+
+/* public functions */
+
+void
+gimp_tools_blink_lock_box (Gimp *gimp,
+ GimpItem *item)
+{
+ GtkWidget *dockable;
+ GimpItemTreeView *view;
+ GdkScreen *screen;
+ gint monitor;
+ const gchar *identifier;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ if (GIMP_IS_LAYER (item))
+ identifier = "gimp-layer-list";
+ else if (GIMP_IS_CHANNEL (item))
+ identifier = "gimp-channel-list";
+ else if (GIMP_IS_VECTORS (item))
+ identifier = "gimp-vectors-list";
+ else
+ return;
+
+ monitor = gimp_get_monitor_at_pointer (&screen);
+
+ dockable = gimp_window_strategy_show_dockable_dialog (
+ GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (gimp)),
+ gimp,
+ gimp_dialog_factory_get_singleton (),
+ screen, monitor,
+ identifier);
+
+ if (! dockable)
+ return;
+
+ view = GIMP_ITEM_TREE_VIEW (gtk_bin_get_child (GTK_BIN (dockable)));
+
+ gimp_widget_blink (gimp_item_tree_view_get_lock_box (view));
+}
diff --git a/app/tools/gimptools-utils.h b/app/tools/gimptools-utils.h
new file mode 100644
index 0000000..5810f19
--- /dev/null
+++ b/app/tools/gimptools-utils.h
@@ -0,0 +1,26 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis and others
+ *
+ * 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_TOOLS_UTILS_H__
+#define __GIMP_TOOLS_UTILS_H__
+
+
+void gimp_tools_blink_lock_box (Gimp *gimp,
+ GimpItem *item);
+
+
+#endif /* __GIMP_TOOLS_UTILS_H__ */
diff --git a/app/tools/gimptransform3doptions.c b/app/tools/gimptransform3doptions.c
new file mode 100644
index 0000000..bede62b
--- /dev/null
+++ b/app/tools/gimptransform3doptions.c
@@ -0,0 +1,225 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimptransform3doptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_MODE,
+ PROP_UNIFIED,
+ PROP_CONSTRAIN_AXIS,
+ PROP_Z_AXIS,
+ PROP_LOCAL_FRAME
+};
+
+
+static void gimp_transform_3d_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_transform_3d_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpTransform3DOptions, gimp_transform_3d_options,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS)
+
+#define parent_class gimp_transform_3d_options_parent_class
+
+
+static void
+gimp_transform_3d_options_class_init (GimpTransform3DOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_transform_3d_options_set_property;
+ object_class->get_property = gimp_transform_3d_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_MODE,
+ "mode",
+ _("Mode"),
+ _("Transform mode"),
+ GIMP_TYPE_TRANSFORM_3D_MODE,
+ GIMP_TRANSFORM_3D_MODE_ROTATE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_UNIFIED,
+ "unified",
+ _("Unified interaction"),
+ _("Combine all interaction modes"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONSTRAIN_AXIS,
+ "constrain-axis",
+ NULL,
+ _("Constrain transformation to a single axis"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_Z_AXIS,
+ "z-axis",
+ NULL,
+ _("Transform along the Z axis"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_LOCAL_FRAME,
+ "local-frame",
+ NULL,
+ _("Transform in the local frame of reference"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_transform_3d_options_init (GimpTransform3DOptions *options)
+{
+}
+
+static void
+gimp_transform_3d_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransform3DOptions *options = GIMP_TRANSFORM_3D_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_MODE:
+ options->mode = g_value_get_enum (value);
+ break;
+ case PROP_UNIFIED:
+ options->unified = g_value_get_boolean (value);
+ break;
+
+ case PROP_CONSTRAIN_AXIS:
+ options->constrain_axis = g_value_get_boolean (value);
+ break;
+ case PROP_Z_AXIS:
+ options->z_axis = g_value_get_boolean (value);
+ break;
+ case PROP_LOCAL_FRAME:
+ options->local_frame = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_transform_3d_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransform3DOptions *options = GIMP_TRANSFORM_3D_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_MODE:
+ g_value_set_enum (value, options->mode);
+ break;
+ case PROP_UNIFIED:
+ g_value_set_boolean (value, options->unified);
+ break;
+
+ case PROP_CONSTRAIN_AXIS:
+ g_value_set_boolean (value, options->constrain_axis);
+ break;
+ case PROP_Z_AXIS:
+ g_value_set_boolean (value, options->z_axis);
+ break;
+ case PROP_LOCAL_FRAME:
+ g_value_set_boolean (value, options->local_frame);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_transform_3d_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_transform_grid_options_gui (tool_options);
+ GtkWidget *button;
+ gchar *label;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType constrain_mask = gimp_get_constrain_behavior_mask ();
+
+ button = gimp_prop_check_button_new (config, "unified", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ label = g_strdup_printf (_("Constrain axis (%s)"),
+ gimp_get_mod_string (extend_mask));
+
+ button = gimp_prop_check_button_new (config, "constrain-axis", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_free (label);
+
+ label = g_strdup_printf (_("Z axis (%s)"),
+ gimp_get_mod_string (constrain_mask));
+
+ button = gimp_prop_check_button_new (config, "z-axis", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_free (label);
+
+ label = g_strdup_printf (_("Local frame (%s)"),
+ gimp_get_mod_string (GDK_MOD1_MASK));
+
+ button = gimp_prop_check_button_new (config, "local-frame", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_free (label);
+
+ return vbox;
+}
diff --git a/app/tools/gimptransform3doptions.h b/app/tools/gimptransform3doptions.h
new file mode 100644
index 0000000..d0fd3e0
--- /dev/null
+++ b/app/tools/gimptransform3doptions.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_TRANSFORM_3D_OPTIONS_H__
+#define __GIMP_TRANSFORM_3D_OPTIONS_H__
+
+
+#include "gimptransformgridoptions.h"
+
+
+#define GIMP_TYPE_TRANSFORM_3D_OPTIONS (gimp_transform_3d_options_get_type ())
+#define GIMP_TRANSFORM_3D_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_3D_OPTIONS, GimpTransform3DOptions))
+#define GIMP_TRANSFORM_3D_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_3D_OPTIONS, GimpTransform3DOptionsClass))
+#define GIMP_IS_TRANSFORM_3D_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_3D_OPTIONS))
+#define GIMP_IS_TRANSFORM_3D_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSFORM_3D_OPTIONS))
+#define GIMP_TRANSFORM_3D_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_3D_OPTIONS, GimpTransform3DOptionsClass))
+
+
+typedef struct _GimpTransform3DOptions GimpTransform3DOptions;
+typedef struct _GimpTransform3DOptionsClass GimpTransform3DOptionsClass;
+
+struct _GimpTransform3DOptions
+{
+ GimpTransformGridOptions parent_instance;
+
+ GimpTransform3DMode mode;
+ gboolean unified;
+
+ gboolean constrain_axis;
+ gboolean z_axis;
+ gboolean local_frame;
+};
+
+struct _GimpTransform3DOptionsClass
+{
+ GimpTransformGridOptionsClass parent_class;
+};
+
+
+GType gimp_transform_3d_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_transform_3d_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_TRANSFORM_3D_OPTIONS_H__ */
diff --git a/app/tools/gimptransform3dtool.c b/app/tools/gimptransform3dtool.c
new file mode 100644
index 0000000..a572452
--- /dev/null
+++ b/app/tools/gimptransform3dtool.c
@@ -0,0 +1,1036 @@
+/* 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 "tools-types.h"
+
+#include "core/gimp-transform-3d-utils.h"
+#include "core/gimpimage.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimppivotselector.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptooltransform3dgrid.h"
+
+#include "gimptoolcontrol.h"
+#include "gimptransform3doptions.h"
+#include "gimptransform3dtool.h"
+
+#include "gimp-intl.h"
+
+
+/* index into trans_info array */
+enum
+{
+ VANISHING_POINT_X,
+ VANISHING_POINT_Y,
+ LENS_MODE,
+ LENS_VALUE,
+ OFFSET_X,
+ OFFSET_Y,
+ OFFSET_Z,
+ ROTATION_ORDER,
+ ANGLE_X,
+ ANGLE_Y,
+ ANGLE_Z,
+ PIVOT_X,
+ PIVOT_Y,
+ PIVOT_Z
+};
+
+
+/* local function prototypes */
+
+static void gimp_transform_3d_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static gboolean gimp_transform_3d_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+static void gimp_transform_3d_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_transform_3d_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_transform_3d_tool_prepare (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_transform_3d_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_transform_3d_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_transform_3d_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_3d_tool_dialog_changed (GObject *object,
+ GimpTransform3DTool *t3d);
+static void gimp_transform_3d_tool_lens_mode_changed (GtkComboBox *combo,
+ GimpTransform3DTool *t3d);
+static void gimp_transform_3d_tool_rotation_order_clicked (GtkButton *button,
+ GimpTransform3DTool *t3d);
+static void gimp_transform_3d_tool_pivot_changed (GimpPivotSelector *selector,
+ GimpTransform3DTool *t3d);
+
+static gdouble gimp_transform_3d_tool_get_focal_length (GimpTransform3DTool *t3d);
+
+
+G_DEFINE_TYPE (GimpTransform3DTool, gimp_transform_3d_tool, GIMP_TYPE_TRANSFORM_GRID_TOOL)
+
+#define parent_class gimp_transform_3d_tool_parent_class
+
+
+void
+gimp_transform_3d_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_TRANSFORM_3D_TOOL,
+ GIMP_TYPE_TRANSFORM_3D_OPTIONS,
+ gimp_transform_3d_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-transform-3d-tool",
+ _("3D Transform"),
+ _("3D Transform Tool: Apply a 3D transformation to the layer, selection or path"),
+ N_("_3D Transform"), "<shift>W",
+ NULL, GIMP_ICON_TOOL_TRANSFORM_3D,
+ GIMP_ICON_TOOL_TRANSFORM_3D,
+ data);
+}
+
+static void
+gimp_transform_3d_tool_class_init (GimpTransform3DToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+
+ tool_class->modifier_key = gimp_transform_3d_tool_modifier_key;
+
+ tg_class->info_to_matrix = gimp_transform_3d_tool_info_to_matrix;
+ tg_class->dialog = gimp_transform_3d_tool_dialog;
+ tg_class->dialog_update = gimp_transform_3d_tool_dialog_update;
+ tg_class->prepare = gimp_transform_3d_tool_prepare;
+ tg_class->get_widget = gimp_transform_3d_tool_get_widget;
+ tg_class->update_widget = gimp_transform_3d_tool_update_widget;
+ tg_class->widget_changed = gimp_transform_3d_tool_widget_changed;
+
+ tr_class->undo_desc = C_("undo-type", "3D Transform");
+ tr_class->progress_text = _("3D transformation");
+}
+
+static void
+gimp_transform_3d_tool_init (GimpTransform3DTool *t3d)
+{
+}
+
+static void
+gimp_transform_3d_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransform3DOptions *options = GIMP_TRANSFORM_3D_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_extend_selection_mask ())
+ {
+ g_object_set (options,
+ "constrain-axis", ! options->constrain_axis,
+ NULL);
+ }
+ else if (key == gimp_get_constrain_behavior_mask ())
+ {
+ g_object_set (options,
+ "z-axis", ! options->z_axis,
+ NULL);
+ }
+ else if (key == GDK_MOD1_MASK)
+ {
+ g_object_set (options,
+ "local-frame", ! options->local_frame,
+ NULL);
+ }
+}
+
+static gboolean
+gimp_transform_3d_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ GimpTransform3DTool *t3d = GIMP_TRANSFORM_3D_TOOL (tg_tool);
+
+ gimp_transform_3d_matrix (transform,
+
+ tg_tool->trans_info[VANISHING_POINT_X],
+ tg_tool->trans_info[VANISHING_POINT_Y],
+ -gimp_transform_3d_tool_get_focal_length (t3d),
+
+ tg_tool->trans_info[OFFSET_X],
+ tg_tool->trans_info[OFFSET_Y],
+ tg_tool->trans_info[OFFSET_Z],
+
+ tg_tool->trans_info[ROTATION_ORDER],
+ tg_tool->trans_info[ANGLE_X],
+ tg_tool->trans_info[ANGLE_Y],
+ tg_tool->trans_info[ANGLE_Z],
+
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ tg_tool->trans_info[PIVOT_Z]);
+
+ return TRUE;
+}
+
+static void
+gimp_transform_3d_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+ GimpTransform3DTool *t3d = GIMP_TRANSFORM_3D_TOOL (tg_tool);
+ GimpTransform3DOptions *options = GIMP_TRANSFORM_3D_TOOL_GET_OPTIONS (tg_tool);
+ GtkWidget *notebook;
+ GtkWidget *label;
+ GtkWidget *vbox;
+ GtkWidget *frame;
+ GtkWidget *vbox2;
+ GtkWidget *se;
+ GtkWidget *spinbutton;
+ GtkWidget *combo;
+ GtkWidget *scale;
+ GtkWidget *table;
+ GtkWidget *button;
+ GtkWidget *selector;
+ gint i;
+
+ /* main notebook */
+ notebook = gtk_notebook_new ();
+ gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tg_tool->gui)),
+ notebook, FALSE, FALSE, 0);
+ gtk_widget_show (notebook);
+
+ t3d->notebook = notebook;
+
+ /* camera page */
+ label = gtk_image_new_from_icon_name (GIMP_ICON_TRANSFORM_3D_CAMERA,
+ GTK_ICON_SIZE_MENU);
+ gimp_help_set_help_data (label, _("Camera"), NULL);
+ gtk_widget_show (label);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label);
+ gtk_widget_show (vbox);
+
+ /* vanishing-point frame */
+ frame = gimp_frame_new (_("Vanishing Point"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* vanishing-point size entry */
+ se = gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a", TRUE, TRUE, FALSE, 6,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gtk_table_set_row_spacings (GTK_TABLE (se), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (se), 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), se, FALSE, FALSE, 0);
+ gtk_widget_show (se);
+
+ t3d->vanishing_point_se = se;
+
+ spinbutton = gimp_spin_button_new_with_range (0.0, 0.0, 1.0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 6);
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (se),
+ GTK_SPIN_BUTTON (spinbutton), NULL);
+ gtk_table_attach_defaults (GTK_TABLE (se), spinbutton, 1, 2, 0, 1);
+ gtk_widget_show (spinbutton);
+
+ gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (se), _("_X:"), 0, 0, 0.0);
+ gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (se), _("_Y:"), 1, 0, 0.0);
+
+ gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (se), 0, 2);
+ gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (se), 1, 2);
+
+ g_signal_connect (se, "value-changed",
+ G_CALLBACK (gimp_transform_3d_tool_dialog_changed),
+ t3d);
+
+ /* lens frame */
+ frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* lens-mode combo */
+ combo = gimp_enum_combo_box_new (GIMP_TYPE_TRANSFORM_3D_LENS_MODE);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), combo);
+ gtk_widget_show (combo);
+
+ t3d->lens_mode_combo = combo;
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_transform_3d_tool_lens_mode_changed),
+ t3d);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* focal-length size entry */
+ se = gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a", TRUE, FALSE, FALSE, 6,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gtk_table_set_col_spacings (GTK_TABLE (se), 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), se, FALSE, FALSE, 0);
+
+ t3d->focal_length_se = se;
+
+ gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (se), 0, 2);
+
+ gimp_size_entry_set_value_boundaries (GIMP_SIZE_ENTRY (se), 0,
+ 0.0, G_MAXDOUBLE);
+
+ g_signal_connect (se, "value-changed",
+ G_CALLBACK (gimp_transform_3d_tool_dialog_changed),
+ t3d);
+
+ /* angle-of-view spin scale */
+ t3d->angle_of_view_adj = (GtkAdjustment *)
+ gtk_adjustment_new (0, 0, 180, 1, 10, 0);
+ scale = gimp_spin_scale_new (t3d->angle_of_view_adj,
+ _("Angle"), 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ t3d->angle_of_view_scale = scale;
+
+ g_signal_connect (t3d->angle_of_view_adj, "value-changed",
+ G_CALLBACK (gimp_transform_3d_tool_dialog_changed),
+ t3d);
+
+ /* move page */
+ label = gtk_image_new_from_icon_name (GIMP_ICON_TRANSFORM_3D_MOVE,
+ GTK_ICON_SIZE_MENU);
+ gimp_help_set_help_data (label, _("Move"), NULL);
+ gtk_widget_show (label);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label);
+ gtk_widget_show (vbox);
+
+ /* offset frame */
+ frame = gimp_frame_new (_("Offset"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* offset size entry */
+ se = gimp_size_entry_new (1, GIMP_UNIT_PIXEL, "%a", TRUE, TRUE, FALSE, 6,
+ GIMP_SIZE_ENTRY_UPDATE_SIZE);
+ gtk_table_set_row_spacings (GTK_TABLE (se), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (se), 2);
+ gtk_box_pack_start (GTK_BOX (vbox2), se, FALSE, FALSE, 0);
+ gtk_widget_show (se);
+
+ t3d->offset_se = se;
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_table_attach_defaults (GTK_TABLE (se), table, 0, 2, 0, 1);
+ gtk_widget_show (table);
+
+ spinbutton = gimp_spin_button_new_with_range (0.0, 0.0, 1.0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 6);
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (se),
+ GTK_SPIN_BUTTON (spinbutton), NULL);
+ gtk_table_attach_defaults (GTK_TABLE (table), spinbutton, 1, 2, 1, 2);
+ gtk_widget_show (spinbutton);
+
+ spinbutton = gimp_spin_button_new_with_range (0.0, 0.0, 1.0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 6);
+ gimp_size_entry_add_field (GIMP_SIZE_ENTRY (se),
+ GTK_SPIN_BUTTON (spinbutton), NULL);
+ gtk_table_attach_defaults (GTK_TABLE (table), spinbutton, 1, 2, 0, 1);
+ gtk_widget_show (spinbutton);
+
+ label = gtk_label_new_with_mnemonic (_("_X:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
+ GTK_SHRINK, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ label = gtk_label_new_with_mnemonic (_("_Y:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
+ GTK_SHRINK, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ label = gtk_label_new_with_mnemonic (_("_Z:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (se), label, 0, 1, 1, 2,
+ GTK_SHRINK, GTK_SHRINK, 0, 0);
+ gtk_widget_show (label);
+
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (se), 0,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (se), 1,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+ gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (se), 2,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+
+ gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (se), 0, 2);
+ gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (se), 1, 2);
+ gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (se), 2, 2);
+
+ g_signal_connect (se, "value-changed",
+ G_CALLBACK (gimp_transform_3d_tool_dialog_changed),
+ t3d);
+
+ /* rotate page */
+ label = gtk_image_new_from_icon_name (GIMP_ICON_TRANSFORM_3D_ROTATE,
+ GTK_ICON_SIZE_MENU);
+ gimp_help_set_help_data (label, _("Rotate"), NULL);
+ gtk_widget_show (label);
+
+ /* angle frame */
+ frame = gimp_frame_new (_("Angle"));
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (3, 3, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ for (i = 0; i < 3; i++)
+ {
+ const gchar *labels[3] = {_("X"), _("Y"), _("Z")};
+
+ /* rotation-order button */
+ button = gtk_button_new ();
+ gimp_help_set_help_data (button, _("Rotation axis order"), NULL);
+ gtk_table_attach (GTK_TABLE (table), button, 0, 1, i, i + 1,
+ GTK_SHRINK, GTK_FILL, 0, 0);
+ gtk_widget_show (button);
+
+ t3d->rotation_order_buttons[i] = button;
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (gimp_transform_3d_tool_rotation_order_clicked),
+ t3d);
+
+ /* angle spin scale */
+ t3d->angle_adj[i] = (GtkAdjustment *)
+ gtk_adjustment_new (0, -180, 180, 1, 10, 0);
+ scale = gimp_spin_scale_new (t3d->angle_adj[i],
+ labels[i], 2);
+ gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (scale), TRUE);
+ gtk_table_attach (GTK_TABLE (table), scale, 1, 2, i, i + 1,
+ GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
+ gtk_widget_show (scale);
+
+ g_signal_connect (t3d->angle_adj[i], "value-changed",
+ G_CALLBACK (gimp_transform_3d_tool_dialog_changed),
+ t3d);
+ }
+
+ /* pivot selector */
+ selector = gimp_pivot_selector_new (0.0, 0.0, 0.0, 0.0);
+ gtk_table_attach (GTK_TABLE (table), selector, 2, 3, 0, 3,
+ GTK_SHRINK, GTK_SHRINK, 0, 0);
+ gtk_widget_show (selector);
+
+ t3d->pivot_selector = selector;
+
+ g_signal_connect (selector, "changed",
+ G_CALLBACK (gimp_transform_3d_tool_pivot_changed),
+ t3d);
+
+ g_object_bind_property (options, "mode",
+ t3d->notebook, "page",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+}
+
+static void
+gimp_transform_3d_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ GimpTransform3DTool *t3d = GIMP_TRANSFORM_3D_TOOL (tg_tool);
+ Gimp3DTrasnformLensMode lens_mode;
+ gint permutation[3];
+ gint i;
+
+ t3d->updating = TRUE;
+
+ /* vanishing-point size entry */
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (t3d->vanishing_point_se),
+ 0, tg_tool->trans_info[VANISHING_POINT_X]);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (t3d->vanishing_point_se),
+ 1, tg_tool->trans_info[VANISHING_POINT_Y]);
+
+ lens_mode = tg_tool->trans_info[LENS_MODE];
+
+ /* lens-mode combo */
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (t3d->lens_mode_combo),
+ lens_mode);
+
+ /* focal-length size entry / angle-of-view spin scale */
+ gtk_widget_set_visible (t3d->focal_length_se,
+ lens_mode ==
+ GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH);
+ gtk_widget_set_visible (t3d->angle_of_view_scale,
+ lens_mode !=
+ GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH);
+
+ switch (lens_mode)
+ {
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH:
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (t3d->focal_length_se),
+ 0, tg_tool->trans_info[LENS_VALUE]);
+ break;
+
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE:
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM:
+ gtk_adjustment_set_value (
+ t3d->angle_of_view_adj,
+ gimp_rad_to_deg (tg_tool->trans_info[LENS_VALUE]));
+ break;
+ }
+
+ /* offset size entry */
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (t3d->offset_se),
+ 0, tg_tool->trans_info[OFFSET_X]);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (t3d->offset_se),
+ 1, tg_tool->trans_info[OFFSET_Y]);
+ gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (t3d->offset_se),
+ 2, tg_tool->trans_info[OFFSET_Z]);
+
+ /* rotation-order buttons */
+ gimp_transform_3d_rotation_order_to_permutation (
+ tg_tool->trans_info[ROTATION_ORDER], permutation);
+
+ for (i = 0; i < 3; i++)
+ {
+ gchar *label;
+
+ label = g_strdup_printf ("%d", i + 1);
+
+ gtk_button_set_label (
+ GTK_BUTTON (t3d->rotation_order_buttons[permutation[i]]), label);
+
+ g_free (label);
+ }
+
+ /* angle spin scales */
+ gtk_adjustment_set_value (t3d->angle_adj[0],
+ gimp_rad_to_deg (tg_tool->trans_info[ANGLE_X]));
+ gtk_adjustment_set_value (t3d->angle_adj[1],
+ gimp_rad_to_deg (tg_tool->trans_info[ANGLE_Y]));
+ gtk_adjustment_set_value (t3d->angle_adj[2],
+ gimp_rad_to_deg (tg_tool->trans_info[ANGLE_Z]));
+
+ /* pivot selector */
+ gimp_pivot_selector_set_position (GIMP_PIVOT_SELECTOR (t3d->pivot_selector),
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y]);
+
+ t3d->updating = FALSE;
+}
+
+static void
+gimp_transform_3d_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransform3DTool *t3d = GIMP_TRANSFORM_3D_TOOL (tg_tool);
+ GimpDisplay *display = tool->display;
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpSizeEntry *se;
+ gdouble xres;
+ gdouble yres;
+ gint width;
+ gint height;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+
+ tg_tool->trans_info[VANISHING_POINT_X] = (tr_tool->x1 + tr_tool->x2) / 2.0;
+ tg_tool->trans_info[VANISHING_POINT_Y] = (tr_tool->y1 + tr_tool->y2) / 2.0;
+
+ tg_tool->trans_info[LENS_MODE] = GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM;
+ tg_tool->trans_info[LENS_VALUE] = gimp_deg_to_rad (45.0);
+
+ tg_tool->trans_info[OFFSET_X] = 0.0;
+ tg_tool->trans_info[OFFSET_Y] = 0.0;
+ tg_tool->trans_info[OFFSET_Z] = 0.0;
+
+ tg_tool->trans_info[ROTATION_ORDER] =
+ gimp_transform_3d_permutation_to_rotation_order ((const gint[]) {0, 1, 2});
+ tg_tool->trans_info[ANGLE_X] = 0.0;
+ tg_tool->trans_info[ANGLE_Y] = 0.0;
+ tg_tool->trans_info[ANGLE_Z] = 0.0;
+
+ tg_tool->trans_info[PIVOT_X] = (tr_tool->x1 + tr_tool->x2) / 2.0;
+ tg_tool->trans_info[PIVOT_Y] = (tr_tool->y1 + tr_tool->y2) / 2.0;
+ tg_tool->trans_info[PIVOT_Z] = 0.0;
+
+ t3d->updating = TRUE;
+
+ /* vanishing-point size entry */
+ se = GIMP_SIZE_ENTRY (t3d->vanishing_point_se);
+
+ gimp_size_entry_set_unit (se, shell->unit);
+
+ gimp_size_entry_set_resolution (se, 0, xres, FALSE);
+ gimp_size_entry_set_resolution (se, 1, yres, FALSE);
+
+ gimp_size_entry_set_refval_boundaries (se, 0,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+ gimp_size_entry_set_refval_boundaries (se, 1,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE);
+
+ gimp_size_entry_set_size (se, 0, tr_tool->x1, tr_tool->x2);
+ gimp_size_entry_set_size (se, 1, tr_tool->y1, tr_tool->y2);
+
+ /* focal-length size entry */
+ se = GIMP_SIZE_ENTRY (t3d->focal_length_se);
+
+ gimp_size_entry_set_unit (se, shell->unit);
+
+ gimp_size_entry_set_resolution (se, 0, width >= height ? xres : yres, FALSE);
+
+ /* offset size entry */
+ se = GIMP_SIZE_ENTRY (t3d->offset_se);
+
+ gimp_size_entry_set_unit (se, shell->unit);
+
+ gimp_size_entry_set_resolution (se, 0, xres, FALSE);
+ gimp_size_entry_set_resolution (se, 1, yres, FALSE);
+ gimp_size_entry_set_resolution (se, 2, width >= height ? xres : yres, FALSE);
+
+ gimp_size_entry_set_size (se, 0, 0, width);
+ gimp_size_entry_set_size (se, 1, 0, height);
+ gimp_size_entry_set_size (se, 2, 0, MAX (width, height));
+
+ /* pivot selector */
+ gimp_pivot_selector_set_bounds (GIMP_PIVOT_SELECTOR (t3d->pivot_selector),
+ tr_tool->x1, tr_tool->y1,
+ tr_tool->x2, tr_tool->y2);
+
+ t3d->updating = FALSE;
+}
+
+static GimpToolWidget *
+gimp_transform_3d_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransform3DTool *t3d = GIMP_TRANSFORM_3D_TOOL (tg_tool);
+ GimpTransform3DOptions *options = GIMP_TRANSFORM_3D_TOOL_GET_OPTIONS (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+ gint i;
+
+ static const gchar *bound_properties[] =
+ {
+ "mode",
+ "unified",
+ "constrain-axis",
+ "z-axis",
+ "local-frame",
+ };
+
+ widget = gimp_tool_transform_3d_grid_new (
+ shell,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2,
+ tg_tool->trans_info[VANISHING_POINT_X],
+ tg_tool->trans_info[VANISHING_POINT_Y],
+ -gimp_transform_3d_tool_get_focal_length (t3d));
+
+ for (i = 0; i < G_N_ELEMENTS (bound_properties); i++)
+ {
+ g_object_bind_property (options, bound_properties[i],
+ widget, bound_properties[i],
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+ }
+
+ return widget;
+}
+
+static void
+gimp_transform_3d_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransform3DTool *t3d = GIMP_TRANSFORM_3D_TOOL (tg_tool);
+ GimpMatrix3 transform;
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ gimp_transform_grid_tool_info_to_matrix (tg_tool, &transform);
+
+ g_object_set (
+ tg_tool->widget,
+
+ "x1", (gdouble) tr_tool->x1,
+ "y1", (gdouble) tr_tool->y1,
+ "x2", (gdouble) tr_tool->x2,
+ "y2", (gdouble) tr_tool->y2,
+
+ "camera-x", tg_tool->trans_info[VANISHING_POINT_X],
+ "camera-y", tg_tool->trans_info[VANISHING_POINT_Y],
+ "camera-z", -gimp_transform_3d_tool_get_focal_length (t3d),
+
+ "offset-x", tg_tool->trans_info[OFFSET_X],
+ "offset-y", tg_tool->trans_info[OFFSET_Y],
+ "offset-z", tg_tool->trans_info[OFFSET_Z],
+
+ "rotation-order", (gint) tg_tool->trans_info[ROTATION_ORDER],
+ "angle-x", tg_tool->trans_info[ANGLE_X],
+ "angle-y", tg_tool->trans_info[ANGLE_Y],
+ "angle-z", tg_tool->trans_info[ANGLE_Z],
+
+ "pivot-3d-x", tg_tool->trans_info[PIVOT_X],
+ "pivot-3d-y", tg_tool->trans_info[PIVOT_Y],
+ "pivot-3d-z", tg_tool->trans_info[PIVOT_Z],
+
+ "transform", &transform,
+
+ NULL);
+}
+
+static void
+gimp_transform_3d_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ g_object_get (
+ tg_tool->widget,
+
+ "camera-x", &tg_tool->trans_info[VANISHING_POINT_X],
+ "camera-y", &tg_tool->trans_info[VANISHING_POINT_Y],
+
+ "offset-x", &tg_tool->trans_info[OFFSET_X],
+ "offset-y", &tg_tool->trans_info[OFFSET_Y],
+ "offset-z", &tg_tool->trans_info[OFFSET_Z],
+
+ "angle-x", &tg_tool->trans_info[ANGLE_X],
+ "angle-y", &tg_tool->trans_info[ANGLE_Y],
+ "angle-z", &tg_tool->trans_info[ANGLE_Z],
+
+ NULL);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+gimp_transform_3d_tool_dialog_changed (GObject *object,
+ GimpTransform3DTool *t3d)
+{
+ GimpTool *tool = GIMP_TOOL (t3d);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (t3d);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (t3d);
+
+ if (t3d->updating)
+ return;
+
+ /* vanishing-point size entry */
+ tg_tool->trans_info[VANISHING_POINT_X] = gimp_size_entry_get_refval (
+ GIMP_SIZE_ENTRY (t3d->vanishing_point_se), 0);
+ tg_tool->trans_info[VANISHING_POINT_Y] = gimp_size_entry_get_refval (
+ GIMP_SIZE_ENTRY (t3d->vanishing_point_se), 1);
+
+ /* focal-length size entry / angle-of-view spin scale */
+ switch ((gint) tg_tool->trans_info[LENS_MODE])
+ {
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH:
+ tg_tool->trans_info[LENS_VALUE] = gimp_size_entry_get_refval (
+ GIMP_SIZE_ENTRY (t3d->focal_length_se), 0);
+ break;
+
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE:
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM:
+ tg_tool->trans_info[LENS_VALUE] = gimp_deg_to_rad (
+ gtk_adjustment_get_value (t3d->angle_of_view_adj));
+ break;
+ }
+
+ /* offset size entry */
+ tg_tool->trans_info[OFFSET_X] = gimp_size_entry_get_refval (
+ GIMP_SIZE_ENTRY (t3d->offset_se), 0);
+ tg_tool->trans_info[OFFSET_Y] = gimp_size_entry_get_refval (
+ GIMP_SIZE_ENTRY (t3d->offset_se), 1);
+ tg_tool->trans_info[OFFSET_Z] = gimp_size_entry_get_refval (
+ GIMP_SIZE_ENTRY (t3d->offset_se), 2);
+
+ /* angle spin scales */
+ tg_tool->trans_info[ANGLE_X] = gimp_deg_to_rad (gtk_adjustment_get_value (
+ t3d->angle_adj[0]));
+ tg_tool->trans_info[ANGLE_Y] = gimp_deg_to_rad (gtk_adjustment_get_value (
+ t3d->angle_adj[1]));
+ tg_tool->trans_info[ANGLE_Z] = gimp_deg_to_rad (gtk_adjustment_get_value (
+ t3d->angle_adj[2]));
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+}
+
+static void
+gimp_transform_3d_tool_lens_mode_changed (GtkComboBox *combo,
+ GimpTransform3DTool *t3d)
+{
+ GimpTool *tool = GIMP_TOOL (t3d);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (t3d);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (t3d);
+ GimpDisplay *display = tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+ gdouble x1 = 0.0;
+ gdouble y1 = 0.0;
+ gdouble x2 = 0.0;
+ gdouble y2 = 0.0;
+ gint width;
+ gint height;
+ gint lens_mode;
+ gdouble focal_length;
+
+ if (t3d->updating)
+ return;
+
+ if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &lens_mode))
+ return;
+
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+
+ focal_length = gimp_transform_3d_tool_get_focal_length (t3d);
+
+ tg_tool->trans_info[LENS_MODE] = lens_mode;
+
+ switch (lens_mode)
+ {
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH:
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE:
+ x1 = 0.0;
+ y1 = 0.0;
+
+ x2 = width;
+ y2 = height;
+ break;
+
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM:
+ x1 = tr_tool->x1;
+ y1 = tr_tool->y1;
+
+ x2 = tr_tool->x2;
+ y2 = tr_tool->y2;
+ break;
+ }
+
+ switch (lens_mode)
+ {
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH:
+ tg_tool->trans_info[LENS_VALUE] = focal_length;
+ break;
+
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE:
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM:
+ tg_tool->trans_info[LENS_VALUE] =
+ gimp_transform_3d_focal_length_to_angle_of_view (
+ focal_length, x2 - x1, y2 - y1);
+ break;
+ }
+
+ /* vanishing-point size entry */
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (t3d->vanishing_point_se), 0,
+ x1, x2);
+ gimp_size_entry_set_size (GIMP_SIZE_ENTRY (t3d->vanishing_point_se), 1,
+ y1, y2);
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+}
+
+static void
+gimp_transform_3d_tool_rotation_order_clicked (GtkButton *button,
+ GimpTransform3DTool *t3d)
+{
+ GimpTool *tool = GIMP_TOOL (t3d);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (t3d);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (t3d);
+ GimpMatrix4 matrix;
+ gint permutation[3];
+ gint b;
+ gint i;
+
+ for (b = 0; b < 3; b++)
+ {
+ if (GTK_BUTTON (t3d->rotation_order_buttons[b]) == button)
+ break;
+ }
+
+ gimp_transform_3d_rotation_order_to_permutation (
+ tg_tool->trans_info[ROTATION_ORDER], permutation);
+
+ if (permutation[0] == b)
+ {
+ gint temp;
+
+ temp = permutation[1];
+ permutation[1] = permutation[2];
+ permutation[2] = temp;
+ }
+ else
+ {
+ gint temp;
+
+ temp = permutation[0];
+ permutation[0] = b;
+
+ for (i = 1; i < 3; i++)
+ {
+ if (permutation[i] == b)
+ {
+ permutation[i] = temp;
+
+ break;
+ }
+ }
+ }
+
+ gimp_matrix4_identity (&matrix);
+
+ gimp_transform_3d_matrix4_rotate_euler (&matrix,
+ tg_tool->trans_info[ROTATION_ORDER],
+ tg_tool->trans_info[ANGLE_X],
+ tg_tool->trans_info[ANGLE_Y],
+ tg_tool->trans_info[ANGLE_Z],
+ 0.0, 0.0, 0.0);
+
+ tg_tool->trans_info[ROTATION_ORDER] =
+ gimp_transform_3d_permutation_to_rotation_order (permutation);
+
+ gimp_transform_3d_matrix4_rotate_euler_decompose (
+ &matrix,
+ tg_tool->trans_info[ROTATION_ORDER],
+ &tg_tool->trans_info[ANGLE_X],
+ &tg_tool->trans_info[ANGLE_Y],
+ &tg_tool->trans_info[ANGLE_Z]);
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+}
+
+static void
+gimp_transform_3d_tool_pivot_changed (GimpPivotSelector *selector,
+ GimpTransform3DTool *t3d)
+{
+ GimpTool *tool = GIMP_TOOL (t3d);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (t3d);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (t3d);
+ GimpMatrix4 matrix;
+ gdouble offset_x;
+ gdouble offset_y;
+ gdouble offset_z;
+
+ if (t3d->updating)
+ return;
+
+ gimp_matrix4_identity (&matrix);
+
+ gimp_transform_3d_matrix4_rotate_euler (&matrix,
+ tg_tool->trans_info[ROTATION_ORDER],
+ tg_tool->trans_info[ANGLE_X],
+ tg_tool->trans_info[ANGLE_Y],
+ tg_tool->trans_info[ANGLE_Z],
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ tg_tool->trans_info[PIVOT_Z]);
+
+ gimp_pivot_selector_get_position (GIMP_PIVOT_SELECTOR (t3d->pivot_selector),
+ &tg_tool->trans_info[PIVOT_X],
+ &tg_tool->trans_info[PIVOT_Y]);
+
+ gimp_matrix4_transform_point (&matrix,
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ tg_tool->trans_info[PIVOT_Z],
+ &offset_x, &offset_y, &offset_z);
+
+ tg_tool->trans_info[OFFSET_X] += offset_x - tg_tool->trans_info[PIVOT_X];
+ tg_tool->trans_info[OFFSET_Y] += offset_y - tg_tool->trans_info[PIVOT_Y];
+ tg_tool->trans_info[OFFSET_Z] += offset_z - tg_tool->trans_info[PIVOT_Z];
+
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, TRUE);
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+}
+
+static gdouble
+gimp_transform_3d_tool_get_focal_length (GimpTransform3DTool *t3d)
+{
+ GimpTool *tool = GIMP_TOOL (t3d);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (t3d);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (t3d);
+ GimpImage *image = gimp_display_get_image (tool->display);
+ gdouble width = 0.0;
+ gdouble height = 0.0;
+
+ switch ((int) tg_tool->trans_info[LENS_MODE])
+ {
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH:
+ return tg_tool->trans_info[LENS_VALUE];
+
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE:
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+ break;
+
+ case GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM:
+ width = tr_tool->x2 - tr_tool->x1;
+ height = tr_tool->y2 - tr_tool->y1;
+ break;
+ }
+
+ return gimp_transform_3d_angle_of_view_to_focal_length (
+ tg_tool->trans_info[LENS_VALUE], width, height);
+}
diff --git a/app/tools/gimptransform3dtool.h b/app/tools/gimptransform3dtool.h
new file mode 100644
index 0000000..636beff
--- /dev/null
+++ b/app/tools/gimptransform3dtool.h
@@ -0,0 +1,67 @@
+/* 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_TRANSFORM_3D_TOOL_H__
+#define __GIMP_TRANSFORM_3D_TOOL_H__
+
+
+#include "gimptransformgridtool.h"
+
+
+#define GIMP_TYPE_TRANSFORM_3D_TOOL (gimp_transform_3d_tool_get_type ())
+#define GIMP_TRANSFORM_3D_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_3D_TOOL, GimpTransform3DTool))
+#define GIMP_TRANSFORM_3D_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_3D_TOOL, GimpTransform3DToolClass))
+#define GIMP_IS_TRANSFORM_3D_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_3D_TOOL))
+#define GIMP_TRANSFORM_3D_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_3D_TOOL, GimpTransform3DToolClass))
+
+#define GIMP_TRANSFORM_3D_TOOL_GET_OPTIONS(t) (GIMP_TRANSFORM_3D_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpTransform3DTool GimpTransform3DTool;
+typedef struct _GimpTransform3DToolClass GimpTransform3DToolClass;
+
+struct _GimpTransform3DTool
+{
+ GimpTransformGridTool parent_instance;
+
+ gboolean updating;
+
+ GtkWidget *notebook;
+ GtkWidget *vanishing_point_se;
+ GtkWidget *lens_mode_combo;
+ GtkWidget *focal_length_se;
+ GtkWidget *angle_of_view_scale;
+ GtkAdjustment *angle_of_view_adj;
+ GtkWidget *offset_se;
+ GtkWidget *rotation_order_buttons[3];
+ GtkAdjustment *angle_adj[3];
+ GtkWidget *pivot_selector;
+};
+
+struct _GimpTransform3DToolClass
+{
+ GimpTransformGridToolClass parent_class;
+};
+
+
+void gimp_transform_3d_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_transform_3d_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_TRANSFORM_3D_TOOL_H__ */
diff --git a/app/tools/gimptransformgridoptions.c b/app/tools/gimptransformgridoptions.c
new file mode 100644
index 0000000..c8d0b09
--- /dev/null
+++ b/app/tools/gimptransformgridoptions.c
@@ -0,0 +1,712 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpperspectivetool.h"
+#include "gimprotatetool.h"
+#include "gimpscaletool.h"
+#include "gimpunifiedtransformtool.h"
+#include "gimptooloptions-gui.h"
+#include "gimptransformgridoptions.h"
+#include "gimptransformgridtool.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_DIRECTION,
+ PROP_DIRECTION_LINKED,
+ PROP_SHOW_PREVIEW,
+ PROP_COMPOSITED_PREVIEW,
+ PROP_PREVIEW_LINKED,
+ PROP_SYNCHRONOUS_PREVIEW,
+ PROP_PREVIEW_OPACITY,
+ PROP_GRID_TYPE,
+ PROP_GRID_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,
+};
+
+
+static void gimp_transform_grid_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_transform_grid_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gboolean gimp_transform_grid_options_sync_grid (GBinding *binding,
+ const GValue *source_value,
+ GValue *target_value,
+ gpointer user_data);
+
+
+G_DEFINE_TYPE (GimpTransformGridOptions, gimp_transform_grid_options,
+ GIMP_TYPE_TRANSFORM_OPTIONS)
+
+#define parent_class gimp_transform_grid_options_parent_class
+
+
+static void
+gimp_transform_grid_options_class_init (GimpTransformGridOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_transform_grid_options_set_property;
+ object_class->get_property = gimp_transform_grid_options_get_property;
+
+ g_object_class_override_property (object_class, PROP_DIRECTION,
+ "direction");
+
+ g_object_class_install_property (object_class, PROP_DIRECTION_LINKED,
+ g_param_spec_boolean ("direction-linked",
+ NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SHOW_PREVIEW,
+ "show-preview",
+ _("Show image preview"),
+ _("Show a preview of the transformed image"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_COMPOSITED_PREVIEW,
+ "composited-preview",
+ _("Composited preview"),
+ _("Show preview as part of the image composition"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_PREVIEW_LINKED,
+ "preview-linked",
+ _("Preview linked items"),
+ _("Include linked items in the preview"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SYNCHRONOUS_PREVIEW,
+ "synchronous-preview",
+ _("Synchronous preview"),
+ _("Render the preview synchronously"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_PREVIEW_OPACITY,
+ "preview-opacity",
+ _("Image opacity"),
+ _("Opacity of the preview image"),
+ 0.0, 1.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_GRID_TYPE,
+ "grid-type",
+ _("Guides"),
+ _("Composition guides such as rule of thirds"),
+ GIMP_TYPE_GUIDES_TYPE,
+ GIMP_GUIDES_NONE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_GRID_SIZE,
+ "grid-size",
+ NULL,
+ _("Size of a grid cell for variable number "
+ "of composition guides"),
+ 1, 128, 15,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONSTRAIN_MOVE,
+ "constrain-move",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONSTRAIN_SCALE,
+ "constrain-scale",
+ NULL, NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONSTRAIN_ROTATE,
+ "constrain-rotate",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONSTRAIN_SHEAR,
+ "constrain-shear",
+ NULL, NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CONSTRAIN_PERSPECTIVE,
+ "constrain-perspective",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FROMPIVOT_SCALE,
+ "frompivot-scale",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FROMPIVOT_SHEAR,
+ "frompivot-shear",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FROMPIVOT_PERSPECTIVE,
+ "frompivot-perspective",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CORNERSNAP,
+ "cornersnap",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FIXEDPIVOT,
+ "fixedpivot",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_transform_grid_options_init (GimpTransformGridOptions *options)
+{
+}
+
+static void
+gimp_transform_grid_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_OPTIONS (object);
+ GimpTransformOptions *transform_options = GIMP_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_DIRECTION:
+ transform_options->direction = g_value_get_enum (value);
+
+ /* Expected default for corrective transform_grid is to see the
+ * original image only.
+ */
+ g_object_set (options,
+ "show-preview",
+ transform_options->direction != GIMP_TRANSFORM_BACKWARD,
+ NULL);
+ break;
+ case PROP_DIRECTION_LINKED:
+ options->direction_linked = g_value_get_boolean (value);
+ break;
+ case PROP_SHOW_PREVIEW:
+ options->show_preview = g_value_get_boolean (value);
+ break;
+ case PROP_COMPOSITED_PREVIEW:
+ options->composited_preview = g_value_get_boolean (value);
+ break;
+ case PROP_PREVIEW_LINKED:
+ options->preview_linked = g_value_get_boolean (value);
+ break;
+ case PROP_SYNCHRONOUS_PREVIEW:
+ options->synchronous_preview = g_value_get_boolean (value);
+ break;
+ case PROP_PREVIEW_OPACITY:
+ options->preview_opacity = g_value_get_double (value);
+ break;
+ case PROP_GRID_TYPE:
+ options->grid_type = g_value_get_enum (value);
+ break;
+ case PROP_GRID_SIZE:
+ options->grid_size = g_value_get_int (value);
+ break;
+ case PROP_CONSTRAIN_MOVE:
+ options->constrain_move = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_SCALE:
+ options->constrain_scale = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_ROTATE:
+ options->constrain_rotate = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_SHEAR:
+ options->constrain_shear = g_value_get_boolean (value);
+ break;
+ case PROP_CONSTRAIN_PERSPECTIVE:
+ options->constrain_perspective = g_value_get_boolean (value);
+ break;
+ case PROP_FROMPIVOT_SCALE:
+ options->frompivot_scale = g_value_get_boolean (value);
+ break;
+ case PROP_FROMPIVOT_SHEAR:
+ options->frompivot_shear = g_value_get_boolean (value);
+ break;
+ case PROP_FROMPIVOT_PERSPECTIVE:
+ options->frompivot_perspective = g_value_get_boolean (value);
+ break;
+ case PROP_CORNERSNAP:
+ options->cornersnap = g_value_get_boolean (value);
+ break;
+ case PROP_FIXEDPIVOT:
+ options->fixedpivot = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_transform_grid_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_OPTIONS (object);
+ GimpTransformOptions *transform_options = GIMP_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_DIRECTION:
+ g_value_set_enum (value, transform_options->direction);
+ break;
+ case PROP_DIRECTION_LINKED:
+ g_value_set_boolean (value, options->direction_linked);
+ break;
+ case PROP_SHOW_PREVIEW:
+ g_value_set_boolean (value, options->show_preview);
+ break;
+ case PROP_COMPOSITED_PREVIEW:
+ g_value_set_boolean (value, options->composited_preview);
+ break;
+ case PROP_PREVIEW_LINKED:
+ g_value_set_boolean (value, options->preview_linked);
+ break;
+ case PROP_SYNCHRONOUS_PREVIEW:
+ g_value_set_boolean (value, options->synchronous_preview);
+ break;
+ case PROP_PREVIEW_OPACITY:
+ g_value_set_double (value, options->preview_opacity);
+ break;
+ case PROP_GRID_TYPE:
+ g_value_set_enum (value, options->grid_type);
+ break;
+ case PROP_GRID_SIZE:
+ g_value_set_int (value, options->grid_size);
+ break;
+ case PROP_CONSTRAIN_MOVE:
+ g_value_set_boolean (value, options->constrain_move);
+ break;
+ case PROP_CONSTRAIN_SCALE:
+ g_value_set_boolean (value, options->constrain_scale);
+ break;
+ case PROP_CONSTRAIN_ROTATE:
+ g_value_set_boolean (value, options->constrain_rotate);
+ break;
+ case PROP_CONSTRAIN_SHEAR:
+ g_value_set_boolean (value, options->constrain_shear);
+ break;
+ case PROP_CONSTRAIN_PERSPECTIVE:
+ g_value_set_boolean (value, options->constrain_perspective);
+ break;
+ case PROP_FROMPIVOT_SCALE:
+ g_value_set_boolean (value, options->frompivot_scale);
+ break;
+ case PROP_FROMPIVOT_SHEAR:
+ g_value_set_boolean (value, options->frompivot_shear);
+ break;
+ case PROP_FROMPIVOT_PERSPECTIVE:
+ g_value_set_boolean (value, options->frompivot_perspective);
+ break;
+ case PROP_CORNERSNAP:
+ g_value_set_boolean (value, options->cornersnap);
+ break;
+ case PROP_FIXEDPIVOT:
+ g_value_set_boolean (value, options->fixedpivot);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_transform_grid_options_gui:
+ * @tool_options: a #GimpToolOptions
+ *
+ * Build the TransformGrid Tool Options.
+ *
+ * Return value: a container holding the transform_grid tool options
+ **/
+GtkWidget *
+gimp_transform_grid_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_OPTIONS (tool_options);
+ GimpTransformGridToolClass *tg_class;
+ GtkWidget *vbox;
+ GtkWidget *vbox2;
+ GtkWidget *vbox3;
+ GtkWidget *button;
+ GtkWidget *frame;
+ GtkWidget *combo;
+ GtkWidget *scale;
+ GtkWidget *grid_box;
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType constrain_mask = gimp_get_constrain_behavior_mask ();
+
+ vbox = gimp_transform_options_gui (tool_options, TRUE, TRUE, TRUE);
+
+ tg_class = g_type_class_ref (tool_options->tool_info->tool_type);
+
+ /* the direction-link button */
+ if (tg_class->matrix_to_info)
+ {
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_OPTIONS (tool_options);
+ GtkWidget *hbox;
+
+ vbox2 = gtk_bin_get_child (GTK_BIN (tr_options->direction_frame));
+ g_object_ref (vbox2);
+ gtk_container_remove (GTK_CONTAINER (tr_options->direction_frame), vbox2);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1);
+ gtk_container_add (GTK_CONTAINER (tr_options->direction_frame), hbox);
+ gtk_widget_show (hbox);
+
+ gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0);
+ g_object_unref (vbox2);
+
+ button = gimp_chain_button_new (GIMP_CHAIN_RIGHT);
+ gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_set_sensitive (button, FALSE);
+ gimp_chain_button_set_icon_size (GIMP_CHAIN_BUTTON (button),
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (button);
+
+ g_object_bind_property (config, "direction-linked",
+ button, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ options->direction_chain_button = button;
+ }
+
+ g_type_class_unref (tg_class);
+
+ /* the preview frame */
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+ vbox3 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+ button = gimp_prop_check_button_new (config, "preview-linked", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox3), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_prop_check_button_new (config, "synchronous-preview", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox3), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ frame = gimp_prop_expanding_frame_new (config, "composited-preview", NULL,
+ vbox3, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ scale = gimp_prop_spin_scale_new (config, "preview-opacity", NULL,
+ 0.01, 0.1, 0);
+ gimp_prop_widget_set_factor (scale, 100.0, 0.0, 0.0, 1);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ g_object_bind_property (config, "composited-preview",
+ scale, "sensitive",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ frame = gimp_prop_expanding_frame_new (config, "show-preview", NULL,
+ vbox2, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the guides frame */
+ frame = gimp_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the guides type menu */
+ combo = gimp_prop_enum_combo_box_new (config, "grid-type", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Guides"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), combo);
+ gtk_widget_show (combo);
+
+ /* the grid density scale */
+ scale = gimp_prop_spin_scale_new (config, "grid-size", NULL,
+ 1.8, 8.0, 0);
+ gimp_spin_scale_set_label (GIMP_SPIN_SCALE (scale), NULL);
+ gtk_container_add (GTK_CONTAINER (frame), scale);
+
+ g_object_bind_property_full (config, "grid-type",
+ scale, "visible",
+ G_BINDING_SYNC_CREATE,
+ gimp_transform_grid_options_sync_grid,
+ NULL,
+ NULL, NULL);
+
+ if (tool_options->tool_info->tool_type == GIMP_TYPE_ROTATE_TOOL)
+ {
+ gchar *label;
+
+ label = g_strdup_printf (_("15 degrees (%s)"),
+ gimp_get_mod_string (extend_mask));
+
+ button = gimp_prop_check_button_new (config, "constrain-rotate", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (button, _("Limit rotation steps to 15 degrees"),
+ NULL);
+
+ g_free (label);
+ }
+ else if (tool_options->tool_info->tool_type == GIMP_TYPE_SCALE_TOOL)
+ {
+ gchar *label;
+
+ label = g_strdup_printf (_("Keep aspect (%s)"),
+ gimp_get_mod_string (extend_mask));
+
+ button = gimp_prop_check_button_new (config, "constrain-scale", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (button, _("Keep the original aspect ratio"),
+ NULL);
+
+ g_free (label);
+
+ label = g_strdup_printf (_("Around center (%s)"),
+ gimp_get_mod_string (constrain_mask));
+
+ button = gimp_prop_check_button_new (config, "frompivot-scale", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (button, _("Scale around the center point"),
+ NULL);
+
+ g_free (label);
+ }
+ else if (tool_options->tool_info->tool_type == GIMP_TYPE_PERSPECTIVE_TOOL)
+ {
+ gchar *label;
+
+ label = g_strdup_printf (_("Constrain handles (%s)"),
+ gimp_get_mod_string (extend_mask));
+
+ button = gimp_prop_check_button_new (config, "constrain-perspective", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (
+ button, _("Constrain handles to move along edges and diagonal (%s)"),
+ NULL);
+
+ g_free (label);
+
+ label = g_strdup_printf (_("Around center (%s)"),
+ gimp_get_mod_string (constrain_mask));
+
+ button = gimp_prop_check_button_new (config, "frompivot-perspective", label);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gimp_help_set_help_data (
+ button, _("Transform around the center point"),
+ NULL);
+
+ g_free (label);
+ }
+ else if (tool_options->tool_info->tool_type == GIMP_TYPE_UNIFIED_TRANSFORM_TOOL)
+ {
+ struct
+ {
+ GdkModifierType mod;
+ gchar *name;
+ gchar *desc;
+ gchar *tip;
+ }
+ opt_list[] =
+ {
+ { extend_mask, NULL, N_("Constrain (%s)") },
+ { extend_mask, "constrain-move", N_("Move"),
+ N_("Constrain movement to 45 degree angles from center (%s)") },
+ { extend_mask, "constrain-scale", N_("Scale"),
+ N_("Maintain aspect ratio when scaling (%s)") },
+ { extend_mask, "constrain-rotate", N_("Rotate"),
+ N_("Constrain rotation to 15 degree increments (%s)") },
+ { extend_mask, "constrain-shear", N_("Shear"),
+ N_("Shear along edge direction only (%s)") },
+ { extend_mask, "constrain-perspective", N_("Perspective"),
+ N_("Constrain perspective handles to move along edges and diagonal (%s)") },
+
+ { constrain_mask, NULL,
+ N_("From pivot (%s)") },
+ { constrain_mask, "frompivot-scale", N_("Scale"),
+ N_("Scale from pivot point (%s)") },
+ { constrain_mask, "frompivot-shear", N_("Shear"),
+ N_("Shear opposite edge by same amount (%s)") },
+ { constrain_mask, "frompivot-perspective", N_("Perspective"),
+ N_("Maintain position of pivot while changing perspective (%s)") },
+
+ { 0, NULL,
+ N_("Pivot") },
+ { extend_mask, "cornersnap", N_("Snap (%s)"),
+ N_("Snap pivot to corners and center (%s)") },
+ { 0, "fixedpivot", N_("Lock"),
+ N_("Lock pivot position to canvas") },
+ };
+
+ gchar *label;
+ gint i;
+
+ frame = NULL;
+
+ for (i = 0; i < G_N_ELEMENTS (opt_list); i++)
+ {
+ if (! opt_list[i].name && ! opt_list[i].desc)
+ {
+ frame = NULL;
+ continue;
+ }
+
+ label = g_strdup_printf (gettext (opt_list[i].desc),
+ gimp_get_mod_string (opt_list[i].mod));
+
+ if (opt_list[i].name)
+ {
+ button = gimp_prop_check_button_new (config, opt_list[i].name,
+ label);
+
+ gtk_box_pack_start (GTK_BOX (frame ? grid_box : vbox),
+ button, FALSE, FALSE, 0);
+
+ gtk_widget_show (button);
+
+ g_free (label);
+ label = g_strdup_printf (gettext (opt_list[i].tip),
+ gimp_get_mod_string (opt_list[i].mod));
+
+ gimp_help_set_help_data (button, label, NULL);
+ }
+ else
+ {
+ frame = gimp_frame_new (label);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ grid_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), grid_box);
+ gtk_widget_show (grid_box);
+ }
+
+ g_free (label);
+ }
+ }
+
+ return vbox;
+}
+
+gboolean
+gimp_transform_grid_options_show_preview (GimpTransformGridOptions *options)
+{
+ GimpTransformOptions *transform_options;
+
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_GRID_OPTIONS (options), FALSE);
+
+ transform_options = GIMP_TRANSFORM_OPTIONS (options);
+
+ if (options->show_preview)
+ {
+ switch (transform_options->type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ return TRUE;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ case GIMP_TRANSFORM_TYPE_PATH:
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_transform_grid_options_sync_grid (GBinding *binding,
+ const GValue *source_value,
+ GValue *target_value,
+ gpointer user_data)
+{
+ GimpGuidesType type = g_value_get_enum (source_value);
+
+ g_value_set_boolean (target_value,
+ type == GIMP_GUIDES_N_LINES ||
+ type == GIMP_GUIDES_SPACING);
+
+ return TRUE;
+}
+
diff --git a/app/tools/gimptransformgridoptions.h b/app/tools/gimptransformgridoptions.h
new file mode 100644
index 0000000..e20b2e8
--- /dev/null
+++ b/app/tools/gimptransformgridoptions.h
@@ -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/>.
+ */
+
+#ifndef __GIMP_TRANSFORM_GRID_OPTIONS_H__
+#define __GIMP_TRANSFORM_GRID_OPTIONS_H__
+
+
+#include "gimptransformoptions.h"
+
+
+#define GIMP_TYPE_TRANSFORM_GRID_OPTIONS (gimp_transform_grid_options_get_type ())
+#define GIMP_TRANSFORM_GRID_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_GRID_OPTIONS, GimpTransformGridOptions))
+#define GIMP_TRANSFORM_GRID_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_GRID_OPTIONS, GimpTransformGridOptionsClass))
+#define GIMP_IS_TRANSFORM_GRID_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_GRID_OPTIONS))
+#define GIMP_IS_TRANSFORM_GRID_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSFORM_GRID_OPTIONS))
+#define GIMP_TRANSFORM_GRID_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_GRID_OPTIONS, GimpTransformGridOptionsClass))
+
+
+typedef struct _GimpTransformGridOptions GimpTransformGridOptions;
+typedef struct _GimpTransformGridOptionsClass GimpTransformGridOptionsClass;
+
+struct _GimpTransformGridOptions
+{
+ GimpTransformOptions parent_instance;
+
+ gboolean direction_linked;
+ gboolean show_preview;
+ gboolean composited_preview;
+ gboolean preview_linked;
+ gboolean synchronous_preview;
+ gdouble preview_opacity;
+ GimpGuidesType grid_type;
+ gint grid_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;
+
+ /* options gui */
+ GtkWidget *direction_chain_button;
+};
+
+struct _GimpTransformGridOptionsClass
+{
+ GimpTransformOptionsClass parent_class;
+};
+
+
+GType gimp_transform_grid_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_transform_grid_options_gui (GimpToolOptions *tool_options);
+
+gboolean gimp_transform_grid_options_show_preview (GimpTransformGridOptions *options);
+
+
+#endif /* __GIMP_TRANSFORM_GRID_OPTIONS_H__ */
diff --git a/app/tools/gimptransformgridtool.c b/app/tools/gimptransformgridtool.c
new file mode 100644
index 0000000..87ade22
--- /dev/null
+++ b/app/tools/gimptransformgridtool.c
@@ -0,0 +1,2209 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "gegl/gimpapplicator.h"
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-transform-resize.h"
+#include "core/gimp-transform-utils.h"
+#include "core/gimpboundary.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpfilter.h"
+#include "core/gimpgrouplayer.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-item-list.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpimage-undo-push.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimppickable.h"
+#include "core/gimpprojection.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimpviewable.h"
+
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpstroke.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptoolwidget.h"
+
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+#include "gimptransformgridtool.h"
+#include "gimptransformgridtoolundo.h"
+#include "gimptransformoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 1e-6
+
+
+#define RESPONSE_RESET 1
+#define RESPONSE_READJUST 2
+
+#define UNDO_COMPRESS_TIME (0.5 * G_TIME_SPAN_SECOND)
+
+
+typedef struct
+{
+ GimpTransformGridTool *tg_tool;
+
+ GimpDrawable *drawable;
+ GimpDrawableFilter *filter;
+
+ GimpDrawable *root_drawable;
+
+ GeglNode *transform_node;
+ GeglNode *crop_node;
+
+ GimpMatrix3 transform;
+ GeglRectangle bounds;
+} Filter;
+
+typedef struct
+{
+ GimpTransformGridTool *tg_tool;
+ GimpDrawable *root_drawable;
+} AddFilterData;
+
+typedef struct
+{
+ gint64 time;
+ GimpTransformDirection direction;
+ TransInfo trans_infos[2];
+} UndoInfo;
+
+
+static void gimp_transform_grid_tool_finalize (GObject *object);
+
+static gboolean gimp_transform_grid_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_transform_grid_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static const gchar * gimp_transform_grid_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+static const gchar * gimp_transform_grid_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_transform_grid_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_transform_grid_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_transform_grid_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_transform_grid_tool_recalc_matrix (GimpTransformTool *tr_tool);
+static gchar * gimp_transform_grid_tool_get_undo_desc (GimpTransformTool *tr_tool);
+static GimpTransformDirection gimp_transform_grid_tool_get_direction
+ (GimpTransformTool *tr_tool);
+static GeglBuffer * gimp_transform_grid_tool_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+static void gimp_transform_grid_tool_real_apply_info (GimpTransformGridTool *tg_tool,
+ const TransInfo info);
+static gchar * gimp_transform_grid_tool_real_get_undo_desc (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_real_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_real_widget_changed (GimpTransformGridTool *tg_tool);
+static GeglBuffer * gimp_transform_grid_tool_real_transform (GimpTransformGridTool *tg_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+static void gimp_transform_grid_tool_widget_changed (GimpToolWidget *widget,
+ GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_widget_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_image_linked_items_changed
+ (GimpImage *image,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_halt (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_commit (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_prepare (GimpTransformGridTool *tg_tool,
+ GimpDisplay *display);
+static GimpToolWidget * gimp_transform_grid_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_widget (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpTransformGridTool *tg_tool);
+
+static gboolean gimp_transform_grid_tool_composited_preview (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_sensitivity (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_preview (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_filters (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_hide_active_object (GimpTransformGridTool *tg_tool,
+ GimpObject *object);
+static void gimp_transform_grid_tool_show_active_object (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_add_filter (GimpDrawable *drawable,
+ AddFilterData *data);
+static void gimp_transform_grid_tool_remove_filter (GimpDrawable *drawable,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_effective_mode_changed
+ (GimpLayer *layer,
+ GimpTransformGridTool *tg_tool);
+
+static Filter * filter_new (GimpTransformGridTool *tg_tool,
+ GimpDrawable *drawable,
+ GimpDrawable *root_drawable,
+ gboolean add_filter);
+static void filter_free (Filter *filter);
+
+static UndoInfo * undo_info_new (void);
+static void undo_info_free (UndoInfo *info);
+
+static gboolean trans_info_equal (const TransInfo trans_info1,
+ const TransInfo trans_info2);
+static gboolean trans_infos_equal (const TransInfo *trans_infos1,
+ const TransInfo *trans_infos2);
+
+
+G_DEFINE_TYPE (GimpTransformGridTool, gimp_transform_grid_tool, GIMP_TYPE_TRANSFORM_TOOL)
+
+#define parent_class gimp_transform_grid_tool_parent_class
+
+
+static void
+gimp_transform_grid_tool_class_init (GimpTransformGridToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_class = GIMP_DRAW_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_transform_grid_tool_finalize;
+
+ tool_class->initialize = gimp_transform_grid_tool_initialize;
+ tool_class->control = gimp_transform_grid_tool_control;
+ tool_class->button_press = gimp_transform_grid_tool_button_press;
+ tool_class->button_release = gimp_transform_grid_tool_button_release;
+ tool_class->motion = gimp_transform_grid_tool_motion;
+ tool_class->modifier_key = gimp_transform_grid_tool_modifier_key;
+ tool_class->cursor_update = gimp_transform_grid_tool_cursor_update;
+ tool_class->can_undo = gimp_transform_grid_tool_can_undo;
+ tool_class->can_redo = gimp_transform_grid_tool_can_redo;
+ tool_class->undo = gimp_transform_grid_tool_undo;
+ tool_class->redo = gimp_transform_grid_tool_redo;
+ tool_class->options_notify = gimp_transform_grid_tool_options_notify;
+
+ draw_class->draw = gimp_transform_grid_tool_draw;
+
+ tr_class->recalc_matrix = gimp_transform_grid_tool_recalc_matrix;
+ tr_class->get_undo_desc = gimp_transform_grid_tool_get_undo_desc;
+ tr_class->get_direction = gimp_transform_grid_tool_get_direction;
+ tr_class->transform = gimp_transform_grid_tool_transform;
+
+ klass->info_to_matrix = NULL;
+ klass->matrix_to_info = NULL;
+ klass->apply_info = gimp_transform_grid_tool_real_apply_info;
+ klass->get_undo_desc = gimp_transform_grid_tool_real_get_undo_desc;
+ klass->dialog = NULL;
+ klass->dialog_update = NULL;
+ klass->prepare = NULL;
+ klass->readjust = NULL;
+ klass->get_widget = NULL;
+ klass->update_widget = gimp_transform_grid_tool_real_update_widget;
+ klass->widget_changed = gimp_transform_grid_tool_real_widget_changed;
+ klass->transform = gimp_transform_grid_tool_real_transform;
+
+ klass->ok_button_label = _("_Transform");
+}
+
+static void
+gimp_transform_grid_tool_init (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE_SIZE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SAME);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_cursor (tool->control,
+ GIMP_CURSOR_CROSSHAIR_SMALL);
+ gimp_tool_control_set_action_opacity (tool->control,
+ "tools/tools-transform-preview-opacity-set");
+
+ tg_tool->strokes = g_ptr_array_new ();
+}
+
+static void
+gimp_transform_grid_tool_finalize (GObject *object)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (object);
+
+ g_clear_object (&tg_tool->gui);
+ g_clear_pointer (&tg_tool->strokes, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_transform_grid_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpObject *object;
+ UndoInfo *undo_info;
+
+ object = gimp_transform_tool_check_active_object (tr_tool, display, error);
+
+ if (! object)
+ return FALSE;
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ tr_tool->object = object;
+
+ if (GIMP_IS_DRAWABLE (object))
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (object));
+
+ /* Initialize the transform_grid tool dialog */
+ if (! tg_tool->gui)
+ gimp_transform_grid_tool_dialog (tg_tool);
+
+ /* Find the transform bounds for some tools (like scale,
+ * perspective) that actually need the bounds for initializing
+ */
+ gimp_transform_tool_bounds (tr_tool, display);
+
+ /* Initialize the tool-specific trans_info, and adjust the tool dialog */
+ gimp_transform_grid_tool_prepare (tg_tool, display);
+
+ /* Recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ /* Get the on-canvas gui */
+ tg_tool->widget = gimp_transform_grid_tool_get_widget (tg_tool);
+
+ gimp_transform_grid_tool_hide_active_object (tg_tool, object);
+
+ /* start drawing the bounding box and handles... */
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+
+ /* Initialize undo and redo lists */
+ undo_info = undo_info_new ();
+ tg_tool->undo_list = g_list_prepend (NULL, undo_info);
+ tg_tool->redo_list = NULL;
+
+ /* Save the current transformation info */
+ memcpy (undo_info->trans_infos, tg_tool->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ if (tg_options->direction_chain_button)
+ gtk_widget_set_sensitive (tg_options->direction_chain_button, TRUE);
+
+ g_signal_connect (
+ image, "linked-items-changed",
+ G_CALLBACK (gimp_transform_grid_tool_image_linked_items_changed),
+ tg_tool);
+
+ return TRUE;
+}
+
+static void
+gimp_transform_grid_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_transform_grid_tool_halt (tg_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ if (tool->display)
+ gimp_transform_grid_tool_commit (tg_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_transform_grid_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (tg_tool->widget)
+ {
+ gimp_tool_widget_hover (tg_tool->widget, coords, state, TRUE);
+
+ if (gimp_tool_widget_button_press (tg_tool->widget, coords, time, state,
+ press_type))
+ {
+ tg_tool->grab_widget = tg_tool->widget;
+ }
+ }
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_transform_grid_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (tg_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (tg_tool->grab_widget,
+ coords, time, state, release_type);
+ tg_tool->grab_widget = NULL;
+ }
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* We're done with an interaction, save it on the undo list */
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, FALSE);
+ }
+ else
+ {
+ UndoInfo *undo_info = tg_tool->undo_list->data;
+
+ /* Restore the last saved state */
+ memcpy (tg_tool->trans_infos, undo_info->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ }
+}
+
+static void
+gimp_transform_grid_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (tg_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (tg_tool->grab_widget, coords, time, state);
+ }
+}
+
+static void
+gimp_transform_grid_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (tg_tool->widget)
+ {
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press,
+ state, display);
+ }
+ else
+ {
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_constrain_behavior_mask ())
+ {
+ g_object_set (options,
+ "frompivot-scale", ! options->frompivot_scale,
+ "frompivot-shear", ! options->frompivot_shear,
+ "frompivot-perspective", ! options->frompivot_perspective,
+ NULL);
+ }
+ else if (key == gimp_get_extend_selection_mask ())
+ {
+ g_object_set (options,
+ "cornersnap", ! options->cornersnap,
+ "constrain-move", ! options->constrain_move,
+ "constrain-scale", ! options->constrain_scale,
+ "constrain-rotate", ! options->constrain_rotate,
+ "constrain-shear", ! options->constrain_shear,
+ "constrain-perspective", ! options->constrain_perspective,
+ NULL);
+ }
+ }
+}
+
+static void
+gimp_transform_grid_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+
+ if (display != tool->display &&
+ ! gimp_transform_tool_check_active_object (tr_tool, display, NULL))
+ {
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ return;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static const gchar *
+gimp_transform_grid_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (! tg_tool->undo_list || ! tg_tool->undo_list->next)
+ return NULL;
+
+ return _("Transform Step");
+}
+
+static const gchar *
+gimp_transform_grid_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (! tg_tool->redo_list)
+ return NULL;
+
+ return _("Transform Step");
+}
+
+static gboolean
+gimp_transform_grid_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tool);
+ UndoInfo *undo_info;
+ GimpTransformDirection direction;
+
+ undo_info = tg_tool->undo_list->data;
+ direction = undo_info->direction;
+
+ /* Move undo_info from undo_list to redo_list */
+ tg_tool->redo_list = g_list_prepend (tg_tool->redo_list, undo_info);
+ tg_tool->undo_list = g_list_remove (tg_tool->undo_list, undo_info);
+
+ undo_info = tg_tool->undo_list->data;
+
+ /* Restore the previous transformation info */
+ memcpy (tg_tool->trans_infos, undo_info->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* Restore the previous transformation direction */
+ if (direction != tr_options->direction)
+ {
+ g_object_set (tr_options,
+ "direction", direction,
+ NULL);
+ }
+
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_transform_grid_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tool);
+ UndoInfo *undo_info;
+ GimpTransformDirection direction;
+
+ undo_info = tg_tool->redo_list->data;
+ direction = undo_info->direction;
+
+ /* Move undo_info from redo_list to undo_list */
+ tg_tool->undo_list = g_list_prepend (tg_tool->undo_list, undo_info);
+ tg_tool->redo_list = g_list_remove (tg_tool->redo_list, undo_info);
+
+ /* Restore the previous transformation info */
+ memcpy (tg_tool->trans_infos, undo_info->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* Restore the previous transformation direction */
+ if (direction != tr_options->direction)
+ {
+ g_object_set (tr_options,
+ "direction", direction,
+ NULL);
+ }
+
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ return TRUE;
+}
+
+static void
+gimp_transform_grid_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_OPTIONS (options);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "type"))
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ return;
+ }
+
+ if (! tg_tool->widget)
+ return;
+
+ if (! strcmp (pspec->name, "direction"))
+ {
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+ else if (! strcmp (pspec->name, "show-preview") ||
+ ! strcmp (pspec->name, "composited-preview"))
+ {
+ if (tg_tool->preview)
+ {
+ GimpDisplay *display;
+ GimpObject *object;
+
+ display = tool->display;
+ object = gimp_transform_tool_get_active_object (tr_tool, display);
+
+ if (object)
+ {
+ if (tg_options->show_preview &&
+ ! gimp_transform_grid_tool_composited_preview (tg_tool))
+ {
+ gimp_transform_grid_tool_hide_active_object (tg_tool, object);
+ }
+ else
+ {
+ gimp_transform_grid_tool_show_active_object (tg_tool);
+ }
+ }
+
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+ }
+ else if (! strcmp (pspec->name, "preview-linked") &&
+ tg_tool->filters)
+ {
+ gimp_transform_grid_tool_update_filters (tg_tool);
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+ else if (! strcmp (pspec->name, "interpolation") ||
+ ! strcmp (pspec->name, "clip") ||
+ ! strcmp (pspec->name, "preview-opacity"))
+ {
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+ else if (g_str_has_prefix (pspec->name, "constrain-") ||
+ g_str_has_prefix (pspec->name, "frompivot-") ||
+ ! strcmp (pspec->name, "fixedpivot") ||
+ ! strcmp (pspec->name, "cornersnap"))
+ {
+ gimp_transform_grid_tool_dialog_update (tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpTool *tool = GIMP_TOOL (draw_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (draw_tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (draw_tool);
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_OPTIONS (options);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpImage *image = gimp_display_get_image (tool->display);
+ GimpMatrix3 matrix = tr_tool->transform;
+ GimpCanvasItem *item;
+
+ if (tr_options->direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&matrix);
+
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_LAYER ||
+ tr_options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ GimpPickable *pickable;
+
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ if (! shell->show_all)
+ pickable = GIMP_PICKABLE (image);
+ else
+ pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
+ }
+ else
+ {
+ pickable = GIMP_PICKABLE (tool->drawable);
+ }
+
+ tg_tool->preview =
+ gimp_draw_tool_add_transform_preview (draw_tool,
+ pickable,
+ &matrix,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2);
+ g_object_add_weak_pointer (G_OBJECT (tg_tool->preview),
+ (gpointer) &tg_tool->preview);
+ }
+
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_SELECTION)
+ {
+ const GimpBoundSeg *segs_in;
+ const GimpBoundSeg *segs_out;
+ gint n_segs_in;
+ gint n_segs_out;
+
+ gimp_channel_boundary (gimp_image_get_mask (image),
+ &segs_in, &segs_out,
+ &n_segs_in, &n_segs_out,
+ 0, 0, 0, 0);
+
+ if (segs_in)
+ {
+ tg_tool->boundary_in =
+ gimp_draw_tool_add_boundary (draw_tool,
+ segs_in, n_segs_in,
+ &matrix,
+ 0, 0);
+ g_object_add_weak_pointer (G_OBJECT (tg_tool->boundary_in),
+ (gpointer) &tg_tool->boundary_in);
+
+ gimp_canvas_item_set_visible (tg_tool->boundary_in,
+ tr_tool->transform_valid);
+ }
+
+ if (segs_out)
+ {
+ tg_tool->boundary_out =
+ gimp_draw_tool_add_boundary (draw_tool,
+ segs_out, n_segs_out,
+ &matrix,
+ 0, 0);
+ g_object_add_weak_pointer (G_OBJECT (tg_tool->boundary_out),
+ (gpointer) &tg_tool->boundary_out);
+
+ gimp_canvas_item_set_visible (tg_tool->boundary_out,
+ tr_tool->transform_valid);
+ }
+ }
+ else if (tr_options->type == GIMP_TRANSFORM_TYPE_PATH)
+ {
+ GimpVectors *vectors = gimp_image_get_active_vectors (image);
+
+ if (vectors)
+ {
+ GimpStroke *stroke = NULL;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ GArray *coords;
+ gboolean closed;
+
+ coords = gimp_stroke_interpolate (stroke, 1.0, &closed);
+
+ if (coords && coords->len)
+ {
+ item =
+ gimp_draw_tool_add_strokes (draw_tool,
+ &g_array_index (coords,
+ GimpCoords, 0),
+ coords->len, &matrix, FALSE);
+
+ g_ptr_array_add (tg_tool->strokes, item);
+ g_object_weak_ref (G_OBJECT (item),
+ (GWeakNotify) g_ptr_array_remove,
+ tg_tool->strokes);
+
+ gimp_canvas_item_set_visible (item, tr_tool->transform_valid);
+ }
+
+ if (coords)
+ g_array_free (coords, TRUE);
+ }
+ }
+ }
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+
+ gimp_transform_grid_tool_update_preview (tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_recalc_matrix (GimpTransformTool *tr_tool)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tr_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tr_tool);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix)
+ {
+ GimpMatrix3 forward_transform;
+ GimpMatrix3 backward_transform;
+ gboolean forward_transform_valid;
+ gboolean backward_transform_valid;
+
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD];
+ forward_transform_valid = gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &forward_transform);
+
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD];
+ backward_transform_valid = gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &backward_transform);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info &&
+ tg_options->direction_linked)
+ {
+ GimpMatrix3 transform = tr_tool->transform;
+
+ switch (tr_options->direction)
+ {
+ case GIMP_TRANSFORM_FORWARD:
+ if (forward_transform_valid)
+ {
+ gimp_matrix3_invert (&transform);
+
+ backward_transform = forward_transform;
+ gimp_matrix3_mult (&transform, &backward_transform);
+
+ tg_tool->trans_info =
+ tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD];
+ gimp_transform_grid_tool_matrix_to_info (tg_tool,
+ &backward_transform);
+ backward_transform_valid =
+ gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &backward_transform);
+ }
+ break;
+
+ case GIMP_TRANSFORM_BACKWARD:
+ if (backward_transform_valid)
+ {
+ forward_transform = backward_transform;
+ gimp_matrix3_mult (&transform, &forward_transform);
+
+ tg_tool->trans_info =
+ tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD];
+ gimp_transform_grid_tool_matrix_to_info (tg_tool,
+ &forward_transform);
+ forward_transform_valid =
+ gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &forward_transform);
+ }
+ break;
+ }
+ }
+ else if (forward_transform_valid && backward_transform_valid)
+ {
+ tr_tool->transform = backward_transform;
+ gimp_matrix3_invert (&tr_tool->transform);
+ gimp_matrix3_mult (&forward_transform, &tr_tool->transform);
+ }
+
+ tr_tool->transform_valid = forward_transform_valid &&
+ backward_transform_valid;
+ }
+
+ tg_tool->trans_info = tg_tool->trans_infos[tr_options->direction];
+
+ gimp_transform_grid_tool_dialog_update (tg_tool);
+ gimp_transform_grid_tool_update_sensitivity (tg_tool);
+ gimp_transform_grid_tool_update_widget (tg_tool);
+ gimp_transform_grid_tool_update_preview (tg_tool);
+
+ if (tg_tool->gui)
+ gimp_tool_gui_show (tg_tool->gui);
+}
+
+static gchar *
+gimp_transform_grid_tool_get_undo_desc (GimpTransformTool *tr_tool)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tr_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ gchar *result;
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info)
+ {
+ TransInfo trans_info;
+
+ memcpy (&trans_info, &tg_tool->init_trans_info, sizeof (TransInfo));
+
+ tg_tool->trans_info = trans_info;
+ gimp_transform_grid_tool_matrix_to_info (tg_tool, &tr_tool->transform);
+ result = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc (
+ tg_tool);
+ }
+ else if (trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info))
+ {
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD];
+ result = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc (
+ tg_tool);
+ }
+ else if (trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info))
+ {
+ gchar *desc;
+
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD];
+ desc = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc (
+ tg_tool);
+
+ result = g_strdup_printf (_("%s (Corrective)"), desc);
+
+ g_free (desc);
+ }
+ else
+ {
+ result = GIMP_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc (
+ tr_tool);
+ }
+
+ tg_tool->trans_info = tg_tool->trans_infos[tr_options->direction];
+
+ return result;
+}
+
+static GimpTransformDirection
+gimp_transform_grid_tool_get_direction (GimpTransformTool *tr_tool)
+{
+ return GIMP_TRANSFORM_FORWARD;
+}
+
+static GeglBuffer *
+gimp_transform_grid_tool_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ GimpTool *tool = GIMP_TOOL (tr_tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tr_tool);
+ GimpDisplay *display = tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+ GeglBuffer *new_buffer;
+
+ /* Send the request for the transformation to the tool...
+ */
+ new_buffer =
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->transform (tg_tool,
+ object,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ buffer_profile,
+ new_offset_x,
+ new_offset_y);
+
+ gimp_image_undo_push (image, GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO,
+ GIMP_UNDO_TRANSFORM_GRID, NULL,
+ 0,
+ "transform-tool", tg_tool,
+ NULL);
+
+ return new_buffer;
+}
+
+static void
+gimp_transform_grid_tool_real_apply_info (GimpTransformGridTool *tg_tool,
+ const TransInfo info)
+{
+ memcpy (tg_tool->trans_info, info, sizeof (TransInfo));
+}
+
+static gchar *
+gimp_transform_grid_tool_real_get_undo_desc (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ return GIMP_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc (tr_tool);
+}
+
+static void
+gimp_transform_grid_tool_real_update_widget (GimpTransformGridTool *tg_tool)
+{
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix)
+ {
+ GimpMatrix3 transform;
+
+ gimp_transform_grid_tool_info_to_matrix (tg_tool, &transform);
+
+ g_object_set (tg_tool->widget,
+ "transform", &transform,
+ NULL);
+ }
+}
+
+static void
+gimp_transform_grid_tool_real_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpToolWidget *widget = tg_tool->widget;
+
+ /* suppress the call to GimpTransformGridTool::update_widget() when
+ * recalculating the matrix
+ */
+ tg_tool->widget = NULL;
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+
+ tg_tool->widget = widget;
+}
+
+static GeglBuffer *
+gimp_transform_grid_tool_real_transform (GimpTransformGridTool *tg_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ return GIMP_TRANSFORM_TOOL_CLASS (parent_class)->transform (tr_tool,
+ object,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ buffer_profile,
+ new_offset_x,
+ new_offset_y);
+}
+
+static void
+gimp_transform_grid_tool_widget_changed (GimpToolWidget *widget,
+ GimpTransformGridTool *tg_tool)
+{
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->widget_changed)
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->widget_changed (tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_widget_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpTransformGridTool *tg_tool)
+{
+ switch (response_id)
+ {
+ case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
+ gimp_transform_grid_tool_response (NULL, GTK_RESPONSE_OK, tg_tool);
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
+ gimp_transform_grid_tool_response (NULL, GTK_RESPONSE_CANCEL, tg_tool);
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_RESET:
+ gimp_transform_grid_tool_response (NULL, RESPONSE_RESET, tg_tool);
+ break;
+ }
+}
+
+static void
+gimp_transform_grid_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_transform_grid_tool_image_linked_items_changed (GimpImage *image,
+ GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->filters)
+ {
+ gimp_transform_grid_tool_update_filters (tg_tool);
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_halt (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+
+ if (tool->display)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ g_signal_handlers_disconnect_by_func (
+ image,
+ gimp_transform_grid_tool_image_linked_items_changed,
+ tg_tool);
+ }
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tg_tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tg_tool));
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tg_tool), NULL);
+ g_clear_object (&tg_tool->widget);
+
+ g_clear_pointer (&tg_tool->filters, g_hash_table_unref);
+ g_clear_pointer (&tg_tool->preview_drawables, g_list_free);
+
+ if (tg_tool->gui)
+ gimp_tool_gui_hide (tg_tool->gui);
+
+ if (tg_tool->redo_list)
+ {
+ g_list_free_full (tg_tool->redo_list, (GDestroyNotify) undo_info_free);
+ tg_tool->redo_list = NULL;
+ }
+
+ if (tg_tool->undo_list)
+ {
+ g_list_free_full (tg_tool->undo_list, (GDestroyNotify) undo_info_free);
+ tg_tool->undo_list = NULL;
+ }
+
+ gimp_transform_grid_tool_show_active_object (tg_tool);
+
+ if (tg_options->direction_chain_button)
+ {
+ g_object_set (tg_options,
+ "direction-linked", FALSE,
+ NULL);
+
+ gtk_widget_set_sensitive (tg_options->direction_chain_button, FALSE);
+ }
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+
+ if (tr_tool->object)
+ {
+ if (GIMP_IS_DRAWABLE (tr_tool->object))
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (tr_tool->object));
+
+ tr_tool->object = NULL;
+ }
+}
+
+static void
+gimp_transform_grid_tool_commit (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplay *display = tool->display;
+
+ /* undraw the tool before we muck around with the transform matrix */
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tg_tool));
+
+ gimp_transform_tool_transform (tr_tool, display);
+}
+
+static void
+gimp_transform_grid_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpToolInfo *tool_info = tool->tool_info;
+ GimpDisplayShell *shell;
+ const gchar *ok_button_label;
+
+ if (! GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog)
+ return;
+
+ g_return_if_fail (tool->display != NULL);
+
+ shell = gimp_display_get_shell (tool->display);
+
+ ok_button_label = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->ok_button_label;
+
+ tg_tool->gui = gimp_tool_gui_new (tool_info,
+ NULL, NULL, NULL, NULL,
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ TRUE,
+ NULL);
+
+ gimp_tool_gui_add_button (tg_tool->gui, _("_Reset"), RESPONSE_RESET);
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust)
+ gimp_tool_gui_add_button (tg_tool->gui, _("Re_adjust"), RESPONSE_READJUST);
+ gimp_tool_gui_add_button (tg_tool->gui, _("_Cancel"), GTK_RESPONSE_CANCEL);
+ gimp_tool_gui_add_button (tg_tool->gui, ok_button_label, GTK_RESPONSE_OK);
+
+ gimp_tool_gui_set_auto_overlay (tg_tool->gui, TRUE);
+ gimp_tool_gui_set_default_response (tg_tool->gui, GTK_RESPONSE_OK);
+
+ gimp_tool_gui_set_alternative_button_order (tg_tool->gui,
+ RESPONSE_RESET,
+ RESPONSE_READJUST,
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_signal_connect (tg_tool->gui, "response",
+ G_CALLBACK (gimp_transform_grid_tool_response),
+ tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog (tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->gui &&
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog_update)
+ {
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog_update (tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_prepare (GimpTransformGridTool *tg_tool,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ if (tg_tool->gui)
+ {
+ GimpObject *object = gimp_transform_tool_get_active_object (tr_tool,
+ display);
+
+ gimp_tool_gui_set_shell (tg_tool->gui, gimp_display_get_shell (display));
+ gimp_tool_gui_set_viewable (tg_tool->gui, GIMP_VIEWABLE (object));
+ }
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->prepare)
+ {
+ tg_tool->trans_info = tg_tool->init_trans_info;
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->prepare (tg_tool);
+
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info, sizeof (TransInfo));
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info, sizeof (TransInfo));
+ }
+
+ gimp_matrix3_identity (&tr_tool->transform);
+ tr_tool->transform_valid = TRUE;
+}
+
+static GimpToolWidget *
+gimp_transform_grid_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ static const gchar *properties[] =
+ {
+ "constrain-move",
+ "constrain-scale",
+ "constrain-rotate",
+ "constrain-shear",
+ "constrain-perspective",
+ "frompivot-scale",
+ "frompivot-shear",
+ "frompivot-perspective",
+ "cornersnap",
+ "fixedpivot"
+ };
+
+ GimpToolWidget *widget = NULL;
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_widget)
+ {
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ gint i;
+
+ widget = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_widget (tg_tool);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tg_tool), widget);
+
+ g_object_bind_property (G_OBJECT (options), "grid-type",
+ G_OBJECT (widget), "guide-type",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (G_OBJECT (options), "grid-size",
+ G_OBJECT (widget), "n-guides",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ for (i = 0; i < G_N_ELEMENTS (properties); i++)
+ g_object_bind_property (G_OBJECT (options), properties[i],
+ G_OBJECT (widget), properties[i],
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (gimp_transform_grid_tool_widget_changed),
+ tg_tool);
+ g_signal_connect (widget, "response",
+ G_CALLBACK (gimp_transform_grid_tool_widget_response),
+ tg_tool);
+ }
+
+ return widget;
+}
+
+static void
+gimp_transform_grid_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->widget &&
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->update_widget)
+ {
+ g_signal_handlers_block_by_func (
+ tg_tool->widget,
+ G_CALLBACK (gimp_transform_grid_tool_widget_changed),
+ tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->update_widget (tg_tool);
+
+ g_signal_handlers_unblock_by_func (
+ tg_tool->widget,
+ G_CALLBACK (gimp_transform_grid_tool_widget_changed),
+ tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpDisplay *display = tool->display;
+
+ /* we can get here while already committing a transformation. just return in
+ * this case. see issue #4734.
+ */
+ if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tg_tool)))
+ return;
+
+ switch (response_id)
+ {
+ case RESPONSE_RESET:
+ {
+ gboolean direction_linked;
+
+ /* restore the initial transformation info */
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info,
+ sizeof (TransInfo));
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info,
+ sizeof (TransInfo));
+
+ /* recalculate the tool's transformation matrix */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = FALSE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ /* push the restored info to the undo stack */
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, FALSE);
+ }
+ break;
+
+ case RESPONSE_READJUST:
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust &&
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info &&
+ tr_tool->transform_valid)
+ {
+ TransInfo old_trans_infos[2];
+ gboolean direction_linked;
+ gboolean transform_valid;
+
+ /* save the current transformation info */
+ memcpy (old_trans_infos, tg_tool->trans_infos,
+ sizeof (old_trans_infos));
+
+ /* readjust the transformation info to view */
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust (tg_tool);
+
+ /* recalculate the tool's transformation matrix, preserving the
+ * overall transformation
+ */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = TRUE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ transform_valid = tr_tool->transform_valid;
+
+ /* if the resulting transformation is invalid, or if the
+ * transformation info is already adjusted to view ...
+ */
+ if (! transform_valid ||
+ trans_infos_equal (old_trans_infos, tg_tool->trans_infos))
+ {
+ /* ... readjust the transformation info to the item bounds */
+ GimpMatrix3 transform = tr_tool->transform;
+
+ if (tr_options->direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&transform);
+
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->apply_info (
+ tg_tool, tg_tool->init_trans_info);
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info (
+ tg_tool, &transform);
+
+ /* recalculate the tool's transformation matrix, preserving the
+ * overall transformation
+ */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = TRUE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ if (! tr_tool->transform_valid ||
+ ! trans_infos_equal (old_trans_infos, tg_tool->trans_infos))
+ {
+ transform_valid = tr_tool->transform_valid;
+ }
+ }
+
+ if (transform_valid)
+ {
+ /* push the new info to the undo stack */
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, FALSE);
+ }
+ else
+ {
+ /* restore the old transformation info */
+ memcpy (tg_tool->trans_infos, old_trans_infos,
+ sizeof (old_trans_infos));
+
+ /* recalculate the tool's transformation matrix */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = FALSE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ gimp_tool_message_literal (tool, tool->display,
+ _("Cannot readjust the transformation"));
+ }
+ }
+ break;
+
+ case GTK_RESPONSE_OK:
+ g_return_if_fail (display != NULL);
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ break;
+
+ default:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ /* update the undo actions / menu items */
+ if (display)
+ gimp_image_flush (gimp_display_get_image (display));
+ break;
+ }
+}
+
+static gboolean
+gimp_transform_grid_tool_composited_preview (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ return tg_options->composited_preview &&
+ tr_options->type == GIMP_TRANSFORM_TYPE_LAYER &&
+ gimp_channel_is_empty (gimp_image_get_mask (image));
+}
+
+static void
+gimp_transform_grid_tool_update_sensitivity (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ if (! tg_tool->gui)
+ return;
+
+ gimp_tool_gui_set_response_sensitive (
+ tg_tool->gui, GTK_RESPONSE_OK,
+ tr_tool->transform_valid);
+
+ gimp_tool_gui_set_response_sensitive (
+ tg_tool->gui, RESPONSE_RESET,
+ ! (trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info) &&
+ trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info)));
+
+ gimp_tool_gui_set_response_sensitive (
+ tg_tool->gui, RESPONSE_READJUST,
+ tr_tool->transform_valid);
+}
+
+static void
+gimp_transform_grid_tool_update_preview (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ gint i;
+
+ if (! tool->display)
+ return;
+
+ if (tg_options->show_preview &&
+ gimp_transform_grid_tool_composited_preview (tg_tool) &&
+ tr_tool->transform_valid)
+ {
+ GHashTableIter iter;
+ GimpDrawable *drawable;
+ Filter *filter;
+ gboolean flush = FALSE;
+
+ if (! tg_tool->filters)
+ {
+ tg_tool->filters = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) filter_free);
+
+ gimp_transform_grid_tool_update_filters (tg_tool);
+ }
+
+ g_hash_table_iter_init (&iter, tg_tool->filters);
+
+ while (g_hash_table_iter_next (&iter,
+ (gpointer *) &drawable,
+ (gpointer *) &filter))
+ {
+ GimpMatrix3 transform;
+ GeglRectangle bounds;
+ gint offset_x;
+ gint offset_y;
+ gint width;
+ gint height;
+ gint x1, y1;
+ gint x2, y2;
+ gboolean update = FALSE;
+
+ if (! filter->filter)
+ continue;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
+
+ width = gimp_item_get_width (GIMP_ITEM (drawable));
+ height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ gimp_matrix3_identity (&transform);
+ gimp_matrix3_translate (&transform, +offset_x, +offset_y);
+ gimp_matrix3_mult (&tr_tool->transform, &transform);
+ gimp_matrix3_translate (&transform, -offset_x, -offset_y);
+
+ gimp_transform_resize_boundary (&tr_tool->transform,
+ gimp_item_get_clip (
+ GIMP_ITEM (filter->root_drawable),
+ tr_options->clip),
+ offset_x, offset_y,
+ offset_x + width, offset_y + height,
+ &x1, &y1,
+ &x2, &y2);
+
+ bounds.x = x1 - offset_x;
+ bounds.y = y1 - offset_y;
+ bounds.width = x2 - x1;
+ bounds.height = y2 - y1;
+
+ if (! gimp_matrix3_equal (&transform, &filter->transform))
+ {
+ filter->transform = transform;
+
+ gimp_gegl_node_set_matrix (filter->transform_node, &transform);
+
+ update = TRUE;
+ }
+
+ if (! gegl_rectangle_equal (&bounds, &filter->bounds))
+ {
+ filter->bounds = bounds;
+
+ gegl_node_set (filter->crop_node,
+ "x", (gdouble) bounds.x,
+ "y", (gdouble) bounds.y,
+ "width", (gdouble) bounds.width,
+ "height", (gdouble) bounds.height,
+ NULL);
+
+ update = TRUE;
+ }
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ gimp_drawable_filter_set_add_alpha (
+ filter->filter,
+ tr_options->interpolation != GIMP_INTERPOLATION_NONE);
+ }
+
+ if (update)
+ {
+ if (tg_options->synchronous_preview)
+ {
+ g_signal_handlers_block_by_func (
+ filter->filter,
+ G_CALLBACK (gimp_transform_grid_tool_filter_flush),
+ tg_tool);
+ }
+
+ gimp_drawable_filter_apply (filter->filter, NULL);
+
+ if (tg_options->synchronous_preview)
+ {
+ g_signal_handlers_unblock_by_func (
+ filter->filter,
+ G_CALLBACK (gimp_transform_grid_tool_filter_flush),
+ tg_tool);
+
+ flush = TRUE;
+ }
+ }
+ }
+
+ if (flush)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (tool->display);
+ }
+ }
+ else
+ {
+ g_clear_pointer (&tg_tool->filters, g_hash_table_unref);
+ g_clear_pointer (&tg_tool->preview_drawables, g_list_free);
+ }
+
+ if (tg_tool->preview)
+ {
+ if (tg_options->show_preview &&
+ ! gimp_transform_grid_tool_composited_preview (tg_tool) &&
+ tr_tool->transform_valid)
+ {
+ gimp_canvas_item_begin_change (tg_tool->preview);
+ gimp_canvas_item_set_visible (tg_tool->preview, TRUE);
+ g_object_set (
+ tg_tool->preview,
+ "transform", &tr_tool->transform,
+ "clip", gimp_item_get_clip (GIMP_ITEM (tool->drawable),
+ tr_options->clip),
+ "opacity", tg_options->preview_opacity,
+ NULL);
+ gimp_canvas_item_end_change (tg_tool->preview);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (tg_tool->preview, FALSE);
+ }
+ }
+
+ if (tg_tool->boundary_in)
+ {
+ gimp_canvas_item_begin_change (tg_tool->boundary_in);
+ gimp_canvas_item_set_visible (tg_tool->boundary_in,
+ tr_tool->transform_valid);
+ g_object_set (tg_tool->boundary_in,
+ "transform", &tr_tool->transform,
+ NULL);
+ gimp_canvas_item_end_change (tg_tool->boundary_in);
+ }
+
+ if (tg_tool->boundary_out)
+ {
+ gimp_canvas_item_begin_change (tg_tool->boundary_out);
+ gimp_canvas_item_set_visible (tg_tool->boundary_out,
+ tr_tool->transform_valid);
+ g_object_set (tg_tool->boundary_out,
+ "transform", &tr_tool->transform,
+ NULL);
+ gimp_canvas_item_end_change (tg_tool->boundary_out);
+ }
+
+ for (i = 0; i < tg_tool->strokes->len; i++)
+ {
+ GimpCanvasItem *item = g_ptr_array_index (tg_tool->strokes, i);
+
+ gimp_canvas_item_begin_change (item);
+ gimp_canvas_item_set_visible (item, tr_tool->transform_valid);
+ g_object_set (item,
+ "transform", &tr_tool->transform,
+ NULL);
+ gimp_canvas_item_end_change (item);
+ }
+}
+
+static void
+gimp_transform_grid_tool_update_filters (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GHashTable *new_drawables;
+ GList *drawables;
+ GList *iter;
+ GimpDrawable *drawable;
+ GHashTableIter hash_iter;
+
+ if (! tg_tool->filters)
+ return;
+
+ if (options->preview_linked &&
+ gimp_item_get_linked (GIMP_ITEM (tool->drawable)))
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ drawables = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_LAYERS |
+ GIMP_ITEM_TYPE_CHANNELS,
+ GIMP_ITEM_SET_LINKED);
+
+ drawables = gimp_image_item_list_filter (drawables);
+ }
+ else
+ {
+ drawables = g_list_prepend (NULL, tool->drawable);
+ }
+
+ new_drawables = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ for (iter = drawables; iter; iter = g_list_next (iter))
+ g_hash_table_add (new_drawables, iter->data);
+
+ for (iter = tg_tool->preview_drawables; iter; iter = g_list_next (iter))
+ {
+ drawable = iter->data;
+
+ if (! g_hash_table_remove (new_drawables, drawable))
+ gimp_transform_grid_tool_remove_filter (drawable, tg_tool);
+ }
+
+ g_hash_table_iter_init (&hash_iter, new_drawables);
+
+ while (g_hash_table_iter_next (&hash_iter, (gpointer *) &drawable, NULL))
+ {
+ AddFilterData data;
+
+ data.tg_tool = tg_tool;
+ data.root_drawable = drawable;
+
+ gimp_transform_grid_tool_add_filter (drawable, &data);
+ }
+
+ g_hash_table_unref (new_drawables);
+
+ g_list_free (tg_tool->preview_drawables);
+ tg_tool->preview_drawables = drawables;
+}
+
+static void
+gimp_transform_grid_tool_hide_active_object (GimpTransformGridTool *tg_tool,
+ GimpObject *object)
+{
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_OPTIONS (options);
+ GimpDisplay *display = GIMP_TOOL (tg_tool)->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (options->show_preview)
+ {
+ /* hide only complete layers and channels, not layer masks */
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_LAYER &&
+ ! options->composited_preview &&
+ GIMP_IS_DRAWABLE (object) &&
+ ! GIMP_IS_LAYER_MASK (object) &&
+ gimp_item_get_visible (GIMP_ITEM (object)) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ tg_tool->hidden_object = object;
+
+ gimp_item_set_visible (GIMP_ITEM (object), FALSE, FALSE);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+ }
+ else if (tr_options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ tg_tool->hidden_object = object;
+
+ gimp_display_shell_set_show_image (gimp_display_get_shell (display),
+ FALSE);
+ }
+ }
+}
+
+static void
+gimp_transform_grid_tool_show_active_object (GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->hidden_object)
+ {
+ GimpDisplay *display = GIMP_TOOL (tg_tool)->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (GIMP_IS_ITEM (tg_tool->hidden_object))
+ {
+ gimp_item_set_visible (GIMP_ITEM (tg_tool->hidden_object), TRUE,
+ FALSE);
+ }
+ else
+ {
+ g_return_if_fail (GIMP_IS_IMAGE (tg_tool->hidden_object));
+
+ gimp_display_shell_set_show_image (gimp_display_get_shell (display),
+ TRUE);
+ }
+
+ tg_tool->hidden_object = NULL;
+
+ gimp_image_flush (image);
+ }
+}
+
+static void
+gimp_transform_grid_tool_add_filter (GimpDrawable *drawable,
+ AddFilterData *data)
+{
+ Filter *filter;
+ GimpLayerMode mode = GIMP_LAYER_MODE_NORMAL;
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ gimp_layer_get_effective_mode (GIMP_LAYER (drawable),
+ &mode, NULL, NULL, NULL);
+ }
+
+ if (mode != GIMP_LAYER_MODE_PASS_THROUGH)
+ {
+ filter = filter_new (data->tg_tool, drawable, data->root_drawable, TRUE);
+ }
+ else
+ {
+ GimpContainer *container;
+
+ filter = filter_new (data->tg_tool, drawable, data->root_drawable, FALSE);
+
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (drawable));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_transform_grid_tool_add_filter,
+ data);
+ }
+
+ g_hash_table_insert (data->tg_tool->filters, drawable, filter);
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ if (mask)
+ gimp_transform_grid_tool_add_filter (GIMP_DRAWABLE (mask), data);
+ }
+}
+
+static void
+gimp_transform_grid_tool_remove_filter (GimpDrawable *drawable,
+ GimpTransformGridTool *tg_tool)
+{
+ Filter *filter = g_hash_table_lookup (tg_tool->filters, drawable);
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ if (mask)
+ gimp_transform_grid_tool_remove_filter (GIMP_DRAWABLE (mask), tg_tool);
+ }
+
+ if (! filter->filter)
+ {
+ GimpContainer *container;
+
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (drawable));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_transform_grid_tool_remove_filter,
+ tg_tool);
+ }
+
+ g_hash_table_remove (tg_tool->filters, drawable);
+}
+
+static void
+gimp_transform_grid_tool_effective_mode_changed (GimpLayer *layer,
+ GimpTransformGridTool *tg_tool)
+{
+ Filter *filter = g_hash_table_lookup (tg_tool->filters, layer);
+ GimpLayerMode mode;
+ gboolean old_pass_through;
+ gboolean new_pass_through;
+
+ gimp_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);
+
+ old_pass_through = ! filter->filter;
+ new_pass_through = mode == GIMP_LAYER_MODE_PASS_THROUGH;
+
+ if (old_pass_through != new_pass_through)
+ {
+ AddFilterData data;
+
+ data.tg_tool = tg_tool;
+ data.root_drawable = filter->root_drawable;
+
+ gimp_transform_grid_tool_remove_filter (GIMP_DRAWABLE (layer), tg_tool);
+ gimp_transform_grid_tool_add_filter (GIMP_DRAWABLE (layer), &data);
+
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+}
+
+static Filter *
+filter_new (GimpTransformGridTool *tg_tool,
+ GimpDrawable *drawable,
+ GimpDrawable *root_drawable,
+ gboolean add_filter)
+{
+ Filter *filter = g_slice_new0 (Filter);
+ GeglNode *node;
+ GeglNode *input_node;
+ GeglNode *output_node;
+
+ filter->tg_tool = tg_tool;
+ filter->drawable = drawable;
+ filter->root_drawable = root_drawable;
+
+ if (add_filter)
+ {
+ node = gegl_node_new ();
+
+ input_node = gegl_node_get_input_proxy (node, "input");
+ output_node = gegl_node_get_input_proxy (node, "output");
+
+ filter->transform_node = gegl_node_new_child (
+ node,
+ "operation", "gegl:transform",
+ "near-z", GIMP_TRANSFORM_NEAR_Z,
+ "sampler", GEGL_SAMPLER_NEAREST,
+ NULL);
+
+ filter->crop_node = gegl_node_new_child (
+ node,
+ "operation", "gegl:crop",
+ NULL);
+
+ gegl_node_link_many (input_node,
+ filter->transform_node,
+ filter->crop_node,
+ output_node,
+ NULL);
+
+ gimp_gegl_node_set_underlying_operation (node, filter->transform_node);
+
+ filter->filter = gimp_drawable_filter_new (
+ drawable,
+ GIMP_TRANSFORM_TOOL_GET_CLASS (tg_tool)->undo_desc,
+ node,
+ gimp_tool_get_icon_name (GIMP_TOOL (tg_tool)));
+
+ gimp_drawable_filter_set_clip (filter->filter, FALSE);
+ gimp_drawable_filter_set_override_constraints (filter->filter, TRUE);
+
+ g_signal_connect (
+ filter->filter, "flush",
+ G_CALLBACK (gimp_transform_grid_tool_filter_flush),
+ tg_tool);
+
+ g_object_unref (node);
+ }
+
+ if (GIMP_IS_GROUP_LAYER (drawable))
+ {
+ g_signal_connect (
+ drawable, "effective-mode-changed",
+ G_CALLBACK (gimp_transform_grid_tool_effective_mode_changed),
+ tg_tool);
+ }
+
+ return filter;
+}
+
+static void
+filter_free (Filter *filter)
+{
+ if (filter->filter)
+ {
+ gimp_drawable_filter_abort (filter->filter);
+
+ g_object_unref (filter->filter);
+ }
+
+ if (GIMP_IS_GROUP_LAYER (filter->drawable))
+ {
+ g_signal_handlers_disconnect_by_func (
+ filter->drawable,
+ gimp_transform_grid_tool_effective_mode_changed,
+ filter->tg_tool);
+ }
+
+ g_slice_free (Filter, filter);
+}
+
+static UndoInfo *
+undo_info_new (void)
+{
+ return g_slice_new0 (UndoInfo);
+}
+
+static void
+undo_info_free (UndoInfo *info)
+{
+ g_slice_free (UndoInfo, info);
+}
+
+static gboolean
+trans_info_equal (const TransInfo trans_info1,
+ const TransInfo trans_info2)
+{
+ gint i;
+
+ for (i = 0; i < TRANS_INFO_SIZE; i++)
+ {
+ if (fabs (trans_info1[i] - trans_info2[i]) > EPSILON)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+trans_infos_equal (const TransInfo *trans_infos1,
+ const TransInfo *trans_infos2)
+{
+ return trans_info_equal (trans_infos1[GIMP_TRANSFORM_FORWARD],
+ trans_infos2[GIMP_TRANSFORM_FORWARD]) &&
+ trans_info_equal (trans_infos1[GIMP_TRANSFORM_BACKWARD],
+ trans_infos2[GIMP_TRANSFORM_BACKWARD]);
+}
+
+gboolean
+gimp_transform_grid_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool), FALSE);
+ g_return_val_if_fail (transform != NULL, FALSE);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix)
+ {
+ return GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix (
+ tg_tool, transform);
+ }
+
+ return FALSE;
+}
+
+void
+gimp_transform_grid_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ g_return_if_fail (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool));
+ g_return_if_fail (transform != NULL);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info)
+ {
+ return GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info (
+ tg_tool, transform);
+ }
+}
+
+void
+gimp_transform_grid_tool_push_internal_undo (GimpTransformGridTool *tg_tool,
+ gboolean compress)
+{
+ UndoInfo *undo_info;
+
+ g_return_if_fail (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool));
+ g_return_if_fail (tg_tool->undo_list != NULL);
+
+ undo_info = tg_tool->undo_list->data;
+
+ /* push current state on the undo list and set this state as the
+ * current state, but avoid doing this if there were no changes
+ */
+ if (! trans_infos_equal (undo_info->trans_infos, tg_tool->trans_infos))
+ {
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ gint64 time = 0;
+ gboolean flush = FALSE;
+
+ if (tg_tool->undo_list->next == NULL)
+ flush = TRUE;
+
+ if (compress)
+ time = g_get_monotonic_time ();
+
+ if (! compress || time - undo_info->time >= UNDO_COMPRESS_TIME)
+ {
+ undo_info = undo_info_new ();
+
+ tg_tool->undo_list = g_list_prepend (tg_tool->undo_list, undo_info);
+ }
+
+ undo_info->time = time;
+ undo_info->direction = tr_options->direction;
+ memcpy (undo_info->trans_infos, tg_tool->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* If we undid anything and started interacting, we have to
+ * discard the redo history
+ */
+ if (tg_tool->redo_list)
+ {
+ g_list_free_full (tg_tool->redo_list,
+ (GDestroyNotify) undo_info_free);
+ tg_tool->redo_list = NULL;
+
+ flush = TRUE;
+ }
+
+ gimp_transform_grid_tool_update_sensitivity (tg_tool);
+
+ /* update the undo actions / menu items */
+ if (flush)
+ {
+ gimp_image_flush (
+ gimp_display_get_image (GIMP_TOOL (tg_tool)->display));
+ }
+ }
+}
diff --git a/app/tools/gimptransformgridtool.h b/app/tools/gimptransformgridtool.h
new file mode 100644
index 0000000..3a83418
--- /dev/null
+++ b/app/tools/gimptransformgridtool.h
@@ -0,0 +1,118 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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_TRANSFORM_GRID_TOOL_H__
+#define __GIMP_TRANSFORM_GRID_TOOL_H__
+
+
+#include "gimptransformtool.h"
+
+
+/* This is not the number of items in the enum above, but the max size
+ * of the enums at the top of each transformation tool, stored in
+ * trans_info and related
+ */
+#define TRANS_INFO_SIZE 17
+
+typedef gdouble TransInfo[TRANS_INFO_SIZE];
+
+
+#define GIMP_TYPE_TRANSFORM_GRID_TOOL (gimp_transform_grid_tool_get_type ())
+#define GIMP_TRANSFORM_GRID_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_GRID_TOOL, GimpTransformGridTool))
+#define GIMP_TRANSFORM_GRID_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_GRID_TOOL, GimpTransformGridToolClass))
+#define GIMP_IS_TRANSFORM_GRID_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_GRID_TOOL))
+#define GIMP_IS_TRANSFORM_GRID_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSFORM_GRID_TOOL))
+#define GIMP_TRANSFORM_GRID_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_GRID_TOOL, GimpTransformGridToolClass))
+
+#define GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS(t) (GIMP_TRANSFORM_GRID_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpTransformGridToolClass GimpTransformGridToolClass;
+
+struct _GimpTransformGridTool
+{
+ GimpTransformTool parent_instance;
+
+ TransInfo init_trans_info; /* initial transformation info */
+ TransInfo trans_infos[2]; /* forward/backward transformation info */
+ gdouble *trans_info; /* current transformation info */
+ GList *undo_list; /* list of all states,
+ head is current == prev_trans_info,
+ tail is original == old_trans_info */
+ GList *redo_list; /* list of all undone states,
+ NULL when nothing undone */
+
+ GimpObject *hidden_object; /* the object that was hidden during
+ the transform */
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+ GimpCanvasItem *preview;
+ GimpCanvasItem *boundary_in;
+ GimpCanvasItem *boundary_out;
+ GPtrArray *strokes;
+
+ GHashTable *filters;
+ GList *preview_drawables;
+
+ GimpToolGui *gui;
+};
+
+struct _GimpTransformGridToolClass
+{
+ GimpTransformToolClass parent_class;
+
+ /* virtual functions */
+ gboolean (* info_to_matrix) (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+ void (* matrix_to_info) (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+ void (* apply_info) (GimpTransformGridTool *tg_tool,
+ const TransInfo info);
+ gchar * (* get_undo_desc) (GimpTransformGridTool *tg_tool);
+ void (* dialog) (GimpTransformGridTool *tg_tool);
+ void (* dialog_update) (GimpTransformGridTool *tg_tool);
+ void (* prepare) (GimpTransformGridTool *tg_tool);
+ void (* readjust) (GimpTransformGridTool *tg_tool);
+ GimpToolWidget * (* get_widget) (GimpTransformGridTool *tg_tool);
+ void (* update_widget) (GimpTransformGridTool *tg_tool);
+ void (* widget_changed) (GimpTransformGridTool *tg_tool);
+ GeglBuffer * (* transform) (GimpTransformGridTool *tg_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+ const gchar *ok_button_label;
+};
+
+
+GType gimp_transform_grid_tool_get_type (void) G_GNUC_CONST;
+
+gboolean gimp_transform_grid_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform);
+void gimp_transform_grid_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+
+void gimp_transform_grid_tool_push_internal_undo (GimpTransformGridTool *tg_tool,
+ gboolean compress);
+
+
+#endif /* __GIMP_TRANSFORM_GRID_TOOL_H__ */
diff --git a/app/tools/gimptransformgridtoolundo.c b/app/tools/gimptransformgridtoolundo.c
new file mode 100644
index 0000000..035d9e5
--- /dev/null
+++ b/app/tools/gimptransformgridtoolundo.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 "tools-types.h"
+
+#include "gimptoolcontrol.h"
+#include "gimptransformgridtool.h"
+#include "gimptransformgridtoolundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_TRANSFORM_TOOL
+};
+
+
+static void gimp_transform_grid_tool_undo_constructed (GObject *object);
+static void gimp_transform_grid_tool_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_transform_grid_tool_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_transform_grid_tool_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_transform_grid_tool_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpTransformGridToolUndo, gimp_transform_grid_tool_undo, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_transform_grid_tool_undo_parent_class
+
+
+static void
+gimp_transform_grid_tool_undo_class_init (GimpTransformGridToolUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_transform_grid_tool_undo_constructed;
+ object_class->set_property = gimp_transform_grid_tool_undo_set_property;
+ object_class->get_property = gimp_transform_grid_tool_undo_get_property;
+
+ undo_class->pop = gimp_transform_grid_tool_undo_pop;
+ undo_class->free = gimp_transform_grid_tool_undo_free;
+
+ g_object_class_install_property (object_class, PROP_TRANSFORM_TOOL,
+ g_param_spec_object ("transform-tool",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_GRID_TOOL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_transform_grid_tool_undo_init (GimpTransformGridToolUndo *undo)
+{
+}
+
+static void
+gimp_transform_grid_tool_undo_constructed (GObject *object)
+{
+ GimpTransformGridToolUndo *tg_tool_undo = GIMP_TRANSFORM_GRID_TOOL_UNDO (object);
+ GimpTransformGridTool *tg_tool;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool_undo->tg_tool));
+
+ tg_tool = tg_tool_undo->tg_tool;
+
+ memcpy (tg_tool_undo->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info, sizeof (TransInfo));
+ memcpy (tg_tool_undo->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info, sizeof (TransInfo));
+
+#if 0
+ if (tg_tool->original)
+ tg_tool_undo->original = tile_manager_ref (tg_tool->original);
+#endif
+
+ g_object_add_weak_pointer (G_OBJECT (tg_tool_undo->tg_tool),
+ (gpointer) &tg_tool_undo->tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransformGridToolUndo *tg_tool_undo = GIMP_TRANSFORM_GRID_TOOL_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_TRANSFORM_TOOL:
+ tg_tool_undo->tg_tool = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_transform_grid_tool_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransformGridToolUndo *tg_tool_undo = GIMP_TRANSFORM_GRID_TOOL_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_TRANSFORM_TOOL:
+ g_value_set_object (value, tg_tool_undo->tg_tool);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_transform_grid_tool_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpTransformGridToolUndo *tg_tool_undo = GIMP_TRANSFORM_GRID_TOOL_UNDO (undo);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if (tg_tool_undo->tg_tool)
+ {
+ GimpTransformGridTool *tg_tool;
+#if 0
+ TileManager *temp;
+#endif
+ TransInfo temp_trans_infos[2];
+
+ tg_tool = tg_tool_undo->tg_tool;
+
+ /* swap the transformation information00 arrays */
+ memcpy (temp_trans_infos, tg_tool_undo->trans_infos,
+ sizeof (tg_tool->trans_infos));
+ memcpy (tg_tool_undo->trans_infos, tg_tool->trans_infos,
+ sizeof (tg_tool->trans_infos));
+ memcpy (tg_tool->trans_infos, temp_trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+#if 0
+ /* swap the original buffer--the source buffer for repeated transform_grids
+ */
+ temp = tg_tool_undo->original;
+ tg_tool_undo->original = tg_tool->original;
+ tg_tool->original = temp;
+#endif
+
+#if 0
+ /* If we're re-implementing the first transform_grid, reactivate tool */
+ if (undo_mode == GIMP_UNDO_MODE_REDO && tg_tool->original)
+ {
+ gimp_tool_control_activate (GIMP_TOOL (tg_tool)->control);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tg_tool));
+ }
+#endif
+ }
+ }
+
+static void
+gimp_transform_grid_tool_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpTransformGridToolUndo *tg_tool_undo = GIMP_TRANSFORM_GRID_TOOL_UNDO (undo);
+
+ if (tg_tool_undo->tg_tool)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (tg_tool_undo->tg_tool),
+ (gpointer) &tg_tool_undo->tg_tool);
+ tg_tool_undo->tg_tool = NULL;
+ }
+
+#if 0
+ if (tg_tool_undo->original)
+ {
+ tile_manager_unref (tg_tool_undo->original);
+ tg_tool_undo->original = NULL;
+ }
+#endif
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/tools/gimptransformgridtoolundo.h b/app/tools/gimptransformgridtoolundo.h
new file mode 100644
index 0000000..3fc7a24
--- /dev/null
+++ b/app/tools/gimptransformgridtoolundo.h
@@ -0,0 +1,56 @@
+/* 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_TRANSFORM_GRID_TOOL_UNDO_H__
+#define __GIMP_TRANSFORM_GRID_TOOL_UNDO_H__
+
+
+#include "core/gimpundo.h"
+
+
+#define GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO (gimp_transform_grid_tool_undo_get_type ())
+#define GIMP_TRANSFORM_GRID_TOOL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO, GimpTransformGridToolUndo))
+#define GIMP_TRANSFORM_GRID_TOOL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO, GimpTransformGridToolUndoClass))
+#define GIMP_IS_TRANSFORM_GRID_TOOL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO))
+#define GIMP_IS_TRANSFORM_GRID_TOOL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO))
+#define GIMP_TRANSFORM_GRID_TOOL_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO, GimpTransformGridToolUndoClass))
+
+
+typedef struct _GimpTransformGridToolUndo GimpTransformGridToolUndo;
+typedef struct _GimpTransformGridToolUndoClass GimpTransformGridToolUndoClass;
+
+struct _GimpTransformGridToolUndo
+{
+ GimpUndo parent_instance;
+
+ GimpTransformGridTool *tg_tool;
+ TransInfo trans_infos[2];
+#if 0
+ TileManager *original;
+#endif
+};
+
+struct _GimpTransformGridToolUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_transform_grid_tool_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_TRANSFORM_GRID_TOOL_UNDO_H__ */
diff --git a/app/tools/gimptransformoptions.c b/app/tools/gimptransformoptions.c
new file mode 100644
index 0000000..d1c75e6
--- /dev/null
+++ b/app/tools/gimptransformoptions.c
@@ -0,0 +1,271 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimptooloptions-gui.h"
+#include "gimptransformoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_DIRECTION,
+ PROP_INTERPOLATION,
+ PROP_CLIP
+};
+
+
+static void gimp_transform_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_transform_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_transform_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_transform_options_reset (GimpConfig *config);
+
+G_DEFINE_TYPE_WITH_CODE (GimpTransformOptions, gimp_transform_options,
+ GIMP_TYPE_TOOL_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_transform_options_config_iface_init))
+
+#define parent_class gimp_transform_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_transform_options_class_init (GimpTransformOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_transform_options_set_property;
+ object_class->get_property = gimp_transform_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_TYPE,
+ "type",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_TYPE,
+ GIMP_TRANSFORM_TYPE_LAYER,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_DIRECTION,
+ "direction",
+ _("Direction"),
+ _("Direction of transformation"),
+ GIMP_TYPE_TRANSFORM_DIRECTION,
+ GIMP_TRANSFORM_FORWARD,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_INTERPOLATION,
+ "interpolation",
+ _("Interpolation"),
+ _("Interpolation method"),
+ GIMP_TYPE_INTERPOLATION_TYPE,
+ GIMP_INTERPOLATION_LINEAR,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_CLIP,
+ "clip",
+ _("Clipping"),
+ _("How to clip"),
+ GIMP_TYPE_TRANSFORM_RESIZE,
+ GIMP_TRANSFORM_RESIZE_ADJUST,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_transform_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ config_iface->reset = gimp_transform_options_reset;
+}
+
+static void
+gimp_transform_options_init (GimpTransformOptions *options)
+{
+}
+
+static void
+gimp_transform_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransformOptions *options = GIMP_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ options->type = g_value_get_enum (value);
+ break;
+ case PROP_DIRECTION:
+ options->direction = g_value_get_enum (value);
+ break;
+ case PROP_INTERPOLATION:
+ options->interpolation = g_value_get_enum (value);
+ break;
+ case PROP_CLIP:
+ options->clip = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_transform_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTransformOptions *options = GIMP_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ g_value_set_enum (value, options->type);
+ break;
+ case PROP_DIRECTION:
+ g_value_set_enum (value, options->direction);
+ break;
+ case PROP_INTERPOLATION:
+ g_value_set_enum (value, options->interpolation);
+ break;
+ case PROP_CLIP:
+ g_value_set_enum (value, options->clip);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_transform_options_reset (GimpConfig *config)
+{
+ GimpToolOptions *tool_options = GIMP_TOOL_OPTIONS (config);
+ GParamSpec *pspec;
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+ "interpolation");
+
+ if (pspec)
+ G_PARAM_SPEC_ENUM (pspec)->default_value =
+ tool_options->tool_info->gimp->config->interpolation_type;
+
+ parent_config_iface->reset (config);
+}
+
+/**
+ * gimp_transform_options_gui:
+ * @tool_options: a #GimpToolOptions
+ * @direction: whether to show the direction frame
+ * @interpolation: whether to show the interpolation menu
+ * @clipping: whether to show the clipping menu
+ *
+ * Build the Transform Tool Options.
+ *
+ * Return value: a container holding the transform tool options
+ **/
+GtkWidget *
+gimp_transform_options_gui (GimpToolOptions *tool_options,
+ gboolean direction,
+ gboolean interpolation,
+ gboolean clipping)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpTransformOptions *options = GIMP_TRANSFORM_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *hbox;
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *frame;
+ GtkWidget *combo;
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ options->type_box = hbox;
+
+ label = gtk_label_new (_("Transform:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ box = gimp_prop_enum_icon_box_new (config, "type", "gimp", 0, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), box, FALSE, FALSE, 0);
+ gtk_widget_show (box);
+
+ if (direction)
+ {
+ frame = gimp_prop_enum_radio_frame_new (config, "direction", NULL,
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ options->direction_frame = frame;
+ }
+
+ /* the interpolation menu */
+ if (interpolation)
+ {
+ combo = gimp_prop_enum_combo_box_new (config, "interpolation", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Interpolation"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+ }
+
+ /* the clipping menu */
+ if (clipping)
+ {
+ combo = gimp_prop_enum_combo_box_new (config, "clip", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Clipping"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+ }
+
+ return vbox;
+}
diff --git a/app/tools/gimptransformoptions.h b/app/tools/gimptransformoptions.h
new file mode 100644
index 0000000..ca8d032
--- /dev/null
+++ b/app/tools/gimptransformoptions.h
@@ -0,0 +1,64 @@
+/* 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_TRANSFORM_OPTIONS_H__
+#define __GIMP_TRANSFORM_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_TRANSFORM_OPTIONS (gimp_transform_options_get_type ())
+#define GIMP_TRANSFORM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_OPTIONS, GimpTransformOptions))
+#define GIMP_TRANSFORM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_OPTIONS, GimpTransformOptionsClass))
+#define GIMP_IS_TRANSFORM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_OPTIONS))
+#define GIMP_IS_TRANSFORM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSFORM_OPTIONS))
+#define GIMP_TRANSFORM_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_OPTIONS, GimpTransformOptionsClass))
+
+
+typedef struct _GimpTransformOptions GimpTransformOptions;
+typedef struct _GimpTransformOptionsClass GimpTransformOptionsClass;
+
+struct _GimpTransformOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpTransformType type;
+ GimpTransformDirection direction;
+ GimpInterpolationType interpolation;
+ GimpTransformResize clip;
+
+ /* options gui */
+ GtkWidget *type_box;
+ GtkWidget *direction_frame;
+};
+
+struct _GimpTransformOptionsClass
+{
+ GimpToolOptionsClass parent_class;
+};
+
+
+GType gimp_transform_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_transform_options_gui (GimpToolOptions *tool_options,
+ gboolean direction,
+ gboolean interpolation,
+ gboolean clipping);
+
+
+#endif /* __GIMP_TRANSFORM_OPTIONS_H__ */
diff --git a/app/tools/gimptransformtool.c b/app/tools/gimptransformtool.c
new file mode 100644
index 0000000..3190e14
--- /dev/null
+++ b/app/tools/gimptransformtool.c
@@ -0,0 +1,925 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable-transform.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-item-list.h"
+#include "core/gimpimage-transform.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpitem-linked.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimpprogress.h"
+#include "core/gimp-transform-resize.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+
+#include "widgets/gimpmessagedialog.h"
+#include "widgets/gimpmessagebox.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+#include "gimptransformoptions.h"
+#include "gimptransformtool.h"
+
+#include "gimp-intl.h"
+
+
+/* the minimal ratio between the transformed item size and the image size,
+ * above which confirmation is required.
+ */
+#define MIN_CONFIRMATION_RATIO 10
+
+
+/* local function prototypes */
+
+static void gimp_transform_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+
+static gchar * gimp_transform_tool_real_get_undo_desc (GimpTransformTool *tr_tool);
+static GimpTransformDirection gimp_transform_tool_real_get_direction (GimpTransformTool *tr_tool);
+static GeglBuffer * gimp_transform_tool_real_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+static void gimp_transform_tool_halt (GimpTransformTool *tr_tool);
+
+static gboolean gimp_transform_tool_confirm (GimpTransformTool *tr_tool,
+ GimpDisplay *display);
+
+
+G_DEFINE_TYPE (GimpTransformTool, gimp_transform_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_transform_tool_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_transform_tool_class_init (GimpTransformToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ tool_class->control = gimp_transform_tool_control;
+
+ klass->recalc_matrix = NULL;
+ klass->get_undo_desc = gimp_transform_tool_real_get_undo_desc;
+ klass->get_direction = gimp_transform_tool_real_get_direction;
+ klass->transform = gimp_transform_tool_real_transform;
+
+ klass->undo_desc = _("Transform");
+ klass->progress_text = _("Transforming");
+}
+
+static void
+gimp_transform_tool_init (GimpTransformTool *tr_tool)
+{
+ gimp_matrix3_identity (&tr_tool->transform);
+ tr_tool->transform_valid = TRUE;
+
+ tr_tool->restore_type = FALSE;
+}
+
+static void
+gimp_transform_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_transform_tool_halt (tr_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static gchar *
+gimp_transform_tool_real_get_undo_desc (GimpTransformTool *tr_tool)
+{
+ return g_strdup (GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->undo_desc);
+}
+
+static GimpTransformDirection
+gimp_transform_tool_real_get_direction (GimpTransformTool *tr_tool)
+{
+ GimpTransformOptions *options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ return options->direction;
+}
+
+static GeglBuffer *
+gimp_transform_tool_real_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ GimpTransformToolClass *klass = GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool);
+ GimpTool *tool = GIMP_TOOL (tr_tool);
+ GimpTransformOptions *options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tool);
+ GimpContext *context = GIMP_CONTEXT (options);
+ GeglBuffer *ret = NULL;
+ GimpTransformResize clip = options->clip;
+ GimpTransformDirection direction;
+ GimpProgress *progress;
+
+ direction = GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->get_direction (tr_tool);
+
+ progress = gimp_progress_start (GIMP_PROGRESS (tool), FALSE,
+ "%s", klass->progress_text);
+
+ if (orig_buffer)
+ {
+ /* this happens when transforming a selection cut out of a
+ * normal drawable
+ */
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (object), NULL);
+
+ ret = gimp_drawable_transform_buffer_affine (GIMP_DRAWABLE (object),
+ context,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ &tr_tool->transform,
+ direction,
+ options->interpolation,
+ clip,
+ buffer_profile,
+ new_offset_x,
+ new_offset_y,
+ progress);
+ }
+ else if (GIMP_IS_ITEM (object))
+ {
+ /* this happens for entire drawables, paths and layer groups */
+
+ GimpItem *item = GIMP_ITEM (object);
+
+ if (gimp_item_get_linked (item))
+ {
+ gimp_item_linked_transform (item, context,
+ &tr_tool->transform,
+ direction,
+ options->interpolation,
+ clip,
+ progress);
+ }
+ else
+ {
+ clip = gimp_item_get_clip (item, clip);
+
+ gimp_item_transform (item, context,
+ &tr_tool->transform,
+ direction,
+ options->interpolation,
+ clip,
+ progress);
+ }
+ }
+ else
+ {
+ /* this happens for images */
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (object), NULL);
+
+ gimp_image_transform (GIMP_IMAGE (object), context,
+ &tr_tool->transform,
+ direction,
+ options->interpolation,
+ clip,
+ progress);
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ return ret;
+}
+
+static void
+gimp_transform_tool_halt (GimpTransformTool *tr_tool)
+{
+ GimpTransformOptions *options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ tr_tool->x1 = 0;
+ tr_tool->y1 = 0;
+ tr_tool->x2 = 0;
+ tr_tool->y2 = 0;
+
+ if (tr_tool->restore_type)
+ {
+ g_object_set (options,
+ "type", tr_tool->saved_type,
+ NULL);
+
+ tr_tool->restore_type = FALSE;
+ }
+}
+
+static gboolean
+gimp_transform_tool_confirm (GimpTransformTool *tr_tool,
+ GimpDisplay *display)
+{
+ GimpTransformOptions *options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpObject *active_object;
+ gdouble max_ratio = 0.0;
+ GimpObject *max_ratio_object = NULL;
+
+ active_object = gimp_transform_tool_get_active_object (tr_tool, display);
+
+ if (GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->recalc_matrix)
+ {
+ GimpMatrix3 transform;
+ GimpTransformDirection direction;
+ GeglRectangle selection_bounds;
+ gboolean selection_empty = TRUE;
+ GList *objects;
+ GList *iter;
+
+ transform = tr_tool->transform;
+ direction = GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->get_direction (
+ tr_tool);
+
+ if (direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&transform);
+
+ if (options->type == GIMP_TRANSFORM_TYPE_LAYER &&
+ ! gimp_viewable_get_children (GIMP_VIEWABLE (active_object)))
+ {
+ selection_empty = ! gimp_item_bounds (
+ GIMP_ITEM (gimp_image_get_mask (image)),
+ &selection_bounds.x, &selection_bounds.y,
+ &selection_bounds.width, &selection_bounds.height);
+ }
+
+ if (selection_empty &&
+ GIMP_IS_ITEM (active_object) &&
+ gimp_item_get_linked (GIMP_ITEM (active_object)))
+ {
+ objects = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_LINKED);
+ }
+ else
+ {
+ objects = g_list_append (NULL, active_object);
+ }
+
+ if (options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ objects = g_list_concat (
+ objects,
+ gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_ALL));
+ }
+
+ for (iter = objects; iter; iter = g_list_next (iter))
+ {
+ GimpObject *object = iter->data;
+ GimpTransformResize clip = options->clip;
+ GeglRectangle orig_bounds;
+ GeglRectangle new_bounds;
+ gdouble ratio = 0.0;
+
+ if (GIMP_IS_DRAWABLE (object))
+ {
+ if (selection_empty)
+ {
+ GimpItem *item = GIMP_ITEM (object);
+
+ gimp_item_get_offset (item, &orig_bounds.x, &orig_bounds.y);
+
+ orig_bounds.width = gimp_item_get_width (item);
+ orig_bounds.height = gimp_item_get_height (item);
+
+ clip = gimp_item_get_clip (item, clip);
+ }
+ else
+ {
+ orig_bounds = selection_bounds;
+ }
+ }
+ else if (GIMP_IS_ITEM (object))
+ {
+ GimpItem *item = GIMP_ITEM (object);
+
+ gimp_item_bounds (item,
+ &orig_bounds.x, &orig_bounds.y,
+ &orig_bounds.width, &orig_bounds.height);
+
+ clip = gimp_item_get_clip (item, clip);
+ }
+ else
+ {
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (object), FALSE);
+
+ image = GIMP_IMAGE (object);
+
+ orig_bounds.x = 0;
+ orig_bounds.y = 0;
+ orig_bounds.width = gimp_image_get_width (image);
+ orig_bounds.height = gimp_image_get_height (image);
+ }
+
+ gimp_transform_resize_boundary (&transform, clip,
+
+ orig_bounds.x,
+ orig_bounds.y,
+ orig_bounds.x + orig_bounds.width,
+ orig_bounds.y + orig_bounds.height,
+
+ &new_bounds.x,
+ &new_bounds.y,
+ &new_bounds.width,
+ &new_bounds.height);
+
+ new_bounds.width -= new_bounds.x;
+ new_bounds.height -= new_bounds.y;
+
+ if (new_bounds.width > orig_bounds.width)
+ {
+ ratio = MAX (ratio,
+ (gdouble) new_bounds.width /
+ (gdouble) gimp_image_get_width (image));
+ }
+
+ if (new_bounds.height > orig_bounds.height)
+ {
+ ratio = MAX (ratio,
+ (gdouble) new_bounds.height /
+ (gdouble) gimp_image_get_height (image));
+ }
+
+ if (ratio > max_ratio)
+ {
+ max_ratio = ratio;
+ max_ratio_object = object;
+ }
+ }
+
+ g_list_free (objects);
+ }
+
+ if (max_ratio > MIN_CONFIRMATION_RATIO)
+ {
+ GtkWidget *dialog;
+ gint response;
+
+ dialog = gimp_message_dialog_new (_("Confirm Transformation"),
+ GIMP_ICON_DIALOG_WARNING,
+ GTK_WIDGET (shell),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ gimp_standard_help_func, NULL,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Transform"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ if (GIMP_IS_ITEM (max_ratio_object))
+ {
+ gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box,
+ _("Transformation creates "
+ "a very large item."));
+
+ gimp_message_box_set_text (
+ GIMP_MESSAGE_DIALOG (dialog)->box,
+ _("Applying the transformation will result "
+ "in an item that is over %g times larger "
+ "than the image."),
+ floor (max_ratio));
+ }
+ else
+ {
+ gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box,
+ _("Transformation creates "
+ "a very large image."));
+
+ gimp_message_box_set_text (
+ GIMP_MESSAGE_DIALOG (dialog)->box,
+ _("Applying the transformation will enlarge "
+ "the image by a factor of %g."),
+ floor (max_ratio));
+ }
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ if (response != GTK_RESPONSE_OK)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+
+gboolean
+gimp_transform_tool_bounds (GimpTransformTool *tr_tool,
+ GimpDisplay *display)
+{
+ GimpTransformOptions *options;
+ GimpDisplayShell *shell;
+ GimpImage *image;
+ gboolean non_empty = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_TOOL (tr_tool), FALSE);
+
+ options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ image = gimp_display_get_image (display);
+ shell = gimp_display_get_shell (display);
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ switch (options->type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ {
+ GimpDrawable *drawable;
+ gint offset_x;
+ gint offset_y;
+ gint x, y;
+ gint width, height;
+
+ drawable = gimp_image_get_active_drawable (image);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
+
+ non_empty = gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &x, &y, &width, &height);
+ tr_tool->x1 = x + offset_x;
+ tr_tool->y1 = y + offset_y;
+ tr_tool->x2 = x + width + offset_x;
+ tr_tool->y2 = y + height + offset_y;
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ {
+ gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ &tr_tool->x1, &tr_tool->y1,
+ &tr_tool->x2, &tr_tool->y2);
+ tr_tool->x2 += tr_tool->x1;
+ tr_tool->y2 += tr_tool->y1;
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_PATH:
+ {
+ GimpChannel *selection = gimp_image_get_mask (image);
+
+ /* if selection is not empty, use its bounds to perform the
+ * transformation of the path
+ */
+
+ if (! gimp_channel_is_empty (selection))
+ {
+ gimp_item_bounds (GIMP_ITEM (selection),
+ &tr_tool->x1, &tr_tool->y1,
+ &tr_tool->x2, &tr_tool->y2);
+ }
+ else
+ {
+ /* without selection, test the emptiness of the path bounds :
+ * if empty, use the canvas bounds
+ * else use the path bounds
+ */
+
+ if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_active_vectors (image)),
+ &tr_tool->x1, &tr_tool->y1,
+ &tr_tool->x2, &tr_tool->y2))
+ {
+ tr_tool->x1 = 0;
+ tr_tool->y1 = 0;
+ tr_tool->x2 = gimp_image_get_width (image);
+ tr_tool->y2 = gimp_image_get_height (image);
+ }
+ }
+
+ tr_tool->x2 += tr_tool->x1;
+ tr_tool->y2 += tr_tool->y1;
+ }
+
+ break;
+
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ if (! shell->show_all)
+ {
+ tr_tool->x1 = 0;
+ tr_tool->y1 = 0;
+ tr_tool->x2 = gimp_image_get_width (image);
+ tr_tool->y2 = gimp_image_get_height (image);
+ }
+ else
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_display_shell_get_bounding_box (shell);
+
+ tr_tool->x1 = bounding_box.x;
+ tr_tool->y1 = bounding_box.y;
+ tr_tool->x2 = bounding_box.x + bounding_box.width;
+ tr_tool->y2 = bounding_box.y + bounding_box.height;
+ }
+ break;
+ }
+
+ return non_empty;
+}
+
+void
+gimp_transform_tool_recalc_matrix (GimpTransformTool *tr_tool,
+ GimpDisplay *display)
+{
+ g_return_if_fail (GIMP_IS_TRANSFORM_TOOL (tr_tool));
+ g_return_if_fail (GIMP_IS_DISPLAY (display));
+
+ if (tr_tool->x1 == tr_tool->x2 && tr_tool->y1 == tr_tool->y2)
+ gimp_transform_tool_bounds (tr_tool, display);
+
+ if (GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->recalc_matrix)
+ GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->recalc_matrix (tr_tool);
+}
+
+GimpObject *
+gimp_transform_tool_get_active_object (GimpTransformTool *tr_tool,
+ GimpDisplay *display)
+{
+ GimpTransformOptions *options;
+ GimpImage *image;
+ GimpObject *object = NULL;
+
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_TOOL (tr_tool), NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+
+ options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ image = gimp_display_get_image (display);
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ if (tr_tool->object)
+ return tr_tool->object;
+
+ switch (options->type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ object = GIMP_OBJECT (gimp_image_get_active_drawable (image));
+ break;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ object = GIMP_OBJECT (gimp_image_get_mask (image));
+
+ if (gimp_channel_is_empty (GIMP_CHANNEL (object)))
+ object = NULL;
+ break;
+
+ case GIMP_TRANSFORM_TYPE_PATH:
+ object = GIMP_OBJECT (gimp_image_get_active_vectors (image));
+ break;
+
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ object = GIMP_OBJECT (image);
+ break;
+ }
+
+ return object;
+}
+
+GimpObject *
+gimp_transform_tool_check_active_object (GimpTransformTool *tr_tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpTransformOptions *options;
+ GimpObject *object;
+ const gchar *null_message = NULL;
+ const gchar *locked_message = NULL;
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_TOOL (tr_tool), NULL);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ object = gimp_transform_tool_get_active_object (tr_tool, display);
+
+ switch (options->type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ null_message = _("There is no layer to transform.");
+
+ if (object)
+ {
+ GimpItem *item = GIMP_ITEM (object);
+
+ if (gimp_item_is_content_locked (item))
+ locked_message = _("The active layer's pixels are locked.");
+ else if (gimp_item_is_position_locked (item))
+ locked_message = _("The active layer's position and size are locked.");
+
+ if (! gimp_item_is_visible (item) &&
+ ! config->edit_non_visible &&
+ object != tr_tool->object) /* see bug #759194 */
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer is not visible."));
+ return NULL;
+ }
+
+ if (! gimp_transform_tool_bounds (tr_tool, display))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The selection does not intersect with the layer."));
+ return NULL;
+ }
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ null_message = _("There is no selection to transform.");
+
+ if (object)
+ {
+ GimpItem *item = GIMP_ITEM (object);
+
+ /* cannot happen, so don't translate these messages */
+ if (gimp_item_is_content_locked (item))
+ locked_message = "The selection's pixels are locked.";
+ else if (gimp_item_is_position_locked (item))
+ locked_message = "The selection's position and size are locked.";
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_PATH:
+ null_message = _("There is no path to transform.");
+
+ if (object)
+ {
+ GimpItem *item = GIMP_ITEM (object);
+
+ if (gimp_item_is_content_locked (item))
+ locked_message = _("The active path's strokes are locked.");
+ else if (gimp_item_is_position_locked (item))
+ locked_message = _("The active path's position is locked.");
+ else if (! gimp_vectors_get_n_strokes (GIMP_VECTORS (item)))
+ locked_message = _("The active path has no strokes.");
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ /* cannot happen, so don't translate this message */
+ null_message = "There is no image to transform.";
+ break;
+ }
+
+ if (! object)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, null_message);
+ if (error)
+ gimp_widget_blink (options->type_box);
+ return NULL;
+ }
+
+ if (locked_message)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, locked_message);
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (object));
+ return NULL;
+ }
+
+ return object;
+}
+
+gboolean
+gimp_transform_tool_transform (GimpTransformTool *tr_tool,
+ GimpDisplay *display)
+{
+ GimpTool *tool;
+ GimpTransformOptions *options;
+ GimpContext *context;
+ GimpImage *image;
+ GimpObject *active_object;
+ GeglBuffer *orig_buffer = NULL;
+ gint orig_offset_x = 0;
+ gint orig_offset_y = 0;
+ GeglBuffer *new_buffer;
+ gint new_offset_x;
+ gint new_offset_y;
+ GimpColorProfile *buffer_profile;
+ gchar *undo_desc = NULL;
+ gboolean new_layer = FALSE;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_TOOL (tr_tool), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ tool = GIMP_TOOL (tr_tool);
+ options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tool);
+ context = GIMP_CONTEXT (options);
+ image = gimp_display_get_image (display);
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ active_object = gimp_transform_tool_check_active_object (tr_tool, display,
+ &error);
+
+ if (! active_object)
+ {
+ gimp_tool_message_literal (tool, display, error->message);
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ if (! tr_tool->transform_valid)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The current transform is invalid"));
+ return FALSE;
+ }
+
+ if (GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->recalc_matrix &&
+ gimp_matrix3_is_identity (&tr_tool->transform))
+ {
+ /* No need to commit an identity transformation! */
+ return TRUE;
+ }
+
+ if (! gimp_transform_tool_confirm (tr_tool, display))
+ return FALSE;
+
+ gimp_set_busy (display->gimp);
+
+ /* We're going to dirty this image, but we want to keep the tool around */
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ undo_desc = GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->get_undo_desc (tr_tool);
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM, undo_desc);
+ g_free (undo_desc);
+
+ switch (options->type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ if (! gimp_viewable_get_children (GIMP_VIEWABLE (active_object)) &&
+ ! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ orig_buffer = gimp_drawable_transform_cut (
+ GIMP_DRAWABLE (active_object),
+ context,
+ &orig_offset_x,
+ &orig_offset_y,
+ &new_layer);
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ case GIMP_TRANSFORM_TYPE_PATH:
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ break;
+ }
+
+ /* Send the request for the transformation to the tool...
+ */
+ new_buffer = GIMP_TRANSFORM_TOOL_GET_CLASS (tr_tool)->transform (
+ tr_tool,
+ active_object,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ &buffer_profile,
+ &new_offset_x,
+ &new_offset_y);
+
+ if (orig_buffer)
+ g_object_unref (orig_buffer);
+
+ switch (options->type)
+ {
+ case GIMP_TRANSFORM_TYPE_LAYER:
+ if (new_buffer)
+ {
+ /* paste the new transformed image to the image...also implement
+ * undo...
+ */
+ gimp_drawable_transform_paste (GIMP_DRAWABLE (active_object),
+ new_buffer, buffer_profile,
+ new_offset_x, new_offset_y,
+ new_layer);
+ g_object_unref (new_buffer);
+ }
+ break;
+
+ case GIMP_TRANSFORM_TYPE_SELECTION:
+ case GIMP_TRANSFORM_TYPE_PATH:
+ case GIMP_TRANSFORM_TYPE_IMAGE:
+ /* Nothing to be done */
+ break;
+ }
+
+ gimp_image_undo_group_end (image);
+
+ /* We're done dirtying the image, and would like to be restarted if
+ * the image gets dirty while the tool exists
+ */
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_unset_busy (display->gimp);
+
+ gimp_image_flush (image);
+
+ return TRUE;
+}
+
+void
+gimp_transform_tool_set_type (GimpTransformTool *tr_tool,
+ GimpTransformType type)
+{
+ GimpTransformOptions *options;
+
+ g_return_if_fail (GIMP_IS_TRANSFORM_TOOL (tr_tool));
+
+ options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ if (! tr_tool->restore_type)
+ tr_tool->saved_type = options->type;
+
+ tr_tool->restore_type = FALSE;
+
+ g_object_set (options,
+ "type", type,
+ NULL);
+
+ tr_tool->restore_type = TRUE;
+}
diff --git a/app/tools/gimptransformtool.h b/app/tools/gimptransformtool.h
new file mode 100644
index 0000000..afe17e3
--- /dev/null
+++ b/app/tools/gimptransformtool.h
@@ -0,0 +1,104 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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_TRANSFORM_TOOL_H__
+#define __GIMP_TRANSFORM_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+/* This is not the number of items in the enum above, but the max size
+ * of the enums at the top of each transformation tool, stored in
+ * trans_info and related
+ */
+#define TRANS_INFO_SIZE 17
+
+typedef gdouble TransInfo[TRANS_INFO_SIZE];
+
+
+#define GIMP_TYPE_TRANSFORM_TOOL (gimp_transform_tool_get_type ())
+#define GIMP_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSFORM_TOOL, GimpTransformTool))
+#define GIMP_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSFORM_TOOL, GimpTransformToolClass))
+#define GIMP_IS_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSFORM_TOOL))
+#define GIMP_IS_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSFORM_TOOL))
+#define GIMP_TRANSFORM_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSFORM_TOOL, GimpTransformToolClass))
+
+#define GIMP_TRANSFORM_TOOL_GET_OPTIONS(t) (GIMP_TRANSFORM_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpTransformToolClass GimpTransformToolClass;
+
+struct _GimpTransformTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpObject *object;
+
+ gint x1, y1; /* upper left hand coordinate */
+ gint x2, y2; /* lower right hand coords */
+
+ GimpMatrix3 transform; /* transformation matrix */
+ gboolean transform_valid; /* whether the matrix is valid */
+
+ gboolean restore_type;
+ GimpTransformType saved_type;
+};
+
+struct _GimpTransformToolClass
+{
+ GimpDrawToolClass parent_class;
+
+ /* virtual functions */
+ void (* recalc_matrix) (GimpTransformTool *tr_tool);
+ gchar * (* get_undo_desc) (GimpTransformTool *tr_tool);
+ GimpTransformDirection (* get_direction) (GimpTransformTool *tr_tool);
+ GeglBuffer * (* transform) (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+ const gchar *undo_desc;
+ const gchar *progress_text;
+};
+
+
+GType gimp_transform_tool_get_type (void) G_GNUC_CONST;
+
+GimpObject * gimp_transform_tool_get_active_object (GimpTransformTool *tr_tool,
+ GimpDisplay *display);
+GimpObject * gimp_transform_tool_check_active_object (GimpTransformTool *tr_tool,
+ GimpDisplay *display,
+ GError **error);
+
+gboolean gimp_transform_tool_bounds (GimpTransformTool *tr_tool,
+ GimpDisplay *display);
+void gimp_transform_tool_recalc_matrix (GimpTransformTool *tr_tool,
+ GimpDisplay *display);
+
+gboolean gimp_transform_tool_transform (GimpTransformTool *tr_tool,
+ GimpDisplay *display);
+
+void gimp_transform_tool_set_type (GimpTransformTool *tr_tool,
+ GimpTransformType type);
+
+
+#endif /* __GIMP_TRANSFORM_TOOL_H__ */
diff --git a/app/tools/gimpunifiedtransformtool.c b/app/tools/gimpunifiedtransformtool.c
new file mode 100644
index 0000000..85682bb
--- /dev/null
+++ b/app/tools/gimpunifiedtransformtool.c
@@ -0,0 +1,355 @@
+/* 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 "tools-types.h"
+
+#include "widgets/gimphelp-ids.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimpdisplayshell-transform.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptooltransformgrid.h"
+
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+#include "gimpunifiedtransformtool.h"
+
+#include "gimp-intl.h"
+
+
+/* index into trans_info array */
+enum
+{
+ X0,
+ Y0,
+ X1,
+ Y1,
+ X2,
+ Y2,
+ X3,
+ Y3,
+ PIVOT_X,
+ PIVOT_Y,
+};
+
+
+/* local function prototypes */
+
+static void gimp_unified_transform_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform);
+static void gimp_unified_transform_tool_apply_info (GimpTransformGridTool *tg_tool,
+ const TransInfo info);
+static void gimp_unified_transform_tool_prepare (GimpTransformGridTool *tg_tool);
+static void gimp_unified_transform_tool_readjust (GimpTransformGridTool *tg_tool);
+static GimpToolWidget * gimp_unified_transform_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_unified_transform_tool_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_unified_transform_tool_widget_changed (GimpTransformGridTool *tg_tool);
+
+static void gimp_unified_transform_tool_info_to_points (GimpGenericTransformTool *generic);
+
+
+G_DEFINE_TYPE (GimpUnifiedTransformTool, gimp_unified_transform_tool,
+ GIMP_TYPE_GENERIC_TRANSFORM_TOOL)
+
+#define parent_class gimp_unified_transform_tool_parent_class
+
+
+void
+gimp_unified_transform_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_UNIFIED_TRANSFORM_TOOL,
+ GIMP_TYPE_TRANSFORM_GRID_OPTIONS,
+ gimp_transform_grid_options_gui,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND,
+ "gimp-unified-transform-tool",
+ _("Unified Transform"),
+ _("Unified Transform Tool: "
+ "Transform the layer, selection or path"),
+ N_("_Unified Transform"), "<shift>T",
+ NULL, GIMP_HELP_TOOL_UNIFIED_TRANSFORM,
+ GIMP_ICON_TOOL_UNIFIED_TRANSFORM,
+ data);
+}
+
+static void
+gimp_unified_transform_tool_class_init (GimpUnifiedTransformToolClass *klass)
+{
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+ GimpTransformGridToolClass *tg_class = GIMP_TRANSFORM_GRID_TOOL_CLASS (klass);
+ GimpGenericTransformToolClass *generic_class = GIMP_GENERIC_TRANSFORM_TOOL_CLASS (klass);
+
+ tg_class->matrix_to_info = gimp_unified_transform_tool_matrix_to_info;
+ tg_class->apply_info = gimp_unified_transform_tool_apply_info;
+ tg_class->prepare = gimp_unified_transform_tool_prepare;
+ tg_class->readjust = gimp_unified_transform_tool_readjust;
+ tg_class->get_widget = gimp_unified_transform_tool_get_widget;
+ tg_class->update_widget = gimp_unified_transform_tool_update_widget;
+ tg_class->widget_changed = gimp_unified_transform_tool_widget_changed;
+
+ generic_class->info_to_points = gimp_unified_transform_tool_info_to_points;
+
+ tr_class->undo_desc = C_("undo-type", "Unified Transform");
+ tr_class->progress_text = _("Unified transform");
+}
+
+static void
+gimp_unified_transform_tool_init (GimpUnifiedTransformTool *unified_tool)
+{
+}
+
+static void
+gimp_unified_transform_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+
+ if (! tg_options->fixedpivot)
+ {
+ GimpMatrix3 transfer;
+
+ gimp_transform_grid_tool_info_to_matrix (tg_tool, &transfer);
+ gimp_matrix3_invert (&transfer);
+ gimp_matrix3_mult (transform, &transfer);
+
+ gimp_matrix3_transform_point (&transfer,
+ tg_tool->trans_info[PIVOT_X],
+ tg_tool->trans_info[PIVOT_Y],
+ &tg_tool->trans_info[PIVOT_X],
+ &tg_tool->trans_info[PIVOT_Y]);
+ }
+
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2,
+ tr_tool->y1,
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1,
+ tr_tool->y2,
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2,
+ tr_tool->y2,
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+}
+
+static void
+gimp_unified_transform_tool_apply_info (GimpTransformGridTool *tg_tool,
+ const TransInfo info)
+{
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+
+ tg_tool->trans_info[X0] = info[X0];
+ tg_tool->trans_info[Y0] = info[Y0];
+
+ tg_tool->trans_info[X1] = info[X1];
+ tg_tool->trans_info[Y1] = info[Y1];
+
+ tg_tool->trans_info[X2] = info[X2];
+ tg_tool->trans_info[Y2] = info[Y2];
+
+ tg_tool->trans_info[X3] = info[X3];
+ tg_tool->trans_info[Y3] = info[Y3];
+
+ if (! tg_options->fixedpivot)
+ {
+ tg_tool->trans_info[PIVOT_X] = info[PIVOT_X];
+ tg_tool->trans_info[PIVOT_Y] = info[PIVOT_Y];
+ }
+}
+
+static void
+gimp_unified_transform_tool_prepare (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->prepare (tg_tool);
+
+ tg_tool->trans_info[PIVOT_X] = (gdouble) (tr_tool->x1 + tr_tool->x2) / 2.0;
+ tg_tool->trans_info[PIVOT_Y] = (gdouble) (tr_tool->y1 + tr_tool->y2) / 2.0;
+
+ tg_tool->trans_info[X0] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y0] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X1] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y1] = (gdouble) tr_tool->y1;
+ tg_tool->trans_info[X2] = (gdouble) tr_tool->x1;
+ tg_tool->trans_info[Y2] = (gdouble) tr_tool->y2;
+ tg_tool->trans_info[X3] = (gdouble) tr_tool->x2;
+ tg_tool->trans_info[Y3] = (gdouble) tr_tool->y2;
+}
+
+static void
+gimp_unified_transform_tool_readjust (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ gdouble x;
+ gdouble y;
+ gdouble r;
+
+ x = shell->disp_width / 2.0;
+ y = shell->disp_height / 2.0;
+ r = MAX (MIN (x, y) / G_SQRT2 -
+ GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE / 2.0,
+ GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE / 2.0);
+
+ if (! tg_options->fixedpivot)
+ {
+ gimp_display_shell_untransform_xy_f (shell,
+ x, y,
+ &tg_tool->trans_info[PIVOT_X],
+ &tg_tool->trans_info[PIVOT_Y]);
+ }
+
+ gimp_display_shell_untransform_xy_f (shell,
+ x - r, y - r,
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_display_shell_untransform_xy_f (shell,
+ x + r, y - r,
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_display_shell_untransform_xy_f (shell,
+ x - r, y + r,
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_display_shell_untransform_xy_f (shell,
+ x + r, y + r,
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+}
+
+static GimpToolWidget *
+gimp_unified_transform_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpToolWidget *widget;
+
+ widget = gimp_tool_transform_grid_new (shell,
+ &tr_tool->transform,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2);
+
+ g_object_set (widget,
+ "pivot-x", (tr_tool->x1 + tr_tool->x2) / 2.0,
+ "pivot-y", (tr_tool->y1 + tr_tool->y2) / 2.0,
+ "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+ "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+ "use-corner-handles", TRUE,
+ "use-perspective-handles", TRUE,
+ "use-side-handles", TRUE,
+ "use-shear-handles", TRUE,
+ "use-pivot-handle", TRUE,
+ NULL);
+
+ return widget;
+}
+
+static void
+gimp_unified_transform_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->update_widget (tg_tool);
+
+ g_object_set (tg_tool->widget,
+ "x1", (gdouble) tr_tool->x1,
+ "y1", (gdouble) tr_tool->y1,
+ "x2", (gdouble) tr_tool->x2,
+ "y2", (gdouble) tr_tool->y2,
+ "pivot-x", tg_tool->trans_info[PIVOT_X],
+ "pivot-y", tg_tool->trans_info[PIVOT_Y],
+ NULL);
+}
+
+static void
+gimp_unified_transform_tool_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpMatrix3 *transform;
+
+ g_object_get (tg_tool->widget,
+ "transform", &transform,
+ "pivot-x", &tg_tool->trans_info[PIVOT_X],
+ "pivot-y", &tg_tool->trans_info[PIVOT_Y],
+ NULL);
+
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1, tr_tool->y1,
+ &tg_tool->trans_info[X0],
+ &tg_tool->trans_info[Y0]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2, tr_tool->y1,
+ &tg_tool->trans_info[X1],
+ &tg_tool->trans_info[Y1]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x1, tr_tool->y2,
+ &tg_tool->trans_info[X2],
+ &tg_tool->trans_info[Y2]);
+ gimp_matrix3_transform_point (transform,
+ tr_tool->x2, tr_tool->y2,
+ &tg_tool->trans_info[X3],
+ &tg_tool->trans_info[Y3]);
+
+ g_free (transform);
+
+ GIMP_TRANSFORM_GRID_TOOL_CLASS (parent_class)->widget_changed (tg_tool);
+}
+
+static void
+gimp_unified_transform_tool_info_to_points (GimpGenericTransformTool *generic)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (generic);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (generic);
+
+ generic->input_points[0] = (GimpVector2) {tr_tool->x1, tr_tool->y1};
+ generic->input_points[1] = (GimpVector2) {tr_tool->x2, tr_tool->y1};
+ generic->input_points[2] = (GimpVector2) {tr_tool->x1, tr_tool->y2};
+ generic->input_points[3] = (GimpVector2) {tr_tool->x2, tr_tool->y2};
+
+ generic->output_points[0] = (GimpVector2) {tg_tool->trans_info[X0],
+ tg_tool->trans_info[Y0]};
+ generic->output_points[1] = (GimpVector2) {tg_tool->trans_info[X1],
+ tg_tool->trans_info[Y1]};
+ generic->output_points[2] = (GimpVector2) {tg_tool->trans_info[X2],
+ tg_tool->trans_info[Y2]};
+ generic->output_points[3] = (GimpVector2) {tg_tool->trans_info[X3],
+ tg_tool->trans_info[Y3]};
+}
diff --git a/app/tools/gimpunifiedtransformtool.h b/app/tools/gimpunifiedtransformtool.h
new file mode 100644
index 0000000..923f933
--- /dev/null
+++ b/app/tools/gimpunifiedtransformtool.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 __GIMP_UNIFIED_TRANSFORM_TOOL_H__
+#define __GIMP_UNIFIED_TRANSFORM_TOOL_H__
+
+
+#include "gimpgenerictransformtool.h"
+
+
+#define GIMP_TYPE_UNIFIED_TRANSFORM_TOOL (gimp_unified_transform_tool_get_type ())
+#define GIMP_UNIFIED_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNIFIED_TRANSFORM_TOOL, GimpUnifiedTransformTool))
+#define GIMP_UNIFIED_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNIFIED_TRANSFORM_TOOL, GimpUnifiedTransformToolClass))
+#define GIMP_IS_UNIFIED_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNIFIED_TRANSFORM_TOOL))
+#define GIMP_IS_UNIFIED_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNIFIED_TRANSFORM_TOOL))
+#define GIMP_UNIFIED_TRANSFORM_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNIFIED_TRANSFORM_TOOL, GimpUnifiedTransformToolClass))
+
+
+typedef struct _GimpUnifiedTransformTool GimpUnifiedTransformTool;
+typedef struct _GimpUnifiedTransformToolClass GimpUnifiedTransformToolClass;
+
+struct _GimpUnifiedTransformTool
+{
+ GimpGenericTransformTool parent_instance;
+};
+
+struct _GimpUnifiedTransformToolClass
+{
+ GimpGenericTransformToolClass parent_class;
+};
+
+
+void gimp_unified_transform_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_unified_transform_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_UNIFIED_TRANSFORM_TOOL_H__ */
diff --git a/app/tools/gimpvectoroptions.c b/app/tools/gimpvectoroptions.c
new file mode 100644
index 0000000..c6abb2c
--- /dev/null
+++ b/app/tools/gimpvectoroptions.c
@@ -0,0 +1,219 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectoroptions.c
+ * Copyright (C) 1999 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpvectoroptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_VECTORS_EDIT_MODE,
+ PROP_VECTORS_POLYGONAL
+};
+
+
+static void gimp_vector_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_vector_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpVectorOptions, gimp_vector_options, GIMP_TYPE_TOOL_OPTIONS)
+
+
+static void
+gimp_vector_options_class_init (GimpVectorOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_vector_options_set_property;
+ object_class->get_property = gimp_vector_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_VECTORS_EDIT_MODE,
+ "vectors-edit-mode",
+ _("Edit Mode"),
+ NULL,
+ GIMP_TYPE_VECTOR_MODE,
+ GIMP_VECTOR_MODE_DESIGN,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_VECTORS_POLYGONAL,
+ "vectors-polygonal",
+ _("Polygonal"),
+ _("Restrict editing to polygons"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_vector_options_init (GimpVectorOptions *options)
+{
+}
+
+static void
+gimp_vector_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpVectorOptions *options = GIMP_VECTOR_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_VECTORS_EDIT_MODE:
+ options->edit_mode = g_value_get_enum (value);
+ break;
+ case PROP_VECTORS_POLYGONAL:
+ options->polygonal = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gimp_vector_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpVectorOptions *options = GIMP_VECTOR_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_VECTORS_EDIT_MODE:
+ g_value_set_enum (value, options->edit_mode);
+ break;
+ case PROP_VECTORS_POLYGONAL:
+ g_value_set_boolean (value, options->polygonal);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+button_append_modifier (GtkWidget *button,
+ GdkModifierType modifiers)
+{
+ gchar *str = g_strdup_printf ("%s (%s)",
+ gtk_button_get_label (GTK_BUTTON (button)),
+ gimp_get_mod_string (modifiers));
+
+ gtk_button_set_label (GTK_BUTTON (button), str);
+ g_free (str);
+}
+
+GtkWidget *
+gimp_vector_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GimpVectorOptions *options = GIMP_VECTOR_OPTIONS (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *button;
+ gchar *str;
+
+ /* tool toggle */
+ frame = gimp_prop_enum_radio_frame_new (config, "vectors-edit-mode", NULL,
+ 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ button = g_object_get_data (G_OBJECT (frame), "radio-button");
+
+ if (GTK_IS_RADIO_BUTTON (button))
+ {
+ GSList *list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+
+ /* GIMP_VECTOR_MODE_MOVE */
+ button_append_modifier (list->data, GDK_MOD1_MASK);
+
+ if (list->next) /* GIMP_VECTOR_MODE_EDIT */
+ button_append_modifier (list->next->data,
+ gimp_get_toggle_behavior_mask ());
+ }
+
+ button = gimp_prop_check_button_new (config, "vectors-polygonal", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ str = g_strdup_printf (_("Path to Selection\n"
+ "%s Add\n"
+ "%s Subtract\n"
+ "%s Intersect"),
+ gimp_get_mod_string (gimp_get_extend_selection_mask ()),
+ gimp_get_mod_string (gimp_get_modify_selection_mask ()),
+ gimp_get_mod_string (gimp_get_extend_selection_mask () |
+ gimp_get_modify_selection_mask ()));
+
+ button = gimp_button_new ();
+ /* Create a selection from the current path */
+ gtk_button_set_label (GTK_BUTTON (button), _("Selection from Path"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_set_sensitive (button, FALSE);
+ gimp_help_set_help_data (button, str, GIMP_HELP_PATH_SELECTION_REPLACE);
+ gtk_widget_show (button);
+
+ g_free (str);
+
+ options->to_selection_button = button;
+
+ button = gtk_button_new_with_label (_("Fill Path"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_set_sensitive (button, FALSE);
+ gimp_help_set_help_data (button, NULL, GIMP_HELP_PATH_FILL);
+ gtk_widget_show (button);
+
+ options->fill_button = button;
+
+ button = gtk_button_new_with_label (_("Stroke Path"));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_set_sensitive (button, FALSE);
+ gimp_help_set_help_data (button, NULL, GIMP_HELP_PATH_STROKE);
+ gtk_widget_show (button);
+
+ options->stroke_button = button;
+
+ return vbox;
+}
diff --git a/app/tools/gimpvectoroptions.h b/app/tools/gimpvectoroptions.h
new file mode 100644
index 0000000..e13e8f3
--- /dev/null
+++ b/app/tools/gimpvectoroptions.h
@@ -0,0 +1,55 @@
+/* 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_VECTOR_OPTIONS_H__
+#define __GIMP_VECTOR_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_VECTOR_OPTIONS (gimp_vector_options_get_type ())
+#define GIMP_VECTOR_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTOR_OPTIONS, GimpVectorOptions))
+#define GIMP_VECTOR_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTOR_OPTIONS, GimpVectorOptionsClass))
+#define GIMP_IS_VECTOR_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTOR_OPTIONS))
+#define GIMP_IS_VECTOR_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTOR_OPTIONS))
+#define GIMP_VECTOR_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTOR_OPTIONS, GimpVectorOptionsClass))
+
+
+typedef struct _GimpVectorOptions GimpVectorOptions;
+typedef struct _GimpToolOptionsClass GimpVectorOptionsClass;
+
+struct _GimpVectorOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpVectorMode edit_mode;
+ gboolean polygonal;
+
+ /* options gui */
+ GtkWidget *to_selection_button;
+ GtkWidget *fill_button;
+ GtkWidget *stroke_button;
+};
+
+
+GType gimp_vector_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_vector_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_VECTOR_OPTIONS_H__ */
diff --git a/app/tools/gimpvectortool.c b/app/tools/gimpvectortool.c
new file mode 100644
index 0000000..3040a2a
--- /dev/null
+++ b/app/tools/gimpvectortool.c
@@ -0,0 +1,852 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpdialogconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-pick-item.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpimage-undo-push.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimpundostack.h"
+
+#include "paint/gimppaintoptions.h" /* GIMP_PAINT_OPTIONS_CONTEXT_MASK */
+
+#include "vectors/gimpvectors.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolpath.h"
+
+#include "gimptoolcontrol.h"
+#include "gimpvectoroptions.h"
+#include "gimpvectortool.h"
+
+#include "dialogs/fill-dialog.h"
+#include "dialogs/stroke-dialog.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 ()
+
+
+/* local function prototypes */
+
+static void gimp_vector_tool_dispose (GObject *object);
+
+static void gimp_vector_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_vector_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_vector_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_vector_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_vector_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_vector_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_vector_tool_start (GimpVectorTool *vector_tool,
+ GimpDisplay *display);
+static void gimp_vector_tool_halt (GimpVectorTool *vector_tool);
+
+static void gimp_vector_tool_path_changed (GimpToolWidget *path,
+ GimpVectorTool *vector_tool);
+static void gimp_vector_tool_path_begin_change
+ (GimpToolWidget *path,
+ const gchar *desc,
+ GimpVectorTool *vector_tool);
+static void gimp_vector_tool_path_end_change (GimpToolWidget *path,
+ gboolean success,
+ GimpVectorTool *vector_tool);
+static void gimp_vector_tool_path_activate (GimpToolWidget *path,
+ GdkModifierType state,
+ GimpVectorTool *vector_tool);
+
+static void gimp_vector_tool_vectors_changed (GimpImage *image,
+ GimpVectorTool *vector_tool);
+static void gimp_vector_tool_vectors_removed (GimpVectors *vectors,
+ GimpVectorTool *vector_tool);
+
+static void gimp_vector_tool_to_selection (GimpVectorTool *vector_tool);
+static void gimp_vector_tool_to_selection_extended
+ (GimpVectorTool *vector_tool,
+ GdkModifierType state);
+
+static void gimp_vector_tool_fill_vectors (GimpVectorTool *vector_tool,
+ GtkWidget *button);
+static void gimp_vector_tool_fill_callback (GtkWidget *dialog,
+ GimpItem *item,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GimpFillOptions *options,
+ gpointer data);
+
+static void gimp_vector_tool_stroke_vectors (GimpVectorTool *vector_tool,
+ GtkWidget *button);
+static void gimp_vector_tool_stroke_callback (GtkWidget *dialog,
+ GimpItem *item,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GimpStrokeOptions *options,
+ gpointer data);
+
+
+G_DEFINE_TYPE (GimpVectorTool, gimp_vector_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_vector_tool_parent_class
+
+
+void
+gimp_vector_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_VECTOR_TOOL,
+ GIMP_TYPE_VECTOR_OPTIONS,
+ gimp_vector_options_gui,
+ GIMP_PAINT_OPTIONS_CONTEXT_MASK |
+ GIMP_CONTEXT_PROP_MASK_PATTERN |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT, /* for stroking */
+ "gimp-vector-tool",
+ _("Paths"),
+ _("Paths Tool: Create and edit paths"),
+ N_("Pat_hs"), "b",
+ NULL, GIMP_HELP_TOOL_PATH,
+ GIMP_ICON_TOOL_PATH,
+ data);
+}
+
+static void
+gimp_vector_tool_class_init (GimpVectorToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+
+ object_class->dispose = gimp_vector_tool_dispose;
+
+ tool_class->control = gimp_vector_tool_control;
+ tool_class->button_press = gimp_vector_tool_button_press;
+ tool_class->button_release = gimp_vector_tool_button_release;
+ tool_class->motion = gimp_vector_tool_motion;
+ tool_class->modifier_key = gimp_vector_tool_modifier_key;
+ tool_class->cursor_update = gimp_vector_tool_cursor_update;
+}
+
+static void
+gimp_vector_tool_init (GimpVectorTool *vector_tool)
+{
+ GimpTool *tool = GIMP_TOOL (vector_tool);
+
+ gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PATHS);
+
+ vector_tool->saved_mode = GIMP_VECTOR_MODE_DESIGN;
+}
+
+static void
+gimp_vector_tool_dispose (GObject *object)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (object);
+
+ gimp_vector_tool_set_vectors (vector_tool, NULL);
+ g_clear_object (&vector_tool->widget);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_vector_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_vector_tool_halt (vector_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_vector_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (tool);
+
+ if (tool->display && display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ if (! tool->display)
+ {
+ gimp_vector_tool_start (vector_tool, display);
+
+ gimp_tool_widget_hover (vector_tool->widget, coords, state, TRUE);
+ }
+
+ if (gimp_tool_widget_button_press (vector_tool->widget, coords, time, state,
+ press_type))
+ {
+ vector_tool->grab_widget = vector_tool->widget;
+ }
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_vector_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (vector_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (vector_tool->grab_widget,
+ coords, time, state, release_type);
+ vector_tool->grab_widget = NULL;
+ }
+}
+
+static void
+gimp_vector_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (tool);
+
+ if (vector_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (vector_tool->grab_widget, coords, time, state);
+ }
+}
+
+static void
+gimp_vector_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (tool);
+ GimpVectorOptions *options = GIMP_VECTOR_TOOL_GET_OPTIONS (tool);
+
+ if (key == TOGGLE_MASK)
+ return;
+
+ if (key == INSDEL_MASK || key == MOVE_MASK)
+ {
+ GimpVectorMode button_mode = options->edit_mode;
+
+ if (press)
+ {
+ if (key == (state & (INSDEL_MASK | MOVE_MASK)))
+ {
+ /* first modifier pressed */
+
+ vector_tool->saved_mode = options->edit_mode;
+ }
+ }
+ else
+ {
+ if (! (state & (INSDEL_MASK | MOVE_MASK)))
+ {
+ /* last modifier released */
+
+ button_mode = vector_tool->saved_mode;
+ }
+ }
+
+ if (state & MOVE_MASK)
+ {
+ button_mode = GIMP_VECTOR_MODE_MOVE;
+ }
+ else if (state & INSDEL_MASK)
+ {
+ button_mode = GIMP_VECTOR_MODE_EDIT;
+ }
+
+ if (button_mode != options->edit_mode)
+ {
+ g_object_set (options, "vectors-edit-mode", button_mode, NULL);
+ }
+ }
+}
+
+static void
+gimp_vector_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpVectorTool *vector_tool = GIMP_VECTOR_TOOL (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+
+ if (display != tool->display || ! vector_tool->widget)
+ {
+ GimpToolCursorType tool_cursor = GIMP_TOOL_CURSOR_PATHS;
+
+ if (gimp_image_pick_vectors (gimp_display_get_image (display),
+ coords->x, coords->y,
+ FUNSCALEX (shell,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE / 2),
+ FUNSCALEY (shell,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE / 2)))
+ {
+ tool_cursor = GIMP_TOOL_CURSOR_HAND;
+ }
+
+ gimp_tool_control_set_tool_cursor (tool->control, tool_cursor);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_vector_tool_start (GimpVectorTool *vector_tool,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (vector_tool);
+ GimpVectorOptions *options = GIMP_VECTOR_TOOL_GET_OPTIONS (tool);
+ GimpDisplayShell *shell = gimp_display_get_shell (display);
+ GimpToolWidget *widget;
+
+ tool->display = display;
+
+ vector_tool->widget = widget = gimp_tool_path_new (shell);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), widget);
+
+ g_object_bind_property (G_OBJECT (options), "vectors-edit-mode",
+ G_OBJECT (widget), "edit-mode",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (G_OBJECT (options), "vectors-polygonal",
+ G_OBJECT (widget), "polygonal",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ gimp_tool_path_set_vectors (GIMP_TOOL_PATH (widget),
+ vector_tool->vectors);
+
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (gimp_vector_tool_path_changed),
+ vector_tool);
+ g_signal_connect (widget, "begin-change",
+ G_CALLBACK (gimp_vector_tool_path_begin_change),
+ vector_tool);
+ g_signal_connect (widget, "end-change",
+ G_CALLBACK (gimp_vector_tool_path_end_change),
+ vector_tool);
+ g_signal_connect (widget, "activate",
+ G_CALLBACK (gimp_vector_tool_path_activate),
+ vector_tool);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+}
+
+static void
+gimp_vector_tool_halt (GimpVectorTool *vector_tool)
+{
+ GimpTool *tool = GIMP_TOOL (vector_tool);
+
+ if (tool->display)
+ gimp_tool_pop_status (tool, tool->display);
+
+ gimp_vector_tool_set_vectors (vector_tool, NULL);
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
+ g_clear_object (&vector_tool->widget);
+
+ tool->display = NULL;
+}
+
+static void
+gimp_vector_tool_path_changed (GimpToolWidget *path,
+ GimpVectorTool *vector_tool)
+{
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (path);
+ GimpImage *image = gimp_display_get_image (shell->display);
+ GimpVectors *vectors;
+
+ g_object_get (path,
+ "vectors", &vectors,
+ NULL);
+
+ if (vectors != vector_tool->vectors)
+ {
+ if (vectors && ! gimp_item_is_attached (GIMP_ITEM (vectors)))
+ {
+ gimp_image_add_vectors (image, vectors,
+ GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
+ gimp_image_flush (image);
+
+ gimp_vector_tool_set_vectors (vector_tool, vectors);
+ }
+ else
+ {
+ gimp_vector_tool_set_vectors (vector_tool, vectors);
+
+ if (vectors)
+ gimp_image_set_active_vectors (image, vectors);
+ }
+ }
+
+ if (vectors)
+ g_object_unref (vectors);
+}
+
+static void
+gimp_vector_tool_path_begin_change (GimpToolWidget *path,
+ const gchar *desc,
+ GimpVectorTool *vector_tool)
+{
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (path);
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ gimp_image_undo_push_vectors_mod (image, desc, vector_tool->vectors);
+}
+
+static void
+gimp_vector_tool_path_end_change (GimpToolWidget *path,
+ gboolean success,
+ GimpVectorTool *vector_tool)
+{
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (path);
+ GimpImage *image = gimp_display_get_image (shell->display);
+
+ if (! success)
+ {
+ GimpUndo *undo;
+ GimpUndoAccumulator accum = { 0, };
+
+ undo = gimp_undo_stack_pop_undo (gimp_image_get_undo_stack (image),
+ GIMP_UNDO_MODE_UNDO, &accum);
+
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_EXPIRED, undo);
+
+ gimp_undo_free (undo, GIMP_UNDO_MODE_UNDO);
+ g_object_unref (undo);
+ }
+
+ gimp_image_flush (image);
+}
+
+static void
+gimp_vector_tool_path_activate (GimpToolWidget *path,
+ GdkModifierType state,
+ GimpVectorTool *vector_tool)
+{
+ gimp_vector_tool_to_selection_extended (vector_tool, state);
+}
+
+static void
+gimp_vector_tool_vectors_changed (GimpImage *image,
+ GimpVectorTool *vector_tool)
+{
+ gimp_vector_tool_set_vectors (vector_tool,
+ gimp_image_get_active_vectors (image));
+}
+
+static void
+gimp_vector_tool_vectors_removed (GimpVectors *vectors,
+ GimpVectorTool *vector_tool)
+{
+ gimp_vector_tool_set_vectors (vector_tool, NULL);
+}
+
+void
+gimp_vector_tool_set_vectors (GimpVectorTool *vector_tool,
+ GimpVectors *vectors)
+{
+ GimpTool *tool;
+ GimpItem *item = NULL;
+ GimpVectorOptions *options;
+
+ g_return_if_fail (GIMP_IS_VECTOR_TOOL (vector_tool));
+ g_return_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors));
+
+ tool = GIMP_TOOL (vector_tool);
+ options = GIMP_VECTOR_TOOL_GET_OPTIONS (vector_tool);
+
+ if (vectors)
+ item = GIMP_ITEM (vectors);
+
+ if (vectors == vector_tool->vectors)
+ return;
+
+ if (vector_tool->vectors)
+ {
+ GimpImage *old_image;
+
+ old_image = gimp_item_get_image (GIMP_ITEM (vector_tool->vectors));
+
+ g_signal_handlers_disconnect_by_func (old_image,
+ gimp_vector_tool_vectors_changed,
+ vector_tool);
+ g_signal_handlers_disconnect_by_func (vector_tool->vectors,
+ gimp_vector_tool_vectors_removed,
+ vector_tool);
+
+ g_clear_object (&vector_tool->vectors);
+
+ if (options->to_selection_button)
+ {
+ gtk_widget_set_sensitive (options->to_selection_button, FALSE);
+ g_signal_handlers_disconnect_by_func (options->to_selection_button,
+ gimp_vector_tool_to_selection,
+ tool);
+ g_signal_handlers_disconnect_by_func (options->to_selection_button,
+ gimp_vector_tool_to_selection_extended,
+ tool);
+ }
+
+ if (options->fill_button)
+ {
+ gtk_widget_set_sensitive (options->fill_button, FALSE);
+ g_signal_handlers_disconnect_by_func (options->fill_button,
+ gimp_vector_tool_fill_vectors,
+ tool);
+ }
+
+ if (options->stroke_button)
+ {
+ gtk_widget_set_sensitive (options->stroke_button, FALSE);
+ g_signal_handlers_disconnect_by_func (options->stroke_button,
+ gimp_vector_tool_stroke_vectors,
+ tool);
+ }
+ }
+
+ if (! vectors ||
+ (tool->display &&
+ gimp_display_get_image (tool->display) != gimp_item_get_image (item)))
+ {
+ gimp_vector_tool_halt (vector_tool);
+ }
+
+ if (! vectors)
+ return;
+
+ vector_tool->vectors = g_object_ref (vectors);
+
+ g_signal_connect_object (gimp_item_get_image (item), "active-vectors-changed",
+ G_CALLBACK (gimp_vector_tool_vectors_changed),
+ vector_tool, 0);
+ g_signal_connect_object (vectors, "removed",
+ G_CALLBACK (gimp_vector_tool_vectors_removed),
+ vector_tool, 0);
+
+ if (options->to_selection_button)
+ {
+ g_signal_connect_swapped (options->to_selection_button, "clicked",
+ G_CALLBACK (gimp_vector_tool_to_selection),
+ tool);
+ g_signal_connect_swapped (options->to_selection_button, "extended-clicked",
+ G_CALLBACK (gimp_vector_tool_to_selection_extended),
+ tool);
+ gtk_widget_set_sensitive (options->to_selection_button, TRUE);
+ }
+
+ if (options->fill_button)
+ {
+ g_signal_connect_swapped (options->fill_button, "clicked",
+ G_CALLBACK (gimp_vector_tool_fill_vectors),
+ tool);
+ gtk_widget_set_sensitive (options->fill_button, TRUE);
+ }
+
+ if (options->stroke_button)
+ {
+ g_signal_connect_swapped (options->stroke_button, "clicked",
+ G_CALLBACK (gimp_vector_tool_stroke_vectors),
+ tool);
+ gtk_widget_set_sensitive (options->stroke_button, TRUE);
+ }
+
+ if (tool->display)
+ {
+ gimp_tool_path_set_vectors (GIMP_TOOL_PATH (vector_tool->widget), vectors);
+ }
+ else
+ {
+ GimpContext *context = gimp_get_user_context (tool->tool_info->gimp);
+ GimpDisplay *display = gimp_context_get_display (context);
+
+ if (! display ||
+ gimp_display_get_image (display) != gimp_item_get_image (item))
+ {
+ GList *list;
+
+ display = NULL;
+
+ for (list = gimp_get_display_iter (gimp_item_get_image (item)->gimp);
+ list;
+ list = g_list_next (list))
+ {
+ display = list->data;
+
+ if (gimp_display_get_image (display) == gimp_item_get_image (item))
+ {
+ gimp_context_set_display (context, display);
+ break;
+ }
+
+ display = NULL;
+ }
+ }
+
+ if (display)
+ gimp_vector_tool_start (vector_tool, display);
+ }
+
+ if (options->edit_mode != GIMP_VECTOR_MODE_DESIGN)
+ g_object_set (options, "vectors-edit-mode",
+ GIMP_VECTOR_MODE_DESIGN, NULL);
+}
+
+static void
+gimp_vector_tool_to_selection (GimpVectorTool *vector_tool)
+{
+ gimp_vector_tool_to_selection_extended (vector_tool, 0);
+}
+
+static void
+gimp_vector_tool_to_selection_extended (GimpVectorTool *vector_tool,
+ GdkModifierType state)
+{
+ GimpImage *image;
+
+ if (! vector_tool->vectors)
+ return;
+
+ image = gimp_item_get_image (GIMP_ITEM (vector_tool->vectors));
+
+ gimp_item_to_selection (GIMP_ITEM (vector_tool->vectors),
+ gimp_modifiers_to_channel_op (state),
+ TRUE, FALSE, 0, 0);
+ gimp_image_flush (image);
+}
+
+
+static void
+gimp_vector_tool_fill_vectors (GimpVectorTool *vector_tool,
+ GtkWidget *button)
+{
+ GimpDialogConfig *config;
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GtkWidget *dialog;
+
+ if (! vector_tool->vectors)
+ return;
+
+ image = gimp_item_get_image (GIMP_ITEM (vector_tool->vectors));
+
+ config = GIMP_DIALOG_CONFIG (image->gimp->config);
+
+ drawable = gimp_image_get_active_drawable (image);
+
+ if (! drawable)
+ {
+ gimp_tool_message (GIMP_TOOL (vector_tool),
+ GIMP_TOOL (vector_tool)->display,
+ _("There is no active layer or channel to fill"));
+ return;
+ }
+
+ dialog = fill_dialog_new (GIMP_ITEM (vector_tool->vectors),
+ drawable,
+ GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (vector_tool)),
+ _("Fill Path"),
+ GIMP_ICON_TOOL_BUCKET_FILL,
+ GIMP_HELP_PATH_FILL,
+ button,
+ config->fill_options,
+ gimp_vector_tool_fill_callback,
+ vector_tool);
+ gtk_widget_show (dialog);
+}
+
+static void
+gimp_vector_tool_fill_callback (GtkWidget *dialog,
+ GimpItem *item,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GimpFillOptions *options,
+ gpointer data)
+{
+ GimpDialogConfig *config = GIMP_DIALOG_CONFIG (context->gimp->config);
+ GimpImage *image = gimp_item_get_image (item);
+ GError *error = NULL;
+
+ gimp_config_sync (G_OBJECT (options),
+ G_OBJECT (config->fill_options), 0);
+
+ if (! gimp_item_fill (item, drawable, options,
+ TRUE, NULL, &error))
+ {
+ gimp_message_literal (context->gimp,
+ G_OBJECT (dialog),
+ GIMP_MESSAGE_WARNING,
+ error ? error->message : "NULL");
+
+ g_clear_error (&error);
+ return;
+ }
+
+ gimp_image_flush (image);
+
+ gtk_widget_destroy (dialog);
+}
+
+
+static void
+gimp_vector_tool_stroke_vectors (GimpVectorTool *vector_tool,
+ GtkWidget *button)
+{
+ GimpDialogConfig *config;
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GtkWidget *dialog;
+
+ if (! vector_tool->vectors)
+ return;
+
+ image = gimp_item_get_image (GIMP_ITEM (vector_tool->vectors));
+
+ config = GIMP_DIALOG_CONFIG (image->gimp->config);
+
+ drawable = gimp_image_get_active_drawable (image);
+
+ if (! drawable)
+ {
+ gimp_tool_message (GIMP_TOOL (vector_tool),
+ GIMP_TOOL (vector_tool)->display,
+ _("There is no active layer or channel to stroke to"));
+ return;
+ }
+
+ dialog = stroke_dialog_new (GIMP_ITEM (vector_tool->vectors),
+ drawable,
+ GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (vector_tool)),
+ _("Stroke Path"),
+ GIMP_ICON_PATH_STROKE,
+ GIMP_HELP_PATH_STROKE,
+ button,
+ config->stroke_options,
+ gimp_vector_tool_stroke_callback,
+ vector_tool);
+ gtk_widget_show (dialog);
+}
+
+static void
+gimp_vector_tool_stroke_callback (GtkWidget *dialog,
+ GimpItem *item,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GimpStrokeOptions *options,
+ gpointer data)
+{
+ GimpDialogConfig *config = GIMP_DIALOG_CONFIG (context->gimp->config);
+ GimpImage *image = gimp_item_get_image (item);
+ GError *error = NULL;
+
+ gimp_config_sync (G_OBJECT (options),
+ G_OBJECT (config->stroke_options), 0);
+
+ if (! gimp_item_stroke (item, drawable, context, options, NULL,
+ TRUE, NULL, &error))
+ {
+ gimp_message_literal (context->gimp,
+ G_OBJECT (dialog),
+ GIMP_MESSAGE_WARNING,
+ error ? error->message : "NULL");
+
+ g_clear_error (&error);
+ return;
+ }
+
+ gimp_image_flush (image);
+
+ gtk_widget_destroy (dialog);
+}
diff --git a/app/tools/gimpvectortool.h b/app/tools/gimpvectortool.h
new file mode 100644
index 0000000..686ad88
--- /dev/null
+++ b/app/tools/gimpvectortool.h
@@ -0,0 +1,67 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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/>.
+ */
+
+#ifndef __GIMP_VECTOR_TOOL_H__
+#define __GIMP_VECTOR_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_VECTOR_TOOL (gimp_vector_tool_get_type ())
+#define GIMP_VECTOR_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTOR_TOOL, GimpVectorTool))
+#define GIMP_VECTOR_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTOR_TOOL, GimpVectorToolClass))
+#define GIMP_IS_VECTOR_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTOR_TOOL))
+#define GIMP_IS_VECTOR_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTOR_TOOL))
+#define GIMP_VECTOR_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTOR_TOOL, GimpVectorToolClass))
+
+#define GIMP_VECTOR_TOOL_GET_OPTIONS(t) (GIMP_VECTOR_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpVectorTool GimpVectorTool;
+typedef struct _GimpVectorToolClass GimpVectorToolClass;
+
+struct _GimpVectorTool
+{
+ GimpDrawTool parent_instance;
+
+ GimpVectors *vectors; /* the current Vector data */
+ GimpVectorMode saved_mode; /* used by modifier_key() */
+
+ GimpToolWidget *widget;
+ GimpToolWidget *grab_widget;
+};
+
+struct _GimpVectorToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_vector_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_vector_tool_get_type (void) G_GNUC_CONST;
+
+void gimp_vector_tool_set_vectors (GimpVectorTool *vector_tool,
+ GimpVectors *vectors);
+
+
+#endif /* __GIMP_VECTOR_TOOL_H__ */
diff --git a/app/tools/gimpwarpoptions.c b/app/tools/gimpwarpoptions.c
new file mode 100644
index 0000000..2c2d3d9
--- /dev/null
+++ b/app/tools/gimpwarpoptions.c
@@ -0,0 +1,407 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpwarpoptions.c
+ * Copyright (C) 2011 Michael Muré <batolettre@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 "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+
+#include "gimpwarpoptions.h"
+#include "gimptooloptions-gui.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_BEHAVIOR,
+ PROP_EFFECT_SIZE,
+ PROP_EFFECT_HARDNESS,
+ PROP_EFFECT_STRENGTH,
+ PROP_STROKE_SPACING,
+ PROP_INTERPOLATION,
+ PROP_ABYSS_POLICY,
+ PROP_HIGH_QUALITY_PREVIEW,
+ PROP_REAL_TIME_PREVIEW,
+ PROP_STROKE_DURING_MOTION,
+ PROP_STROKE_PERIODICALLY,
+ PROP_STROKE_PERIODICALLY_RATE,
+ PROP_N_ANIMATION_FRAMES
+};
+
+
+static void gimp_warp_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_warp_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpWarpOptions, gimp_warp_options,
+ GIMP_TYPE_TOOL_OPTIONS)
+
+#define parent_class gimp_warp_options_parent_class
+
+
+static void
+gimp_warp_options_class_init (GimpWarpOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_warp_options_set_property;
+ object_class->get_property = gimp_warp_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_BEHAVIOR,
+ "behavior",
+ _("Behavior"),
+ _("Behavior"),
+ GIMP_TYPE_WARP_BEHAVIOR,
+ GIMP_WARP_BEHAVIOR_MOVE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_EFFECT_SIZE,
+ "effect-size",
+ _("Size"),
+ _("Effect Size"),
+ 1.0, 10000.0, 40.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_EFFECT_HARDNESS,
+ "effect-hardness",
+ _("Hardness"),
+ _("Effect Hardness"),
+ 0.0, 100.0, 50.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_EFFECT_STRENGTH,
+ "effect-strength",
+ _("Strength"),
+ _("Effect Strength"),
+ 1.0, 100.0, 50.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_STROKE_SPACING,
+ "stroke-spacing",
+ _("Spacing"),
+ _("Stroke Spacing"),
+ 1.0, 100.0, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_INTERPOLATION,
+ "interpolation",
+ _("Interpolation"),
+ _("Interpolation method"),
+ GIMP_TYPE_INTERPOLATION_TYPE,
+ GIMP_INTERPOLATION_CUBIC,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ABYSS_POLICY,
+ "abyss-policy",
+ _("Abyss policy"),
+ _("Out-of-bounds sampling behavior"),
+ GEGL_TYPE_ABYSS_POLICY,
+ GEGL_ABYSS_NONE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_HIGH_QUALITY_PREVIEW,
+ "high-quality-preview",
+ _("High quality preview"),
+ _("Use an accurate but slower preview"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_REAL_TIME_PREVIEW,
+ "real-time-preview",
+ _("Real-time preview"),
+ _("Render preview in real time (slower)"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_STROKE_DURING_MOTION,
+ "stroke-during-motion",
+ _("During motion"),
+ _("Apply effect during motion"),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_STROKE_PERIODICALLY,
+ "stroke-periodically",
+ _("Periodically"),
+ _("Apply effect periodically"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_STROKE_PERIODICALLY_RATE,
+ "stroke-periodically-rate",
+ _("Rate"),
+ _("Periodic stroke rate"),
+ 0.0, 100.0, 50.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_N_ANIMATION_FRAMES,
+ "n-animation-frames",
+ _("Frames"),
+ _("Number of animation frames"),
+ 3, 1000, 10,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_warp_options_init (GimpWarpOptions *options)
+{
+}
+
+static void
+gimp_warp_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpWarpOptions *options = GIMP_WARP_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_BEHAVIOR:
+ options->behavior = g_value_get_enum (value);
+ break;
+ case PROP_EFFECT_SIZE:
+ options->effect_size = g_value_get_double (value);
+ break;
+ case PROP_EFFECT_HARDNESS:
+ options->effect_hardness = g_value_get_double (value);
+ break;
+ case PROP_EFFECT_STRENGTH:
+ options->effect_strength = g_value_get_double (value);
+ break;
+ case PROP_STROKE_SPACING:
+ options->stroke_spacing = g_value_get_double (value);
+ break;
+ case PROP_INTERPOLATION:
+ options->interpolation = g_value_get_enum (value);
+ break;
+ case PROP_ABYSS_POLICY:
+ options->abyss_policy = g_value_get_enum (value);
+ break;
+ case PROP_HIGH_QUALITY_PREVIEW:
+ options->high_quality_preview = g_value_get_boolean (value);
+ break;
+ case PROP_REAL_TIME_PREVIEW:
+ options->real_time_preview = g_value_get_boolean (value);
+ break;
+ case PROP_STROKE_DURING_MOTION:
+ options->stroke_during_motion = g_value_get_boolean (value);
+ break;
+ case PROP_STROKE_PERIODICALLY:
+ options->stroke_periodically = g_value_get_boolean (value);
+ break;
+ case PROP_STROKE_PERIODICALLY_RATE:
+ options->stroke_periodically_rate = g_value_get_double (value);
+ break;
+ case PROP_N_ANIMATION_FRAMES:
+ options->n_animation_frames = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_warp_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpWarpOptions *options = GIMP_WARP_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_BEHAVIOR:
+ g_value_set_enum (value, options->behavior);
+ break;
+ case PROP_EFFECT_SIZE:
+ g_value_set_double (value, options->effect_size);
+ break;
+ case PROP_EFFECT_HARDNESS:
+ g_value_set_double (value, options->effect_hardness);
+ break;
+ case PROP_EFFECT_STRENGTH:
+ g_value_set_double (value, options->effect_strength);
+ break;
+ case PROP_STROKE_SPACING:
+ g_value_set_double (value, options->stroke_spacing);
+ break;
+ case PROP_INTERPOLATION:
+ g_value_set_enum (value, options->interpolation);
+ break;
+ case PROP_ABYSS_POLICY:
+ g_value_set_enum (value, options->abyss_policy);
+ break;
+ case PROP_HIGH_QUALITY_PREVIEW:
+ g_value_set_boolean (value, options->high_quality_preview);
+ break;
+ case PROP_REAL_TIME_PREVIEW:
+ g_value_set_boolean (value, options->real_time_preview);
+ break;
+ case PROP_STROKE_DURING_MOTION:
+ g_value_set_boolean (value, options->stroke_during_motion);
+ break;
+ case PROP_STROKE_PERIODICALLY:
+ g_value_set_boolean (value, options->stroke_periodically);
+ break;
+ case PROP_STROKE_PERIODICALLY_RATE:
+ g_value_set_double (value, options->stroke_periodically_rate);
+ break;
+ case PROP_N_ANIMATION_FRAMES:
+ g_value_set_int (value, options->n_animation_frames);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_warp_options_gui (GimpToolOptions *tool_options)
+{
+ GimpWarpOptions *options = GIMP_WARP_OPTIONS (tool_options);
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_tool_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *vbox2;
+ GtkWidget *button;
+ GtkWidget *combo;
+ GtkWidget *scale;
+
+ combo = gimp_prop_enum_combo_box_new (config, "behavior", 0, 0);
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ options->behavior_combo = combo;
+
+ scale = gimp_prop_spin_scale_new (config, "effect-size", NULL,
+ 0.01, 1.0, 2);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 1000.0);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ scale = gimp_prop_spin_scale_new (config, "effect-hardness", NULL,
+ 1, 10, 1);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 0.0, 100.0);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ scale = gimp_prop_spin_scale_new (config, "effect-strength", NULL,
+ 1, 10, 1);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 100.0);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ scale = gimp_prop_spin_scale_new (config, "stroke-spacing", NULL,
+ 1, 10, 1);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 100.0);
+ gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ combo = gimp_prop_enum_combo_box_new (config, "interpolation", 0, 0);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Interpolation"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ combo = gimp_prop_enum_combo_box_new (config, "abyss-policy",
+ GEGL_ABYSS_NONE, GEGL_ABYSS_LOOP);
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Abyss policy"));
+ g_object_set (combo, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ button = gimp_prop_check_button_new (config, "high-quality-preview", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gimp_prop_check_button_new (config, "real-time-preview", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ /* the stroke frame */
+ frame = gimp_frame_new (_("Stroke"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ options->stroke_frame = frame;
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ button = gimp_prop_check_button_new (config, "stroke-during-motion", NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ scale = gimp_prop_spin_scale_new (config, "stroke-periodically-rate", NULL,
+ 1, 10, 1);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 0.0, 100.0);
+
+ frame = gimp_prop_expanding_frame_new (config, "stroke-periodically", NULL,
+ scale, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* the animation frame */
+ frame = gimp_frame_new (_("Animate"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ scale = gimp_prop_spin_scale_new (config, "n-animation-frames", NULL,
+ 1.0, 10.0, 0);
+ gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 3.0, 100.0);
+ gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
+ gtk_widget_show (scale);
+
+ options->animate_button = gtk_button_new_with_label (_("Create Animation"));
+ gtk_widget_set_sensitive (options->animate_button, FALSE);
+ gtk_box_pack_start (GTK_BOX (vbox2), options->animate_button,
+ FALSE, FALSE, 0);
+ gtk_widget_show (options->animate_button);
+
+ g_object_add_weak_pointer (G_OBJECT (options->animate_button),
+ (gpointer) &options->animate_button);
+
+ return vbox;
+}
diff --git a/app/tools/gimpwarpoptions.h b/app/tools/gimpwarpoptions.h
new file mode 100644
index 0000000..eacfb71
--- /dev/null
+++ b/app/tools/gimpwarpoptions.h
@@ -0,0 +1,75 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpwarpoptions.h
+ * Copyright (C) 2011 Michael Muré <batolettre@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_WARP_OPTIONS_H__
+#define __GIMP_WARP_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_TYPE_WARP_OPTIONS (gimp_warp_options_get_type ())
+#define GIMP_WARP_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_WARP_OPTIONS, GimpWarpOptions))
+#define GIMP_WARP_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_WARP_OPTIONS, GimpWarpOptionsClass))
+#define GIMP_IS_WARP_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_WARP_OPTIONS))
+#define GIMP_IS_WARP_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_WARP_OPTIONS))
+#define GIMP_WARP_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_WARP_OPTIONS, GimpWarpOptionsClass))
+
+
+typedef struct _GimpWarpOptions GimpWarpOptions;
+typedef struct _GimpWarpOptionsClass GimpWarpOptionsClass;
+
+struct _GimpWarpOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpWarpBehavior behavior;
+ gdouble effect_size;
+ gdouble effect_hardness;
+ gdouble effect_strength;
+ gdouble stroke_spacing;
+ GimpInterpolationType interpolation;
+ GeglAbyssPolicy abyss_policy;
+ gboolean high_quality_preview;
+ gboolean real_time_preview;
+
+ gboolean stroke_during_motion;
+ gboolean stroke_periodically;
+ gdouble stroke_periodically_rate;
+
+ gint n_animation_frames;
+
+ /* options gui */
+ GtkWidget *behavior_combo;
+ GtkWidget *stroke_frame;
+ GtkWidget *animate_button;
+};
+
+struct _GimpWarpOptionsClass
+{
+ GimpToolOptionsClass parent_class;
+};
+
+
+GType gimp_warp_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_warp_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_WARP_OPTIONS_H__ */
diff --git a/app/tools/gimpwarptool.c b/app/tools/gimpwarptool.c
new file mode 100644
index 0000000..c76f950
--- /dev/null
+++ b/app/tools/gimpwarptool.c
@@ -0,0 +1,1484 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpwarptool.c
+ * Copyright (C) 2011 Michael Muré <batolettre@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 <gegl-plugin.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpdisplayconfig.h"
+#include "config/gimpguiconfig.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+
+#include "core/gimp.h"
+#include "core/gimpchannel.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimpimage.h"
+#include "core/gimplayer.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+#include "core/gimpsubprogress.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpdisplay.h"
+
+#include "gimpwarptool.h"
+#include "gimpwarpoptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+#define STROKE_TIMER_MAX_FPS 20
+#define PREVIEW_SAMPLER GEGL_SAMPLER_NEAREST
+
+
+static void gimp_warp_tool_constructed (GObject *object);
+
+static void gimp_warp_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_warp_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_warp_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_warp_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_warp_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_warp_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_warp_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+const gchar * gimp_warp_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+const gchar * gimp_warp_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_warp_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_warp_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+static void gimp_warp_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_warp_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_warp_tool_cursor_notify (GimpDisplayConfig *config,
+ GParamSpec *pspec,
+ GimpWarpTool *wt);
+
+static gboolean gimp_warp_tool_can_stroke (GimpWarpTool *wt,
+ GimpDisplay *display,
+ gboolean show_message);
+
+static gboolean gimp_warp_tool_start (GimpWarpTool *wt,
+ GimpDisplay *display);
+static void gimp_warp_tool_halt (GimpWarpTool *wt);
+static void gimp_warp_tool_commit (GimpWarpTool *wt);
+
+static void gimp_warp_tool_start_stroke_timer (GimpWarpTool *wt);
+static void gimp_warp_tool_stop_stroke_timer (GimpWarpTool *wt);
+static gboolean gimp_warp_tool_stroke_timer (GimpWarpTool *wt);
+
+static void gimp_warp_tool_create_graph (GimpWarpTool *wt);
+static void gimp_warp_tool_create_filter (GimpWarpTool *wt,
+ GimpDrawable *drawable);
+static void gimp_warp_tool_set_sampler (GimpWarpTool *wt,
+ gboolean commit);
+static GeglRectangle
+ gimp_warp_tool_get_stroke_bounds (GeglNode *node);
+static GeglRectangle gimp_warp_tool_get_node_bounds (GeglNode *node);
+static void gimp_warp_tool_clear_node_bounds (GeglNode *node);
+static GeglRectangle gimp_warp_tool_get_invalidated_by_change (GimpWarpTool *wt,
+ const GeglRectangle *area);
+static void gimp_warp_tool_update_bounds (GimpWarpTool *wt);
+static void gimp_warp_tool_update_area (GimpWarpTool *wt,
+ const GeglRectangle *area,
+ gboolean synchronous);
+static void gimp_warp_tool_update_stroke (GimpWarpTool *wt,
+ GeglNode *node);
+static void gimp_warp_tool_stroke_append (GimpWarpTool *wt,
+ gchar type,
+ gdouble x,
+ gdouble y);
+static void gimp_warp_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+static void gimp_warp_tool_add_op (GimpWarpTool *wt,
+ GeglNode *op);
+static void gimp_warp_tool_remove_op (GimpWarpTool *wt,
+ GeglNode *op);
+static void gimp_warp_tool_free_op (GeglNode *op);
+
+static void gimp_warp_tool_animate (GimpWarpTool *wt);
+
+
+G_DEFINE_TYPE (GimpWarpTool, gimp_warp_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_warp_tool_parent_class
+
+
+void
+gimp_warp_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_WARP_TOOL,
+ GIMP_TYPE_WARP_OPTIONS,
+ gimp_warp_options_gui,
+ 0,
+ "gimp-warp-tool",
+ _("Warp Transform"),
+ _("Warp Transform: Deform with different tools"),
+ N_("_Warp Transform"), "W",
+ NULL, GIMP_HELP_TOOL_WARP,
+ GIMP_ICON_TOOL_WARP,
+ data);
+}
+
+static void
+gimp_warp_tool_class_init (GimpWarpToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ object_class->constructed = gimp_warp_tool_constructed;
+
+ tool_class->control = gimp_warp_tool_control;
+ tool_class->button_press = gimp_warp_tool_button_press;
+ tool_class->button_release = gimp_warp_tool_button_release;
+ tool_class->motion = gimp_warp_tool_motion;
+ tool_class->key_press = gimp_warp_tool_key_press;
+ tool_class->oper_update = gimp_warp_tool_oper_update;
+ tool_class->cursor_update = gimp_warp_tool_cursor_update;
+ tool_class->can_undo = gimp_warp_tool_can_undo;
+ tool_class->can_redo = gimp_warp_tool_can_redo;
+ tool_class->undo = gimp_warp_tool_undo;
+ tool_class->redo = gimp_warp_tool_redo;
+ tool_class->options_notify = gimp_warp_tool_options_notify;
+
+ draw_tool_class->draw = gimp_warp_tool_draw;
+}
+
+static void
+gimp_warp_tool_init (GimpWarpTool *self)
+{
+ GimpTool *tool = GIMP_TOOL (self);
+
+ gimp_tool_control_set_motion_mode (tool->control, GIMP_MOTION_MODE_EXACT);
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_dirty_action (tool->control,
+ GIMP_TOOL_ACTION_COMMIT);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_WARP);
+ gimp_tool_control_set_action_size (tool->control,
+ "tools/tools-warp-effect-size-set");
+ gimp_tool_control_set_action_hardness (tool->control,
+ "tools/tools-warp-effect-hardness-set");
+
+ self->show_cursor = TRUE;
+ self->draw_brush = TRUE;
+ self->snap_brush = FALSE;
+}
+
+static void
+gimp_warp_tool_constructed (GObject *object)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (object);
+ GimpTool *tool = GIMP_TOOL (object);
+ GimpDisplayConfig *display_config;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ display_config = GIMP_DISPLAY_CONFIG (tool->tool_info->gimp->config);
+
+ wt->show_cursor = display_config->show_paint_tool_cursor;
+ wt->draw_brush = display_config->show_brush_outline;
+ wt->snap_brush = display_config->snap_brush_outline;
+
+ g_signal_connect_object (display_config, "notify::show-paint-tool-cursor",
+ G_CALLBACK (gimp_warp_tool_cursor_notify),
+ wt, 0);
+ g_signal_connect_object (display_config, "notify::show-brush-outline",
+ G_CALLBACK (gimp_warp_tool_cursor_notify),
+ wt, 0);
+ g_signal_connect_object (display_config, "notify::snap-brush-outline",
+ G_CALLBACK (gimp_warp_tool_cursor_notify),
+ wt, 0);
+}
+
+static void
+gimp_warp_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_warp_tool_halt (wt);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_warp_tool_commit (wt);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_warp_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GeglNode *new_op;
+ gint off_x, off_y;
+
+ if (tool->display && display != tool->display)
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+
+ if (! tool->display)
+ {
+ if (! gimp_warp_tool_start (wt, display))
+ return;
+ }
+
+ if (! gimp_warp_tool_can_stroke (wt, display, TRUE))
+ return;
+
+ wt->current_stroke = gegl_path_new ();
+
+ wt->last_pos.x = coords->x;
+ wt->last_pos.y = coords->y;
+
+ wt->total_dist = 0.0;
+
+ new_op = gegl_node_new_child (NULL,
+ "operation", "gegl:warp",
+ "behavior", options->behavior,
+ "size", options->effect_size,
+ "hardness", options->effect_hardness / 100.0,
+ "strength", options->effect_strength,
+ /* we implement spacing manually.
+ * anything > 1 will do.
+ */
+ "spacing", 10.0,
+ "stroke", wt->current_stroke,
+ NULL);
+
+ gimp_warp_tool_add_op (wt, new_op);
+ g_object_unref (new_op);
+
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
+
+ gimp_warp_tool_stroke_append (wt,
+ 'M', wt->last_pos.x - off_x,
+ wt->last_pos.y - off_y);
+
+ gimp_warp_tool_start_stroke_timer (wt);
+
+ gimp_tool_control_activate (tool->control);
+}
+
+void
+gimp_warp_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (wt));
+
+ gimp_tool_control_halt (tool->control);
+
+ gimp_warp_tool_stop_stroke_timer (wt);
+
+#ifdef WARP_DEBUG
+ g_printerr ("%s\n", gegl_path_to_string (wt->current_stroke));
+#endif
+
+ g_clear_object (&wt->current_stroke);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ gimp_warp_tool_undo (tool, display);
+
+ /* the just undone stroke has no business on the redo stack */
+ gimp_warp_tool_free_op (wt->redo_stack->data);
+ wt->redo_stack = g_list_remove_link (wt->redo_stack, wt->redo_stack);
+ }
+ else
+ {
+ if (wt->redo_stack)
+ {
+ /* the redo stack becomes invalid by actually doing a stroke */
+ g_list_free_full (wt->redo_stack,
+ (GDestroyNotify) gimp_warp_tool_free_op);
+ wt->redo_stack = NULL;
+ }
+
+ gimp_tool_push_status (tool, tool->display,
+ _("Press ENTER to commit the transform"));
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+
+ /* update the undo actions / menu items */
+ gimp_image_flush (gimp_display_get_image (GIMP_TOOL (wt)->display));
+}
+
+static void
+gimp_warp_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GimpVector2 old_cursor_pos;
+ GimpVector2 delta;
+ gdouble dist;
+ gdouble step;
+ gboolean stroke_changed = FALSE;
+
+ if (! wt->snap_brush)
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (wt));
+
+ old_cursor_pos = wt->cursor_pos;
+
+ wt->cursor_pos.x = coords->x;
+ wt->cursor_pos.y = coords->y;
+
+ gimp_vector2_sub (&delta, &wt->cursor_pos, &old_cursor_pos);
+ dist = gimp_vector2_length (&delta);
+
+ step = options->effect_size * options->stroke_spacing / 100.0;
+
+ while (wt->total_dist + dist >= step)
+ {
+ gdouble diff = step - wt->total_dist;
+
+ gimp_vector2_mul (&delta, diff / dist);
+ gimp_vector2_add (&old_cursor_pos, &old_cursor_pos, &delta);
+
+ gimp_vector2_sub (&delta, &wt->cursor_pos, &old_cursor_pos);
+ dist -= diff;
+
+ wt->last_pos = old_cursor_pos;
+ wt->total_dist = 0.0;
+
+ if (options->stroke_during_motion)
+ {
+ gint off_x, off_y;
+
+ if (! stroke_changed)
+ {
+ stroke_changed = TRUE;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
+
+ gimp_warp_tool_stroke_append (wt,
+ 'L', wt->last_pos.x - off_x,
+ wt->last_pos.y - off_y);
+ }
+ }
+
+ wt->total_dist += dist;
+
+ if (stroke_changed)
+ {
+ gimp_warp_tool_start_stroke_timer (wt);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+
+ if (! wt->snap_brush)
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (wt));
+}
+
+static gboolean
+gimp_warp_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ return TRUE;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_warp_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (proximity)
+ {
+ gimp_draw_tool_pause (draw_tool);
+
+ if (! tool->display || display == tool->display)
+ {
+ wt->cursor_pos.x = coords->x;
+ wt->cursor_pos.y = coords->y;
+
+ wt->last_pos = wt->cursor_pos;
+ }
+
+ if (! gimp_draw_tool_is_active (draw_tool))
+ gimp_draw_tool_start (draw_tool, display);
+
+ gimp_draw_tool_resume (draw_tool);
+ }
+ else if (gimp_draw_tool_is_active (draw_tool))
+ {
+ gimp_draw_tool_stop (draw_tool);
+ }
+}
+
+static void
+gimp_warp_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (tool);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ if (! gimp_warp_tool_can_stroke (wt, display, FALSE))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ else if (display == tool->display)
+ {
+#if 0
+ /* FIXME have better cursors */
+
+ switch (options->behavior)
+ {
+ case GIMP_WARP_BEHAVIOR_MOVE:
+ case GIMP_WARP_BEHAVIOR_GROW:
+ case GIMP_WARP_BEHAVIOR_SHRINK:
+ case GIMP_WARP_BEHAVIOR_SWIRL_CW:
+ case GIMP_WARP_BEHAVIOR_SWIRL_CCW:
+ case GIMP_WARP_BEHAVIOR_ERASE:
+ case GIMP_WARP_BEHAVIOR_SMOOTH:
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ break;
+ }
+#else
+ (void) options;
+#endif
+ }
+
+ if (! wt->show_cursor && modifier != GIMP_CURSOR_MODIFIER_BAD)
+ {
+ gimp_tool_set_cursor (tool, display,
+ GIMP_CURSOR_NONE,
+ GIMP_TOOL_CURSOR_NONE,
+ GIMP_CURSOR_MODIFIER_NONE);
+ return;
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+const gchar *
+gimp_warp_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GeglNode *to_delete;
+ const gchar *type;
+
+ if (! wt->render_node)
+ return NULL;
+
+ to_delete = gegl_node_get_producer (wt->render_node, "aux", NULL);
+ type = gegl_node_get_operation (to_delete);
+
+ if (strcmp (type, "gegl:warp"))
+ return NULL;
+
+ return _("Warp Tool Stroke");
+}
+
+const gchar *
+gimp_warp_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+
+ if (! wt->render_node || ! wt->redo_stack)
+ return NULL;
+
+ return _("Warp Tool Stroke");
+}
+
+static gboolean
+gimp_warp_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GeglNode *to_delete;
+ GeglNode *prev_node;
+
+ to_delete = gegl_node_get_producer (wt->render_node, "aux", NULL);
+
+ wt->redo_stack = g_list_prepend (wt->redo_stack, to_delete);
+
+ /* we connect render_node to the previous node, but keep the current node
+ * in the graph, connected to the previous node as well, so that it doesn't
+ * get invalidated and maintains its cache. this way, redoing it doesn't
+ * require reprocessing.
+ */
+ prev_node = gegl_node_get_producer (to_delete, "input", NULL);
+
+ gegl_node_connect_to (prev_node, "output",
+ wt->render_node, "aux");
+
+ gimp_warp_tool_update_bounds (wt);
+ gimp_warp_tool_update_stroke (wt, to_delete);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_warp_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GeglNode *to_add;
+
+ to_add = wt->redo_stack->data;
+
+ gegl_node_connect_to (to_add, "output",
+ wt->render_node, "aux");
+
+ wt->redo_stack = g_list_remove_link (wt->redo_stack, wt->redo_stack);
+
+ gimp_warp_tool_update_bounds (wt);
+ gimp_warp_tool_update_stroke (wt, to_add);
+
+ return TRUE;
+}
+
+static void
+gimp_warp_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
+ GimpWarpOptions *wt_options = GIMP_WARP_OPTIONS (options);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "effect-size"))
+ {
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+ else if (! strcmp (pspec->name, "interpolation"))
+ {
+ gimp_warp_tool_set_sampler (wt, /* commit = */ FALSE);
+ }
+ else if (! strcmp (pspec->name, "abyss-policy"))
+ {
+ if (wt->render_node)
+ {
+ gegl_node_set (wt->render_node,
+ "abyss-policy", wt_options->abyss_policy,
+ NULL);
+
+ gimp_warp_tool_update_stroke (wt, NULL);
+ }
+ }
+ else if (! strcmp (pspec->name, "high-quality-preview"))
+ {
+ gimp_warp_tool_set_sampler (wt, /* commit = */ FALSE);
+ }
+}
+
+static void
+gimp_warp_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpWarpTool *wt = GIMP_WARP_TOOL (draw_tool);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ gdouble x, y;
+
+ if (wt->snap_brush)
+ {
+ x = wt->last_pos.x;
+ y = wt->last_pos.y;
+ }
+ else
+ {
+ x = wt->cursor_pos.x;
+ y = wt->cursor_pos.y;
+ }
+
+ if (wt->draw_brush)
+ {
+ gimp_draw_tool_add_arc (draw_tool,
+ FALSE,
+ x - options->effect_size * 0.5,
+ y - options->effect_size * 0.5,
+ options->effect_size,
+ options->effect_size,
+ 0.0, 2.0 * G_PI);
+ }
+ else if (! wt->show_cursor)
+ {
+ /* don't leave the user without any indication and draw
+ * a fallback crosshair
+ */
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CROSSHAIR,
+ x, y,
+ GIMP_TOOL_HANDLE_SIZE_CROSSHAIR,
+ GIMP_TOOL_HANDLE_SIZE_CROSSHAIR,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+}
+
+static void
+gimp_warp_tool_cursor_notify (GimpDisplayConfig *config,
+ GParamSpec *pspec,
+ GimpWarpTool *wt)
+{
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (wt));
+
+ wt->show_cursor = config->show_paint_tool_cursor;
+ wt->draw_brush = config->show_brush_outline;
+ wt->snap_brush = config->snap_brush_outline;
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (wt));
+}
+
+static gboolean
+gimp_warp_tool_can_stroke (GimpWarpTool *wt,
+ GimpDisplay *display,
+ gboolean show_message)
+{
+ GimpTool *tool = GIMP_TOOL (wt);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ if (show_message)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("Cannot warp layer groups."));
+ }
+
+ return FALSE;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ if (show_message)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The active layer's pixels are locked."));
+
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ }
+
+ return FALSE;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ if (show_message)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("The active layer is not visible."));
+ }
+
+ return FALSE;
+ }
+
+ if (! options->stroke_during_motion &&
+ ! options->stroke_periodically)
+ {
+ if (show_message)
+ {
+ gimp_tool_message_literal (tool, display,
+ _("No stroke events selected."));
+
+ gimp_widget_blink (options->stroke_frame);
+ }
+
+ return FALSE;
+ }
+
+ if (! wt->filter || ! gimp_tool_can_undo (tool, display))
+ {
+ const gchar *message = NULL;
+
+ switch (options->behavior)
+ {
+ case GIMP_WARP_BEHAVIOR_MOVE:
+ case GIMP_WARP_BEHAVIOR_GROW:
+ case GIMP_WARP_BEHAVIOR_SHRINK:
+ case GIMP_WARP_BEHAVIOR_SWIRL_CW:
+ case GIMP_WARP_BEHAVIOR_SWIRL_CCW:
+ break;
+
+ case GIMP_WARP_BEHAVIOR_ERASE:
+ message = _("No warp to erase.");
+ break;
+
+ case GIMP_WARP_BEHAVIOR_SMOOTH:
+ message = _("No warp to smooth.");
+ break;
+ }
+
+ if (message)
+ {
+ if (show_message)
+ {
+ gimp_tool_message_literal (tool, display, message);
+
+ gimp_widget_blink (options->behavior_combo);
+ }
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_warp_tool_start (GimpWarpTool *wt,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (wt);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ const Babl *format;
+ GeglRectangle bbox;
+
+ if (! gimp_warp_tool_can_stroke (wt, display, TRUE))
+ return FALSE;
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ /* Create the coords buffer, with the size of the selection */
+ format = babl_format_n (babl_type ("float"), 2);
+
+ gimp_item_mask_intersect (GIMP_ITEM (drawable), &bbox.x, &bbox.y,
+ &bbox.width, &bbox.height);
+
+#ifdef WARP_DEBUG
+ g_printerr ("Initialize coordinate buffer (%d,%d) at %d,%d\n",
+ bbox.width, bbox.height, bbox.x, bbox.y);
+#endif
+
+ wt->coords_buffer = gegl_buffer_new (&bbox, format);
+
+ gimp_warp_tool_create_filter (wt, drawable);
+
+ if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (wt)))
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (wt), display);
+
+ if (options->animate_button)
+ {
+ g_signal_connect_swapped (options->animate_button, "clicked",
+ G_CALLBACK (gimp_warp_tool_animate),
+ wt);
+
+ gtk_widget_set_sensitive (options->animate_button, TRUE);
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_warp_tool_halt (GimpWarpTool *wt)
+{
+ GimpTool *tool = GIMP_TOOL (wt);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+
+ g_clear_object (&wt->coords_buffer);
+
+ g_clear_object (&wt->graph);
+ wt->render_node = NULL;
+
+ if (wt->filter)
+ {
+ gimp_drawable_filter_abort (wt->filter);
+ g_clear_object (&wt->filter);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+
+ if (wt->redo_stack)
+ {
+ g_list_free (wt->redo_stack);
+ wt->redo_stack = NULL;
+ }
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (wt)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (wt));
+
+ if (options->animate_button)
+ {
+ gtk_widget_set_sensitive (options->animate_button, FALSE);
+
+ g_signal_handlers_disconnect_by_func (options->animate_button,
+ gimp_warp_tool_animate,
+ wt);
+ }
+}
+
+static void
+gimp_warp_tool_commit (GimpWarpTool *wt)
+{
+ GimpTool *tool = GIMP_TOOL (wt);
+
+ /* don't commit a nop */
+ if (tool->display && gimp_tool_can_undo (tool, tool->display))
+ {
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_warp_tool_set_sampler (wt, /* commit = */ TRUE);
+
+ gimp_drawable_filter_commit (wt->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&wt->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+}
+
+static void
+gimp_warp_tool_start_stroke_timer (GimpWarpTool *wt)
+{
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+
+ gimp_warp_tool_stop_stroke_timer (wt);
+
+ if (options->stroke_periodically &&
+ options->stroke_periodically_rate > 0.0 &&
+ ! (options->behavior == GIMP_WARP_BEHAVIOR_MOVE &&
+ options->stroke_during_motion))
+ {
+ gdouble fps;
+
+ fps = STROKE_TIMER_MAX_FPS * options->stroke_periodically_rate / 100.0;
+
+ wt->stroke_timer = g_timeout_add (1000.0 / fps,
+ (GSourceFunc) gimp_warp_tool_stroke_timer,
+ wt);
+ }
+}
+
+static void
+gimp_warp_tool_stop_stroke_timer (GimpWarpTool *wt)
+{
+ if (wt->stroke_timer)
+ g_source_remove (wt->stroke_timer);
+
+ wt->stroke_timer = 0;
+}
+
+static gboolean
+gimp_warp_tool_stroke_timer (GimpWarpTool *wt)
+{
+ GimpTool *tool = GIMP_TOOL (wt);
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
+
+ gimp_warp_tool_stroke_append (wt,
+ 'L', wt->last_pos.x - off_x,
+ wt->last_pos.y - off_y);
+
+ return TRUE;
+}
+
+static void
+gimp_warp_tool_create_graph (GimpWarpTool *wt)
+{
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GeglNode *graph; /* Wrapper to be returned */
+ GeglNode *input, *output; /* Proxy nodes */
+ GeglNode *coords, *render; /* Render nodes */
+
+ /* render_node is not supposed to be recreated */
+ g_return_if_fail (wt->graph == NULL);
+
+ graph = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (graph, "input");
+ output = gegl_node_get_output_proxy (graph, "output");
+
+ coords = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ "buffer", wt->coords_buffer,
+ NULL);
+
+ render = gegl_node_new_child (graph,
+ "operation", "gegl:map-relative",
+ "abyss-policy", options->abyss_policy,
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ render, "input");
+
+ gegl_node_connect_to (coords, "output",
+ render, "aux");
+
+ gegl_node_connect_to (render, "output",
+ output, "input");
+
+ wt->graph = graph;
+ wt->render_node = render;
+}
+
+static void
+gimp_warp_tool_create_filter (GimpWarpTool *wt,
+ GimpDrawable *drawable)
+{
+ if (! wt->graph)
+ gimp_warp_tool_create_graph (wt);
+
+ gimp_warp_tool_set_sampler (wt, /* commit = */ FALSE);
+
+ wt->filter = gimp_drawable_filter_new (drawable,
+ _("Warp transform"),
+ wt->graph,
+ GIMP_ICON_TOOL_WARP);
+
+ gimp_drawable_filter_set_region (wt->filter, GIMP_FILTER_REGION_DRAWABLE);
+
+#if 0
+ g_object_set (wt->filter, "gegl-caching", TRUE, NULL);
+#endif
+
+ g_signal_connect (wt->filter, "flush",
+ G_CALLBACK (gimp_warp_tool_filter_flush),
+ wt);
+}
+
+static void
+gimp_warp_tool_set_sampler (GimpWarpTool *wt,
+ gboolean commit)
+{
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GeglSamplerType sampler;
+ GeglSamplerType old_sampler;
+
+ if (! wt->render_node)
+ return;
+
+ if (commit || options->high_quality_preview)
+ sampler = (GeglSamplerType) options->interpolation;
+ else
+ sampler = PREVIEW_SAMPLER;
+
+ gegl_node_get (wt->render_node,
+ "sampler-type", &old_sampler,
+ NULL);
+
+ if (sampler != old_sampler)
+ {
+ gegl_node_set (wt->render_node,
+ "sampler-type", sampler,
+ NULL);
+
+ gimp_warp_tool_update_bounds (wt);
+ gimp_warp_tool_update_stroke (wt, NULL);
+ }
+}
+
+static GeglRectangle
+gimp_warp_tool_get_stroke_bounds (GeglNode *node)
+{
+ GeglRectangle bbox = {0, 0, 0, 0};
+ GeglPath *stroke;
+ gdouble size;
+
+ gegl_node_get (node,
+ "stroke", &stroke,
+ "size", &size,
+ NULL);
+
+ if (stroke)
+ {
+ gdouble min_x;
+ gdouble max_x;
+ gdouble min_y;
+ gdouble max_y;
+
+ gegl_path_get_bounds (stroke, &min_x, &max_x, &min_y, &max_y);
+ g_object_unref (stroke);
+
+ bbox.x = floor (min_x - size * 0.5);
+ bbox.y = floor (min_y - size * 0.5);
+ bbox.width = ceil (max_x + size * 0.5) - bbox.x;
+ bbox.height = ceil (max_y + size * 0.5) - bbox.y;
+ }
+
+ return bbox;
+}
+
+static GeglRectangle
+gimp_warp_tool_get_node_bounds (GeglNode *node)
+{
+ GeglRectangle *bounds;
+
+ if (! node || strcmp (gegl_node_get_operation (node), "gegl:warp"))
+ return *GEGL_RECTANGLE (0, 0, 0, 0);
+
+ bounds = g_object_get_data (G_OBJECT (node), "gimp-warp-tool-bounds");
+
+ if (! bounds)
+ {
+ GeglNode *input_node;
+ GeglRectangle input_bounds;
+ GeglRectangle stroke_bounds;
+
+ input_node = gegl_node_get_producer (node, "input", NULL);
+ input_bounds = gimp_warp_tool_get_node_bounds (input_node);
+
+ stroke_bounds = gimp_warp_tool_get_stroke_bounds (node);
+
+ gegl_rectangle_bounding_box (&input_bounds,
+ &input_bounds, &stroke_bounds);
+
+ bounds = gegl_rectangle_dup (&input_bounds);
+
+ g_object_set_data_full (G_OBJECT (node), "gimp-warp-tool-bounds",
+ bounds, g_free);
+ }
+
+ return *bounds;
+}
+
+static void
+gimp_warp_tool_clear_node_bounds (GeglNode *node)
+{
+ if (node && ! strcmp (gegl_node_get_operation (node), "gegl:warp"))
+ g_object_set_data (G_OBJECT (node), "gimp-warp-tool-bounds", NULL);
+}
+
+static GeglRectangle
+gimp_warp_tool_get_invalidated_by_change (GimpWarpTool *wt,
+ const GeglRectangle *area)
+{
+ GeglRectangle result = *area;
+
+ if (! wt->filter)
+ return result;
+
+ if (wt->render_node)
+ {
+ GeglOperation *operation = gegl_node_get_gegl_operation (wt->render_node);
+
+ result = gegl_operation_get_invalidated_by_change (operation,
+ "aux", area);
+ }
+
+ return result;
+}
+
+static void
+gimp_warp_tool_update_bounds (GimpWarpTool *wt)
+{
+ GeglRectangle bounds = {0, 0, 0, 0};
+
+ if (! wt->filter)
+ return;
+
+ if (wt->render_node)
+ {
+ GeglNode *node = gegl_node_get_producer (wt->render_node, "aux", NULL);
+
+ bounds = gimp_warp_tool_get_node_bounds (node);
+
+ bounds = gimp_warp_tool_get_invalidated_by_change (wt, &bounds);
+ }
+
+ gimp_drawable_filter_set_crop (wt->filter, &bounds, FALSE);
+}
+
+static void
+gimp_warp_tool_update_area (GimpWarpTool *wt,
+ const GeglRectangle *area,
+ gboolean synchronous)
+{
+ GeglRectangle rect;
+
+ if (! wt->filter)
+ return;
+
+ rect = gimp_warp_tool_get_invalidated_by_change (wt, area);
+
+ if (synchronous)
+ {
+ GimpTool *tool = GIMP_TOOL (wt);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ g_signal_handlers_block_by_func (wt->filter,
+ gimp_warp_tool_filter_flush,
+ wt);
+
+ gimp_drawable_filter_apply (wt->filter, &rect);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (tool->display);
+
+ g_signal_handlers_unblock_by_func (wt->filter,
+ gimp_warp_tool_filter_flush,
+ wt);
+ }
+ else
+ {
+ gimp_drawable_filter_apply (wt->filter, &rect);
+ }
+}
+
+static void
+gimp_warp_tool_update_stroke (GimpWarpTool *wt,
+ GeglNode *node)
+{
+ GeglRectangle bounds = {0, 0, 0, 0};
+
+ if (! wt->filter)
+ return;
+
+ if (node)
+ {
+ /* update just this stroke */
+ bounds = gimp_warp_tool_get_stroke_bounds (node);
+ }
+ else if (wt->render_node)
+ {
+ node = gegl_node_get_producer (wt->render_node, "aux", NULL);
+
+ bounds = gimp_warp_tool_get_node_bounds (node);
+ }
+
+ if (! gegl_rectangle_is_empty (&bounds))
+ {
+#ifdef WARP_DEBUG
+ g_printerr ("update stroke: (%d,%d), %dx%d\n",
+ bounds.x, bounds.y,
+ bounds.width, bounds.height);
+#endif
+
+ gimp_warp_tool_update_area (wt, &bounds, FALSE);
+ }
+}
+
+static void
+gimp_warp_tool_stroke_append (GimpWarpTool *wt,
+ gchar type,
+ gdouble x,
+ gdouble y)
+{
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GeglRectangle area;
+
+ if (! wt->filter)
+ return;
+
+ gegl_path_append (wt->current_stroke, type, x, y);
+
+ area.x = floor (x - options->effect_size * 0.5);
+ area.y = floor (y - options->effect_size * 0.5);
+ area.width = ceil (x + options->effect_size * 0.5) - area.x;
+ area.height = ceil (y + options->effect_size * 0.5) - area.y;
+
+#ifdef WARP_DEBUG
+ g_printerr ("update rect: (%d,%d), %dx%d\n",
+ area.x, area.y,
+ area.width, area.height);
+#endif
+
+ if (wt->render_node)
+ {
+ GeglNode *node = gegl_node_get_producer (wt->render_node, "aux", NULL);
+
+ gimp_warp_tool_clear_node_bounds (node);
+
+ gimp_warp_tool_update_bounds (wt);
+ }
+
+ gimp_warp_tool_update_area (wt, &area, options->real_time_preview);
+}
+
+static void
+gimp_warp_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_warp_tool_add_op (GimpWarpTool *wt,
+ GeglNode *op)
+{
+ GeglNode *last_op;
+
+ g_return_if_fail (GEGL_IS_NODE (wt->render_node));
+
+ gegl_node_add_child (wt->graph, op);
+
+ last_op = gegl_node_get_producer (wt->render_node, "aux", NULL);
+
+ gegl_node_disconnect (wt->render_node, "aux");
+ gegl_node_connect_to (last_op, "output",
+ op , "input");
+ gegl_node_connect_to (op, "output",
+ wt->render_node, "aux");
+}
+
+static void
+gimp_warp_tool_remove_op (GimpWarpTool *wt,
+ GeglNode *op)
+{
+ GeglNode *previous;
+
+ g_return_if_fail (GEGL_IS_NODE (wt->render_node));
+
+ previous = gegl_node_get_producer (op, "input", NULL);
+
+ gegl_node_disconnect (op, "input");
+ gegl_node_connect_to (previous, "output",
+ wt->render_node, "aux");
+
+ gegl_node_remove_child (wt->graph, op);
+}
+
+static void
+gimp_warp_tool_free_op (GeglNode *op)
+{
+ GeglNode *parent;
+
+ parent = gegl_node_get_parent (op);
+
+ gimp_assert (parent != NULL);
+
+ gegl_node_remove_child (parent, op);
+}
+
+static void
+gimp_warp_tool_animate (GimpWarpTool *wt)
+{
+ GimpTool *tool = GIMP_TOOL (wt);
+ GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
+ GimpImage *orig_image;
+ GimpImage *image;
+ GimpLayer *layer;
+ GimpLayer *first_layer;
+ GeglNode *scale_node;
+ GimpProgress *progress;
+ GtkWidget *widget;
+ gint i;
+
+ if (! gimp_warp_tool_can_undo (tool, tool->display))
+ {
+ gimp_tool_message_literal (tool, tool->display,
+ _("Please add some warp strokes first."));
+ return;
+ }
+
+ /* get rid of the image map so we can use wt->graph */
+ if (wt->filter)
+ {
+ gimp_drawable_filter_abort (wt->filter);
+ g_clear_object (&wt->filter);
+ }
+
+ gimp_warp_tool_set_sampler (wt, /* commit = */ TRUE);
+
+ gimp_progress_start (GIMP_PROGRESS (tool), FALSE,
+ _("Rendering Frame %d"), 1);
+
+ orig_image = gimp_item_get_image (GIMP_ITEM (tool->drawable));
+
+ image = gimp_create_image (orig_image->gimp,
+ gimp_item_get_width (GIMP_ITEM (tool->drawable)),
+ gimp_item_get_height (GIMP_ITEM (tool->drawable)),
+ gimp_drawable_get_base_type (tool->drawable),
+ gimp_drawable_get_precision (tool->drawable),
+ TRUE);
+
+ /* the first frame is always the unwarped image */
+ layer = GIMP_LAYER (gimp_item_convert (GIMP_ITEM (tool->drawable), image,
+ GIMP_TYPE_LAYER));
+ gimp_object_take_name (GIMP_OBJECT (layer),
+ g_strdup_printf (_("Frame %d"), 1));
+
+ gimp_item_set_offset (GIMP_ITEM (layer), 0, 0);
+ gimp_item_set_visible (GIMP_ITEM (layer), TRUE, FALSE);
+ gimp_layer_set_mode (layer, gimp_image_get_default_new_layer_mode (image),
+ FALSE);
+ gimp_layer_set_opacity (layer, GIMP_OPACITY_OPAQUE, FALSE);
+ gimp_image_add_layer (image, layer, NULL, 0, FALSE);
+
+ first_layer = layer;
+
+ scale_node = gegl_node_new_child (NULL,
+ "operation", "gimp:scalar-multiply",
+ "n-components", 2,
+ NULL);
+ gimp_warp_tool_add_op (wt, scale_node);
+
+ progress = gimp_sub_progress_new (GIMP_PROGRESS (tool));
+
+ for (i = 1; i < options->n_animation_frames; i++)
+ {
+ gimp_progress_set_text (GIMP_PROGRESS (tool),
+ _("Rendering Frame %d"), i + 1);
+
+ gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (progress),
+ i, options->n_animation_frames);
+
+ layer = GIMP_LAYER (gimp_item_duplicate (GIMP_ITEM (first_layer),
+ GIMP_TYPE_LAYER));
+ gimp_object_take_name (GIMP_OBJECT (layer),
+ g_strdup_printf (_("Frame %d"), i + 1));
+
+ gegl_node_set (scale_node,
+ "factor", (gdouble) i /
+ (gdouble) (options->n_animation_frames - 1),
+ NULL);
+
+ gimp_gegl_apply_operation (gimp_drawable_get_buffer (GIMP_DRAWABLE (first_layer)),
+ progress,
+ _("Frame"),
+ wt->graph,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, FALSE);
+
+ gimp_image_add_layer (image, layer, NULL, 0, FALSE);
+ }
+
+ g_object_unref (progress);
+
+ gimp_warp_tool_remove_op (wt, scale_node);
+
+ gimp_progress_end (GIMP_PROGRESS (tool));
+
+ /* recreate the image map */
+ gimp_warp_tool_create_filter (wt, tool->drawable);
+ gimp_warp_tool_update_stroke (wt, NULL);
+
+ widget = GTK_WIDGET (gimp_display_get_shell (tool->display));
+ gimp_create_display (orig_image->gimp, image, GIMP_UNIT_PIXEL, 1.0,
+ G_OBJECT (gtk_widget_get_screen (widget)),
+ gimp_widget_get_monitor (widget));
+ g_object_unref (image);
+}
diff --git a/app/tools/gimpwarptool.h b/app/tools/gimpwarptool.h
new file mode 100644
index 0000000..ad2e390
--- /dev/null
+++ b/app/tools/gimpwarptool.h
@@ -0,0 +1,78 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpwarptool.h
+ * Copyright (C) 2011 Michael Muré <batolettre@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_WARP_TOOL_H__
+#define __GIMP_WARP_TOOL_H__
+
+
+#include "gimpdrawtool.h"
+
+
+#define GIMP_TYPE_WARP_TOOL (gimp_warp_tool_get_type ())
+#define GIMP_WARP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_WARP_TOOL, GimpWarpTool))
+#define GIMP_WARP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_WARP_TOOL, GimpWarpToolClass))
+#define GIMP_IS_WARP_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_WARP_TOOL))
+#define GIMP_IS_WARP_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_WARP_TOOL))
+#define GIMP_WARP_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_WARP_TOOL, GimpWarpToolClass))
+
+#define GIMP_WARP_TOOL_GET_OPTIONS(t) (GIMP_WARP_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpWarpTool GimpWarpTool;
+typedef struct _GimpWarpToolClass GimpWarpToolClass;
+
+struct _GimpWarpTool
+{
+ GimpDrawTool parent_instance;
+
+ gboolean show_cursor;
+ gboolean draw_brush;
+ gboolean snap_brush;
+
+ GimpVector2 cursor_pos; /* Hold the cursor position */
+
+ GeglBuffer *coords_buffer; /* Buffer where coordinates are stored */
+
+ GeglNode *graph; /* Top level GeglNode */
+ GeglNode *render_node; /* Node to render the transformation */
+
+ GeglPath *current_stroke;
+ guint stroke_timer;
+
+ GimpVector2 last_pos;
+ gdouble total_dist;
+
+ GimpDrawableFilter *filter;
+
+ GList *redo_stack;
+};
+
+struct _GimpWarpToolClass
+{
+ GimpDrawToolClass parent_class;
+};
+
+
+void gimp_warp_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_warp_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_WARP_TOOL_H__ */
diff --git a/app/tools/tool_manager.c b/app/tools/tool_manager.c
new file mode 100644
index 0000000..3b8bc88
--- /dev/null
+++ b/app/tools/tool_manager.c
@@ -0,0 +1,966 @@
+/* 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 "libgimpconfig/gimpconfig.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimplist.h"
+#include "core/gimpimage.h"
+#include "core/gimptoolgroup.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimptooloptions.h"
+#include "core/gimptoolpreset.h"
+
+#include "display/gimpdisplay.h"
+
+#include "widgets/gimpcairo-wilber.h"
+
+#include "gimptool.h"
+#include "gimptoolcontrol.h"
+#include "tool_manager.h"
+
+
+typedef struct _GimpToolManager GimpToolManager;
+
+struct _GimpToolManager
+{
+ Gimp *gimp;
+
+ GimpTool *active_tool;
+ GSList *tool_stack;
+
+ GimpToolGroup *active_tool_group;
+
+ GQuark image_clean_handler_id;
+ GQuark image_dirty_handler_id;
+ GQuark image_saving_handler_id;
+};
+
+
+/* local function prototypes */
+
+static void tool_manager_set (Gimp *gimp,
+ GimpToolManager *tool_manager);
+static GimpToolManager * tool_manager_get (Gimp *gimp);
+
+static void tool_manager_select_tool (GimpToolManager *tool_manager,
+ GimpTool *tool);
+
+static void tool_manager_set_active_tool_group (GimpToolManager *tool_manager,
+ GimpToolGroup *tool_group);
+
+static void tool_manager_tool_changed (GimpContext *user_context,
+ GimpToolInfo *tool_info,
+ GimpToolManager *tool_manager);
+static void tool_manager_preset_changed (GimpContext *user_context,
+ GimpToolPreset *preset,
+ GimpToolManager *tool_manager);
+static void tool_manager_image_clean_dirty (GimpImage *image,
+ GimpDirtyMask dirty_mask,
+ GimpToolManager *tool_manager);
+static void tool_manager_image_saving (GimpImage *image,
+ GimpToolManager *tool_manager);
+static void tool_manager_tool_ancestry_changed (GimpToolInfo *tool_info,
+ GimpToolManager *tool_manager);
+static void tool_manager_group_active_tool_changed (GimpToolGroup *tool_group,
+ GimpToolManager *tool_manager);
+
+static void tool_manager_cast_spell (GimpToolInfo *tool_info);
+
+
+/* public functions */
+
+void
+tool_manager_init (Gimp *gimp)
+{
+ GimpToolManager *tool_manager;
+ GimpContext *user_context;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = g_slice_new0 (GimpToolManager);
+
+ tool_manager->gimp = gimp;
+ tool_manager->active_tool = NULL;
+ tool_manager->tool_stack = NULL;
+ tool_manager->active_tool_group = NULL;
+ tool_manager->image_clean_handler_id = 0;
+ tool_manager->image_dirty_handler_id = 0;
+ tool_manager->image_saving_handler_id = 0;
+
+ tool_manager_set (gimp, tool_manager);
+
+ tool_manager->image_clean_handler_id =
+ gimp_container_add_handler (gimp->images, "clean",
+ G_CALLBACK (tool_manager_image_clean_dirty),
+ tool_manager);
+
+ tool_manager->image_dirty_handler_id =
+ gimp_container_add_handler (gimp->images, "dirty",
+ G_CALLBACK (tool_manager_image_clean_dirty),
+ tool_manager);
+
+ tool_manager->image_saving_handler_id =
+ gimp_container_add_handler (gimp->images, "saving",
+ G_CALLBACK (tool_manager_image_saving),
+ tool_manager);
+
+ user_context = gimp_get_user_context (gimp);
+
+ g_signal_connect (user_context, "tool-changed",
+ G_CALLBACK (tool_manager_tool_changed),
+ tool_manager);
+ g_signal_connect (user_context, "tool-preset-changed",
+ G_CALLBACK (tool_manager_preset_changed),
+ tool_manager);
+
+ tool_manager_tool_changed (user_context,
+ gimp_context_get_tool (user_context),
+ tool_manager);
+}
+
+void
+tool_manager_exit (Gimp *gimp)
+{
+ GimpToolManager *tool_manager;
+ GimpContext *user_context;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+ tool_manager_set (gimp, NULL);
+
+ user_context = gimp_get_user_context (gimp);
+
+ g_signal_handlers_disconnect_by_func (user_context,
+ tool_manager_tool_changed,
+ tool_manager);
+ g_signal_handlers_disconnect_by_func (user_context,
+ tool_manager_preset_changed,
+ tool_manager);
+
+ gimp_container_remove_handler (gimp->images,
+ tool_manager->image_clean_handler_id);
+ gimp_container_remove_handler (gimp->images,
+ tool_manager->image_dirty_handler_id);
+ gimp_container_remove_handler (gimp->images,
+ tool_manager->image_saving_handler_id);
+
+ if (tool_manager->active_tool)
+ {
+ g_signal_handlers_disconnect_by_func (
+ tool_manager->active_tool->tool_info,
+ tool_manager_tool_ancestry_changed,
+ tool_manager);
+
+ g_clear_object (&tool_manager->active_tool);
+ }
+
+ tool_manager_set_active_tool_group (tool_manager, NULL);
+
+ g_slice_free (GimpToolManager, tool_manager);
+}
+
+GimpTool *
+tool_manager_get_active (Gimp *gimp)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ tool_manager = tool_manager_get (gimp);
+
+ return tool_manager->active_tool;
+}
+
+void
+tool_manager_push_tool (Gimp *gimp,
+ GimpTool *tool)
+{
+ GimpToolManager *tool_manager;
+ GimpDisplay *focus_display = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_TOOL (tool));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ focus_display = tool_manager->active_tool->focus_display;
+
+ tool_manager->tool_stack = g_slist_prepend (tool_manager->tool_stack,
+ tool_manager->active_tool);
+
+ g_object_ref (tool_manager->tool_stack->data);
+ }
+
+ tool_manager_select_tool (tool_manager, tool);
+
+ if (focus_display)
+ tool_manager_focus_display_active (gimp, focus_display);
+}
+
+void
+tool_manager_pop_tool (Gimp *gimp)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->tool_stack)
+ {
+ GimpTool *tool = tool_manager->tool_stack->data;
+
+ tool_manager->tool_stack = g_slist_remove (tool_manager->tool_stack,
+ tool);
+
+ tool_manager_select_tool (tool_manager, tool);
+
+ g_object_unref (tool);
+ }
+}
+
+gboolean
+tool_manager_initialize_active (Gimp *gimp,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ GimpTool *tool = tool_manager->active_tool;
+
+ if (gimp_tool_initialize (tool, display))
+ {
+ GimpImage *image = gimp_display_get_image (display);
+
+ tool->drawable = gimp_image_get_active_drawable (image);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void
+tool_manager_control_active (Gimp *gimp,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ GimpTool *tool = tool_manager->active_tool;
+
+ if (display && gimp_tool_has_display (tool, display))
+ {
+ gimp_tool_control (tool, action, display);
+ }
+ else if (action == GIMP_TOOL_ACTION_HALT)
+ {
+ if (gimp_tool_control_is_active (tool->control))
+ gimp_tool_control_halt (tool->control);
+ }
+ }
+}
+
+void
+tool_manager_button_press_active (Gimp *gimp,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ gimp_tool_button_press (tool_manager->active_tool,
+ coords, time, state, press_type,
+ display);
+ }
+}
+
+void
+tool_manager_button_release_active (Gimp *gimp,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ gimp_tool_button_release (tool_manager->active_tool,
+ coords, time, state,
+ display);
+ }
+}
+
+void
+tool_manager_motion_active (Gimp *gimp,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ gimp_tool_motion (tool_manager->active_tool,
+ coords, time, state,
+ display);
+ }
+}
+
+gboolean
+tool_manager_key_press_active (Gimp *gimp,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_key_press (tool_manager->active_tool,
+ kevent,
+ display);
+ }
+
+ return FALSE;
+}
+
+gboolean
+tool_manager_key_release_active (Gimp *gimp,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_key_release (tool_manager->active_tool,
+ kevent,
+ display);
+ }
+
+ return FALSE;
+}
+
+void
+tool_manager_focus_display_active (Gimp *gimp,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool &&
+ ! gimp_tool_control_is_active (tool_manager->active_tool->control))
+ {
+ gimp_tool_set_focus_display (tool_manager->active_tool,
+ display);
+ }
+}
+
+void
+tool_manager_modifier_state_active (Gimp *gimp,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool &&
+ ! gimp_tool_control_is_active (tool_manager->active_tool->control))
+ {
+ gimp_tool_set_modifier_state (tool_manager->active_tool,
+ state,
+ display);
+ }
+}
+
+void
+tool_manager_active_modifier_state_active (Gimp *gimp,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ gimp_tool_set_active_modifier_state (tool_manager->active_tool,
+ state,
+ display);
+ }
+}
+
+void
+tool_manager_oper_update_active (Gimp *gimp,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool &&
+ ! gimp_tool_control_is_active (tool_manager->active_tool->control))
+ {
+ gimp_tool_oper_update (tool_manager->active_tool,
+ coords, state, proximity,
+ display);
+ }
+}
+
+void
+tool_manager_cursor_update_active (Gimp *gimp,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool &&
+ ! gimp_tool_control_is_active (tool_manager->active_tool->control))
+ {
+ gimp_tool_cursor_update (tool_manager->active_tool,
+ coords, state,
+ display);
+ }
+}
+
+const gchar *
+tool_manager_can_undo_active (Gimp *gimp,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_can_undo (tool_manager->active_tool,
+ display);
+ }
+
+ return NULL;
+}
+
+const gchar *
+tool_manager_can_redo_active (Gimp *gimp,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_can_redo (tool_manager->active_tool,
+ display);
+ }
+
+ return NULL;
+}
+
+gboolean
+tool_manager_undo_active (Gimp *gimp,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_undo (tool_manager->active_tool,
+ display);
+ }
+
+ return FALSE;
+}
+
+gboolean
+tool_manager_redo_active (Gimp *gimp,
+ GimpDisplay *display)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_redo (tool_manager->active_tool,
+ display);
+ }
+
+ return FALSE;
+}
+
+GimpUIManager *
+tool_manager_get_popup_active (Gimp *gimp,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path)
+{
+ GimpToolManager *tool_manager;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ tool_manager = tool_manager_get (gimp);
+
+ if (tool_manager->active_tool)
+ {
+ return gimp_tool_get_popup (tool_manager->active_tool,
+ coords, state,
+ display,
+ ui_path);
+ }
+
+ return NULL;
+}
+
+
+/* private functions */
+
+static GQuark tool_manager_quark = 0;
+
+static void
+tool_manager_set (Gimp *gimp,
+ GimpToolManager *tool_manager)
+{
+ if (! tool_manager_quark)
+ tool_manager_quark = g_quark_from_static_string ("gimp-tool-manager");
+
+ g_object_set_qdata (G_OBJECT (gimp), tool_manager_quark, tool_manager);
+}
+
+static GimpToolManager *
+tool_manager_get (Gimp *gimp)
+{
+ if (! tool_manager_quark)
+ tool_manager_quark = g_quark_from_static_string ("gimp-tool-manager");
+
+ return g_object_get_qdata (G_OBJECT (gimp), tool_manager_quark);
+}
+
+static void
+tool_manager_select_tool (GimpToolManager *tool_manager,
+ GimpTool *tool)
+{
+ Gimp *gimp = tool_manager->gimp;
+
+ /* reset the previously selected tool, but only if it is not only
+ * temporarily pushed to the tool stack
+ */
+ if (tool_manager->active_tool)
+ {
+ if (! tool_manager->tool_stack ||
+ tool_manager->active_tool != tool_manager->tool_stack->data)
+ {
+ GimpTool *active_tool = tool_manager->active_tool;
+ GimpDisplay *display;
+
+ /* NULL image returns any display (if there is any) */
+ display = gimp_tool_has_image (active_tool, NULL);
+
+ tool_manager_control_active (gimp, GIMP_TOOL_ACTION_HALT, display);
+ tool_manager_focus_display_active (gimp, NULL);
+ }
+ }
+
+ g_set_object (&tool_manager->active_tool, tool);
+}
+
+static void
+tool_manager_set_active_tool_group (GimpToolManager *tool_manager,
+ GimpToolGroup *tool_group)
+{
+ if (tool_group != tool_manager->active_tool_group)
+ {
+ if (tool_manager->active_tool_group)
+ {
+ g_signal_handlers_disconnect_by_func (
+ tool_manager->active_tool_group,
+ tool_manager_group_active_tool_changed,
+ tool_manager);
+ }
+
+ g_set_weak_pointer (&tool_manager->active_tool_group, tool_group);
+
+ if (tool_manager->active_tool_group)
+ {
+ g_signal_connect (
+ tool_manager->active_tool_group, "active-tool-changed",
+ G_CALLBACK (tool_manager_group_active_tool_changed),
+ tool_manager);
+ }
+ }
+}
+
+static void
+tool_manager_tool_changed (GimpContext *user_context,
+ GimpToolInfo *tool_info,
+ GimpToolManager *tool_manager)
+{
+ GimpTool *new_tool = NULL;
+
+ if (! tool_info)
+ return;
+
+ if (! g_type_is_a (tool_info->tool_type, GIMP_TYPE_TOOL))
+ {
+ g_warning ("%s: tool_info->tool_type is no GimpTool subclass",
+ G_STRFUNC);
+ return;
+ }
+
+ /* FIXME: gimp_busy HACK */
+ if (user_context->gimp->busy)
+ {
+ /* there may be contexts waiting for the user_context's "tool-changed"
+ * signal, so stop emitting it.
+ */
+ g_signal_stop_emission_by_name (user_context, "tool-changed");
+
+ if (G_TYPE_FROM_INSTANCE (tool_manager->active_tool) !=
+ tool_info->tool_type)
+ {
+ g_signal_handlers_block_by_func (user_context,
+ tool_manager_tool_changed,
+ tool_manager);
+
+ /* explicitly set the current tool */
+ gimp_context_set_tool (user_context,
+ tool_manager->active_tool->tool_info);
+
+ g_signal_handlers_unblock_by_func (user_context,
+ tool_manager_tool_changed,
+ tool_manager);
+ }
+
+ return;
+ }
+
+ g_return_if_fail (tool_manager->tool_stack == NULL);
+
+ if (tool_manager->active_tool)
+ {
+ GimpTool *active_tool = tool_manager->active_tool;
+ GimpDisplay *display;
+
+ /* NULL image returns any display (if there is any) */
+ display = gimp_tool_has_image (active_tool, NULL);
+
+ /* commit the old tool's operation before creating the new tool
+ * because creating a tool might mess with the old tool's
+ * options (old and new tool might be the same)
+ */
+ if (display)
+ tool_manager_control_active (user_context->gimp, GIMP_TOOL_ACTION_COMMIT,
+ display);
+
+ g_signal_handlers_disconnect_by_func (active_tool->tool_info,
+ tool_manager_tool_ancestry_changed,
+ tool_manager);
+ }
+
+ g_signal_connect (tool_info, "ancestry-changed",
+ G_CALLBACK (tool_manager_tool_ancestry_changed),
+ tool_manager);
+
+ tool_manager_tool_ancestry_changed (tool_info, tool_manager);
+
+ new_tool = g_object_new (tool_info->tool_type,
+ "tool-info", tool_info,
+ NULL);
+
+ tool_manager_select_tool (tool_manager, new_tool);
+
+ g_object_unref (new_tool);
+
+ /* ??? */
+ tool_manager_cast_spell (tool_info);
+}
+
+static void
+tool_manager_copy_tool_options (GObject *src,
+ GObject *dest)
+{
+ GList *diff;
+
+ diff = gimp_config_diff (src, dest, G_PARAM_READWRITE);
+
+ if (diff)
+ {
+ GList *list;
+
+ g_object_freeze_notify (dest);
+
+ for (list = diff; list; list = list->next)
+ {
+ GParamSpec *prop_spec = list->data;
+
+ if (g_type_is_a (prop_spec->owner_type, GIMP_TYPE_TOOL_OPTIONS) &&
+ ! (prop_spec->flags & G_PARAM_CONSTRUCT_ONLY))
+ {
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, prop_spec->value_type);
+
+ g_object_get_property (src, prop_spec->name, &value);
+ g_object_set_property (dest, prop_spec->name, &value);
+
+ g_value_unset (&value);
+ }
+ }
+
+ g_object_thaw_notify (dest);
+
+ g_list_free (diff);
+ }
+}
+
+static void
+tool_manager_preset_changed (GimpContext *user_context,
+ GimpToolPreset *preset,
+ GimpToolManager *tool_manager)
+{
+ GimpToolInfo *preset_tool;
+
+ if (! preset || user_context->gimp->busy)
+ return;
+
+ preset_tool = gimp_context_get_tool (GIMP_CONTEXT (preset->tool_options));
+
+ /* first, select the preset's tool, even if it's already the active
+ * tool
+ */
+ gimp_context_set_tool (user_context, preset_tool);
+
+ /* then, copy the context properties the preset remembers, possibly
+ * changing some tool options due to the "link brush stuff to brush
+ * defaults" settings in gimptooloptions.c
+ */
+ gimp_context_copy_properties (GIMP_CONTEXT (preset->tool_options),
+ user_context,
+ gimp_tool_preset_get_prop_mask (preset));
+
+ /* finally, copy all tool options properties, overwriting any
+ * changes resulting from setting the context properties above, we
+ * really want exactly what is in the preset and nothing else
+ */
+ tool_manager_copy_tool_options (G_OBJECT (preset->tool_options),
+ G_OBJECT (preset_tool->tool_options));
+}
+
+static void
+tool_manager_image_clean_dirty (GimpImage *image,
+ GimpDirtyMask dirty_mask,
+ GimpToolManager *tool_manager)
+{
+ GimpTool *tool = tool_manager->active_tool;
+
+ if (tool &&
+ ! gimp_tool_control_get_preserve (tool->control) &&
+ (gimp_tool_control_get_dirty_mask (tool->control) & dirty_mask))
+ {
+ GimpDisplay *display = gimp_tool_has_image (tool, image);
+
+ if (display)
+ {
+ tool_manager_control_active (
+ image->gimp,
+ gimp_tool_control_get_dirty_action (tool->control),
+ display);
+ }
+ }
+}
+
+static void
+tool_manager_image_saving (GimpImage *image,
+ GimpToolManager *tool_manager)
+{
+ GimpTool *tool = tool_manager->active_tool;
+
+ if (tool &&
+ ! gimp_tool_control_get_preserve (tool->control))
+ {
+ GimpDisplay *display = gimp_tool_has_image (tool, image);
+
+ if (display)
+ tool_manager_control_active (image->gimp, GIMP_TOOL_ACTION_COMMIT,
+ display);
+ }
+}
+
+static void
+tool_manager_tool_ancestry_changed (GimpToolInfo *tool_info,
+ GimpToolManager *tool_manager)
+{
+ GimpViewable *parent;
+
+ parent = gimp_viewable_get_parent (GIMP_VIEWABLE (tool_info));
+
+ if (parent)
+ {
+ gimp_tool_group_set_active_tool_info (GIMP_TOOL_GROUP (parent),
+ tool_info);
+ }
+
+ tool_manager_set_active_tool_group (tool_manager, GIMP_TOOL_GROUP (parent));
+}
+
+static void
+tool_manager_group_active_tool_changed (GimpToolGroup *tool_group,
+ GimpToolManager *tool_manager)
+{
+ gimp_context_set_tool (tool_manager->gimp->user_context,
+ gimp_tool_group_get_active_tool_info (tool_group));
+}
+
+static void
+tool_manager_cast_spell (GimpToolInfo *tool_info)
+{
+ typedef struct
+ {
+ const gchar *sequence;
+ GCallback func;
+ } Spell;
+
+ static const Spell spells[] =
+ {
+ { .sequence = "gimp-warp-tool\0"
+ "gimp-iscissors-tool\0"
+ "gimp-gradient-tool\0"
+ "gimp-vector-tool\0"
+ "gimp-ellipse-select-tool\0"
+ "gimp-rect-select-tool\0",
+ .func = gimp_cairo_wilber_toggle_pointer_eyes
+ }
+ };
+
+ static const gchar *spell_progress[G_N_ELEMENTS (spells)];
+ const gchar *tool_name;
+ gint i;
+
+ tool_name = gimp_object_get_name (GIMP_OBJECT (tool_info));
+
+ for (i = 0; i < G_N_ELEMENTS (spells); i++)
+ {
+ if (! spell_progress[i])
+ spell_progress[i] = spells[i].sequence;
+
+ while (spell_progress[i])
+ {
+ if (! strcmp (tool_name, spell_progress[i]))
+ {
+ spell_progress[i] += strlen (spell_progress[i]) + 1;
+
+ if (! *spell_progress[i])
+ {
+ spell_progress[i] = NULL;
+
+ spells[i].func ();
+ }
+
+ break;
+ }
+ else
+ {
+ if (spell_progress[i] == spells[i].sequence)
+ spell_progress[i] = NULL;
+ else
+ spell_progress[i] = spells[i].sequence;
+ }
+ }
+ }
+}
diff --git a/app/tools/tool_manager.h b/app/tools/tool_manager.h
new file mode 100644
index 0000000..2f406a1
--- /dev/null
+++ b/app/tools/tool_manager.h
@@ -0,0 +1,96 @@
+/* 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 __TOOL_MANAGER_H__
+#define __TOOL_MANAGER_H__
+
+
+void tool_manager_init (Gimp *gimp);
+void tool_manager_exit (Gimp *gimp);
+
+GimpTool * tool_manager_get_active (Gimp *gimp);
+
+void tool_manager_push_tool (Gimp *gimp,
+ GimpTool *tool);
+void tool_manager_pop_tool (Gimp *gimp);
+
+
+gboolean tool_manager_initialize_active (Gimp *gimp,
+ GimpDisplay *display);
+void tool_manager_control_active (Gimp *gimp,
+ GimpToolAction action,
+ GimpDisplay *display);
+void tool_manager_button_press_active (Gimp *gimp,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+void tool_manager_button_release_active (Gimp *gimp,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+void tool_manager_motion_active (Gimp *gimp,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+gboolean tool_manager_key_press_active (Gimp *gimp,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+gboolean tool_manager_key_release_active (Gimp *gimp,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+
+void tool_manager_focus_display_active (Gimp *gimp,
+ GimpDisplay *display);
+void tool_manager_modifier_state_active (Gimp *gimp,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+void tool_manager_active_modifier_state_active (Gimp *gimp,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+void tool_manager_oper_update_active (Gimp *gimp,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+void tool_manager_cursor_update_active (Gimp *gimp,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+const gchar * tool_manager_can_undo_active (Gimp *gimp,
+ GimpDisplay *display);
+const gchar * tool_manager_can_redo_active (Gimp *gimp,
+ GimpDisplay *display);
+gboolean tool_manager_undo_active (Gimp *gimp,
+ GimpDisplay *display);
+gboolean tool_manager_redo_active (Gimp *gimp,
+ GimpDisplay *display);
+
+GimpUIManager * tool_manager_get_popup_active (Gimp *gimp,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display,
+ const gchar **ui_path);
+
+
+#endif /* __TOOL_MANAGER_H__ */
diff --git a/app/tools/tools-enums.c b/app/tools/tools-enums.c
new file mode 100644
index 0000000..5e637e9
--- /dev/null
+++ b/app/tools/tools-enums.c
@@ -0,0 +1,342 @@
+
+/* Generated data (by gimp-mkenums) */
+
+#include "config.h"
+#include <gio/gio.h>
+#include "libgimpbase/gimpbase.h"
+#include "core/core-enums.h"
+#include "tools-enums.h"
+#include "gimp-intl.h"
+
+/* enumerations from "tools-enums.h" */
+GType
+gimp_bucket_fill_area_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_BUCKET_FILL_SELECTION, "GIMP_BUCKET_FILL_SELECTION", "selection" },
+ { GIMP_BUCKET_FILL_SIMILAR_COLORS, "GIMP_BUCKET_FILL_SIMILAR_COLORS", "similar-colors" },
+ { GIMP_BUCKET_FILL_LINE_ART, "GIMP_BUCKET_FILL_LINE_ART", "line-art" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_BUCKET_FILL_SELECTION, NC_("bucket-fill-area", "Fill whole selection"), NULL },
+ { GIMP_BUCKET_FILL_SIMILAR_COLORS, NC_("bucket-fill-area", "Fill similar colors"), NULL },
+ { GIMP_BUCKET_FILL_LINE_ART, NC_("bucket-fill-area", "Fill by line art detection"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpBucketFillArea", values);
+ gimp_type_set_translation_context (type, "bucket-fill-area");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_line_art_source_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_LINE_ART_SOURCE_SAMPLE_MERGED, "GIMP_LINE_ART_SOURCE_SAMPLE_MERGED", "sample-merged" },
+ { GIMP_LINE_ART_SOURCE_ACTIVE_LAYER, "GIMP_LINE_ART_SOURCE_ACTIVE_LAYER", "active-layer" },
+ { GIMP_LINE_ART_SOURCE_LOWER_LAYER, "GIMP_LINE_ART_SOURCE_LOWER_LAYER", "lower-layer" },
+ { GIMP_LINE_ART_SOURCE_UPPER_LAYER, "GIMP_LINE_ART_SOURCE_UPPER_LAYER", "upper-layer" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_LINE_ART_SOURCE_SAMPLE_MERGED, NC_("line-art-source", "All visible layers"), NULL },
+ { GIMP_LINE_ART_SOURCE_ACTIVE_LAYER, NC_("line-art-source", "Active layer"), NULL },
+ { GIMP_LINE_ART_SOURCE_LOWER_LAYER, NC_("line-art-source", "Layer below the active one"), NULL },
+ { GIMP_LINE_ART_SOURCE_UPPER_LAYER, NC_("line-art-source", "Layer above the active one"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpLineArtSource", values);
+ gimp_type_set_translation_context (type, "line-art-source");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_rect_select_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_RECT_SELECT_MODE_FREE, "GIMP_RECT_SELECT_MODE_FREE", "free" },
+ { GIMP_RECT_SELECT_MODE_FIXED_SIZE, "GIMP_RECT_SELECT_MODE_FIXED_SIZE", "fixed-size" },
+ { GIMP_RECT_SELECT_MODE_FIXED_RATIO, "GIMP_RECT_SELECT_MODE_FIXED_RATIO", "fixed-ratio" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_RECT_SELECT_MODE_FREE, NC_("rect-select-mode", "Free select"), NULL },
+ { GIMP_RECT_SELECT_MODE_FIXED_SIZE, NC_("rect-select-mode", "Fixed size"), NULL },
+ { GIMP_RECT_SELECT_MODE_FIXED_RATIO, NC_("rect-select-mode", "Fixed aspect ratio"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpRectSelectMode", values);
+ gimp_type_set_translation_context (type, "rect-select-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_transform_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_TRANSFORM_TYPE_LAYER, "GIMP_TRANSFORM_TYPE_LAYER", "layer" },
+ { GIMP_TRANSFORM_TYPE_SELECTION, "GIMP_TRANSFORM_TYPE_SELECTION", "selection" },
+ { GIMP_TRANSFORM_TYPE_PATH, "GIMP_TRANSFORM_TYPE_PATH", "path" },
+ { GIMP_TRANSFORM_TYPE_IMAGE, "GIMP_TRANSFORM_TYPE_IMAGE", "image" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_TRANSFORM_TYPE_LAYER, NC_("transform-type", "Layer"), NULL },
+ { GIMP_TRANSFORM_TYPE_SELECTION, NC_("transform-type", "Selection"), NULL },
+ { GIMP_TRANSFORM_TYPE_PATH, NC_("transform-type", "Path"), NULL },
+ { GIMP_TRANSFORM_TYPE_IMAGE, NC_("transform-type", "Image"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpTransformType", values);
+ gimp_type_set_translation_context (type, "transform-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_tool_action_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_TOOL_ACTION_PAUSE, "GIMP_TOOL_ACTION_PAUSE", "pause" },
+ { GIMP_TOOL_ACTION_RESUME, "GIMP_TOOL_ACTION_RESUME", "resume" },
+ { GIMP_TOOL_ACTION_HALT, "GIMP_TOOL_ACTION_HALT", "halt" },
+ { GIMP_TOOL_ACTION_COMMIT, "GIMP_TOOL_ACTION_COMMIT", "commit" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_TOOL_ACTION_PAUSE, "GIMP_TOOL_ACTION_PAUSE", NULL },
+ { GIMP_TOOL_ACTION_RESUME, "GIMP_TOOL_ACTION_RESUME", NULL },
+ { GIMP_TOOL_ACTION_HALT, "GIMP_TOOL_ACTION_HALT", NULL },
+ { GIMP_TOOL_ACTION_COMMIT, "GIMP_TOOL_ACTION_COMMIT", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpToolAction", values);
+ gimp_type_set_translation_context (type, "tool-action");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_tool_active_modifiers_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_TOOL_ACTIVE_MODIFIERS_OFF, "GIMP_TOOL_ACTIVE_MODIFIERS_OFF", "off" },
+ { GIMP_TOOL_ACTIVE_MODIFIERS_SAME, "GIMP_TOOL_ACTIVE_MODIFIERS_SAME", "same" },
+ { GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE, "GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE", "separate" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_TOOL_ACTIVE_MODIFIERS_OFF, "GIMP_TOOL_ACTIVE_MODIFIERS_OFF", NULL },
+ { GIMP_TOOL_ACTIVE_MODIFIERS_SAME, "GIMP_TOOL_ACTIVE_MODIFIERS_SAME", NULL },
+ { GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE, "GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpToolActiveModifiers", values);
+ gimp_type_set_translation_context (type, "tool-active-modifiers");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_matting_draw_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_MATTING_DRAW_MODE_FOREGROUND, "GIMP_MATTING_DRAW_MODE_FOREGROUND", "foreground" },
+ { GIMP_MATTING_DRAW_MODE_BACKGROUND, "GIMP_MATTING_DRAW_MODE_BACKGROUND", "background" },
+ { GIMP_MATTING_DRAW_MODE_UNKNOWN, "GIMP_MATTING_DRAW_MODE_UNKNOWN", "unknown" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_MATTING_DRAW_MODE_FOREGROUND, NC_("matting-draw-mode", "Draw foreground"), NULL },
+ { GIMP_MATTING_DRAW_MODE_BACKGROUND, NC_("matting-draw-mode", "Draw background"), NULL },
+ { GIMP_MATTING_DRAW_MODE_UNKNOWN, NC_("matting-draw-mode", "Draw unknown"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpMattingDrawMode", values);
+ gimp_type_set_translation_context (type, "matting-draw-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_matting_preview_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_MATTING_PREVIEW_MODE_ON_COLOR, "GIMP_MATTING_PREVIEW_MODE_ON_COLOR", "on-color" },
+ { GIMP_MATTING_PREVIEW_MODE_GRAYSCALE, "GIMP_MATTING_PREVIEW_MODE_GRAYSCALE", "grayscale" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_MATTING_PREVIEW_MODE_ON_COLOR, NC_("matting-preview-mode", "Color"), NULL },
+ { GIMP_MATTING_PREVIEW_MODE_GRAYSCALE, NC_("matting-preview-mode", "Grayscale"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpMattingPreviewMode", values);
+ gimp_type_set_translation_context (type, "matting-preview-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_transform_3d_lens_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH, "GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH", "focal-length" },
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE, "GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE", "fov-image" },
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM, "GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM", "fov-item" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH, NC_("3-dtrasnform-lens-mode", "Focal length"), NULL },
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE, NC_("3-dtrasnform-lens-mode", "Field of view (relative to image)"), NULL },
+ /* Translators: this is an abbreviated version of "Field of view (relative to image)".
+ Keep it short. */
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE, NC_("3-dtrasnform-lens-mode", "FOV (image)"), NULL },
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM, NC_("3-dtrasnform-lens-mode", "Field of view (relative to item)"), NULL },
+ /* Translators: this is an abbreviated version of "Field of view (relative to item)".
+ Keep it short. */
+ { GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM, NC_("3-dtrasnform-lens-mode", "FOV (item)"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("Gimp3DTrasnformLensMode", values);
+ gimp_type_set_translation_context (type, "3-dtrasnform-lens-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_warp_behavior_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_WARP_BEHAVIOR_MOVE, "GIMP_WARP_BEHAVIOR_MOVE", "move" },
+ { GIMP_WARP_BEHAVIOR_GROW, "GIMP_WARP_BEHAVIOR_GROW", "grow" },
+ { GIMP_WARP_BEHAVIOR_SHRINK, "GIMP_WARP_BEHAVIOR_SHRINK", "shrink" },
+ { GIMP_WARP_BEHAVIOR_SWIRL_CW, "GIMP_WARP_BEHAVIOR_SWIRL_CW", "swirl-cw" },
+ { GIMP_WARP_BEHAVIOR_SWIRL_CCW, "GIMP_WARP_BEHAVIOR_SWIRL_CCW", "swirl-ccw" },
+ { GIMP_WARP_BEHAVIOR_ERASE, "GIMP_WARP_BEHAVIOR_ERASE", "erase" },
+ { GIMP_WARP_BEHAVIOR_SMOOTH, "GIMP_WARP_BEHAVIOR_SMOOTH", "smooth" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_WARP_BEHAVIOR_MOVE, NC_("warp-behavior", "Move pixels"), NULL },
+ { GIMP_WARP_BEHAVIOR_GROW, NC_("warp-behavior", "Grow area"), NULL },
+ { GIMP_WARP_BEHAVIOR_SHRINK, NC_("warp-behavior", "Shrink area"), NULL },
+ { GIMP_WARP_BEHAVIOR_SWIRL_CW, NC_("warp-behavior", "Swirl clockwise"), NULL },
+ { GIMP_WARP_BEHAVIOR_SWIRL_CCW, NC_("warp-behavior", "Swirl counter-clockwise"), NULL },
+ { GIMP_WARP_BEHAVIOR_ERASE, NC_("warp-behavior", "Erase warping"), NULL },
+ { GIMP_WARP_BEHAVIOR_SMOOTH, NC_("warp-behavior", "Smooth warping"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpWarpBehavior", values);
+ gimp_type_set_translation_context (type, "warp-behavior");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+
+/* Generated data ends here */
+
diff --git a/app/tools/tools-enums.h b/app/tools/tools-enums.h
new file mode 100644
index 0000000..f994bc1
--- /dev/null
+++ b/app/tools/tools-enums.h
@@ -0,0 +1,203 @@
+/* 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 __TOOLS_ENUMS_H__
+#define __TOOLS_ENUMS_H__
+
+
+/*
+ * these enums are registered with the type system
+ */
+
+/**
+ * GimpBucketFillArea:
+ * @GIMP_BUCKET_FILL_SELECTION: Fill whole selection
+ * @GIMP_BUCKET_FILL_SIMILAR_COLORS: Fill similar colors
+ * @GIMP_BUCKET_FILL_LINE_ART: Fill by line art detection
+ *
+ * Bucket fill area.
+ */
+#define GIMP_TYPE_BUCKET_FILL_AREA (gimp_bucket_fill_area_get_type ())
+
+GType gimp_bucket_fill_area_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_BUCKET_FILL_SELECTION, /*< desc="Fill whole selection" >*/
+ GIMP_BUCKET_FILL_SIMILAR_COLORS, /*< desc="Fill similar colors" >*/
+ GIMP_BUCKET_FILL_LINE_ART /*< desc="Fill by line art detection" >*/
+} GimpBucketFillArea;
+
+
+/**
+ * GimpLineArtSource:
+ * @GIMP_LINE_ART_SOURCE_SAMPLE_MERGED: All visible layers
+ * @GIMP_LINE_ART_SOURCE_ACTIVE_LAYER: Active layer
+ * @GIMP_LINE_ART_SOURCE_LOWER_LAYER: Layer below the active one
+ * @GIMP_LINE_ART_SOURCE_UPPER_LAYER: Layer above the active one
+ *
+ * Bucket fill area.
+ */
+#define GIMP_TYPE_LINE_ART_SOURCE (gimp_line_art_source_get_type ())
+
+GType gimp_line_art_source_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_LINE_ART_SOURCE_SAMPLE_MERGED, /*< desc="All visible layers" >*/
+ GIMP_LINE_ART_SOURCE_ACTIVE_LAYER, /*< desc="Active layer" >*/
+ GIMP_LINE_ART_SOURCE_LOWER_LAYER, /*< desc="Layer below the active one" >*/
+ GIMP_LINE_ART_SOURCE_UPPER_LAYER /*< desc="Layer above the active one" >*/
+} GimpLineArtSource;
+
+
+#define GIMP_TYPE_RECT_SELECT_MODE (gimp_rect_select_mode_get_type ())
+
+GType gimp_rect_select_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_RECT_SELECT_MODE_FREE, /*< desc="Free select" >*/
+ GIMP_RECT_SELECT_MODE_FIXED_SIZE, /*< desc="Fixed size" >*/
+ GIMP_RECT_SELECT_MODE_FIXED_RATIO /*< desc="Fixed aspect ratio" >*/
+} GimpRectSelectMode;
+
+
+#define GIMP_TYPE_TRANSFORM_TYPE (gimp_transform_type_get_type ())
+
+GType gimp_transform_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_TRANSFORM_TYPE_LAYER, /*< desc="Layer" >*/
+ GIMP_TRANSFORM_TYPE_SELECTION, /*< desc="Selection" >*/
+ GIMP_TRANSFORM_TYPE_PATH, /*< desc="Path" >*/
+ GIMP_TRANSFORM_TYPE_IMAGE /*< desc="Image" >*/
+} GimpTransformType;
+
+
+#define GIMP_TYPE_TOOL_ACTION (gimp_tool_action_get_type ())
+
+GType gimp_tool_action_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_TOOL_ACTION_PAUSE,
+ GIMP_TOOL_ACTION_RESUME,
+ GIMP_TOOL_ACTION_HALT,
+ GIMP_TOOL_ACTION_COMMIT
+} GimpToolAction;
+
+
+#define GIMP_TYPE_TOOL_ACTIVE_MODIFIERS (gimp_tool_active_modifiers_get_type ())
+
+GType gimp_tool_active_modifiers_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_TOOL_ACTIVE_MODIFIERS_OFF,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SAME,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE,
+} GimpToolActiveModifiers;
+
+
+#define GIMP_TYPE_MATTING_DRAW_MODE (gimp_matting_draw_mode_get_type ())
+
+GType gimp_matting_draw_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_MATTING_DRAW_MODE_FOREGROUND, /*< desc="Draw foreground" >*/
+ GIMP_MATTING_DRAW_MODE_BACKGROUND, /*< desc="Draw background" >*/
+ GIMP_MATTING_DRAW_MODE_UNKNOWN, /*< desc="Draw unknown" >*/
+} GimpMattingDrawMode;
+
+
+#define GIMP_TYPE_MATTING_PREVIEW_MODE (gimp_matting_preview_mode_get_type ())
+
+GType gimp_matting_preview_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_MATTING_PREVIEW_MODE_ON_COLOR, /*< desc="Color" >*/
+ GIMP_MATTING_PREVIEW_MODE_GRAYSCALE, /*< desc="Grayscale" >*/
+} GimpMattingPreviewMode;
+
+
+#define GIMP_TYPE_TRANSFORM_3D_LENS_MODE (gimp_transform_3d_lens_mode_get_type ())
+
+GType gimp_transform_3d_lens_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< lowercase_name=gimp_transform_3d_lens_mode >*/
+{
+ GIMP_TRANSFORM_3D_LENS_MODE_FOCAL_LENGTH, /*< desc="Focal length" >*/
+ GIMP_TRANSFORM_3D_LENS_MODE_FOV_IMAGE, /*< desc="Field of view (relative to image)", abbrev="FOV (image)" >*/
+ GIMP_TRANSFORM_3D_LENS_MODE_FOV_ITEM, /*< desc="Field of view (relative to item)", abbrev="FOV (item)" >*/
+} Gimp3DTrasnformLensMode;
+
+
+#define GIMP_TYPE_WARP_BEHAVIOR (gimp_warp_behavior_get_type ())
+
+GType gimp_warp_behavior_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_WARP_BEHAVIOR_MOVE, /*< desc="Move pixels" >*/
+ GIMP_WARP_BEHAVIOR_GROW, /*< desc="Grow area" >*/
+ GIMP_WARP_BEHAVIOR_SHRINK, /*< desc="Shrink area" >*/
+ GIMP_WARP_BEHAVIOR_SWIRL_CW, /*< desc="Swirl clockwise" >*/
+ GIMP_WARP_BEHAVIOR_SWIRL_CCW, /*< desc="Swirl counter-clockwise" >*/
+ GIMP_WARP_BEHAVIOR_ERASE, /*< desc="Erase warping" >*/
+ GIMP_WARP_BEHAVIOR_SMOOTH /*< desc="Smooth warping" >*/
+} GimpWarpBehavior;
+
+
+/*
+ * non-registered enums; register them if needed
+ */
+
+typedef enum /*< skip >*/
+{
+ SELECTION_SELECT,
+ SELECTION_MOVE_MASK,
+ SELECTION_MOVE,
+ SELECTION_MOVE_COPY,
+ SELECTION_ANCHOR
+} SelectFunction;
+
+/* Modes of GimpEditSelectionTool */
+typedef enum /*< skip >*/
+{
+ GIMP_TRANSLATE_MODE_VECTORS,
+ GIMP_TRANSLATE_MODE_CHANNEL,
+ GIMP_TRANSLATE_MODE_LAYER_MASK,
+ GIMP_TRANSLATE_MODE_MASK,
+ GIMP_TRANSLATE_MODE_MASK_TO_LAYER,
+ GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER,
+ GIMP_TRANSLATE_MODE_LAYER,
+ GIMP_TRANSLATE_MODE_FLOATING_SEL
+} GimpTranslateMode;
+
+/* Motion event report modes */
+typedef enum /*< skip >*/
+{
+ GIMP_MOTION_MODE_EXACT,
+ GIMP_MOTION_MODE_COMPRESS
+} GimpMotionMode;
+
+
+#endif /* __TOOLS_ENUMS_H__ */
diff --git a/app/tools/tools-types.h b/app/tools/tools-types.h
new file mode 100644
index 0000000..ea115ba
--- /dev/null
+++ b/app/tools/tools-types.h
@@ -0,0 +1,68 @@
+/* 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 __TOOLS_TYPES_H__
+#define __TOOLS_TYPES_H__
+
+#include "paint/paint-types.h"
+#include "display/display-types.h"
+
+#include "tools/tools-enums.h"
+
+
+G_BEGIN_DECLS
+
+
+typedef struct _GimpTool GimpTool;
+typedef struct _GimpToolControl GimpToolControl;
+
+typedef struct _GimpBrushTool GimpBrushTool;
+typedef struct _GimpColorTool GimpColorTool;
+typedef struct _GimpDrawTool GimpDrawTool;
+typedef struct _GimpFilterTool GimpFilterTool;
+typedef struct _GimpGenericTransformTool GimpGenericTransformTool;
+typedef struct _GimpPaintTool GimpPaintTool;
+typedef struct _GimpTransformGridTool GimpTransformGridTool;
+typedef struct _GimpTransformTool GimpTransformTool;
+
+typedef struct _GimpColorOptions GimpColorOptions;
+typedef struct _GimpFilterOptions GimpFilterOptions;
+
+
+/* functions */
+
+typedef void (* GimpToolRegisterCallback) (GType tool_type,
+ GType tool_option_type,
+ GimpToolOptionsGUIFunc options_gui_func,
+ GimpContextPropMask context_props,
+ const gchar *identifier,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *menu_path,
+ const gchar *menu_accel,
+ const gchar *help_domain,
+ const gchar *help_data,
+ const gchar *icon_name,
+ gpointer register_data);
+
+typedef void (* GimpToolRegisterFunc) (GimpToolRegisterCallback callback,
+ gpointer register_data);
+
+
+G_END_DECLS
+
+#endif /* __TOOLS_TYPES_H__ */