diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:23:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:23:22 +0000 |
commit | e42129241681dde7adae7d20697e7b421682fbb4 (patch) | |
tree | af1fe815a5e639e68e59fabd8395ec69458b3e5e /app/tools | |
parent | Initial commit. (diff) | |
download | gimp-e42129241681dde7adae7d20697e7b421682fbb4.tar.xz gimp-e42129241681dde7adae7d20697e7b421682fbb4.zip |
Adding upstream version 2.10.22.upstream/2.10.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/tools')
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", ®ion, + 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 (®ion_sel->region_mask); + + g_clear_pointer (®ion_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 (®ion_sel->region_mask); + + g_clear_pointer (®ion_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, + ®ion_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 (®ion_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__ */ |