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/display | |
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 '')
183 files changed, 67738 insertions, 0 deletions
diff --git a/app/display/Makefile.am b/app/display/Makefile.am new file mode 100644 index 0000000..dbee710 --- /dev/null +++ b/app/display/Makefile.am @@ -0,0 +1,247 @@ +## Process this file with automake to produce Makefile.in + +if PLATFORM_OSX +xobjective_c = "-xobjective-c" +xobjective_cxx = "-xobjective-c++" +xnone = "-xnone" +endif + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"Gimp-Display\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/app \ + -I$(top_srcdir)/app \ + $(GEGL_CFLAGS) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +AM_CFLAGS = \ + $(xobjective_c) + +AM_CXXFLAGS = \ + $(xobjective_cxx) + +AM_LDFLAGS = \ + $(xnone) + +noinst_LIBRARIES = libappdisplay.a + +libappdisplay_a_sources = \ + display-enums.h \ + display-types.h \ + gimpcanvas.c \ + gimpcanvas.h \ + gimpcanvas-style.c \ + gimpcanvas-style.h \ + gimpcanvasarc.c \ + gimpcanvasarc.h \ + gimpcanvasboundary.c \ + gimpcanvasboundary.h \ + gimpcanvasbufferpreview.c \ + gimpcanvasbufferpreview.h \ + gimpcanvascanvasboundary.c \ + gimpcanvascanvasboundary.h \ + gimpcanvascorner.c \ + gimpcanvascorner.h \ + gimpcanvascursor.c \ + gimpcanvascursor.h \ + gimpcanvasgrid.c \ + gimpcanvasgrid.h \ + gimpcanvasgroup.c \ + gimpcanvasgroup.h \ + gimpcanvasguide.c \ + gimpcanvasguide.h \ + gimpcanvashandle.c \ + gimpcanvashandle.h \ + gimpcanvasitem.c \ + gimpcanvasitem.h \ + gimpcanvasitem-utils.c \ + gimpcanvasitem-utils.h \ + gimpcanvaslayerboundary.c \ + gimpcanvaslayerboundary.h \ + gimpcanvaslimit.c \ + gimpcanvaslimit.h \ + gimpcanvasline.c \ + gimpcanvasline.h \ + gimpcanvaspassepartout.c \ + gimpcanvaspassepartout.h \ + gimpcanvaspath.c \ + gimpcanvaspath.h \ + gimpcanvaspen.c \ + gimpcanvaspen.h \ + gimpcanvaspolygon.c \ + gimpcanvaspolygon.h \ + gimpcanvasprogress.c \ + gimpcanvasprogress.h \ + gimpcanvasproxygroup.c \ + gimpcanvasproxygroup.h \ + gimpcanvasrectangle.c \ + gimpcanvasrectangle.h \ + gimpcanvasrectangleguides.c \ + gimpcanvasrectangleguides.h \ + gimpcanvassamplepoint.c \ + gimpcanvassamplepoint.h \ + gimpcanvastextcursor.c \ + gimpcanvastextcursor.h \ + gimpcanvastransformguides.c \ + gimpcanvastransformguides.h \ + gimpcanvastransformpreview.c \ + gimpcanvastransformpreview.h \ + gimpcursorview.c \ + gimpcursorview.h \ + gimpdisplay.c \ + gimpdisplay.h \ + gimpdisplay-foreach.c \ + gimpdisplay-foreach.h \ + gimpdisplay-handlers.c \ + gimpdisplay-handlers.h \ + gimpdisplayshell.c \ + gimpdisplayshell.h \ + gimpdisplayshell-actions.c \ + gimpdisplayshell-actions.h \ + gimpdisplayshell-appearance.c \ + gimpdisplayshell-appearance.h \ + gimpdisplayshell-autoscroll.c \ + gimpdisplayshell-autoscroll.h \ + gimpdisplayshell-callbacks.c \ + gimpdisplayshell-callbacks.h \ + gimpdisplayshell-close.c \ + gimpdisplayshell-close.h \ + gimpdisplayshell-cursor.c \ + gimpdisplayshell-cursor.h \ + gimpdisplayshell-dnd.c \ + gimpdisplayshell-dnd.h \ + gimpdisplayshell-draw.c \ + gimpdisplayshell-draw.h \ + gimpdisplayshell-expose.c \ + gimpdisplayshell-expose.h \ + gimpdisplayshell-grab.c \ + gimpdisplayshell-grab.h \ + gimpdisplayshell-handlers.c \ + gimpdisplayshell-handlers.h \ + gimpdisplayshell-filter.c \ + gimpdisplayshell-filter.h \ + gimpdisplayshell-filter-dialog.c \ + gimpdisplayshell-filter-dialog.h \ + gimpdisplayshell-layer-select.c \ + gimpdisplayshell-layer-select.h \ + gimpdisplayshell-icon.c \ + gimpdisplayshell-icon.h \ + gimpdisplayshell-items.c \ + gimpdisplayshell-items.h \ + gimpdisplayshell-profile.c \ + gimpdisplayshell-profile.h \ + gimpdisplayshell-progress.c \ + gimpdisplayshell-progress.h \ + gimpdisplayshell-render.c \ + gimpdisplayshell-render.h \ + gimpdisplayshell-rotate.c \ + gimpdisplayshell-rotate.h \ + gimpdisplayshell-rotate-dialog.c \ + gimpdisplayshell-rotate-dialog.h \ + gimpdisplayshell-rulers.c \ + gimpdisplayshell-rulers.h \ + gimpdisplayshell-scale.c \ + gimpdisplayshell-scale.h \ + gimpdisplayshell-scale-dialog.c \ + gimpdisplayshell-scale-dialog.h \ + gimpdisplayshell-scroll.c \ + gimpdisplayshell-scroll.h \ + gimpdisplayshell-scrollbars.c \ + gimpdisplayshell-scrollbars.h \ + gimpdisplayshell-selection.c \ + gimpdisplayshell-selection.h \ + gimpdisplayshell-title.c \ + gimpdisplayshell-title.h \ + gimpdisplayshell-tool-events.c \ + gimpdisplayshell-tool-events.h \ + gimpdisplayshell-transform.c \ + gimpdisplayshell-transform.h \ + gimpdisplayshell-utils.c \ + gimpdisplayshell-utils.h \ + gimpdisplayxfer.c \ + gimpdisplayxfer.h \ + gimpimagewindow.c \ + gimpimagewindow.h \ + gimpmotionbuffer.c \ + gimpmotionbuffer.h \ + gimpmultiwindowstrategy.c \ + gimpmultiwindowstrategy.h \ + gimpnavigationeditor.c \ + gimpnavigationeditor.h \ + gimpscalecombobox.c \ + gimpscalecombobox.h \ + gimpsinglewindowstrategy.c \ + gimpsinglewindowstrategy.h \ + gimpstatusbar.c \ + gimpstatusbar.h \ + gimptooldialog.c \ + gimptooldialog.h \ + gimptoolgui.c \ + gimptoolgui.h \ + gimptoolcompass.c \ + gimptoolcompass.h \ + gimptoolfocus.h \ + gimptoolfocus.c \ + gimptoolgyroscope.c \ + gimptoolgyroscope.h \ + gimptoolhandlegrid.c \ + gimptoolhandlegrid.h \ + gimptoolline.c \ + gimptoolline.h \ + gimptoolpath.c \ + gimptoolpath.h \ + gimptoolpolygon.c \ + gimptoolpolygon.h \ + gimptoolrectangle.c \ + gimptoolrectangle.h \ + gimptoolrotategrid.c \ + gimptoolrotategrid.h \ + gimptoolsheargrid.c \ + gimptoolsheargrid.h \ + gimptooltransform3dgrid.c \ + gimptooltransform3dgrid.h \ + gimptooltransformgrid.c \ + gimptooltransformgrid.h \ + gimptoolwidget.c \ + gimptoolwidget.h \ + gimptoolwidgetgroup.c \ + gimptoolwidgetgroup.h + +libappdisplay_a_built_sources = display-enums.c + +libappdisplay_a_SOURCES = \ + $(libappdisplay_a_built_sources) \ + $(libappdisplay_a_sources) + +# +# rules to generate built sources +# +# setup autogeneration dependencies +gen_sources = xgen-dec +CLEANFILES = $(gen_sources) + +xgen-dec: $(srcdir)/display-enums.h $(GIMP_MKENUMS) Makefile.am + $(AM_V_GEN) $(GIMP_MKENUMS) \ + --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"display-enums.h\"\n#include\"gimp-intl.h\"" \ + --fprod "\n/* enumerations from \"@basename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n" \ + --dhead " static const Gimp@Type@Desc descs[] =\n {" \ + --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \ + --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \ + $< > $@ + +# copy the generated enum file back to the source directory only if it's +# changed; otherwise, only update its timestamp, so that the recipe isn't +# executed again on the next build, however, allow this to (harmlessly) fail, +# to support building from a read-only source tree. +$(srcdir)/display-enums.c: xgen-dec + $(AM_V_GEN) if ! cmp -s $< $@; then \ + cp $< $@; \ + else \ + touch $@ 2> /dev/null \ + || true; \ + fi diff --git a/app/display/Makefile.in b/app/display/Makefile.in new file mode 100644 index 0000000..d1c91e8 --- /dev/null +++ b/app/display/Makefile.in @@ -0,0 +1,1545 @@ +# Makefile.in generated by automake 1.16.2 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = app/display +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/gtk-doc.m4 \ + $(top_srcdir)/m4macros/intltool.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4macros/alsa.m4 \ + $(top_srcdir)/m4macros/ax_compare_version.m4 \ + $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \ + $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \ + $(top_srcdir)/m4macros/detectcflags.m4 \ + $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +ARFLAGS = cru +AM_V_AR = $(am__v_AR_@AM_V@) +am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) +am__v_AR_0 = @echo " AR " $@; +am__v_AR_1 = +libappdisplay_a_AR = $(AR) $(ARFLAGS) +libappdisplay_a_LIBADD = +am__objects_1 = display-enums.$(OBJEXT) +am__objects_2 = gimpcanvas.$(OBJEXT) gimpcanvas-style.$(OBJEXT) \ + gimpcanvasarc.$(OBJEXT) gimpcanvasboundary.$(OBJEXT) \ + gimpcanvasbufferpreview.$(OBJEXT) \ + gimpcanvascanvasboundary.$(OBJEXT) gimpcanvascorner.$(OBJEXT) \ + gimpcanvascursor.$(OBJEXT) gimpcanvasgrid.$(OBJEXT) \ + gimpcanvasgroup.$(OBJEXT) gimpcanvasguide.$(OBJEXT) \ + gimpcanvashandle.$(OBJEXT) gimpcanvasitem.$(OBJEXT) \ + gimpcanvasitem-utils.$(OBJEXT) \ + gimpcanvaslayerboundary.$(OBJEXT) gimpcanvaslimit.$(OBJEXT) \ + gimpcanvasline.$(OBJEXT) gimpcanvaspassepartout.$(OBJEXT) \ + gimpcanvaspath.$(OBJEXT) gimpcanvaspen.$(OBJEXT) \ + gimpcanvaspolygon.$(OBJEXT) gimpcanvasprogress.$(OBJEXT) \ + gimpcanvasproxygroup.$(OBJEXT) gimpcanvasrectangle.$(OBJEXT) \ + gimpcanvasrectangleguides.$(OBJEXT) \ + gimpcanvassamplepoint.$(OBJEXT) gimpcanvastextcursor.$(OBJEXT) \ + gimpcanvastransformguides.$(OBJEXT) \ + gimpcanvastransformpreview.$(OBJEXT) gimpcursorview.$(OBJEXT) \ + gimpdisplay.$(OBJEXT) gimpdisplay-foreach.$(OBJEXT) \ + gimpdisplay-handlers.$(OBJEXT) gimpdisplayshell.$(OBJEXT) \ + gimpdisplayshell-actions.$(OBJEXT) \ + gimpdisplayshell-appearance.$(OBJEXT) \ + gimpdisplayshell-autoscroll.$(OBJEXT) \ + gimpdisplayshell-callbacks.$(OBJEXT) \ + gimpdisplayshell-close.$(OBJEXT) \ + gimpdisplayshell-cursor.$(OBJEXT) \ + gimpdisplayshell-dnd.$(OBJEXT) gimpdisplayshell-draw.$(OBJEXT) \ + gimpdisplayshell-expose.$(OBJEXT) \ + gimpdisplayshell-grab.$(OBJEXT) \ + gimpdisplayshell-handlers.$(OBJEXT) \ + gimpdisplayshell-filter.$(OBJEXT) \ + gimpdisplayshell-filter-dialog.$(OBJEXT) \ + gimpdisplayshell-layer-select.$(OBJEXT) \ + gimpdisplayshell-icon.$(OBJEXT) \ + gimpdisplayshell-items.$(OBJEXT) \ + gimpdisplayshell-profile.$(OBJEXT) \ + gimpdisplayshell-progress.$(OBJEXT) \ + gimpdisplayshell-render.$(OBJEXT) \ + gimpdisplayshell-rotate.$(OBJEXT) \ + gimpdisplayshell-rotate-dialog.$(OBJEXT) \ + gimpdisplayshell-rulers.$(OBJEXT) \ + gimpdisplayshell-scale.$(OBJEXT) \ + gimpdisplayshell-scale-dialog.$(OBJEXT) \ + gimpdisplayshell-scroll.$(OBJEXT) \ + gimpdisplayshell-scrollbars.$(OBJEXT) \ + gimpdisplayshell-selection.$(OBJEXT) \ + gimpdisplayshell-title.$(OBJEXT) \ + gimpdisplayshell-tool-events.$(OBJEXT) \ + gimpdisplayshell-transform.$(OBJEXT) \ + gimpdisplayshell-utils.$(OBJEXT) gimpdisplayxfer.$(OBJEXT) \ + gimpimagewindow.$(OBJEXT) gimpmotionbuffer.$(OBJEXT) \ + gimpmultiwindowstrategy.$(OBJEXT) \ + gimpnavigationeditor.$(OBJEXT) gimpscalecombobox.$(OBJEXT) \ + gimpsinglewindowstrategy.$(OBJEXT) gimpstatusbar.$(OBJEXT) \ + gimptooldialog.$(OBJEXT) gimptoolgui.$(OBJEXT) \ + gimptoolcompass.$(OBJEXT) gimptoolfocus.$(OBJEXT) \ + gimptoolgyroscope.$(OBJEXT) gimptoolhandlegrid.$(OBJEXT) \ + gimptoolline.$(OBJEXT) gimptoolpath.$(OBJEXT) \ + gimptoolpolygon.$(OBJEXT) gimptoolrectangle.$(OBJEXT) \ + gimptoolrotategrid.$(OBJEXT) gimptoolsheargrid.$(OBJEXT) \ + gimptooltransform3dgrid.$(OBJEXT) \ + gimptooltransformgrid.$(OBJEXT) gimptoolwidget.$(OBJEXT) \ + gimptoolwidgetgroup.$(OBJEXT) +am_libappdisplay_a_OBJECTS = $(am__objects_1) $(am__objects_2) +libappdisplay_a_OBJECTS = $(am_libappdisplay_a_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/display-enums.Po \ + ./$(DEPDIR)/gimpcanvas-style.Po ./$(DEPDIR)/gimpcanvas.Po \ + ./$(DEPDIR)/gimpcanvasarc.Po ./$(DEPDIR)/gimpcanvasboundary.Po \ + ./$(DEPDIR)/gimpcanvasbufferpreview.Po \ + ./$(DEPDIR)/gimpcanvascanvasboundary.Po \ + ./$(DEPDIR)/gimpcanvascorner.Po \ + ./$(DEPDIR)/gimpcanvascursor.Po ./$(DEPDIR)/gimpcanvasgrid.Po \ + ./$(DEPDIR)/gimpcanvasgroup.Po ./$(DEPDIR)/gimpcanvasguide.Po \ + ./$(DEPDIR)/gimpcanvashandle.Po \ + ./$(DEPDIR)/gimpcanvasitem-utils.Po \ + ./$(DEPDIR)/gimpcanvasitem.Po \ + ./$(DEPDIR)/gimpcanvaslayerboundary.Po \ + ./$(DEPDIR)/gimpcanvaslimit.Po ./$(DEPDIR)/gimpcanvasline.Po \ + ./$(DEPDIR)/gimpcanvaspassepartout.Po \ + ./$(DEPDIR)/gimpcanvaspath.Po ./$(DEPDIR)/gimpcanvaspen.Po \ + ./$(DEPDIR)/gimpcanvaspolygon.Po \ + ./$(DEPDIR)/gimpcanvasprogress.Po \ + ./$(DEPDIR)/gimpcanvasproxygroup.Po \ + ./$(DEPDIR)/gimpcanvasrectangle.Po \ + ./$(DEPDIR)/gimpcanvasrectangleguides.Po \ + ./$(DEPDIR)/gimpcanvassamplepoint.Po \ + ./$(DEPDIR)/gimpcanvastextcursor.Po \ + ./$(DEPDIR)/gimpcanvastransformguides.Po \ + ./$(DEPDIR)/gimpcanvastransformpreview.Po \ + ./$(DEPDIR)/gimpcursorview.Po \ + ./$(DEPDIR)/gimpdisplay-foreach.Po \ + ./$(DEPDIR)/gimpdisplay-handlers.Po ./$(DEPDIR)/gimpdisplay.Po \ + ./$(DEPDIR)/gimpdisplayshell-actions.Po \ + ./$(DEPDIR)/gimpdisplayshell-appearance.Po \ + ./$(DEPDIR)/gimpdisplayshell-autoscroll.Po \ + ./$(DEPDIR)/gimpdisplayshell-callbacks.Po \ + ./$(DEPDIR)/gimpdisplayshell-close.Po \ + ./$(DEPDIR)/gimpdisplayshell-cursor.Po \ + ./$(DEPDIR)/gimpdisplayshell-dnd.Po \ + ./$(DEPDIR)/gimpdisplayshell-draw.Po \ + ./$(DEPDIR)/gimpdisplayshell-expose.Po \ + ./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po \ + ./$(DEPDIR)/gimpdisplayshell-filter.Po \ + ./$(DEPDIR)/gimpdisplayshell-grab.Po \ + ./$(DEPDIR)/gimpdisplayshell-handlers.Po \ + ./$(DEPDIR)/gimpdisplayshell-icon.Po \ + ./$(DEPDIR)/gimpdisplayshell-items.Po \ + ./$(DEPDIR)/gimpdisplayshell-layer-select.Po \ + ./$(DEPDIR)/gimpdisplayshell-profile.Po \ + ./$(DEPDIR)/gimpdisplayshell-progress.Po \ + ./$(DEPDIR)/gimpdisplayshell-render.Po \ + ./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po \ + ./$(DEPDIR)/gimpdisplayshell-rotate.Po \ + ./$(DEPDIR)/gimpdisplayshell-rulers.Po \ + ./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po \ + ./$(DEPDIR)/gimpdisplayshell-scale.Po \ + ./$(DEPDIR)/gimpdisplayshell-scroll.Po \ + ./$(DEPDIR)/gimpdisplayshell-scrollbars.Po \ + ./$(DEPDIR)/gimpdisplayshell-selection.Po \ + ./$(DEPDIR)/gimpdisplayshell-title.Po \ + ./$(DEPDIR)/gimpdisplayshell-tool-events.Po \ + ./$(DEPDIR)/gimpdisplayshell-transform.Po \ + ./$(DEPDIR)/gimpdisplayshell-utils.Po \ + ./$(DEPDIR)/gimpdisplayshell.Po ./$(DEPDIR)/gimpdisplayxfer.Po \ + ./$(DEPDIR)/gimpimagewindow.Po ./$(DEPDIR)/gimpmotionbuffer.Po \ + ./$(DEPDIR)/gimpmultiwindowstrategy.Po \ + ./$(DEPDIR)/gimpnavigationeditor.Po \ + ./$(DEPDIR)/gimpscalecombobox.Po \ + ./$(DEPDIR)/gimpsinglewindowstrategy.Po \ + ./$(DEPDIR)/gimpstatusbar.Po ./$(DEPDIR)/gimptoolcompass.Po \ + ./$(DEPDIR)/gimptooldialog.Po ./$(DEPDIR)/gimptoolfocus.Po \ + ./$(DEPDIR)/gimptoolgui.Po ./$(DEPDIR)/gimptoolgyroscope.Po \ + ./$(DEPDIR)/gimptoolhandlegrid.Po ./$(DEPDIR)/gimptoolline.Po \ + ./$(DEPDIR)/gimptoolpath.Po ./$(DEPDIR)/gimptoolpolygon.Po \ + ./$(DEPDIR)/gimptoolrectangle.Po \ + ./$(DEPDIR)/gimptoolrotategrid.Po \ + ./$(DEPDIR)/gimptoolsheargrid.Po \ + ./$(DEPDIR)/gimptooltransform3dgrid.Po \ + ./$(DEPDIR)/gimptooltransformgrid.Po \ + ./$(DEPDIR)/gimptoolwidget.Po \ + ./$(DEPDIR)/gimptoolwidgetgroup.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libappdisplay_a_SOURCES) +DIST_SOURCES = $(libappdisplay_a_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +AA_LIBS = @AA_LIBS@ +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +ALL_LINGUAS = @ALL_LINGUAS@ +ALSA_CFLAGS = @ALSA_CFLAGS@ +ALSA_LIBS = @ALSA_LIBS@ +ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPSTREAM_UTIL = @APPSTREAM_UTIL@ +AR = @AR@ +AS = @AS@ +ATK_CFLAGS = @ATK_CFLAGS@ +ATK_LIBS = @ATK_LIBS@ +ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BABL_CFLAGS = @BABL_CFLAGS@ +BABL_LIBS = @BABL_LIBS@ +BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@ +BUG_REPORT_URL = @BUG_REPORT_URL@ +BUILD_EXEEXT = @BUILD_EXEEXT@ +BUILD_OBJEXT = @BUILD_OBJEXT@ +BZIP2_LIBS = @BZIP2_LIBS@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@ +CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@ +CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@ +CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CC_VERSION = @CC_VERSION@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@ +CPP_FOR_BUILD = @CPP_FOR_BUILD@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DESKTOP_DATADIR = @DESKTOP_DATADIR@ +DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@ +DLLTOOL = @DLLTOOL@ +DOC_SHOOTER = @DOC_SHOOTER@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILE_AA = @FILE_AA@ +FILE_EXR = @FILE_EXR@ +FILE_HEIF = @FILE_HEIF@ +FILE_JP2_LOAD = @FILE_JP2_LOAD@ +FILE_MNG = @FILE_MNG@ +FILE_PDF_SAVE = @FILE_PDF_SAVE@ +FILE_PS = @FILE_PS@ +FILE_WMF = @FILE_WMF@ +FILE_XMC = @FILE_XMC@ +FILE_XPM = @FILE_XPM@ +FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@ +FONTCONFIG_LIBS = @FONTCONFIG_LIBS@ +FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@ +FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@ +FREETYPE_CFLAGS = @FREETYPE_CFLAGS@ +FREETYPE_LIBS = @FREETYPE_LIBS@ +GDBUS_CODEGEN = @GDBUS_CODEGEN@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@ +GEGL = @GEGL@ +GEGL_CFLAGS = @GEGL_CFLAGS@ +GEGL_LIBS = @GEGL_LIBS@ +GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@ +GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GEXIV2_CFLAGS = @GEXIV2_CFLAGS@ +GEXIV2_LIBS = @GEXIV2_LIBS@ +GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@ +GIMP_API_VERSION = @GIMP_API_VERSION@ +GIMP_APP_VERSION = @GIMP_APP_VERSION@ +GIMP_BINARY_AGE = @GIMP_BINARY_AGE@ +GIMP_COMMAND = @GIMP_COMMAND@ +GIMP_DATA_VERSION = @GIMP_DATA_VERSION@ +GIMP_FULL_NAME = @GIMP_FULL_NAME@ +GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@ +GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@ +GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@ +GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@ +GIMP_MKENUMS = @GIMP_MKENUMS@ +GIMP_MODULES = @GIMP_MODULES@ +GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@ +GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@ +GIMP_PLUGINS = @GIMP_PLUGINS@ +GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@ +GIMP_REAL_VERSION = @GIMP_REAL_VERSION@ +GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@ +GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@ +GIMP_UNSTABLE = @GIMP_UNSTABLE@ +GIMP_USER_VERSION = @GIMP_USER_VERSION@ +GIMP_VERSION = @GIMP_VERSION@ +GIO_CFLAGS = @GIO_CFLAGS@ +GIO_LIBS = @GIO_LIBS@ +GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@ +GIO_UNIX_LIBS = @GIO_UNIX_LIBS@ +GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@ +GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@ +GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@ +GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GREP = @GREP@ +GS_LIBS = @GS_LIBS@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@ +GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@ +GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@ +GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@ +HARFBUZZ_LIBS = @HARFBUZZ_LIBS@ +HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@ +HAVE_CXX14 = @HAVE_CXX14@ +HAVE_FINITE = @HAVE_FINITE@ +HAVE_ISFINITE = @HAVE_ISFINITE@ +HAVE_VFORK = @HAVE_VFORK@ +HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@ +ISO_CODES_LOCATION = @ISO_CODES_LOCATION@ +JPEG_LIBS = @JPEG_LIBS@ +JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@ +JSON_GLIB_LIBS = @JSON_GLIB_LIBS@ +LCMS_CFLAGS = @LCMS_CFLAGS@ +LCMS_LIBS = @LCMS_LIBS@ +LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@ +LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@ +LIBHEIF_LIBS = @LIBHEIF_LIBS@ +LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@ +LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@ +LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@ +LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@ +LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@ +LIBOBJS = @LIBOBJS@ +LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LT_VERSION_INFO = @LT_VERSION_INFO@ +LZMA_CFLAGS = @LZMA_CFLAGS@ +LZMA_LIBS = @LZMA_LIBS@ +MAIL = @MAIL@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@ +MIME_INFO_LIBS = @MIME_INFO_LIBS@ +MIME_TYPES = @MIME_TYPES@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@ +MNG_CFLAGS = @MNG_CFLAGS@ +MNG_LIBS = @MNG_LIBS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@ +MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@ +NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@ +NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENEXR_CFLAGS = @OPENEXR_CFLAGS@ +OPENEXR_LIBS = @OPENEXR_LIBS@ +OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@ +OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@ +OPENJPEG_LIBS = @OPENJPEG_LIBS@ +OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@ +PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@ +PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@ +PATHSEP = @PATHSEP@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@ +PERL_VERSION = @PERL_VERSION@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PNG_CFLAGS = @PNG_CFLAGS@ +PNG_LIBS = @PNG_LIBS@ +POFILES = @POFILES@ +POPPLER_CFLAGS = @POPPLER_CFLAGS@ +POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@ +POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@ +POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@ +POPPLER_LIBS = @POPPLER_LIBS@ +POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PYBIN_PATH = @PYBIN_PATH@ +PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@ +PYCAIRO_LIBS = @PYCAIRO_LIBS@ +PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@ +PYGTK_CFLAGS = @PYGTK_CFLAGS@ +PYGTK_CODEGEN = @PYGTK_CODEGEN@ +PYGTK_DEFSDIR = @PYGTK_DEFSDIR@ +PYGTK_LIBS = @PYGTK_LIBS@ +PYLINK_LIBS = @PYLINK_LIBS@ +PYTHON = @PYTHON@ +PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_INCLUDES = @PYTHON_INCLUDES@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@ +RT_LIBS = @RT_LIBS@ +SCREENSHOT_LIBS = @SCREENSHOT_LIBS@ +SED = @SED@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SOCKET_LIBS = @SOCKET_LIBS@ +SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@ +SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@ +SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@ +STRIP = @STRIP@ +SVG_CFLAGS = @SVG_CFLAGS@ +SVG_LIBS = @SVG_LIBS@ +SYMPREFIX = @SYMPREFIX@ +TIFF_LIBS = @TIFF_LIBS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WEBKIT_CFLAGS = @WEBKIT_CFLAGS@ +WEBKIT_LIBS = @WEBKIT_LIBS@ +WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@ +WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@ +WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@ +WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@ +WEBPMUX_LIBS = @WEBPMUX_LIBS@ +WEBP_CFLAGS = @WEBP_CFLAGS@ +WEBP_LIBS = @WEBP_LIBS@ +WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@ +WEB_PAGE = @WEB_PAGE@ +WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@ +WINDRES = @WINDRES@ +WMF_CFLAGS = @WMF_CFLAGS@ +WMF_CONFIG = @WMF_CONFIG@ +WMF_LIBS = @WMF_LIBS@ +WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@ +XDG_EMAIL = @XDG_EMAIL@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@ +XMC_CFLAGS = @XMC_CFLAGS@ +XMC_LIBS = @XMC_LIBS@ +XMKMF = @XMKMF@ +XMLLINT = @XMLLINT@ +XMU_LIBS = @XMU_LIBS@ +XPM_LIBS = @XPM_LIBS@ +XSLTPROC = @XSLTPROC@ +XVFB_RUN = @XVFB_RUN@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +Z_LIBS = @Z_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gimpdatadir = @gimpdatadir@ +gimpdir = @gimpdir@ +gimplocaledir = @gimplocaledir@ +gimpplugindir = @gimpplugindir@ +gimpsysconfdir = @gimpsysconfdir@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +manpage_gimpdir = @manpage_gimpdir@ +mkdir_p = @mkdir_p@ +ms_librarian = @ms_librarian@ +mypaint_brushes_dir = @mypaint_brushes_dir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c" +@PLATFORM_OSX_TRUE@xobjective_cxx = "-xobjective-c++" +@PLATFORM_OSX_TRUE@xnone = "-xnone" +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"Gimp-Display\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/app \ + -I$(top_srcdir)/app \ + $(GEGL_CFLAGS) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +AM_CFLAGS = \ + $(xobjective_c) + +AM_CXXFLAGS = \ + $(xobjective_cxx) + +AM_LDFLAGS = \ + $(xnone) + +noinst_LIBRARIES = libappdisplay.a +libappdisplay_a_sources = \ + display-enums.h \ + display-types.h \ + gimpcanvas.c \ + gimpcanvas.h \ + gimpcanvas-style.c \ + gimpcanvas-style.h \ + gimpcanvasarc.c \ + gimpcanvasarc.h \ + gimpcanvasboundary.c \ + gimpcanvasboundary.h \ + gimpcanvasbufferpreview.c \ + gimpcanvasbufferpreview.h \ + gimpcanvascanvasboundary.c \ + gimpcanvascanvasboundary.h \ + gimpcanvascorner.c \ + gimpcanvascorner.h \ + gimpcanvascursor.c \ + gimpcanvascursor.h \ + gimpcanvasgrid.c \ + gimpcanvasgrid.h \ + gimpcanvasgroup.c \ + gimpcanvasgroup.h \ + gimpcanvasguide.c \ + gimpcanvasguide.h \ + gimpcanvashandle.c \ + gimpcanvashandle.h \ + gimpcanvasitem.c \ + gimpcanvasitem.h \ + gimpcanvasitem-utils.c \ + gimpcanvasitem-utils.h \ + gimpcanvaslayerboundary.c \ + gimpcanvaslayerboundary.h \ + gimpcanvaslimit.c \ + gimpcanvaslimit.h \ + gimpcanvasline.c \ + gimpcanvasline.h \ + gimpcanvaspassepartout.c \ + gimpcanvaspassepartout.h \ + gimpcanvaspath.c \ + gimpcanvaspath.h \ + gimpcanvaspen.c \ + gimpcanvaspen.h \ + gimpcanvaspolygon.c \ + gimpcanvaspolygon.h \ + gimpcanvasprogress.c \ + gimpcanvasprogress.h \ + gimpcanvasproxygroup.c \ + gimpcanvasproxygroup.h \ + gimpcanvasrectangle.c \ + gimpcanvasrectangle.h \ + gimpcanvasrectangleguides.c \ + gimpcanvasrectangleguides.h \ + gimpcanvassamplepoint.c \ + gimpcanvassamplepoint.h \ + gimpcanvastextcursor.c \ + gimpcanvastextcursor.h \ + gimpcanvastransformguides.c \ + gimpcanvastransformguides.h \ + gimpcanvastransformpreview.c \ + gimpcanvastransformpreview.h \ + gimpcursorview.c \ + gimpcursorview.h \ + gimpdisplay.c \ + gimpdisplay.h \ + gimpdisplay-foreach.c \ + gimpdisplay-foreach.h \ + gimpdisplay-handlers.c \ + gimpdisplay-handlers.h \ + gimpdisplayshell.c \ + gimpdisplayshell.h \ + gimpdisplayshell-actions.c \ + gimpdisplayshell-actions.h \ + gimpdisplayshell-appearance.c \ + gimpdisplayshell-appearance.h \ + gimpdisplayshell-autoscroll.c \ + gimpdisplayshell-autoscroll.h \ + gimpdisplayshell-callbacks.c \ + gimpdisplayshell-callbacks.h \ + gimpdisplayshell-close.c \ + gimpdisplayshell-close.h \ + gimpdisplayshell-cursor.c \ + gimpdisplayshell-cursor.h \ + gimpdisplayshell-dnd.c \ + gimpdisplayshell-dnd.h \ + gimpdisplayshell-draw.c \ + gimpdisplayshell-draw.h \ + gimpdisplayshell-expose.c \ + gimpdisplayshell-expose.h \ + gimpdisplayshell-grab.c \ + gimpdisplayshell-grab.h \ + gimpdisplayshell-handlers.c \ + gimpdisplayshell-handlers.h \ + gimpdisplayshell-filter.c \ + gimpdisplayshell-filter.h \ + gimpdisplayshell-filter-dialog.c \ + gimpdisplayshell-filter-dialog.h \ + gimpdisplayshell-layer-select.c \ + gimpdisplayshell-layer-select.h \ + gimpdisplayshell-icon.c \ + gimpdisplayshell-icon.h \ + gimpdisplayshell-items.c \ + gimpdisplayshell-items.h \ + gimpdisplayshell-profile.c \ + gimpdisplayshell-profile.h \ + gimpdisplayshell-progress.c \ + gimpdisplayshell-progress.h \ + gimpdisplayshell-render.c \ + gimpdisplayshell-render.h \ + gimpdisplayshell-rotate.c \ + gimpdisplayshell-rotate.h \ + gimpdisplayshell-rotate-dialog.c \ + gimpdisplayshell-rotate-dialog.h \ + gimpdisplayshell-rulers.c \ + gimpdisplayshell-rulers.h \ + gimpdisplayshell-scale.c \ + gimpdisplayshell-scale.h \ + gimpdisplayshell-scale-dialog.c \ + gimpdisplayshell-scale-dialog.h \ + gimpdisplayshell-scroll.c \ + gimpdisplayshell-scroll.h \ + gimpdisplayshell-scrollbars.c \ + gimpdisplayshell-scrollbars.h \ + gimpdisplayshell-selection.c \ + gimpdisplayshell-selection.h \ + gimpdisplayshell-title.c \ + gimpdisplayshell-title.h \ + gimpdisplayshell-tool-events.c \ + gimpdisplayshell-tool-events.h \ + gimpdisplayshell-transform.c \ + gimpdisplayshell-transform.h \ + gimpdisplayshell-utils.c \ + gimpdisplayshell-utils.h \ + gimpdisplayxfer.c \ + gimpdisplayxfer.h \ + gimpimagewindow.c \ + gimpimagewindow.h \ + gimpmotionbuffer.c \ + gimpmotionbuffer.h \ + gimpmultiwindowstrategy.c \ + gimpmultiwindowstrategy.h \ + gimpnavigationeditor.c \ + gimpnavigationeditor.h \ + gimpscalecombobox.c \ + gimpscalecombobox.h \ + gimpsinglewindowstrategy.c \ + gimpsinglewindowstrategy.h \ + gimpstatusbar.c \ + gimpstatusbar.h \ + gimptooldialog.c \ + gimptooldialog.h \ + gimptoolgui.c \ + gimptoolgui.h \ + gimptoolcompass.c \ + gimptoolcompass.h \ + gimptoolfocus.h \ + gimptoolfocus.c \ + gimptoolgyroscope.c \ + gimptoolgyroscope.h \ + gimptoolhandlegrid.c \ + gimptoolhandlegrid.h \ + gimptoolline.c \ + gimptoolline.h \ + gimptoolpath.c \ + gimptoolpath.h \ + gimptoolpolygon.c \ + gimptoolpolygon.h \ + gimptoolrectangle.c \ + gimptoolrectangle.h \ + gimptoolrotategrid.c \ + gimptoolrotategrid.h \ + gimptoolsheargrid.c \ + gimptoolsheargrid.h \ + gimptooltransform3dgrid.c \ + gimptooltransform3dgrid.h \ + gimptooltransformgrid.c \ + gimptooltransformgrid.h \ + gimptoolwidget.c \ + gimptoolwidget.h \ + gimptoolwidgetgroup.c \ + gimptoolwidgetgroup.h + +libappdisplay_a_built_sources = display-enums.c +libappdisplay_a_SOURCES = \ + $(libappdisplay_a_built_sources) \ + $(libappdisplay_a_sources) + + +# +# rules to generate built sources +# +# setup autogeneration dependencies +gen_sources = xgen-dec +CLEANFILES = $(gen_sources) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu app/display/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu app/display/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +libappdisplay.a: $(libappdisplay_a_OBJECTS) $(libappdisplay_a_DEPENDENCIES) $(EXTRA_libappdisplay_a_DEPENDENCIES) + $(AM_V_at)-rm -f libappdisplay.a + $(AM_V_AR)$(libappdisplay_a_AR) libappdisplay.a $(libappdisplay_a_OBJECTS) $(libappdisplay_a_LIBADD) + $(AM_V_at)$(RANLIB) libappdisplay.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display-enums.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvas-style.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvas.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasarc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasboundary.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasbufferpreview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvascanvasboundary.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvascorner.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvascursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasgrid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasgroup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasguide.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvashandle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasitem-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasitem.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaslayerboundary.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaslimit.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasline.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspassepartout.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspath.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspen.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvaspolygon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasprogress.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasproxygroup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasrectangle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvasrectangleguides.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvassamplepoint.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvastextcursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvastransformguides.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcanvastransformpreview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcursorview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplay-foreach.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplay-handlers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplay.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-actions.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-appearance.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-autoscroll.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-callbacks.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-close.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-cursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-dnd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-draw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-expose.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-filter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-grab.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-handlers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-icon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-items.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-layer-select.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-profile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-progress.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-render.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-rotate.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-rulers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scale.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scroll.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-scrollbars.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-selection.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-title.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-tool-events.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-transform.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayshell.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdisplayxfer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimagewindow.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmotionbuffer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmultiwindowstrategy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpnavigationeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscalecombobox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsinglewindowstrategy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstatusbar.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolcompass.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooldialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolfocus.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolgui.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolgyroscope.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolhandlegrid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolline.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpath.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpolygon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolrectangle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolrotategrid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolsheargrid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooltransform3dgrid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooltransformgrid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolwidget.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolwidgetgroup.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/display-enums.Po + -rm -f ./$(DEPDIR)/gimpcanvas-style.Po + -rm -f ./$(DEPDIR)/gimpcanvas.Po + -rm -f ./$(DEPDIR)/gimpcanvasarc.Po + -rm -f ./$(DEPDIR)/gimpcanvasboundary.Po + -rm -f ./$(DEPDIR)/gimpcanvasbufferpreview.Po + -rm -f ./$(DEPDIR)/gimpcanvascanvasboundary.Po + -rm -f ./$(DEPDIR)/gimpcanvascorner.Po + -rm -f ./$(DEPDIR)/gimpcanvascursor.Po + -rm -f ./$(DEPDIR)/gimpcanvasgrid.Po + -rm -f ./$(DEPDIR)/gimpcanvasgroup.Po + -rm -f ./$(DEPDIR)/gimpcanvasguide.Po + -rm -f ./$(DEPDIR)/gimpcanvashandle.Po + -rm -f ./$(DEPDIR)/gimpcanvasitem-utils.Po + -rm -f ./$(DEPDIR)/gimpcanvasitem.Po + -rm -f ./$(DEPDIR)/gimpcanvaslayerboundary.Po + -rm -f ./$(DEPDIR)/gimpcanvaslimit.Po + -rm -f ./$(DEPDIR)/gimpcanvasline.Po + -rm -f ./$(DEPDIR)/gimpcanvaspassepartout.Po + -rm -f ./$(DEPDIR)/gimpcanvaspath.Po + -rm -f ./$(DEPDIR)/gimpcanvaspen.Po + -rm -f ./$(DEPDIR)/gimpcanvaspolygon.Po + -rm -f ./$(DEPDIR)/gimpcanvasprogress.Po + -rm -f ./$(DEPDIR)/gimpcanvasproxygroup.Po + -rm -f ./$(DEPDIR)/gimpcanvasrectangle.Po + -rm -f ./$(DEPDIR)/gimpcanvasrectangleguides.Po + -rm -f ./$(DEPDIR)/gimpcanvassamplepoint.Po + -rm -f ./$(DEPDIR)/gimpcanvastextcursor.Po + -rm -f ./$(DEPDIR)/gimpcanvastransformguides.Po + -rm -f ./$(DEPDIR)/gimpcanvastransformpreview.Po + -rm -f ./$(DEPDIR)/gimpcursorview.Po + -rm -f ./$(DEPDIR)/gimpdisplay-foreach.Po + -rm -f ./$(DEPDIR)/gimpdisplay-handlers.Po + -rm -f ./$(DEPDIR)/gimpdisplay.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-actions.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-appearance.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-autoscroll.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-callbacks.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-close.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-cursor.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-dnd.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-draw.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-expose.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-filter.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-grab.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-handlers.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-icon.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-items.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-layer-select.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-profile.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-progress.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-render.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-rulers.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scale.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scroll.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scrollbars.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-selection.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-title.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-tool-events.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-transform.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-utils.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell.Po + -rm -f ./$(DEPDIR)/gimpdisplayxfer.Po + -rm -f ./$(DEPDIR)/gimpimagewindow.Po + -rm -f ./$(DEPDIR)/gimpmotionbuffer.Po + -rm -f ./$(DEPDIR)/gimpmultiwindowstrategy.Po + -rm -f ./$(DEPDIR)/gimpnavigationeditor.Po + -rm -f ./$(DEPDIR)/gimpscalecombobox.Po + -rm -f ./$(DEPDIR)/gimpsinglewindowstrategy.Po + -rm -f ./$(DEPDIR)/gimpstatusbar.Po + -rm -f ./$(DEPDIR)/gimptoolcompass.Po + -rm -f ./$(DEPDIR)/gimptooldialog.Po + -rm -f ./$(DEPDIR)/gimptoolfocus.Po + -rm -f ./$(DEPDIR)/gimptoolgui.Po + -rm -f ./$(DEPDIR)/gimptoolgyroscope.Po + -rm -f ./$(DEPDIR)/gimptoolhandlegrid.Po + -rm -f ./$(DEPDIR)/gimptoolline.Po + -rm -f ./$(DEPDIR)/gimptoolpath.Po + -rm -f ./$(DEPDIR)/gimptoolpolygon.Po + -rm -f ./$(DEPDIR)/gimptoolrectangle.Po + -rm -f ./$(DEPDIR)/gimptoolrotategrid.Po + -rm -f ./$(DEPDIR)/gimptoolsheargrid.Po + -rm -f ./$(DEPDIR)/gimptooltransform3dgrid.Po + -rm -f ./$(DEPDIR)/gimptooltransformgrid.Po + -rm -f ./$(DEPDIR)/gimptoolwidget.Po + -rm -f ./$(DEPDIR)/gimptoolwidgetgroup.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/display-enums.Po + -rm -f ./$(DEPDIR)/gimpcanvas-style.Po + -rm -f ./$(DEPDIR)/gimpcanvas.Po + -rm -f ./$(DEPDIR)/gimpcanvasarc.Po + -rm -f ./$(DEPDIR)/gimpcanvasboundary.Po + -rm -f ./$(DEPDIR)/gimpcanvasbufferpreview.Po + -rm -f ./$(DEPDIR)/gimpcanvascanvasboundary.Po + -rm -f ./$(DEPDIR)/gimpcanvascorner.Po + -rm -f ./$(DEPDIR)/gimpcanvascursor.Po + -rm -f ./$(DEPDIR)/gimpcanvasgrid.Po + -rm -f ./$(DEPDIR)/gimpcanvasgroup.Po + -rm -f ./$(DEPDIR)/gimpcanvasguide.Po + -rm -f ./$(DEPDIR)/gimpcanvashandle.Po + -rm -f ./$(DEPDIR)/gimpcanvasitem-utils.Po + -rm -f ./$(DEPDIR)/gimpcanvasitem.Po + -rm -f ./$(DEPDIR)/gimpcanvaslayerboundary.Po + -rm -f ./$(DEPDIR)/gimpcanvaslimit.Po + -rm -f ./$(DEPDIR)/gimpcanvasline.Po + -rm -f ./$(DEPDIR)/gimpcanvaspassepartout.Po + -rm -f ./$(DEPDIR)/gimpcanvaspath.Po + -rm -f ./$(DEPDIR)/gimpcanvaspen.Po + -rm -f ./$(DEPDIR)/gimpcanvaspolygon.Po + -rm -f ./$(DEPDIR)/gimpcanvasprogress.Po + -rm -f ./$(DEPDIR)/gimpcanvasproxygroup.Po + -rm -f ./$(DEPDIR)/gimpcanvasrectangle.Po + -rm -f ./$(DEPDIR)/gimpcanvasrectangleguides.Po + -rm -f ./$(DEPDIR)/gimpcanvassamplepoint.Po + -rm -f ./$(DEPDIR)/gimpcanvastextcursor.Po + -rm -f ./$(DEPDIR)/gimpcanvastransformguides.Po + -rm -f ./$(DEPDIR)/gimpcanvastransformpreview.Po + -rm -f ./$(DEPDIR)/gimpcursorview.Po + -rm -f ./$(DEPDIR)/gimpdisplay-foreach.Po + -rm -f ./$(DEPDIR)/gimpdisplay-handlers.Po + -rm -f ./$(DEPDIR)/gimpdisplay.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-actions.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-appearance.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-autoscroll.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-callbacks.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-close.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-cursor.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-dnd.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-draw.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-expose.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-filter-dialog.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-filter.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-grab.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-handlers.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-icon.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-items.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-layer-select.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-profile.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-progress.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-render.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate-dialog.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-rotate.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-rulers.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scale-dialog.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scale.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scroll.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-scrollbars.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-selection.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-title.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-tool-events.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-transform.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell-utils.Po + -rm -f ./$(DEPDIR)/gimpdisplayshell.Po + -rm -f ./$(DEPDIR)/gimpdisplayxfer.Po + -rm -f ./$(DEPDIR)/gimpimagewindow.Po + -rm -f ./$(DEPDIR)/gimpmotionbuffer.Po + -rm -f ./$(DEPDIR)/gimpmultiwindowstrategy.Po + -rm -f ./$(DEPDIR)/gimpnavigationeditor.Po + -rm -f ./$(DEPDIR)/gimpscalecombobox.Po + -rm -f ./$(DEPDIR)/gimpsinglewindowstrategy.Po + -rm -f ./$(DEPDIR)/gimpstatusbar.Po + -rm -f ./$(DEPDIR)/gimptoolcompass.Po + -rm -f ./$(DEPDIR)/gimptooldialog.Po + -rm -f ./$(DEPDIR)/gimptoolfocus.Po + -rm -f ./$(DEPDIR)/gimptoolgui.Po + -rm -f ./$(DEPDIR)/gimptoolgyroscope.Po + -rm -f ./$(DEPDIR)/gimptoolhandlegrid.Po + -rm -f ./$(DEPDIR)/gimptoolline.Po + -rm -f ./$(DEPDIR)/gimptoolpath.Po + -rm -f ./$(DEPDIR)/gimptoolpolygon.Po + -rm -f ./$(DEPDIR)/gimptoolrectangle.Po + -rm -f ./$(DEPDIR)/gimptoolrotategrid.Po + -rm -f ./$(DEPDIR)/gimptoolsheargrid.Po + -rm -f ./$(DEPDIR)/gimptooltransform3dgrid.Po + -rm -f ./$(DEPDIR)/gimptooltransformgrid.Po + -rm -f ./$(DEPDIR)/gimptoolwidget.Po + -rm -f ./$(DEPDIR)/gimptoolwidgetgroup.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +xgen-dec: $(srcdir)/display-enums.h $(GIMP_MKENUMS) Makefile.am + $(AM_V_GEN) $(GIMP_MKENUMS) \ + --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"display-enums.h\"\n#include\"gimp-intl.h\"" \ + --fprod "\n/* enumerations from \"@basename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n" \ + --dhead " static const Gimp@Type@Desc descs[] =\n {" \ + --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \ + --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \ + $< > $@ + +# copy the generated enum file back to the source directory only if it's +# changed; otherwise, only update its timestamp, so that the recipe isn't +# executed again on the next build, however, allow this to (harmlessly) fail, +# to support building from a read-only source tree. +$(srcdir)/display-enums.c: xgen-dec + $(AM_V_GEN) if ! cmp -s $< $@; then \ + cp $< $@; \ + else \ + touch $@ 2> /dev/null \ + || true; \ + fi + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/app/display/display-enums.c b/app/display/display-enums.c new file mode 100644 index 0000000..438fc3e --- /dev/null +++ b/app/display/display-enums.c @@ -0,0 +1,592 @@ + +/* Generated data (by gimp-mkenums) */ + +#include "config.h" +#include <gio/gio.h> +#include "libgimpbase/gimpbase.h" +#include "display-enums.h" +#include"gimp-intl.h" + +/* enumerations from "display-enums.h" */ +GType +gimp_button_press_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_BUTTON_PRESS_NORMAL, "GIMP_BUTTON_PRESS_NORMAL", "normal" }, + { GIMP_BUTTON_PRESS_DOUBLE, "GIMP_BUTTON_PRESS_DOUBLE", "double" }, + { GIMP_BUTTON_PRESS_TRIPLE, "GIMP_BUTTON_PRESS_TRIPLE", "triple" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_BUTTON_PRESS_NORMAL, "GIMP_BUTTON_PRESS_NORMAL", NULL }, + { GIMP_BUTTON_PRESS_DOUBLE, "GIMP_BUTTON_PRESS_DOUBLE", NULL }, + { GIMP_BUTTON_PRESS_TRIPLE, "GIMP_BUTTON_PRESS_TRIPLE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpButtonPressType", values); + gimp_type_set_translation_context (type, "button-press-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_button_release_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_BUTTON_RELEASE_NORMAL, "GIMP_BUTTON_RELEASE_NORMAL", "normal" }, + { GIMP_BUTTON_RELEASE_CANCEL, "GIMP_BUTTON_RELEASE_CANCEL", "cancel" }, + { GIMP_BUTTON_RELEASE_CLICK, "GIMP_BUTTON_RELEASE_CLICK", "click" }, + { GIMP_BUTTON_RELEASE_NO_MOTION, "GIMP_BUTTON_RELEASE_NO_MOTION", "no-motion" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_BUTTON_RELEASE_NORMAL, "GIMP_BUTTON_RELEASE_NORMAL", NULL }, + { GIMP_BUTTON_RELEASE_CANCEL, "GIMP_BUTTON_RELEASE_CANCEL", NULL }, + { GIMP_BUTTON_RELEASE_CLICK, "GIMP_BUTTON_RELEASE_CLICK", NULL }, + { GIMP_BUTTON_RELEASE_NO_MOTION, "GIMP_BUTTON_RELEASE_NO_MOTION", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpButtonReleaseType", values); + gimp_type_set_translation_context (type, "button-release-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_compass_orientation_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COMPASS_ORIENTATION_AUTO, "GIMP_COMPASS_ORIENTATION_AUTO", "auto" }, + { GIMP_COMPASS_ORIENTATION_HORIZONTAL, "GIMP_COMPASS_ORIENTATION_HORIZONTAL", "horizontal" }, + { GIMP_COMPASS_ORIENTATION_VERTICAL, "GIMP_COMPASS_ORIENTATION_VERTICAL", "vertical" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COMPASS_ORIENTATION_AUTO, NC_("compass-orientation", "Auto"), NULL }, + { GIMP_COMPASS_ORIENTATION_HORIZONTAL, NC_("compass-orientation", "Horizontal"), NULL }, + { GIMP_COMPASS_ORIENTATION_VERTICAL, NC_("compass-orientation", "Vertical"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpCompassOrientation", values); + gimp_type_set_translation_context (type, "compass-orientation"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_cursor_precision_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_CURSOR_PRECISION_PIXEL_CENTER, "GIMP_CURSOR_PRECISION_PIXEL_CENTER", "pixel-center" }, + { GIMP_CURSOR_PRECISION_PIXEL_BORDER, "GIMP_CURSOR_PRECISION_PIXEL_BORDER", "pixel-border" }, + { GIMP_CURSOR_PRECISION_SUBPIXEL, "GIMP_CURSOR_PRECISION_SUBPIXEL", "subpixel" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_CURSOR_PRECISION_PIXEL_CENTER, "GIMP_CURSOR_PRECISION_PIXEL_CENTER", NULL }, + { GIMP_CURSOR_PRECISION_PIXEL_BORDER, "GIMP_CURSOR_PRECISION_PIXEL_BORDER", NULL }, + { GIMP_CURSOR_PRECISION_SUBPIXEL, "GIMP_CURSOR_PRECISION_SUBPIXEL", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpCursorPrecision", values); + gimp_type_set_translation_context (type, "cursor-precision"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_guides_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_GUIDES_NONE, "GIMP_GUIDES_NONE", "none" }, + { GIMP_GUIDES_CENTER_LINES, "GIMP_GUIDES_CENTER_LINES", "center-lines" }, + { GIMP_GUIDES_THIRDS, "GIMP_GUIDES_THIRDS", "thirds" }, + { GIMP_GUIDES_FIFTHS, "GIMP_GUIDES_FIFTHS", "fifths" }, + { GIMP_GUIDES_GOLDEN, "GIMP_GUIDES_GOLDEN", "golden" }, + { GIMP_GUIDES_DIAGONALS, "GIMP_GUIDES_DIAGONALS", "diagonals" }, + { GIMP_GUIDES_N_LINES, "GIMP_GUIDES_N_LINES", "n-lines" }, + { GIMP_GUIDES_SPACING, "GIMP_GUIDES_SPACING", "spacing" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_GUIDES_NONE, NC_("guides-type", "No guides"), NULL }, + { GIMP_GUIDES_CENTER_LINES, NC_("guides-type", "Center lines"), NULL }, + { GIMP_GUIDES_THIRDS, NC_("guides-type", "Rule of thirds"), NULL }, + { GIMP_GUIDES_FIFTHS, NC_("guides-type", "Rule of fifths"), NULL }, + { GIMP_GUIDES_GOLDEN, NC_("guides-type", "Golden sections"), NULL }, + { GIMP_GUIDES_DIAGONALS, NC_("guides-type", "Diagonal lines"), NULL }, + { GIMP_GUIDES_N_LINES, NC_("guides-type", "Number of lines"), NULL }, + { GIMP_GUIDES_SPACING, NC_("guides-type", "Line spacing"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpGuidesType", values); + gimp_type_set_translation_context (type, "guides-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_handle_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_HANDLE_SQUARE, "GIMP_HANDLE_SQUARE", "square" }, + { GIMP_HANDLE_DASHED_SQUARE, "GIMP_HANDLE_DASHED_SQUARE", "dashed-square" }, + { GIMP_HANDLE_FILLED_SQUARE, "GIMP_HANDLE_FILLED_SQUARE", "filled-square" }, + { GIMP_HANDLE_CIRCLE, "GIMP_HANDLE_CIRCLE", "circle" }, + { GIMP_HANDLE_DASHED_CIRCLE, "GIMP_HANDLE_DASHED_CIRCLE", "dashed-circle" }, + { GIMP_HANDLE_FILLED_CIRCLE, "GIMP_HANDLE_FILLED_CIRCLE", "filled-circle" }, + { GIMP_HANDLE_DIAMOND, "GIMP_HANDLE_DIAMOND", "diamond" }, + { GIMP_HANDLE_DASHED_DIAMOND, "GIMP_HANDLE_DASHED_DIAMOND", "dashed-diamond" }, + { GIMP_HANDLE_FILLED_DIAMOND, "GIMP_HANDLE_FILLED_DIAMOND", "filled-diamond" }, + { GIMP_HANDLE_CROSS, "GIMP_HANDLE_CROSS", "cross" }, + { GIMP_HANDLE_CROSSHAIR, "GIMP_HANDLE_CROSSHAIR", "crosshair" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_HANDLE_SQUARE, "GIMP_HANDLE_SQUARE", NULL }, + { GIMP_HANDLE_DASHED_SQUARE, "GIMP_HANDLE_DASHED_SQUARE", NULL }, + { GIMP_HANDLE_FILLED_SQUARE, "GIMP_HANDLE_FILLED_SQUARE", NULL }, + { GIMP_HANDLE_CIRCLE, "GIMP_HANDLE_CIRCLE", NULL }, + { GIMP_HANDLE_DASHED_CIRCLE, "GIMP_HANDLE_DASHED_CIRCLE", NULL }, + { GIMP_HANDLE_FILLED_CIRCLE, "GIMP_HANDLE_FILLED_CIRCLE", NULL }, + { GIMP_HANDLE_DIAMOND, "GIMP_HANDLE_DIAMOND", NULL }, + { GIMP_HANDLE_DASHED_DIAMOND, "GIMP_HANDLE_DASHED_DIAMOND", NULL }, + { GIMP_HANDLE_FILLED_DIAMOND, "GIMP_HANDLE_FILLED_DIAMOND", NULL }, + { GIMP_HANDLE_CROSS, "GIMP_HANDLE_CROSS", NULL }, + { GIMP_HANDLE_CROSSHAIR, "GIMP_HANDLE_CROSSHAIR", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpHandleType", values); + gimp_type_set_translation_context (type, "handle-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_handle_anchor_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_HANDLE_ANCHOR_CENTER, "GIMP_HANDLE_ANCHOR_CENTER", "center" }, + { GIMP_HANDLE_ANCHOR_NORTH, "GIMP_HANDLE_ANCHOR_NORTH", "north" }, + { GIMP_HANDLE_ANCHOR_NORTH_WEST, "GIMP_HANDLE_ANCHOR_NORTH_WEST", "north-west" }, + { GIMP_HANDLE_ANCHOR_NORTH_EAST, "GIMP_HANDLE_ANCHOR_NORTH_EAST", "north-east" }, + { GIMP_HANDLE_ANCHOR_SOUTH, "GIMP_HANDLE_ANCHOR_SOUTH", "south" }, + { GIMP_HANDLE_ANCHOR_SOUTH_WEST, "GIMP_HANDLE_ANCHOR_SOUTH_WEST", "south-west" }, + { GIMP_HANDLE_ANCHOR_SOUTH_EAST, "GIMP_HANDLE_ANCHOR_SOUTH_EAST", "south-east" }, + { GIMP_HANDLE_ANCHOR_WEST, "GIMP_HANDLE_ANCHOR_WEST", "west" }, + { GIMP_HANDLE_ANCHOR_EAST, "GIMP_HANDLE_ANCHOR_EAST", "east" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_HANDLE_ANCHOR_CENTER, "GIMP_HANDLE_ANCHOR_CENTER", NULL }, + { GIMP_HANDLE_ANCHOR_NORTH, "GIMP_HANDLE_ANCHOR_NORTH", NULL }, + { GIMP_HANDLE_ANCHOR_NORTH_WEST, "GIMP_HANDLE_ANCHOR_NORTH_WEST", NULL }, + { GIMP_HANDLE_ANCHOR_NORTH_EAST, "GIMP_HANDLE_ANCHOR_NORTH_EAST", NULL }, + { GIMP_HANDLE_ANCHOR_SOUTH, "GIMP_HANDLE_ANCHOR_SOUTH", NULL }, + { GIMP_HANDLE_ANCHOR_SOUTH_WEST, "GIMP_HANDLE_ANCHOR_SOUTH_WEST", NULL }, + { GIMP_HANDLE_ANCHOR_SOUTH_EAST, "GIMP_HANDLE_ANCHOR_SOUTH_EAST", NULL }, + { GIMP_HANDLE_ANCHOR_WEST, "GIMP_HANDLE_ANCHOR_WEST", NULL }, + { GIMP_HANDLE_ANCHOR_EAST, "GIMP_HANDLE_ANCHOR_EAST", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpHandleAnchor", values); + gimp_type_set_translation_context (type, "handle-anchor"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_limit_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_LIMIT_CIRCLE, "GIMP_LIMIT_CIRCLE", "circle" }, + { GIMP_LIMIT_SQUARE, "GIMP_LIMIT_SQUARE", "square" }, + { GIMP_LIMIT_DIAMOND, "GIMP_LIMIT_DIAMOND", "diamond" }, + { GIMP_LIMIT_HORIZONTAL, "GIMP_LIMIT_HORIZONTAL", "horizontal" }, + { GIMP_LIMIT_VERTICAL, "GIMP_LIMIT_VERTICAL", "vertical" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_LIMIT_CIRCLE, "GIMP_LIMIT_CIRCLE", NULL }, + { GIMP_LIMIT_SQUARE, "GIMP_LIMIT_SQUARE", NULL }, + { GIMP_LIMIT_DIAMOND, "GIMP_LIMIT_DIAMOND", NULL }, + { GIMP_LIMIT_HORIZONTAL, "GIMP_LIMIT_HORIZONTAL", NULL }, + { GIMP_LIMIT_VERTICAL, "GIMP_LIMIT_VERTICAL", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpLimitType", values); + gimp_type_set_translation_context (type, "limit-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_path_style_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_PATH_STYLE_DEFAULT, "GIMP_PATH_STYLE_DEFAULT", "default" }, + { GIMP_PATH_STYLE_VECTORS, "GIMP_PATH_STYLE_VECTORS", "vectors" }, + { GIMP_PATH_STYLE_OUTLINE, "GIMP_PATH_STYLE_OUTLINE", "outline" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_PATH_STYLE_DEFAULT, "GIMP_PATH_STYLE_DEFAULT", NULL }, + { GIMP_PATH_STYLE_VECTORS, "GIMP_PATH_STYLE_VECTORS", NULL }, + { GIMP_PATH_STYLE_OUTLINE, "GIMP_PATH_STYLE_OUTLINE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpPathStyle", values); + gimp_type_set_translation_context (type, "path-style"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_rectangle_constraint_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_RECTANGLE_CONSTRAIN_NONE, "GIMP_RECTANGLE_CONSTRAIN_NONE", "none" }, + { GIMP_RECTANGLE_CONSTRAIN_IMAGE, "GIMP_RECTANGLE_CONSTRAIN_IMAGE", "image" }, + { GIMP_RECTANGLE_CONSTRAIN_DRAWABLE, "GIMP_RECTANGLE_CONSTRAIN_DRAWABLE", "drawable" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_RECTANGLE_CONSTRAIN_NONE, "GIMP_RECTANGLE_CONSTRAIN_NONE", NULL }, + { GIMP_RECTANGLE_CONSTRAIN_IMAGE, "GIMP_RECTANGLE_CONSTRAIN_IMAGE", NULL }, + { GIMP_RECTANGLE_CONSTRAIN_DRAWABLE, "GIMP_RECTANGLE_CONSTRAIN_DRAWABLE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpRectangleConstraint", values); + gimp_type_set_translation_context (type, "rectangle-constraint"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_rectangle_fixed_rule_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_RECTANGLE_FIXED_ASPECT, "GIMP_RECTANGLE_FIXED_ASPECT", "aspect" }, + { GIMP_RECTANGLE_FIXED_WIDTH, "GIMP_RECTANGLE_FIXED_WIDTH", "width" }, + { GIMP_RECTANGLE_FIXED_HEIGHT, "GIMP_RECTANGLE_FIXED_HEIGHT", "height" }, + { GIMP_RECTANGLE_FIXED_SIZE, "GIMP_RECTANGLE_FIXED_SIZE", "size" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_RECTANGLE_FIXED_ASPECT, NC_("rectangle-fixed-rule", "Aspect ratio"), NULL }, + { GIMP_RECTANGLE_FIXED_WIDTH, NC_("rectangle-fixed-rule", "Width"), NULL }, + { GIMP_RECTANGLE_FIXED_HEIGHT, NC_("rectangle-fixed-rule", "Height"), NULL }, + { GIMP_RECTANGLE_FIXED_SIZE, NC_("rectangle-fixed-rule", "Size"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpRectangleFixedRule", values); + gimp_type_set_translation_context (type, "rectangle-fixed-rule"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_rectangle_precision_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_RECTANGLE_PRECISION_INT, "GIMP_RECTANGLE_PRECISION_INT", "int" }, + { GIMP_RECTANGLE_PRECISION_DOUBLE, "GIMP_RECTANGLE_PRECISION_DOUBLE", "double" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_RECTANGLE_PRECISION_INT, "GIMP_RECTANGLE_PRECISION_INT", NULL }, + { GIMP_RECTANGLE_PRECISION_DOUBLE, "GIMP_RECTANGLE_PRECISION_DOUBLE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpRectanglePrecision", values); + gimp_type_set_translation_context (type, "rectangle-precision"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_transform_3d_mode_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_TRANSFORM_3D_MODE_CAMERA, "GIMP_TRANSFORM_3D_MODE_CAMERA", "camera" }, + { GIMP_TRANSFORM_3D_MODE_MOVE, "GIMP_TRANSFORM_3D_MODE_MOVE", "move" }, + { GIMP_TRANSFORM_3D_MODE_ROTATE, "GIMP_TRANSFORM_3D_MODE_ROTATE", "rotate" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_TRANSFORM_3D_MODE_CAMERA, "GIMP_TRANSFORM_3D_MODE_CAMERA", NULL }, + { GIMP_TRANSFORM_3D_MODE_MOVE, "GIMP_TRANSFORM_3D_MODE_MOVE", NULL }, + { GIMP_TRANSFORM_3D_MODE_ROTATE, "GIMP_TRANSFORM_3D_MODE_ROTATE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpTransform3DMode", values); + gimp_type_set_translation_context (type, "transform3-dmode"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_transform_function_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_TRANSFORM_FUNCTION_NONE, "GIMP_TRANSFORM_FUNCTION_NONE", "none" }, + { GIMP_TRANSFORM_FUNCTION_MOVE, "GIMP_TRANSFORM_FUNCTION_MOVE", "move" }, + { GIMP_TRANSFORM_FUNCTION_SCALE, "GIMP_TRANSFORM_FUNCTION_SCALE", "scale" }, + { GIMP_TRANSFORM_FUNCTION_ROTATE, "GIMP_TRANSFORM_FUNCTION_ROTATE", "rotate" }, + { GIMP_TRANSFORM_FUNCTION_SHEAR, "GIMP_TRANSFORM_FUNCTION_SHEAR", "shear" }, + { GIMP_TRANSFORM_FUNCTION_PERSPECTIVE, "GIMP_TRANSFORM_FUNCTION_PERSPECTIVE", "perspective" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_TRANSFORM_FUNCTION_NONE, "GIMP_TRANSFORM_FUNCTION_NONE", NULL }, + { GIMP_TRANSFORM_FUNCTION_MOVE, "GIMP_TRANSFORM_FUNCTION_MOVE", NULL }, + { GIMP_TRANSFORM_FUNCTION_SCALE, "GIMP_TRANSFORM_FUNCTION_SCALE", NULL }, + { GIMP_TRANSFORM_FUNCTION_ROTATE, "GIMP_TRANSFORM_FUNCTION_ROTATE", NULL }, + { GIMP_TRANSFORM_FUNCTION_SHEAR, "GIMP_TRANSFORM_FUNCTION_SHEAR", NULL }, + { GIMP_TRANSFORM_FUNCTION_PERSPECTIVE, "GIMP_TRANSFORM_FUNCTION_PERSPECTIVE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpTransformFunction", values); + gimp_type_set_translation_context (type, "transform-function"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_transform_handle_mode_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_HANDLE_MODE_ADD_TRANSFORM, "GIMP_HANDLE_MODE_ADD_TRANSFORM", "add-transform" }, + { GIMP_HANDLE_MODE_MOVE, "GIMP_HANDLE_MODE_MOVE", "move" }, + { GIMP_HANDLE_MODE_REMOVE, "GIMP_HANDLE_MODE_REMOVE", "remove" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_HANDLE_MODE_ADD_TRANSFORM, NC_("transform-handle-mode", "Add / Transform"), NULL }, + { GIMP_HANDLE_MODE_MOVE, NC_("transform-handle-mode", "Move"), NULL }, + { GIMP_HANDLE_MODE_REMOVE, NC_("transform-handle-mode", "Remove"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpTransformHandleMode", values); + gimp_type_set_translation_context (type, "transform-handle-mode"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_vector_mode_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_VECTOR_MODE_DESIGN, "GIMP_VECTOR_MODE_DESIGN", "design" }, + { GIMP_VECTOR_MODE_EDIT, "GIMP_VECTOR_MODE_EDIT", "edit" }, + { GIMP_VECTOR_MODE_MOVE, "GIMP_VECTOR_MODE_MOVE", "move" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_VECTOR_MODE_DESIGN, NC_("vector-mode", "Design"), NULL }, + { GIMP_VECTOR_MODE_EDIT, NC_("vector-mode", "Edit"), NULL }, + { GIMP_VECTOR_MODE_MOVE, NC_("vector-mode", "Move"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpVectorMode", values); + gimp_type_set_translation_context (type, "vector-mode"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_zoom_focus_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_ZOOM_FOCUS_BEST_GUESS, "GIMP_ZOOM_FOCUS_BEST_GUESS", "best-guess" }, + { GIMP_ZOOM_FOCUS_POINTER, "GIMP_ZOOM_FOCUS_POINTER", "pointer" }, + { GIMP_ZOOM_FOCUS_IMAGE_CENTER, "GIMP_ZOOM_FOCUS_IMAGE_CENTER", "image-center" }, + { GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS, "GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS", "retain-centering-else-best-guess" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_ZOOM_FOCUS_BEST_GUESS, "GIMP_ZOOM_FOCUS_BEST_GUESS", NULL }, + { GIMP_ZOOM_FOCUS_POINTER, "GIMP_ZOOM_FOCUS_POINTER", NULL }, + { GIMP_ZOOM_FOCUS_IMAGE_CENTER, "GIMP_ZOOM_FOCUS_IMAGE_CENTER", NULL }, + { GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS, "GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpZoomFocus", values); + gimp_type_set_translation_context (type, "zoom-focus"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + + +/* Generated data ends here */ + diff --git a/app/display/display-enums.h b/app/display/display-enums.h new file mode 100644 index 0000000..4cc84a8 --- /dev/null +++ b/app/display/display-enums.h @@ -0,0 +1,275 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __DISPLAY_ENUMS_H__ +#define __DISPLAY_ENUMS_H__ + + +#define GIMP_TYPE_BUTTON_PRESS_TYPE (gimp_button_press_type_get_type ()) + +GType gimp_button_press_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_BUTTON_PRESS_NORMAL, + GIMP_BUTTON_PRESS_DOUBLE, + GIMP_BUTTON_PRESS_TRIPLE +} GimpButtonPressType; + + +#define GIMP_TYPE_BUTTON_RELEASE_TYPE (gimp_button_release_type_get_type ()) + +GType gimp_button_release_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_BUTTON_RELEASE_NORMAL, + GIMP_BUTTON_RELEASE_CANCEL, + GIMP_BUTTON_RELEASE_CLICK, + GIMP_BUTTON_RELEASE_NO_MOTION +} GimpButtonReleaseType; + + +#define GIMP_TYPE_COMPASS_ORIENTATION (gimp_compass_orientation_get_type ()) + +GType gimp_compass_orientation_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COMPASS_ORIENTATION_AUTO, /*< desc="Auto" >*/ + GIMP_COMPASS_ORIENTATION_HORIZONTAL, /*< desc="Horizontal" >*/ + GIMP_COMPASS_ORIENTATION_VERTICAL /*< desc="Vertical" >*/ +} GimpCompassOrientation; + + +#define GIMP_TYPE_CURSOR_PRECISION (gimp_cursor_precision_get_type ()) + +GType gimp_cursor_precision_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_CURSOR_PRECISION_PIXEL_CENTER, + GIMP_CURSOR_PRECISION_PIXEL_BORDER, + GIMP_CURSOR_PRECISION_SUBPIXEL +} GimpCursorPrecision; + + +#define GIMP_TYPE_GUIDES_TYPE (gimp_guides_type_get_type ()) + +GType gimp_guides_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_GUIDES_NONE, /*< desc="No guides" >*/ + GIMP_GUIDES_CENTER_LINES, /*< desc="Center lines" >*/ + GIMP_GUIDES_THIRDS, /*< desc="Rule of thirds" >*/ + GIMP_GUIDES_FIFTHS, /*< desc="Rule of fifths" >*/ + GIMP_GUIDES_GOLDEN, /*< desc="Golden sections" >*/ + GIMP_GUIDES_DIAGONALS, /*< desc="Diagonal lines" >*/ + GIMP_GUIDES_N_LINES, /*< desc="Number of lines" >*/ + GIMP_GUIDES_SPACING /*< desc="Line spacing" >*/ +} GimpGuidesType; + + +#define GIMP_TYPE_HANDLE_TYPE (gimp_handle_type_get_type ()) + +GType gimp_handle_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_HANDLE_SQUARE, + GIMP_HANDLE_DASHED_SQUARE, + GIMP_HANDLE_FILLED_SQUARE, + GIMP_HANDLE_CIRCLE, + GIMP_HANDLE_DASHED_CIRCLE, + GIMP_HANDLE_FILLED_CIRCLE, + GIMP_HANDLE_DIAMOND, + GIMP_HANDLE_DASHED_DIAMOND, + GIMP_HANDLE_FILLED_DIAMOND, + GIMP_HANDLE_CROSS, + GIMP_HANDLE_CROSSHAIR +} GimpHandleType; + + +#define GIMP_TYPE_HANDLE_ANCHOR (gimp_handle_anchor_get_type ()) + +GType gimp_handle_anchor_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_HANDLE_ANCHOR_CENTER, + GIMP_HANDLE_ANCHOR_NORTH, + GIMP_HANDLE_ANCHOR_NORTH_WEST, + GIMP_HANDLE_ANCHOR_NORTH_EAST, + GIMP_HANDLE_ANCHOR_SOUTH, + GIMP_HANDLE_ANCHOR_SOUTH_WEST, + GIMP_HANDLE_ANCHOR_SOUTH_EAST, + GIMP_HANDLE_ANCHOR_WEST, + GIMP_HANDLE_ANCHOR_EAST +} GimpHandleAnchor; + + +#define GIMP_TYPE_LIMIT_TYPE (gimp_limit_type_get_type ()) + +GType gimp_limit_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_LIMIT_CIRCLE, + GIMP_LIMIT_SQUARE, + GIMP_LIMIT_DIAMOND, + GIMP_LIMIT_HORIZONTAL, + GIMP_LIMIT_VERTICAL +} GimpLimitType; + + +#define GIMP_TYPE_PATH_STYLE (gimp_path_style_get_type ()) + +GType gimp_path_style_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_PATH_STYLE_DEFAULT, + GIMP_PATH_STYLE_VECTORS, + GIMP_PATH_STYLE_OUTLINE +} GimpPathStyle; + + +#define GIMP_TYPE_RECTANGLE_CONSTRAINT (gimp_rectangle_constraint_get_type ()) + +GType gimp_rectangle_constraint_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_RECTANGLE_CONSTRAIN_NONE, + GIMP_RECTANGLE_CONSTRAIN_IMAGE, + GIMP_RECTANGLE_CONSTRAIN_DRAWABLE +} GimpRectangleConstraint; + + +#define GIMP_TYPE_RECTANGLE_FIXED_RULE (gimp_rectangle_fixed_rule_get_type ()) + +GType gimp_rectangle_fixed_rule_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_RECTANGLE_FIXED_ASPECT, /*< desc="Aspect ratio" >*/ + GIMP_RECTANGLE_FIXED_WIDTH, /*< desc="Width" >*/ + GIMP_RECTANGLE_FIXED_HEIGHT, /*< desc="Height" >*/ + GIMP_RECTANGLE_FIXED_SIZE, /*< desc="Size" >*/ +} GimpRectangleFixedRule; + + +#define GIMP_TYPE_RECTANGLE_PRECISION (gimp_rectangle_precision_get_type ()) + +GType gimp_rectangle_precision_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_RECTANGLE_PRECISION_INT, + GIMP_RECTANGLE_PRECISION_DOUBLE, +} GimpRectanglePrecision; + + +#define GIMP_TYPE_TRANSFORM_3D_MODE (gimp_transform_3d_mode_get_type ()) + +GType gimp_transform_3d_mode_get_type (void) G_GNUC_CONST; + +typedef enum /*< lowercase_name=gimp_transform_3d_mode >*/ +{ + GIMP_TRANSFORM_3D_MODE_CAMERA, + GIMP_TRANSFORM_3D_MODE_MOVE, + GIMP_TRANSFORM_3D_MODE_ROTATE +} GimpTransform3DMode; + + +#define GIMP_TYPE_TRANSFORM_FUNCTION (gimp_transform_function_get_type ()) + +GType gimp_transform_function_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_TRANSFORM_FUNCTION_NONE, + GIMP_TRANSFORM_FUNCTION_MOVE, + GIMP_TRANSFORM_FUNCTION_SCALE, + GIMP_TRANSFORM_FUNCTION_ROTATE, + GIMP_TRANSFORM_FUNCTION_SHEAR, + GIMP_TRANSFORM_FUNCTION_PERSPECTIVE +} GimpTransformFunction; + + +#define GIMP_TYPE_TRANSFORM_HANDLE_MODE (gimp_transform_handle_mode_get_type ()) + +GType gimp_transform_handle_mode_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_HANDLE_MODE_ADD_TRANSFORM, /*< desc="Add / Transform" >*/ + GIMP_HANDLE_MODE_MOVE, /*< desc="Move" >*/ + GIMP_HANDLE_MODE_REMOVE /*< desc="Remove" >*/ +} GimpTransformHandleMode; + + +#define GIMP_TYPE_VECTOR_MODE (gimp_vector_mode_get_type ()) + +GType gimp_vector_mode_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_VECTOR_MODE_DESIGN, /*< desc="Design" >*/ + GIMP_VECTOR_MODE_EDIT, /*< desc="Edit" >*/ + GIMP_VECTOR_MODE_MOVE /*< desc="Move" >*/ +} GimpVectorMode; + + +#define GIMP_TYPE_ZOOM_FOCUS (gimp_zoom_focus_get_type ()) + +GType gimp_zoom_focus_get_type (void) G_GNUC_CONST; + +typedef enum +{ + /* Make a best guess */ + GIMP_ZOOM_FOCUS_BEST_GUESS, + + /* Use the mouse cursor (if within canvas) */ + GIMP_ZOOM_FOCUS_POINTER, + + /* Use the image center */ + GIMP_ZOOM_FOCUS_IMAGE_CENTER, + + /* If the image is centered, retain the centering. Else use + * _BEST_GUESS + */ + GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS + +} GimpZoomFocus; + + +/* + * non-registered enums; register them if needed + */ + + +typedef enum /*< pdb-skip, skip >*/ +{ + GIMP_HIT_NONE, + GIMP_HIT_INDIRECT, + GIMP_HIT_DIRECT +} GimpHit; + + +#endif /* __DISPLAY_ENUMS_H__ */ diff --git a/app/display/display-types.h b/app/display/display-types.h new file mode 100644 index 0000000..6de751d --- /dev/null +++ b/app/display/display-types.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __DISPLAY_TYPES_H__ +#define __DISPLAY_TYPES_H__ + + +#include "propgui/propgui-types.h" + +#include "display/display-enums.h" + + +typedef struct _GimpCanvas GimpCanvas; +typedef struct _GimpCanvasGroup GimpCanvasGroup; +typedef struct _GimpCanvasItem GimpCanvasItem; + +typedef struct _GimpDisplay GimpDisplay; +typedef struct _GimpDisplayShell GimpDisplayShell; +typedef struct _GimpMotionBuffer GimpMotionBuffer; + +typedef struct _GimpImageWindow GimpImageWindow; +typedef struct _GimpMultiWindowStrategy GimpMultiWindowStrategy; +typedef struct _GimpSingleWindowStrategy GimpSingleWindowStrategy; + +typedef struct _GimpCursorView GimpCursorView; +typedef struct _GimpNavigationEditor GimpNavigationEditor; +typedef struct _GimpScaleComboBox GimpScaleComboBox; +typedef struct _GimpStatusbar GimpStatusbar; + +typedef struct _GimpToolDialog GimpToolDialog; +typedef struct _GimpToolGui GimpToolGui; +typedef struct _GimpToolWidget GimpToolWidget; +typedef struct _GimpToolWidgetGroup GimpToolWidgetGroup; + +typedef struct _GimpDisplayXfer GimpDisplayXfer; +typedef struct _Selection Selection; + + +#endif /* __DISPLAY_TYPES_H__ */ diff --git a/app/display/gimpcanvas-style.c b/app/display/gimpcanvas-style.c new file mode 100644 index 0000000..8d47961 --- /dev/null +++ b/app/display/gimpcanvas-style.c @@ -0,0 +1,458 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvas-style.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" +#include "core/gimpgrid.h" +#include "core/gimplayer.h" + +#include "gimpcanvas-style.h" + +/* Styles for common and custom guides. */ +static const GimpRGB guide_normal_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB guide_normal_bg = { 0.0, 0.8, 1.0, 1.0 }; +static const GimpRGB guide_active_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB guide_active_bg = { 1.0, 0.0, 0.0, 1.0 }; + +static const GimpRGB guide_mirror_normal_fg = { 1.0, 1.0, 1.0, 1.0 }; +static const GimpRGB guide_mirror_normal_bg = { 0.0, 1.0, 0.0, 1.0 }; +static const GimpRGB guide_mirror_active_fg = { 0.0, 1.0, 0.0, 1.0 }; +static const GimpRGB guide_mirror_active_bg = { 1.0, 0.0, 0.0, 1.0 }; + +static const GimpRGB guide_mandala_normal_fg = { 1.0, 1.0, 1.0, 1.0 }; +static const GimpRGB guide_mandala_normal_bg = { 0.0, 1.0, 1.0, 1.0 }; +static const GimpRGB guide_mandala_active_fg = { 0.0, 1.0, 1.0, 1.0 }; +static const GimpRGB guide_mandala_active_bg = { 1.0, 0.0, 0.0, 1.0 }; + +static const GimpRGB guide_split_normal_fg = { 1.0, 1.0, 1.0, 1.0 }; +static const GimpRGB guide_split_normal_bg = { 1.0, 0.0, 1.0, 1.0 }; +static const GimpRGB guide_split_active_fg = { 1.0, 0.0, 1.0, 1.0 }; +static const GimpRGB guide_split_active_bg = { 1.0, 0.0, 0.0, 1.0 }; + +/* Styles for other canvas items. */ +static const GimpRGB sample_point_normal = { 0.0, 0.8, 1.0, 1.0 }; +static const GimpRGB sample_point_active = { 1.0, 0.0, 0.0, 1.0 }; + +static const GimpRGB layer_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB layer_bg = { 1.0, 1.0, 0.0, 1.0 }; + +static const GimpRGB layer_group_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB layer_group_bg = { 0.0, 1.0, 1.0, 1.0 }; + +static const GimpRGB layer_mask_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB layer_mask_bg = { 0.0, 1.0, 0.0, 1.0 }; + +static const GimpRGB canvas_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB canvas_bg = { 1.0, 0.5, 0.0, 1.0 }; + +static const GimpRGB selection_out_fg = { 1.0, 1.0, 1.0, 1.0 }; +static const GimpRGB selection_out_bg = { 0.5, 0.5, 0.5, 1.0 }; + +static const GimpRGB selection_in_fg = { 0.0, 0.0, 0.0, 1.0 }; +static const GimpRGB selection_in_bg = { 1.0, 1.0, 1.0, 1.0 }; + +static const GimpRGB vectors_normal_bg = { 1.0, 1.0, 1.0, 0.6 }; +static const GimpRGB vectors_normal_fg = { 0.0, 0.0, 1.0, 0.8 }; + +static const GimpRGB vectors_active_bg = { 1.0, 1.0, 1.0, 0.6 }; +static const GimpRGB vectors_active_fg = { 1.0, 0.0, 0.0, 0.8 }; + +static const GimpRGB outline_bg = { 1.0, 1.0, 1.0, 0.6 }; +static const GimpRGB outline_fg = { 0.0, 0.0, 0.0, 0.8 }; + +static const GimpRGB passe_partout = { 0.0, 0.0, 0.0, 1.0 }; + +static const GimpRGB tool_bg = { 0.0, 0.0, 0.0, 0.4 }; +static const GimpRGB tool_fg = { 1.0, 1.0, 1.0, 0.8 }; +static const GimpRGB tool_fg_highlight = { 1.0, 0.8, 0.2, 0.8 }; + + +/* public functions */ + +void +gimp_canvas_set_guide_style (GtkWidget *canvas, + cairo_t *cr, + GimpGuideStyle style, + gboolean active, + gdouble offset_x, + gdouble offset_y) +{ + cairo_pattern_t *pattern; + GimpRGB normal_fg; + GimpRGB normal_bg; + GimpRGB active_fg; + GimpRGB active_bg; + gdouble line_width; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + switch (style) + { + case GIMP_GUIDE_STYLE_NORMAL: + normal_fg = guide_normal_fg; + normal_bg = guide_normal_bg; + active_fg = guide_active_fg; + active_bg = guide_active_bg; + line_width = 1.0; + break; + + case GIMP_GUIDE_STYLE_MIRROR: + normal_fg = guide_mirror_normal_fg; + normal_bg = guide_mirror_normal_bg; + active_fg = guide_mirror_active_fg; + active_bg = guide_mirror_active_bg; + line_width = 1.0; + break; + + case GIMP_GUIDE_STYLE_MANDALA: + normal_fg = guide_mandala_normal_fg; + normal_bg = guide_mandala_normal_bg; + active_fg = guide_mandala_active_fg; + active_bg = guide_mandala_active_bg; + line_width = 1.0; + break; + + case GIMP_GUIDE_STYLE_SPLIT_VIEW: + normal_fg = guide_split_normal_fg; + normal_bg = guide_split_normal_bg; + active_fg = guide_split_active_fg; + active_bg = guide_split_active_bg; + line_width = 1.0; + break; + + default: /* GIMP_GUIDE_STYLE_NONE */ + /* This should not happen. */ + g_return_if_reached (); + } + + cairo_set_line_width (cr, line_width); + + if (active) + pattern = gimp_cairo_pattern_create_stipple (&active_fg, &active_bg, 0, + offset_x, offset_y); + else + pattern = gimp_cairo_pattern_create_stipple (&normal_fg, &normal_bg, 0, + offset_x, offset_y); + + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); +} + +void +gimp_canvas_set_sample_point_style (GtkWidget *canvas, + cairo_t *cr, + gboolean active) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + + if (active) + gimp_cairo_set_source_rgb (cr, &sample_point_active); + else + gimp_cairo_set_source_rgb (cr, &sample_point_normal); +} + +void +gimp_canvas_set_grid_style (GtkWidget *canvas, + cairo_t *cr, + GimpGrid *grid, + gdouble offset_x, + gdouble offset_y) +{ + GimpRGB fg; + GimpRGB bg; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + g_return_if_fail (GIMP_IS_GRID (grid)); + + cairo_set_line_width (cr, 1.0); + + gimp_grid_get_fgcolor (grid, &fg); + + switch (gimp_grid_get_style (grid)) + { + cairo_pattern_t *pattern; + + case GIMP_GRID_ON_OFF_DASH: + case GIMP_GRID_DOUBLE_DASH: + if (grid->style == GIMP_GRID_DOUBLE_DASH) + { + gimp_grid_get_bgcolor (grid, &bg); + + pattern = gimp_cairo_pattern_create_stipple (&fg, &bg, 0, + offset_x, offset_y); + } + else + { + gimp_rgba_set (&bg, 0.0, 0.0, 0.0, 0.0); + + pattern = gimp_cairo_pattern_create_stipple (&fg, &bg, 0, + offset_x, offset_y); + } + + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + break; + + case GIMP_GRID_DOTS: + case GIMP_GRID_INTERSECTIONS: + case GIMP_GRID_SOLID: + gimp_cairo_set_source_rgb (cr, &fg); + break; + } +} + +void +gimp_canvas_set_pen_style (GtkWidget *canvas, + cairo_t *cr, + const GimpRGB *color, + gint width) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + g_return_if_fail (color != NULL); + + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + cairo_set_line_width (cr, width); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + gimp_cairo_set_source_rgb (cr, color); +} + +void +gimp_canvas_set_layer_style (GtkWidget *canvas, + cairo_t *cr, + GimpLayer *layer, + gdouble offset_x, + gdouble offset_y) +{ + cairo_pattern_t *pattern; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + g_return_if_fail (GIMP_IS_LAYER (layer)); + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + + if (gimp_layer_get_mask (layer) && + gimp_layer_get_edit_mask (layer)) + { + pattern = gimp_cairo_pattern_create_stipple (&layer_mask_fg, + &layer_mask_bg, + 0, + offset_x, offset_y); + } + else if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) + { + pattern = gimp_cairo_pattern_create_stipple (&layer_group_fg, + &layer_group_bg, + 0, + offset_x, offset_y); + } + else + { + pattern = gimp_cairo_pattern_create_stipple (&layer_fg, + &layer_bg, + 0, + offset_x, offset_y); + } + + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); +} + +void +gimp_canvas_set_canvas_style (GtkWidget *canvas, + cairo_t *cr, + gdouble offset_x, + gdouble offset_y) +{ + cairo_pattern_t *pattern; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + + pattern = gimp_cairo_pattern_create_stipple (&canvas_fg, + &canvas_bg, + 0, + offset_x, offset_y); + + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); +} + +void +gimp_canvas_set_selection_out_style (GtkWidget *canvas, + cairo_t *cr, + gdouble offset_x, + gdouble offset_y) +{ + cairo_pattern_t *pattern; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + + pattern = gimp_cairo_pattern_create_stipple (&selection_out_fg, + &selection_out_bg, + 0, + offset_x, offset_y); + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); +} + +void +gimp_canvas_set_selection_in_style (GtkWidget *canvas, + cairo_t *cr, + gint index, + gdouble offset_x, + gdouble offset_y) +{ + cairo_pattern_t *pattern; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + + pattern = gimp_cairo_pattern_create_stipple (&selection_in_fg, + &selection_in_bg, + index, + offset_x, offset_y); + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); +} + +void +gimp_canvas_set_vectors_bg_style (GtkWidget *canvas, + cairo_t *cr, + gboolean active) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 3.0); + + if (active) + gimp_cairo_set_source_rgba (cr, &vectors_active_bg); + else + gimp_cairo_set_source_rgba (cr, &vectors_normal_bg); +} + +void +gimp_canvas_set_vectors_fg_style (GtkWidget *canvas, + cairo_t *cr, + gboolean active) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + + if (active) + gimp_cairo_set_source_rgba (cr, &vectors_active_fg); + else + gimp_cairo_set_source_rgba (cr, &vectors_normal_fg); +} + +void +gimp_canvas_set_outline_bg_style (GtkWidget *canvas, + cairo_t *cr) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + gimp_cairo_set_source_rgba (cr, &outline_bg); +} + +void +gimp_canvas_set_outline_fg_style (GtkWidget *canvas, + cairo_t *cr) +{ + static const double dashes[] = { 4.0, 4.0 }; + + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + gimp_cairo_set_source_rgba (cr, &outline_fg); + cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0); +} + +void +gimp_canvas_set_passe_partout_style (GtkWidget *canvas, + cairo_t *cr) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + gimp_cairo_set_source_rgba (cr, &passe_partout); +} + +void +gimp_canvas_set_tool_bg_style (GtkWidget *canvas, + cairo_t *cr) +{ + g_return_if_fail (GTK_IS_WIDGET (canvas)); + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 3.0); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + gimp_cairo_set_source_rgba (cr, &tool_bg); +} + +void +gimp_canvas_set_tool_fg_style (GtkWidget *canvas, + cairo_t *cr, + gboolean highlight) +{ + g_return_if_fail (cr != NULL); + + cairo_set_line_width (cr, 1.0); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + if (highlight) + gimp_cairo_set_source_rgba (cr, &tool_fg_highlight); + else + gimp_cairo_set_source_rgba (cr, &tool_fg); +} diff --git a/app/display/gimpcanvas-style.h b/app/display/gimpcanvas-style.h new file mode 100644 index 0000000..3a37b98 --- /dev/null +++ b/app/display/gimpcanvas-style.h @@ -0,0 +1,81 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdisplayshell-style.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_STYLE_H__ +#define __GIMP_CANVAS_STYLE_H__ + + +void gimp_canvas_set_guide_style (GtkWidget *canvas, + cairo_t *cr, + GimpGuideStyle style, + gboolean active, + gdouble offset_x, + gdouble offset_y); +void gimp_canvas_set_sample_point_style (GtkWidget *canvas, + cairo_t *cr, + gboolean active); +void gimp_canvas_set_grid_style (GtkWidget *canvas, + cairo_t *cr, + GimpGrid *grid, + gdouble offset_x, + gdouble offset_y); +void gimp_canvas_set_pen_style (GtkWidget *canvas, + cairo_t *cr, + const GimpRGB *color, + gint width); +void gimp_canvas_set_layer_style (GtkWidget *canvas, + cairo_t *cr, + GimpLayer *layer, + gdouble offset_x, + gdouble offset_y); +void gimp_canvas_set_canvas_style (GtkWidget *canvas, + cairo_t *cr, + gdouble offset_x, + gdouble offset_y); +void gimp_canvas_set_selection_out_style (GtkWidget *canvas, + cairo_t *cr, + gdouble offset_x, + gdouble offset_y); +void gimp_canvas_set_selection_in_style (GtkWidget *canvas, + cairo_t *cr, + gint index, + gdouble offset_x, + gdouble offset_y); +void gimp_canvas_set_vectors_bg_style (GtkWidget *canvas, + cairo_t *cr, + gboolean active); +void gimp_canvas_set_vectors_fg_style (GtkWidget *canvas, + cairo_t *cr, + gboolean active); +void gimp_canvas_set_outline_bg_style (GtkWidget *canvas, + cairo_t *cr); +void gimp_canvas_set_outline_fg_style (GtkWidget *canvas, + cairo_t *cr); +void gimp_canvas_set_passe_partout_style (GtkWidget *canvas, + cairo_t *cr); + +void gimp_canvas_set_tool_bg_style (GtkWidget *canvas, + cairo_t *cr); +void gimp_canvas_set_tool_fg_style (GtkWidget *canvas, + cairo_t *cr, + gboolean highlight); + + +#endif /* __GIMP_CANVAS_STYLE_H__ */ diff --git a/app/display/gimpcanvas.c b/app/display/gimpcanvas.c new file mode 100644 index 0000000..46b9ff5 --- /dev/null +++ b/app/display/gimpcanvas.c @@ -0,0 +1,298 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvas.h" + +#include "gimp-intl.h" + + +#define MAX_BATCH_SIZE 32000 + + +enum +{ + PROP_0, + PROP_CONFIG +}; + + +/* local function prototypes */ + +static void gimp_canvas_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_canvas_unrealize (GtkWidget *widget); +static void gimp_canvas_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_canvas_focus_in_event (GtkWidget *widget, + GdkEventFocus *event); +static gboolean gimp_canvas_focus_out_event (GtkWidget *widget, + GdkEventFocus *event); +static gboolean gimp_canvas_focus (GtkWidget *widget, + GtkDirectionType direction); + + +G_DEFINE_TYPE (GimpCanvas, gimp_canvas, GIMP_TYPE_OVERLAY_BOX) + +#define parent_class gimp_canvas_parent_class + + +static void +gimp_canvas_class_init (GimpCanvasClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->set_property = gimp_canvas_set_property; + object_class->get_property = gimp_canvas_get_property; + + widget_class->unrealize = gimp_canvas_unrealize; + widget_class->style_set = gimp_canvas_style_set; + widget_class->focus_in_event = gimp_canvas_focus_in_event; + widget_class->focus_out_event = gimp_canvas_focus_out_event; + widget_class->focus = gimp_canvas_focus; + + g_object_class_install_property (object_class, PROP_CONFIG, + g_param_spec_object ("config", NULL, NULL, + GIMP_TYPE_DISPLAY_CONFIG, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_canvas_init (GimpCanvas *canvas) +{ + GtkWidget *widget = GTK_WIDGET (canvas); + + gtk_widget_set_can_focus (widget, TRUE); + gtk_widget_add_events (widget, GIMP_CANVAS_EVENT_MASK); + gtk_widget_set_extension_events (widget, GDK_EXTENSION_EVENTS_ALL); +} + +static void +gimp_canvas_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvas *canvas = GIMP_CANVAS (object); + + switch (property_id) + { + case PROP_CONFIG: + canvas->config = g_value_get_object (value); /* don't dup */ + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvas *canvas = GIMP_CANVAS (object); + + switch (property_id) + { + case PROP_CONFIG: + g_value_set_object (value, canvas->config); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_unrealize (GtkWidget *widget) +{ + GimpCanvas *canvas = GIMP_CANVAS (widget); + + g_clear_object (&canvas->layout); + + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +gimp_canvas_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpCanvas *canvas = GIMP_CANVAS (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_object (&canvas->layout); +} + +static gboolean +gimp_canvas_focus_in_event (GtkWidget *widget, + GdkEventFocus *event) +{ + /* don't allow the default impl to invalidate the whole widget, + * we don't draw a focus indicator anyway. + */ + return FALSE; +} + +static gboolean +gimp_canvas_focus_out_event (GtkWidget *widget, + GdkEventFocus *event) +{ + /* see focus-in-event + */ + return FALSE; +} + +static gboolean +gimp_canvas_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkWidget *focus = gtk_container_get_focus_child (GTK_CONTAINER (widget)); + + /* override GtkContainer's focus() implementation which would always + * give focus to the canvas because it is focussable. Instead, try + * navigating in the focused overlay child first, and use + * GtkContainer's default implementation only if that fails (which + * happens when focus navigation leaves the overlay child). + */ + + if (focus && gtk_widget_child_focus (focus, direction)) + return TRUE; + + return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction); +} + + +/* public functions */ + +/** + * gimp_canvas_new: + * + * Creates a new #GimpCanvas widget. + * + * The #GimpCanvas widget is a #GtkDrawingArea abstraction. It manages + * a set of graphic contexts for drawing on a GIMP display. If you + * draw using a #GimpCanvasStyle, #GimpCanvas makes sure that the + * associated #GdkGC is created. All drawing on the canvas needs to + * happen by means of the #GimpCanvas drawing functions. Besides from + * not needing a #GdkGC pointer, the #GimpCanvas drawing functions + * look and work like their #GdkDrawable counterparts. #GimpCanvas + * gracefully handles attempts to draw on the unrealized widget. + * + * Return value: a new #GimpCanvas widget + **/ +GtkWidget * +gimp_canvas_new (GimpDisplayConfig *config) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_CONFIG (config), NULL); + + return g_object_new (GIMP_TYPE_CANVAS, + "name", "gimp-canvas", + "config", config, + NULL); +} + +/** + * gimp_canvas_get_layout: + * @canvas: a #GimpCanvas widget + * @format: a standard printf() format string. + * @Varargs: the parameters to insert into the format string. + * + * Returns a layout which can be used for + * pango_cairo_show_layout(). The layout belongs to the canvas and + * should not be freed, not should a pointer to it be kept around + * after drawing. + * + * Returns: a #PangoLayout owned by the canvas. + **/ +PangoLayout * +gimp_canvas_get_layout (GimpCanvas *canvas, + const gchar *format, + ...) +{ + va_list args; + gchar *text; + + if (! canvas->layout) + canvas->layout = gtk_widget_create_pango_layout (GTK_WIDGET (canvas), + NULL); + + va_start (args, format); + text = g_strdup_vprintf (format, args); + va_end (args); + + pango_layout_set_text (canvas->layout, text, -1); + g_free (text); + + return canvas->layout; +} + +/** + * gimp_canvas_set_bg_color: + * @canvas: a #GimpCanvas widget + * @color: a color in #GimpRGB format + * + * Sets the background color of the canvas's window. This + * is the color the canvas is set to if it is cleared. + **/ +void +gimp_canvas_set_bg_color (GimpCanvas *canvas, + GimpRGB *color) +{ + GtkWidget *widget = GTK_WIDGET (canvas); + GdkColormap *colormap; + GdkColor gdk_color; + + if (! gtk_widget_get_realized (widget)) + return; + + gimp_rgb_get_gdk_color (color, &gdk_color); + + colormap = gdk_drawable_get_colormap (gtk_widget_get_window (widget)); + g_return_if_fail (colormap != NULL); + gdk_colormap_alloc_color (colormap, &gdk_color, FALSE, TRUE); + + gdk_window_set_background (gtk_widget_get_window (widget), &gdk_color); + + gtk_widget_queue_draw (GTK_WIDGET (canvas)); +} diff --git a/app/display/gimpcanvas.h b/app/display/gimpcanvas.h new file mode 100644 index 0000000..dc52be8 --- /dev/null +++ b/app/display/gimpcanvas.h @@ -0,0 +1,74 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_H__ +#define __GIMP_CANVAS_H__ + + +#include "widgets/gimpoverlaybox.h" + + +#define GIMP_CANVAS_EVENT_MASK (GDK_EXPOSURE_MASK | \ + GDK_POINTER_MOTION_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK | \ + GDK_STRUCTURE_MASK | \ + GDK_ENTER_NOTIFY_MASK | \ + GDK_LEAVE_NOTIFY_MASK | \ + GDK_FOCUS_CHANGE_MASK | \ + GDK_KEY_PRESS_MASK | \ + GDK_KEY_RELEASE_MASK | \ + GDK_PROXIMITY_OUT_MASK) + + +#define GIMP_TYPE_CANVAS (gimp_canvas_get_type ()) +#define GIMP_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS, GimpCanvas)) +#define GIMP_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS, GimpCanvasClass)) +#define GIMP_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS)) +#define GIMP_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS)) +#define GIMP_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS, GimpCanvasClass)) + + +typedef struct _GimpCanvasClass GimpCanvasClass; + +struct _GimpCanvas +{ + GimpOverlayBox parent_instance; + + GimpDisplayConfig *config; + PangoLayout *layout; +}; + +struct _GimpCanvasClass +{ + GimpOverlayBoxClass parent_class; +}; + + +GType gimp_canvas_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_canvas_new (GimpDisplayConfig *config); + +PangoLayout * gimp_canvas_get_layout (GimpCanvas *canvas, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); + +void gimp_canvas_set_bg_color (GimpCanvas *canvas, + GimpRGB *color); + + +#endif /* __GIMP_CANVAS_H__ */ diff --git a/app/display/gimpcanvasarc.c b/app/display/gimpcanvasarc.c new file mode 100644 index 0000000..a4041b2 --- /dev/null +++ b/app/display/gimpcanvasarc.c @@ -0,0 +1,369 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasarc.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" + +#include "gimpcanvasarc.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_CENTER_X, + PROP_CENTER_Y, + PROP_RADIUS_X, + PROP_RADIUS_Y, + PROP_START_ANGLE, + PROP_SLICE_ANGLE, + PROP_FILLED +}; + + +typedef struct _GimpCanvasArcPrivate GimpCanvasArcPrivate; + +struct _GimpCanvasArcPrivate +{ + gdouble center_x; + gdouble center_y; + gdouble radius_x; + gdouble radius_y; + gdouble start_angle; + gdouble slice_angle; + gboolean filled; +}; + +#define GET_PRIVATE(arc) \ + ((GimpCanvasArcPrivate *) gimp_canvas_arc_get_instance_private ((GimpCanvasArc *) (arc))) + + +/* local function prototypes */ + +static void gimp_canvas_arc_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_arc_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_arc_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_arc_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasArc, gimp_canvas_arc, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_arc_parent_class + + +static void +gimp_canvas_arc_class_init (GimpCanvasArcClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_arc_set_property; + object_class->get_property = gimp_canvas_arc_get_property; + + item_class->draw = gimp_canvas_arc_draw; + item_class->get_extents = gimp_canvas_arc_get_extents; + + g_object_class_install_property (object_class, PROP_CENTER_X, + g_param_spec_double ("center-x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CENTER_Y, + g_param_spec_double ("center-y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_RADIUS_X, + g_param_spec_double ("radius-x", NULL, NULL, + 0, GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_RADIUS_Y, + g_param_spec_double ("radius-y", NULL, NULL, + 0, GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_START_ANGLE, + g_param_spec_double ("start-angle", NULL, NULL, + -1000, 1000, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SLICE_ANGLE, + g_param_spec_double ("slice-angle", NULL, NULL, + -1000, 1000, 2 * G_PI, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_FILLED, + g_param_spec_boolean ("filled", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_arc_init (GimpCanvasArc *arc) +{ +} + +static void +gimp_canvas_arc_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasArcPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_CENTER_X: + private->center_x = g_value_get_double (value); + break; + case PROP_CENTER_Y: + private->center_y = g_value_get_double (value); + break; + case PROP_RADIUS_X: + private->radius_x = g_value_get_double (value); + break; + case PROP_RADIUS_Y: + private->radius_y = g_value_get_double (value); + break; + case PROP_START_ANGLE: + private->start_angle = g_value_get_double (value); + break; + case PROP_SLICE_ANGLE: + private->slice_angle = g_value_get_double (value); + break; + case PROP_FILLED: + private->filled = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_arc_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasArcPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_CENTER_X: + g_value_set_double (value, private->center_x); + break; + case PROP_CENTER_Y: + g_value_set_double (value, private->center_y); + break; + case PROP_RADIUS_X: + g_value_set_double (value, private->radius_x); + break; + case PROP_RADIUS_Y: + g_value_set_double (value, private->radius_y); + break; + case PROP_START_ANGLE: + g_value_set_double (value, private->start_angle); + break; + case PROP_SLICE_ANGLE: + g_value_set_double (value, private->slice_angle); + break; + case PROP_FILLED: + g_value_set_boolean (value, private->filled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_arc_transform (GimpCanvasItem *item, + gdouble *center_x, + gdouble *center_y, + gdouble *radius_x, + gdouble *radius_y) +{ + GimpCanvasArcPrivate *private = GET_PRIVATE (item); + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_item_transform_xy_f (item, + private->center_x - private->radius_x, + private->center_y - private->radius_y, + &x1, &y1); + gimp_canvas_item_transform_xy_f (item, + private->center_x + private->radius_x, + private->center_y + private->radius_y, + &x2, &y2); + + x1 = floor (x1); + y1 = floor (y1); + x2 = ceil (x2); + y2 = ceil (y2); + + *center_x = (x1 + x2) / 2.0; + *center_y = (y1 + y2) / 2.0; + + *radius_x = (x2 - x1) / 2.0; + *radius_y = (y2 - y1) / 2.0; + + if (! private->filled) + { + *radius_x = MAX (*radius_x - 0.5, 0.0); + *radius_y = MAX (*radius_y - 0.5, 0.0); + } + + /* avoid cairo_scale (cr, 0.0, 0.0) */ + if (*radius_x == 0.0) *radius_x = 0.000001; + if (*radius_y == 0.0) *radius_y = 0.000001; +} + +static void +gimp_canvas_arc_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasArcPrivate *private = GET_PRIVATE (item); + gdouble center_x, center_y; + gdouble radius_x, radius_y; + + gimp_canvas_arc_transform (item, + ¢er_x, ¢er_y, + &radius_x, &radius_y); + + cairo_save (cr); + cairo_translate (cr, center_x, center_y); + cairo_scale (cr, radius_x, radius_y); + gimp_cairo_arc (cr, 0.0, 0.0, 1.0, + private->start_angle, private->slice_angle); + cairo_restore (cr); + + if (private->filled) + _gimp_canvas_item_fill (item, cr); + else + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_arc_get_extents (GimpCanvasItem *item) +{ + GimpCanvasArcPrivate *private = GET_PRIVATE (item); + cairo_region_t *region; + cairo_rectangle_int_t rectangle; + gdouble center_x, center_y; + gdouble radius_x, radius_y; + + gimp_canvas_arc_transform (item, + ¢er_x, ¢er_y, + &radius_x, &radius_y); + + rectangle.x = floor (center_x - radius_x - 1.5); + rectangle.y = floor (center_y - radius_y - 1.5); + rectangle.width = ceil (center_x + radius_x + 1.5) - rectangle.x; + rectangle.height = ceil (center_y + radius_y + 1.5) - rectangle.y; + + region = cairo_region_create_rectangle (&rectangle); + + if (! private->filled && + rectangle.width > 64 * 1.43 && + rectangle.height > 64 * 1.43) + { + radius_x *= 0.7; + radius_y *= 0.7; + + rectangle.x = ceil (center_x - radius_x + 1.5); + rectangle.y = ceil (center_y - radius_y + 1.5); + rectangle.width = floor (center_x + radius_x - 1.5) - rectangle.x; + rectangle.height = floor (center_y + radius_y - 1.5) - rectangle.y; + + cairo_region_subtract_rectangle (region, &rectangle); + } + + return region; +} + +GimpCanvasItem * +gimp_canvas_arc_new (GimpDisplayShell *shell, + gdouble center_x, + gdouble center_y, + gdouble radius_x, + gdouble radius_y, + gdouble start_angle, + gdouble slice_angle, + gboolean filled) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_ARC, + "shell", shell, + "center-x", center_x, + "center-y", center_y, + "radius-x", radius_x, + "radius-y", radius_y, + "start-angle", start_angle, + "slice-angle", slice_angle, + "filled", filled, + NULL); +} + +void +gimp_canvas_arc_set (GimpCanvasItem *arc, + gdouble center_x, + gdouble center_y, + gdouble radius_x, + gdouble radius_y, + gdouble start_angle, + gdouble slice_angle) +{ + g_return_if_fail (GIMP_IS_CANVAS_ARC (arc)); + + gimp_canvas_item_begin_change (arc); + g_object_set (arc, + "center-x", center_x, + "center-y", center_y, + "radius-x", radius_x, + "radius-y", radius_y, + "start-angle", start_angle, + "slice-angle", slice_angle, + NULL); + gimp_canvas_item_end_change (arc); +} diff --git a/app/display/gimpcanvasarc.h b/app/display/gimpcanvasarc.h new file mode 100644 index 0000000..1896352 --- /dev/null +++ b/app/display/gimpcanvasarc.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasarc.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_ARC_H__ +#define __GIMP_CANVAS_ARC_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_ARC (gimp_canvas_arc_get_type ()) +#define GIMP_CANVAS_ARC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_ARC, GimpCanvasArc)) +#define GIMP_CANVAS_ARC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_ARC, GimpCanvasArcClass)) +#define GIMP_IS_CANVAS_ARC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_ARC)) +#define GIMP_IS_CANVAS_ARC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_ARC)) +#define GIMP_CANVAS_ARC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_ARC, GimpCanvasArcClass)) + + +typedef struct _GimpCanvasArc GimpCanvasArc; +typedef struct _GimpCanvasArcClass GimpCanvasArcClass; + +struct _GimpCanvasArc +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasArcClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_arc_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_arc_new (GimpDisplayShell *shell, + gdouble center_x, + gdouble center_y, + gdouble radius_x, + gdouble radius_y, + gdouble start_angle, + gdouble slice_angle, + gboolean filled); + +void gimp_canvas_arc_set (GimpCanvasItem *arc, + gdouble center_x, + gdouble center_y, + gdouble radius_x, + gdouble radius_y, + gdouble start_angle, + gdouble slice_angle); + +#endif /* __GIMP_CANVAS_ARC_H__ */ diff --git a/app/display/gimpcanvasboundary.c b/app/display/gimpcanvasboundary.c new file mode 100644 index 0000000..a6cab9d --- /dev/null +++ b/app/display/gimpcanvasboundary.c @@ -0,0 +1,384 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasboundary.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" +#include "core/gimp-transform-utils.h" +#include "core/gimpboundary.h" +#include "core/gimpparamspecs.h" + +#include "gimpcanvasboundary.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_SEGS, + PROP_TRANSFORM, + PROP_OFFSET_X, + PROP_OFFSET_Y +}; + + +typedef struct _GimpCanvasBoundaryPrivate GimpCanvasBoundaryPrivate; + +struct _GimpCanvasBoundaryPrivate +{ + GimpBoundSeg *segs; + gint n_segs; + GimpMatrix3 *transform; + gdouble offset_x; + gdouble offset_y; +}; + +#define GET_PRIVATE(boundary) \ + ((GimpCanvasBoundaryPrivate *) gimp_canvas_boundary_get_instance_private ((GimpCanvasBoundary *) (boundary))) + + +/* local function prototypes */ + +static void gimp_canvas_boundary_finalize (GObject *object); +static void gimp_canvas_boundary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_boundary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_boundary_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_boundary_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasBoundary, gimp_canvas_boundary, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_boundary_parent_class + + +static void +gimp_canvas_boundary_class_init (GimpCanvasBoundaryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->finalize = gimp_canvas_boundary_finalize; + object_class->set_property = gimp_canvas_boundary_set_property; + object_class->get_property = gimp_canvas_boundary_get_property; + + item_class->draw = gimp_canvas_boundary_draw; + item_class->get_extents = gimp_canvas_boundary_get_extents; + + g_object_class_install_property (object_class, PROP_SEGS, + gimp_param_spec_array ("segs", NULL, NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_TRANSFORM, + g_param_spec_pointer ("transform", NULL, NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_OFFSET_X, + g_param_spec_double ("offset-x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_OFFSET_Y, + g_param_spec_double ("offset-y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_boundary_init (GimpCanvasBoundary *boundary) +{ + gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (boundary), + CAIRO_LINE_CAP_SQUARE); +} + +static void +gimp_canvas_boundary_finalize (GObject *object) +{ + GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->segs, g_free); + private->n_segs = 0; + + g_clear_pointer (&private->transform, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_boundary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_SEGS: + break; + + case PROP_TRANSFORM: + { + GimpMatrix3 *transform = g_value_get_pointer (value); + if (private->transform) + g_free (private->transform); + if (transform) + private->transform = g_memdup (transform, sizeof (GimpMatrix3)); + else + private->transform = NULL; + } + break; + + case PROP_OFFSET_X: + private->offset_x = g_value_get_double (value); + break; + case PROP_OFFSET_Y: + private->offset_y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_boundary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_SEGS: + break; + + case PROP_TRANSFORM: + g_value_set_pointer (value, private->transform); + break; + + case PROP_OFFSET_X: + g_value_set_double (value, private->offset_x); + break; + case PROP_OFFSET_Y: + g_value_set_double (value, private->offset_y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_boundary_transform (GimpCanvasItem *item, + GimpSegment *segs, + gint *n_segs) +{ + GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item); + gint i; + + if (private->transform) + { + gint n = 0; + + for (i = 0; i < private->n_segs; i++) + { + GimpVector2 vertices[2]; + GimpVector2 t_vertices[2]; + gint n_t_vertices; + + vertices[0] = (GimpVector2) { private->segs[i].x1, private->segs[i].y1 }; + vertices[1] = (GimpVector2) { private->segs[i].x2, private->segs[i].y2 }; + + gimp_transform_polygon (private->transform, vertices, 2, FALSE, + t_vertices, &n_t_vertices); + + if (n_t_vertices == 2) + { + gimp_canvas_item_transform_xy (item, + t_vertices[0].x + private->offset_x, + t_vertices[0].y + private->offset_y, + &segs[n].x1, &segs[n].y1); + gimp_canvas_item_transform_xy (item, + t_vertices[1].x + private->offset_x, + t_vertices[1].y + private->offset_y, + &segs[n].x2, &segs[n].y2); + + n++; + } + } + + *n_segs = n; + } + else + { + for (i = 0; i < private->n_segs; i++) + { + gimp_canvas_item_transform_xy (item, + private->segs[i].x1 + private->offset_x, + private->segs[i].y1 + private->offset_y, + &segs[i].x1, + &segs[i].y1); + gimp_canvas_item_transform_xy (item, + private->segs[i].x2 + private->offset_x, + private->segs[i].y2 + private->offset_y, + &segs[i].x2, + &segs[i].y2); + + /* If this segment is a closing segment && the segments lie inside + * the region, OR if this is an opening segment and the segments + * lie outside the region... + * we need to transform it by one display pixel + */ + if (! private->segs[i].open) + { + /* If it is vertical */ + if (segs[i].x1 == segs[i].x2) + { + segs[i].x1 -= 1; + segs[i].x2 -= 1; + } + else + { + segs[i].y1 -= 1; + segs[i].y2 -= 1; + } + } + } + + *n_segs = private->n_segs; + } +} + +static void +gimp_canvas_boundary_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item); + GimpSegment *segs; + gint n_segs; + + segs = g_new0 (GimpSegment, private->n_segs); + + gimp_canvas_boundary_transform (item, segs, &n_segs); + + gimp_cairo_segments (cr, segs, n_segs); + + _gimp_canvas_item_stroke (item, cr); + + g_free (segs); +} + +static cairo_region_t * +gimp_canvas_boundary_get_extents (GimpCanvasItem *item) +{ + GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item); + cairo_rectangle_int_t rectangle; + GimpSegment *segs; + gint n_segs; + gint x1, y1, x2, y2; + gint i; + + segs = g_new0 (GimpSegment, private->n_segs); + + gimp_canvas_boundary_transform (item, segs, &n_segs); + + if (n_segs == 0) + { + g_free (segs); + + return cairo_region_create (); + } + + x1 = MIN (segs[0].x1, segs[0].x2); + y1 = MIN (segs[0].y1, segs[0].y2); + x2 = MAX (segs[0].x1, segs[0].x2); + y2 = MAX (segs[0].y1, segs[0].y2); + + for (i = 1; i < n_segs; i++) + { + gint x3 = MIN (segs[i].x1, segs[i].x2); + gint y3 = MIN (segs[i].y1, segs[i].y2); + gint x4 = MAX (segs[i].x1, segs[i].x2); + gint y4 = MAX (segs[i].y1, segs[i].y2); + + x1 = MIN (x1, x3); + y1 = MIN (y1, y3); + x2 = MAX (x2, x4); + y2 = MAX (y2, y4); + } + + g_free (segs); + + rectangle.x = x1 - 2; + rectangle.y = y1 - 2; + rectangle.width = x2 - x1 + 4; + rectangle.height = y2 - y1 + 4; + + return cairo_region_create_rectangle (&rectangle); +} + +GimpCanvasItem * +gimp_canvas_boundary_new (GimpDisplayShell *shell, + const GimpBoundSeg *segs, + gint n_segs, + GimpMatrix3 *transform, + gdouble offset_x, + gdouble offset_y) +{ + GimpCanvasItem *item; + GimpCanvasBoundaryPrivate *private; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + item = g_object_new (GIMP_TYPE_CANVAS_BOUNDARY, + "shell", shell, + "transform", transform, + "offset-x", offset_x, + "offset-y", offset_y, + NULL); + private = GET_PRIVATE (item); + + /* puke */ + private->segs = g_memdup (segs, n_segs * sizeof (GimpBoundSeg)); + private->n_segs = n_segs; + + return item; +} diff --git a/app/display/gimpcanvasboundary.h b/app/display/gimpcanvasboundary.h new file mode 100644 index 0000000..527f89e --- /dev/null +++ b/app/display/gimpcanvasboundary.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995 + * Spencer Kimball and Peter Mattis + * + * gimpcanvasboundary.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_BOUNDARY_H__ +#define __GIMP_CANVAS_BOUNDARY_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_BOUNDARY (gimp_canvas_boundary_get_type ()) +#define GIMP_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_BOUNDARY, GimpCanvasBoundary)) +#define GIMP_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_BOUNDARY, GimpCanvasBoundaryClass)) +#define GIMP_IS_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_BOUNDARY)) +#define GIMP_IS_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_BOUNDARY)) +#define GIMP_CANVAS_BOUNDARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_BOUNDARY, GimpCanvasBoundaryClass)) + + +typedef struct _GimpCanvasBoundary GimpCanvasBoundary; +typedef struct _GimpCanvasBoundaryClass GimpCanvasBoundaryClass; + +struct _GimpCanvasBoundary +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasBoundaryClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_boundary_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_boundary_new (GimpDisplayShell *shell, + const GimpBoundSeg *segs, + gint n_segs, + GimpMatrix3 *transform, + gdouble offset_x, + gdouble offset_y); + + +#endif /* __GIMP_CANVAS_BOUNDARY_H__ */ diff --git a/app/display/gimpcanvasbufferpreview.c b/app/display/gimpcanvasbufferpreview.c new file mode 100644 index 0000000..858bab6 --- /dev/null +++ b/app/display/gimpcanvasbufferpreview.c @@ -0,0 +1,263 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasbufferpreview.c + * Copyright (C) 2013 Marek Dvoroznak <dvoromar@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <cairo/cairo.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display/display-types.h" + +#include "core/gimpimage.h" + +#include "gimpcanvas.h" +#include "gimpcanvasbufferpreview.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scroll.h" + + +enum +{ + PROP_0, + PROP_BUFFER +}; + + +typedef struct _GimpCanvasBufferPreviewPrivate GimpCanvasBufferPreviewPrivate; + +struct _GimpCanvasBufferPreviewPrivate +{ + GeglBuffer *buffer; +}; + + +#define GET_PRIVATE(transform_preview) \ + ((GimpCanvasBufferPreviewPrivate *) gimp_canvas_buffer_preview_get_instance_private ((GimpCanvasBufferPreview *) (transform_preview))) + + +/* local function prototypes */ + +static void gimp_canvas_buffer_preview_dispose (GObject *object); +static void gimp_canvas_buffer_preview_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_buffer_preview_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_canvas_buffer_preview_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_buffer_preview_get_extents (GimpCanvasItem *item); +static void gimp_canvas_buffer_preview_compute_bounds (GimpCanvasItem *item, + cairo_rectangle_int_t *bounds); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasBufferPreview, gimp_canvas_buffer_preview, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_buffer_preview_parent_class + + +static void +gimp_canvas_buffer_preview_class_init (GimpCanvasBufferPreviewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->dispose = gimp_canvas_buffer_preview_dispose; + object_class->set_property = gimp_canvas_buffer_preview_set_property; + object_class->get_property = gimp_canvas_buffer_preview_get_property; + + item_class->draw = gimp_canvas_buffer_preview_draw; + item_class->get_extents = gimp_canvas_buffer_preview_get_extents; + + g_object_class_install_property (object_class, PROP_BUFFER, + g_param_spec_object ("buffer", + NULL, NULL, + GEGL_TYPE_BUFFER, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_buffer_preview_init (GimpCanvasBufferPreview *transform_preview) +{ +} + +static void +gimp_canvas_buffer_preview_dispose (GObject *object) +{ + GimpCanvasBufferPreviewPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->buffer); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_canvas_buffer_preview_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasBufferPreviewPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_BUFFER: + g_set_object (&private->buffer, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_buffer_preview_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasBufferPreviewPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_BUFFER: + g_value_set_object (value, private->buffer); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_buffer_preview_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + GeglBuffer *buffer = GET_PRIVATE (item)->buffer; + cairo_surface_t *area; + guchar *data; + cairo_rectangle_int_t rectangle; + + g_return_if_fail (GEGL_IS_BUFFER (buffer)); + + gimp_canvas_buffer_preview_compute_bounds (item, &rectangle); + + area = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + rectangle.width, + rectangle.height); + + data = cairo_image_surface_get_data (area); + gegl_buffer_get (buffer, + GEGL_RECTANGLE (rectangle.x + shell->offset_x, + rectangle.y + shell->offset_y, + rectangle.width, + rectangle.height), + shell->scale_x, + babl_format ("cairo-ARGB32"), + data, + cairo_image_surface_get_stride (area), + GEGL_ABYSS_NONE); + + cairo_surface_flush (area); + cairo_surface_mark_dirty (area); + + cairo_set_source_surface (cr, area, rectangle.x, rectangle.y); + cairo_rectangle (cr, + rectangle.x, rectangle.y, + rectangle.width, rectangle.height); + cairo_fill (cr); + + cairo_surface_destroy (area); +} + +static void +gimp_canvas_buffer_preview_compute_bounds (GimpCanvasItem *item, + cairo_rectangle_int_t *bounds) +{ + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + GeglBuffer *buffer = GET_PRIVATE (item)->buffer; + GeglRectangle extent; + gdouble x1, y1; + gdouble x2, y2; + + g_return_if_fail (GEGL_IS_BUFFER (buffer)); + + extent = *gegl_buffer_get_extent (buffer); + + gimp_canvas_item_transform_xy_f (item, + extent.x, + extent.y, + &x1, &y1); + gimp_canvas_item_transform_xy_f (item, + extent.x + extent.width, + extent.y + extent.height, + &x2, &y2); + + extent.x = floor (x1); + extent.y = floor (y1); + extent.width = ceil (x2) - extent.x; + extent.height = ceil (y2) - extent.y; + + gegl_rectangle_intersect (&extent, + &extent, + GEGL_RECTANGLE (0, + 0, + shell->disp_width, + shell->disp_height)); + + bounds->x = extent.x; + bounds->y = extent.y; + bounds->width = extent.width; + bounds->height = extent.height; +} + +static cairo_region_t * +gimp_canvas_buffer_preview_get_extents (GimpCanvasItem *item) +{ + cairo_rectangle_int_t rectangle; + + gimp_canvas_buffer_preview_compute_bounds (item, &rectangle); + + return cairo_region_create_rectangle (&rectangle); +} + +GimpCanvasItem * +gimp_canvas_buffer_preview_new (GimpDisplayShell *shell, + GeglBuffer *buffer) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_BUFFER_PREVIEW, + "shell", shell, + "buffer", buffer, + NULL); +} diff --git a/app/display/gimpcanvasbufferpreview.h b/app/display/gimpcanvasbufferpreview.h new file mode 100644 index 0000000..28d8806 --- /dev/null +++ b/app/display/gimpcanvasbufferpreview.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasbufferpreview.h + * Copyright (C) 2013 Marek Dvoroznak <dvoromar@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_BUFFER_PREVIEW_H__ +#define __GIMP_CANVAS_BUFFER_PREVIEW_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_BUFFER_PREVIEW (gimp_canvas_buffer_preview_get_type ()) +#define GIMP_CANVAS_BUFFER_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_BUFFER_PREVIEW, GimpCanvasBufferPreview)) +#define GIMP_CANVAS_BUFFER_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_BUFFER_PREVIEW, GimpCanvasBufferPreviewClass)) +#define GIMP_IS_CANVAS_BUFFER_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_BUFFER_PREVIEW)) +#define GIMP_IS_CANVAS_BUFFER_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_BUFFER_PREVIEW)) +#define GIMP_CANVAS_BUFFER_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_BUFFER_PREVIEW, GimpCanvasBufferPreviewClass)) + + +typedef struct _GimpCanvasBufferPreview GimpCanvasBufferPreview; +typedef struct _GimpCanvasBufferPreviewClass GimpCanvasBufferPreviewClass; + +struct _GimpCanvasBufferPreview +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasBufferPreviewClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_buffer_preview_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_buffer_preview_new (GimpDisplayShell *shell, + GeglBuffer *buffer); + + +#endif /* __GIMP_CANVAS_BUFFER_PREVIEW_H__ */ diff --git a/app/display/gimpcanvascanvasboundary.c b/app/display/gimpcanvascanvasboundary.c new file mode 100644 index 0000000..01ac5a3 --- /dev/null +++ b/app/display/gimpcanvascanvasboundary.c @@ -0,0 +1,270 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvascanvasboundary.c + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpimage.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvascanvasboundary.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_IMAGE +}; + + +typedef struct _GimpCanvasCanvasBoundaryPrivate GimpCanvasCanvasBoundaryPrivate; + +struct _GimpCanvasCanvasBoundaryPrivate +{ + GimpImage *image; +}; + +#define GET_PRIVATE(canvas_boundary) \ + ((GimpCanvasCanvasBoundaryPrivate *) gimp_canvas_canvas_boundary_get_instance_private ((GimpCanvasCanvasBoundary *) (canvas_boundary))) + + +/* local function prototypes */ + +static void gimp_canvas_canvas_boundary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_canvas_boundary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_canvas_boundary_finalize (GObject *object); +static void gimp_canvas_canvas_boundary_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_canvas_boundary_get_extents (GimpCanvasItem *item); +static void gimp_canvas_canvas_boundary_stroke (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasCanvasBoundary, gimp_canvas_canvas_boundary, + GIMP_TYPE_CANVAS_RECTANGLE) + +#define parent_class gimp_canvas_canvas_boundary_parent_class + + +static void +gimp_canvas_canvas_boundary_class_init (GimpCanvasCanvasBoundaryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_canvas_boundary_set_property; + object_class->get_property = gimp_canvas_canvas_boundary_get_property; + object_class->finalize = gimp_canvas_canvas_boundary_finalize; + + item_class->draw = gimp_canvas_canvas_boundary_draw; + item_class->get_extents = gimp_canvas_canvas_boundary_get_extents; + item_class->stroke = gimp_canvas_canvas_boundary_stroke; + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_canvas_boundary_init (GimpCanvasCanvasBoundary *canvas_boundary) +{ +} + +static void +gimp_canvas_canvas_boundary_finalize (GObject *object) +{ + GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (object); + + if (private->image) + g_object_remove_weak_pointer (G_OBJECT (private->image), + (gpointer) &private->image); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_canvas_boundary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_IMAGE: + if (private->image) + g_object_remove_weak_pointer (G_OBJECT (private->image), + (gpointer) &private->image); + private->image = g_value_get_object (value); /* don't ref */ + if (private->image) + g_object_add_weak_pointer (G_OBJECT (private->image), + (gpointer) &private->image); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_canvas_boundary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_IMAGE: + g_value_set_object (value, private->image); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_canvas_boundary_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (item); + + if (private->image) + GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr); +} + +static cairo_region_t * +gimp_canvas_canvas_boundary_get_extents (GimpCanvasItem *item) +{ + GimpCanvasCanvasBoundaryPrivate *private = GET_PRIVATE (item); + + if (private->image) + return GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item); + + return NULL; +} + +static void +gimp_canvas_canvas_boundary_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + + gimp_canvas_set_canvas_style (gimp_canvas_item_get_canvas (item), cr, + shell->offset_x, shell->offset_y); + cairo_stroke (cr); +} + +GimpCanvasItem * +gimp_canvas_canvas_boundary_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, + "shell", shell, + NULL); +} + +void +gimp_canvas_canvas_boundary_set_image (GimpCanvasCanvasBoundary *boundary, + GimpImage *image) +{ + GimpCanvasCanvasBoundaryPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_CANVAS_BOUNDARY (boundary)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + private = GET_PRIVATE (boundary); + + if (image != private->image) + { + gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary)); + + if (image) + { + g_object_set (boundary, + "x", (gdouble) 0, + "y", (gdouble) 0, + "width", (gdouble) gimp_image_get_width (image), + "height", (gdouble) gimp_image_get_height (image), + NULL); + } + + g_object_set (boundary, + "image", image, + NULL); + + gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary)); + } + else if (image && image == private->image) + { + gint lx, ly, lw, lh; + gdouble x, y, w ,h; + + lx = 0; + ly = 0; + lw = gimp_image_get_width (image); + lh = gimp_image_get_height (image); + + g_object_get (boundary, + "x", &x, + "y", &y, + "width", &w, + "height", &h, + NULL); + + if (lx != (gint) x || + ly != (gint) y || + lw != (gint) w || + lh != (gint) h) + { + gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary)); + + g_object_set (boundary, + "x", (gdouble) lx, + "y", (gdouble) ly, + "width", (gdouble) lw, + "height", (gdouble) lh, + NULL); + + gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary)); + } + } +} diff --git a/app/display/gimpcanvascanvasboundary.h b/app/display/gimpcanvascanvasboundary.h new file mode 100644 index 0000000..c8fe16f --- /dev/null +++ b/app/display/gimpcanvascanvasboundary.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvascanvasvboundary.h + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_CANVAS_BOUNDARY_H__ +#define __GIMP_CANVAS_CANVAS_BOUNDARY_H__ + + +#include "gimpcanvasrectangle.h" + + +#define GIMP_TYPE_CANVAS_CANVAS_BOUNDARY (gimp_canvas_canvas_boundary_get_type ()) +#define GIMP_CANVAS_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, GimpCanvasCanvasBoundary)) +#define GIMP_CANVAS_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, GimpCanvasCanvasBoundaryClass)) +#define GIMP_IS_CANVAS_CANVAS_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY)) +#define GIMP_IS_CANVAS_CANVAS_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY)) +#define GIMP_CANVAS_CANVAS_BOUNDARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_CANVAS_BOUNDARY, GimpCanvasCanvasBoundaryClass)) + + +typedef struct _GimpCanvasCanvasBoundary GimpCanvasCanvasBoundary; +typedef struct _GimpCanvasCanvasBoundaryClass GimpCanvasCanvasBoundaryClass; + +struct _GimpCanvasCanvasBoundary +{ + GimpCanvasRectangle parent_instance; +}; + +struct _GimpCanvasCanvasBoundaryClass +{ + GimpCanvasRectangleClass parent_class; +}; + + +GType gimp_canvas_canvas_boundary_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_canvas_boundary_new (GimpDisplayShell *shell); + +void gimp_canvas_canvas_boundary_set_image (GimpCanvasCanvasBoundary *boundary, + GimpImage *image); + + +#endif /* __GIMP_CANVAS_CANVAS_BOUNDARY_H__ */ diff --git a/app/display/gimpcanvascorner.c b/app/display/gimpcanvascorner.c new file mode 100644 index 0000000..50aebfb --- /dev/null +++ b/app/display/gimpcanvascorner.c @@ -0,0 +1,468 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvascorner.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvascorner.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_ANCHOR, + PROP_CORNER_WIDTH, + PROP_CORNER_HEIGHT, + PROP_OUTSIDE +}; + + +typedef struct _GimpCanvasCornerPrivate GimpCanvasCornerPrivate; + +struct _GimpCanvasCornerPrivate +{ + gdouble x; + gdouble y; + gdouble width; + gdouble height; + GimpHandleAnchor anchor; + gint corner_width; + gint corner_height; + gboolean outside; +}; + +#define GET_PRIVATE(corner) \ + ((GimpCanvasCornerPrivate *) gimp_canvas_corner_get_instance_private ((GimpCanvasCorner *) (corner))) + + +/* local function prototypes */ + +static void gimp_canvas_corner_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_corner_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_corner_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_corner_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasCorner, gimp_canvas_corner, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_corner_parent_class + + +static void +gimp_canvas_corner_class_init (GimpCanvasCornerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_corner_set_property; + object_class->get_property = gimp_canvas_corner_get_property; + + item_class->draw = gimp_canvas_corner_draw; + item_class->get_extents = gimp_canvas_corner_get_extents; + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_double ("width", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_double ("height", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ANCHOR, + g_param_spec_enum ("anchor", NULL, NULL, + GIMP_TYPE_HANDLE_ANCHOR, + GIMP_HANDLE_ANCHOR_CENTER, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CORNER_WIDTH, + g_param_spec_int ("corner-width", NULL, NULL, + 3, GIMP_MAX_IMAGE_SIZE, 3, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CORNER_HEIGHT, + g_param_spec_int ("corner-height", NULL, NULL, + 3, GIMP_MAX_IMAGE_SIZE, 3, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_OUTSIDE, + g_param_spec_boolean ("outside", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_corner_init (GimpCanvasCorner *corner) +{ +} + +static void +gimp_canvas_corner_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasCornerPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + case PROP_WIDTH: + private->width = g_value_get_double (value); + break; + case PROP_HEIGHT: + private->height = g_value_get_double (value); + break; + case PROP_ANCHOR: + private->anchor = g_value_get_enum (value); + break; + case PROP_CORNER_WIDTH: + private->corner_width = g_value_get_int (value); + break; + case PROP_CORNER_HEIGHT: + private->corner_height = g_value_get_int (value); + break; + case PROP_OUTSIDE: + private->outside = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_corner_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasCornerPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + case PROP_WIDTH: + g_value_set_double (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_double (value, private->height); + break; + case PROP_ANCHOR: + g_value_set_enum (value, private->anchor); + break; + case PROP_CORNER_WIDTH: + g_value_set_int (value, private->corner_width); + break; + case PROP_CORNER_HEIGHT: + g_value_set_int (value, private->corner_height); + break; + case PROP_OUTSIDE: + g_value_set_boolean (value, private->outside); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_corner_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y, + gdouble *w, + gdouble *h) +{ + GimpCanvasCornerPrivate *private = GET_PRIVATE (item); + gdouble rx, ry; + gdouble rw, rh; + gint top_and_bottom_handle_x_offset; + gint left_and_right_handle_y_offset; + + gimp_canvas_item_transform_xy_f (item, + MIN (private->x, + private->x + private->width), + MIN (private->y, + private->y + private->height), + &rx, &ry); + gimp_canvas_item_transform_xy_f (item, + MAX (private->x, + private->x + private->width), + MAX (private->y, + private->y + private->height), + &rw, &rh); + + rw -= rx; + rh -= ry; + + rx = floor (rx) + 0.5; + ry = floor (ry) + 0.5; + rw = ceil (rw) - 1.0; + rh = ceil (rh) - 1.0; + + top_and_bottom_handle_x_offset = (rw - private->corner_width) / 2; + left_and_right_handle_y_offset = (rh - private->corner_height) / 2; + + *w = private->corner_width; + *h = private->corner_height; + + switch (private->anchor) + { + case GIMP_HANDLE_ANCHOR_CENTER: + break; + + case GIMP_HANDLE_ANCHOR_NORTH_WEST: + if (private->outside) + { + *x = rx - private->corner_width; + *y = ry - private->corner_height; + } + else + { + *x = rx; + *y = ry; + } + break; + + case GIMP_HANDLE_ANCHOR_NORTH_EAST: + if (private->outside) + { + *x = rx + rw; + *y = ry - private->corner_height; + } + else + { + *x = rx + rw - private->corner_width; + *y = ry; + } + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_WEST: + if (private->outside) + { + *x = rx - private->corner_width; + *y = ry + rh; + } + else + { + *x = rx; + *y = ry + rh - private->corner_height; + } + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_EAST: + if (private->outside) + { + *x = rx + rw; + *y = ry + rh; + } + else + { + *x = rx + rw - private->corner_width; + *y = ry + rh - private->corner_height; + } + break; + + case GIMP_HANDLE_ANCHOR_NORTH: + if (private->outside) + { + *x = rx; + *y = ry - private->corner_height; + *w = rw; + } + else + { + *x = rx + top_and_bottom_handle_x_offset; + *y = ry; + } + break; + + case GIMP_HANDLE_ANCHOR_SOUTH: + if (private->outside) + { + *x = rx; + *y = ry + rh; + *w = rw; + } + else + { + *x = rx + top_and_bottom_handle_x_offset; + *y = ry + rh - private->corner_height; + } + break; + + case GIMP_HANDLE_ANCHOR_WEST: + if (private->outside) + { + *x = rx - private->corner_width; + *y = ry; + *h = rh; + } + else + { + *x = rx; + *y = ry + left_and_right_handle_y_offset; + } + break; + + case GIMP_HANDLE_ANCHOR_EAST: + if (private->outside) + { + *x = rx + rw; + *y = ry; + *h = rh; + } + else + { + *x = rx + rw - private->corner_width; + *y = ry + left_and_right_handle_y_offset; + } + break; + } +} + +static void +gimp_canvas_corner_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + gdouble x, y; + gdouble w, h; + + gimp_canvas_corner_transform (item, &x, &y, &w, &h); + + cairo_rectangle (cr, x, y, w, h); + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_corner_get_extents (GimpCanvasItem *item) +{ + cairo_rectangle_int_t rectangle; + gdouble x, y; + gdouble w, h; + + gimp_canvas_corner_transform (item, &x, &y, &w, &h); + + rectangle.x = floor (x - 1.5); + rectangle.y = floor (y - 1.5); + rectangle.width = ceil (w + 3.0); + rectangle.height = ceil (h + 3.0); + + return cairo_region_create_rectangle (&rectangle); +} + +GimpCanvasItem * +gimp_canvas_corner_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpHandleAnchor anchor, + gint corner_width, + gint corner_height, + gboolean outside) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_CORNER, + "shell", shell, + "x", x, + "y", y, + "width", width, + "height", height, + "anchor", anchor, + "corner-width", corner_width, + "corner-height", corner_height, + "outside", outside, + NULL); +} + +void +gimp_canvas_corner_set (GimpCanvasItem *corner, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gint corner_width, + gint corner_height, + gboolean outside) +{ + g_return_if_fail (GIMP_IS_CANVAS_CORNER (corner)); + + gimp_canvas_item_begin_change (corner); + g_object_set (corner, + "x", x, + "y", y, + "width", width, + "height", height, + "corner-width", corner_width, + "corner-height", corner_height, + "outside", outside, + NULL); + gimp_canvas_item_end_change (corner); +} diff --git a/app/display/gimpcanvascorner.h b/app/display/gimpcanvascorner.h new file mode 100644 index 0000000..47cc53b --- /dev/null +++ b/app/display/gimpcanvascorner.h @@ -0,0 +1,72 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvascorner.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_CORNER_H__ +#define __GIMP_CANVAS_CORNER_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_CORNER (gimp_canvas_corner_get_type ()) +#define GIMP_CANVAS_CORNER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_CORNER, GimpCanvasCorner)) +#define GIMP_CANVAS_CORNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_CORNER, GimpCanvasCornerClass)) +#define GIMP_IS_CANVAS_CORNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_CORNER)) +#define GIMP_IS_CANVAS_CORNER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_CORNER)) +#define GIMP_CANVAS_CORNER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_CORNER, GimpCanvasCornerClass)) + + +typedef struct _GimpCanvasCorner GimpCanvasCorner; +typedef struct _GimpCanvasCornerClass GimpCanvasCornerClass; + +struct _GimpCanvasCorner +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasCornerClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_corner_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_corner_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpHandleAnchor anchor, + gint corner_width, + gint corner_height, + gboolean outside); + +void gimp_canvas_corner_set (GimpCanvasItem *corner, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gint corner_width, + gint corner_height, + gboolean outside); + + +#endif /* __GIMP_CANVAS_CORNER_H__ */ diff --git a/app/display/gimpcanvascursor.c b/app/display/gimpcanvascursor.c new file mode 100644 index 0000000..581fa76 --- /dev/null +++ b/app/display/gimpcanvascursor.c @@ -0,0 +1,228 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvascursor.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" + +#include "gimpcanvascursor.h" +#include "gimpdisplayshell.h" + + +#define GIMP_CURSOR_SIZE 7 + + +enum +{ + PROP_0, + PROP_X, + PROP_Y +}; + + +typedef struct _GimpCanvasCursorPrivate GimpCanvasCursorPrivate; + +struct _GimpCanvasCursorPrivate +{ + gdouble x; + gdouble y; +}; + +#define GET_PRIVATE(cursor) \ + ((GimpCanvasCursorPrivate *) gimp_canvas_cursor_get_instance_private ((GimpCanvasCursor *) (cursor))) + + +/* local function prototypes */ + +static void gimp_canvas_cursor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_cursor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_cursor_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_cursor_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasCursor, gimp_canvas_cursor, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_cursor_parent_class + + +static void +gimp_canvas_cursor_class_init (GimpCanvasCursorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_cursor_set_property; + object_class->get_property = gimp_canvas_cursor_get_property; + + item_class->draw = gimp_canvas_cursor_draw; + item_class->get_extents = gimp_canvas_cursor_get_extents; + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_cursor_init (GimpCanvasCursor *cursor) +{ + gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (cursor), + CAIRO_LINE_CAP_SQUARE); +} + +static void +gimp_canvas_cursor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasCursorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_cursor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasCursorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_cursor_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasCursorPrivate *private = GET_PRIVATE (item); + gdouble x, y; + + x = floor (private->x) + 0.5; + y = floor (private->y) + 0.5; + + cairo_move_to (cr, x - GIMP_CURSOR_SIZE, y); + cairo_line_to (cr, x + GIMP_CURSOR_SIZE, y); + + cairo_move_to (cr, x, y - GIMP_CURSOR_SIZE); + cairo_line_to (cr, x, y + GIMP_CURSOR_SIZE); + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_cursor_get_extents (GimpCanvasItem *item) +{ + GimpCanvasCursorPrivate *private = GET_PRIVATE (item); + cairo_rectangle_int_t rectangle; + gdouble x, y; + + x = floor (private->x) + 0.5; + y = floor (private->y) + 0.5; + + rectangle.x = floor (x - GIMP_CURSOR_SIZE - 1.5); + rectangle.y = floor (y - GIMP_CURSOR_SIZE - 1.5); + rectangle.width = ceil (x + GIMP_CURSOR_SIZE + 1.5) - rectangle.x; + rectangle.height = ceil (y + GIMP_CURSOR_SIZE + 1.5) - rectangle.y; + + return cairo_region_create_rectangle (&rectangle); +} + +GimpCanvasItem * +gimp_canvas_cursor_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_CURSOR, + "shell", shell, + NULL); +} + +void +gimp_canvas_cursor_set (GimpCanvasItem *cursor, + gdouble x, + gdouble y) +{ + GimpCanvasCursorPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_CURSOR (cursor)); + + private = GET_PRIVATE (cursor); + + if (private->x != x || private->y != y) + { + gimp_canvas_item_begin_change (cursor); + + g_object_set (cursor, + "x", x, + "y", y, + NULL); + + gimp_canvas_item_end_change (cursor); + } +} diff --git a/app/display/gimpcanvascursor.h b/app/display/gimpcanvascursor.h new file mode 100644 index 0000000..b518563 --- /dev/null +++ b/app/display/gimpcanvascursor.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvascursor.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_CURSOR_H__ +#define __GIMP_CANVAS_CURSOR_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_CURSOR (gimp_canvas_cursor_get_type ()) +#define GIMP_CANVAS_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_CURSOR, GimpCanvasCursor)) +#define GIMP_CANVAS_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_CURSOR, GimpCanvasCursorClass)) +#define GIMP_IS_CANVAS_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_CURSOR)) +#define GIMP_IS_CANVAS_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_CURSOR)) +#define GIMP_CANVAS_CURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_CURSOR, GimpCanvasCursorClass)) + + +typedef struct _GimpCanvasCursor GimpCanvasCursor; +typedef struct _GimpCanvasCursorClass GimpCanvasCursorClass; + +struct _GimpCanvasCursor +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasCursorClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_cursor_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_cursor_new (GimpDisplayShell *shell); + +void gimp_canvas_cursor_set (GimpCanvasItem *cursor, + gdouble x, + gdouble y); + + +#endif /* __GIMP_CANVAS_CURSOR_H__ */ diff --git a/app/display/gimpcanvasgrid.c b/app/display/gimpcanvasgrid.c new file mode 100644 index 0000000..23b46ee --- /dev/null +++ b/app/display/gimpcanvasgrid.c @@ -0,0 +1,414 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasgrid.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpconfig/gimpconfig.h" + +#include "display-types.h" + +#include "core/gimpgrid.h" +#include "core/gimpimage.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvasgrid.h" +#include "gimpcanvasitem-utils.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scale.h" + + +enum +{ + PROP_0, + PROP_GRID, + PROP_GRID_STYLE +}; + + +typedef struct _GimpCanvasGridPrivate GimpCanvasGridPrivate; + +struct _GimpCanvasGridPrivate +{ + GimpGrid *grid; + gboolean grid_style; +}; + +#define GET_PRIVATE(grid) \ + ((GimpCanvasGridPrivate *) gimp_canvas_grid_get_instance_private ((GimpCanvasGrid *) (grid))) + + +/* local function prototypes */ + +static void gimp_canvas_grid_finalize (GObject *object); +static void gimp_canvas_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_grid_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_grid_get_extents (GimpCanvasItem *item); +static void gimp_canvas_grid_stroke (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGrid, gimp_canvas_grid, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_grid_parent_class + + +static void +gimp_canvas_grid_class_init (GimpCanvasGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->finalize = gimp_canvas_grid_finalize; + object_class->set_property = gimp_canvas_grid_set_property; + object_class->get_property = gimp_canvas_grid_get_property; + + item_class->draw = gimp_canvas_grid_draw; + item_class->get_extents = gimp_canvas_grid_get_extents; + item_class->stroke = gimp_canvas_grid_stroke; + + g_object_class_install_property (object_class, PROP_GRID, + g_param_spec_object ("grid", NULL, NULL, + GIMP_TYPE_GRID, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_GRID_STYLE, + g_param_spec_boolean ("grid-style", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_grid_init (GimpCanvasGrid *grid) +{ + GimpCanvasGridPrivate *private = GET_PRIVATE (grid); + + private->grid = g_object_new (GIMP_TYPE_GRID, NULL); +} + +static void +gimp_canvas_grid_finalize (GObject *object) +{ + GimpCanvasGridPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->grid); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasGridPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GRID: + { + GimpGrid *grid = g_value_get_object (value); + if (grid) + gimp_config_sync (G_OBJECT (grid), G_OBJECT (private->grid), 0); + } + break; + case PROP_GRID_STYLE: + private->grid_style = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasGridPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GRID: + g_value_set_object (value, private->grid); + break; + case PROP_GRID_STYLE: + g_value_set_boolean (value, private->grid_style); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_grid_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasGridPrivate *private = GET_PRIVATE (item); + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + gdouble xspacing, yspacing; + gdouble xoffset, yoffset; + gboolean vert, horz; + gdouble dx1, dy1, dx2, dy2; + gint x1, y1, x2, y2; + gdouble dx, dy; + gint x, y; + +#define CROSSHAIR 2 + + gimp_grid_get_spacing (private->grid, &xspacing, &yspacing); + gimp_grid_get_offset (private->grid, &xoffset, &yoffset); + + g_return_if_fail (xspacing >= 0.0 && + yspacing >= 0.0); + + xspacing *= shell->scale_x; + yspacing *= shell->scale_y; + + xoffset *= shell->scale_x; + yoffset *= shell->scale_y; + + /* skip grid drawing when the space between grid lines starts + * disappearing, see bug #599267. + */ + vert = (xspacing >= 2.0); + horz = (yspacing >= 2.0); + + if (! vert && ! horz) + return; + + cairo_clip_extents (cr, &dx1, &dy1, &dx2, &dy2); + + x1 = floor (dx1) - 1; + y1 = floor (dy1) - 1; + x2 = ceil (dx2) + 1; + y2 = ceil (dy2) + 1; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + GeglRectangle bounds; + + gimp_display_shell_scale_get_image_unrotated_bounds ( + shell, + &bounds.x, &bounds.y, &bounds.width, &bounds.height); + + if (! gegl_rectangle_intersect (&bounds, + &bounds, + GEGL_RECTANGLE (x1, y1, + x2 - x1, y2 - y1))) + { + return; + } + + x1 = bounds.x; + y1 = bounds.y; + x2 = bounds.x + bounds.width; + y2 = bounds.y + bounds.height; + } + + switch (gimp_grid_get_style (private->grid)) + { + case GIMP_GRID_INTERSECTIONS: + x1 -= CROSSHAIR; + y1 -= CROSSHAIR; + x2 += CROSSHAIR; + y2 += CROSSHAIR; + break; + + case GIMP_GRID_DOTS: + case GIMP_GRID_ON_OFF_DASH: + case GIMP_GRID_DOUBLE_DASH: + case GIMP_GRID_SOLID: + break; + } + + xoffset = fmod (xoffset - shell->offset_x - x1, xspacing); + yoffset = fmod (yoffset - shell->offset_y - y1, yspacing); + + if (xoffset < 0.0) + xoffset += xspacing; + + if (yoffset < 0.0) + yoffset += yspacing; + + switch (gimp_grid_get_style (private->grid)) + { + case GIMP_GRID_DOTS: + if (vert && horz) + { + for (dx = x1 + xoffset; dx <= x2; dx += xspacing) + { + x = RINT (dx); + + for (dy = y1 + yoffset; dy <= y2; dy += yspacing) + { + y = RINT (dy); + + cairo_move_to (cr, x, y + 0.5); + cairo_line_to (cr, x + 1.0, y + 0.5); + } + } + } + break; + + case GIMP_GRID_INTERSECTIONS: + if (vert && horz) + { + for (dx = x1 + xoffset; dx <= x2; dx += xspacing) + { + x = RINT (dx); + + for (dy = y1 + yoffset; dy <= y2; dy += yspacing) + { + y = RINT (dy); + + cairo_move_to (cr, x + 0.5, y - CROSSHAIR); + cairo_line_to (cr, x + 0.5, y + CROSSHAIR + 1.0); + + cairo_move_to (cr, x - CROSSHAIR, y + 0.5); + cairo_line_to (cr, x + CROSSHAIR + 1.0, y + 0.5); + } + } + } + break; + + case GIMP_GRID_ON_OFF_DASH: + case GIMP_GRID_DOUBLE_DASH: + case GIMP_GRID_SOLID: + if (vert) + { + for (dx = x1 + xoffset; dx < x2; dx += xspacing) + { + x = RINT (dx); + + cairo_move_to (cr, x + 0.5, y1); + cairo_line_to (cr, x + 0.5, y2); + } + } + + if (horz) + { + for (dy = y1 + yoffset; dy < y2; dy += yspacing) + { + y = RINT (dy); + + cairo_move_to (cr, x1, y + 0.5); + cairo_line_to (cr, x2, y + 0.5); + } + } + break; + } + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_grid_get_extents (GimpCanvasItem *item) +{ + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + GimpImage *image = gimp_canvas_item_get_image (item); + cairo_rectangle_int_t rectangle; + + if (! image) + return NULL; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + + gdouble x1, y1; + gdouble x2, y2; + gint w, h; + + w = gimp_image_get_width (image); + h = gimp_image_get_height (image); + + gimp_canvas_item_transform_xy_f (item, 0, 0, &x1, &y1); + gimp_canvas_item_transform_xy_f (item, w, h, &x2, &y2); + + rectangle.x = floor (x1); + rectangle.y = floor (y1); + rectangle.width = ceil (x2) - rectangle.x; + rectangle.height = ceil (y2) - rectangle.y; + } + else + { + gimp_canvas_item_untransform_viewport (item, + &rectangle.x, + &rectangle.y, + &rectangle.width, + &rectangle.height); + } + + return cairo_region_create_rectangle (&rectangle); +} + +static void +gimp_canvas_grid_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasGridPrivate *private = GET_PRIVATE (item); + + if (private->grid_style) + { + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + + gimp_canvas_set_grid_style (gimp_canvas_item_get_canvas (item), cr, + private->grid, + shell->offset_x, shell->offset_y); + cairo_stroke (cr); + } + else + { + GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr); + } +} + +GimpCanvasItem * +gimp_canvas_grid_new (GimpDisplayShell *shell, + GimpGrid *grid) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (grid == NULL || GIMP_IS_GRID (grid), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_GRID, + "shell", shell, + "grid", grid, + NULL); +} diff --git a/app/display/gimpcanvasgrid.h b/app/display/gimpcanvasgrid.h new file mode 100644 index 0000000..391d2ea --- /dev/null +++ b/app/display/gimpcanvasgrid.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasgrid.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_GRID_H__ +#define __GIMP_CANVAS_GRID_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_GRID (gimp_canvas_grid_get_type ()) +#define GIMP_CANVAS_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_GRID, GimpCanvasGrid)) +#define GIMP_CANVAS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_GRID, GimpCanvasGridClass)) +#define GIMP_IS_CANVAS_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_GRID)) +#define GIMP_IS_CANVAS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_GRID)) +#define GIMP_CANVAS_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_GRID, GimpCanvasGridClass)) + + +typedef struct _GimpCanvasGrid GimpCanvasGrid; +typedef struct _GimpCanvasGridClass GimpCanvasGridClass; + +struct _GimpCanvasGrid +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasGridClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_grid_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_grid_new (GimpDisplayShell *shell, + GimpGrid *grid); + + +#endif /* __GIMP_CANVAS_GRID_H__ */ diff --git a/app/display/gimpcanvasgroup.c b/app/display/gimpcanvasgroup.c new file mode 100644 index 0000000..13d00ff --- /dev/null +++ b/app/display/gimpcanvasgroup.c @@ -0,0 +1,387 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasgroup.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvasgroup.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_GROUP_STROKING, + PROP_GROUP_FILLING +}; + + +struct _GimpCanvasGroupPrivate +{ + GQueue *items; + gboolean group_stroking; + gboolean group_filling; +}; + + +/* local function prototypes */ + +static void gimp_canvas_group_finalize (GObject *object); +static void gimp_canvas_group_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_group_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_group_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_group_get_extents (GimpCanvasItem *item); +static gboolean gimp_canvas_group_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + +static void gimp_canvas_group_child_update (GimpCanvasItem *item, + cairo_region_t *region, + GimpCanvasGroup *group); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGroup, gimp_canvas_group, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_group_parent_class + + +static void +gimp_canvas_group_class_init (GimpCanvasGroupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->finalize = gimp_canvas_group_finalize; + object_class->set_property = gimp_canvas_group_set_property; + object_class->get_property = gimp_canvas_group_get_property; + + item_class->draw = gimp_canvas_group_draw; + item_class->get_extents = gimp_canvas_group_get_extents; + item_class->hit = gimp_canvas_group_hit; + + g_object_class_install_property (object_class, PROP_GROUP_STROKING, + g_param_spec_boolean ("group-stroking", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_GROUP_FILLING, + g_param_spec_boolean ("group-filling", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_group_init (GimpCanvasGroup *group) +{ + group->priv = gimp_canvas_group_get_instance_private (group); + + group->priv->items = g_queue_new (); +} + +static void +gimp_canvas_group_finalize (GObject *object) +{ + GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object); + GimpCanvasItem *item; + + while ((item = g_queue_peek_head (group->priv->items))) + gimp_canvas_group_remove_item (group, item); + + g_queue_free (group->priv->items); + group->priv->items = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_group_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object); + + switch (property_id) + { + case PROP_GROUP_STROKING: + group->priv->group_stroking = g_value_get_boolean (value); + break; + case PROP_GROUP_FILLING: + group->priv->group_filling = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_group_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object); + + switch (property_id) + { + case PROP_GROUP_STROKING: + g_value_set_boolean (value, group->priv->group_stroking); + break; + case PROP_GROUP_FILLING: + g_value_set_boolean (value, group->priv->group_filling); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_group_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item); + GList *list; + + for (list = group->priv->items->head; list; list = g_list_next (list)) + { + GimpCanvasItem *sub_item = list->data; + + gimp_canvas_item_draw (sub_item, cr); + } + + if (group->priv->group_stroking) + _gimp_canvas_item_stroke (item, cr); + + if (group->priv->group_filling) + _gimp_canvas_item_fill (item, cr); +} + +static cairo_region_t * +gimp_canvas_group_get_extents (GimpCanvasItem *item) +{ + GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item); + cairo_region_t *region = NULL; + GList *list; + + for (list = group->priv->items->head; list; list = g_list_next (list)) + { + GimpCanvasItem *sub_item = list->data; + cairo_region_t *sub_region = gimp_canvas_item_get_extents (sub_item); + + if (! region) + { + region = sub_region; + } + else if (sub_region) + { + cairo_region_union (region, sub_region); + cairo_region_destroy (sub_region); + } + } + + return region; +} + +static gboolean +gimp_canvas_group_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item); + GList *list; + + for (list = group->priv->items->head; list; list = g_list_next (list)) + { + if (gimp_canvas_item_hit (list->data, x, y)) + return TRUE; + } + + return FALSE; +} + +static void +gimp_canvas_group_child_update (GimpCanvasItem *item, + cairo_region_t *region, + GimpCanvasGroup *group) +{ + if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group))) + _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region); +} + + +/* public functions */ + +GimpCanvasItem * +gimp_canvas_group_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_GROUP, + "shell", shell, + NULL); +} + +void +gimp_canvas_group_add_item (GimpCanvasGroup *group, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + g_return_if_fail (GIMP_CANVAS_ITEM (group) != item); + + if (group->priv->group_stroking) + gimp_canvas_item_suspend_stroking (item); + + if (group->priv->group_filling) + gimp_canvas_item_suspend_filling (item); + + g_queue_push_tail (group->priv->items, g_object_ref (item)); + + if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group))) + { + cairo_region_t *region = gimp_canvas_item_get_extents (item); + + if (region) + { + _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region); + cairo_region_destroy (region); + } + } + + g_signal_connect (item, "update", + G_CALLBACK (gimp_canvas_group_child_update), + group); +} + +void +gimp_canvas_group_remove_item (GimpCanvasGroup *group, + GimpCanvasItem *item) +{ + GList *list; + + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + list = g_queue_find (group->priv->items, item); + + g_return_if_fail (list != NULL); + + g_queue_delete_link (group->priv->items, list); + + if (group->priv->group_stroking) + gimp_canvas_item_resume_stroking (item); + + if (group->priv->group_filling) + gimp_canvas_item_resume_filling (item); + + if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group))) + { + cairo_region_t *region = gimp_canvas_item_get_extents (item); + + if (region) + { + _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region); + cairo_region_destroy (region); + } + } + + g_signal_handlers_disconnect_by_func (item, + gimp_canvas_group_child_update, + group); + + g_object_unref (item); +} + +void +gimp_canvas_group_set_group_stroking (GimpCanvasGroup *group, + gboolean group_stroking) +{ + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + + if (group->priv->group_stroking != group_stroking) + { + GList *list; + + gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (group)); + + g_object_set (group, + "group-stroking", group_stroking ? TRUE : FALSE, + NULL); + + for (list = group->priv->items->head; list; list = g_list_next (list)) + { + if (group->priv->group_stroking) + gimp_canvas_item_suspend_stroking (list->data); + else + gimp_canvas_item_resume_stroking (list->data); + } + + gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (group)); + } +} + +void +gimp_canvas_group_set_group_filling (GimpCanvasGroup *group, + gboolean group_filling) +{ + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + + if (group->priv->group_filling != group_filling) + { + GList *list; + + gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (group)); + + g_object_set (group, + "group-filling", group_filling ? TRUE : FALSE, + NULL); + + for (list = group->priv->items->head; list; list = g_list_next (list)) + { + if (group->priv->group_filling) + gimp_canvas_item_suspend_filling (list->data); + else + gimp_canvas_item_resume_filling (list->data); + } + + gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (group)); + } +} diff --git a/app/display/gimpcanvasgroup.h b/app/display/gimpcanvasgroup.h new file mode 100644 index 0000000..d2fb9cc --- /dev/null +++ b/app/display/gimpcanvasgroup.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasgroup.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_GROUP_H__ +#define __GIMP_CANVAS_GROUP_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_GROUP (gimp_canvas_group_get_type ()) +#define GIMP_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_GROUP, GimpCanvasGroup)) +#define GIMP_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_GROUP, GimpCanvasGroupClass)) +#define GIMP_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_GROUP)) +#define GIMP_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_GROUP)) +#define GIMP_CANVAS_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_GROUP, GimpCanvasGroupClass)) + + +typedef struct _GimpCanvasGroupPrivate GimpCanvasGroupPrivate; +typedef struct _GimpCanvasGroupClass GimpCanvasGroupClass; + +struct _GimpCanvasGroup +{ + GimpCanvasItem parent_instance; + + GimpCanvasGroupPrivate *priv; + +}; + +struct _GimpCanvasGroupClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_group_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_group_new (GimpDisplayShell *shell); + +void gimp_canvas_group_add_item (GimpCanvasGroup *group, + GimpCanvasItem *item); +void gimp_canvas_group_remove_item (GimpCanvasGroup *group, + GimpCanvasItem *item); + +void gimp_canvas_group_set_group_stroking (GimpCanvasGroup *group, + gboolean group_stroking); +void gimp_canvas_group_set_group_filling (GimpCanvasGroup *group, + gboolean group_filling); + + +#endif /* __GIMP_CANVAS_GROUP_H__ */ diff --git a/app/display/gimpcanvasguide.c b/app/display/gimpcanvasguide.c new file mode 100644 index 0000000..5712633 --- /dev/null +++ b/app/display/gimpcanvasguide.c @@ -0,0 +1,295 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasguide.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvasguide.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_POSITION, + PROP_STYLE +}; + + +typedef struct _GimpCanvasGuidePrivate GimpCanvasGuidePrivate; + +struct _GimpCanvasGuidePrivate +{ + GimpOrientationType orientation; + gint position; + + GimpGuideStyle style; +}; + +#define GET_PRIVATE(guide) \ + ((GimpCanvasGuidePrivate *) gimp_canvas_guide_get_instance_private ((GimpCanvasGuide *) (guide))) + + +/* local function prototypes */ + +static void gimp_canvas_guide_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_guide_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_guide_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_guide_get_extents (GimpCanvasItem *item); +static void gimp_canvas_guide_stroke (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGuide, gimp_canvas_guide, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_guide_parent_class + + +static void +gimp_canvas_guide_class_init (GimpCanvasGuideClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_guide_set_property; + object_class->get_property = gimp_canvas_guide_get_property; + + item_class->draw = gimp_canvas_guide_draw; + item_class->get_extents = gimp_canvas_guide_get_extents; + item_class->stroke = gimp_canvas_guide_stroke; + + g_object_class_install_property (object_class, PROP_ORIENTATION, + g_param_spec_enum ("orientation", NULL, NULL, + GIMP_TYPE_ORIENTATION_TYPE, + GIMP_ORIENTATION_HORIZONTAL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_POSITION, + g_param_spec_int ("position", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_STYLE, + g_param_spec_enum ("style", NULL, NULL, + GIMP_TYPE_GUIDE_STYLE, + GIMP_GUIDE_STYLE_NONE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_guide_init (GimpCanvasGuide *guide) +{ +} + +static void +gimp_canvas_guide_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasGuidePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ORIENTATION: + private->orientation = g_value_get_enum (value); + break; + case PROP_POSITION: + private->position = g_value_get_int (value); + break; + case PROP_STYLE: + private->style = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_guide_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasGuidePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, private->orientation); + break; + case PROP_POSITION: + g_value_set_int (value, private->position); + break; + case PROP_STYLE: + g_value_set_enum (value, private->style); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_guide_transform (GimpCanvasItem *item, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + GimpCanvasGuidePrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + GtkAllocation allocation; + gint max_outside; + gint x, y; + + gtk_widget_get_allocation (canvas, &allocation); + + max_outside = allocation.width + allocation.height; + + *x1 = -max_outside; + *y1 = -max_outside; + *x2 = allocation.width + max_outside; + *y2 = allocation.height + max_outside; + + switch (private->orientation) + { + case GIMP_ORIENTATION_HORIZONTAL: + gimp_canvas_item_transform_xy (item, 0, private->position, &x, &y); + *y1 = *y2 = y + 0.5; + break; + + case GIMP_ORIENTATION_VERTICAL: + gimp_canvas_item_transform_xy (item, private->position, 0, &x, &y); + *x1 = *x2 = x + 0.5; + break; + + case GIMP_ORIENTATION_UNKNOWN: + return; + } +} + +static void +gimp_canvas_guide_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_guide_transform (item, &x1, &y1, &x2, &y2); + + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x2, y2); + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_guide_get_extents (GimpCanvasItem *item) +{ + cairo_rectangle_int_t rectangle; + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_guide_transform (item, &x1, &y1, &x2, &y2); + + rectangle.x = MIN (x1, x2) - 1.5; + rectangle.y = MIN (y1, y2) - 1.5; + rectangle.width = ABS (x2 - x1) + 3.0; + rectangle.height = ABS (y2 - y1) + 3.0; + + return cairo_region_create_rectangle (&rectangle); +} + +static void +gimp_canvas_guide_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasGuidePrivate *private = GET_PRIVATE (item); + + if (private->style != GIMP_GUIDE_STYLE_NONE) + { + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + + gimp_canvas_set_guide_style (gimp_canvas_item_get_canvas (item), cr, + private->style, + gimp_canvas_item_get_highlight (item), + shell->offset_x, shell->offset_y); + cairo_stroke (cr); + } + else + { + GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr); + } +} + +GimpCanvasItem * +gimp_canvas_guide_new (GimpDisplayShell *shell, + GimpOrientationType orientation, + gint position, + GimpGuideStyle style) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_GUIDE, + "shell", shell, + "orientation", orientation, + "position", position, + "style", style, + NULL); +} + +void +gimp_canvas_guide_set (GimpCanvasItem *guide, + GimpOrientationType orientation, + gint position) +{ + g_return_if_fail (GIMP_IS_CANVAS_GUIDE (guide)); + + gimp_canvas_item_begin_change (guide); + + g_object_set (guide, + "orientation", orientation, + "position", position, + NULL); + + gimp_canvas_item_end_change (guide); +} diff --git a/app/display/gimpcanvasguide.h b/app/display/gimpcanvasguide.h new file mode 100644 index 0000000..54b1696 --- /dev/null +++ b/app/display/gimpcanvasguide.h @@ -0,0 +1,62 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasguide.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_GUIDE_H__ +#define __GIMP_CANVAS_GUIDE_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_GUIDE (gimp_canvas_guide_get_type ()) +#define GIMP_CANVAS_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_GUIDE, GimpCanvasGuide)) +#define GIMP_CANVAS_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_GUIDE, GimpCanvasGuideClass)) +#define GIMP_IS_CANVAS_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_GUIDE)) +#define GIMP_IS_CANVAS_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_GUIDE)) +#define GIMP_CANVAS_GUIDE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_GUIDE, GimpCanvasGuideClass)) + + +typedef struct _GimpCanvasGuide GimpCanvasGuide; +typedef struct _GimpCanvasGuideClass GimpCanvasGuideClass; + +struct _GimpCanvasGuide +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasGuideClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_guide_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_guide_new (GimpDisplayShell *shell, + GimpOrientationType orientation, + gint position, + GimpGuideStyle style); + +void gimp_canvas_guide_set (GimpCanvasItem *guide, + GimpOrientationType orientation, + gint position); + + +#endif /* __GIMP_CANVAS_GUIDE_H__ */ diff --git a/app/display/gimpcanvashandle.c b/app/display/gimpcanvashandle.c new file mode 100644 index 0000000..954eead --- /dev/null +++ b/app/display/gimpcanvashandle.c @@ -0,0 +1,703 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvashandle.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" + +#include "gimpcanvashandle.h" +#include "gimpcanvasitem-utils.h" +#include "gimpdisplayshell.h" + + +#define N_DASHES 8 +#define DASH_ON_RATIO 0.3 +#define DASH_OFF_RATIO 0.7 + + +enum +{ + PROP_0, + PROP_TYPE, + PROP_ANCHOR, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_START_ANGLE, + PROP_SLICE_ANGLE +}; + + +typedef struct _GimpCanvasHandlePrivate GimpCanvasHandlePrivate; + +struct _GimpCanvasHandlePrivate +{ + GimpHandleType type; + GimpHandleAnchor anchor; + gdouble x; + gdouble y; + gint width; + gint height; + gdouble start_angle; + gdouble slice_angle; +}; + +#define GET_PRIVATE(handle) \ + ((GimpCanvasHandlePrivate *) gimp_canvas_handle_get_instance_private ((GimpCanvasHandle *) (handle))) + + +/* local function prototypes */ + +static void gimp_canvas_handle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_handle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_handle_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_handle_get_extents (GimpCanvasItem *item); +static gboolean gimp_canvas_handle_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasHandle, gimp_canvas_handle, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_handle_parent_class + + +static void +gimp_canvas_handle_class_init (GimpCanvasHandleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_handle_set_property; + object_class->get_property = gimp_canvas_handle_get_property; + + item_class->draw = gimp_canvas_handle_draw; + item_class->get_extents = gimp_canvas_handle_get_extents; + item_class->hit = gimp_canvas_handle_hit; + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", NULL, NULL, + GIMP_TYPE_HANDLE_TYPE, + GIMP_HANDLE_CROSS, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ANCHOR, + g_param_spec_enum ("anchor", NULL, NULL, + GIMP_TYPE_HANDLE_ANCHOR, + GIMP_HANDLE_ANCHOR_CENTER, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_int ("width", NULL, NULL, + 3, 1001, 7, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_int ("height", NULL, NULL, + 3, 1001, 7, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_START_ANGLE, + g_param_spec_double ("start-angle", NULL, NULL, + -1000, 1000, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SLICE_ANGLE, + g_param_spec_double ("slice-angle", NULL, NULL, + -1000, 1000, 2 * G_PI, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_handle_init (GimpCanvasHandle *handle) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (handle); + + gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (handle), + CAIRO_LINE_CAP_SQUARE); + + private->start_angle = 0.0; + private->slice_angle = 2.0 * G_PI; +} + +static void +gimp_canvas_handle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TYPE: + private->type = g_value_get_enum (value); + break; + case PROP_ANCHOR: + private->anchor = g_value_get_enum (value); + break; + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + case PROP_WIDTH: + private->width = g_value_get_int (value); + break; + case PROP_HEIGHT: + private->height = g_value_get_int (value); + break; + case PROP_START_ANGLE: + private->start_angle = g_value_get_double (value); + break; + case PROP_SLICE_ANGLE: + private->slice_angle = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_handle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TYPE: + g_value_set_enum (value, private->type); + break; + case PROP_ANCHOR: + g_value_set_enum (value, private->anchor); + break; + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + case PROP_WIDTH: + g_value_set_int (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_int (value, private->height); + break; + case PROP_START_ANGLE: + g_value_set_double (value, private->start_angle); + break; + case PROP_SLICE_ANGLE: + g_value_set_double (value, private->slice_angle); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_handle_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (item); + + gimp_canvas_item_transform_xy_f (item, + private->x, private->y, + x, y); + + switch (private->type) + { + case GIMP_HANDLE_SQUARE: + case GIMP_HANDLE_DASHED_SQUARE: + case GIMP_HANDLE_FILLED_SQUARE: + gimp_canvas_item_shift_to_north_west (private->anchor, + *x, *y, + private->width, + private->height, + x, y); + break; + + case GIMP_HANDLE_CIRCLE: + case GIMP_HANDLE_DASHED_CIRCLE: + case GIMP_HANDLE_FILLED_CIRCLE: + case GIMP_HANDLE_CROSS: + case GIMP_HANDLE_CROSSHAIR: + case GIMP_HANDLE_DIAMOND: + case GIMP_HANDLE_DASHED_DIAMOND: + case GIMP_HANDLE_FILLED_DIAMOND: + gimp_canvas_item_shift_to_center (private->anchor, + *x, *y, + private->width, + private->height, + x, y); + break; + + default: + break; + } + + *x = floor (*x) + 0.5; + *y = floor (*y) + 0.5; +} + +static void +gimp_canvas_handle_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (item); + gdouble x, y, tx, ty; + + gimp_canvas_handle_transform (item, &x, &y); + + gimp_canvas_item_transform_xy_f (item, + private->x, private->y, + &tx, &ty); + + tx = floor (tx) + 0.5; + ty = floor (ty) + 0.5; + + switch (private->type) + { + case GIMP_HANDLE_SQUARE: + case GIMP_HANDLE_DASHED_SQUARE: + case GIMP_HANDLE_FILLED_SQUARE: + case GIMP_HANDLE_DIAMOND: + case GIMP_HANDLE_DASHED_DIAMOND: + case GIMP_HANDLE_FILLED_DIAMOND: + case GIMP_HANDLE_CROSS: + cairo_save (cr); + cairo_translate (cr, tx, ty); + cairo_rotate (cr, private->start_angle); + cairo_translate (cr, -tx, -ty); + + switch (private->type) + { + case GIMP_HANDLE_SQUARE: + case GIMP_HANDLE_DASHED_SQUARE: + if (private->type == GIMP_HANDLE_DASHED_SQUARE) + { + gdouble circ; + gdouble dashes[2]; + + cairo_save (cr); + + circ = 2.0 * ((private->width - 1.0) + (private->height - 1.0)); + + dashes[0] = (circ / N_DASHES) * DASH_ON_RATIO; + dashes[1] = (circ / N_DASHES) * DASH_OFF_RATIO; + + cairo_set_dash (cr, dashes, 2, dashes[0] / 2.0); + } + + cairo_rectangle (cr, x, y, private->width - 1.0, private->height - 1.0); + _gimp_canvas_item_stroke (item, cr); + + if (private->type == GIMP_HANDLE_DASHED_SQUARE) + cairo_restore (cr); + break; + + case GIMP_HANDLE_FILLED_SQUARE: + cairo_rectangle (cr, x - 0.5, y - 0.5, private->width, private->height); + _gimp_canvas_item_fill (item, cr); + break; + + case GIMP_HANDLE_DIAMOND: + case GIMP_HANDLE_DASHED_DIAMOND: + case GIMP_HANDLE_FILLED_DIAMOND: + if (private->type == GIMP_HANDLE_DASHED_DIAMOND) + { + gdouble circ; + gdouble dashes[2]; + + cairo_save (cr); + + circ = 4.0 * hypot ((gdouble) private->width / 2.0, + (gdouble) private->height / 2.0); + + dashes[0] = (circ / N_DASHES) * DASH_ON_RATIO; + dashes[1] = (circ / N_DASHES) * DASH_OFF_RATIO; + + cairo_set_dash (cr, dashes, 2, dashes[0] / 2.0); + } + + cairo_move_to (cr, x, y - (gdouble) private->height / 2.0); + cairo_line_to (cr, x + (gdouble) private->width / 2.0, y); + cairo_line_to (cr, x, y + (gdouble) private->height / 2.0); + cairo_line_to (cr, x - (gdouble) private->width / 2.0, y); + cairo_line_to (cr, x, y - (gdouble) private->height / 2.0); + if (private->type == GIMP_HANDLE_DIAMOND || + private->type == GIMP_HANDLE_DASHED_DIAMOND) + _gimp_canvas_item_stroke (item, cr); + else + _gimp_canvas_item_fill (item, cr); + + if (private->type == GIMP_HANDLE_DASHED_SQUARE) + cairo_restore (cr); + break; + + case GIMP_HANDLE_CROSS: + cairo_move_to (cr, x - private->width / 2.0, y); + cairo_line_to (cr, x + private->width / 2.0 - 0.5, y); + cairo_move_to (cr, x, y - private->height / 2.0); + cairo_line_to (cr, x, y + private->height / 2.0 - 0.5); + _gimp_canvas_item_stroke (item, cr); + break; + + default: + gimp_assert_not_reached (); + } + + cairo_restore (cr); + break; + + case GIMP_HANDLE_CIRCLE: + case GIMP_HANDLE_DASHED_CIRCLE: + if (private->type == GIMP_HANDLE_DASHED_CIRCLE) + { + gdouble circ; + gdouble dashes[2]; + + cairo_save (cr); + + circ = 2.0 * G_PI * (private->width / 2.0); + + dashes[0] = (circ / N_DASHES) * DASH_ON_RATIO; + dashes[1] = (circ / N_DASHES) * DASH_OFF_RATIO; + + cairo_set_dash (cr, dashes, 2, dashes[0] / 2.0); + } + + gimp_cairo_arc (cr, x, y, private->width / 2.0, + private->start_angle, + private->slice_angle); + + _gimp_canvas_item_stroke (item, cr); + + if (private->type == GIMP_HANDLE_DASHED_CIRCLE) + cairo_restore (cr); + break; + + case GIMP_HANDLE_FILLED_CIRCLE: + cairo_move_to (cr, x, y); + + gimp_cairo_arc (cr, x, y, (gdouble) private->width / 2.0, + private->start_angle, + private->slice_angle); + + _gimp_canvas_item_fill (item, cr); + break; + + case GIMP_HANDLE_CROSSHAIR: + cairo_move_to (cr, x - private->width / 2.0, y); + cairo_line_to (cr, x - private->width * 0.4, y); + + cairo_move_to (cr, x + private->width / 2.0 - 0.5, y); + cairo_line_to (cr, x + private->width * 0.4, y); + + cairo_move_to (cr, x, y - private->height / 2.0); + cairo_line_to (cr, x, y - private->height * 0.4 - 0.5); + + cairo_move_to (cr, x, y + private->height / 2.0 - 0.5); + cairo_line_to (cr, x, y + private->height * 0.4 - 0.5); + + _gimp_canvas_item_stroke (item, cr); + break; + + default: + break; + } +} + +static cairo_region_t * +gimp_canvas_handle_get_extents (GimpCanvasItem *item) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (item); + cairo_rectangle_int_t rectangle; + gdouble x, y; + gdouble w, h; + + gimp_canvas_handle_transform (item, &x, &y); + + switch (private->type) + { + case GIMP_HANDLE_SQUARE: + case GIMP_HANDLE_DASHED_SQUARE: + case GIMP_HANDLE_FILLED_SQUARE: + w = private->width * (sqrt(2) - 1) / 2; + h = private->height * (sqrt(2) - 1) / 2; + rectangle.x = x - 1.5 - w; + rectangle.y = y - 1.5 - h; + rectangle.width = private->width + 3.0 + w * 2; + rectangle.height = private->height + 3.0 + h * 2; + break; + + case GIMP_HANDLE_CIRCLE: + case GIMP_HANDLE_DASHED_CIRCLE: + case GIMP_HANDLE_FILLED_CIRCLE: + case GIMP_HANDLE_CROSS: + case GIMP_HANDLE_CROSSHAIR: + case GIMP_HANDLE_DIAMOND: + case GIMP_HANDLE_DASHED_DIAMOND: + case GIMP_HANDLE_FILLED_DIAMOND: + rectangle.x = x - private->width / 2.0 - 2.0; + rectangle.y = y - private->height / 2.0 - 2.0; + rectangle.width = private->width + 4.0; + rectangle.height = private->height + 4.0; + break; + + default: + break; + } + + return cairo_region_create_rectangle (&rectangle); +} + +static gboolean +gimp_canvas_handle_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + GimpCanvasHandlePrivate *private = GET_PRIVATE (item); + gdouble handle_tx, handle_ty; + gdouble mx, my, tx, ty, mmx, mmy; + gdouble diamond_offset_x = 0.0; + gdouble diamond_offset_y = 0.0; + gdouble angle = -private->start_angle; + + gimp_canvas_handle_transform (item, &handle_tx, &handle_ty); + + gimp_canvas_item_transform_xy_f (item, + x, y, + &mx, &my); + + switch (private->type) + { + case GIMP_HANDLE_DIAMOND: + case GIMP_HANDLE_DASHED_DIAMOND: + case GIMP_HANDLE_FILLED_DIAMOND: + angle -= G_PI / 4.0; + diamond_offset_x = private->width / 2.0; + diamond_offset_y = private->height / 2.0; + case GIMP_HANDLE_SQUARE: + case GIMP_HANDLE_DASHED_SQUARE: + case GIMP_HANDLE_FILLED_SQUARE: + gimp_canvas_item_transform_xy_f (item, + private->x, private->y, + &tx, &ty); + mmx = mx - tx; mmy = my - ty; + mx = cos (angle) * mmx - sin (angle) * mmy + tx + diamond_offset_x; + my = sin (angle) * mmx + cos (angle) * mmy + ty + diamond_offset_y; + return mx > handle_tx && mx < handle_tx + private->width && + my > handle_ty && my < handle_ty + private->height; + + case GIMP_HANDLE_CIRCLE: + case GIMP_HANDLE_DASHED_CIRCLE: + case GIMP_HANDLE_FILLED_CIRCLE: + case GIMP_HANDLE_CROSS: + case GIMP_HANDLE_CROSSHAIR: + { + gint width = private->width; + + if (width != private->height) + width = (width + private->height) / 2; + + width /= 2; + + return ((SQR (handle_tx - mx) + SQR (handle_ty - my)) < SQR (width)); + } + + default: + break; + } + + return FALSE; +} + +GimpCanvasItem * +gimp_canvas_handle_new (GimpDisplayShell *shell, + GimpHandleType type, + GimpHandleAnchor anchor, + gdouble x, + gdouble y, + gint width, + gint height) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_HANDLE, + "shell", shell, + "type", type, + "anchor", anchor, + "x", x, + "y", y, + "width", width, + "height", height, + NULL); +} + +void +gimp_canvas_handle_get_position (GimpCanvasItem *handle, + gdouble *x, + gdouble *y) +{ + g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + g_object_get (handle, + "x", x, + "y", y, + NULL); +} + +void +gimp_canvas_handle_set_position (GimpCanvasItem *handle, + gdouble x, + gdouble y) +{ + g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle)); + + gimp_canvas_item_begin_change (handle); + + g_object_set (handle, + "x", x, + "y", y, + NULL); + + gimp_canvas_item_end_change (handle); +} + +gint +gimp_canvas_handle_calc_size (GimpCanvasItem *item, + gdouble mouse_x, + gdouble mouse_y, + gint normal_size, + gint hover_size) +{ + gdouble x, y; + gdouble distance; + gdouble size; + gint full_threshold_sq = SQR (hover_size / 2) * 9; + gint partial_threshold_sq = full_threshold_sq * 5; + + g_return_val_if_fail (GIMP_IS_CANVAS_HANDLE (item), normal_size); + + gimp_canvas_handle_get_position (item, &x, &y); + distance = gimp_canvas_item_transform_distance_square (item, + mouse_x, + mouse_y, + x, y); + + /* calculate the handle size based on distance from the cursor */ + size = (1.0 - (distance - full_threshold_sq) / + (partial_threshold_sq - full_threshold_sq)); + + size = CLAMP (size, 0.0, 1.0); + + return (gint) CLAMP ((size * hover_size), + normal_size, + hover_size); +} + +void +gimp_canvas_handle_get_size (GimpCanvasItem *handle, + gint *width, + gint *height) +{ + g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle)); + g_return_if_fail (width != NULL); + g_return_if_fail (height != NULL); + + g_object_get (handle, + "width", width, + "height", height, + NULL); +} + +void +gimp_canvas_handle_set_size (GimpCanvasItem *handle, + gint width, + gint height) +{ + g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle)); + + gimp_canvas_item_begin_change (handle); + + g_object_set (handle, + "width", width, + "height", height, + NULL); + + gimp_canvas_item_end_change (handle); +} + +void +gimp_canvas_handle_set_angles (GimpCanvasItem *handle, + gdouble start_angle, + gdouble slice_angle) +{ + g_return_if_fail (GIMP_IS_CANVAS_HANDLE (handle)); + + gimp_canvas_item_begin_change (handle); + + g_object_set (handle, + "start-angle", start_angle, + "slice-angle", slice_angle, + NULL); + + gimp_canvas_item_end_change (handle); +} diff --git a/app/display/gimpcanvashandle.h b/app/display/gimpcanvashandle.h new file mode 100644 index 0000000..0dea957 --- /dev/null +++ b/app/display/gimpcanvashandle.h @@ -0,0 +1,92 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvashandle.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_HANDLE_H__ +#define __GIMP_CANVAS_HANDLE_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_CANVAS_HANDLE_SIZE_CIRCLE 13 +#define GIMP_CANVAS_HANDLE_SIZE_CROSS 15 +#define GIMP_CANVAS_HANDLE_SIZE_CROSSHAIR 43 +#define GIMP_CANVAS_HANDLE_SIZE_LARGE 25 +#define GIMP_CANVAS_HANDLE_SIZE_SMALL 7 + + +#define GIMP_TYPE_CANVAS_HANDLE (gimp_canvas_handle_get_type ()) +#define GIMP_CANVAS_HANDLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_HANDLE, GimpCanvasHandle)) +#define GIMP_CANVAS_HANDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_HANDLE, GimpCanvasHandleClass)) +#define GIMP_IS_CANVAS_HANDLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_HANDLE)) +#define GIMP_IS_CANVAS_HANDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_HANDLE)) +#define GIMP_CANVAS_HANDLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_HANDLE, GimpCanvasHandleClass)) + + +typedef struct _GimpCanvasHandle GimpCanvasHandle; +typedef struct _GimpCanvasHandleClass GimpCanvasHandleClass; + +struct _GimpCanvasHandle +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasHandleClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_handle_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_handle_new (GimpDisplayShell *shell, + GimpHandleType type, + GimpHandleAnchor anchor, + gdouble x, + gdouble y, + gint width, + gint height); + +void gimp_canvas_handle_get_position (GimpCanvasItem *handle, + gdouble *x, + gdouble *y); +void gimp_canvas_handle_set_position (GimpCanvasItem *handle, + gdouble x, + gdouble y); + +gint gimp_canvas_handle_calc_size (GimpCanvasItem *item, + gdouble mouse_x, + gdouble mouse_y, + gint normal_size, + gint hover_size); + +void gimp_canvas_handle_get_size (GimpCanvasItem *handle, + gint *width, + gint *height); +void gimp_canvas_handle_set_size (GimpCanvasItem *handle, + gint width, + gint height); + +void gimp_canvas_handle_set_angles (GimpCanvasItem *handle, + gdouble start_handle, + gdouble slice_handle); + + +#endif /* __GIMP_CANVAS_HANDLE_H__ */ diff --git a/app/display/gimpcanvasitem-utils.c b/app/display/gimpcanvasitem-utils.c new file mode 100644 index 0000000..4c23592 --- /dev/null +++ b/app/display/gimpcanvasitem-utils.c @@ -0,0 +1,474 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasitem-utils.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpimage.h" + +#include "vectors/gimpanchor.h" +#include "vectors/gimpbezierstroke.h" +#include "vectors/gimpvectors.h" + +#include "gimpcanvasitem.h" +#include "gimpcanvasitem-utils.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-transform.h" + + +gboolean +gimp_canvas_item_on_handle (GimpCanvasItem *item, + gdouble x, + gdouble y, + GimpHandleType type, + gdouble handle_x, + gdouble handle_y, + gint width, + gint height, + GimpHandleAnchor anchor) +{ + GimpDisplayShell *shell; + gdouble tx, ty; + gdouble handle_tx, handle_ty; + + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + + shell = gimp_canvas_item_get_shell (item); + + gimp_display_shell_zoom_xy_f (shell, + x, y, + &tx, &ty); + gimp_display_shell_zoom_xy_f (shell, + handle_x, handle_y, + &handle_tx, &handle_ty); + + switch (type) + { + case GIMP_HANDLE_SQUARE: + case GIMP_HANDLE_FILLED_SQUARE: + case GIMP_HANDLE_CROSS: + case GIMP_HANDLE_CROSSHAIR: + gimp_canvas_item_shift_to_north_west (anchor, + handle_tx, handle_ty, + width, height, + &handle_tx, &handle_ty); + + return (tx == CLAMP (tx, handle_tx, handle_tx + width) && + ty == CLAMP (ty, handle_ty, handle_ty + height)); + + case GIMP_HANDLE_CIRCLE: + case GIMP_HANDLE_FILLED_CIRCLE: + gimp_canvas_item_shift_to_center (anchor, + handle_tx, handle_ty, + width, height, + &handle_tx, &handle_ty); + + /* FIXME */ + if (width != height) + width = (width + height) / 2; + + width /= 2; + + return ((SQR (handle_tx - tx) + SQR (handle_ty - ty)) < SQR (width)); + + default: + g_warning ("%s: invalid handle type %d", G_STRFUNC, type); + break; + } + + return FALSE; +} + +gboolean +gimp_canvas_item_on_vectors_handle (GimpCanvasItem *item, + GimpVectors *vectors, + const GimpCoords *coord, + gint width, + gint height, + GimpAnchorType preferred, + gboolean exclusive, + GimpAnchor **ret_anchor, + GimpStroke **ret_stroke) +{ + GimpStroke *stroke = NULL; + GimpStroke *pref_stroke = NULL; + GimpAnchor *anchor = NULL; + GimpAnchor *pref_anchor = NULL; + gdouble dx, dy; + gdouble pref_mindist = -1; + gdouble mindist = -1; + + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE); + g_return_val_if_fail (coord != NULL, FALSE); + + if (ret_anchor) *ret_anchor = NULL; + if (ret_stroke) *ret_stroke = NULL; + + while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke))) + { + GList *anchor_list; + GList *list; + + anchor_list = g_list_concat (gimp_stroke_get_draw_anchors (stroke), + gimp_stroke_get_draw_controls (stroke)); + + for (list = anchor_list; list; list = g_list_next (list)) + { + dx = coord->x - GIMP_ANCHOR (list->data)->position.x; + dy = coord->y - GIMP_ANCHOR (list->data)->position.y; + + if (mindist < 0 || mindist > dx * dx + dy * dy) + { + mindist = dx * dx + dy * dy; + anchor = GIMP_ANCHOR (list->data); + + if (ret_stroke) + *ret_stroke = stroke; + } + + if ((pref_mindist < 0 || pref_mindist > dx * dx + dy * dy) && + GIMP_ANCHOR (list->data)->type == preferred) + { + pref_mindist = dx * dx + dy * dy; + pref_anchor = GIMP_ANCHOR (list->data); + pref_stroke = stroke; + } + } + + g_list_free (anchor_list); + } + + /* If the data passed into ret_anchor is a preferred anchor, return it. */ + if (ret_anchor && *ret_anchor && + gimp_canvas_item_on_handle (item, + coord->x, + coord->y, + GIMP_HANDLE_CIRCLE, + (*ret_anchor)->position.x, + (*ret_anchor)->position.y, + width, height, + GIMP_HANDLE_ANCHOR_CENTER) && + (*ret_anchor)->type == preferred) + { + if (ret_stroke) *ret_stroke = pref_stroke; + + return TRUE; + } + + if (pref_anchor && gimp_canvas_item_on_handle (item, + coord->x, + coord->y, + GIMP_HANDLE_CIRCLE, + pref_anchor->position.x, + pref_anchor->position.y, + width, height, + GIMP_HANDLE_ANCHOR_CENTER)) + { + if (ret_anchor) *ret_anchor = pref_anchor; + if (ret_stroke) *ret_stroke = pref_stroke; + + return TRUE; + } + else if (!exclusive && anchor && + gimp_canvas_item_on_handle (item, + coord->x, + coord->y, + GIMP_HANDLE_CIRCLE, + anchor->position.x, + anchor->position.y, + width, height, + GIMP_HANDLE_ANCHOR_CENTER)) + { + if (ret_anchor) + *ret_anchor = anchor; + + /* *ret_stroke already set correctly. */ + return TRUE; + } + + if (ret_anchor) + *ret_anchor = NULL; + if (ret_stroke) + *ret_stroke = NULL; + + return FALSE; +} + +gboolean +gimp_canvas_item_on_vectors_curve (GimpCanvasItem *item, + GimpVectors *vectors, + const GimpCoords *coord, + gint width, + gint height, + GimpCoords *ret_coords, + gdouble *ret_pos, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + GimpStroke **ret_stroke) +{ + GimpStroke *stroke = NULL; + GimpAnchor *segment_start; + GimpAnchor *segment_end; + GimpCoords min_coords = GIMP_COORDS_DEFAULT_VALUES; + GimpCoords cur_coords; + gdouble min_dist, cur_dist, cur_pos; + + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE); + g_return_val_if_fail (coord != NULL, FALSE); + + if (ret_coords) *ret_coords = *coord; + if (ret_pos) *ret_pos = -1.0; + if (ret_segment_start) *ret_segment_start = NULL; + if (ret_segment_end) *ret_segment_end = NULL; + if (ret_stroke) *ret_stroke = NULL; + + min_dist = -1.0; + + while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke))) + { + cur_dist = gimp_stroke_nearest_point_get (stroke, coord, 1.0, + &cur_coords, + &segment_start, + &segment_end, + &cur_pos); + + if (cur_dist >= 0 && (min_dist < 0 || cur_dist < min_dist)) + { + min_dist = cur_dist; + min_coords = cur_coords; + + if (ret_coords) *ret_coords = cur_coords; + if (ret_pos) *ret_pos = cur_pos; + if (ret_segment_start) *ret_segment_start = segment_start; + if (ret_segment_end) *ret_segment_end = segment_end; + if (ret_stroke) *ret_stroke = stroke; + } + } + + if (min_dist >= 0 && + gimp_canvas_item_on_handle (item, + coord->x, + coord->y, + GIMP_HANDLE_CIRCLE, + min_coords.x, + min_coords.y, + width, height, + GIMP_HANDLE_ANCHOR_CENTER)) + { + return TRUE; + } + + return FALSE; +} + +gboolean +gimp_canvas_item_on_vectors (GimpCanvasItem *item, + const GimpCoords *coords, + gint width, + gint height, + GimpCoords *ret_coords, + gdouble *ret_pos, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + GimpStroke **ret_stroke, + GimpVectors **ret_vectors) +{ + GimpDisplayShell *shell; + GimpImage *image; + GList *all_vectors; + GList *list; + + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + g_return_val_if_fail (coords != NULL, FALSE); + + shell = gimp_canvas_item_get_shell (item); + image = gimp_display_get_image (shell->display); + + if (ret_coords) *ret_coords = *coords; + if (ret_pos) *ret_pos = -1.0; + if (ret_segment_start) *ret_segment_start = NULL; + if (ret_segment_end) *ret_segment_end = NULL; + if (ret_stroke) *ret_stroke = NULL; + if (ret_vectors) *ret_vectors = NULL; + + all_vectors = gimp_image_get_vectors_list (image); + + for (list = all_vectors; list; list = g_list_next (list)) + { + GimpVectors *vectors = list->data; + + if (! gimp_item_get_visible (GIMP_ITEM (vectors))) + continue; + + if (gimp_canvas_item_on_vectors_curve (item, + vectors, coords, + width, height, + ret_coords, + ret_pos, + ret_segment_start, + ret_segment_end, + ret_stroke)) + { + if (ret_vectors) + *ret_vectors = vectors; + + g_list_free (all_vectors); + + return TRUE; + } + } + + g_list_free (all_vectors); + + return FALSE; +} + +void +gimp_canvas_item_shift_to_north_west (GimpHandleAnchor anchor, + gdouble x, + gdouble y, + gint width, + gint height, + gdouble *shifted_x, + gdouble *shifted_y) +{ + switch (anchor) + { + case GIMP_HANDLE_ANCHOR_CENTER: + x -= width / 2; + y -= height / 2; + break; + + case GIMP_HANDLE_ANCHOR_NORTH: + x -= width / 2; + break; + + case GIMP_HANDLE_ANCHOR_NORTH_WEST: + /* nothing, this is the default */ + break; + + case GIMP_HANDLE_ANCHOR_NORTH_EAST: + x -= width; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH: + x -= width / 2; + y -= height; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_WEST: + y -= height; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_EAST: + x -= width; + y -= height; + break; + + case GIMP_HANDLE_ANCHOR_WEST: + y -= height / 2; + break; + + case GIMP_HANDLE_ANCHOR_EAST: + x -= width; + y -= height / 2; + break; + + default: + break; + } + + if (shifted_x) + *shifted_x = x; + + if (shifted_y) + *shifted_y = y; +} + +void +gimp_canvas_item_shift_to_center (GimpHandleAnchor anchor, + gdouble x, + gdouble y, + gint width, + gint height, + gdouble *shifted_x, + gdouble *shifted_y) +{ + switch (anchor) + { + case GIMP_HANDLE_ANCHOR_CENTER: + /* nothing, this is the default */ + break; + + case GIMP_HANDLE_ANCHOR_NORTH: + y += height / 2; + break; + + case GIMP_HANDLE_ANCHOR_NORTH_WEST: + x += width / 2; + y += height / 2; + break; + + case GIMP_HANDLE_ANCHOR_NORTH_EAST: + x -= width / 2; + y += height / 2; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH: + y -= height / 2; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_WEST: + x += width / 2; + y -= height / 2; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_EAST: + x -= width / 2; + y -= height / 2; + break; + + case GIMP_HANDLE_ANCHOR_WEST: + x += width / 2; + break; + + case GIMP_HANDLE_ANCHOR_EAST: + x -= width / 2; + break; + + default: + break; + } + + if (shifted_x) + *shifted_x = x; + + if (shifted_y) + *shifted_y = y; +} diff --git a/app/display/gimpcanvasitem-utils.h b/app/display/gimpcanvasitem-utils.h new file mode 100644 index 0000000..240385e --- /dev/null +++ b/app/display/gimpcanvasitem-utils.h @@ -0,0 +1,81 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasitem-utils.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_ITEM_UTILS_H__ +#define __GIMP_CANVAS_ITEM_UTILS_H__ + + +gboolean gimp_canvas_item_on_handle (GimpCanvasItem *item, + gdouble x, + gdouble y, + GimpHandleType type, + gdouble handle_x, + gdouble handle_y, + gint width, + gint height, + GimpHandleAnchor anchor); + +gboolean gimp_canvas_item_on_vectors_handle (GimpCanvasItem *item, + GimpVectors *vectors, + const GimpCoords *coord, + gint width, + gint height, + GimpAnchorType preferred, + gboolean exclusive, + GimpAnchor **ret_anchor, + GimpStroke **ret_stroke); +gboolean gimp_canvas_item_on_vectors_curve (GimpCanvasItem *item, + GimpVectors *vectors, + const GimpCoords *coord, + gint width, + gint height, + GimpCoords *ret_coords, + gdouble *ret_pos, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + GimpStroke **ret_stroke); +gboolean gimp_canvas_item_on_vectors (GimpCanvasItem *item, + const GimpCoords *coords, + gint width, + gint height, + GimpCoords *ret_coords, + gdouble *ret_pos, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + GimpStroke **ret_stroke, + GimpVectors **ret_vectors); + +void gimp_canvas_item_shift_to_north_west (GimpHandleAnchor anchor, + gdouble x, + gdouble y, + gint width, + gint height, + gdouble *shifted_x, + gdouble *shifted_y); +void gimp_canvas_item_shift_to_center (GimpHandleAnchor anchor, + gdouble x, + gdouble y, + gint width, + gint height, + gdouble *shifted_x, + gdouble *shifted_y); + + +#endif /* __GIMP_CANVAS_ITEM_UTILS_H__ */ diff --git a/app/display/gimpcanvasitem.c b/app/display/gimpcanvasitem.c new file mode 100644 index 0000000..560210a --- /dev/null +++ b/app/display/gimpcanvasitem.c @@ -0,0 +1,731 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasitem.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvasitem.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-transform.h" + + +enum +{ + PROP_0, + PROP_SHELL, + PROP_VISIBLE, + PROP_LINE_CAP, + PROP_HIGHLIGHT +}; + +enum +{ + UPDATE, + LAST_SIGNAL +}; + + +struct _GimpCanvasItemPrivate +{ + GimpDisplayShell *shell; + gboolean visible; + cairo_line_cap_t line_cap; + gboolean highlight; + gint suspend_stroking; + gint suspend_filling; + gint change_count; + cairo_region_t *change_region; +}; + + +/* local function prototypes */ + +static void gimp_canvas_item_dispose (GObject *object); +static void gimp_canvas_item_constructed (GObject *object); +static void gimp_canvas_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_item_dispatch_properties_changed (GObject *object, + guint n_pspecs, + GParamSpec **pspecs); + +static void gimp_canvas_item_real_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_item_real_get_extents (GimpCanvasItem *item); +static void gimp_canvas_item_real_stroke (GimpCanvasItem *item, + cairo_t *cr); +static void gimp_canvas_item_real_fill (GimpCanvasItem *item, + cairo_t *cr); +static gboolean gimp_canvas_item_real_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasItem, gimp_canvas_item, GIMP_TYPE_OBJECT) + +#define parent_class gimp_canvas_item_parent_class + +static guint item_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_canvas_item_class_init (GimpCanvasItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_canvas_item_dispose; + object_class->constructed = gimp_canvas_item_constructed; + object_class->set_property = gimp_canvas_item_set_property; + object_class->get_property = gimp_canvas_item_get_property; + object_class->dispatch_properties_changed = gimp_canvas_item_dispatch_properties_changed; + + klass->update = NULL; + klass->draw = gimp_canvas_item_real_draw; + klass->get_extents = gimp_canvas_item_real_get_extents; + klass->stroke = gimp_canvas_item_real_stroke; + klass->fill = gimp_canvas_item_real_fill; + klass->hit = gimp_canvas_item_real_hit; + + item_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpCanvasItemClass, update), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + g_object_class_install_property (object_class, PROP_SHELL, + g_param_spec_object ("shell", + NULL, NULL, + GIMP_TYPE_DISPLAY_SHELL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_VISIBLE, + g_param_spec_boolean ("visible", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_LINE_CAP, + g_param_spec_int ("line-cap", + NULL, NULL, + CAIRO_LINE_CAP_BUTT, + CAIRO_LINE_CAP_SQUARE, + CAIRO_LINE_CAP_ROUND, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HIGHLIGHT, + g_param_spec_boolean ("highlight", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_item_init (GimpCanvasItem *item) +{ + GimpCanvasItemPrivate *private; + + item->private = gimp_canvas_item_get_instance_private (item); + private = item->private; + + private->shell = NULL; + private->visible = TRUE; + private->line_cap = CAIRO_LINE_CAP_ROUND; + private->highlight = FALSE; + private->suspend_stroking = 0; + private->suspend_filling = 0; + private->change_count = 1; /* avoid emissions during construction */ + private->change_region = NULL; +} + +static void +gimp_canvas_item_constructed (GObject *object) +{ + GimpCanvasItem *item = GIMP_CANVAS_ITEM (object); + + gimp_assert (GIMP_IS_DISPLAY_SHELL (item->private->shell)); + + item->private->change_count = 0; /* undo hack from init() */ + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gimp_canvas_item_dispose (GObject *object) +{ + GimpCanvasItem *item = GIMP_CANVAS_ITEM (object); + + item->private->change_count++; /* avoid emissions during destruction */ + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_canvas_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasItem *item = GIMP_CANVAS_ITEM (object); + GimpCanvasItemPrivate *private = item->private; + + switch (property_id) + { + case PROP_SHELL: + private->shell = g_value_get_object (value); /* don't ref */ + break; + case PROP_VISIBLE: + private->visible = g_value_get_boolean (value); + break; + case PROP_LINE_CAP: + private->line_cap = g_value_get_int (value); + break; + case PROP_HIGHLIGHT: + private->highlight = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasItem *item = GIMP_CANVAS_ITEM (object); + GimpCanvasItemPrivate *private = item->private; + + switch (property_id) + { + case PROP_SHELL: + g_value_set_object (value, private->shell); + break; + case PROP_VISIBLE: + g_value_set_boolean (value, private->visible); + break; + case PROP_LINE_CAP: + g_value_set_int (value, private->line_cap); + break; + case PROP_HIGHLIGHT: + g_value_set_boolean (value, private->highlight); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_item_dispatch_properties_changed (GObject *object, + guint n_pspecs, + GParamSpec **pspecs) +{ + GimpCanvasItem *item = GIMP_CANVAS_ITEM (object); + + G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object, + n_pspecs, + pspecs); + + if (_gimp_canvas_item_needs_update (item)) + { + cairo_region_t *region = gimp_canvas_item_get_extents (item); + + if (region) + { + g_signal_emit (object, item_signals[UPDATE], 0, + region); + cairo_region_destroy (region); + } + } +} + +static void +gimp_canvas_item_real_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + g_warn_if_reached (); +} + +static cairo_region_t * +gimp_canvas_item_real_get_extents (GimpCanvasItem *item) +{ + return NULL; +} + +static void +gimp_canvas_item_real_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + cairo_set_line_cap (cr, item->private->line_cap); + + gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr); + cairo_stroke_preserve (cr); + + gimp_canvas_set_tool_fg_style (gimp_canvas_item_get_canvas (item), cr, + item->private->highlight); + cairo_stroke (cr); +} + +static void +gimp_canvas_item_real_fill (GimpCanvasItem *item, + cairo_t *cr) +{ + gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr); + cairo_set_line_width (cr, 2.0); + cairo_stroke_preserve (cr); + + gimp_canvas_set_tool_fg_style (gimp_canvas_item_get_canvas (item), cr, + item->private->highlight); + cairo_fill (cr); +} + +static gboolean +gimp_canvas_item_real_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + return FALSE; +} + + +/* public functions */ + +GimpDisplayShell * +gimp_canvas_item_get_shell (GimpCanvasItem *item) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL); + + return item->private->shell; +} + +GimpImage * +gimp_canvas_item_get_image (GimpCanvasItem *item) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL); + + return gimp_display_get_image (item->private->shell->display); +} + +GtkWidget * +gimp_canvas_item_get_canvas (GimpCanvasItem *item) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL); + + return item->private->shell->canvas; +} + +void +gimp_canvas_item_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + g_return_if_fail (cr != NULL); + + if (item->private->visible) + { + cairo_save (cr); + GIMP_CANVAS_ITEM_GET_CLASS (item)->draw (item, cr); + cairo_restore (cr); + } +} + +cairo_region_t * +gimp_canvas_item_get_extents (GimpCanvasItem *item) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), NULL); + + if (item->private->visible) + return GIMP_CANVAS_ITEM_GET_CLASS (item)->get_extents (item); + + return NULL; +} + +gboolean +gimp_canvas_item_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + + if (item->private->visible) + return GIMP_CANVAS_ITEM_GET_CLASS (item)->hit (item, x, y); + + return FALSE; +} + +void +gimp_canvas_item_set_visible (GimpCanvasItem *item, + gboolean visible) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + if (item->private->visible != visible) + { + gimp_canvas_item_begin_change (item); + g_object_set (G_OBJECT (item), + "visible", visible, + NULL); + gimp_canvas_item_end_change (item); + } +} + +gboolean +gimp_canvas_item_get_visible (GimpCanvasItem *item) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + + return item->private->visible; +} + +void +gimp_canvas_item_set_line_cap (GimpCanvasItem *item, + cairo_line_cap_t line_cap) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + if (item->private->line_cap != line_cap) + { + gimp_canvas_item_begin_change (item); + g_object_set (G_OBJECT (item), + "line-cap", line_cap, + NULL); + gimp_canvas_item_end_change (item); + } +} + +void +gimp_canvas_item_set_highlight (GimpCanvasItem *item, + gboolean highlight) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + if (item->private->highlight != highlight) + { + g_object_set (G_OBJECT (item), + "highlight", highlight, + NULL); + } +} + +gboolean +gimp_canvas_item_get_highlight (GimpCanvasItem *item) +{ + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE); + + return item->private->highlight; +} + +void +gimp_canvas_item_begin_change (GimpCanvasItem *item) +{ + GimpCanvasItemPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + private = item->private; + + private->change_count++; + + if (private->change_count == 1 && + g_signal_has_handler_pending (item, item_signals[UPDATE], 0, FALSE)) + { + private->change_region = gimp_canvas_item_get_extents (item); + } +} + +void +gimp_canvas_item_end_change (GimpCanvasItem *item) +{ + GimpCanvasItemPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + private = item->private; + + g_return_if_fail (private->change_count > 0); + + private->change_count--; + + if (private->change_count == 0) + { + if (g_signal_has_handler_pending (item, item_signals[UPDATE], 0, FALSE)) + { + cairo_region_t *region = gimp_canvas_item_get_extents (item); + + if (! region) + { + region = private->change_region; + } + else if (private->change_region) + { + cairo_region_union (region, private->change_region); + cairo_region_destroy (private->change_region); + } + + private->change_region = NULL; + + if (region) + { + g_signal_emit (item, item_signals[UPDATE], 0, + region); + cairo_region_destroy (region); + } + } + else + { + g_clear_pointer (&private->change_region, cairo_region_destroy); + } + } +} + +void +gimp_canvas_item_suspend_stroking (GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + item->private->suspend_stroking++; +} + +void +gimp_canvas_item_resume_stroking (GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + g_return_if_fail (item->private->suspend_stroking > 0); + + item->private->suspend_stroking--; +} + +void +gimp_canvas_item_suspend_filling (GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + item->private->suspend_filling++; +} + +void +gimp_canvas_item_resume_filling (GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + g_return_if_fail (item->private->suspend_filling > 0); + + item->private->suspend_filling--; +} + +void +gimp_canvas_item_transform (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasItemPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + g_return_if_fail (cr != NULL); + + private = item->private; + + cairo_translate (cr, -private->shell->offset_x, -private->shell->offset_y); + cairo_scale (cr, private->shell->scale_x, private->shell->scale_y); +} + +void +gimp_canvas_item_transform_xy (GimpCanvasItem *item, + gdouble x, + gdouble y, + gint *tx, + gint *ty) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_display_shell_zoom_xy (item->private->shell, x, y, tx, ty); +} + +void +gimp_canvas_item_transform_xy_f (GimpCanvasItem *item, + gdouble x, + gdouble y, + gdouble *tx, + gdouble *ty) +{ + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_display_shell_zoom_xy_f (item->private->shell, x, y, tx, ty); +} + +/** + * gimp_canvas_item_transform_distance: + * @item: a #GimpCanvasItem + * @x1: start point X in image coordinates + * @y1: start point Y in image coordinates + * @x2: end point X in image coordinates + * @y2: end point Y in image coordinates + * + * If you just need to compare distances, consider to use + * gimp_canvas_item_transform_distance_square() instead. + * + * Returns: the distance between the given points in display coordinates + **/ +gdouble +gimp_canvas_item_transform_distance (GimpCanvasItem *item, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + return sqrt (gimp_canvas_item_transform_distance_square (item, + x1, y1, x2, y2)); +} + +/** + * gimp_canvas_item_transform_distance_square: + * @item: a #GimpCanvasItem + * @x1: start point X in image coordinates + * @y1: start point Y in image coordinates + * @x2: end point X in image coordinates + * @y2: end point Y in image coordinates + * + * This function is more effective than + * gimp_canvas_item_transform_distance() as it doesn't perform a + * sqrt(). Use this if you just need to compare distances. + * + * Returns: the square of the distance between the given points in + * display coordinates + **/ +gdouble +gimp_canvas_item_transform_distance_square (GimpCanvasItem *item, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + gdouble tx1, ty1; + gdouble tx2, ty2; + + g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), 0.0); + + gimp_canvas_item_transform_xy_f (item, x1, y1, &tx1, &ty1); + gimp_canvas_item_transform_xy_f (item, x2, y2, &tx2, &ty2); + + return SQR (tx2 - tx1) + SQR (ty2 - ty1); +} + +void +gimp_canvas_item_untransform_viewport (GimpCanvasItem *item, + gint *x, + gint *y, + gint *w, + gint *h) +{ + GimpDisplayShell *shell; + gdouble x1, y1; + gdouble x2, y2; + + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + shell = item->private->shell; + + gimp_display_shell_unrotate_bounds (shell, + 0.0, 0.0, + shell->disp_width, shell->disp_height, + &x1, &y1, + &x2, &y2); + + *x = floor (x1); + *y = floor (y1); + *w = ceil (x2) - *x; + *h = ceil (y2) - *y; +} + + +/* protected functions */ + +void +_gimp_canvas_item_update (GimpCanvasItem *item, + cairo_region_t *region) +{ + g_signal_emit (item, item_signals[UPDATE], 0, + region); +} + +gboolean +_gimp_canvas_item_needs_update (GimpCanvasItem *item) +{ + return (item->private->change_count == 0 && + g_signal_has_handler_pending (item, item_signals[UPDATE], 0, FALSE)); +} + +void +_gimp_canvas_item_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + if (item->private->suspend_filling > 0) + g_warning ("_gimp_canvas_item_stroke() on an item that is in a filling group"); + + if (item->private->suspend_stroking == 0) + { + GIMP_CANVAS_ITEM_GET_CLASS (item)->stroke (item, cr); + } + else + { + cairo_new_sub_path (cr); + } +} + +void +_gimp_canvas_item_fill (GimpCanvasItem *item, + cairo_t *cr) +{ + if (item->private->suspend_stroking > 0) + g_warning ("_gimp_canvas_item_fill() on an item that is in a stroking group"); + + if (item->private->suspend_filling == 0) + { + GIMP_CANVAS_ITEM_GET_CLASS (item)->fill (item, cr); + } + else + { + cairo_new_sub_path (cr); + } +} diff --git a/app/display/gimpcanvasitem.h b/app/display/gimpcanvasitem.h new file mode 100644 index 0000000..2004260 --- /dev/null +++ b/app/display/gimpcanvasitem.h @@ -0,0 +1,147 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasitem.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_ITEM_H__ +#define __GIMP_CANVAS_ITEM_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_CANVAS_ITEM (gimp_canvas_item_get_type ()) +#define GIMP_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_ITEM, GimpCanvasItem)) +#define GIMP_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_ITEM, GimpCanvasItemClass)) +#define GIMP_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_ITEM)) +#define GIMP_IS_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_ITEM)) +#define GIMP_CANVAS_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_ITEM, GimpCanvasItemClass)) + + +typedef struct _GimpCanvasItemPrivate GimpCanvasItemPrivate; +typedef struct _GimpCanvasItemClass GimpCanvasItemClass; + +struct _GimpCanvasItem +{ + GimpObject parent_instance; + + GimpCanvasItemPrivate *private; +}; + +struct _GimpCanvasItemClass +{ + GimpObjectClass parent_class; + + /* signals */ + void (* update) (GimpCanvasItem *item, + cairo_region_t *region); + + /* virtual functions */ + void (* draw) (GimpCanvasItem *item, + cairo_t *cr); + cairo_region_t * (* get_extents) (GimpCanvasItem *item); + + void (* stroke) (GimpCanvasItem *item, + cairo_t *cr); + void (* fill) (GimpCanvasItem *item, + cairo_t *cr); + + gboolean (* hit) (GimpCanvasItem *item, + gdouble x, + gdouble y); +}; + + +GType gimp_canvas_item_get_type (void) G_GNUC_CONST; + +GimpDisplayShell * gimp_canvas_item_get_shell (GimpCanvasItem *item); +GimpImage * gimp_canvas_item_get_image (GimpCanvasItem *item); +GtkWidget * gimp_canvas_item_get_canvas (GimpCanvasItem *item); + +void gimp_canvas_item_draw (GimpCanvasItem *item, + cairo_t *cr); +cairo_region_t * gimp_canvas_item_get_extents (GimpCanvasItem *item); + +gboolean gimp_canvas_item_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + +void gimp_canvas_item_set_visible (GimpCanvasItem *item, + gboolean visible); +gboolean gimp_canvas_item_get_visible (GimpCanvasItem *item); + +void gimp_canvas_item_set_line_cap (GimpCanvasItem *item, + cairo_line_cap_t line_cap); + +void gimp_canvas_item_set_highlight (GimpCanvasItem *item, + gboolean highlight); +gboolean gimp_canvas_item_get_highlight (GimpCanvasItem *item); + +void gimp_canvas_item_begin_change (GimpCanvasItem *item); +void gimp_canvas_item_end_change (GimpCanvasItem *item); + +void gimp_canvas_item_suspend_stroking (GimpCanvasItem *item); +void gimp_canvas_item_resume_stroking (GimpCanvasItem *item); + +void gimp_canvas_item_suspend_filling (GimpCanvasItem *item); +void gimp_canvas_item_resume_filling (GimpCanvasItem *item); + +void gimp_canvas_item_transform (GimpCanvasItem *item, + cairo_t *cr); +void gimp_canvas_item_transform_xy (GimpCanvasItem *item, + gdouble x, + gdouble y, + gint *tx, + gint *ty); +void gimp_canvas_item_transform_xy_f (GimpCanvasItem *item, + gdouble x, + gdouble y, + gdouble *tx, + gdouble *ty); +gdouble gimp_canvas_item_transform_distance + (GimpCanvasItem *item, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); +gdouble gimp_canvas_item_transform_distance_square + (GimpCanvasItem *item, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); +void gimp_canvas_item_untransform_viewport + (GimpCanvasItem *item, + gint *x, + gint *y, + gint *w, + gint *h); + + +/* protected */ + +void _gimp_canvas_item_update (GimpCanvasItem *item, + cairo_region_t *region); +gboolean _gimp_canvas_item_needs_update (GimpCanvasItem *item); +void _gimp_canvas_item_stroke (GimpCanvasItem *item, + cairo_t *cr); +void _gimp_canvas_item_fill (GimpCanvasItem *item, + cairo_t *cr); + + +#endif /* __GIMP_CANVAS_ITEM_H__ */ diff --git a/app/display/gimpcanvaslayerboundary.c b/app/display/gimpcanvaslayerboundary.c new file mode 100644 index 0000000..0e4b43b --- /dev/null +++ b/app/display/gimpcanvaslayerboundary.c @@ -0,0 +1,322 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaslayerboundary.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpchannel.h" +#include "core/gimplayer.h" +#include "core/gimplayer-floating-selection.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvaslayerboundary.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_LAYER, + PROP_EDIT_MASK +}; + + +typedef struct _GimpCanvasLayerBoundaryPrivate GimpCanvasLayerBoundaryPrivate; + +struct _GimpCanvasLayerBoundaryPrivate +{ + GimpLayer *layer; + gboolean edit_mask; +}; + +#define GET_PRIVATE(layer_boundary) \ + ((GimpCanvasLayerBoundaryPrivate *) gimp_canvas_layer_boundary_get_instance_private ((GimpCanvasLayerBoundary *) (layer_boundary))) + + +/* local function prototypes */ + +static void gimp_canvas_layer_boundary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_layer_boundary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_layer_boundary_finalize (GObject *object); +static void gimp_canvas_layer_boundary_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_layer_boundary_get_extents (GimpCanvasItem *item); +static void gimp_canvas_layer_boundary_stroke (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasLayerBoundary, gimp_canvas_layer_boundary, + GIMP_TYPE_CANVAS_RECTANGLE) + +#define parent_class gimp_canvas_layer_boundary_parent_class + + +static void +gimp_canvas_layer_boundary_class_init (GimpCanvasLayerBoundaryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_layer_boundary_set_property; + object_class->get_property = gimp_canvas_layer_boundary_get_property; + object_class->finalize = gimp_canvas_layer_boundary_finalize; + + item_class->draw = gimp_canvas_layer_boundary_draw; + item_class->get_extents = gimp_canvas_layer_boundary_get_extents; + item_class->stroke = gimp_canvas_layer_boundary_stroke; + + g_object_class_install_property (object_class, PROP_LAYER, + g_param_spec_object ("layer", NULL, NULL, + GIMP_TYPE_LAYER, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_EDIT_MASK, + g_param_spec_boolean ("edit-mask", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_layer_boundary_init (GimpCanvasLayerBoundary *layer_boundary) +{ +} + +static void +gimp_canvas_layer_boundary_finalize (GObject *object) +{ + GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (object); + + if (private->layer) + g_object_remove_weak_pointer (G_OBJECT (private->layer), + (gpointer) &private->layer); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_layer_boundary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_LAYER: + if (private->layer) + g_object_remove_weak_pointer (G_OBJECT (private->layer), + (gpointer) &private->layer); + private->layer = g_value_get_object (value); /* don't ref */ + if (private->layer) + g_object_add_weak_pointer (G_OBJECT (private->layer), + (gpointer) &private->layer); + break; + case PROP_EDIT_MASK: + private->edit_mask = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_layer_boundary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_LAYER: + g_value_set_object (value, private->layer); + break; + case PROP_EDIT_MASK: + g_value_set_boolean (value, private->edit_mask); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_layer_boundary_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (item); + + if (private->layer) + GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr); +} + +static cairo_region_t * +gimp_canvas_layer_boundary_get_extents (GimpCanvasItem *item) +{ + GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (item); + + if (private->layer) + return GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item); + + return NULL; +} + +static void +gimp_canvas_layer_boundary_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasLayerBoundaryPrivate *private = GET_PRIVATE (item); + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + + gimp_canvas_set_layer_style (gimp_canvas_item_get_canvas (item), cr, + private->layer, + shell->offset_x, shell->offset_y); + cairo_stroke (cr); +} + +GimpCanvasItem * +gimp_canvas_layer_boundary_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_LAYER_BOUNDARY, + "shell", shell, + NULL); +} + +void +gimp_canvas_layer_boundary_set_layer (GimpCanvasLayerBoundary *boundary, + GimpLayer *layer) +{ + GimpCanvasLayerBoundaryPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_LAYER_BOUNDARY (boundary)); + g_return_if_fail (layer == NULL || GIMP_IS_LAYER (layer)); + + private = GET_PRIVATE (boundary); + + if (layer && gimp_layer_is_floating_sel (layer)) + { + GimpDrawable *drawable; + + drawable = gimp_layer_get_floating_sel_drawable (layer); + + if (GIMP_IS_CHANNEL (drawable)) + { + /* if the owner drawable is a channel, show no outline */ + + layer = NULL; + } + else + { + /* otherwise, set the layer to the owner drawable */ + + layer = GIMP_LAYER (drawable); + } + } + + if (layer != private->layer) + { + gboolean edit_mask = FALSE; + + gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary)); + + if (layer) + { + GimpItem *item = GIMP_ITEM (layer); + + edit_mask = (gimp_layer_get_mask (layer) && + gimp_layer_get_edit_mask (layer)); + + g_object_set (boundary, + "x", (gdouble) gimp_item_get_offset_x (item), + "y", (gdouble) gimp_item_get_offset_y (item), + "width", (gdouble) gimp_item_get_width (item), + "height", (gdouble) gimp_item_get_height (item), + NULL); + } + + g_object_set (boundary, + "layer", layer, + "edit-mask", edit_mask, + NULL); + + gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary)); + } + else if (layer && layer == private->layer) + { + GimpItem *item = GIMP_ITEM (layer); + gint lx, ly, lw, lh; + gdouble x, y, w ,h; + gboolean edit_mask; + + lx = gimp_item_get_offset_x (item); + ly = gimp_item_get_offset_y (item); + lw = gimp_item_get_width (item); + lh = gimp_item_get_height (item); + + edit_mask = (gimp_layer_get_mask (layer) && + gimp_layer_get_edit_mask (layer)); + + g_object_get (boundary, + "x", &x, + "y", &y, + "width", &w, + "height", &h, + NULL); + + if (lx != (gint) x || + ly != (gint) y || + lw != (gint) w || + lh != (gint) h || + edit_mask != private->edit_mask) + { + gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (boundary)); + + g_object_set (boundary, + "x", (gdouble) lx, + "y", (gdouble) ly, + "width", (gdouble) lw, + "height", (gdouble) lh, + "edit-mask", edit_mask, + NULL); + + gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (boundary)); + } + } +} diff --git a/app/display/gimpcanvaslayerboundary.h b/app/display/gimpcanvaslayerboundary.h new file mode 100644 index 0000000..5f90131 --- /dev/null +++ b/app/display/gimpcanvaslayerboundary.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaslayerboundary.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_LAYER_BOUNDARY_H__ +#define __GIMP_CANVAS_LAYER_BOUNDARY_H__ + + +#include "gimpcanvasrectangle.h" + + +#define GIMP_TYPE_CANVAS_LAYER_BOUNDARY (gimp_canvas_layer_boundary_get_type ()) +#define GIMP_CANVAS_LAYER_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_LAYER_BOUNDARY, GimpCanvasLayerBoundary)) +#define GIMP_CANVAS_LAYER_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_LAYER_BOUNDARY, GimpCanvasLayerBoundaryClass)) +#define GIMP_IS_CANVAS_LAYER_BOUNDARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_LAYER_BOUNDARY)) +#define GIMP_IS_CANVAS_LAYER_BOUNDARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_LAYER_BOUNDARY)) +#define GIMP_CANVAS_LAYER_BOUNDARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_LAYER_BOUNDARY, GimpCanvasLayerBoundaryClass)) + + +typedef struct _GimpCanvasLayerBoundary GimpCanvasLayerBoundary; +typedef struct _GimpCanvasLayerBoundaryClass GimpCanvasLayerBoundaryClass; + +struct _GimpCanvasLayerBoundary +{ + GimpCanvasRectangle parent_instance; +}; + +struct _GimpCanvasLayerBoundaryClass +{ + GimpCanvasRectangleClass parent_class; +}; + + +GType gimp_canvas_layer_boundary_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_layer_boundary_new (GimpDisplayShell *shell); + +void gimp_canvas_layer_boundary_set_layer (GimpCanvasLayerBoundary *boundary, + GimpLayer *layer); + + +#endif /* __GIMP_CANVAS_LAYER_BOUNDARY_H__ */ diff --git a/app/display/gimpcanvaslimit.c b/app/display/gimpcanvaslimit.c new file mode 100644 index 0000000..05dacbb --- /dev/null +++ b/app/display/gimpcanvaslimit.c @@ -0,0 +1,760 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaslimit.c + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" + +#include "gimpcanvaslimit.h" +#include "gimpdisplayshell.h" + + +#define DASH_LENGTH 4.0 +#define HIT_DISTANCE 16.0 + + +enum +{ + PROP_0, + PROP_TYPE, + PROP_X, + PROP_Y, + PROP_RADIUS, + PROP_ASPECT_RATIO, + PROP_ANGLE, + PROP_DASHED +}; + + +typedef struct _GimpCanvasLimitPrivate GimpCanvasLimitPrivate; + +struct _GimpCanvasLimitPrivate +{ + GimpLimitType type; + + gdouble x; + gdouble y; + gdouble radius; + gdouble aspect_ratio; + gdouble angle; + + gboolean dashed; +}; + +#define GET_PRIVATE(limit) \ + ((GimpCanvasLimitPrivate *) gimp_canvas_limit_get_instance_private ((GimpCanvasLimit *) (limit))) + + +/* local function prototypes */ + +static void gimp_canvas_limit_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_limit_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_canvas_limit_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_limit_get_extents (GimpCanvasItem *item); +static gboolean gimp_canvas_limit_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasLimit, gimp_canvas_limit, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_limit_parent_class + + +/* private functions */ + +static void +gimp_canvas_limit_class_init (GimpCanvasLimitClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_limit_set_property; + object_class->get_property = gimp_canvas_limit_get_property; + + item_class->draw = gimp_canvas_limit_draw; + item_class->get_extents = gimp_canvas_limit_get_extents; + item_class->hit = gimp_canvas_limit_hit; + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", NULL, NULL, + GIMP_TYPE_LIMIT_TYPE, + GIMP_LIMIT_CIRCLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_RADIUS, + g_param_spec_double ("radius", NULL, NULL, + 0.0, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ASPECT_RATIO, + g_param_spec_double ("aspect-ratio", NULL, NULL, + -1.0, + +1.0, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ANGLE, + g_param_spec_double ("angle", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_DASHED, + g_param_spec_boolean ("dashed", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_limit_init (GimpCanvasLimit *limit) +{ +} + +static void +gimp_canvas_limit_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasLimitPrivate *priv = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TYPE: + priv->type = g_value_get_enum (value); + break; + + case PROP_X: + priv->x = g_value_get_double (value); + break; + + case PROP_Y: + priv->y = g_value_get_double (value); + break; + + case PROP_RADIUS: + priv->radius = g_value_get_double (value); + break; + + case PROP_ASPECT_RATIO: + priv->aspect_ratio = g_value_get_double (value); + break; + + case PROP_ANGLE: + priv->angle = g_value_get_double (value); + break; + + case PROP_DASHED: + priv->dashed = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_limit_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasLimitPrivate *priv = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TYPE: + g_value_set_enum (value, priv->type); + break; + + case PROP_X: + g_value_set_double (value, priv->x); + break; + + case PROP_Y: + g_value_set_double (value, priv->y); + break; + + case PROP_RADIUS: + g_value_set_double (value, priv->radius); + break; + + case PROP_ASPECT_RATIO: + g_value_set_double (value, priv->aspect_ratio); + break; + + case PROP_ANGLE: + g_value_set_double (value, priv->angle); + break; + + case PROP_DASHED: + g_value_set_boolean (value, priv->dashed); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_limit_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y, + gdouble *rx, + gdouble *ry) +{ + GimpCanvasLimit *limit = GIMP_CANVAS_LIMIT (item); + GimpCanvasLimitPrivate *priv = GET_PRIVATE (item); + gdouble x1, y1; + gdouble x2, y2; + gdouble min_radius = 0.0; + + gimp_canvas_limit_get_radii (limit, rx, ry); + + gimp_canvas_item_transform_xy_f (item, + priv->x - *rx, priv->y - *ry, + &x1, &y1); + gimp_canvas_item_transform_xy_f (item, + priv->x + *rx, priv->y + *ry, + &x2, &y2); + + x1 = floor (x1) + 0.5; + y1 = floor (y1) + 0.5; + + x2 = floor (x2) + 0.5; + y2 = floor (y2) + 0.5; + + *x = (x1 + x2) / 2.0; + *y = (y1 + y2) / 2.0; + + *rx = (x2 - x1) / 2.0; + *ry = (y2 - y1) / 2.0; + + switch (priv->type) + { + case GIMP_LIMIT_CIRCLE: + case GIMP_LIMIT_SQUARE: + min_radius = 2.0; + break; + + case GIMP_LIMIT_DIAMOND: + min_radius = 3.0; + break; + + case GIMP_LIMIT_HORIZONTAL: + case GIMP_LIMIT_VERTICAL: + min_radius = 1.0; + break; + } + + *rx = MAX (*rx, min_radius); + *ry = MAX (*ry, min_radius); +} + +static void +gimp_canvas_limit_paint (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasLimitPrivate *priv = GET_PRIVATE (item); + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + gdouble x, y; + gdouble rx, ry; + gdouble inf; + + gimp_canvas_limit_transform (item, + &x, &y, + &rx, &ry); + + cairo_save (cr); + + cairo_translate (cr, x, y); + cairo_rotate (cr, priv->angle); + cairo_scale (cr, rx, ry); + + inf = MAX (x, shell->disp_width - x) + + MAX (y, shell->disp_height - y); + + switch (priv->type) + { + case GIMP_LIMIT_CIRCLE: + cairo_arc (cr, 0.0, 0.0, 1.0, 0.0, 2.0 * G_PI); + break; + + case GIMP_LIMIT_SQUARE: + cairo_rectangle (cr, -1.0, -1.0, 2.0, 2.0); + break; + + case GIMP_LIMIT_DIAMOND: + cairo_move_to (cr, 0.0, -1.0); + cairo_line_to (cr, +1.0, 0.0); + cairo_line_to (cr, 0.0, +1.0); + cairo_line_to (cr, -1.0, 0.0); + cairo_close_path (cr); + break; + + case GIMP_LIMIT_HORIZONTAL: + cairo_move_to (cr, -inf / rx, -1.0); + cairo_line_to (cr, +inf / rx, -1.0); + + cairo_move_to (cr, -inf / rx, +1.0); + cairo_line_to (cr, +inf / rx, +1.0); + break; + + case GIMP_LIMIT_VERTICAL: + cairo_move_to (cr, -1.0, -inf / ry); + cairo_line_to (cr, -1.0, +inf / ry); + + cairo_move_to (cr, +1.0, -inf / ry); + cairo_line_to (cr, +1.0, +inf / ry); + break; + } + + cairo_restore (cr); +} + +static void +gimp_canvas_limit_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasLimitPrivate *priv = GET_PRIVATE (item); + + gimp_canvas_limit_paint (item, cr); + + cairo_save (cr); + + if (priv->dashed) + cairo_set_dash (cr, (const gdouble[]) {DASH_LENGTH}, 1, 0.0); + + _gimp_canvas_item_stroke (item, cr); + + cairo_restore (cr); +} + +static cairo_region_t * +gimp_canvas_limit_get_extents (GimpCanvasItem *item) +{ + cairo_t *cr; + cairo_surface_t *surface; + cairo_rectangle_int_t rectangle; + gdouble x1, y1; + gdouble x2, y2; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1); + + cr = cairo_create (surface); + + gimp_canvas_limit_paint (item, cr); + + cairo_path_extents (cr, + &x1, &y1, + &x2, &y2); + + cairo_destroy (cr); + + cairo_surface_destroy (surface); + + rectangle.x = floor (x1 - 1.5); + rectangle.y = floor (y1 - 1.5); + rectangle.width = ceil (x2 + 1.5) - rectangle.x; + rectangle.height = ceil (y2 + 1.5) - rectangle.y; + + return cairo_region_create_rectangle (&rectangle); +} + +static gboolean +gimp_canvas_limit_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + GimpCanvasLimit *limit = GIMP_CANVAS_LIMIT (item); + gdouble bx, by; + + gimp_canvas_limit_boundary_point (limit, + x, y, + &bx, &by); + + return gimp_canvas_item_transform_distance (item, + x, y, + bx, by) <= HIT_DISTANCE; +} + + +/* public functions */ + +GimpCanvasItem * +gimp_canvas_limit_new (GimpDisplayShell *shell, + GimpLimitType type, + gdouble x, + gdouble y, + gdouble radius, + gdouble aspect_ratio, + gdouble angle, + gboolean dashed) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_LIMIT, + "shell", shell, + "type", type, + "x", x, + "y", y, + "radius", radius, + "aspect-ratio", aspect_ratio, + "angle", angle, + "dashed", dashed, + NULL); +} + +void +gimp_canvas_limit_get_radii (GimpCanvasLimit *limit, + gdouble *rx, + gdouble *ry) +{ + GimpCanvasLimitPrivate *priv; + + g_return_if_fail (GIMP_IS_CANVAS_LIMIT (limit)); + + priv = GET_PRIVATE (limit); + + if (priv->aspect_ratio >= 0.0) + { + if (rx) *rx = priv->radius; + if (ry) *ry = priv->radius * (1.0 - priv->aspect_ratio); + } + else + { + if (rx) *rx = priv->radius * (1.0 + priv->aspect_ratio); + if (ry) *ry = priv->radius; + } +} + +gboolean +gimp_canvas_limit_is_inside (GimpCanvasLimit *limit, + gdouble x, + gdouble y) +{ + GimpCanvasLimitPrivate *priv; + GimpVector2 p; + gdouble rx, ry; + + g_return_val_if_fail (GIMP_IS_CANVAS_LIMIT (limit), FALSE); + + priv = GET_PRIVATE (limit); + + gimp_canvas_limit_get_radii (limit, &rx, &ry); + + if (rx == 0.0 || ry == 0.0) + return FALSE; + + p.x = x - priv->x; + p.y = y - priv->y; + + gimp_vector2_rotate (&p, +priv->angle); + + p.x = fabs (p.x / rx); + p.y = fabs (p.y / ry); + + switch (priv->type) + { + case GIMP_LIMIT_CIRCLE: + return gimp_vector2_length (&p) < 1.0; + + case GIMP_LIMIT_SQUARE: + return p.x < 1.0 && p.y < 1.0; + + case GIMP_LIMIT_DIAMOND: + return p.x + p.y < 1.0; + + case GIMP_LIMIT_HORIZONTAL: + return p.y < 1.0; + + case GIMP_LIMIT_VERTICAL: + return p.x < 1.0; + } + + g_return_val_if_reached (FALSE); +} + +void +gimp_canvas_limit_boundary_point (GimpCanvasLimit *limit, + gdouble x, + gdouble y, + gdouble *bx, + gdouble *by) +{ + GimpCanvasLimitPrivate *priv; + GimpVector2 p; + gdouble rx, ry; + gboolean flip_x = FALSE; + gboolean flip_y = FALSE; + + g_return_if_fail (GIMP_IS_CANVAS_LIMIT (limit)); + g_return_if_fail (bx != NULL); + g_return_if_fail (by != NULL); + + priv = GET_PRIVATE (limit); + + gimp_canvas_limit_get_radii (limit, &rx, &ry); + + p.x = x - priv->x; + p.y = y - priv->y; + + gimp_vector2_rotate (&p, +priv->angle); + + if (p.x < 0.0) + { + p.x = -p.x; + + flip_x = TRUE; + } + + if (p.y < 0.0) + { + p.y = -p.y; + + flip_y = TRUE; + } + + switch (priv->type) + { + case GIMP_LIMIT_CIRCLE: + if (rx == ry) + { + gimp_vector2_normalize (&p); + + gimp_vector2_mul (&p, rx); + } + else + { + gdouble a0 = 0.0; + gdouble a1 = G_PI / 2.0; + gdouble a; + gint i; + + for (i = 0; i < 20; i++) + { + GimpVector2 r; + GimpVector2 n; + + a = (a0 + a1) / 2.0; + + r.x = p.x - rx * cos (a); + r.y = p.y - ry * sin (a); + + n.x = 1.0; + n.y = tan (a) * rx / ry; + + if (gimp_vector2_cross_product (&r, &n).x >= 0.0) + a1 = a; + else + a0 = a; + } + + a = (a0 + a1) / 2.0; + + p.x = rx * cos (a); + p.y = ry * sin (a); + } + break; + + case GIMP_LIMIT_SQUARE: + if (p.x <= rx || p.y <= ry) + { + if (rx - p.x <= ry - p.y) + p.x = rx; + else + p.y = ry; + } + else + { + p.x = rx; + p.y = ry; + } + break; + + case GIMP_LIMIT_DIAMOND: + { + GimpVector2 l; + GimpVector2 r; + gdouble t; + + l.x = rx; + l.y = -ry; + + r.x = p.x; + r.y = p.y - ry; + + t = gimp_vector2_inner_product (&r, &l) / + gimp_vector2_inner_product (&l, &l); + t = CLAMP (t, 0.0, 1.0); + + p.x = rx * t; + p.y = ry * (1.0 - t); + } + break; + + case GIMP_LIMIT_HORIZONTAL: + p.y = ry; + break; + + case GIMP_LIMIT_VERTICAL: + p.x = rx; + break; + } + + if (flip_x) + p.x = -p.x; + + if (flip_y) + p.y = -p.y; + + gimp_vector2_rotate (&p, -priv->angle); + + *bx = priv->x + p.x; + *by = priv->y + p.y; +} + +gdouble +gimp_canvas_limit_boundary_radius (GimpCanvasLimit *limit, + gdouble x, + gdouble y) +{ + GimpCanvasLimitPrivate *priv; + GimpVector2 p; + + g_return_val_if_fail (GIMP_IS_CANVAS_LIMIT (limit), 0.0); + + priv = GET_PRIVATE (limit); + + p.x = x - priv->x; + p.y = y - priv->y; + + gimp_vector2_rotate (&p, +priv->angle); + + p.x = fabs (p.x); + p.y = fabs (p.y); + + if (priv->aspect_ratio >= 0.0) + p.y /= 1.0 - priv->aspect_ratio; + else + p.x /= 1.0 + priv->aspect_ratio; + + switch (priv->type) + { + case GIMP_LIMIT_CIRCLE: + return gimp_vector2_length (&p); + + case GIMP_LIMIT_SQUARE: + return MAX (p.x, p.y); + + case GIMP_LIMIT_DIAMOND: + return p.x + p.y; + + case GIMP_LIMIT_HORIZONTAL: + return p.y; + + case GIMP_LIMIT_VERTICAL: + return p.x; + } + + g_return_val_if_reached (0.0); +} + +void +gimp_canvas_limit_center_point (GimpCanvasLimit *limit, + gdouble x, + gdouble y, + gdouble *cx, + gdouble *cy) +{ + GimpCanvasLimitPrivate *priv; + GimpVector2 p; + + g_return_if_fail (GIMP_IS_CANVAS_LIMIT (limit)); + g_return_if_fail (cx != NULL); + g_return_if_fail (cy != NULL); + + priv = GET_PRIVATE (limit); + + p.x = x - priv->x; + p.y = y - priv->y; + + gimp_vector2_rotate (&p, +priv->angle); + + switch (priv->type) + { + case GIMP_LIMIT_CIRCLE: + case GIMP_LIMIT_SQUARE: + case GIMP_LIMIT_DIAMOND: + p.x = 0.0; + p.y = 0.0; + break; + + case GIMP_LIMIT_HORIZONTAL: + p.y = 0.0; + break; + + case GIMP_LIMIT_VERTICAL: + p.x = 0.0; + break; + } + + gimp_vector2_rotate (&p, -priv->angle); + + *cx = priv->x + p.x; + *cy = priv->y + p.y; +} diff --git a/app/display/gimpcanvaslimit.h b/app/display/gimpcanvaslimit.h new file mode 100644 index 0000000..cf6ffc8 --- /dev/null +++ b/app/display/gimpcanvaslimit.h @@ -0,0 +1,84 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaslimit.h + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_LIMIT_H__ +#define __GIMP_CANVAS_LIMIT_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_LIMIT (gimp_canvas_limit_get_type ()) +#define GIMP_CANVAS_LIMIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_LIMIT, GimpCanvasLimit)) +#define GIMP_CANVAS_LIMIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_LIMIT, GimpCanvasLimitClass)) +#define GIMP_IS_CANVAS_LIMIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_LIMIT)) +#define GIMP_IS_CANVAS_LIMIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_LIMIT)) +#define GIMP_CANVAS_LIMIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_LIMIT, GimpCanvasLimitClass)) + + +typedef struct _GimpCanvasLimit GimpCanvasLimit; +typedef struct _GimpCanvasLimitClass GimpCanvasLimitClass; + +struct _GimpCanvasLimit +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasLimitClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_limit_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_limit_new (GimpDisplayShell *shell, + GimpLimitType type, + gdouble x, + gdouble y, + gdouble radius, + gdouble aspect_ratio, + gdouble angle, + gboolean dashed); + +void gimp_canvas_limit_get_radii (GimpCanvasLimit *limit, + gdouble *rx, + gdouble *ry); + +gboolean gimp_canvas_limit_is_inside (GimpCanvasLimit *limit, + gdouble x, + gdouble y); +void gimp_canvas_limit_boundary_point (GimpCanvasLimit *limit, + gdouble x, + gdouble y, + gdouble *bx, + gdouble *by); +gdouble gimp_canvas_limit_boundary_radius (GimpCanvasLimit *limit, + gdouble x, + gdouble y); + +void gimp_canvas_limit_center_point (GimpCanvasLimit *limit, + gdouble x, + gdouble y, + gdouble *cx, + gdouble *cy); + + +#endif /* __GIMP_CANVAS_LIMIT_H__ */ diff --git a/app/display/gimpcanvasline.c b/app/display/gimpcanvasline.c new file mode 100644 index 0000000..38a00c8 --- /dev/null +++ b/app/display/gimpcanvasline.c @@ -0,0 +1,281 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasline.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvasline.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2 +}; + + +typedef struct _GimpCanvasLinePrivate GimpCanvasLinePrivate; + +struct _GimpCanvasLinePrivate +{ + gdouble x1; + gdouble y1; + gdouble x2; + gdouble y2; +}; + +#define GET_PRIVATE(line) \ + ((GimpCanvasLinePrivate *) gimp_canvas_line_get_instance_private ((GimpCanvasLine *) (line))) + + +/* local function prototypes */ + +static void gimp_canvas_line_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_line_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_line_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_line_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasLine, gimp_canvas_line, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_line_parent_class + + +static void +gimp_canvas_line_class_init (GimpCanvasLineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_line_set_property; + object_class->get_property = gimp_canvas_line_get_property; + + item_class->draw = gimp_canvas_line_draw; + item_class->get_extents = gimp_canvas_line_get_extents; + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_line_init (GimpCanvasLine *line) +{ +} + +static void +gimp_canvas_line_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasLinePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X1: + private->x1 = g_value_get_double (value); + break; + case PROP_Y1: + private->y1 = g_value_get_double (value); + break; + case PROP_X2: + private->x2 = g_value_get_double (value); + break; + case PROP_Y2: + private->y2 = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_line_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasLinePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X1: + g_value_set_double (value, private->x1); + break; + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + case PROP_X2: + g_value_set_double (value, private->x2); + break; + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_line_transform (GimpCanvasItem *item, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + GimpCanvasLinePrivate *private = GET_PRIVATE (item); + + gimp_canvas_item_transform_xy_f (item, + private->x1, private->y1, + x1, y1); + gimp_canvas_item_transform_xy_f (item, + private->x2, private->y2, + x2, y2); + + *x1 = floor (*x1) + 0.5; + *y1 = floor (*y1) + 0.5; + *x2 = floor (*x2) + 0.5; + *y2 = floor (*y2) + 0.5; +} + +static void +gimp_canvas_line_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_line_transform (item, &x1, &y1, &x2, &y2); + + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x2, y2); + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_line_get_extents (GimpCanvasItem *item) +{ + cairo_rectangle_int_t rectangle; + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_line_transform (item, &x1, &y1, &x2, &y2); + + if (x1 == x2 || y1 == y2) + { + rectangle.x = MIN (x1, x2) - 1.5; + rectangle.y = MIN (y1, y2) - 1.5; + rectangle.width = ABS (x2 - x1) + 3.0; + rectangle.height = ABS (y2 - y1) + 3.0; + } + else + { + rectangle.x = floor (MIN (x1, x2) - 2.5); + rectangle.y = floor (MIN (y1, y2) - 2.5); + rectangle.width = ceil (ABS (x2 - x1) + 5.0); + rectangle.height = ceil (ABS (y2 - y1) + 5.0); + } + + return cairo_region_create_rectangle (&rectangle); +} + +GimpCanvasItem * +gimp_canvas_line_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_LINE, + "shell", shell, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); +} + +void +gimp_canvas_line_set (GimpCanvasItem *line, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_if_fail (GIMP_IS_CANVAS_LINE (line)); + + gimp_canvas_item_begin_change (line); + + g_object_set (line, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); + + gimp_canvas_item_end_change (line); +} diff --git a/app/display/gimpcanvasline.h b/app/display/gimpcanvasline.h new file mode 100644 index 0000000..6f3f5f2 --- /dev/null +++ b/app/display/gimpcanvasline.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasline.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_LINE_H__ +#define __GIMP_CANVAS_LINE_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_LINE (gimp_canvas_line_get_type ()) +#define GIMP_CANVAS_LINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_LINE, GimpCanvasLine)) +#define GIMP_CANVAS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_LINE, GimpCanvasLineClass)) +#define GIMP_IS_CANVAS_LINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_LINE)) +#define GIMP_IS_CANVAS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_LINE)) +#define GIMP_CANVAS_LINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_LINE, GimpCanvasLineClass)) + + +typedef struct _GimpCanvasLine GimpCanvasLine; +typedef struct _GimpCanvasLineClass GimpCanvasLineClass; + +struct _GimpCanvasLine +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasLineClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_line_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_line_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + +void gimp_canvas_line_set (GimpCanvasItem *line, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + + +#endif /* __GIMP_CANVAS_LINE_H__ */ diff --git a/app/display/gimpcanvaspassepartout.c b/app/display/gimpcanvaspassepartout.c new file mode 100644 index 0000000..21e7307 --- /dev/null +++ b/app/display/gimpcanvaspassepartout.c @@ -0,0 +1,235 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaspassepartout.c + * Copyright (C) 2010 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvasitem-utils.h" +#include "gimpcanvaspassepartout.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scale.h" + + +enum +{ + PROP_0, + PROP_OPACITY, +}; + + +typedef struct _GimpCanvasPassePartoutPrivate GimpCanvasPassePartoutPrivate; + +struct _GimpCanvasPassePartoutPrivate +{ + gdouble opacity; +}; + +#define GET_PRIVATE(item) \ + ((GimpCanvasPassePartoutPrivate *) gimp_canvas_passe_partout_get_instance_private ((GimpCanvasPassePartout *) (item))) + + +/* local function prototypes */ + +static void gimp_canvas_passe_partout_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_passe_partout_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_passe_partout_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_passe_partout_get_extents (GimpCanvasItem *item); +static void gimp_canvas_passe_partout_fill (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPassePartout, gimp_canvas_passe_partout, + GIMP_TYPE_CANVAS_RECTANGLE) + +#define parent_class gimp_canvas_passe_partout_parent_class + + +static void +gimp_canvas_passe_partout_class_init (GimpCanvasPassePartoutClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_passe_partout_set_property; + object_class->get_property = gimp_canvas_passe_partout_get_property; + + item_class->draw = gimp_canvas_passe_partout_draw; + item_class->get_extents = gimp_canvas_passe_partout_get_extents; + item_class->fill = gimp_canvas_passe_partout_fill; + + g_object_class_install_property (object_class, PROP_OPACITY, + g_param_spec_double ("opacity", NULL, NULL, + 0.0, + 1.0, 0.5, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_passe_partout_init (GimpCanvasPassePartout *passe_partout) +{ +} + +static void +gimp_canvas_passe_partout_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPassePartoutPrivate *priv = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_OPACITY: + priv->opacity = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_passe_partout_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPassePartoutPrivate *priv = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_OPACITY: + g_value_set_double (value, priv->opacity); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_passe_partout_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + gint x, y; + gint w, h; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + x = -shell->offset_x; + y = -shell->offset_y; + + gimp_display_shell_scale_get_image_size (shell, &w, &h); + } + else + { + gimp_canvas_item_untransform_viewport (item, &x, &y, &w, &h); + } + + cairo_rectangle (cr, x, y, w, h); + + GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr); +} + +static cairo_region_t * +gimp_canvas_passe_partout_get_extents (GimpCanvasItem *item) +{ + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + cairo_rectangle_int_t rectangle; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + cairo_region_t *inner; + cairo_region_t *outer; + + rectangle.x = - shell->offset_x; + rectangle.y = - shell->offset_y; + gimp_display_shell_scale_get_image_size (shell, + &rectangle.width, + &rectangle.height); + + outer = cairo_region_create_rectangle (&rectangle); + + inner = GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item); + + cairo_region_xor (outer, inner); + + cairo_region_destroy (inner); + + return outer; + } + else + { + gimp_canvas_item_untransform_viewport (item, + &rectangle.x, + &rectangle.y, + &rectangle.width, + &rectangle.height); + + return cairo_region_create_rectangle (&rectangle); + } +} + +static void +gimp_canvas_passe_partout_fill (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasPassePartoutPrivate *priv = GET_PRIVATE (item); + + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_clip (cr); + + gimp_canvas_set_passe_partout_style (gimp_canvas_item_get_canvas (item), cr); + cairo_paint_with_alpha (cr, priv->opacity); +} + +GimpCanvasItem * +gimp_canvas_passe_partout_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_PASSE_PARTOUT, + "shell", shell, + "x", x, + "y", y, + "width", width, + "height", height, + "filled", TRUE, + NULL); +} diff --git a/app/display/gimpcanvaspassepartout.h b/app/display/gimpcanvaspassepartout.h new file mode 100644 index 0000000..7356e3a --- /dev/null +++ b/app/display/gimpcanvaspassepartout.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaspassepartout.h + * Copyright (C) 2010 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_PASSE_PARTOUT_H__ +#define __GIMP_CANVAS_PASSE_PARTOUT_H__ + + +#include "gimpcanvasrectangle.h" + + +#define GIMP_TYPE_CANVAS_PASSE_PARTOUT (gimp_canvas_passe_partout_get_type ()) +#define GIMP_CANVAS_PASSE_PARTOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PASSE_PARTOUT, GimpCanvasPassePartout)) +#define GIMP_CANVAS_PASSE_PARTOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PASSE_PARTOUT, GimpCanvasPassePartoutClass)) +#define GIMP_IS_CANVAS_PASSE_PARTOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PASSE_PARTOUT)) +#define GIMP_IS_CANVAS_PASSE_PARTOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PASSE_PARTOUT)) +#define GIMP_CANVAS_PASSE_PARTOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PASSE_PARTOUT, GimpCanvasPassePartoutClass)) + + +typedef struct _GimpCanvasPassePartout GimpCanvasPassePartout; +typedef struct _GimpCanvasPassePartoutClass GimpCanvasPassePartoutClass; + +struct _GimpCanvasPassePartout +{ + GimpCanvasRectangle parent_instance; +}; + +struct _GimpCanvasPassePartoutClass +{ + GimpCanvasRectangleClass parent_class; +}; + + +GType gimp_canvas_passe_partout_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_passe_partout_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height); + + +#endif /* __GIMP_CANVAS_PASSE_PARTOUT_H__ */ diff --git a/app/display/gimpcanvaspath.c b/app/display/gimpcanvaspath.c new file mode 100644 index 0000000..71a001f --- /dev/null +++ b/app/display/gimpcanvaspath.c @@ -0,0 +1,352 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaspath.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpbezierdesc.h" +#include "core/gimpparamspecs.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvaspath.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_PATH, + PROP_X, + PROP_Y, + PROP_FILLED, + PROP_PATH_STYLE +}; + + +typedef struct _GimpCanvasPathPrivate GimpCanvasPathPrivate; + +struct _GimpCanvasPathPrivate +{ + cairo_path_t *path; + gdouble x; + gdouble y; + gboolean filled; + GimpPathStyle path_style; +}; + +#define GET_PRIVATE(path) \ + ((GimpCanvasPathPrivate *) gimp_canvas_path_get_instance_private ((GimpCanvasPath *) (path))) + +/* local function prototypes */ + +static void gimp_canvas_path_finalize (GObject *object); +static void gimp_canvas_path_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_path_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_path_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_path_get_extents (GimpCanvasItem *item); +static void gimp_canvas_path_stroke (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPath, gimp_canvas_path, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_path_parent_class + + +static void +gimp_canvas_path_class_init (GimpCanvasPathClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->finalize = gimp_canvas_path_finalize; + object_class->set_property = gimp_canvas_path_set_property; + object_class->get_property = gimp_canvas_path_get_property; + + item_class->draw = gimp_canvas_path_draw; + item_class->get_extents = gimp_canvas_path_get_extents; + item_class->stroke = gimp_canvas_path_stroke; + + g_object_class_install_property (object_class, PROP_PATH, + g_param_spec_boxed ("path", NULL, NULL, + GIMP_TYPE_BEZIER_DESC, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_FILLED, + g_param_spec_boolean ("filled", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + + g_object_class_install_property (object_class, PROP_PATH_STYLE, + g_param_spec_enum ("path-style", NULL, NULL, + GIMP_TYPE_PATH_STYLE, + GIMP_PATH_STYLE_DEFAULT, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_path_init (GimpCanvasPath *path) +{ +} + +static void +gimp_canvas_path_finalize (GObject *object) +{ + GimpCanvasPathPrivate *private = GET_PRIVATE (object); + + if (private->path) + { + gimp_bezier_desc_free (private->path); + private->path = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_path_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPathPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_PATH: + if (private->path) + gimp_bezier_desc_free (private->path); + private->path = g_value_dup_boxed (value); + break; + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + case PROP_FILLED: + private->filled = g_value_get_boolean (value); + break; + case PROP_PATH_STYLE: + private->path_style = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_path_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPathPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_PATH: + g_value_set_boxed (value, private->path); + break; + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + case PROP_FILLED: + g_value_set_boolean (value, private->filled); + break; + case PROP_PATH_STYLE: + g_value_set_enum (value, private->path_style); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_path_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasPathPrivate *private = GET_PRIVATE (item); + + if (private->path) + { + cairo_save (cr); + gimp_canvas_item_transform (item, cr); + cairo_translate (cr, private->x, private->y); + + cairo_append_path (cr, private->path); + cairo_restore (cr); + + if (private->filled) + _gimp_canvas_item_fill (item, cr); + else + _gimp_canvas_item_stroke (item, cr); + } +} + +static cairo_region_t * +gimp_canvas_path_get_extents (GimpCanvasItem *item) +{ + GimpCanvasPathPrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + + if (private->path && gtk_widget_get_realized (canvas)) + { + cairo_t *cr; + cairo_rectangle_int_t rectangle; + gdouble x1, y1, x2, y2; + + cr = gdk_cairo_create (gtk_widget_get_window (canvas)); + + cairo_save (cr); + gimp_canvas_item_transform (item, cr); + cairo_translate (cr, private->x, private->y); + + cairo_append_path (cr, private->path); + cairo_restore (cr); + + cairo_path_extents (cr, &x1, &y1, &x2, &y2); + + cairo_destroy (cr); + + if (private->filled) + { + rectangle.x = floor (x1 - 1.0); + rectangle.y = floor (y1 - 1.0); + rectangle.width = ceil (x2 + 1.0) - rectangle.x; + rectangle.height = ceil (y2 + 1.0) - rectangle.y; + } + else + { + rectangle.x = floor (x1 - 1.5); + rectangle.y = floor (y1 - 1.5); + rectangle.width = ceil (x2 + 1.5) - rectangle.x; + rectangle.height = ceil (y2 + 1.5) - rectangle.y; + } + + return cairo_region_create_rectangle (&rectangle); + } + + return NULL; +} + +static void +gimp_canvas_path_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasPathPrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + gboolean active; + + switch (private->path_style) + { + case GIMP_PATH_STYLE_VECTORS: + active = gimp_canvas_item_get_highlight (item); + + gimp_canvas_set_vectors_bg_style (canvas, cr, active); + cairo_stroke_preserve (cr); + + gimp_canvas_set_vectors_fg_style (canvas, cr, active); + cairo_stroke (cr); + break; + + case GIMP_PATH_STYLE_OUTLINE: + gimp_canvas_set_outline_bg_style (canvas, cr); + cairo_stroke_preserve (cr); + + gimp_canvas_set_outline_fg_style (canvas, cr); + cairo_stroke (cr); + break; + + case GIMP_PATH_STYLE_DEFAULT: + GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr); + break; + } +} + +GimpCanvasItem * +gimp_canvas_path_new (GimpDisplayShell *shell, + const GimpBezierDesc *bezier, + gdouble x, + gdouble y, + gboolean filled, + GimpPathStyle style) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_PATH, + "shell", shell, + "path", bezier, + "x", x, + "y", y, + "filled", filled, + "path-style", style, + NULL); +} + +void +gimp_canvas_path_set (GimpCanvasItem *path, + const GimpBezierDesc *bezier) +{ + g_return_if_fail (GIMP_IS_CANVAS_PATH (path)); + + gimp_canvas_item_begin_change (path); + + g_object_set (path, + "path", bezier, + NULL); + + gimp_canvas_item_end_change (path); +} diff --git a/app/display/gimpcanvaspath.h b/app/display/gimpcanvaspath.h new file mode 100644 index 0000000..03fbb01 --- /dev/null +++ b/app/display/gimpcanvaspath.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995 + * Spencer Kimball and Peter Mattis + * + * gimpcanvaspolygon.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_PATH_H__ +#define __GIMP_CANVAS_PATH_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_PATH (gimp_canvas_path_get_type ()) +#define GIMP_CANVAS_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PATH, GimpCanvasPath)) +#define GIMP_CANVAS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PATH, GimpCanvasPathClass)) +#define GIMP_IS_CANVAS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PATH)) +#define GIMP_IS_CANVAS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PATH)) +#define GIMP_CANVAS_PATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PATH, GimpCanvasPathClass)) + + +typedef struct _GimpCanvasPath GimpCanvasPath; +typedef struct _GimpCanvasPathClass GimpCanvasPathClass; + +struct _GimpCanvasPath +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasPathClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_path_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_path_new (GimpDisplayShell *shell, + const GimpBezierDesc *bezier, + gdouble x, + gdouble y, + gboolean filled, + GimpPathStyle style); + +void gimp_canvas_path_set (GimpCanvasItem *path, + const GimpBezierDesc *bezier); + + +#endif /* __GIMP_CANVAS_PATH_H__ */ diff --git a/app/display/gimpcanvaspen.c b/app/display/gimpcanvaspen.c new file mode 100644 index 0000000..d0d01f9 --- /dev/null +++ b/app/display/gimpcanvaspen.c @@ -0,0 +1,231 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaspen.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpparamspecs.h" + +#include "gimpcanvas-style.h" +#include "gimpcanvaspen.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_COLOR, + PROP_WIDTH +}; + + +typedef struct _GimpCanvasPenPrivate GimpCanvasPenPrivate; + +struct _GimpCanvasPenPrivate +{ + GimpRGB color; + gint width; +}; + +#define GET_PRIVATE(pen) \ + ((GimpCanvasPenPrivate *) gimp_canvas_pen_get_instance_private ((GimpCanvasPen *) (pen))) + + +/* local function prototypes */ + +static void gimp_canvas_pen_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_pen_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static cairo_region_t * gimp_canvas_pen_get_extents (GimpCanvasItem *item); +static void gimp_canvas_pen_stroke (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPen, gimp_canvas_pen, + GIMP_TYPE_CANVAS_POLYGON) + +#define parent_class gimp_canvas_pen_parent_class + + +static void +gimp_canvas_pen_class_init (GimpCanvasPenClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_pen_set_property; + object_class->get_property = gimp_canvas_pen_get_property; + + item_class->get_extents = gimp_canvas_pen_get_extents; + item_class->stroke = gimp_canvas_pen_stroke; + + g_object_class_install_property (object_class, PROP_COLOR, + gimp_param_spec_rgb ("color", NULL, NULL, + FALSE, NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_int ("width", NULL, NULL, + 1, G_MAXINT, 1, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_pen_init (GimpCanvasPen *pen) +{ +} + +static void +gimp_canvas_pen_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPenPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_COLOR: + gimp_value_get_rgb (value, &private->color); + break; + case PROP_WIDTH: + private->width = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_pen_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPenPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_COLOR: + gimp_value_set_rgb (value, &private->color); + break; + case PROP_WIDTH: + g_value_set_int (value, private->width); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static cairo_region_t * +gimp_canvas_pen_get_extents (GimpCanvasItem *item) +{ + GimpCanvasPenPrivate *private = GET_PRIVATE (item); + cairo_region_t *region; + + region = GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item); + + if (region) + { + cairo_rectangle_int_t rectangle; + + cairo_region_get_extents (region, &rectangle); + + rectangle.x -= ceil (private->width / 2.0); + rectangle.y -= ceil (private->width / 2.0); + rectangle.width += private->width + 1; + rectangle.height += private->width + 1; + + cairo_region_union_rectangle (region, &rectangle); + } + + return region; +} + +static void +gimp_canvas_pen_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasPenPrivate *private = GET_PRIVATE (item); + + gimp_canvas_set_pen_style (gimp_canvas_item_get_canvas (item), cr, + &private->color, private->width); + cairo_stroke (cr); +} + +GimpCanvasItem * +gimp_canvas_pen_new (GimpDisplayShell *shell, + const GimpVector2 *points, + gint n_points, + GimpContext *context, + GimpActiveColor color, + gint width) +{ + GimpCanvasItem *item; + GimpArray *array; + GimpRGB rgb; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (points != NULL && n_points > 1, NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + array = gimp_array_new ((const guint8 *) points, + n_points * sizeof (GimpVector2), TRUE); + + switch (color) + { + case GIMP_ACTIVE_COLOR_FOREGROUND: + gimp_context_get_foreground (context, &rgb); + break; + + case GIMP_ACTIVE_COLOR_BACKGROUND: + gimp_context_get_background (context, &rgb); + break; + } + + item = g_object_new (GIMP_TYPE_CANVAS_PEN, + "shell", shell, + "points", array, + "color", &rgb, + "width", width, + NULL); + + gimp_array_free (array); + + return item; +} diff --git a/app/display/gimpcanvaspen.h b/app/display/gimpcanvaspen.h new file mode 100644 index 0000000..3380790 --- /dev/null +++ b/app/display/gimpcanvaspen.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995 + * Spencer Kimball and Peter Mattis + * + * gimpcanvaspen.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_PEN_H__ +#define __GIMP_CANVAS_PEN_H__ + + +#include "gimpcanvaspolygon.h" + + +#define GIMP_TYPE_CANVAS_PEN (gimp_canvas_pen_get_type ()) +#define GIMP_CANVAS_PEN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PEN, GimpCanvasPen)) +#define GIMP_CANVAS_PEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PEN, GimpCanvasPenClass)) +#define GIMP_IS_CANVAS_PEN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PEN)) +#define GIMP_IS_CANVAS_PEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PEN)) +#define GIMP_CANVAS_PEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PEN, GimpCanvasPenClass)) + + +typedef struct _GimpCanvasPen GimpCanvasPen; +typedef struct _GimpCanvasPenClass GimpCanvasPenClass; + +struct _GimpCanvasPen +{ + GimpCanvasPolygon parent_instance; +}; + +struct _GimpCanvasPenClass +{ + GimpCanvasPolygonClass parent_class; +}; + + +GType gimp_canvas_pen_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_pen_new (GimpDisplayShell *shell, + const GimpVector2 *points, + gint n_points, + GimpContext *context, + GimpActiveColor color, + gint width); + + +#endif /* __GIMP_CANVAS_PEN_H__ */ diff --git a/app/display/gimpcanvaspolygon.c b/app/display/gimpcanvaspolygon.c new file mode 100644 index 0000000..9c670e7 --- /dev/null +++ b/app/display/gimpcanvaspolygon.c @@ -0,0 +1,505 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvaspolygon.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimpparamspecs.h" + +#include "gimpcanvaspolygon.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_POINTS, + PROP_TRANSFORM, + PROP_FILLED +}; + + +typedef struct _GimpCanvasPolygonPrivate GimpCanvasPolygonPrivate; + +struct _GimpCanvasPolygonPrivate +{ + GimpVector2 *points; + gint n_points; + GimpMatrix3 *transform; + gboolean filled; +}; + +#define GET_PRIVATE(polygon) \ + ((GimpCanvasPolygonPrivate *) gimp_canvas_polygon_get_instance_private ((GimpCanvasPolygon *) (polygon))) + + +/* local function prototypes */ + +static void gimp_canvas_polygon_finalize (GObject *object); +static void gimp_canvas_polygon_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_polygon_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_polygon_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_polygon_get_extents (GimpCanvasItem *item); +static gboolean gimp_canvas_polygon_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasPolygon, gimp_canvas_polygon, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_polygon_parent_class + + +static void +gimp_canvas_polygon_class_init (GimpCanvasPolygonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->finalize = gimp_canvas_polygon_finalize; + object_class->set_property = gimp_canvas_polygon_set_property; + object_class->get_property = gimp_canvas_polygon_get_property; + + item_class->draw = gimp_canvas_polygon_draw; + item_class->get_extents = gimp_canvas_polygon_get_extents; + item_class->hit = gimp_canvas_polygon_hit; + + g_object_class_install_property (object_class, PROP_POINTS, + gimp_param_spec_array ("points", NULL, NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_TRANSFORM, + g_param_spec_pointer ("transform", NULL, NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_FILLED, + g_param_spec_boolean ("filled", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_polygon_init (GimpCanvasPolygon *polygon) +{ +} + +static void +gimp_canvas_polygon_finalize (GObject *object) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->points, g_free); + private->n_points = 0; + + g_clear_pointer (&private->transform, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_polygon_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_POINTS: + { + GimpArray *array = g_value_get_boxed (value); + + g_clear_pointer (&private->points, g_free); + private->n_points = 0; + + if (array) + { + private->points = g_memdup (array->data, array->length); + private->n_points = array->length / sizeof (GimpVector2); + } + } + break; + + case PROP_TRANSFORM: + { + GimpMatrix3 *transform = g_value_get_pointer (value); + if (private->transform) + g_free (private->transform); + if (transform) + private->transform = g_memdup (transform, sizeof (GimpMatrix3)); + else + private->transform = NULL; + } + break; + + case PROP_FILLED: + private->filled = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_polygon_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_POINTS: + if (private->points) + { + GimpArray *array; + + array = gimp_array_new ((const guint8 *) private->points, + private->n_points * sizeof (GimpVector2), + FALSE); + g_value_take_boxed (value, array); + } + else + { + g_value_set_boxed (value, NULL); + } + break; + + case PROP_TRANSFORM: + g_value_set_pointer (value, private->transform); + break; + + case PROP_FILLED: + g_value_set_boolean (value, private->filled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_polygon_transform (GimpCanvasItem *item, + GimpVector2 *points, + gint *n_points) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (item); + gint i; + + if (private->transform) + { + gimp_transform_polygon (private->transform, + private->points, private->n_points, FALSE, + points, n_points); + + for (i = 0; i < *n_points; i++) + { + gimp_canvas_item_transform_xy_f (item, + points[i].x, + points[i].y, + &points[i].x, + &points[i].y); + + points[i].x = floor (points[i].x) + 0.5; + points[i].y = floor (points[i].y) + 0.5; + } + } + else + { + for (i = 0; i < private->n_points; i++) + { + gimp_canvas_item_transform_xy_f (item, + private->points[i].x, + private->points[i].y, + &points[i].x, + &points[i].y); + + points[i].x = floor (points[i].x) + 0.5; + points[i].y = floor (points[i].y) + 0.5; + } + + *n_points = private->n_points; + } +} + +static void +gimp_canvas_polygon_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (item); + GimpVector2 *points; + gint n_points; + gint i; + + if (! private->points) + return; + + n_points = private->n_points; + + if (private->transform) + n_points = 3 * n_points / 2; + + points = g_new0 (GimpVector2, n_points); + + gimp_canvas_polygon_transform (item, points, &n_points); + + if (n_points < 2) + { + g_free (points); + + return; + } + + cairo_move_to (cr, points[0].x, points[0].y); + + for (i = 1; i < n_points; i++) + { + cairo_line_to (cr, points[i].x, points[i].y); + } + + if (private->filled) + _gimp_canvas_item_fill (item, cr); + else + _gimp_canvas_item_stroke (item, cr); + + g_free (points); +} + +static cairo_region_t * +gimp_canvas_polygon_get_extents (GimpCanvasItem *item) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (item); + cairo_rectangle_int_t rectangle; + GimpVector2 *points; + gint n_points; + gint x1, y1, x2, y2; + gint i; + + if (! private->points) + return NULL; + + n_points = private->n_points; + + if (private->transform) + n_points = 3 * n_points / 2; + + points = g_new0 (GimpVector2, n_points); + + gimp_canvas_polygon_transform (item, points, &n_points); + + if (n_points < 2) + { + g_free (points); + + return NULL; + } + + x1 = floor (points[0].x - 1.5); + y1 = floor (points[0].y - 1.5); + x2 = x1 + 3; + y2 = y1 + 3; + + for (i = 1; i < n_points; i++) + { + gint x3 = floor (points[i].x - 1.5); + gint y3 = floor (points[i].y - 1.5); + gint x4 = x3 + 3; + gint y4 = y3 + 3; + + x1 = MIN (x1, x3); + y1 = MIN (y1, y3); + x2 = MAX (x2, x4); + y2 = MAX (y2, y4); + } + + g_free (points); + + rectangle.x = x1; + rectangle.y = y1; + rectangle.width = x2 - x1; + rectangle.height = y2 - y1; + + return cairo_region_create_rectangle (&rectangle); +} + +static gboolean +gimp_canvas_polygon_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + GimpCanvasPolygonPrivate *private = GET_PRIVATE (item); + GimpVector2 *points; + gint n_points; + gdouble tx, ty; + cairo_surface_t *surface; + cairo_t *cr; + gboolean hit; + gint i; + + if (! private->points) + return FALSE; + + gimp_canvas_item_transform_xy_f (item, x, y, &tx, &ty); + + n_points = private->n_points; + + if (private->transform) + n_points = 3 * n_points / 2; + + points = g_new0 (GimpVector2, n_points); + + gimp_canvas_polygon_transform (item, points, &n_points); + + if (n_points < 2) + { + g_free (points); + + return FALSE; + } + + surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 1, 1); + cr = cairo_create (surface); + cairo_surface_destroy (surface); + + cairo_move_to (cr, points[0].x, points[0].y); + + for (i = 1; i < private->n_points; i++) + { + cairo_line_to (cr, points[i].x, points[i].y); + } + + g_free (points); + + hit = cairo_in_fill (cr, tx, ty); + + cairo_destroy (cr); + + return hit; +} + +GimpCanvasItem * +gimp_canvas_polygon_new (GimpDisplayShell *shell, + const GimpVector2 *points, + gint n_points, + GimpMatrix3 *transform, + gboolean filled) +{ + GimpCanvasItem *item; + GimpArray *array; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (points == NULL || n_points > 0, NULL); + + array = gimp_array_new ((const guint8 *) points, + n_points * sizeof (GimpVector2), TRUE); + + item = g_object_new (GIMP_TYPE_CANVAS_POLYGON, + "shell", shell, + "transform", transform, + "filled", filled, + "points", array, + NULL); + + gimp_array_free (array); + + return item; +} + +GimpCanvasItem * +gimp_canvas_polygon_new_from_coords (GimpDisplayShell *shell, + const GimpCoords *coords, + gint n_coords, + GimpMatrix3 *transform, + gboolean filled) +{ + GimpCanvasItem *item; + GimpVector2 *points; + GimpArray *array; + gint i; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (coords == NULL || n_coords > 0, NULL); + + points = g_new (GimpVector2, n_coords); + + for (i = 0; i < n_coords; i++) + { + points[i].x = coords[i].x; + points[i].y = coords[i].y; + } + + array = gimp_array_new ((const guint8 *) points, + n_coords * sizeof (GimpVector2), TRUE); + + item = g_object_new (GIMP_TYPE_CANVAS_POLYGON, + "shell", shell, + "transform", transform, + "filled", filled, + "points", array, + NULL); + + gimp_array_free (array); + g_free (points); + + return item; +} + +void +gimp_canvas_polygon_set_points (GimpCanvasItem *polygon, + const GimpVector2 *points, + gint n_points) +{ + GimpArray *array; + + g_return_if_fail (GIMP_IS_CANVAS_POLYGON (polygon)); + g_return_if_fail (points == NULL || n_points > 0); + + array = gimp_array_new ((const guint8 *) points, + n_points * sizeof (GimpVector2), TRUE); + + gimp_canvas_item_begin_change (polygon); + g_object_set (polygon, + "points", array, + NULL); + gimp_canvas_item_end_change (polygon); + + gimp_array_free (array); +} diff --git a/app/display/gimpcanvaspolygon.h b/app/display/gimpcanvaspolygon.h new file mode 100644 index 0000000..f68e36a --- /dev/null +++ b/app/display/gimpcanvaspolygon.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995 + * Spencer Kimball and Peter Mattis + * + * gimpcanvaspolygon.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_POLYGON_H__ +#define __GIMP_CANVAS_POLYGON_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_POLYGON (gimp_canvas_polygon_get_type ()) +#define GIMP_CANVAS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_POLYGON, GimpCanvasPolygon)) +#define GIMP_CANVAS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_POLYGON, GimpCanvasPolygonClass)) +#define GIMP_IS_CANVAS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_POLYGON)) +#define GIMP_IS_CANVAS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_POLYGON)) +#define GIMP_CANVAS_POLYGON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_POLYGON, GimpCanvasPolygonClass)) + + +typedef struct _GimpCanvasPolygon GimpCanvasPolygon; +typedef struct _GimpCanvasPolygonClass GimpCanvasPolygonClass; + +struct _GimpCanvasPolygon +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasPolygonClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_polygon_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_polygon_new (GimpDisplayShell *shell, + const GimpVector2 *points, + gint n_points, + GimpMatrix3 *transform, + gboolean filled); +GimpCanvasItem * gimp_canvas_polygon_new_from_coords (GimpDisplayShell *shell, + const GimpCoords *coords, + gint n_coords, + GimpMatrix3 *transform, + gboolean filled); + +void gimp_canvas_polygon_set_points (GimpCanvasItem *polygon, + const GimpVector2 *points, + gint n_points); + + +#endif /* __GIMP_CANVAS_POLYGON_H__ */ diff --git a/app/display/gimpcanvasprogress.c b/app/display/gimpcanvasprogress.c new file mode 100644 index 0000000..4def1ac --- /dev/null +++ b/app/display/gimpcanvasprogress.c @@ -0,0 +1,459 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasprogress.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpprogress.h" + +#include "gimpcanvas.h" +#include "gimpcanvas-style.h" +#include "gimpcanvasitem-utils.h" +#include "gimpcanvasprogress.h" +#include "gimpdisplayshell.h" + + +#define BORDER 5 +#define RADIUS 20 + +#define MIN_UPDATE_INTERVAL 50000 /* microseconds */ + + +enum +{ + PROP_0, + PROP_ANCHOR, + PROP_X, + PROP_Y +}; + + +typedef struct _GimpCanvasProgressPrivate GimpCanvasProgressPrivate; + +struct _GimpCanvasProgressPrivate +{ + GimpHandleAnchor anchor; + gdouble x; + gdouble y; + + gchar *text; + gdouble value; + + guint64 last_update_time; +}; + +#define GET_PRIVATE(progress) \ + ((GimpCanvasProgressPrivate *) gimp_canvas_progress_get_instance_private ((GimpCanvasProgress *) (progress))) + + +/* local function prototypes */ + +static void gimp_canvas_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_canvas_progress_finalize (GObject *object); +static void gimp_canvas_progress_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_progress_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_progress_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_progress_get_extents (GimpCanvasItem *item); +static gboolean gimp_canvas_progress_hit (GimpCanvasItem *item, + gdouble x, + gdouble y); + +static GimpProgress * gimp_canvas_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_canvas_progress_end (GimpProgress *progress); +static gboolean gimp_canvas_progress_is_active (GimpProgress *progress); +static void gimp_canvas_progress_set_text (GimpProgress *progress, + const gchar *message); +static void gimp_canvas_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_canvas_progress_get_value (GimpProgress *progress); +static void gimp_canvas_progress_pulse (GimpProgress *progress); +static gboolean gimp_canvas_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message); + + +G_DEFINE_TYPE_WITH_CODE (GimpCanvasProgress, gimp_canvas_progress, + GIMP_TYPE_CANVAS_ITEM, + G_ADD_PRIVATE (GimpCanvasProgress) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_canvas_progress_iface_init)) + +#define parent_class gimp_canvas_progress_parent_class + + +static void +gimp_canvas_progress_class_init (GimpCanvasProgressClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->finalize = gimp_canvas_progress_finalize; + object_class->set_property = gimp_canvas_progress_set_property; + object_class->get_property = gimp_canvas_progress_get_property; + + item_class->draw = gimp_canvas_progress_draw; + item_class->get_extents = gimp_canvas_progress_get_extents; + item_class->hit = gimp_canvas_progress_hit; + + g_object_class_install_property (object_class, PROP_ANCHOR, + g_param_spec_enum ("anchor", NULL, NULL, + GIMP_TYPE_HANDLE_ANCHOR, + GIMP_HANDLE_ANCHOR_CENTER, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_canvas_progress_start; + iface->end = gimp_canvas_progress_end; + iface->is_active = gimp_canvas_progress_is_active; + iface->set_text = gimp_canvas_progress_set_text; + iface->set_value = gimp_canvas_progress_set_value; + iface->get_value = gimp_canvas_progress_get_value; + iface->pulse = gimp_canvas_progress_pulse; + iface->message = gimp_canvas_progress_message; +} + +static void +gimp_canvas_progress_init (GimpCanvasProgress *progress) +{ +} + +static void +gimp_canvas_progress_finalize (GObject *object) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->text, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_progress_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ANCHOR: + private->anchor = g_value_get_enum (value); + break; + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_progress_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ANCHOR: + g_value_set_enum (value, private->anchor); + break; + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static PangoLayout * +gimp_canvas_progress_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y, + gint *width, + gint *height) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + PangoLayout *layout; + + layout = gimp_canvas_get_layout (GIMP_CANVAS (canvas), "%s", + private->text); + + pango_layout_get_pixel_size (layout, width, height); + + *width = MAX (*width, 2 * RADIUS); + + *width += 2 * BORDER; + *height += 3 * BORDER + 2 * RADIUS; + + gimp_canvas_item_transform_xy_f (item, + private->x, private->y, + x, y); + + gimp_canvas_item_shift_to_north_west (private->anchor, + *x, *y, + *width, + *height, + x, y); + + *x = floor (*x); + *y = floor (*y); + + return layout; +} + +static void +gimp_canvas_progress_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + gdouble x, y; + gint width, height; + + gimp_canvas_progress_transform (item, &x, &y, &width, &height); + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + width, y); + cairo_line_to (cr, x + width, y + height - BORDER - 2 * RADIUS); + cairo_line_to (cr, x + 2 * BORDER + 2 * RADIUS, y + height - BORDER - 2 * RADIUS); + cairo_arc (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS, + BORDER + RADIUS, 0, G_PI); + cairo_close_path (cr); + + _gimp_canvas_item_fill (item, cr); + + cairo_move_to (cr, x + BORDER, y + BORDER); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0); + pango_cairo_show_layout (cr, + gimp_canvas_get_layout (GIMP_CANVAS (canvas), + "%s", private->text)); + + gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr); + cairo_arc (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS, + RADIUS, - G_PI / 2.0, 2 * G_PI - G_PI / 2.0); + cairo_fill (cr); + + cairo_set_source_rgba (cr, 0.0, 1.0, 0.0, 1.0); + cairo_move_to (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS); + cairo_arc (cr, x + BORDER + RADIUS, y + height - BORDER - RADIUS, + RADIUS, - G_PI / 2.0, 2 * G_PI * private->value - G_PI / 2.0); + cairo_fill (cr); +} + +static cairo_region_t * +gimp_canvas_progress_get_extents (GimpCanvasItem *item) +{ + cairo_rectangle_int_t rectangle; + gdouble x, y; + gint width, height; + + gimp_canvas_progress_transform (item, &x, &y, &width, &height); + + /* add 1px on each side because fill()'s default impl does the same */ + rectangle.x = (gint) x - 1; + rectangle.y = (gint) y - 1; + rectangle.width = width + 2; + rectangle.height = height + 2; + + return cairo_region_create_rectangle (&rectangle); +} + +static gboolean +gimp_canvas_progress_hit (GimpCanvasItem *item, + gdouble x, + gdouble y) +{ + gdouble px, py; + gint pwidth, pheight; + gdouble tx, ty; + + gimp_canvas_progress_transform (item, &px, &py, &pwidth, &pheight); + gimp_canvas_item_transform_xy_f (item, x, y, &tx, &ty); + + pheight -= BORDER + 2 * RADIUS; + + return (tx >= px && tx < (px + pwidth) && + ty >= py && ty < (py + pheight)); +} + +static GimpProgress * +gimp_canvas_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (progress); + + gimp_canvas_progress_set_text (progress, message); + + private->last_update_time = g_get_monotonic_time (); + + return progress; +} + +static void +gimp_canvas_progress_end (GimpProgress *progress) +{ +} + +static gboolean +gimp_canvas_progress_is_active (GimpProgress *progress) +{ + return TRUE; +} + +static void +gimp_canvas_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (progress); + cairo_region_t *old_region; + cairo_region_t *new_region; + + old_region = gimp_canvas_item_get_extents (GIMP_CANVAS_ITEM (progress)); + + if (private->text) + g_free (private->text); + + private->text = g_strdup (message); + + new_region = gimp_canvas_item_get_extents (GIMP_CANVAS_ITEM (progress)); + + cairo_region_union (new_region, old_region); + cairo_region_destroy (old_region); + + _gimp_canvas_item_update (GIMP_CANVAS_ITEM (progress), new_region); + + cairo_region_destroy (new_region); +} + +static void +gimp_canvas_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (progress); + + if (percentage != private->value) + { + guint64 time = g_get_monotonic_time (); + + private->value = percentage; + + if (time - private->last_update_time >= MIN_UPDATE_INTERVAL) + { + cairo_region_t *region; + + private->last_update_time = time; + + region = gimp_canvas_item_get_extents (GIMP_CANVAS_ITEM (progress)); + + _gimp_canvas_item_update (GIMP_CANVAS_ITEM (progress), region); + + cairo_region_destroy (region); + } + } +} + +static gdouble +gimp_canvas_progress_get_value (GimpProgress *progress) +{ + GimpCanvasProgressPrivate *private = GET_PRIVATE (progress); + + return private->value; +} + +static void +gimp_canvas_progress_pulse (GimpProgress *progress) +{ +} + +static gboolean +gimp_canvas_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) +{ + return FALSE; +} + +GimpCanvasItem * +gimp_canvas_progress_new (GimpDisplayShell *shell, + GimpHandleAnchor anchor, + gdouble x, + gdouble y) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_PROGRESS, + "shell", shell, + "anchor", anchor, + "x", x, + "y", y, + NULL); +} diff --git a/app/display/gimpcanvasprogress.h b/app/display/gimpcanvasprogress.h new file mode 100644 index 0000000..1f6a9c7 --- /dev/null +++ b/app/display/gimpcanvasprogress.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasprogress.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_PROGRESS_H__ +#define __GIMP_CANVAS_PROGRESS_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_PROGRESS (gimp_canvas_progress_get_type ()) +#define GIMP_CANVAS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PROGRESS, GimpCanvasProgress)) +#define GIMP_CANVAS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PROGRESS, GimpCanvasProgressClass)) +#define GIMP_IS_CANVAS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PROGRESS)) +#define GIMP_IS_CANVAS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PROGRESS)) +#define GIMP_CANVAS_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PROGRESS, GimpCanvasProgressClass)) + + +typedef struct _GimpCanvasProgress GimpCanvasProgress; +typedef struct _GimpCanvasProgressClass GimpCanvasProgressClass; + +struct _GimpCanvasProgress +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasProgressClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_progress_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_progress_new (GimpDisplayShell *shell, + GimpHandleAnchor anchor, + gdouble x, + gdouble y); + + +#endif /* __GIMP_CANVAS_PROGRESS_H__ */ diff --git a/app/display/gimpcanvasproxygroup.c b/app/display/gimpcanvasproxygroup.c new file mode 100644 index 0000000..a979f24 --- /dev/null +++ b/app/display/gimpcanvasproxygroup.c @@ -0,0 +1,197 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasproxygroup.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvas.h" +#include "gimpcanvasproxygroup.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0 +}; + + +typedef struct _GimpCanvasProxyGroupPrivate GimpCanvasProxyGroupPrivate; + +struct _GimpCanvasProxyGroupPrivate +{ + GHashTable *proxy_hash; +}; + +#define GET_PRIVATE(proxy_group) \ + ((GimpCanvasProxyGroupPrivate *) gimp_canvas_proxy_group_get_instance_private ((GimpCanvasProxyGroup *) (proxy_group))) + + +/* local function prototypes */ + +static void gimp_canvas_proxy_group_finalize (GObject *object); +static void gimp_canvas_proxy_group_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_proxy_group_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasProxyGroup, gimp_canvas_proxy_group, + GIMP_TYPE_CANVAS_GROUP) + +#define parent_class gimp_canvas_proxy_group_parent_class + + +static void +gimp_canvas_proxy_group_class_init (GimpCanvasProxyGroupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_canvas_proxy_group_finalize; + object_class->set_property = gimp_canvas_proxy_group_set_property; + object_class->get_property = gimp_canvas_proxy_group_get_property; +} + +static void +gimp_canvas_proxy_group_init (GimpCanvasProxyGroup *proxy_group) +{ + GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (proxy_group); + + private->proxy_hash = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +gimp_canvas_proxy_group_finalize (GObject *object) +{ + GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->proxy_hash, g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_canvas_proxy_group_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + /* GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (object); */ + + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_proxy_group_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + /* GimpCanvasProxyGroupPrivate *private = GET_PRIVATE (object); */ + + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GimpCanvasItem * +gimp_canvas_proxy_group_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_PROXY_GROUP, + "shell", shell, + NULL); +} + +void +gimp_canvas_proxy_group_add_item (GimpCanvasProxyGroup *group, + gpointer object, + GimpCanvasItem *proxy_item) +{ + GimpCanvasProxyGroupPrivate *private; + + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + g_return_if_fail (object != NULL); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (proxy_item)); + g_return_if_fail (GIMP_CANVAS_ITEM (group) != proxy_item); + + private = GET_PRIVATE (group); + + g_return_if_fail (g_hash_table_lookup (private->proxy_hash, object) == + NULL); + + g_hash_table_insert (private->proxy_hash, object, proxy_item); + + gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (group), proxy_item); +} + +void +gimp_canvas_proxy_group_remove_item (GimpCanvasProxyGroup *group, + gpointer object) +{ + GimpCanvasProxyGroupPrivate *private; + GimpCanvasItem *proxy_item; + + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + g_return_if_fail (object != NULL); + + private = GET_PRIVATE (group); + + proxy_item = g_hash_table_lookup (private->proxy_hash, object); + + g_return_if_fail (proxy_item != NULL); + + g_hash_table_remove (private->proxy_hash, object); + + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (group), proxy_item); +} + +GimpCanvasItem * +gimp_canvas_proxy_group_get_item (GimpCanvasProxyGroup *group, + gpointer object) +{ + GimpCanvasProxyGroupPrivate *private; + + g_return_val_if_fail (GIMP_IS_CANVAS_GROUP (group), NULL); + g_return_val_if_fail (object != NULL, NULL); + + private = GET_PRIVATE (group); + + return g_hash_table_lookup (private->proxy_hash, object); +} diff --git a/app/display/gimpcanvasproxygroup.h b/app/display/gimpcanvasproxygroup.h new file mode 100644 index 0000000..be25787 --- /dev/null +++ b/app/display/gimpcanvasproxygroup.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasproxygroup.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_PROXY_GROUP_H__ +#define __GIMP_CANVAS_PROXY_GROUP_H__ + + +#include "gimpcanvasgroup.h" + + +#define GIMP_TYPE_CANVAS_PROXY_GROUP (gimp_canvas_proxy_group_get_type ()) +#define GIMP_CANVAS_PROXY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_PROXY_GROUP, GimpCanvasProxyGroup)) +#define GIMP_CANVAS_PROXY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_PROXY_GROUP, GimpCanvasProxyGroupClass)) +#define GIMP_IS_CANVAS_PROXY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_PROXY_GROUP)) +#define GIMP_IS_CANVAS_PROXY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_PROXY_GROUP)) +#define GIMP_CANVAS_PROXY_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_PROXY_GROUP, GimpCanvasProxyGroupClass)) + + +typedef struct _GimpCanvasProxyGroup GimpCanvasProxyGroup; +typedef struct _GimpCanvasProxyGroupClass GimpCanvasProxyGroupClass; + +struct _GimpCanvasProxyGroup +{ + GimpCanvasGroup parent_instance; +}; + +struct _GimpCanvasProxyGroupClass +{ + GimpCanvasGroupClass parent_class; +}; + + +GType gimp_canvas_proxy_group_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_proxy_group_new (GimpDisplayShell *shell); + +void gimp_canvas_proxy_group_add_item (GimpCanvasProxyGroup *group, + gpointer object, + GimpCanvasItem *proxy_item); +void gimp_canvas_proxy_group_remove_item (GimpCanvasProxyGroup *group, + gpointer object); +GimpCanvasItem * gimp_canvas_proxy_group_get_item (GimpCanvasProxyGroup *group, + gpointer object); + + +#endif /* __GIMP_CANVAS_PROXY_GROUP_H__ */ diff --git a/app/display/gimpcanvasrectangle.c b/app/display/gimpcanvasrectangle.c new file mode 100644 index 0000000..7ef7351 --- /dev/null +++ b/app/display/gimpcanvasrectangle.c @@ -0,0 +1,360 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasrectangle.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvasrectangle.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_FILLED +}; + + +typedef struct _GimpCanvasRectanglePrivate GimpCanvasRectanglePrivate; + +struct _GimpCanvasRectanglePrivate +{ + gdouble x; + gdouble y; + gdouble width; + gdouble height; + gboolean filled; +}; + +#define GET_PRIVATE(rectangle) \ + ((GimpCanvasRectanglePrivate *) gimp_canvas_rectangle_get_instance_private ((GimpCanvasRectangle *) (rectangle))) + + +/* local function prototypes */ + +static void gimp_canvas_rectangle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_rectangle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_rectangle_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_rectangle_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasRectangle, gimp_canvas_rectangle, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_rectangle_parent_class + + +static void +gimp_canvas_rectangle_class_init (GimpCanvasRectangleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_rectangle_set_property; + object_class->get_property = gimp_canvas_rectangle_get_property; + + item_class->draw = gimp_canvas_rectangle_draw; + item_class->get_extents = gimp_canvas_rectangle_get_extents; + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_double ("width", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_double ("height", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_FILLED, + g_param_spec_boolean ("filled", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_rectangle_init (GimpCanvasRectangle *rectangle) +{ +} + +static void +gimp_canvas_rectangle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasRectanglePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + case PROP_WIDTH: + private->width = g_value_get_double (value); + break; + case PROP_HEIGHT: + private->height = g_value_get_double (value); + break; + case PROP_FILLED: + private->filled = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_rectangle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasRectanglePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + case PROP_WIDTH: + g_value_set_double (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_double (value, private->height); + break; + case PROP_FILLED: + g_value_set_boolean (value, private->filled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_rectangle_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y, + gdouble *w, + gdouble *h) +{ + GimpCanvasRectanglePrivate *private = GET_PRIVATE (item); + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_item_transform_xy_f (item, + MIN (private->x, + private->x + private->width), + MIN (private->y, + private->y + private->height), + &x1, &y1); + gimp_canvas_item_transform_xy_f (item, + MAX (private->x, + private->x + private->width), + MAX (private->y, + private->y + private->height), + &x2, &y2); + + x1 = floor (x1); + y1 = floor (y1); + x2 = ceil (x2); + y2 = ceil (y2); + + if (private->filled) + { + *x = x1; + *y = y1; + *w = x2 - x1; + *h = y2 - y1; + } + else + { + *x = x1 + 0.5; + *y = y1 + 0.5; + *w = x2 - 0.5 - *x; + *h = y2 - 0.5 - *y; + + *w = MAX (0.0, *w); + *h = MAX (0.0, *h); + } +} + +static void +gimp_canvas_rectangle_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasRectanglePrivate *private = GET_PRIVATE (item); + gdouble x, y; + gdouble w, h; + + gimp_canvas_rectangle_transform (item, &x, &y, &w, &h); + + cairo_rectangle (cr, x, y, w, h); + + if (private->filled) + _gimp_canvas_item_fill (item, cr); + else + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_rectangle_get_extents (GimpCanvasItem *item) +{ + GimpCanvasRectanglePrivate *private = GET_PRIVATE (item); + cairo_rectangle_int_t rectangle; + gdouble x, y; + gdouble w, h; + + gimp_canvas_rectangle_transform (item, &x, &y, &w, &h); + + if (private->filled) + { + rectangle.x = floor (x - 1.0); + rectangle.y = floor (y - 1.0); + rectangle.width = ceil (w + 2.0); + rectangle.height = ceil (h + 2.0); + + return cairo_region_create_rectangle (&rectangle); + } + else if (w > 64 && h > 64) + { + cairo_region_t *region; + + /* left */ + rectangle.x = floor (x - 1.5); + rectangle.y = floor (y - 1.5); + rectangle.width = 3.0; + rectangle.height = ceil (h + 3.0); + + region = cairo_region_create_rectangle (&rectangle); + + /* right */ + rectangle.x = floor (x + w - 1.5); + + cairo_region_union_rectangle (region, &rectangle); + + /* top */ + rectangle.x = floor (x - 1.5); + rectangle.y = floor (y - 1.5); + rectangle.width = ceil (w + 3.0); + rectangle.height = 3.0; + + cairo_region_union_rectangle (region, &rectangle); + + /* bottom */ + rectangle.y = floor (y + h - 1.5); + + cairo_region_union_rectangle (region, &rectangle); + + return region; + } + else + { + rectangle.x = floor (x - 1.5); + rectangle.y = floor (y - 1.5); + rectangle.width = ceil (w + 3.0); + rectangle.height = ceil (h + 3.0); + + return cairo_region_create_rectangle (&rectangle); + } +} + +GimpCanvasItem * +gimp_canvas_rectangle_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gboolean filled) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_RECTANGLE, + "shell", shell, + "x", x, + "y", y, + "width", width, + "height", height, + "filled", filled, + NULL); +} + +void +gimp_canvas_rectangle_set (GimpCanvasItem *rectangle, + gdouble x, + gdouble y, + gdouble width, + gdouble height) +{ + g_return_if_fail (GIMP_IS_CANVAS_RECTANGLE (rectangle)); + + gimp_canvas_item_begin_change (rectangle); + + g_object_set (rectangle, + "x", x, + "y", y, + "width", width, + "height", height, + NULL); + + gimp_canvas_item_end_change (rectangle); +} diff --git a/app/display/gimpcanvasrectangle.h b/app/display/gimpcanvasrectangle.h new file mode 100644 index 0000000..6a96c06 --- /dev/null +++ b/app/display/gimpcanvasrectangle.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasrectangle.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_RECTANGLE_H__ +#define __GIMP_CANVAS_RECTANGLE_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_RECTANGLE (gimp_canvas_rectangle_get_type ()) +#define GIMP_CANVAS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_RECTANGLE, GimpCanvasRectangle)) +#define GIMP_CANVAS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_RECTANGLE, GimpCanvasRectangleClass)) +#define GIMP_IS_CANVAS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_RECTANGLE)) +#define GIMP_IS_CANVAS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_RECTANGLE)) +#define GIMP_CANVAS_RECTANGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_RECTANGLE, GimpCanvasRectangleClass)) + + +typedef struct _GimpCanvasRectangle GimpCanvasRectangle; +typedef struct _GimpCanvasRectangleClass GimpCanvasRectangleClass; + +struct _GimpCanvasRectangle +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasRectangleClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_rectangle_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_rectangle_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gboolean filled); + +void gimp_canvas_rectangle_set (GimpCanvasItem *rectangle, + gdouble x, + gdouble y, + gdouble width, + gdouble height); + + +#endif /* __GIMP_CANVAS_RECTANGLE_H__ */ diff --git a/app/display/gimpcanvasrectangleguides.c b/app/display/gimpcanvasrectangleguides.c new file mode 100644 index 0000000..b7cc07d --- /dev/null +++ b/app/display/gimpcanvasrectangleguides.c @@ -0,0 +1,422 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasrectangleguides.c + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvasrectangleguides.h" +#include "gimpdisplayshell.h" + + +#define SQRT5 2.236067977 + + +enum +{ + PROP_0, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_TYPE, + PROP_N_GUIDES +}; + + +typedef struct _GimpCanvasRectangleGuidesPrivate GimpCanvasRectangleGuidesPrivate; + +struct _GimpCanvasRectangleGuidesPrivate +{ + gdouble x; + gdouble y; + gdouble width; + gdouble height; + GimpGuidesType type; + gint n_guides; +}; + +#define GET_PRIVATE(rectangle) \ + ((GimpCanvasRectangleGuidesPrivate *) gimp_canvas_rectangle_guides_get_instance_private ((GimpCanvasRectangleGuides *) (rectangle))) + + +/* local function prototypes */ + +static void gimp_canvas_rectangle_guides_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_rectangle_guides_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_rectangle_guides_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_rectangle_guides_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasRectangleGuides, + gimp_canvas_rectangle_guides, GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_rectangle_guides_parent_class + + +static void +gimp_canvas_rectangle_guides_class_init (GimpCanvasRectangleGuidesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_rectangle_guides_set_property; + object_class->get_property = gimp_canvas_rectangle_guides_get_property; + + item_class->draw = gimp_canvas_rectangle_guides_draw; + item_class->get_extents = gimp_canvas_rectangle_guides_get_extents; + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_double ("width", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_double ("height", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", NULL, NULL, + GIMP_TYPE_GUIDES_TYPE, + GIMP_GUIDES_NONE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_N_GUIDES, + g_param_spec_int ("n-guides", NULL, NULL, + 1, 128, 4, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_rectangle_guides_init (GimpCanvasRectangleGuides *rectangle) +{ +} + +static void +gimp_canvas_rectangle_guides_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + case PROP_WIDTH: + private->width = g_value_get_double (value); + break; + case PROP_HEIGHT: + private->height = g_value_get_double (value); + break; + case PROP_TYPE: + private->type = g_value_get_enum (value); + break; + case PROP_N_GUIDES: + private->n_guides = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_rectangle_guides_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + case PROP_WIDTH: + g_value_set_double (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_double (value, private->height); + break; + case PROP_TYPE: + g_value_set_enum (value, private->type); + break; + case PROP_N_GUIDES: + g_value_set_int (value, private->n_guides); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_rectangle_guides_transform (GimpCanvasItem *item, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (item); + + gimp_canvas_item_transform_xy_f (item, + MIN (private->x, + private->x + private->width), + MIN (private->y, + private->y + private->height), + x1, y1); + gimp_canvas_item_transform_xy_f (item, + MAX (private->x, + private->x + private->width), + MAX (private->y, + private->y + private->height), + x2, y2); + + *x1 = floor (*x1) + 0.5; + *y1 = floor (*y1) + 0.5; + *x2 = ceil (*x2) - 0.5; + *y2 = ceil (*y2) - 0.5; + + *x2 = MAX (*x1, *x2); + *y2 = MAX (*y1, *y2); +} + +static void +draw_hline (cairo_t *cr, + gdouble x1, + gdouble x2, + gdouble y) +{ + y = floor (y) + 0.5; + + cairo_move_to (cr, x1, y); + cairo_line_to (cr, x2, y); +} + +static void +draw_vline (cairo_t *cr, + gdouble y1, + gdouble y2, + gdouble x) +{ + x = floor (x) + 0.5; + + cairo_move_to (cr, x, y1); + cairo_line_to (cr, x, y2); +} + +static void +gimp_canvas_rectangle_guides_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (item); + gdouble x1, y1; + gdouble x2, y2; + gint i; + + gimp_canvas_rectangle_guides_transform (item, &x1, &y1, &x2, &y2); + + switch (private->type) + { + case GIMP_GUIDES_NONE: + break; + + case GIMP_GUIDES_CENTER_LINES: + draw_hline (cr, x1, x2, (y1 + y2) / 2); + draw_vline (cr, y1, y2, (x1 + x2) / 2); + break; + + case GIMP_GUIDES_THIRDS: + draw_hline (cr, x1, x2, (2 * y1 + y2) / 3); + draw_hline (cr, x1, x2, ( y1 + 2 * y2) / 3); + + draw_vline (cr, y1, y2, (2 * x1 + x2) / 3); + draw_vline (cr, y1, y2, ( x1 + 2 * x2) / 3); + break; + + case GIMP_GUIDES_FIFTHS: + for (i = 0; i < 5; i++) + { + draw_hline (cr, x1, x2, y1 + i * (y2 - y1) / 5); + draw_vline (cr, y1, y2, x1 + i * (x2 - x1) / 5); + } + break; + + case GIMP_GUIDES_GOLDEN: + draw_hline (cr, x1, x2, (2 * y1 + (1 + SQRT5) * y2) / (3 + SQRT5)); + draw_hline (cr, x1, x2, ((1 + SQRT5) * y1 + 2 * y2) / (3 + SQRT5)); + + draw_vline (cr, y1, y2, (2 * x1 + (1 + SQRT5) * x2) / (3 + SQRT5)); + draw_vline (cr, y1, y2, ((1 + SQRT5) * x1 + 2 * x2) / (3 + SQRT5)); + break; + + /* This code implements the method of diagonals discovered by + * Edwin Westhoff - see http://www.diagonalmethod.info/ + */ + case GIMP_GUIDES_DIAGONALS: + { + /* the side of the largest square that can be + * fitted in whole into the rectangle (x1, y1), (x2, y2) + */ + const gdouble square_side = MIN (x2 - x1, y2 - y1); + + /* diagonal from the top-left edge */ + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x1 + square_side, y1 + square_side); + + /* diagonal from the top-right edge */ + cairo_move_to (cr, x2, y1); + cairo_line_to (cr, x2 - square_side, y1 + square_side); + + /* diagonal from the bottom-left edge */ + cairo_move_to (cr, x1, y2); + cairo_line_to (cr, x1 + square_side, y2 - square_side); + + /* diagonal from the bottom-right edge */ + cairo_move_to (cr, x2, y2); + cairo_line_to (cr, x2 - square_side, y2 - square_side); + } + break; + + case GIMP_GUIDES_N_LINES: + for (i = 0; i < private->n_guides; i++) + { + draw_hline (cr, x1, x2, y1 + i * (y2 - y1) / private->n_guides); + draw_vline (cr, y1, y2, x1 + i * (x2 - x1) / private->n_guides); + } + break; + + case GIMP_GUIDES_SPACING: + break; + } + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_rectangle_guides_get_extents (GimpCanvasItem *item) +{ + GimpCanvasRectangleGuidesPrivate *private = GET_PRIVATE (item); + + if (private->type != GIMP_GUIDES_NONE) + { + cairo_rectangle_int_t rectangle; + gdouble x1, y1; + gdouble x2, y2; + + gimp_canvas_rectangle_guides_transform (item, &x1, &y1, &x2, &y2); + + rectangle.x = floor (x1 - 1.5); + rectangle.y = floor (y1 - 1.5); + rectangle.width = ceil (x2 - x1 + 3.0); + rectangle.height = ceil (y2 - y1 + 3.0); + + return cairo_region_create_rectangle (&rectangle); + } + + return NULL; +} + +GimpCanvasItem * +gimp_canvas_rectangle_guides_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpGuidesType type, + gint n_guides) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, + "shell", shell, + "x", x, + "y", y, + "width", width, + "height", height, + "type", type, + "n-guides", n_guides, + NULL); +} + +void +gimp_canvas_rectangle_guides_set (GimpCanvasItem *rectangle, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpGuidesType type, + gint n_guides) +{ + g_return_if_fail (GIMP_IS_CANVAS_RECTANGLE_GUIDES (rectangle)); + + gimp_canvas_item_begin_change (rectangle); + + g_object_set (rectangle, + "x", x, + "y", y, + "width", width, + "height", height, + "type", type, + "n-guides", n_guides, + NULL); + + gimp_canvas_item_end_change (rectangle); +} diff --git a/app/display/gimpcanvasrectangleguides.h b/app/display/gimpcanvasrectangleguides.h new file mode 100644 index 0000000..1d37d15 --- /dev/null +++ b/app/display/gimpcanvasrectangleguides.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvasrectangleguides.h + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_RECTANGLE_GUIDES_H__ +#define __GIMP_CANVAS_RECTANGLE_GUIDES_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_RECTANGLE_GUIDES (gimp_canvas_rectangle_guides_get_type ()) +#define GIMP_CANVAS_RECTANGLE_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, GimpCanvasRectangleGuides)) +#define GIMP_CANVAS_RECTANGLE_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, GimpCanvasRectangleGuidesClass)) +#define GIMP_IS_CANVAS_RECTANGLE_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES)) +#define GIMP_IS_CANVAS_RECTANGLE_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES)) +#define GIMP_CANVAS_RECTANGLE_GUIDES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_RECTANGLE_GUIDES, GimpCanvasRectangleGuidesClass)) + + +typedef struct _GimpCanvasRectangleGuides GimpCanvasRectangleGuides; +typedef struct _GimpCanvasRectangleGuidesClass GimpCanvasRectangleGuidesClass; + +struct _GimpCanvasRectangleGuides +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasRectangleGuidesClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_rectangle_guides_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_rectangle_guides_new (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpGuidesType type, + gint n_guides); + +void gimp_canvas_rectangle_guides_set (GimpCanvasItem *rectangle, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpGuidesType type, + gint n_guides); + + +#endif /* __GIMP_CANVAS_RECTANGLE_GUIDES_H__ */ diff --git a/app/display/gimpcanvassamplepoint.c b/app/display/gimpcanvassamplepoint.c new file mode 100644 index 0000000..913a5b7 --- /dev/null +++ b/app/display/gimpcanvassamplepoint.c @@ -0,0 +1,356 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvassamplepoint.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvas.h" +#include "gimpcanvas-style.h" +#include "gimpcanvassamplepoint.h" +#include "gimpdisplayshell.h" + + +#define GIMP_SAMPLE_POINT_DRAW_SIZE 14 + + +enum +{ + PROP_0, + PROP_X, + PROP_Y, + PROP_INDEX, + PROP_SAMPLE_POINT_STYLE +}; + + +typedef struct _GimpCanvasSamplePointPrivate GimpCanvasSamplePointPrivate; + +struct _GimpCanvasSamplePointPrivate +{ + gint x; + gint y; + gint index; + gboolean sample_point_style; +}; + +#define GET_PRIVATE(sample_point) \ + ((GimpCanvasSamplePointPrivate *) gimp_canvas_sample_point_get_instance_private ((GimpCanvasSamplePoint *) (sample_point))) + + +/* local function prototypes */ + +static void gimp_canvas_sample_point_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_sample_point_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_sample_point_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_sample_point_get_extents (GimpCanvasItem *item); +static void gimp_canvas_sample_point_stroke (GimpCanvasItem *item, + cairo_t *cr); +static void gimp_canvas_sample_point_fill (GimpCanvasItem *item, + cairo_t *cr); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasSamplePoint, gimp_canvas_sample_point, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_sample_point_parent_class + + +static void +gimp_canvas_sample_point_class_init (GimpCanvasSamplePointClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_sample_point_set_property; + object_class->get_property = gimp_canvas_sample_point_get_property; + + item_class->draw = gimp_canvas_sample_point_draw; + item_class->get_extents = gimp_canvas_sample_point_get_extents; + item_class->stroke = gimp_canvas_sample_point_stroke; + item_class->fill = gimp_canvas_sample_point_fill; + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_int ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_int ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_INDEX, + g_param_spec_int ("index", NULL, NULL, + 0, G_MAXINT, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SAMPLE_POINT_STYLE, + g_param_spec_boolean ("sample-point-style", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_sample_point_init (GimpCanvasSamplePoint *sample_point) +{ +} + +static void +gimp_canvas_sample_point_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + private->x = g_value_get_int (value); + break; + case PROP_Y: + private->y = g_value_get_int (value); + break; + case PROP_INDEX: + private->index = g_value_get_int (value); + break; + case PROP_SAMPLE_POINT_STYLE: + private->sample_point_style = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_sample_point_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + g_value_set_int (value, private->x); + break; + case PROP_Y: + g_value_set_int (value, private->x); + break; + case PROP_INDEX: + g_value_set_int (value, private->x); + break; + case PROP_SAMPLE_POINT_STYLE: + g_value_set_boolean (value, private->sample_point_style); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_sample_point_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item); + + gimp_canvas_item_transform_xy_f (item, + private->x + 0.5, + private->y + 0.5, + x, y); + + *x = floor (*x) + 0.5; + *y = floor (*y) + 0.5; +} + +#define HALF_SIZE (GIMP_SAMPLE_POINT_DRAW_SIZE / 2) + +static void +gimp_canvas_sample_point_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + PangoLayout *layout; + gdouble x, y; + gint x1, x2, y1, y2; + + gimp_canvas_sample_point_transform (item, &x, &y); + + x1 = x - GIMP_SAMPLE_POINT_DRAW_SIZE; + x2 = x + GIMP_SAMPLE_POINT_DRAW_SIZE; + y1 = y - GIMP_SAMPLE_POINT_DRAW_SIZE; + y2 = y + GIMP_SAMPLE_POINT_DRAW_SIZE; + + cairo_move_to (cr, x, y1); + cairo_line_to (cr, x, y1 + HALF_SIZE); + + cairo_move_to (cr, x, y2); + cairo_line_to (cr, x, y2 - HALF_SIZE); + + cairo_move_to (cr, x1, y); + cairo_line_to (cr, x1 + HALF_SIZE, y); + + cairo_move_to (cr, x2, y); + cairo_line_to (cr, x2 - HALF_SIZE, y); + + cairo_arc_negative (cr, x, y, HALF_SIZE, 0.0, 0.5 * G_PI); + + _gimp_canvas_item_stroke (item, cr); + + layout = gimp_canvas_get_layout (GIMP_CANVAS (canvas), + "%d", private->index); + + cairo_move_to (cr, x + 3, y + 3); + pango_cairo_show_layout (cr, layout); + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_sample_point_get_extents (GimpCanvasItem *item) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item); + GtkWidget *canvas = gimp_canvas_item_get_canvas (item); + cairo_rectangle_int_t rectangle; + PangoLayout *layout; + PangoRectangle ink; + gdouble x, y; + gint x1, x2, y1, y2; + + gimp_canvas_sample_point_transform (item, &x, &y); + + x1 = floor (x - GIMP_SAMPLE_POINT_DRAW_SIZE); + x2 = ceil (x + GIMP_SAMPLE_POINT_DRAW_SIZE); + y1 = floor (y - GIMP_SAMPLE_POINT_DRAW_SIZE); + y2 = ceil (y + GIMP_SAMPLE_POINT_DRAW_SIZE); + + layout = gimp_canvas_get_layout (GIMP_CANVAS (canvas), + "%d", private->index); + + pango_layout_get_extents (layout, &ink, NULL); + + x2 = MAX (x2, 3 + ink.width); + y2 = MAX (y2, 3 + ink.height); + + rectangle.x = x1 - 1.5; + rectangle.y = y1 - 1.5; + rectangle.width = x2 - x1 + 3.0; + rectangle.height = y2 - y1 + 3.0; + + return cairo_region_create_rectangle (&rectangle); +} + +static void +gimp_canvas_sample_point_stroke (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item); + + if (private->sample_point_style) + { + gimp_canvas_set_tool_bg_style (gimp_canvas_item_get_canvas (item), cr); + cairo_stroke_preserve (cr); + + gimp_canvas_set_sample_point_style (gimp_canvas_item_get_canvas (item), cr, + gimp_canvas_item_get_highlight (item)); + cairo_stroke (cr); + } + else + { + GIMP_CANVAS_ITEM_CLASS (parent_class)->stroke (item, cr); + } +} + +static void +gimp_canvas_sample_point_fill (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasSamplePointPrivate *private = GET_PRIVATE (item); + + if (private->sample_point_style) + { + gimp_canvas_set_sample_point_style (gimp_canvas_item_get_canvas (item), cr, + gimp_canvas_item_get_highlight (item)); + cairo_fill (cr); + } + else + { + GIMP_CANVAS_ITEM_CLASS (parent_class)->fill (item, cr); + } +} + +GimpCanvasItem * +gimp_canvas_sample_point_new (GimpDisplayShell *shell, + gint x, + gint y, + gint index, + gboolean sample_point_style) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_SAMPLE_POINT, + "shell", shell, + "x", x, + "y", y, + "index", index, + "sample-point-style", sample_point_style, + NULL); +} + +void +gimp_canvas_sample_point_set (GimpCanvasItem *sample_point, + gint x, + gint y) +{ + g_return_if_fail (GIMP_IS_CANVAS_SAMPLE_POINT (sample_point)); + + gimp_canvas_item_begin_change (sample_point); + + g_object_set (sample_point, + "x", x, + "y", y, + NULL); + + gimp_canvas_item_end_change (sample_point); +} diff --git a/app/display/gimpcanvassamplepoint.h b/app/display/gimpcanvassamplepoint.h new file mode 100644 index 0000000..00ac0d5 --- /dev/null +++ b/app/display/gimpcanvassamplepoint.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvassamplepoint.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_SAMPLE_POINT_H__ +#define __GIMP_CANVAS_SAMPLE_POINT_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_SAMPLE_POINT (gimp_canvas_sample_point_get_type ()) +#define GIMP_CANVAS_SAMPLE_POINT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_SAMPLE_POINT, GimpCanvasSamplePoint)) +#define GIMP_CANVAS_SAMPLE_POINT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_SAMPLE_POINT, GimpCanvasSamplePointClass)) +#define GIMP_IS_CANVAS_SAMPLE_POINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_SAMPLE_POINT)) +#define GIMP_IS_CANVAS_SAMPLE_POINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_SAMPLE_POINT)) +#define GIMP_CANVAS_SAMPLE_POINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_SAMPLE_POINT, GimpCanvasSamplePointClass)) + + +typedef struct _GimpCanvasSamplePoint GimpCanvasSamplePoint; +typedef struct _GimpCanvasSamplePointClass GimpCanvasSamplePointClass; + +struct _GimpCanvasSamplePoint +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasSamplePointClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_sample_point_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_sample_point_new (GimpDisplayShell *shell, + gint x, + gint y, + gint index, + gboolean sample_point_style); + +void gimp_canvas_sample_point_set (GimpCanvasItem *sample_point, + gint x, + gint y); + + +#endif /* __GIMP_CANVAS_SAMPLE_POINT_H__ */ diff --git a/app/display/gimpcanvastextcursor.c b/app/display/gimpcanvastextcursor.c new file mode 100644 index 0000000..aaa8c5f --- /dev/null +++ b/app/display/gimpcanvastextcursor.c @@ -0,0 +1,386 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvastextcursor.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpcanvastextcursor.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_OVERWRITE, + PROP_DIRECTION +}; + + +typedef struct _GimpCanvasTextCursorPrivate GimpCanvasTextCursorPrivate; + +struct _GimpCanvasTextCursorPrivate +{ + gint x; + gint y; + gint width; + gint height; + gboolean overwrite; + GimpTextDirection direction; +}; + +#define GET_PRIVATE(text_cursor) \ + ((GimpCanvasTextCursorPrivate *) gimp_canvas_text_cursor_get_instance_private ((GimpCanvasTextCursor *) (text_cursor))) + + +/* local function prototypes */ + +static void gimp_canvas_text_cursor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_text_cursor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_text_cursor_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_text_cursor_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasTextCursor, gimp_canvas_text_cursor, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_text_cursor_parent_class + + +static void +gimp_canvas_text_cursor_class_init (GimpCanvasTextCursorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_text_cursor_set_property; + object_class->get_property = gimp_canvas_text_cursor_get_property; + + item_class->draw = gimp_canvas_text_cursor_draw; + item_class->get_extents = gimp_canvas_text_cursor_get_extents; + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_int ("x", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_int ("y", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_int ("width", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_int ("height", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_OVERWRITE, + g_param_spec_boolean ("overwrite", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_DIRECTION, + g_param_spec_enum ("direction", NULL, NULL, + gimp_text_direction_get_type(), + GIMP_TEXT_DIRECTION_LTR, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_text_cursor_init (GimpCanvasTextCursor *text_cursor) +{ +} + +static void +gimp_canvas_text_cursor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasTextCursorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + private->x = g_value_get_int (value); + break; + case PROP_Y: + private->y = g_value_get_int (value); + break; + case PROP_WIDTH: + private->width = g_value_get_int (value); + break; + case PROP_HEIGHT: + private->height = g_value_get_int (value); + break; + case PROP_OVERWRITE: + private->overwrite = g_value_get_boolean (value); + break; + case PROP_DIRECTION: + private->direction = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_text_cursor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasTextCursorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_X: + g_value_set_int (value, private->x); + break; + case PROP_Y: + g_value_set_int (value, private->y); + break; + case PROP_WIDTH: + g_value_set_int (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_int (value, private->height); + break; + case PROP_OVERWRITE: + g_value_set_boolean (value, private->overwrite); + break; + case PROP_DIRECTION: + g_value_set_enum (value, private->direction); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_text_cursor_transform (GimpCanvasItem *item, + gdouble *x, + gdouble *y, + gdouble *w, + gdouble *h) +{ + GimpCanvasTextCursorPrivate *private = GET_PRIVATE (item); + + gimp_canvas_item_transform_xy_f (item, + MIN (private->x, + private->x + private->width), + MIN (private->y, + private->y + private->height), + x, y); + gimp_canvas_item_transform_xy_f (item, + MAX (private->x, + private->x + private->width), + MAX (private->y, + private->y + private->height), + w, h); + + *w -= *x; + *h -= *y; + + *x = floor (*x) + 0.5; + *y = floor (*y) + 0.5; + switch (private->direction) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_RTL: + break; + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + *x = *x - *w; + break; + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + *y = *y + *h; + break; + } + + if (private->overwrite) + { + *w = ceil (*w) - 1.0; + *h = ceil (*h) - 1.0; + } + else + { + switch (private->direction) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_RTL: + *w = 0; + *h = ceil (*h) - 1.0; + break; + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + *w = ceil (*w) - 1.0; + *h = 0; + break; + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + *w = ceil (*w) - 1.0; + *h = 0; + break; + } + } +} + +static void +gimp_canvas_text_cursor_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasTextCursorPrivate *private = GET_PRIVATE (item); + gdouble x, y; + gdouble w, h; + + gimp_canvas_text_cursor_transform (item, &x, &y, &w, &h); + + if (private->overwrite) + { + cairo_rectangle (cr, x, y, w, h); + } + else + { + switch (private->direction) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_RTL: + cairo_move_to (cr, x, y); + cairo_line_to (cr, x, y + h); + + cairo_move_to (cr, x - 3.0, y); + cairo_line_to (cr, x + 3.0, y); + + cairo_move_to (cr, x - 3.0, y + h); + cairo_line_to (cr, x + 3.0, y + h); + break; + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + w, y); + + cairo_move_to (cr, x, y - 3.0); + cairo_line_to (cr, x, y + 3.0); + + cairo_move_to (cr, x + w, y - 3.0); + cairo_line_to (cr, x + w, y + 3.0); + break; + } + } + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_text_cursor_get_extents (GimpCanvasItem *item) +{ + GimpCanvasTextCursorPrivate *private = GET_PRIVATE (item); + cairo_rectangle_int_t rectangle; + gdouble x, y; + gdouble w, h; + + gimp_canvas_text_cursor_transform (item, &x, &y, &w, &h); + + if (private->overwrite) + { + rectangle.x = floor (x - 1.5); + rectangle.y = floor (y - 1.5); + rectangle.width = ceil (w + 3.0); + rectangle.height = ceil (h + 3.0); + } + else + { + switch (private->direction) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_RTL: + rectangle.x = floor (x - 4.5); + rectangle.y = floor (y - 1.5); + rectangle.width = ceil (9.0); + rectangle.height = ceil (h + 3.0); + break; + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + rectangle.x = floor (x - 1.5); + rectangle.y = floor (y - 4.5); + rectangle.width = ceil (w + 3.0); + rectangle.height = ceil (9.0); + break; + } + } + + return cairo_region_create_rectangle (&rectangle); +} + +GimpCanvasItem * +gimp_canvas_text_cursor_new (GimpDisplayShell *shell, + PangoRectangle *cursor, + gboolean overwrite, + GimpTextDirection direction) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (cursor != NULL, NULL); + + return g_object_new (GIMP_TYPE_CANVAS_TEXT_CURSOR, + "shell", shell, + "x", cursor->x, + "y", cursor->y, + "width", cursor->width, + "height", cursor->height, + "overwrite", overwrite, + "direction", direction, + NULL); +} diff --git a/app/display/gimpcanvastextcursor.h b/app/display/gimpcanvastextcursor.h new file mode 100644 index 0000000..210b2a2 --- /dev/null +++ b/app/display/gimpcanvastextcursor.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvastextcursor.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_TEXT_CURSOR_H__ +#define __GIMP_CANVAS_TEXT_CURSOR_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_TEXT_CURSOR (gimp_canvas_text_cursor_get_type ()) +#define GIMP_CANVAS_TEXT_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_TEXT_CURSOR, GimpCanvasTextCursor)) +#define GIMP_CANVAS_TEXT_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_TEXT_CURSOR, GimpCanvasTextCursorClass)) +#define GIMP_IS_CANVAS_TEXT_CURSOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_TEXT_CURSOR)) +#define GIMP_IS_CANVAS_TEXT_CURSOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_TEXT_CURSOR)) +#define GIMP_CANVAS_TEXT_CURSOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_TEXT_CURSOR, GimpCanvasTextCursorClass)) + + +typedef struct _GimpCanvasTextCursor GimpCanvasTextCursor; +typedef struct _GimpCanvasTextCursorClass GimpCanvasTextCursorClass; + +struct _GimpCanvasTextCursor +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasTextCursorClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_text_cursor_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_text_cursor_new (GimpDisplayShell *shell, + PangoRectangle *cursor, + gboolean overwrite, + GimpTextDirection direction); + + +#endif /* __GIMP_CANVAS_RECTANGLE_H__ */ diff --git a/app/display/gimpcanvastransformguides.c b/app/display/gimpcanvastransformguides.c new file mode 100644 index 0000000..ba6c4cb --- /dev/null +++ b/app/display/gimpcanvastransformguides.c @@ -0,0 +1,683 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvastransformguides.c + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "gimpcanvastransformguides.h" +#include "gimpdisplayshell.h" + + +#define SQRT5 2.236067977 + + +enum +{ + PROP_0, + PROP_TRANSFORM, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_TYPE, + PROP_N_GUIDES, + PROP_CLIP +}; + + +typedef struct _GimpCanvasTransformGuidesPrivate GimpCanvasTransformGuidesPrivate; + +struct _GimpCanvasTransformGuidesPrivate +{ + GimpMatrix3 transform; + gdouble x1, y1; + gdouble x2, y2; + GimpGuidesType type; + gint n_guides; + gboolean clip; +}; + +#define GET_PRIVATE(transform) \ + ((GimpCanvasTransformGuidesPrivate *) gimp_canvas_transform_guides_get_instance_private ((GimpCanvasTransformGuides *) (transform))) + + +/* local function prototypes */ + +static void gimp_canvas_transform_guides_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_transform_guides_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_canvas_transform_guides_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_transform_guides_get_extents (GimpCanvasItem *item); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasTransformGuides, + gimp_canvas_transform_guides, GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_transform_guides_parent_class + + +static void +gimp_canvas_transform_guides_class_init (GimpCanvasTransformGuidesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->set_property = gimp_canvas_transform_guides_set_property; + object_class->get_property = gimp_canvas_transform_guides_get_property; + + item_class->draw = gimp_canvas_transform_guides_draw; + item_class->get_extents = gimp_canvas_transform_guides_get_extents; + + g_object_class_install_property (object_class, PROP_TRANSFORM, + gimp_param_spec_matrix3 ("transform", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", NULL, NULL, + GIMP_TYPE_GUIDES_TYPE, + GIMP_GUIDES_NONE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_N_GUIDES, + g_param_spec_int ("n-guides", NULL, NULL, + 1, 128, 4, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CLIP, + g_param_spec_boolean ("clip", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_transform_guides_init (GimpCanvasTransformGuides *transform) +{ +} + +static void +gimp_canvas_transform_guides_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TRANSFORM: + { + GimpMatrix3 *transform = g_value_get_boxed (value); + + if (transform) + private->transform = *transform; + else + gimp_matrix3_identity (&private->transform); + } + break; + + case PROP_X1: + private->x1 = g_value_get_double (value); + break; + + case PROP_Y1: + private->y1 = g_value_get_double (value); + break; + + case PROP_X2: + private->x2 = g_value_get_double (value); + break; + + case PROP_Y2: + private->y2 = g_value_get_double (value); + break; + + case PROP_TYPE: + private->type = g_value_get_enum (value); + break; + + case PROP_N_GUIDES: + private->n_guides = g_value_get_int (value); + break; + + case PROP_CLIP: + private->clip = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_transform_guides_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TRANSFORM: + g_value_set_boxed (value, &private->transform); + break; + + case PROP_X1: + g_value_set_double (value, private->x1); + break; + + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + + case PROP_X2: + g_value_set_double (value, private->x2); + break; + + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + case PROP_TYPE: + g_value_set_enum (value, private->type); + break; + + case PROP_N_GUIDES: + g_value_set_int (value, private->n_guides); + break; + + case PROP_CLIP: + g_value_set_boolean (value, private->clip); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_canvas_transform_guides_transform (GimpCanvasItem *item, + GimpVector2 *t_vertices, + gint *n_t_vertices) +{ + GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (item); + GimpVector2 vertices[4]; + + vertices[0] = (GimpVector2) { private->x1, private->y1 }; + vertices[1] = (GimpVector2) { private->x2, private->y1 }; + vertices[2] = (GimpVector2) { private->x2, private->y2 }; + vertices[3] = (GimpVector2) { private->x1, private->y2 }; + + if (private->clip) + { + gimp_transform_polygon (&private->transform, vertices, 4, TRUE, + t_vertices, n_t_vertices); + + return TRUE; + } + else + { + gint i; + + for (i = 0; i < 4; i++) + { + gimp_matrix3_transform_point (&private->transform, + vertices[i].x, vertices[i].y, + &t_vertices[i].x, &t_vertices[i].y); + } + + *n_t_vertices = 4; + + return gimp_transform_polygon_is_convex (t_vertices[0].x, t_vertices[0].y, + t_vertices[1].x, t_vertices[1].y, + t_vertices[3].x, t_vertices[3].y, + t_vertices[2].x, t_vertices[2].y); + } +} + +static void +draw_line (cairo_t *cr, + GimpCanvasItem *item, + GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (item); + GimpVector2 vertices[2]; + GimpVector2 t_vertices[2]; + gint n_t_vertices; + + vertices[0] = (GimpVector2) { x1, y1 }; + vertices[1] = (GimpVector2) { x2, y2 }; + + if (private->clip) + { + gimp_transform_polygon (transform, vertices, 2, FALSE, + t_vertices, &n_t_vertices); + } + else + { + gint i; + + for (i = 0; i < 2; i++) + { + gimp_matrix3_transform_point (transform, + vertices[i].x, vertices[i].y, + &t_vertices[i].x, &t_vertices[i].y); + } + + n_t_vertices = 2; + } + + if (n_t_vertices == 2) + { + gint i; + + for (i = 0; i < 2; i++) + { + GimpVector2 v; + + gimp_canvas_item_transform_xy_f (item, + t_vertices[i].x, t_vertices[i].y, + &v.x, &v.y); + + v.x = floor (v.x) + 0.5; + v.y = floor (v.y) + 0.5; + + if (i == 0) + cairo_move_to (cr, v.x, v.y); + else + cairo_line_to (cr, v.x, v.y); + } + } +} + +static void +draw_hline (cairo_t *cr, + GimpCanvasItem *item, + GimpMatrix3 *transform, + gdouble x1, + gdouble x2, + gdouble y) +{ + draw_line (cr, item, transform, x1, y, x2, y); +} + +static void +draw_vline (cairo_t *cr, + GimpCanvasItem *item, + GimpMatrix3 *transform, + gdouble y1, + gdouble y2, + gdouble x) +{ + draw_line (cr, item, transform, x, y1, x, y2); +} + +static void +gimp_canvas_transform_guides_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasTransformGuidesPrivate *private = GET_PRIVATE (item); + GimpVector2 t_vertices[5]; + gint n_t_vertices; + gboolean convex; + gint i; + + convex = gimp_canvas_transform_guides_transform (item, + t_vertices, &n_t_vertices); + + if (n_t_vertices < 2) + return; + + for (i = 0; i < n_t_vertices; i++) + { + GimpVector2 v; + + gimp_canvas_item_transform_xy_f (item, t_vertices[i].x, t_vertices[i].y, + &v.x, &v.y); + + v.x = floor (v.x) + 0.5; + v.y = floor (v.y) + 0.5; + + if (i == 0) + cairo_move_to (cr, v.x, v.y); + else + cairo_line_to (cr, v.x, v.y); + } + + cairo_close_path (cr); + + if (! convex || n_t_vertices < 3) + { + _gimp_canvas_item_stroke (item, cr); + return; + } + + switch (private->type) + { + case GIMP_GUIDES_NONE: + break; + + case GIMP_GUIDES_CENTER_LINES: + draw_hline (cr, item, &private->transform, + private->x1, private->x2, (private->y1 + private->y2) / 2); + draw_vline (cr, item, &private->transform, + private->y1, private->y2, (private->x1 + private->x2) / 2); + break; + + case GIMP_GUIDES_THIRDS: + draw_hline (cr, item, &private->transform, + private->x1, private->x2, (2 * private->y1 + private->y2) / 3); + draw_hline (cr, item, &private->transform, + private->x1, private->x2, (private->y1 + 2 * private->y2) / 3); + + draw_vline (cr, item, &private->transform, + private->y1, private->y2, (2 * private->x1 + private->x2) / 3); + draw_vline (cr, item, &private->transform, + private->y1, private->y2, (private->x1 + 2 * private->x2) / 3); + break; + + case GIMP_GUIDES_FIFTHS: + for (i = 0; i < 5; i++) + { + draw_hline (cr, item, &private->transform, + private->x1, private->x2, + private->y1 + i * (private->y2 - private->y1) / 5); + draw_vline (cr, item, &private->transform, + private->y1, private->y2, + private->x1 + i * (private->x2 - private->x1) / 5); + } + break; + + case GIMP_GUIDES_GOLDEN: + draw_hline (cr, item, &private->transform, + private->x1, private->x2, + (2 * private->y1 + (1 + SQRT5) * private->y2) / (3 + SQRT5)); + draw_hline (cr, item, &private->transform, + private->x1, private->x2, + ((1 + SQRT5) * private->y1 + 2 * private->y2) / (3 + SQRT5)); + + draw_vline (cr, item, &private->transform, + private->y1, private->y2, + (2 * private->x1 + (1 + SQRT5) * private->x2) / (3 + SQRT5)); + draw_vline (cr, item, &private->transform, + private->y1, private->y2, + ((1 + SQRT5) * private->x1 + 2 * private->x2) / (3 + SQRT5)); + break; + + /* This code implements the method of diagonals discovered by + * Edwin Westhoff - see http://www.diagonalmethod.info/ + */ + case GIMP_GUIDES_DIAGONALS: + { + /* the side of the largest square that can be + * fitted in whole into the rectangle (x1, y1), (x2, y2) + */ + const gdouble square_side = MIN (private->x2 - private->x1, + private->y2 - private->y1); + + /* diagonal from the top-left edge */ + draw_line (cr, item, &private->transform, + private->x1, private->y1, + private->x1 + square_side, + private->y1 + square_side); + + /* diagonal from the top-right edge */ + draw_line (cr, item, &private->transform, + private->x2, private->y1, + private->x2 - square_side, + private->y1 + square_side); + + /* diagonal from the bottom-left edge */ + draw_line (cr, item, &private->transform, + private->x1, private->y2, + private->x1 + square_side, + private->y2 - square_side); + + /* diagonal from the bottom-right edge */ + draw_line (cr, item, &private->transform, + private->x2, private->y2, + private->x2 - square_side, + private->y2 - square_side); + } + break; + + case GIMP_GUIDES_N_LINES: + case GIMP_GUIDES_SPACING: + { + gint width, height; + gint ngx, ngy; + + width = MAX (1, private->x2 - private->x1); + height = MAX (1, private->y2 - private->y1); + + /* the MIN() in the code below limits the grid to one line + * every 5 image pixels, see bug 772667. + */ + + if (private->type == GIMP_GUIDES_N_LINES) + { + if (width <= height) + { + ngx = private->n_guides; + ngx = MIN (ngx, width / 5); + + ngy = ngx * MAX (1, height / width); + ngy = MIN (ngy, height / 5); + } + else + { + ngy = private->n_guides; + ngy = MIN (ngy, height / 5); + + ngx = ngy * MAX (1, width / height); + ngx = MIN (ngx, width / 5); + } + } + else /* GIMP_GUIDES_SPACING */ + { + gint grid_size = MAX (2, private->n_guides); + + ngx = width / grid_size; + ngx = MIN (ngx, width / 5); + + ngy = height / grid_size; + ngy = MIN (ngy, height / 5); + } + + for (i = 1; i <= ngx; i++) + { + gdouble x = private->x1 + (((gdouble) i) / (ngx + 1) * + (private->x2 - private->x1)); + + draw_line (cr, item, &private->transform, + x, private->y1, + x, private->y2); + } + + for (i = 1; i <= ngy; i++) + { + gdouble y = private->y1 + (((gdouble) i) / (ngy + 1) * + (private->y2 - private->y1)); + + draw_line (cr, item, &private->transform, + private->x1, y, + private->x2, y); + } + } + } + + _gimp_canvas_item_stroke (item, cr); +} + +static cairo_region_t * +gimp_canvas_transform_guides_get_extents (GimpCanvasItem *item) +{ + GimpVector2 t_vertices[5]; + gint n_t_vertices; + GimpVector2 top_left; + GimpVector2 bottom_right; + cairo_rectangle_int_t extents; + gint i; + + gimp_canvas_transform_guides_transform (item, t_vertices, &n_t_vertices); + + if (n_t_vertices < 2) + return cairo_region_create (); + + for (i = 0; i < n_t_vertices; i++) + { + GimpVector2 v; + + gimp_canvas_item_transform_xy_f (item, + t_vertices[i].x, t_vertices[i].y, + &v.x, &v.y); + + if (i == 0) + { + top_left = bottom_right = v; + } + else + { + top_left.x = MIN (top_left.x, v.x); + top_left.y = MIN (top_left.y, v.y); + + bottom_right.x = MAX (bottom_right.x, v.x); + bottom_right.y = MAX (bottom_right.y, v.y); + } + } + + extents.x = (gint) floor (top_left.x - 1.5); + extents.y = (gint) floor (top_left.y - 1.5); + extents.width = (gint) ceil (bottom_right.x + 1.5) - extents.x; + extents.height = (gint) ceil (bottom_right.y + 1.5) - extents.y; + + return cairo_region_create_rectangle (&extents); +} + +GimpCanvasItem * +gimp_canvas_transform_guides_new (GimpDisplayShell *shell, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType type, + gint n_guides, + gboolean clip) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, + "shell", shell, + "transform", transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "type", type, + "n-guides", n_guides, + "clip", clip, + NULL); +} + +void +gimp_canvas_transform_guides_set (GimpCanvasItem *guides, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType type, + gint n_guides, + gboolean clip) +{ + g_return_if_fail (GIMP_IS_CANVAS_TRANSFORM_GUIDES (guides)); + + gimp_canvas_item_begin_change (guides); + + g_object_set (guides, + "transform", transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "type", type, + "n-guides", n_guides, + "clip", clip, + NULL); + + gimp_canvas_item_end_change (guides); +} diff --git a/app/display/gimpcanvastransformguides.h b/app/display/gimpcanvastransformguides.h new file mode 100644 index 0000000..4358501 --- /dev/null +++ b/app/display/gimpcanvastransformguides.h @@ -0,0 +1,73 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvastransformguides.h + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_TRANSFORM_GUIDES_H__ +#define __GIMP_CANVAS_TRANSFORM_GUIDES_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_TRANSFORM_GUIDES (gimp_canvas_transform_guides_get_type ()) +#define GIMP_CANVAS_TRANSFORM_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, GimpCanvasTransformGuides)) +#define GIMP_CANVAS_TRANSFORM_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, GimpCanvasTransformGuidesClass)) +#define GIMP_IS_CANVAS_TRANSFORM_GUIDES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES)) +#define GIMP_IS_CANVAS_TRANSFORM_GUIDES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES)) +#define GIMP_CANVAS_TRANSFORM_GUIDES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_TRANSFORM_GUIDES, GimpCanvasTransformGuidesClass)) + + +typedef struct _GimpCanvasTransformGuides GimpCanvasTransformGuides; +typedef struct _GimpCanvasTransformGuidesClass GimpCanvasTransformGuidesClass; + +struct _GimpCanvasTransformGuides +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasTransformGuidesClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_transform_guides_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_transform_guides_new (GimpDisplayShell *shell, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType type, + gint n_guides, + gboolean clip); + +void gimp_canvas_transform_guides_set (GimpCanvasItem *guides, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType type, + gint n_guides, + gboolean clip); + + +#endif /* __GIMP_CANVAS_TRANSFORM_GUIDES_H__ */ diff --git a/app/display/gimpcanvastransformpreview.c b/app/display/gimpcanvastransformpreview.c new file mode 100644 index 0000000..8c8d950 --- /dev/null +++ b/app/display/gimpcanvastransformpreview.c @@ -0,0 +1,791 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvastransformpreview.c + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display/display-types.h" + +#include "gegl/gimp-gegl-nodes.h" +#include "gegl/gimp-gegl-utils.h" +#include "gegl/gimptilehandlervalidate.h" + +#include "core/gimp-transform-resize.h" +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" +#include "core/gimpchannel.h" +#include "core/gimpimage.h" +#include "core/gimplayer.h" +#include "core/gimppickable.h" + +#include "gimpcanvas.h" +#include "gimpcanvastransformpreview.h" +#include "gimpdisplayshell.h" + + +enum +{ + PROP_0, + PROP_PICKABLE, + PROP_TRANSFORM, + PROP_CLIP, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_OPACITY +}; + + +typedef struct _GimpCanvasTransformPreviewPrivate GimpCanvasTransformPreviewPrivate; + +struct _GimpCanvasTransformPreviewPrivate +{ + GimpPickable *pickable; + GimpMatrix3 transform; + GimpTransformResize clip; + gdouble x1, y1; + gdouble x2, y2; + gdouble opacity; + + GeglNode *node; + GeglNode *source_node; + GeglNode *convert_format_node; + GeglNode *layer_mask_source_node; + GeglNode *layer_mask_opacity_node; + GeglNode *mask_source_node; + GeglNode *mask_translate_node; + GeglNode *mask_crop_node; + GeglNode *opacity_node; + GeglNode *cache_node; + GeglNode *transform_node; + + GimpPickable *node_pickable; + GimpDrawable *node_layer_mask; + GimpDrawable *node_mask; + GeglRectangle node_rect; + gdouble node_opacity; + GimpMatrix3 node_matrix; + GeglNode *node_output; +}; + +#define GET_PRIVATE(transform_preview) \ + ((GimpCanvasTransformPreviewPrivate *) gimp_canvas_transform_preview_get_instance_private ((GimpCanvasTransformPreview *) (transform_preview))) + + +/* local function prototypes */ + +static void gimp_canvas_transform_preview_dispose (GObject *object); +static void gimp_canvas_transform_preview_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_canvas_transform_preview_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_canvas_transform_preview_draw (GimpCanvasItem *item, + cairo_t *cr); +static cairo_region_t * gimp_canvas_transform_preview_get_extents (GimpCanvasItem *item); + +static void gimp_canvas_transform_preview_layer_changed (GimpLayer *layer, + GimpCanvasTransformPreview *transform_preview); + +static void gimp_canvas_transform_preview_set_pickable (GimpCanvasTransformPreview *transform_preview, + GimpPickable *pickable); +static void gimp_canvas_transform_preview_sync_node (GimpCanvasTransformPreview *transform_preview); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasTransformPreview, + gimp_canvas_transform_preview, + GIMP_TYPE_CANVAS_ITEM) + +#define parent_class gimp_canvas_transform_preview_parent_class + + +/* private functions */ + + +static void +gimp_canvas_transform_preview_class_init (GimpCanvasTransformPreviewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpCanvasItemClass *item_class = GIMP_CANVAS_ITEM_CLASS (klass); + + object_class->dispose = gimp_canvas_transform_preview_dispose; + object_class->set_property = gimp_canvas_transform_preview_set_property; + object_class->get_property = gimp_canvas_transform_preview_get_property; + + item_class->draw = gimp_canvas_transform_preview_draw; + item_class->get_extents = gimp_canvas_transform_preview_get_extents; + + g_object_class_install_property (object_class, PROP_PICKABLE, + g_param_spec_object ("pickable", + NULL, NULL, + GIMP_TYPE_PICKABLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_TRANSFORM, + gimp_param_spec_matrix3 ("transform", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CLIP, + g_param_spec_enum ("clip", + NULL, NULL, + GIMP_TYPE_TRANSFORM_RESIZE, + GIMP_TRANSFORM_RESIZE_ADJUST, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_OPACITY, + g_param_spec_double ("opacity", + NULL, NULL, + 0.0, 1.0, 1.0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_canvas_transform_preview_init (GimpCanvasTransformPreview *transform_preview) +{ + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (transform_preview); + + private->clip = GIMP_TRANSFORM_RESIZE_ADJUST; + private->opacity = 1.0; +} + +static void +gimp_canvas_transform_preview_dispose (GObject *object) +{ + GimpCanvasTransformPreview *transform_preview = GIMP_CANVAS_TRANSFORM_PREVIEW (object); + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->node); + + gimp_canvas_transform_preview_set_pickable (transform_preview, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_canvas_transform_preview_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCanvasTransformPreview *transform_preview = GIMP_CANVAS_TRANSFORM_PREVIEW (object); + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_PICKABLE: + gimp_canvas_transform_preview_set_pickable (transform_preview, + g_value_get_object (value)); + break; + + case PROP_TRANSFORM: + { + GimpMatrix3 *transform = g_value_get_boxed (value); + + if (transform) + private->transform = *transform; + else + gimp_matrix3_identity (&private->transform); + } + break; + + case PROP_CLIP: + private->clip = g_value_get_enum (value); + break; + + case PROP_X1: + private->x1 = g_value_get_double (value); + break; + + case PROP_Y1: + private->y1 = g_value_get_double (value); + break; + + case PROP_X2: + private->x2 = g_value_get_double (value); + break; + + case PROP_Y2: + private->y2 = g_value_get_double (value); + break; + + case PROP_OPACITY: + private->opacity = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_canvas_transform_preview_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_PICKABLE: + g_value_set_object (value, private->pickable); + break; + + case PROP_TRANSFORM: + g_value_set_boxed (value, &private->transform); + break; + + case PROP_CLIP: + g_value_set_enum (value, private->clip); + break; + + case PROP_X1: + g_value_set_double (value, private->x1); + break; + + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + + case PROP_X2: + g_value_set_double (value, private->x2); + break; + + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + case PROP_OPACITY: + g_value_set_double (value, private->opacity); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_canvas_transform_preview_transform (GimpCanvasItem *item, + cairo_rectangle_int_t *extents) +{ + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (item); + gint x1, y1; + gint x2, y2; + gdouble tx1, ty1; + gdouble tx2, ty2; + + if (! gimp_transform_resize_boundary (&private->transform, + private->clip, + private->x1, private->y1, + private->x2, private->y2, + &x1, &y1, + &x2, &y2)) + { + return FALSE; + } + + gimp_canvas_item_transform_xy_f (item, x1, y1, &tx1, &ty1); + gimp_canvas_item_transform_xy_f (item, x2, y2, &tx2, &ty2); + + extents->x = (gint) floor (tx1); + extents->y = (gint) floor (ty1); + extents->width = (gint) ceil (tx2) - extents->x; + extents->height = (gint) ceil (ty2) - extents->y; + + return TRUE; +} + +static void +gimp_canvas_transform_preview_draw (GimpCanvasItem *item, + cairo_t *cr) +{ + GimpCanvasTransformPreview *transform_preview = GIMP_CANVAS_TRANSFORM_PREVIEW (item); + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (item); + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + cairo_rectangle_int_t extents; + gdouble clip_x1, clip_y1; + gdouble clip_x2, clip_y2; + GeglRectangle bounds; + cairo_surface_t *surface; + guchar *surface_data; + gint surface_stride; + + if (! gimp_canvas_transform_preview_transform (item, &extents)) + return; + + cairo_clip_extents (cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2); + + clip_x1 = floor (clip_x1); + clip_y1 = floor (clip_y1); + clip_x2 = ceil (clip_x2); + clip_y2 = ceil (clip_y2); + + if (! gegl_rectangle_intersect (&bounds, + GEGL_RECTANGLE (extents.x, + extents.y, + extents.width, + extents.height), + GEGL_RECTANGLE (clip_x1, + clip_y1, + clip_x2 - clip_x1, + clip_y2 - clip_y1))) + { + return; + } + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + bounds.width, bounds.height); + + g_return_if_fail (surface != NULL); + + surface_data = cairo_image_surface_get_data (surface); + surface_stride = cairo_image_surface_get_stride (surface); + + gimp_canvas_transform_preview_sync_node (transform_preview); + + gegl_node_blit (private->node_output, 1.0, + GEGL_RECTANGLE (bounds.x + shell->offset_x, + bounds.y + shell->offset_y, + bounds.width, + bounds.height), + babl_format ("cairo-ARGB32"), surface_data, surface_stride, + GEGL_BLIT_CACHE); + + cairo_surface_mark_dirty (surface); + + cairo_set_source_surface (cr, surface, bounds.x, bounds.y); + cairo_rectangle (cr, bounds.x, bounds.y, bounds.width, bounds.height); + cairo_fill (cr); + + cairo_surface_destroy (surface); +} + +static cairo_region_t * +gimp_canvas_transform_preview_get_extents (GimpCanvasItem *item) +{ + cairo_rectangle_int_t rectangle; + + if (gimp_canvas_transform_preview_transform (item, &rectangle)) + return cairo_region_create_rectangle (&rectangle); + + return NULL; +} + +static void +gimp_canvas_transform_preview_layer_changed (GimpLayer *layer, + GimpCanvasTransformPreview *transform_preview) +{ + GimpCanvasItem *item = GIMP_CANVAS_ITEM (transform_preview); + + gimp_canvas_item_begin_change (item); + gimp_canvas_item_end_change (item); +} + +static void +gimp_canvas_transform_preview_set_pickable (GimpCanvasTransformPreview *transform_preview, + GimpPickable *pickable) +{ + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (transform_preview); + + if (private->pickable && GIMP_IS_LAYER (private->pickable)) + { + g_signal_handlers_disconnect_by_func ( + private->pickable, + gimp_canvas_transform_preview_layer_changed, + transform_preview); + } + + g_set_object (&private->pickable, pickable); + + if (pickable && GIMP_IS_LAYER (pickable)) + { + g_signal_connect (pickable, "opacity-changed", + G_CALLBACK (gimp_canvas_transform_preview_layer_changed), + transform_preview); + g_signal_connect (pickable, "mask-changed", + G_CALLBACK (gimp_canvas_transform_preview_layer_changed), + transform_preview); + g_signal_connect (pickable, "apply-mask-changed", + G_CALLBACK (gimp_canvas_transform_preview_layer_changed), + transform_preview); + g_signal_connect (pickable, "show-mask-changed", + G_CALLBACK (gimp_canvas_transform_preview_layer_changed), + transform_preview); + } +} + +static void +gimp_canvas_transform_preview_sync_node (GimpCanvasTransformPreview *transform_preview) +{ + GimpCanvasTransformPreviewPrivate *private = GET_PRIVATE (transform_preview); + GimpCanvasItem *item = GIMP_CANVAS_ITEM (transform_preview); + GimpDisplayShell *shell = gimp_canvas_item_get_shell (item); + GimpImage *image = gimp_canvas_item_get_image (item); + GimpPickable *pickable = private->pickable; + GimpDrawable *layer_mask = NULL; + GimpDrawable *mask = NULL; + gdouble opacity = private->opacity; + gint offset_x = 0; + gint offset_y = 0; + GimpMatrix3 matrix; + + if (! private->node) + { + private->node = gegl_node_new (); + + private->source_node = + gegl_node_new_child (private->node, + "operation", "gimp:buffer-source-validate", + NULL); + + private->convert_format_node = + gegl_node_new_child (private->node, + "operation", "gegl:convert-format", + NULL); + + private->layer_mask_source_node = + gegl_node_new_child (private->node, + "operation", "gimp:buffer-source-validate", + NULL); + + private->layer_mask_opacity_node = + gegl_node_new_child (private->node, + "operation", "gegl:opacity", + NULL); + + private->mask_source_node = + gegl_node_new_child (private->node, + "operation", "gimp:buffer-source-validate", + NULL); + + private->mask_translate_node = + gegl_node_new_child (private->node, + "operation", "gegl:translate", + NULL); + + private->mask_crop_node = + gegl_node_new_child (private->node, + "operation", "gegl:crop", + "width", 0.0, + "height", 0.0, + NULL); + + private->opacity_node = + gegl_node_new_child (private->node, + "operation", "gegl:opacity", + NULL); + + private->cache_node = + gegl_node_new_child (private->node, + "operation", "gegl:cache", + NULL); + + private->transform_node = + gegl_node_new_child (private->node, + "operation", "gegl:transform", + "near-z", GIMP_TRANSFORM_NEAR_Z, + "sampler", GIMP_INTERPOLATION_NONE, + NULL); + + gegl_node_link_many (private->source_node, + private->convert_format_node, + private->transform_node, + NULL); + + gegl_node_connect_to (private->layer_mask_source_node, "output", + private->layer_mask_opacity_node, "aux"); + + gegl_node_link_many (private->mask_source_node, + private->mask_translate_node, + private->mask_crop_node, + NULL); + + private->node_pickable = NULL; + private->node_layer_mask = NULL; + private->node_mask = NULL; + private->node_rect = *GEGL_RECTANGLE (0, 0, 0, 0); + private->node_opacity = 1.0; + gimp_matrix3_identity (&private->node_matrix); + private->node_output = private->transform_node; + } + + if (GIMP_IS_ITEM (pickable)) + { + gimp_item_get_offset (GIMP_ITEM (private->pickable), + &offset_x, &offset_y); + + if (gimp_item_mask_bounds (GIMP_ITEM (pickable), + NULL, NULL, NULL, NULL)) + { + mask = GIMP_DRAWABLE (gimp_image_get_mask (image)); + } + + if (GIMP_IS_LAYER (pickable)) + { + GimpLayer *layer = GIMP_LAYER (pickable); + + opacity *= gimp_layer_get_opacity (layer); + + layer_mask = GIMP_DRAWABLE (gimp_layer_get_mask (layer)); + + if (layer_mask) + { + if (gimp_layer_get_show_mask (layer) && ! mask) + { + pickable = GIMP_PICKABLE (layer_mask); + layer_mask = NULL; + } + else if (! gimp_layer_get_apply_mask (layer)) + { + layer_mask = NULL; + } + } + } + } + + gimp_matrix3_identity (&matrix); + gimp_matrix3_translate (&matrix, offset_x, offset_y); + gimp_matrix3_mult (&private->transform, &matrix); + gimp_matrix3_scale (&matrix, shell->scale_x, shell->scale_y); + + if (pickable != private->node_pickable) + { + GeglBuffer *buffer; + + gimp_pickable_flush (pickable); + + buffer = gimp_pickable_get_buffer (pickable); + + if (gimp_tile_handler_validate_get_assigned (buffer)) + buffer = gimp_gegl_buffer_dup (buffer); + else + buffer = g_object_ref (buffer); + + gegl_node_set (private->source_node, + "buffer", buffer, + NULL); + gegl_node_set (private->convert_format_node, + "format", gimp_pickable_get_format_with_alpha (pickable), + NULL); + + g_object_unref (buffer); + } + + if (layer_mask != private->node_layer_mask) + { + gegl_node_set (private->layer_mask_source_node, + "buffer", layer_mask ? + gimp_drawable_get_buffer (layer_mask) : + NULL, + NULL); + } + + if (mask) + { + GeglRectangle rect; + + rect.x = offset_x; + rect.y = offset_y; + rect.width = gimp_item_get_width (GIMP_ITEM (private->pickable)); + rect.height = gimp_item_get_height (GIMP_ITEM (private->pickable)); + + if (mask != private->node_mask) + { + gegl_node_set (private->mask_source_node, + "buffer", gimp_drawable_get_buffer (mask), + NULL); + } + + if (! gegl_rectangle_equal (&rect, &private->node_rect)) + { + private->node_rect = rect; + + gegl_node_set (private->mask_translate_node, + "x", (gdouble) -rect.x, + "y", (gdouble) -rect.y, + NULL); + + gegl_node_set (private->mask_crop_node, + "width", (gdouble) rect.width, + "height", (gdouble) rect.height, + NULL); + } + + if (! private->node_mask) + { + gegl_node_connect_to (private->mask_crop_node, "output", + private->opacity_node, "aux"); + } + } + else if (private->node_mask) + { + gegl_node_disconnect (private->opacity_node, "aux"); + } + + if (opacity != private->node_opacity) + { + gegl_node_set (private->opacity_node, + "value", opacity, + NULL); + } + + if (layer_mask != private->node_layer_mask || + mask != private->node_mask || + (opacity != 1.0) != (private->node_opacity != 1.0)) + { + GeglNode *output = private->source_node; + + if (layer_mask && ! mask) + { + gegl_node_link (output, private->layer_mask_opacity_node); + output = private->layer_mask_opacity_node; + } + else + { + gegl_node_disconnect (private->layer_mask_opacity_node, "input"); + } + + if (mask || (opacity != 1.0)) + { + gegl_node_link (output, private->opacity_node); + output = private->opacity_node; + } + else + { + gegl_node_disconnect (private->opacity_node, "input"); + } + + if (output == private->source_node) + { + gegl_node_disconnect (private->cache_node, "input"); + + gegl_node_link (output, private->convert_format_node); + output = private->convert_format_node; + } + else + { + gegl_node_disconnect (private->convert_format_node, "input"); + + gegl_node_link (output, private->cache_node); + output = private->cache_node; + } + + gegl_node_link (output, private->transform_node); + output = private->transform_node; + + if (layer_mask && mask) + { + gegl_node_link (output, private->layer_mask_opacity_node); + output = private->layer_mask_opacity_node; + } + + private->node_output = output; + } + + if (memcmp (&matrix, &private->node_matrix, sizeof (matrix))) + { + private->node_matrix = matrix; + + gimp_gegl_node_set_matrix (private->transform_node, &matrix); + } + + private->node_pickable = pickable; + private->node_layer_mask = layer_mask; + private->node_mask = mask; + private->node_opacity = opacity; +} + + +/* public functions */ + + +GimpCanvasItem * +gimp_canvas_transform_preview_new (GimpDisplayShell *shell, + GimpPickable *pickable, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL); + g_return_val_if_fail (transform != NULL, NULL); + + return g_object_new (GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, + "shell", shell, + "pickable", pickable, + "transform", transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); +} diff --git a/app/display/gimpcanvastransformpreview.h b/app/display/gimpcanvastransformpreview.h new file mode 100644 index 0000000..a82d50c --- /dev/null +++ b/app/display/gimpcanvastransformpreview.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcanvastransformpreview.h + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CANVAS_TRANSFORM_PREVIEW_H__ +#define __GIMP_CANVAS_TRANSFORM_PREVIEW_H__ + + +#include "gimpcanvasitem.h" + + +#define GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW (gimp_canvas_transform_preview_get_type ()) +#define GIMP_CANVAS_TRANSFORM_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, GimpCanvasTransformPreview)) +#define GIMP_CANVAS_TRANSFORM_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, GimpCanvasTransformPreviewClass)) +#define GIMP_IS_CANVAS_TRANSFORM_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW)) +#define GIMP_IS_CANVAS_TRANSFORM_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW)) +#define GIMP_CANVAS_TRANSFORM_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CANVAS_TRANSFORM_PREVIEW, GimpCanvasTransformPreviewClass)) + + +typedef struct _GimpCanvasTransformPreview GimpCanvasTransformPreview; +typedef struct _GimpCanvasTransformPreviewClass GimpCanvasTransformPreviewClass; + +struct _GimpCanvasTransformPreview +{ + GimpCanvasItem parent_instance; +}; + +struct _GimpCanvasTransformPreviewClass +{ + GimpCanvasItemClass parent_class; +}; + + +GType gimp_canvas_transform_preview_get_type (void) G_GNUC_CONST; + +GimpCanvasItem * gimp_canvas_transform_preview_new (GimpDisplayShell *shell, + GimpPickable *pickable, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + + +#endif /* __GIMP_CANVAS_TRANSFORM_PREVIEW_H__ */ diff --git a/app/display/gimpcursorview.c b/app/display/gimpcursorview.c new file mode 100644 index 0000000..846df91 --- /dev/null +++ b/app/display/gimpcursorview.c @@ -0,0 +1,887 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcursorview.c + * Copyright (C) 2005-2016 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimage-pick-color.h" +#include "core/gimpitem.h" + +#include "widgets/gimpcolorframe.h" +#include "widgets/gimpdocked.h" +#include "widgets/gimpmenufactory.h" +#include "widgets/gimpsessioninfo-aux.h" + +#include "gimpcursorview.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_SAMPLE_MERGED +}; + + +struct _GimpCursorViewPrivate +{ + GimpEditor parent_instance; + + GtkWidget *coord_hbox; + GtkWidget *selection_hbox; + GtkWidget *color_hbox; + + GtkWidget *pixel_x_label; + GtkWidget *pixel_y_label; + GtkWidget *unit_x_label; + GtkWidget *unit_y_label; + GtkWidget *selection_x_label; + GtkWidget *selection_y_label; + GtkWidget *selection_width_label; + GtkWidget *selection_height_label; + GtkWidget *color_frame_1; + GtkWidget *color_frame_2; + + gboolean sample_merged; + + GimpContext *context; + GimpDisplayShell *shell; + GimpImage *image; + GimpUnit unit; + + guint cursor_idle_id; + GimpImage *cursor_image; + GimpUnit cursor_unit; + gdouble cursor_x; + gdouble cursor_y; +}; + + +static void gimp_cursor_view_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_cursor_view_dispose (GObject *object); +static void gimp_cursor_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cursor_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_cursor_view_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_cursor_view_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_cursor_view_get_aux_info (GimpDocked *docked); + +static void gimp_cursor_view_set_context (GimpDocked *docked, + GimpContext *context); +static void gimp_cursor_view_image_changed (GimpCursorView *view, + GimpImage *image, + GimpContext *context); +static void gimp_cursor_view_mask_changed (GimpCursorView *view, + GimpImage *image); +static void gimp_cursor_view_diplay_changed (GimpCursorView *view, + GimpDisplay *display, + GimpContext *context); +static void gimp_cursor_view_shell_unit_changed (GimpCursorView *view, + GParamSpec *pspec, + GimpDisplayShell *shell); +static void gimp_cursor_view_format_as_unit (GimpUnit unit, + gchar *output_buf, + gint output_buf_size, + gdouble pixel_value, + gdouble image_res); +static void gimp_cursor_view_set_label_italic (GtkWidget *label, + gboolean italic); +static void gimp_cursor_view_update_selection_info (GimpCursorView *view, + GimpImage *image, + GimpUnit unit); +static gboolean gimp_cursor_view_cursor_idle (GimpCursorView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpCursorView, gimp_cursor_view, GIMP_TYPE_EDITOR, + G_ADD_PRIVATE (GimpCursorView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_cursor_view_docked_iface_init)) + +#define parent_class gimp_cursor_view_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_cursor_view_class_init (GimpCursorViewClass* klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_cursor_view_dispose; + object_class->get_property = gimp_cursor_view_get_property; + object_class->set_property = gimp_cursor_view_set_property; + + widget_class->style_set = gimp_cursor_view_style_set; + + g_object_class_install_property (object_class, PROP_SAMPLE_MERGED, + g_param_spec_boolean ("sample-merged", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_cursor_view_init (GimpCursorView *view) +{ + GtkWidget *frame; + GtkWidget *table; + GtkWidget *toggle; + gint content_spacing; + + view->priv = gimp_cursor_view_get_instance_private (view); + + view->priv->sample_merged = TRUE; + view->priv->context = NULL; + view->priv->shell = NULL; + view->priv->image = NULL; + view->priv->unit = GIMP_UNIT_PIXEL; + view->priv->cursor_idle_id = 0; + + gtk_widget_style_get (GTK_WIDGET (view), + "content-spacing", &content_spacing, + NULL); + + + /* cursor information */ + + view->priv->coord_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, + content_spacing); + gtk_box_set_homogeneous (GTK_BOX (view->priv->coord_hbox), TRUE); + gtk_box_pack_start (GTK_BOX (view), view->priv->coord_hbox, + FALSE, FALSE, 0); + gtk_widget_show (view->priv->coord_hbox); + + view->priv->selection_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, + content_spacing); + gtk_box_set_homogeneous (GTK_BOX (view->priv->selection_hbox), TRUE); + gtk_box_pack_start (GTK_BOX (view), view->priv->selection_hbox, + FALSE, FALSE, 0); + gtk_widget_show (view->priv->selection_hbox); + + + /* Pixels */ + + frame = gimp_frame_new (_("Pixels")); + gtk_box_pack_start (GTK_BOX (view->priv->coord_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + view->priv->pixel_x_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->pixel_x_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("X"), 0.5, 0.5, + view->priv->pixel_x_label, 1, FALSE); + + view->priv->pixel_y_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->pixel_y_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Y"), 0.5, 0.5, + view->priv->pixel_y_label, 1, FALSE); + + + /* Units */ + + frame = gimp_frame_new (_("Units")); + gtk_box_pack_start (GTK_BOX (view->priv->coord_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + view->priv->unit_x_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->unit_x_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("X"), 0.5, 0.5, + view->priv->unit_x_label, 1, FALSE); + + view->priv->unit_y_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->unit_y_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Y"), 0.5, 0.5, + view->priv->unit_y_label, 1, FALSE); + + + /* Selection Bounding Box */ + + frame = gimp_frame_new (_("Selection")); + gtk_box_pack_start (GTK_BOX (view->priv->selection_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + gimp_help_set_help_data (frame, _("The selection's bounding box"), NULL); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + view->priv->selection_x_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->selection_x_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("X"), 0.5, 0.5, + view->priv->selection_x_label, 1, FALSE); + + view->priv->selection_y_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->selection_y_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("Y"), 0.5, 0.5, + view->priv->selection_y_label, 1, FALSE); + + frame = gimp_frame_new (""); + gtk_box_pack_start (GTK_BOX (view->priv->selection_hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + view->priv->selection_width_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->selection_width_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + /* Width */ + _("W"), 0.5, 0.5, + view->priv->selection_width_label, 1, FALSE); + + view->priv->selection_height_label = gtk_label_new (_("n/a")); + gtk_label_set_xalign (GTK_LABEL (view->priv->selection_height_label), 1.0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + /* Height */ + _("H"), 0.5, 0.5, + view->priv->selection_height_label, 1, FALSE); + + + /* color information */ + + view->priv->color_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, + content_spacing); + gtk_box_set_homogeneous (GTK_BOX (view->priv->color_hbox), TRUE); + gtk_box_pack_start (GTK_BOX (view), view->priv->color_hbox, FALSE, FALSE, 0); + gtk_widget_show (view->priv->color_hbox); + + view->priv->color_frame_1 = gimp_color_frame_new (); + gimp_color_frame_set_mode (GIMP_COLOR_FRAME (view->priv->color_frame_1), + GIMP_COLOR_PICK_MODE_PIXEL); + gimp_color_frame_set_ellipsize (GIMP_COLOR_FRAME (view->priv->color_frame_1), + PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (view->priv->color_hbox), view->priv->color_frame_1, + TRUE, TRUE, 0); + gtk_widget_show (view->priv->color_frame_1); + + view->priv->color_frame_2 = gimp_color_frame_new (); + gimp_color_frame_set_mode (GIMP_COLOR_FRAME (view->priv->color_frame_2), + GIMP_COLOR_PICK_MODE_RGB_PERCENT); + gtk_box_pack_start (GTK_BOX (view->priv->color_hbox), view->priv->color_frame_2, + TRUE, TRUE, 0); + gtk_widget_show (view->priv->color_frame_2); + + /* sample merged toggle */ + + toggle = gimp_prop_check_button_new (G_OBJECT (view), "sample-merged", + _("_Sample Merged")); + gtk_box_pack_start (GTK_BOX (view), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); +} + +static void +gimp_cursor_view_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_aux_info = gimp_cursor_view_set_aux_info; + iface->get_aux_info = gimp_cursor_view_get_aux_info; + iface->set_context = gimp_cursor_view_set_context; +} + +static void +gimp_cursor_view_dispose (GObject *object) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (object); + + if (view->priv->context) + gimp_docked_set_context (GIMP_DOCKED (view), NULL); + + if (view->priv->cursor_idle_id) + { + g_source_remove (view->priv->cursor_idle_id); + view->priv->cursor_idle_id = 0; + } + + g_clear_object (&view->priv->cursor_image); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_cursor_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (object); + + switch (property_id) + { + case PROP_SAMPLE_MERGED: + view->priv->sample_merged = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_cursor_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (object); + + switch (property_id) + { + case PROP_SAMPLE_MERGED: + g_value_set_boolean (value, view->priv->sample_merged); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +#define AUX_INFO_FRAME_1_MODE "frame-1-mode" +#define AUX_INFO_FRAME_2_MODE "frame-2-mode" + +static void +gimp_cursor_view_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (docked); + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + GtkWidget *frame = NULL; + + if (! strcmp (aux->name, AUX_INFO_FRAME_1_MODE)) + frame = view->priv->color_frame_1; + else if (! strcmp (aux->name, AUX_INFO_FRAME_2_MODE)) + frame = view->priv->color_frame_2; + + if (frame) + { + GEnumClass *enum_class; + GEnumValue *enum_value; + + enum_class = g_type_class_peek (GIMP_TYPE_COLOR_PICK_MODE); + enum_value = g_enum_get_value_by_nick (enum_class, aux->value); + + if (enum_value) + gimp_color_frame_set_mode (GIMP_COLOR_FRAME (frame), + enum_value->value); + } + } +} + +static GList * +gimp_cursor_view_get_aux_info (GimpDocked *docked) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (docked); + GList *aux_info; + const gchar *nick; + GimpSessionInfoAux *aux; + + aux_info = parent_docked_iface->get_aux_info (docked); + + if (gimp_enum_get_value (GIMP_TYPE_COLOR_PICK_MODE, + GIMP_COLOR_FRAME (view->priv->color_frame_1)->pick_mode, + NULL, &nick, NULL, NULL)) + { + aux = gimp_session_info_aux_new (AUX_INFO_FRAME_1_MODE, nick); + aux_info = g_list_append (aux_info, aux); + } + + if (gimp_enum_get_value (GIMP_TYPE_COLOR_PICK_MODE, + GIMP_COLOR_FRAME (view->priv->color_frame_2)->pick_mode, + NULL, &nick, NULL, NULL)) + { + aux = gimp_session_info_aux_new (AUX_INFO_FRAME_2_MODE, nick); + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} + +static void +gimp_cursor_view_format_as_unit (GimpUnit unit, + gchar *output_buf, + gint output_buf_size, + gdouble pixel_value, + gdouble image_res) +{ + gchar format_buf[32]; + gdouble value; + gint unit_digits = 0; + const gchar *unit_str = ""; + + value = gimp_pixels_to_units (pixel_value, unit, image_res); + + if (unit != GIMP_UNIT_PIXEL) + { + unit_digits = gimp_unit_get_scaled_digits (unit, image_res); + unit_str = gimp_unit_get_abbreviation (unit); + } + + g_snprintf (format_buf, sizeof (format_buf), + "%%.%df %s", unit_digits, unit_str); + + g_snprintf (output_buf, output_buf_size, format_buf, value); +} + +static void +gimp_cursor_view_set_label_italic (GtkWidget *label, + gboolean italic) +{ + PangoStyle attribute = italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL; + + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, attribute, + -1); +} + +static void +gimp_cursor_view_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (widget); + gint content_spacing; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (GTK_WIDGET (view), + "content-spacing", &content_spacing, + NULL); + + gtk_box_set_spacing (GTK_BOX (view->priv->coord_hbox), content_spacing); + gtk_box_set_spacing (GTK_BOX (view->priv->selection_hbox), content_spacing); + gtk_box_set_spacing (GTK_BOX (view->priv->color_hbox), content_spacing); +} + +static void +gimp_cursor_view_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpCursorView *view = GIMP_CURSOR_VIEW (docked); + GimpColorConfig *config = NULL; + GimpDisplay *display = NULL; + GimpImage *image = NULL; + + if (context == view->priv->context) + return; + + if (view->priv->context) + { + g_signal_handlers_disconnect_by_func (view->priv->context, + gimp_cursor_view_diplay_changed, + view); + g_signal_handlers_disconnect_by_func (view->priv->context, + gimp_cursor_view_image_changed, + view); + + g_object_unref (view->priv->context); + } + + view->priv->context = context; + + if (view->priv->context) + { + g_object_ref (view->priv->context); + + g_signal_connect_swapped (view->priv->context, "display-changed", + G_CALLBACK (gimp_cursor_view_diplay_changed), + view); + + g_signal_connect_swapped (view->priv->context, "image-changed", + G_CALLBACK (gimp_cursor_view_image_changed), + view); + + config = context->gimp->config->color_management; + display = gimp_context_get_display (context); + image = gimp_context_get_image (context); + } + + gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (view->priv->color_frame_1), + config); + gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (view->priv->color_frame_2), + config); + + gimp_cursor_view_diplay_changed (view, display, view->priv->context); + gimp_cursor_view_image_changed (view, image, view->priv->context); +} + +static void +gimp_cursor_view_image_changed (GimpCursorView *view, + GimpImage *image, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_CURSOR_VIEW (view)); + + if (image == view->priv->image) + return; + + if (view->priv->image) + { + g_signal_handlers_disconnect_by_func (view->priv->image, + gimp_cursor_view_mask_changed, + view); + } + + view->priv->image = image; + + if (view->priv->image) + { + g_signal_connect_swapped (view->priv->image, "mask-changed", + G_CALLBACK (gimp_cursor_view_mask_changed), + view); + } + + gimp_cursor_view_mask_changed (view, view->priv->image); +} + +static void +gimp_cursor_view_mask_changed (GimpCursorView *view, + GimpImage *image) +{ + gimp_cursor_view_update_selection_info (view, + view->priv->image, + view->priv->unit); +} + +static void +gimp_cursor_view_diplay_changed (GimpCursorView *view, + GimpDisplay *display, + GimpContext *context) +{ + GimpDisplayShell *shell = NULL; + + if (display) + shell = gimp_display_get_shell (display); + + if (view->priv->shell) + { + g_signal_handlers_disconnect_by_func (view->priv->shell, + gimp_cursor_view_shell_unit_changed, + view); + } + + view->priv->shell = shell; + + if (view->priv->shell) + { + g_signal_connect_swapped (view->priv->shell, "notify::unit", + G_CALLBACK (gimp_cursor_view_shell_unit_changed), + view); + } + + gimp_cursor_view_shell_unit_changed (view, + NULL, + view->priv->shell); +} + +static void +gimp_cursor_view_shell_unit_changed (GimpCursorView *view, + GParamSpec *pspec, + GimpDisplayShell *shell) +{ + GimpUnit new_unit = GIMP_UNIT_PIXEL; + + if (shell) + { + new_unit = gimp_display_shell_get_unit (shell); + } + + if (view->priv->unit != new_unit) + { + gimp_cursor_view_update_selection_info (view, view->priv->image, new_unit); + view->priv->unit = new_unit; + } +} + +static void +gimp_cursor_view_update_selection_info (GimpCursorView *view, + GimpImage *image, + GimpUnit unit) +{ + gint x, y, width, height; + + if (image && + gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)), + &x, &y, &width, &height)) + { + gdouble xres, yres; + gchar buf[32]; + + gimp_image_get_resolution (image, &xres, &yres); + + gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), x, xres); + gtk_label_set_text (GTK_LABEL (view->priv->selection_x_label), buf); + + gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), y, yres); + gtk_label_set_text (GTK_LABEL (view->priv->selection_y_label), buf); + + gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), width, xres); + gtk_label_set_text (GTK_LABEL (view->priv->selection_width_label), buf); + + gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), height, yres); + gtk_label_set_text (GTK_LABEL (view->priv->selection_height_label), buf); + } + else + { + gtk_label_set_text (GTK_LABEL (view->priv->selection_x_label), + _("n/a")); + gtk_label_set_text (GTK_LABEL (view->priv->selection_y_label), + _("n/a")); + gtk_label_set_text (GTK_LABEL (view->priv->selection_width_label), + _("n/a")); + gtk_label_set_text (GTK_LABEL (view->priv->selection_height_label), + _("n/a")); + } +} + +static gboolean +gimp_cursor_view_cursor_idle (GimpCursorView *view) +{ + + if (view->priv->cursor_image) + { + GimpImage *image = view->priv->cursor_image; + GimpUnit unit = view->priv->cursor_unit; + gdouble x = view->priv->cursor_x; + gdouble y = view->priv->cursor_y; + gboolean in_image; + gchar buf[32]; + const Babl *sample_format; + gdouble pixel[4]; + GimpRGB color; + gdouble xres; + gdouble yres; + gint int_x; + gint int_y; + + if (unit == GIMP_UNIT_PIXEL) + unit = gimp_image_get_unit (image); + + gimp_image_get_resolution (image, &xres, &yres); + + in_image = (x >= 0.0 && x < gimp_image_get_width (image) && + y >= 0.0 && y < gimp_image_get_height (image)); + + g_snprintf (buf, sizeof (buf), "%d", (gint) floor (x)); + gtk_label_set_text (GTK_LABEL (view->priv->pixel_x_label), buf); + gimp_cursor_view_set_label_italic (view->priv->pixel_x_label, ! in_image); + + g_snprintf (buf, sizeof (buf), "%d", (gint) floor (y)); + gtk_label_set_text (GTK_LABEL (view->priv->pixel_y_label), buf); + gimp_cursor_view_set_label_italic (view->priv->pixel_y_label, ! in_image); + + gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), x, xres); + gtk_label_set_text (GTK_LABEL (view->priv->unit_x_label), buf); + gimp_cursor_view_set_label_italic (view->priv->unit_x_label, ! in_image); + + gimp_cursor_view_format_as_unit (unit, buf, sizeof (buf), y, yres); + gtk_label_set_text (GTK_LABEL (view->priv->unit_y_label), buf); + gimp_cursor_view_set_label_italic (view->priv->unit_y_label, ! in_image); + + int_x = (gint) floor (x); + int_y = (gint) floor (y); + + if (gimp_image_pick_color (image, NULL, + int_x, int_y, + view->priv->shell->show_all, + view->priv->sample_merged, + FALSE, 0.0, + &sample_format, pixel, &color)) + { + gimp_color_frame_set_color (GIMP_COLOR_FRAME (view->priv->color_frame_1), + FALSE, sample_format, pixel, &color, + int_x, int_y); + gimp_color_frame_set_color (GIMP_COLOR_FRAME (view->priv->color_frame_2), + FALSE, sample_format, pixel, &color, + int_x, int_y); + } + else + { + gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_1)); + gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_2)); + } + + /* Show the selection info from the image under the cursor if any */ + gimp_cursor_view_update_selection_info (view, + image, + view->priv->cursor_unit); + + g_clear_object (&view->priv->cursor_image); + } + else + { + gtk_label_set_text (GTK_LABEL (view->priv->pixel_x_label), _("n/a")); + gtk_label_set_text (GTK_LABEL (view->priv->pixel_y_label), _("n/a")); + gtk_label_set_text (GTK_LABEL (view->priv->unit_x_label), _("n/a")); + gtk_label_set_text (GTK_LABEL (view->priv->unit_y_label), _("n/a")); + + gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_1)); + gimp_color_frame_set_invalid (GIMP_COLOR_FRAME (view->priv->color_frame_2)); + + /* Start showing selection info from the active image again */ + gimp_cursor_view_update_selection_info (view, + view->priv->image, + view->priv->unit); + } + + view->priv->cursor_idle_id = 0; + + return G_SOURCE_REMOVE; +} + + +/* public functions */ + +GtkWidget * +gimp_cursor_view_new (GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_CURSOR_VIEW, + "menu-factory", menu_factory, + "menu-identifier", "<CursorInfo>", + "ui-path", "/cursor-info-popup", + NULL); +} + +void +gimp_cursor_view_set_sample_merged (GimpCursorView *view, + gboolean sample_merged) +{ + g_return_if_fail (GIMP_IS_CURSOR_VIEW (view)); + + sample_merged = sample_merged ? TRUE : FALSE; + + if (view->priv->sample_merged != sample_merged) + { + view->priv->sample_merged = sample_merged; + + g_object_notify (G_OBJECT (view), "sample-merged"); + } +} + +gboolean +gimp_cursor_view_get_sample_merged (GimpCursorView *view) +{ + g_return_val_if_fail (GIMP_IS_CURSOR_VIEW (view), FALSE); + + return view->priv->sample_merged; +} + +void +gimp_cursor_view_update_cursor (GimpCursorView *view, + GimpImage *image, + GimpUnit shell_unit, + gdouble x, + gdouble y) +{ + g_return_if_fail (GIMP_IS_CURSOR_VIEW (view)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + g_clear_object (&view->priv->cursor_image); + + view->priv->cursor_image = g_object_ref (image); + view->priv->cursor_unit = shell_unit; + view->priv->cursor_x = x; + view->priv->cursor_y = y; + + if (view->priv->cursor_idle_id == 0) + { + view->priv->cursor_idle_id = + g_idle_add ((GSourceFunc) gimp_cursor_view_cursor_idle, view); + } +} + +void +gimp_cursor_view_clear_cursor (GimpCursorView *view) +{ + g_return_if_fail (GIMP_IS_CURSOR_VIEW (view)); + + g_clear_object (&view->priv->cursor_image); + + if (view->priv->cursor_idle_id == 0) + { + view->priv->cursor_idle_id = + g_idle_add ((GSourceFunc) gimp_cursor_view_cursor_idle, view); + } +} diff --git a/app/display/gimpcursorview.h b/app/display/gimpcursorview.h new file mode 100644 index 0000000..f538041 --- /dev/null +++ b/app/display/gimpcursorview.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcursorview.h + * Copyright (C) 2005-2016 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_CURSOR_VIEW_H__ +#define __GIMP_CURSOR_VIEW_H__ + + +#include "widgets/gimpeditor.h" + + +#define GIMP_TYPE_CURSOR_VIEW (gimp_cursor_view_get_type ()) +#define GIMP_CURSOR_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CURSOR_VIEW, GimpCursorView)) +#define GIMP_CURSOR_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CURSOR_VIEW, GimpCursorViewClass)) +#define GIMP_IS_CURSOR_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CURSOR_VIEW)) +#define GIMP_IS_CURSOR_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CURSOR_VIEW)) +#define GIMP_CURSOR_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CURSOR_VIEW, GimpCursorViewClass)) + + +typedef struct _GimpCursorViewClass GimpCursorViewClass; +typedef struct _GimpCursorViewPrivate GimpCursorViewPrivate; + +struct _GimpCursorView +{ + GimpEditor parent_instance; + + GimpCursorViewPrivate *priv; +}; + +struct _GimpCursorViewClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_cursor_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_cursor_view_new (GimpMenuFactory *menu_factory); + +void gimp_cursor_view_set_sample_merged (GimpCursorView *view, + gboolean sample_merged); +gboolean gimp_cursor_view_get_sample_merged (GimpCursorView *view); + +void gimp_cursor_view_update_cursor (GimpCursorView *view, + GimpImage *image, + GimpUnit shell_unit, + gdouble x, + gdouble y); +void gimp_cursor_view_clear_cursor (GimpCursorView *view); + + +#endif /* __GIMP_CURSOR_VIEW_H__ */ diff --git a/app/display/gimpdisplay-foreach.c b/app/display/gimpdisplay-foreach.c new file mode 100644 index 0000000..440113e --- /dev/null +++ b/app/display/gimpdisplay-foreach.c @@ -0,0 +1,308 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimplist.h" + +#include "gimpdisplay.h" +#include "gimpdisplay-foreach.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-cursor.h" + + +gboolean +gimp_displays_dirty (Gimp *gimp) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + for (list = gimp_get_display_iter (gimp); + list; + list = g_list_next (list)) + { + GimpDisplay *display = list->data; + GimpImage *image = gimp_display_get_image (display); + + if (image && gimp_image_is_dirty (image)) + return TRUE; + } + + return FALSE; +} + +static void +gimp_displays_image_dirty_callback (GimpImage *image, + GimpDirtyMask dirty_mask, + GimpContainer *container) +{ + if (gimp_image_is_dirty (image) && + gimp_image_get_display_count (image) > 0 && + ! gimp_container_have (container, GIMP_OBJECT (image))) + gimp_container_add (container, GIMP_OBJECT (image)); +} + +static void +gimp_displays_dirty_images_disconnect (GimpContainer *dirty_container, + GimpContainer *global_container) +{ + GQuark handler; + + handler = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dirty_container), + "clean-handler")); + gimp_container_remove_handler (global_container, handler); + + handler = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dirty_container), + "dirty-handler")); + gimp_container_remove_handler (global_container, handler); +} + +static void +gimp_displays_image_clean_callback (GimpImage *image, + GimpDirtyMask dirty_mask, + GimpContainer *container) +{ + if (! gimp_image_is_dirty (image)) + gimp_container_remove (container, GIMP_OBJECT (image)); +} + +GimpContainer * +gimp_displays_get_dirty_images (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + if (gimp_displays_dirty (gimp)) + { + GimpContainer *container = gimp_list_new_weak (GIMP_TYPE_IMAGE, FALSE); + GList *list; + GQuark handler; + + handler = + gimp_container_add_handler (gimp->images, "clean", + G_CALLBACK (gimp_displays_image_dirty_callback), + container); + g_object_set_data (G_OBJECT (container), "clean-handler", + GINT_TO_POINTER (handler)); + + handler = + gimp_container_add_handler (gimp->images, "dirty", + G_CALLBACK (gimp_displays_image_dirty_callback), + container); + g_object_set_data (G_OBJECT (container), "dirty-handler", + GINT_TO_POINTER (handler)); + + g_signal_connect_object (container, "disconnect", + G_CALLBACK (gimp_displays_dirty_images_disconnect), + G_OBJECT (gimp->images), 0); + + gimp_container_add_handler (container, "clean", + G_CALLBACK (gimp_displays_image_clean_callback), + container); + gimp_container_add_handler (container, "dirty", + G_CALLBACK (gimp_displays_image_clean_callback), + container); + + for (list = gimp_get_image_iter (gimp); + list; + list = g_list_next (list)) + { + GimpImage *image = list->data; + + if (gimp_image_is_dirty (image) && + gimp_image_get_display_count (image) > 0) + gimp_container_add (container, GIMP_OBJECT (image)); + } + + return container; + } + + return NULL; +} + +/** + * gimp_displays_delete: + * @gimp: + * + * Calls gimp_display_delete() an all displays in the display list. + * This closes all displays, including the first one which is usually + * kept open. + */ +void +gimp_displays_delete (Gimp *gimp) +{ + /* this removes the GimpDisplay from the list, so do a while loop + * "around" the first element to get them all + */ + while (! gimp_container_is_empty (gimp->displays)) + { + GimpDisplay *display = gimp_get_display_iter (gimp)->data; + + gimp_display_delete (display); + } +} + +/** + * gimp_displays_close: + * @gimp: + * + * Calls gimp_display_close() an all displays in the display list. The + * first display will remain open without an image. + */ +void +gimp_displays_close (Gimp *gimp) +{ + GList *list; + GList *iter; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + list = g_list_copy (gimp_get_display_iter (gimp)); + + for (iter = list; iter; iter = g_list_next (iter)) + { + GimpDisplay *display = iter->data; + + gimp_display_close (display); + } + + g_list_free (list); +} + +void +gimp_displays_reconnect (Gimp *gimp, + GimpImage *old, + GimpImage *new) +{ + GList *contexts = NULL; + GList *list; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (GIMP_IS_IMAGE (old)); + g_return_if_fail (GIMP_IS_IMAGE (new)); + + /* check which contexts refer to old_image */ + for (list = gimp->context_list; list; list = g_list_next (list)) + { + GimpContext *context = list->data; + + if (gimp_context_get_image (context) == old) + contexts = g_list_prepend (contexts, list->data); + } + + /* set the new_image on the remembered contexts (in reverse order, + * since older contexts are usually the parents of newer + * ones). Also, update the contexts before the displays, or we + * might run into menu update functions that would see an + * inconsistent state (display = new, context = old), and thus + * inadvertently call actions as if the user had selected a menu + * item. + */ + g_list_foreach (contexts, (GFunc) gimp_context_set_image, new); + g_list_free (contexts); + + for (list = gimp_get_display_iter (gimp); + list; + list = g_list_next (list)) + { + GimpDisplay *display = list->data; + + if (gimp_display_get_image (display) == old) + gimp_display_set_image (display, new); + } +} + +gint +gimp_displays_get_num_visible (Gimp *gimp) +{ + GList *list; + gint visible = 0; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), 0); + + for (list = gimp_get_display_iter (gimp); + list; + list = g_list_next (list)) + { + GimpDisplay *display = list->data; + GimpDisplayShell *shell = gimp_display_get_shell (display); + + if (gtk_widget_is_drawable (GTK_WIDGET (shell))) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell)); + + if (GTK_IS_WINDOW (toplevel)) + { + GdkWindow *window = gtk_widget_get_window (toplevel); + GdkWindowState state = gdk_window_get_state (window); + + if ((state & (GDK_WINDOW_STATE_WITHDRAWN | + GDK_WINDOW_STATE_ICONIFIED)) == 0) + { + visible++; + } + } + } + } + + return visible; +} + +void +gimp_displays_set_busy (Gimp *gimp) +{ + GList *list; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + for (list = gimp_get_display_iter (gimp); + list; + list = g_list_next (list)) + { + GimpDisplayShell *shell = + gimp_display_get_shell (GIMP_DISPLAY (list->data)); + + gimp_display_shell_set_override_cursor (shell, (GimpCursorType) GDK_WATCH); + } +} + +void +gimp_displays_unset_busy (Gimp *gimp) +{ + GList *list; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + for (list = gimp_get_display_iter (gimp); + list; + list = g_list_next (list)) + { + GimpDisplayShell *shell = + gimp_display_get_shell (GIMP_DISPLAY (list->data)); + + gimp_display_shell_unset_override_cursor (shell); + } +} diff --git a/app/display/gimpdisplay-foreach.h b/app/display/gimpdisplay-foreach.h new file mode 100644 index 0000000..09fae7c --- /dev/null +++ b/app/display/gimpdisplay-foreach.h @@ -0,0 +1,36 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_FOREACH_H__ +#define __GIMP_DISPLAY_FOREACH_H__ + + +gboolean gimp_displays_dirty (Gimp *gimp); +GimpContainer * gimp_displays_get_dirty_images (Gimp *gimp); +void gimp_displays_delete (Gimp *gimp); +void gimp_displays_close (Gimp *gimp); +void gimp_displays_reconnect (Gimp *gimp, + GimpImage *old, + GimpImage *new); + +gint gimp_displays_get_num_visible (Gimp *gimp); + +void gimp_displays_set_busy (Gimp *gimp); +void gimp_displays_unset_busy (Gimp *gimp); + + +#endif /* __GIMP_DISPLAY_FOREACH_H__ */ diff --git a/app/display/gimpdisplay-handlers.c b/app/display/gimpdisplay-handlers.c new file mode 100644 index 0000000..94cb863 --- /dev/null +++ b/app/display/gimpdisplay-handlers.c @@ -0,0 +1,128 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimpimage.h" + +#include "gimpdisplay.h" +#include "gimpdisplay-handlers.h" + + +/* local function prototypes */ + +static void gimp_display_update_handler (GimpProjection *projection, + gboolean now, + gint x, + gint y, + gint w, + gint h, + GimpDisplay *display); + +static void gimp_display_bounds_changed_handler (GimpImage *image, + gint old_x, + gint old_y, + GimpDisplay *display); +static void gimp_display_flush_handler (GimpImage *image, + gboolean invalidate_preview, + GimpDisplay *display); + + +/* public functions */ + +void +gimp_display_connect (GimpDisplay *display) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + image = gimp_display_get_image (display); + + g_return_if_fail (GIMP_IS_IMAGE (image)); + + g_signal_connect (gimp_image_get_projection (image), "update", + G_CALLBACK (gimp_display_update_handler), + display); + + g_signal_connect (image, "bounds-changed", + G_CALLBACK (gimp_display_bounds_changed_handler), + display); + g_signal_connect (image, "flush", + G_CALLBACK (gimp_display_flush_handler), + display); +} + +void +gimp_display_disconnect (GimpDisplay *display) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + image = gimp_display_get_image (display); + + g_return_if_fail (GIMP_IS_IMAGE (image)); + + g_signal_handlers_disconnect_by_func (image, + gimp_display_flush_handler, + display); + g_signal_handlers_disconnect_by_func (image, + gimp_display_bounds_changed_handler, + display); + + g_signal_handlers_disconnect_by_func (gimp_image_get_projection (image), + gimp_display_update_handler, + display); +} + + +/* private functions */ + +static void +gimp_display_update_handler (GimpProjection *projection, + gboolean now, + gint x, + gint y, + gint w, + gint h, + GimpDisplay *display) +{ + gimp_display_update_area (display, now, x, y, w, h); +} + +static void +gimp_display_bounds_changed_handler (GimpImage *image, + gint old_x, + gint old_y, + GimpDisplay *display) +{ + gimp_display_update_bounding_box (display); +} + +static void +gimp_display_flush_handler (GimpImage *image, + gboolean invalidate_preview, + GimpDisplay *display) +{ + gimp_display_flush (display); +} diff --git a/app/display/gimpdisplay-handlers.h b/app/display/gimpdisplay-handlers.h new file mode 100644 index 0000000..34c547c --- /dev/null +++ b/app/display/gimpdisplay-handlers.h @@ -0,0 +1,26 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_HANDLERS_H__ +#define __GIMP_DISPLAY_HANDLERS_H__ + + +void gimp_display_connect (GimpDisplay *display); +void gimp_display_disconnect (GimpDisplay *display); + + +#endif /* __GIMP_DISPLAY_HANDLERS_H__ */ diff --git a/app/display/gimpdisplay.c b/app/display/gimpdisplay.c new file mode 100644 index 0000000..bf8a238 --- /dev/null +++ b/app/display/gimpdisplay.c @@ -0,0 +1,985 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" +#include "tools/tools-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpprogress.h" + +#include "widgets/gimpdialogfactory.h" + +#include "tools/gimptool.h" +#include "tools/tool_manager.h" + +#include "gimpdisplay.h" +#include "gimpdisplay-handlers.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-handlers.h" +#include "gimpdisplayshell-icon.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-scrollbars.h" +#include "gimpdisplayshell-title.h" +#include "gimpdisplayshell-transform.h" +#include "gimpimagewindow.h" + +#include "gimp-intl.h" + + +#define FLUSH_NOW_INTERVAL (G_TIME_SPAN_SECOND / 60) + +#define PAINT_AREA_CHUNK_WIDTH 32 +#define PAINT_AREA_CHUNK_HEIGHT 32 + + +enum +{ + PROP_0, + PROP_ID, + PROP_GIMP, + PROP_IMAGE, + PROP_SHELL +}; + + +typedef struct _GimpDisplayPrivate GimpDisplayPrivate; + +struct _GimpDisplayPrivate +{ + gint ID; /* unique identifier for this display */ + + GimpImage *image; /* pointer to the associated image */ + gint instance; /* the instance # of this display as + * taken from the image at creation */ + + GeglRectangle bounding_box; + + GtkWidget *shell; + cairo_region_t *update_region; + + guint64 last_flush_now; +}; + +#define GIMP_DISPLAY_GET_PRIVATE(display) \ + ((GimpDisplayPrivate *) gimp_display_get_instance_private ((GimpDisplay *) (display))) + + +/* local function prototypes */ + +static void gimp_display_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_display_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_display_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static GimpProgress * gimp_display_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_display_progress_end (GimpProgress *progress); +static gboolean gimp_display_progress_is_active (GimpProgress *progress); +static void gimp_display_progress_set_text (GimpProgress *progress, + const gchar *message); +static void gimp_display_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_display_progress_get_value (GimpProgress *progress); +static void gimp_display_progress_pulse (GimpProgress *progress); +static guint32 gimp_display_progress_get_window_id (GimpProgress *progress); +static gboolean gimp_display_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message); +static void gimp_display_progress_canceled (GimpProgress *progress, + GimpDisplay *display); + +static void gimp_display_flush_whenever (GimpDisplay *display, + gboolean now); +static void gimp_display_paint_area (GimpDisplay *display, + gint x, + gint y, + gint w, + gint h); + + +G_DEFINE_TYPE_WITH_CODE (GimpDisplay, gimp_display, GIMP_TYPE_OBJECT, + G_ADD_PRIVATE (GimpDisplay) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_display_progress_iface_init)) + +#define parent_class gimp_display_parent_class + + +static void +gimp_display_class_init (GimpDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gimp_display_set_property; + object_class->get_property = gimp_display_get_property; + + g_object_class_install_property (object_class, PROP_ID, + g_param_spec_int ("id", + NULL, NULL, + 0, G_MAXINT, 0, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", + NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_SHELL, + g_param_spec_object ("shell", + NULL, NULL, + GIMP_TYPE_DISPLAY_SHELL, + GIMP_PARAM_READABLE)); +} + +static void +gimp_display_init (GimpDisplay *display) +{ +} + +static void +gimp_display_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_display_progress_start; + iface->end = gimp_display_progress_end; + iface->is_active = gimp_display_progress_is_active; + iface->set_text = gimp_display_progress_set_text; + iface->set_value = gimp_display_progress_set_value; + iface->get_value = gimp_display_progress_get_value; + iface->pulse = gimp_display_progress_pulse; + iface->get_window_id = gimp_display_progress_get_window_id; + iface->message = gimp_display_progress_message; +} + +static void +gimp_display_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDisplay *display = GIMP_DISPLAY (object); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + switch (property_id) + { + case PROP_GIMP: + { + gint ID; + + display->gimp = g_value_get_object (value); /* don't ref the gimp */ + display->config = GIMP_DISPLAY_CONFIG (display->gimp->config); + + do + { + ID = display->gimp->next_display_ID++; + + if (display->gimp->next_display_ID == G_MAXINT) + display->gimp->next_display_ID = 1; + } + while (gimp_display_get_by_ID (display->gimp, ID)); + + private->ID = ID; + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_display_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDisplay *display = GIMP_DISPLAY (object); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + switch (property_id) + { + case PROP_ID: + g_value_set_int (value, private->ID); + break; + + case PROP_GIMP: + g_value_set_object (value, display->gimp); + break; + + case PROP_IMAGE: + g_value_set_object (value, private->image); + break; + + case PROP_SHELL: + g_value_set_object (value, private->shell); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GimpProgress * +gimp_display_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + return gimp_progress_start (GIMP_PROGRESS (private->shell), cancellable, + "%s", message); + + return NULL; +} + +static void +gimp_display_progress_end (GimpProgress *progress) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + gimp_progress_end (GIMP_PROGRESS (private->shell)); +} + +static gboolean +gimp_display_progress_is_active (GimpProgress *progress) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + return gimp_progress_is_active (GIMP_PROGRESS (private->shell)); + + return FALSE; +} + +static void +gimp_display_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + gimp_progress_set_text_literal (GIMP_PROGRESS (private->shell), message); +} + +static void +gimp_display_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + gimp_progress_set_value (GIMP_PROGRESS (private->shell), percentage); +} + +static gdouble +gimp_display_progress_get_value (GimpProgress *progress) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + return gimp_progress_get_value (GIMP_PROGRESS (private->shell)); + + return 0.0; +} + +static void +gimp_display_progress_pulse (GimpProgress *progress) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + gimp_progress_pulse (GIMP_PROGRESS (private->shell)); +} + +static guint32 +gimp_display_progress_get_window_id (GimpProgress *progress) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + return gimp_progress_get_window_id (GIMP_PROGRESS (private->shell)); + + return 0; +} + +static gboolean +gimp_display_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) +{ + GimpDisplay *display = GIMP_DISPLAY (progress); + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->shell) + return gimp_progress_message (GIMP_PROGRESS (private->shell), gimp, + severity, domain, message); + + return FALSE; +} + +static void +gimp_display_progress_canceled (GimpProgress *progress, + GimpDisplay *display) +{ + gimp_progress_cancel (GIMP_PROGRESS (display)); +} + + +/* public functions */ + +GimpDisplay * +gimp_display_new (Gimp *gimp, + GimpImage *image, + GimpUnit unit, + gdouble scale, + GimpUIManager *popup_manager, + GimpDialogFactory *dialog_factory, + GdkScreen *screen, + gint monitor) +{ + GimpDisplay *display; + GimpDisplayPrivate *private; + GimpImageWindow *window = NULL; + GimpDisplayShell *shell; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + /* If there isn't an interface, never create a display */ + if (gimp->no_interface) + return NULL; + + display = g_object_new (GIMP_TYPE_DISPLAY, + "gimp", gimp, + NULL); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + /* refs the image */ + if (image) + gimp_display_set_image (display, image); + + /* get an image window */ + if (GIMP_GUI_CONFIG (display->config)->single_window_mode) + { + GimpDisplay *active_display; + + active_display = gimp_context_get_display (gimp_get_user_context (gimp)); + + if (! active_display) + { + active_display = + GIMP_DISPLAY (gimp_container_get_first_child (gimp->displays)); + } + + if (active_display) + { + GimpDisplayShell *shell = gimp_display_get_shell (active_display); + + window = gimp_display_shell_get_window (shell); + } + } + + if (! window) + { + window = gimp_image_window_new (gimp, + private->image, + dialog_factory, + screen, + monitor); + } + + /* create the shell for the image */ + private->shell = gimp_display_shell_new (display, unit, scale, + popup_manager, + screen, + monitor); + + shell = gimp_display_get_shell (display); + + gimp_display_update_bounding_box (display); + + gimp_image_window_add_shell (window, shell); + gimp_display_shell_present (shell); + + /* make sure the docks are visible, in case all other image windows + * are iconified, see bug #686544. + */ + gimp_dialog_factory_show_with_display (dialog_factory); + + g_signal_connect (gimp_display_shell_get_statusbar (shell), "cancel", + G_CALLBACK (gimp_display_progress_canceled), + display); + + /* add the display to the list */ + gimp_container_add (gimp->displays, GIMP_OBJECT (display)); + + return display; +} + +/** + * gimp_display_delete: + * @display: + * + * Closes the display and removes it from the display list. You should + * not call this function directly, use gimp_display_close() instead. + */ +void +gimp_display_delete (GimpDisplay *display) +{ + GimpDisplayPrivate *private; + GimpTool *active_tool; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + /* remove the display from the list */ + gimp_container_remove (display->gimp->displays, GIMP_OBJECT (display)); + + /* unrefs the image */ + gimp_display_set_image (display, NULL); + + active_tool = tool_manager_get_active (display->gimp); + + if (active_tool && active_tool->focus_display == display) + tool_manager_focus_display_active (display->gimp, NULL); + + if (private->shell) + { + GimpDisplayShell *shell = gimp_display_get_shell (display); + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + /* set private->shell to NULL *before* destroying the shell. + * all callbacks in gimpdisplayshell-callbacks.c will check + * this pointer and do nothing if the shell is in destruction. + */ + private->shell = NULL; + + if (window) + { + if (gimp_image_window_get_n_shells (window) > 1) + { + g_object_ref (shell); + + gimp_image_window_remove_shell (window, shell); + gtk_widget_destroy (GTK_WIDGET (shell)); + + g_object_unref (shell); + } + else + { + gimp_image_window_destroy (window); + } + } + else + { + g_object_unref (shell); + } + } + + g_object_unref (display); +} + +/** + * gimp_display_close: + * @display: + * + * Closes the display. If this is the last display, it will remain + * open, but without an image. + */ +void +gimp_display_close (GimpDisplay *display) +{ + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + if (gimp_container_get_n_children (display->gimp->displays) > 1) + { + gimp_display_delete (display); + } + else + { + gimp_display_empty (display); + } +} + +gint +gimp_display_get_ID (GimpDisplay *display) +{ + GimpDisplayPrivate *private; + + g_return_val_if_fail (GIMP_IS_DISPLAY (display), -1); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + return private->ID; +} + +GimpDisplay * +gimp_display_get_by_ID (Gimp *gimp, + gint ID) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + for (list = gimp_get_display_iter (gimp); + list; + list = g_list_next (list)) + { + GimpDisplay *display = list->data; + + if (gimp_display_get_ID (display) == ID) + return display; + } + + return NULL; +} + +/** + * gimp_display_get_action_name: + * @display: + * + * Returns: The action name for the given display. The action name + * depends on the display ID. The result must be freed with g_free(). + **/ +gchar * +gimp_display_get_action_name (GimpDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL); + + return g_strdup_printf ("windows-display-%04d", + gimp_display_get_ID (display)); +} + +Gimp * +gimp_display_get_gimp (GimpDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL); + + return display->gimp; +} + +GimpImage * +gimp_display_get_image (GimpDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL); + + return GIMP_DISPLAY_GET_PRIVATE (display)->image; +} + +void +gimp_display_set_image (GimpDisplay *display, + GimpImage *image) +{ + GimpDisplayPrivate *private; + GimpImage *old_image = NULL; + GimpDisplayShell *shell; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + shell = gimp_display_get_shell (display); + + if (private->image) + { + /* stop any active tool */ + tool_manager_control_active (display->gimp, GIMP_TOOL_ACTION_HALT, + display); + + gimp_display_shell_disconnect (shell); + + gimp_display_disconnect (display); + + g_clear_pointer (&private->update_region, cairo_region_destroy); + + gimp_image_dec_display_count (private->image); + + /* set private->image before unrefing because there may be code + * that listens for image removals and then iterates the + * display list to find a valid display. + */ + old_image = private->image; + +#if 0 + g_print ("%s: image->ref_count before unrefing: %d\n", + G_STRFUNC, G_OBJECT (old_image)->ref_count); +#endif + } + + private->image = image; + + if (image) + { +#if 0 + g_print ("%s: image->ref_count before refing: %d\n", + G_STRFUNC, G_OBJECT (image)->ref_count); +#endif + + g_object_ref (image); + + private->instance = gimp_image_get_instance_count (image); + gimp_image_inc_instance_count (image); + + gimp_image_inc_display_count (image); + + gimp_display_connect (display); + + if (shell) + gimp_display_shell_connect (shell); + } + + if (old_image) + g_object_unref (old_image); + + gimp_display_update_bounding_box (display); + + if (shell) + { + if (image) + { + gimp_display_shell_reconnect (shell); + } + else + { + gimp_display_shell_title_update (shell); + gimp_display_shell_icon_update (shell); + } + } + + if (old_image != image) + g_object_notify (G_OBJECT (display), "image"); +} + +gint +gimp_display_get_instance (GimpDisplay *display) +{ + GimpDisplayPrivate *private; + + g_return_val_if_fail (GIMP_IS_DISPLAY (display), 0); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + return private->instance; +} + +GimpDisplayShell * +gimp_display_get_shell (GimpDisplay *display) +{ + GimpDisplayPrivate *private; + + g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + return GIMP_DISPLAY_SHELL (private->shell); +} + +void +gimp_display_empty (GimpDisplay *display) +{ + GimpDisplayPrivate *private; + GList *iter; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + g_return_if_fail (GIMP_IS_IMAGE (private->image)); + + for (iter = display->gimp->context_list; iter; iter = g_list_next (iter)) + { + GimpContext *context = iter->data; + + if (gimp_context_get_display (context) == display) + gimp_context_set_image (context, NULL); + } + + gimp_display_set_image (display, NULL); + + gimp_display_shell_empty (gimp_display_get_shell (display)); +} + +void +gimp_display_fill (GimpDisplay *display, + GimpImage *image, + GimpUnit unit, + gdouble scale) +{ + GimpDisplayPrivate *private; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + g_return_if_fail (private->image == NULL); + + gimp_display_set_image (display, image); + + gimp_display_shell_fill (gimp_display_get_shell (display), + image, unit, scale); +} + +void +gimp_display_update_bounding_box (GimpDisplay *display) +{ + GimpDisplayPrivate *private; + GimpDisplayShell *shell; + GeglRectangle bounding_box = {}; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + shell = gimp_display_get_shell (display); + + if (shell) + { + bounding_box = gimp_display_shell_get_bounding_box (shell); + + if (! gegl_rectangle_equal (&bounding_box, &private->bounding_box)) + { + GeglRectangle diff_rects[4]; + gint n_diff_rects; + gint i; + + n_diff_rects = gegl_rectangle_subtract (diff_rects, + &private->bounding_box, + &bounding_box); + + for (i = 0; i < n_diff_rects; i++) + { + gimp_display_paint_area (display, + diff_rects[i].x, diff_rects[i].y, + diff_rects[i].width, diff_rects[i].height); + } + + private->bounding_box = bounding_box; + + gimp_display_shell_scroll_clamp_and_update (shell); + gimp_display_shell_scrollbars_update (shell); + } + } + else + { + private->bounding_box = bounding_box; + } +} + +void +gimp_display_update_area (GimpDisplay *display, + gboolean now, + gint x, + gint y, + gint w, + gint h) +{ + GimpDisplayPrivate *private; + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (now) + { + gimp_display_paint_area (display, x, y, w, h); + } + else + { + cairo_rectangle_int_t rect; + gint image_width; + gint image_height; + + image_width = gimp_image_get_width (private->image); + image_height = gimp_image_get_height (private->image); + + rect.x = CLAMP (x, 0, image_width); + rect.y = CLAMP (y, 0, image_height); + rect.width = CLAMP (x + w, 0, image_width) - rect.x; + rect.height = CLAMP (y + h, 0, image_height) - rect.y; + + if (private->update_region) + cairo_region_union_rectangle (private->update_region, &rect); + else + private->update_region = cairo_region_create_rectangle (&rect); + } +} + +void +gimp_display_flush (GimpDisplay *display) +{ + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + /* FIXME: we can end up being called during shell construction if "show all" + * is enabled by default, in which case the shell's display pointer is still + * NULL + */ + if (gimp_display_get_shell (display)) + gimp_display_flush_whenever (display, FALSE); +} + +void +gimp_display_flush_now (GimpDisplay *display) +{ + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + gimp_display_flush_whenever (display, TRUE); +} + + +/* private functions */ + +static void +gimp_display_flush_whenever (GimpDisplay *display, + gboolean now) +{ + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + + if (private->update_region) + { + gint n_rects = cairo_region_num_rectangles (private->update_region); + gint i; + + for (i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + + cairo_region_get_rectangle (private->update_region, + i, &rect); + + gimp_display_paint_area (display, + rect.x, + rect.y, + rect.width, + rect.height); + } + + g_clear_pointer (&private->update_region, cairo_region_destroy); + } + + if (now) + { + guint64 now = g_get_monotonic_time (); + + if ((now - private->last_flush_now) > FLUSH_NOW_INTERVAL) + { + gimp_display_shell_flush (gimp_display_get_shell (display), TRUE); + + private->last_flush_now = now; + } + } + else + { + gimp_display_shell_flush (gimp_display_get_shell (display), now); + } +} + +static void +gimp_display_paint_area (GimpDisplay *display, + gint x, + gint y, + gint w, + gint h) +{ + GimpDisplayPrivate *private = GIMP_DISPLAY_GET_PRIVATE (display); + GimpDisplayShell *shell = gimp_display_get_shell (display); + GeglRectangle rect; + gint x1, y1, x2, y2; + gdouble x1_f, y1_f, x2_f, y2_f; + + if (! gegl_rectangle_intersect (&rect, + &private->bounding_box, + GEGL_RECTANGLE (x, y, w, h))) + { + return; + } + + /* display the area */ + gimp_display_shell_transform_bounds (shell, + rect.x, + rect.y, + rect.x + rect.width, + rect.y + rect.height, + &x1_f, &y1_f, &x2_f, &y2_f); + + /* make sure to expose a superset of the transformed sub-pixel expose + * area, not a subset. bug #126942. --mitch + * + * also accommodate for spill introduced by potential box filtering. + * (bug #474509). --simon + */ + x1 = floor (x1_f - 0.5); + y1 = floor (y1_f - 0.5); + x2 = ceil (x2_f + 0.5); + y2 = ceil (y2_f + 0.5); + + /* align transformed area to a coarse grid, to simplify the + * invalidated area + */ + x1 = floor ((gdouble) x1 / PAINT_AREA_CHUNK_WIDTH) * PAINT_AREA_CHUNK_WIDTH; + y1 = floor ((gdouble) y1 / PAINT_AREA_CHUNK_HEIGHT) * PAINT_AREA_CHUNK_HEIGHT; + x2 = ceil ((gdouble) x2 / PAINT_AREA_CHUNK_WIDTH) * PAINT_AREA_CHUNK_WIDTH; + y2 = ceil ((gdouble) y2 / PAINT_AREA_CHUNK_HEIGHT) * PAINT_AREA_CHUNK_HEIGHT; + + gimp_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1); +} diff --git a/app/display/gimpdisplay.h b/app/display/gimpdisplay.h new file mode 100644 index 0000000..7e676b0 --- /dev/null +++ b/app/display/gimpdisplay.h @@ -0,0 +1,99 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_H__ +#define __GIMP_DISPLAY_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_DISPLAY (gimp_display_get_type ()) +#define GIMP_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DISPLAY, GimpDisplay)) +#define GIMP_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DISPLAY, GimpDisplayClass)) +#define GIMP_IS_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DISPLAY)) +#define GIMP_IS_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DISPLAY)) +#define GIMP_DISPLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DISPLAY, GimpDisplayClass)) + + +typedef struct _GimpDisplayClass GimpDisplayClass; + +struct _GimpDisplay +{ + GimpObject parent_instance; + + Gimp *gimp; + GimpDisplayConfig *config; + +}; + +struct _GimpDisplayClass +{ + GimpObjectClass parent_class; +}; + + +GType gimp_display_get_type (void) G_GNUC_CONST; + +GimpDisplay * gimp_display_new (Gimp *gimp, + GimpImage *image, + GimpUnit unit, + gdouble scale, + GimpUIManager *popup_manager, + GimpDialogFactory *dialog_factory, + GdkScreen *screen, + gint monitor); +void gimp_display_delete (GimpDisplay *display); +void gimp_display_close (GimpDisplay *display); + +gint gimp_display_get_ID (GimpDisplay *display); +GimpDisplay * gimp_display_get_by_ID (Gimp *gimp, + gint ID); + +gchar * gimp_display_get_action_name (GimpDisplay *display); + +Gimp * gimp_display_get_gimp (GimpDisplay *display); + +GimpImage * gimp_display_get_image (GimpDisplay *display); +void gimp_display_set_image (GimpDisplay *display, + GimpImage *image); + +gint gimp_display_get_instance (GimpDisplay *display); + +GimpDisplayShell * gimp_display_get_shell (GimpDisplay *display); + +void gimp_display_empty (GimpDisplay *display); +void gimp_display_fill (GimpDisplay *display, + GimpImage *image, + GimpUnit unit, + gdouble scale); + +void gimp_display_update_bounding_box + (GimpDisplay *display); + +void gimp_display_update_area (GimpDisplay *display, + gboolean now, + gint x, + gint y, + gint w, + gint h); + +void gimp_display_flush (GimpDisplay *display); +void gimp_display_flush_now (GimpDisplay *display); + + +#endif /* __GIMP_DISPLAY_H__ */ diff --git a/app/display/gimpdisplayshell-actions.c b/app/display/gimpdisplayshell-actions.c new file mode 100644 index 0000000..fa0b5ef --- /dev/null +++ b/app/display/gimpdisplayshell-actions.c @@ -0,0 +1,149 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" + +#include "widgets/gimpactiongroup.h" +#include "widgets/gimpuimanager.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-actions.h" +#include "gimpimagewindow.h" + + +void +gimp_display_shell_set_action_sensitive (GimpDisplayShell *shell, + const gchar *action, + gboolean sensitive) +{ + GimpImageWindow *window; + GimpContext *context; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (action != NULL); + + window = gimp_display_shell_get_window (shell); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + GimpActionGroup *action_group; + + action_group = gimp_ui_manager_get_action_group (manager, "view"); + + if (action_group) + gimp_action_group_set_action_sensitive (action_group, action, sensitive); + } + + context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (context)) + { + GimpActionGroup *action_group; + + action_group = gimp_ui_manager_get_action_group (shell->popup_manager, + "view"); + + if (action_group) + gimp_action_group_set_action_sensitive (action_group, action, sensitive); + } +} + +void +gimp_display_shell_set_action_active (GimpDisplayShell *shell, + const gchar *action, + gboolean active) +{ + GimpImageWindow *window; + GimpContext *context; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (action != NULL); + + window = gimp_display_shell_get_window (shell); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + GimpActionGroup *action_group; + + action_group = gimp_ui_manager_get_action_group (manager, "view"); + + if (action_group) + gimp_action_group_set_action_active (action_group, action, active); + } + + context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (context)) + { + GimpActionGroup *action_group; + + action_group = gimp_ui_manager_get_action_group (shell->popup_manager, + "view"); + + if (action_group) + gimp_action_group_set_action_active (action_group, action, active); + } +} + +void +gimp_display_shell_set_action_color (GimpDisplayShell *shell, + const gchar *action, + const GimpRGB *color) +{ + GimpImageWindow *window; + GimpContext *context; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (action != NULL); + + window = gimp_display_shell_get_window (shell); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + GimpActionGroup *action_group; + + action_group = gimp_ui_manager_get_action_group (manager, "view"); + + if (action_group) + gimp_action_group_set_action_color (action_group, action, color, FALSE); + } + + context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (context)) + { + GimpActionGroup *action_group; + + action_group = gimp_ui_manager_get_action_group (shell->popup_manager, + "view"); + + if (action_group) + gimp_action_group_set_action_color (action_group, action, color, FALSE); + } +} diff --git a/app/display/gimpdisplayshell-actions.h b/app/display/gimpdisplayshell-actions.h new file mode 100644 index 0000000..5d4a4da --- /dev/null +++ b/app/display/gimpdisplayshell-actions.h @@ -0,0 +1,33 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_ACTIONS_H__ +#define __GIMP_DISPLAY_SHELL_ACTIONS_H__ + + +void gimp_display_shell_set_action_sensitive (GimpDisplayShell *shell, + const gchar *action, + gboolean sensitive); +void gimp_display_shell_set_action_active (GimpDisplayShell *shell, + const gchar *action, + gboolean active); +void gimp_display_shell_set_action_color (GimpDisplayShell *shell, + const gchar *action, + const GimpRGB *color); + + +#endif /* __GIMP_DISPLAY_SHELL_ACTIONS_H__ */ diff --git a/app/display/gimpdisplayshell-appearance.c b/app/display/gimpdisplayshell-appearance.c new file mode 100644 index 0000000..1080acf --- /dev/null +++ b/app/display/gimpdisplayshell-appearance.c @@ -0,0 +1,606 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "config/gimpdisplayoptions.h" + +#include "core/gimpimage.h" + +#include "widgets/gimpdockcolumns.h" +#include "widgets/gimprender.h" +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvas.h" +#include "gimpcanvasitem.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-actions.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-selection.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-scrollbars.h" +#include "gimpimagewindow.h" +#include "gimpstatusbar.h" + + +/* local function prototypes */ + +static GimpDisplayOptions * appearance_get_options (GimpDisplayShell *shell); + + +/* public functions */ + +void +gimp_display_shell_appearance_update (GimpDisplayShell *shell) +{ + GimpDisplayOptions *options; + GimpImageWindow *window; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + window = gimp_display_shell_get_window (shell); + + if (window) + { + GimpDockColumns *left_docks; + GimpDockColumns *right_docks; + gboolean fullscreen; + gboolean has_grip; + + fullscreen = gimp_image_window_get_fullscreen (window); + + gimp_display_shell_set_action_active (shell, "view-fullscreen", + fullscreen); + + left_docks = gimp_image_window_get_left_docks (window); + right_docks = gimp_image_window_get_right_docks (window); + + has_grip = (! fullscreen && + ! (left_docks && gimp_dock_columns_get_docks (left_docks)) && + ! (right_docks && gimp_dock_columns_get_docks (right_docks))); + + gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (shell->statusbar), + has_grip); + } + + gimp_display_shell_set_show_menubar (shell, + options->show_menubar); + gimp_display_shell_set_show_statusbar (shell, + options->show_statusbar); + + gimp_display_shell_set_show_rulers (shell, + options->show_rulers); + gimp_display_shell_set_show_scrollbars (shell, + options->show_scrollbars); + gimp_display_shell_set_show_selection (shell, + options->show_selection); + gimp_display_shell_set_show_layer (shell, + options->show_layer_boundary); + gimp_display_shell_set_show_canvas (shell, + options->show_canvas_boundary); + gimp_display_shell_set_show_guides (shell, + options->show_guides); + gimp_display_shell_set_show_grid (shell, + options->show_grid); + gimp_display_shell_set_show_sample_points (shell, + options->show_sample_points); + gimp_display_shell_set_padding (shell, + options->padding_mode, + &options->padding_color); + gimp_display_shell_set_padding_in_show_all (shell, + options->padding_in_show_all); +} + +void +gimp_display_shell_set_show_menubar (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + GimpImageWindow *window; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + window = gimp_display_shell_get_window (shell); + + g_object_set (options, "show-menubar", show, NULL); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell)); + gimp_image_window_set_show_menubar (window, show); + } + + gimp_display_shell_set_action_active (shell, "view-show-menubar", show); +} + +gboolean +gimp_display_shell_get_show_menubar (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_menubar; +} + +void +gimp_display_shell_set_show_statusbar (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-statusbar", show, NULL); + + gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell)); + gimp_statusbar_set_visible (GIMP_STATUSBAR (shell->statusbar), show); + + gimp_display_shell_set_action_active (shell, "view-show-statusbar", show); +} + +gboolean +gimp_display_shell_get_show_statusbar (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_statusbar; +} + +void +gimp_display_shell_set_show_rulers (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-rulers", show, NULL); + + gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell)); + gtk_widget_set_visible (shell->origin, show); + gtk_widget_set_visible (shell->hrule, show); + gtk_widget_set_visible (shell->vrule, show); + + gimp_display_shell_set_action_active (shell, "view-show-rulers", show); +} + +gboolean +gimp_display_shell_get_show_rulers (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_rulers; +} + +void +gimp_display_shell_set_show_scrollbars (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-scrollbars", show, NULL); + + gimp_image_window_keep_canvas_pos (gimp_display_shell_get_window (shell)); + gtk_widget_set_visible (shell->nav_ebox, show); + gtk_widget_set_visible (shell->hsb, show); + gtk_widget_set_visible (shell->vsb, show); + gtk_widget_set_visible (shell->quick_mask_button, show); + gtk_widget_set_visible (shell->zoom_button, show); + + gimp_display_shell_set_action_active (shell, "view-show-scrollbars", show); +} + +gboolean +gimp_display_shell_get_show_scrollbars (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_scrollbars; +} + +void +gimp_display_shell_set_show_selection (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-selection", show, NULL); + + gimp_display_shell_selection_set_show (shell, show); + + gimp_display_shell_set_action_active (shell, "view-show-selection", show); +} + +gboolean +gimp_display_shell_get_show_selection (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_selection; +} + +void +gimp_display_shell_set_show_layer (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-layer-boundary", show, NULL); + + gimp_canvas_item_set_visible (shell->layer_boundary, show); + + gimp_display_shell_set_action_active (shell, "view-show-layer-boundary", show); +} + +gboolean +gimp_display_shell_get_show_layer (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_layer_boundary; +} + +void +gimp_display_shell_set_show_canvas (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-canvas-boundary", show, NULL); + + gimp_canvas_item_set_visible (shell->canvas_boundary, + show && shell->show_all); + + gimp_display_shell_set_action_active (shell, "view-show-canvas-boundary", show); +} + +gboolean +gimp_display_shell_get_show_canvas (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_canvas_boundary; +} + +void +gimp_display_shell_update_show_canvas (GimpDisplayShell *shell) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + gimp_canvas_item_set_visible (shell->canvas_boundary, + options->show_canvas_boundary && + shell->show_all); +} + +void +gimp_display_shell_set_show_guides (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-guides", show, NULL); + + gimp_canvas_item_set_visible (shell->guides, show); + + gimp_display_shell_set_action_active (shell, "view-show-guides", show); +} + +gboolean +gimp_display_shell_get_show_guides (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_guides; +} + +void +gimp_display_shell_set_show_grid (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-grid", show, NULL); + + gimp_canvas_item_set_visible (shell->grid, show); + + gimp_display_shell_set_action_active (shell, "view-show-grid", show); +} + +gboolean +gimp_display_shell_get_show_grid (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_grid; +} + +void +gimp_display_shell_set_show_sample_points (GimpDisplayShell *shell, + gboolean show) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "show-sample-points", show, NULL); + + gimp_canvas_item_set_visible (shell->sample_points, show); + + gimp_display_shell_set_action_active (shell, "view-show-sample-points", show); +} + +gboolean +gimp_display_shell_get_show_sample_points (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->show_sample_points; +} + +void +gimp_display_shell_set_snap_to_grid (GimpDisplayShell *shell, + gboolean snap) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "snap-to-grid", snap, NULL); +} + +gboolean +gimp_display_shell_get_snap_to_grid (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->snap_to_grid; +} + +void +gimp_display_shell_set_snap_to_guides (GimpDisplayShell *shell, + gboolean snap) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "snap-to-guides", snap, NULL); +} + +gboolean +gimp_display_shell_get_snap_to_guides (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->snap_to_guides; +} + +void +gimp_display_shell_set_snap_to_canvas (GimpDisplayShell *shell, + gboolean snap) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "snap-to-canvas", snap, NULL); +} + +gboolean +gimp_display_shell_get_snap_to_canvas (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->snap_to_canvas; +} + +void +gimp_display_shell_set_snap_to_vectors (GimpDisplayShell *shell, + gboolean snap) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + g_object_set (options, "snap-to-path", snap, NULL); +} + +gboolean +gimp_display_shell_get_snap_to_vectors (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->snap_to_path; +} + +void +gimp_display_shell_set_padding (GimpDisplayShell *shell, + GimpCanvasPaddingMode padding_mode, + const GimpRGB *padding_color) +{ + GimpDisplayOptions *options; + GimpRGB color; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (padding_color != NULL); + + options = appearance_get_options (shell); + color = *padding_color; + + switch (padding_mode) + { + case GIMP_CANVAS_PADDING_MODE_DEFAULT: + if (shell->canvas) + { + GtkStyle *style; + + gtk_widget_ensure_style (shell->canvas); + + style = gtk_widget_get_style (shell->canvas); + + gimp_rgb_set_gdk_color (&color, style->bg + GTK_STATE_NORMAL); + } + break; + + case GIMP_CANVAS_PADDING_MODE_LIGHT_CHECK: + color = *gimp_render_light_check_color (); + break; + + case GIMP_CANVAS_PADDING_MODE_DARK_CHECK: + color = *gimp_render_dark_check_color (); + break; + + case GIMP_CANVAS_PADDING_MODE_CUSTOM: + case GIMP_CANVAS_PADDING_MODE_RESET: + break; + } + + g_object_set (options, + "padding-mode", padding_mode, + "padding-color", &color, + NULL); + + gimp_canvas_set_bg_color (GIMP_CANVAS (shell->canvas), &color); + + gimp_display_shell_set_action_color (shell, "view-padding-color-menu", + &options->padding_color); +} + +void +gimp_display_shell_get_padding (GimpDisplayShell *shell, + GimpCanvasPaddingMode *padding_mode, + GimpRGB *padding_color) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + if (padding_mode) + *padding_mode = options->padding_mode; + + if (padding_color) + *padding_color = options->padding_color; +} + +void +gimp_display_shell_set_padding_in_show_all (GimpDisplayShell *shell, + gboolean keep) +{ + GimpDisplayOptions *options; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + options = appearance_get_options (shell); + + if (options->padding_in_show_all != keep) + { + g_object_set (options, "padding-in-show-all", keep, NULL); + + if (shell->display) + { + gimp_display_shell_scroll_clamp_and_update (shell); + gimp_display_shell_scrollbars_update (shell); + + gimp_display_shell_expose_full (shell); + } + + gimp_display_shell_set_action_active (shell, + "view-padding-color-in-show-all", + keep); + + g_object_notify (G_OBJECT (shell), "infinite-canvas"); + } +} + +gboolean +gimp_display_shell_get_padding_in_show_all (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return appearance_get_options (shell)->padding_in_show_all; +} + + +/* private functions */ + +static GimpDisplayOptions * +appearance_get_options (GimpDisplayShell *shell) +{ + if (gimp_display_get_image (shell->display)) + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window && gimp_image_window_get_fullscreen (window)) + return shell->fullscreen_options; + else + return shell->options; + } + + return shell->no_image_options; +} diff --git a/app/display/gimpdisplayshell-appearance.h b/app/display/gimpdisplayshell-appearance.h new file mode 100644 index 0000000..0c67649 --- /dev/null +++ b/app/display/gimpdisplayshell-appearance.h @@ -0,0 +1,92 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_APPEARANCE_H__ +#define __GIMP_DISPLAY_SHELL_APPEARANCE_H__ + + +void gimp_display_shell_appearance_update (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_menubar (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_menubar (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_statusbar (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_statusbar (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_rulers (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_rulers (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_scrollbars (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_scrollbars (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_selection (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_selection (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_layer (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_layer (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_canvas (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_canvas (GimpDisplayShell *shell); +void gimp_display_shell_update_show_canvas (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_grid (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_grid (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_guides (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_guides (GimpDisplayShell *shell); + +void gimp_display_shell_set_snap_to_grid (GimpDisplayShell *shell, + gboolean snap); +gboolean gimp_display_shell_get_snap_to_grid (GimpDisplayShell *shell); + +void gimp_display_shell_set_show_sample_points (GimpDisplayShell *shell, + gboolean show); +gboolean gimp_display_shell_get_show_sample_points (GimpDisplayShell *shell); + +void gimp_display_shell_set_snap_to_guides (GimpDisplayShell *shell, + gboolean snap); +gboolean gimp_display_shell_get_snap_to_guides (GimpDisplayShell *shell); + +void gimp_display_shell_set_snap_to_canvas (GimpDisplayShell *shell, + gboolean snap); +gboolean gimp_display_shell_get_snap_to_canvas (GimpDisplayShell *shell); + +void gimp_display_shell_set_snap_to_vectors (GimpDisplayShell *shell, + gboolean snap); +gboolean gimp_display_shell_get_snap_to_vectors (GimpDisplayShell *shell); + +void gimp_display_shell_set_padding (GimpDisplayShell *shell, + GimpCanvasPaddingMode mode, + const GimpRGB *color); +void gimp_display_shell_get_padding (GimpDisplayShell *shell, + GimpCanvasPaddingMode *mode, + GimpRGB *color); +void gimp_display_shell_set_padding_in_show_all (GimpDisplayShell *shell, + gboolean keep); +gboolean gimp_display_shell_get_padding_in_show_all (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_APPEARANCE_H__ */ diff --git a/app/display/gimpdisplayshell-autoscroll.c b/app/display/gimpdisplayshell-autoscroll.c new file mode 100644 index 0000000..39cf252 --- /dev/null +++ b/app/display/gimpdisplayshell-autoscroll.c @@ -0,0 +1,184 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "widgets/gimpdeviceinfo.h" +#include "widgets/gimpdeviceinfo-coords.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-autoscroll.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-transform.h" + +#include "tools/tools-types.h" +#include "tools/tool_manager.h" +#include "tools/gimptool.h" +#include "tools/gimptoolcontrol.h" + + +#define AUTOSCROLL_DT 20 +#define AUTOSCROLL_DX 0.1 + + +typedef struct +{ + GdkEventMotion *mevent; + GimpDeviceInfo *device; + guint32 time; + GdkModifierType state; + guint timeout_id; +} ScrollInfo; + + +/* local function prototypes */ + +static gboolean gimp_display_shell_autoscroll_timeout (gpointer data); + + +/* public functions */ + +void +gimp_display_shell_autoscroll_start (GimpDisplayShell *shell, + GdkModifierType state, + GdkEventMotion *mevent) +{ + ScrollInfo *info; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->scroll_info) + return; + + info = g_slice_new0 (ScrollInfo); + + info->mevent = mevent; + info->device = gimp_device_info_get_by_device (mevent->device); + info->time = gdk_event_get_time ((GdkEvent *) mevent); + info->state = state; + info->timeout_id = g_timeout_add (AUTOSCROLL_DT, + gimp_display_shell_autoscroll_timeout, + shell); + + shell->scroll_info = info; +} + +void +gimp_display_shell_autoscroll_stop (GimpDisplayShell *shell) +{ + ScrollInfo *info; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->scroll_info) + return; + + info = shell->scroll_info; + + if (info->timeout_id) + { + g_source_remove (info->timeout_id); + info->timeout_id = 0; + } + + g_slice_free (ScrollInfo, info); + shell->scroll_info = NULL; +} + + +/* private functions */ + +static gboolean +gimp_display_shell_autoscroll_timeout (gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + ScrollInfo *info = shell->scroll_info; + GimpCoords device_coords; + GimpCoords image_coords; + gint dx = 0; + gint dy = 0; + + gimp_device_info_get_device_coords (info->device, + gtk_widget_get_window (shell->canvas), + &device_coords); + + if (device_coords.x < 0) + dx = device_coords.x; + else if (device_coords.x > shell->disp_width) + dx = device_coords.x - shell->disp_width; + + if (device_coords.y < 0) + dy = device_coords.y; + else if (device_coords.y > shell->disp_height) + dy = device_coords.y - shell->disp_height; + + if (dx || dy) + { + GimpDisplay *display = shell->display; + GimpTool *active_tool = tool_manager_get_active (display->gimp); + gint scroll_amount_x = AUTOSCROLL_DX * dx; + gint scroll_amount_y = AUTOSCROLL_DX * dy; + + info->time += AUTOSCROLL_DT; + + gimp_display_shell_scroll_unoverscrollify (shell, + scroll_amount_x, + scroll_amount_y, + &scroll_amount_x, + &scroll_amount_y); + + gimp_display_shell_scroll (shell, + scroll_amount_x, + scroll_amount_y); + + gimp_display_shell_untransform_coords (shell, + &device_coords, + &image_coords); + + if (gimp_tool_control_get_snap_to (active_tool->control)) + { + gint x, y, width, height; + + gimp_tool_control_get_snap_offsets (active_tool->control, + &x, &y, &width, &height); + + gimp_display_shell_snap_coords (shell, + &image_coords, + x, y, width, height); + } + + tool_manager_motion_active (display->gimp, + &image_coords, + info->time, info->state, + display); + + return TRUE; + } + else + { + g_slice_free (ScrollInfo, info); + shell->scroll_info = NULL; + + return FALSE; + } +} diff --git a/app/display/gimpdisplayshell-autoscroll.h b/app/display/gimpdisplayshell-autoscroll.h new file mode 100644 index 0000000..c030759 --- /dev/null +++ b/app/display/gimpdisplayshell-autoscroll.h @@ -0,0 +1,28 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_AUTOSCROLL_H__ +#define __GIMP_DISPLAY_SHELL_AUTOSCROLL_H__ + + +void gimp_display_shell_autoscroll_start (GimpDisplayShell *shell, + GdkModifierType state, + GdkEventMotion *mevent); +void gimp_display_shell_autoscroll_stop (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_AUTOSCROLL_H__ */ diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c new file mode 100644 index 0000000..9b65529 --- /dev/null +++ b/app/display/gimpdisplayshell-callbacks.c @@ -0,0 +1,647 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" +#include "core/gimpimage-quick-mask.h" + +#include "widgets/gimpcairo-wilber.h" +#include "widgets/gimpuimanager.h" + +#include "gimpcanvasitem.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-callbacks.h" +#include "gimpdisplayshell-draw.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-scrollbars.h" +#include "gimpdisplayshell-selection.h" +#include "gimpdisplayshell-title.h" +#include "gimpdisplayshell-transform.h" +#include "gimpdisplayxfer.h" +#include "gimpimagewindow.h" +#include "gimpnavigationeditor.h" + +#include "git-version.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_display_shell_vadjustment_changed (GtkAdjustment *adjustment, + GimpDisplayShell *shell); +static void gimp_display_shell_hadjustment_changed (GtkAdjustment *adjustment, + GimpDisplayShell *shell); +static gboolean gimp_display_shell_vscrollbar_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value, + GimpDisplayShell *shell); + +static gboolean gimp_display_shell_hscrollbar_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value, + GimpDisplayShell *shell); + +static void gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell, + cairo_t *cr); +static void gimp_display_shell_canvas_draw_drop_zone (GimpDisplayShell *shell, + cairo_t *cr); + + +/* public functions */ + +void +gimp_display_shell_canvas_realize (GtkWidget *canvas, + GimpDisplayShell *shell) +{ + GimpCanvasPaddingMode padding_mode; + GimpRGB padding_color; + GtkAllocation allocation; + + gtk_widget_grab_focus (canvas); + + gimp_display_shell_get_padding (shell, &padding_mode, &padding_color); + gimp_display_shell_set_padding (shell, padding_mode, &padding_color); + + gtk_widget_get_allocation (canvas, &allocation); + + gimp_display_shell_title_update (shell); + + shell->disp_width = allocation.width; + shell->disp_height = allocation.height; + + /* set up the scrollbar observers */ + g_signal_connect (shell->hsbdata, "value-changed", + G_CALLBACK (gimp_display_shell_hadjustment_changed), + shell); + g_signal_connect (shell->vsbdata, "value-changed", + G_CALLBACK (gimp_display_shell_vadjustment_changed), + shell); + + g_signal_connect (shell->hsb, "change-value", + G_CALLBACK (gimp_display_shell_hscrollbar_change_value), + shell); + + g_signal_connect (shell->vsb, "change-value", + G_CALLBACK (gimp_display_shell_vscrollbar_change_value), + shell); + + /* allow shrinking */ + gtk_widget_set_size_request (GTK_WIDGET (shell), 0, 0); + + shell->xfer = gimp_display_xfer_realize (GTK_WIDGET(shell)); + + /* HACK: remove with GTK+ 3.x: this unconditionally maps the + * rulers, if configured to be hidden they are never visible to the + * user because they will be hidden again right away. + * + * For some obscure reason, having the rulers mapped once prevents + * crashes with tablets and on-canvas dialogs. See bug #784480 and + * all its duplicates. + */ + gtk_widget_show (shell->hrule); + gtk_widget_show (shell->vrule); +} + +void +gimp_display_shell_canvas_realize_after (GtkWidget *canvas, + GimpDisplayShell *shell) +{ + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + /* HACK: see above: must go with GTK+ 3.x too. Restore the rulers' + * intended visibility again. + */ + gimp_image_window_suspend_keep_pos (window); + gimp_display_shell_appearance_update (shell); + gimp_image_window_resume_keep_pos (window); +} + +void +gimp_display_shell_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpDisplayShell *shell) +{ + /* are we in destruction? */ + if (! shell->display || ! gimp_display_get_shell (shell->display)) + return; + + if ((shell->disp_width != allocation->width) || + (shell->disp_height != allocation->height)) + { + if (shell->zoom_on_resize && + shell->disp_width > 64 && + shell->disp_height > 64 && + allocation->width > 64 && + allocation->height > 64) + { + gdouble scale = gimp_zoom_model_get_factor (shell->zoom); + gint offset_x; + gint offset_y; + + /* FIXME: The code is a bit of a mess */ + + /* multiply the zoom_factor with the ratio of the new and + * old canvas diagonals + */ + scale *= (sqrt (SQR (allocation->width) + + SQR (allocation->height)) / + sqrt (SQR (shell->disp_width) + + SQR (shell->disp_height))); + + offset_x = UNSCALEX (shell, shell->offset_x); + offset_y = UNSCALEX (shell, shell->offset_y); + + gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale); + + shell->offset_x = SCALEX (shell, offset_x); + shell->offset_y = SCALEY (shell, offset_y); + } + + shell->disp_width = allocation->width; + shell->disp_height = allocation->height; + + /* When we size-allocate due to resize of the top level window, + * we want some additional logic. Don't apply it on + * zoom_on_resize though. + */ + if (shell->size_allocate_from_configure_event && + ! shell->zoom_on_resize) + { + gboolean center_horizontally; + gboolean center_vertically; + gint target_offset_x; + gint target_offset_y; + gint sw; + gint sh; + + gimp_display_shell_scale_get_image_size (shell, &sw, &sh); + + center_horizontally = sw <= shell->disp_width; + center_vertically = sh <= shell->disp_height; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + gimp_display_shell_scroll_center_image (shell, + center_horizontally, + center_vertically); + } + else + { + gimp_display_shell_scroll_center_content (shell, + center_horizontally, + center_vertically); + } + + /* This is basically the best we can do before we get an + * API for storing the image offset at the start of an + * image window resize using the mouse + */ + target_offset_x = shell->offset_x; + target_offset_y = shell->offset_y; + + if (! center_horizontally) + { + target_offset_x = MAX (shell->offset_x, 0); + } + + if (! center_vertically) + { + target_offset_y = MAX (shell->offset_y, 0); + } + + gimp_display_shell_scroll_set_offset (shell, + target_offset_x, + target_offset_y); + } + + gimp_display_shell_scroll_clamp_and_update (shell); + gimp_display_shell_scaled (shell); + + shell->size_allocate_from_configure_event = FALSE; + } + + if (shell->size_allocate_center_image) + { + gimp_display_shell_scroll_center_image (shell, TRUE, TRUE); + + shell->size_allocate_center_image = FALSE; + } +} + +gboolean +gimp_display_shell_canvas_expose (GtkWidget *widget, + GdkEventExpose *eevent, + GimpDisplayShell *shell) +{ + /* are we in destruction? */ + if (! shell->display || ! gimp_display_get_shell (shell->display)) + return TRUE; + + /* we will scroll around in the next tick anyway, so we just can as + * well skip the drawing of this frame and wait for the next + */ + if (shell->size_allocate_center_image) + return TRUE; + + /* ignore events on overlays */ + if (eevent->window == gtk_widget_get_window (widget)) + { + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (shell->canvas)); + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + if (gimp_display_get_image (shell->display)) + { + gimp_display_shell_canvas_draw_image (shell, cr); + } + else + { + gimp_display_shell_canvas_draw_drop_zone (shell, cr); + } + + cairo_destroy (cr); + } + + return FALSE; +} + +gboolean +gimp_display_shell_origin_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpDisplayShell *shell) +{ + if (! shell->display->gimp->busy) + { + if (event->type == GDK_BUTTON_PRESS && event->button == 1) + { + gboolean unused; + + g_signal_emit_by_name (shell, "popup-menu", &unused); + } + } + + /* Return TRUE to stop signal emission so the button doesn't grab the + * pointer away from us. + */ + return TRUE; +} + +gboolean +gimp_display_shell_quick_mask_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell) +{ + if (! gimp_display_get_image (shell->display)) + return TRUE; + + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_ui_popup (manager, + "/quick-mask-popup", + GTK_WIDGET (shell), + NULL, NULL, NULL, NULL); + } + + return TRUE; + } + + return FALSE; +} + +void +gimp_display_shell_quick_mask_toggled (GtkWidget *widget, + GimpDisplayShell *shell) +{ + GimpImage *image = gimp_display_get_image (shell->display); + gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if (active != gimp_image_get_quick_mask_state (image)) + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_toggle_action (manager, + "quick-mask", "quick-mask-toggle", + active); + } + } +} + +gboolean +gimp_display_shell_navigation_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell) +{ + if (! gimp_display_get_image (shell->display)) + return TRUE; + + if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1) + { + gimp_navigation_editor_popup (shell, widget, bevent->x, bevent->y); + } + + return TRUE; +} + + +/* private functions */ + +static void +gimp_display_shell_vadjustment_changed (GtkAdjustment *adjustment, + GimpDisplayShell *shell) +{ + /* If we are panning with mouse, scrollbars are to be ignored or + * they will cause jitter in motion + */ + if (! shell->scrolling) + gimp_display_shell_scroll (shell, + 0, + gtk_adjustment_get_value (adjustment) - + shell->offset_y); +} + +static void +gimp_display_shell_hadjustment_changed (GtkAdjustment *adjustment, + GimpDisplayShell *shell) +{ + /* If we are panning with mouse, scrollbars are to be ignored or + * they will cause jitter in motion + */ + if (! shell->scrolling) + gimp_display_shell_scroll (shell, + gtk_adjustment_get_value (adjustment) - + shell->offset_x, + 0); +} + +static gboolean +gimp_display_shell_hscrollbar_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value, + GimpDisplayShell *shell) +{ + if (! shell->display) + return TRUE; + + if ((scroll == GTK_SCROLL_JUMP) || + (scroll == GTK_SCROLL_PAGE_BACKWARD) || + (scroll == GTK_SCROLL_PAGE_FORWARD)) + return FALSE; + + g_object_freeze_notify (G_OBJECT (shell->hsbdata)); + + gimp_display_shell_scrollbars_setup_horizontal (shell, value); + + g_object_thaw_notify (G_OBJECT (shell->hsbdata)); /* emits "changed" */ + + return FALSE; +} + +static gboolean +gimp_display_shell_vscrollbar_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value, + GimpDisplayShell *shell) +{ + if (! shell->display) + return TRUE; + + if ((scroll == GTK_SCROLL_JUMP) || + (scroll == GTK_SCROLL_PAGE_BACKWARD) || + (scroll == GTK_SCROLL_PAGE_FORWARD)) + return FALSE; + + g_object_freeze_notify (G_OBJECT (shell->vsbdata)); + + gimp_display_shell_scrollbars_setup_vertical (shell, value); + + g_object_thaw_notify (G_OBJECT (shell->vsbdata)); /* emits "changed" */ + + return FALSE; +} + +static void +gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell, + cairo_t *cr) +{ + cairo_rectangle_list_t *clip_rectangles; + GeglRectangle image_rect; + GeglRectangle rotated_image_rect; + GeglRectangle canvas_rect; + cairo_matrix_t matrix; + gdouble x1, y1; + gdouble x2, y2; + + gimp_display_shell_scale_get_image_unrotated_bounding_box ( + shell, + &image_rect.x, + &image_rect.y, + &image_rect.width, + &image_rect.height); + + gimp_display_shell_scale_get_image_unrotated_bounds ( + shell, + &canvas_rect.x, + &canvas_rect.y, + &canvas_rect.width, + &canvas_rect.height); + + /* the background has already been cleared by GdkWindow + */ + + + /* on top, draw the exposed part of the region that is inside the + * image + */ + + cairo_save (cr); + clip_rectangles = cairo_copy_clip_rectangle_list (cr); + cairo_get_matrix (cr, &matrix); + + if (shell->rotate_transform) + cairo_transform (cr, shell->rotate_transform); + + if (shell->show_all) + { + cairo_save (cr); + + if (gimp_display_shell_get_padding_in_show_all (shell)) + { + cairo_rectangle (cr, + canvas_rect.x, + canvas_rect.y, + canvas_rect.width, + canvas_rect.height); + cairo_clip (cr); + } + + gimp_display_shell_draw_checkerboard (shell, cr); + + cairo_restore (cr); + } + + cairo_rectangle (cr, + image_rect.x, + image_rect.y, + image_rect.width, + image_rect.height); + cairo_clip (cr); + + gimp_display_shell_rotate_bounds (shell, + image_rect.x, + image_rect.y, + image_rect.x + image_rect.width, + image_rect.y + image_rect.height, + &x1, &y1, &x2, &y2); + + rotated_image_rect.x = floor (x1); + rotated_image_rect.y = floor (y1); + rotated_image_rect.width = ceil (x2) - rotated_image_rect.x; + rotated_image_rect.height = ceil (y2) - rotated_image_rect.y; + + if (gdk_cairo_get_clip_rectangle (cr, NULL)) + { + gint i; + + if (! shell->show_all) + { + cairo_save (cr); + gimp_display_shell_draw_checkerboard (shell, cr); + cairo_restore (cr); + } + + if (shell->show_image) + { + cairo_set_matrix (cr, &matrix); + + for (i = 0; i < clip_rectangles->num_rectangles; i++) + { + cairo_rectangle_t clip_rect = clip_rectangles->rectangles[i]; + GeglRectangle rect; + + rect.x = floor (clip_rect.x); + rect.y = floor (clip_rect.y); + rect.width = ceil (clip_rect.x + clip_rect.width) - rect.x; + rect.height = ceil (clip_rect.y + clip_rect.height) - rect.y; + + if (gegl_rectangle_intersect (&rect, &rect, &rotated_image_rect)) + { + gimp_display_shell_draw_image (shell, cr, + rect.x, rect.y, + rect.width, rect.height); + } + } + } + } + + cairo_rectangle_list_destroy (clip_rectangles); + cairo_restore (cr); + + + /* finally, draw all the remaining image window stuff on top + */ + + /* draw canvas items */ + cairo_save (cr); + + if (shell->rotate_transform) + cairo_transform (cr, shell->rotate_transform); + + gimp_canvas_item_draw (shell->canvas_item, cr); + + cairo_restore (cr); + + gimp_canvas_item_draw (shell->unrotated_item, cr); + + /* restart (and recalculate) the selection boundaries */ + gimp_display_shell_selection_restart (shell); +} + +static void +gimp_display_shell_canvas_draw_drop_zone (GimpDisplayShell *shell, + cairo_t *cr) +{ + cairo_save (cr); + + gimp_cairo_draw_drop_wilber (shell->canvas, cr, shell->blink); + + cairo_restore (cr); + +#ifdef GIMP_UNSTABLE + { + PangoLayout *layout; + gchar *msg; + GtkAllocation allocation; + gint width; + gint height; + gdouble scale; + + layout = gtk_widget_create_pango_layout (shell->canvas, NULL); + + msg = g_strdup_printf (_("<big>Unstable Development Version</big>\n\n" + "<small>commit <tt>%s</tt></small>\n\n" + "<small>Please test bugs against " + "latest git master branch\n" + "before reporting them.</small>"), + GIMP_GIT_VERSION_ABBREV); + pango_layout_set_markup (layout, msg, -1); + g_free (msg); + pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); + + pango_layout_get_pixel_size (layout, &width, &height); + gtk_widget_get_allocation (shell->canvas, &allocation); + + scale = MIN (((gdouble) allocation.width / 2.0) / (gdouble) width, + ((gdouble) allocation.height / 2.0) / (gdouble) height); + + cairo_move_to (cr, + (allocation.width - (width * scale)) / 2, + (allocation.height - (height * scale)) / 2); + + cairo_scale (cr, scale, scale); + + pango_cairo_show_layout (cr, layout); + + g_object_unref (layout); + } +#endif /* GIMP_UNSTABLE */ +} diff --git a/app/display/gimpdisplayshell-callbacks.h b/app/display/gimpdisplayshell-callbacks.h new file mode 100644 index 0000000..de1861f --- /dev/null +++ b/app/display/gimpdisplayshell-callbacks.h @@ -0,0 +1,48 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_CALLBACKS_H__ +#define __GIMP_DISPLAY_SHELL_CALLBACKS_H__ + + +void gimp_display_shell_canvas_realize (GtkWidget *widget, + GimpDisplayShell *shell); +void gimp_display_shell_canvas_realize_after (GtkWidget *widget, + GimpDisplayShell *shell); +void gimp_display_shell_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *alloc, + GimpDisplayShell *shell); +gboolean gimp_display_shell_canvas_expose (GtkWidget *widget, + GdkEventExpose *eevent, + GimpDisplayShell *shell); + +gboolean gimp_display_shell_origin_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell); + +gboolean gimp_display_shell_quick_mask_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell); +void gimp_display_shell_quick_mask_toggled (GtkWidget *widget, + GimpDisplayShell *shell); + +gboolean gimp_display_shell_navigation_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_CALLBACKS_H__ */ diff --git a/app/display/gimpdisplayshell-close.c b/app/display/gimpdisplayshell-close.c new file mode 100644 index 0000000..b7d7c16 --- /dev/null +++ b/app/display/gimpdisplayshell-close.c @@ -0,0 +1,447 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <time.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" + +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpmessagebox.h" +#include "widgets/gimpmessagedialog.h" +#include "widgets/gimpuimanager.h" +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-close.h" +#include "gimpimagewindow.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_display_shell_close_dialog (GimpDisplayShell *shell, + GimpImage *image); +static void gimp_display_shell_close_name_changed (GimpImage *image, + GimpMessageBox *box); +static void gimp_display_shell_close_exported (GimpImage *image, + GFile *file, + GimpMessageBox *box); +static gboolean gimp_display_shell_close_time_changed (GimpMessageBox *box); +static void gimp_display_shell_close_response (GtkWidget *widget, + gboolean close, + GimpDisplayShell *shell); +static void gimp_display_shell_close_accel_marshal(GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +static void gimp_time_since (gint64 then, + gint *hours, + gint *minutes); + + +/* public functions */ + +void +gimp_display_shell_close (GimpDisplayShell *shell, + gboolean kill_it) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + /* FIXME: gimp_busy HACK not really appropriate here because we only + * want to prevent the busy image and display to be closed. --Mitch + */ + if (shell->display->gimp->busy) + return; + + /* If the image has been modified, give the user a chance to save + * it before nuking it--this only applies if its the last view + * to an image canvas. (a image with disp_count = 1) + */ + if (! kill_it && + image && + gimp_image_get_display_count (image) == 1 && + gimp_image_is_dirty (image)) + { + /* If there's a save dialog active for this image, then raise it. + * (see bug #511965) + */ + GtkWidget *dialog = g_object_get_data (G_OBJECT (image), + "gimp-file-save-dialog"); + if (dialog) + { + gtk_window_present (GTK_WINDOW (dialog)); + } + else + { + gimp_display_shell_close_dialog (shell, image); + } + } + else if (image) + { + gimp_display_close (shell->display); + } + else + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + /* Activate the action instead of simply calling gimp_exit(), so + * the quit action's sensitivity is taken into account. + */ + gimp_ui_manager_activate_action (manager, "file", "file-quit"); + } + } +} + + +/* private functions */ + +#define RESPONSE_SAVE 1 + + +static void +gimp_display_shell_close_dialog (GimpDisplayShell *shell, + GimpImage *image) +{ + GtkWidget *dialog; + GimpMessageBox *box; + GtkWidget *label; + GtkAccelGroup *accel_group; + GClosure *closure; + GSource *source; + guint accel_key; + GdkModifierType accel_mods; + gchar *title; + gchar *accel_string; + gchar *hint; + gchar *markup; + GFile *file; + + if (shell->close_dialog) + { + gtk_window_present (GTK_WINDOW (shell->close_dialog)); + return; + } + + file = gimp_image_get_file (image); + + title = g_strdup_printf (_("Close %s"), gimp_image_get_display_name (image)); + + shell->close_dialog = + dialog = gimp_message_dialog_new (title, GIMP_ICON_DOCUMENT_SAVE, + GTK_WIDGET (shell), + GTK_DIALOG_DESTROY_WITH_PARENT, + gimp_standard_help_func, NULL, + + file ? + _("_Save") : + _("Save _As"), RESPONSE_SAVE, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Discard Changes"), GTK_RESPONSE_CLOSE, + NULL); + + g_free (title); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_SAVE, + GTK_RESPONSE_CLOSE, + GTK_RESPONSE_CANCEL, + -1); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &shell->close_dialog); + + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_display_shell_close_response), + shell); + + /* connect <Primary>D to the quit/close button */ + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (shell->close_dialog), accel_group); + g_object_unref (accel_group); + + closure = g_closure_new_object (sizeof (GClosure), + G_OBJECT (shell->close_dialog)); + g_closure_set_marshal (closure, gimp_display_shell_close_accel_marshal); + gtk_accelerator_parse ("<Primary>D", &accel_key, &accel_mods); + gtk_accel_group_connect (accel_group, accel_key, accel_mods, 0, closure); + + box = GIMP_MESSAGE_DIALOG (dialog)->box; + + accel_string = gtk_accelerator_get_label (accel_key, accel_mods); + hint = g_strdup_printf (_("Press %s to discard all changes and close the image."), + accel_string); + markup = g_strdup_printf ("<i><small>%s</small></i>", hint); + + label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_markup (GTK_LABEL (label), markup); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + g_free (markup); + g_free (hint); + g_free (accel_string); + + g_signal_connect_object (image, "name-changed", + G_CALLBACK (gimp_display_shell_close_name_changed), + box, 0); + g_signal_connect_object (image, "exported", + G_CALLBACK (gimp_display_shell_close_exported), + box, 0); + + gimp_display_shell_close_name_changed (image, box); + + closure = + g_cclosure_new_object (G_CALLBACK (gimp_display_shell_close_time_changed), + G_OBJECT (box)); + + /* update every 10 seconds */ + source = g_timeout_source_new_seconds (10); + g_source_set_closure (source, closure); + g_source_attach (source, NULL); + g_source_unref (source); + + /* The dialog is destroyed with the shell, so it should be safe + * to hold an image pointer for the lifetime of the dialog. + */ + g_object_set_data (G_OBJECT (box), "gimp-image", image); + + gimp_display_shell_close_time_changed (box); + + gtk_widget_show (dialog); +} + +static void +gimp_display_shell_close_name_changed (GimpImage *image, + GimpMessageBox *box) +{ + GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + if (GTK_IS_WINDOW (window)) + { + gchar *title = g_strdup_printf (_("Close %s"), + gimp_image_get_display_name (image)); + + gtk_window_set_title (GTK_WINDOW (window), title); + g_free (title); + } + + gimp_message_box_set_primary_text (box, + _("Save the changes to image '%s' " + "before closing?"), + gimp_image_get_display_name (image)); +} + +static void +gimp_display_shell_close_exported (GimpImage *image, + GFile *file, + GimpMessageBox *box) +{ + gimp_display_shell_close_time_changed (box); +} + +static gboolean +gimp_display_shell_close_time_changed (GimpMessageBox *box) +{ + GimpImage *image = g_object_get_data (G_OBJECT (box), "gimp-image"); + gint64 dirty_time = gimp_image_get_dirty_time (image); + gchar *time_text = NULL; + gchar *export_text = NULL; + + if (dirty_time) + { + gint hours = 0; + gint minutes = 0; + + gimp_time_since (dirty_time, &hours, &minutes); + + if (hours > 0) + { + if (hours > 1 || minutes == 0) + { + time_text = + g_strdup_printf (ngettext ("If you don't save the image, " + "changes from the last hour " + "will be lost.", + "If you don't save the image, " + "changes from the last %d " + "hours will be lost.", + hours), hours); + } + else + { + time_text = + g_strdup_printf (ngettext ("If you don't save the image, " + "changes from the last hour " + "and %d minute will be lost.", + "If you don't save the image, " + "changes from the last hour " + "and %d minutes will be lost.", + minutes), minutes); + } + } + else + { + time_text = + g_strdup_printf (ngettext ("If you don't save the image, " + "changes from the last minute " + "will be lost.", + "If you don't save the image, " + "changes from the last %d " + "minutes will be lost.", + minutes), minutes); + } + } + + if (! gimp_image_is_export_dirty (image)) + { + GFile *file; + + file = gimp_image_get_exported_file (image); + if (! file) + file = gimp_image_get_imported_file (image); + + export_text = g_strdup_printf (_("The image has been exported to '%s'."), + gimp_file_get_utf8_name (file)); + } + + if (time_text && export_text) + gimp_message_box_set_text (box, "%s\n\n%s", time_text, export_text); + else if (time_text || export_text) + gimp_message_box_set_text (box, "%s", time_text ? time_text : export_text); + else + gimp_message_box_set_text (box, "%s", time_text); + + g_free (time_text); + g_free (export_text); + + return TRUE; +} + +static void +gimp_display_shell_close_response (GtkWidget *widget, + gint response_id, + GimpDisplayShell *shell) +{ + gtk_widget_destroy (widget); + + switch (response_id) + { + case GTK_RESPONSE_CLOSE: + gimp_display_close (shell->display); + break; + + case RESPONSE_SAVE: + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_image_window_set_active_shell (window, shell); + + gimp_ui_manager_activate_action (manager, + "file", "file-save-and-close"); + } + } + break; + + default: + break; + } +} + +static void +gimp_display_shell_close_accel_marshal (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + gtk_dialog_response (GTK_DIALOG (closure->data), GTK_RESPONSE_CLOSE); + + /* we handled the accelerator */ + g_value_set_boolean (return_value, TRUE); +} + +static void +gimp_time_since (gint64 then, + gint *hours, + gint *minutes) +{ + gint64 now = time (NULL); + gint64 diff = 1 + now - then; + + g_return_if_fail (now >= then); + + /* first round up to the nearest minute */ + diff = (diff + 59) / 60; + + /* then optionally round minutes to multiples of 5 or 10 */ + if (diff > 50) + diff = ((diff + 8) / 10) * 10; + else if (diff > 20) + diff = ((diff + 3) / 5) * 5; + + /* determine full hours */ + if (diff >= 60) + { + *hours = diff / 60; + diff = (diff % 60); + } + + /* round up to full hours for 2 and more */ + if (*hours > 1 && diff > 0) + { + *hours += 1; + diff = 0; + } + + *minutes = diff; +} diff --git a/app/display/gimpdisplayshell-close.h b/app/display/gimpdisplayshell-close.h new file mode 100644 index 0000000..3d55650 --- /dev/null +++ b/app/display/gimpdisplayshell-close.h @@ -0,0 +1,26 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_CLOSE_H__ +#define __GIMP_DISPLAY_SHELL_CLOSE_H__ + + +void gimp_display_shell_close (GimpDisplayShell *shell, + gboolean kill_it); + + +#endif /* __GIMP_DISPLAY_SHELL_CLOSE_H__ */ diff --git a/app/display/gimpdisplayshell-cursor.c b/app/display/gimpdisplayshell-cursor.c new file mode 100644 index 0000000..5a536d5 --- /dev/null +++ b/app/display/gimpdisplayshell-cursor.c @@ -0,0 +1,295 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimpimage.h" + +#include "widgets/gimpcursor.h" +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpdockcontainer.h" +#include "widgets/gimpsessioninfo.h" + +#include "gimpcanvascursor.h" +#include "gimpdisplay.h" +#include "gimpcursorview.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-cursor.h" +#include "gimpdisplayshell-transform.h" +#include "gimpstatusbar.h" + + +static void gimp_display_shell_real_set_cursor (GimpDisplayShell *shell, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier, + gboolean always_install); + + +/* public functions */ + +void +gimp_display_shell_set_cursor (GimpDisplayShell *shell, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->using_override_cursor) + { + gimp_display_shell_real_set_cursor (shell, + cursor_type, + tool_cursor, + modifier, + FALSE); + } +} + +void +gimp_display_shell_unset_cursor (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->using_override_cursor) + { + gimp_display_shell_real_set_cursor (shell, + (GimpCursorType) -1, 0, 0, FALSE); + } +} + +void +gimp_display_shell_set_override_cursor (GimpDisplayShell *shell, + GimpCursorType cursor_type) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->using_override_cursor || + (shell->using_override_cursor && + shell->override_cursor != cursor_type)) + { + shell->override_cursor = cursor_type; + shell->using_override_cursor = TRUE; + + gimp_cursor_set (shell->canvas, + shell->cursor_handedness, + cursor_type, + GIMP_TOOL_CURSOR_NONE, + GIMP_CURSOR_MODIFIER_NONE); + } +} + +void +gimp_display_shell_unset_override_cursor (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->using_override_cursor) + { + shell->using_override_cursor = FALSE; + + gimp_display_shell_real_set_cursor (shell, + shell->current_cursor, + shell->tool_cursor, + shell->cursor_modifier, + TRUE); + } +} + +void +gimp_display_shell_update_software_cursor (GimpDisplayShell *shell, + GimpCursorPrecision precision, + gint display_x, + gint display_y, + gdouble image_x, + gdouble image_y) +{ + GimpImageWindow *image_window; + GimpDialogFactory *factory; + GimpStatusbar *statusbar; + GtkWidget *widget; + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + if (shell->draw_cursor && + shell->proximity && + display_x >= 0 && + display_y >= 0) + { + gimp_canvas_item_begin_change (shell->cursor); + + gimp_canvas_cursor_set (shell->cursor, + display_x, + display_y); + gimp_canvas_item_set_visible (shell->cursor, TRUE); + + gimp_canvas_item_end_change (shell->cursor); + } + else + { + gimp_canvas_item_set_visible (shell->cursor, FALSE); + } + + /* use the passed image_coords for the statusbar because they are + * possibly snapped... + */ + statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_statusbar_update_cursor (statusbar, precision, image_x, image_y); + + image_window = gimp_display_shell_get_window (shell); + factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window)); + + widget = gimp_dialog_factory_find_widget (factory, "gimp-cursor-view"); + + if (widget) + { + GtkWidget *cursor_view = gtk_bin_get_child (GTK_BIN (widget)); + + if (cursor_view) + { + gint t_x = -1; + gint t_y = -1; + + /* ...but use the unsnapped display_coords for the info window */ + if (display_x >= 0 && display_y >= 0) + gimp_display_shell_untransform_xy (shell, display_x, display_y, + &t_x, &t_y, FALSE); + + gimp_cursor_view_update_cursor (GIMP_CURSOR_VIEW (cursor_view), + image, shell->unit, + t_x, t_y); + } + } +} + +void +gimp_display_shell_clear_software_cursor (GimpDisplayShell *shell) +{ + GimpImageWindow *image_window; + GimpDialogFactory *factory; + GimpStatusbar *statusbar; + GtkWidget *widget; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_canvas_item_set_visible (shell->cursor, FALSE); + + statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_statusbar_clear_cursor (statusbar); + + image_window = gimp_display_shell_get_window (shell); + factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window)); + + widget = gimp_dialog_factory_find_widget (factory, "gimp-cursor-view"); + + if (widget) + { + GtkWidget *cursor_view = gtk_bin_get_child (GTK_BIN (widget)); + + if (cursor_view) + gimp_cursor_view_clear_cursor (GIMP_CURSOR_VIEW (cursor_view)); + } +} + + +/* private functions */ + +static void +gimp_display_shell_real_set_cursor (GimpDisplayShell *shell, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier, + gboolean always_install) +{ + GimpHandedness cursor_handedness; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (cursor_type == (GimpCursorType) -1) + { + shell->current_cursor = cursor_type; + + if (gtk_widget_is_drawable (shell->canvas)) + gdk_window_set_cursor (gtk_widget_get_window (shell->canvas), NULL); + + return; + } + + if (cursor_type != GIMP_CURSOR_NONE && + cursor_type != GIMP_CURSOR_BAD) + { + switch (shell->display->config->cursor_mode) + { + case GIMP_CURSOR_MODE_TOOL_ICON: + break; + + case GIMP_CURSOR_MODE_TOOL_CROSSHAIR: + if (cursor_type < GIMP_CURSOR_CORNER_TOP || + cursor_type > GIMP_CURSOR_SIDE_TOP_LEFT) + { + /* the corner and side cursors count as crosshair, so leave + * them and override everything else + */ + cursor_type = GIMP_CURSOR_CROSSHAIR_SMALL; + } + break; + + case GIMP_CURSOR_MODE_CROSSHAIR: + cursor_type = GIMP_CURSOR_CROSSHAIR; + tool_cursor = GIMP_TOOL_CURSOR_NONE; + + if (modifier != GIMP_CURSOR_MODIFIER_BAD) + { + /* the bad modifier is always shown */ + modifier = GIMP_CURSOR_MODIFIER_NONE; + } + break; + } + } + + cursor_type = gimp_cursor_rotate (cursor_type, shell->rotate_angle); + + cursor_handedness = GIMP_GUI_CONFIG (shell->display->config)->cursor_handedness; + + if (shell->cursor_handedness != cursor_handedness || + shell->current_cursor != cursor_type || + shell->tool_cursor != tool_cursor || + shell->cursor_modifier != modifier || + always_install) + { + shell->cursor_handedness = cursor_handedness; + shell->current_cursor = cursor_type; + shell->tool_cursor = tool_cursor; + shell->cursor_modifier = modifier; + + gimp_cursor_set (shell->canvas, + cursor_handedness, + cursor_type, tool_cursor, modifier); + } +} diff --git a/app/display/gimpdisplayshell-cursor.h b/app/display/gimpdisplayshell-cursor.h new file mode 100644 index 0000000..8f434c6 --- /dev/null +++ b/app/display/gimpdisplayshell-cursor.h @@ -0,0 +1,47 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_CURSOR_H__ +#define __GIMP_DISPLAY_SHELL_CURSOR_H__ + + +/* functions dealing with the normal windowing system cursor */ + +void gimp_display_shell_set_cursor (GimpDisplayShell *shell, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier); +void gimp_display_shell_unset_cursor (GimpDisplayShell *shell); +void gimp_display_shell_set_override_cursor (GimpDisplayShell *shell, + GimpCursorType cursor_type); +void gimp_display_shell_unset_override_cursor (GimpDisplayShell *shell); + + +/* functions dealing with the software cursor that is drawn to the + * canvas by GIMP + */ + +void gimp_display_shell_update_software_cursor (GimpDisplayShell *shell, + GimpCursorPrecision precision, + gint display_x, + gint display_y, + gdouble image_x, + gdouble image_y); +void gimp_display_shell_clear_software_cursor (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_CURSOR_H__ */ diff --git a/app/display/gimpdisplayshell-dnd.c b/app/display/gimpdisplayshell-dnd.c new file mode 100644 index 0000000..152ca45 --- /dev/null +++ b/app/display/gimpdisplayshell-dnd.c @@ -0,0 +1,770 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimp-edit.h" +#include "core/gimpbuffer.h" +#include "core/gimpdrawable-edit.h" +#include "core/gimpfilloptions.h" +#include "core/gimpimage.h" +#include "core/gimpimage-new.h" +#include "core/gimpimage-undo.h" +#include "core/gimplayer.h" +#include "core/gimplayer-new.h" +#include "core/gimplayermask.h" +#include "core/gimppattern.h" +#include "core/gimpprogress.h" + +#include "file/file-open.h" + +#include "text/gimptext.h" +#include "text/gimptextlayer.h" + +#include "vectors/gimpvectors.h" +#include "vectors/gimpvectors-import.h" + +#include "widgets/gimpdnd.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-dnd.h" +#include "gimpdisplayshell-transform.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_display_shell_drop_drawable (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_display_shell_drop_vectors (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_display_shell_drop_svg (GtkWidget *widget, + gint x, + gint y, + const guchar *svg_data, + gsize svg_data_length, + gpointer data); +static void gimp_display_shell_drop_pattern (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_display_shell_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); +static void gimp_display_shell_drop_buffer (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_display_shell_drop_uri_list (GtkWidget *widget, + gint x, + gint y, + GList *uri_list, + gpointer data); +static void gimp_display_shell_drop_component (GtkWidget *widget, + gint x, + gint y, + GimpImage *image, + GimpChannelType component, + gpointer data); +static void gimp_display_shell_drop_pixbuf (GtkWidget *widget, + gint x, + gint y, + GdkPixbuf *pixbuf, + gpointer data); + + +/* public functions */ + +void +gimp_display_shell_dnd_init (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_LAYER, + gimp_display_shell_drop_drawable, + shell); + gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_LAYER_MASK, + gimp_display_shell_drop_drawable, + shell); + gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_CHANNEL, + gimp_display_shell_drop_drawable, + shell); + gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_VECTORS, + gimp_display_shell_drop_vectors, + shell); + gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_PATTERN, + gimp_display_shell_drop_pattern, + shell); + gimp_dnd_viewable_dest_add (shell->canvas, GIMP_TYPE_BUFFER, + gimp_display_shell_drop_buffer, + shell); + gimp_dnd_color_dest_add (shell->canvas, + gimp_display_shell_drop_color, + shell); + gimp_dnd_component_dest_add (shell->canvas, + gimp_display_shell_drop_component, + shell); + gimp_dnd_uri_list_dest_add (shell->canvas, + gimp_display_shell_drop_uri_list, + shell); + gimp_dnd_svg_dest_add (shell->canvas, + gimp_display_shell_drop_svg, + shell); + gimp_dnd_pixbuf_dest_add (shell->canvas, + gimp_display_shell_drop_pixbuf, + shell); +} + + +/* private functions */ + +/* + * Position the dropped item in the middle of the viewport. + */ +static void +gimp_display_shell_dnd_position_item (GimpDisplayShell *shell, + GimpImage *image, + GimpItem *item) +{ + gint item_width = gimp_item_get_width (item); + gint item_height = gimp_item_get_height (item); + gint off_x, off_y; + + if (item_width >= gimp_image_get_width (image) && + item_height >= gimp_image_get_height (image)) + { + off_x = (gimp_image_get_width (image) - item_width) / 2; + off_y = (gimp_image_get_height (image) - item_height) / 2; + } + else + { + gint x, y; + gint width, height; + + gimp_display_shell_untransform_viewport ( + shell, + ! gimp_display_shell_get_infinite_canvas (shell), + &x, &y, &width, &height); + + off_x = x + (width - item_width) / 2; + off_y = y + (height - item_height) / 2; + } + + gimp_item_translate (item, + off_x - gimp_item_get_offset_x (item), + off_y - gimp_item_get_offset_y (item), + FALSE); +} + +static void +gimp_display_shell_dnd_flush (GimpDisplayShell *shell, + GimpImage *image) +{ + gimp_display_shell_present (shell); + + gimp_image_flush (image); + + gimp_context_set_display (gimp_get_user_context (shell->display->gimp), + shell->display); +} + +static void +gimp_display_shell_drop_drawable (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image = gimp_display_get_image (shell->display); + GType new_type; + GimpItem *new_item; + + GIMP_LOG (DND, NULL); + + if (shell->display->gimp->busy) + return; + + if (! image) + { + image = gimp_image_new_from_drawable (shell->display->gimp, + GIMP_DRAWABLE (viewable)); + gimp_create_display (shell->display->gimp, image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (image); + + return; + } + + if (GIMP_IS_LAYER (viewable)) + new_type = G_TYPE_FROM_INSTANCE (viewable); + else + new_type = GIMP_TYPE_LAYER; + + new_item = gimp_item_convert (GIMP_ITEM (viewable), image, new_type); + + if (new_item) + { + GimpLayer *new_layer = GIMP_LAYER (new_item); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("Drop New Layer")); + + gimp_display_shell_dnd_position_item (shell, image, new_item); + + gimp_item_set_visible (new_item, TRUE, FALSE); + gimp_item_set_linked (new_item, FALSE, FALSE); + + gimp_image_add_layer (image, new_layer, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + gimp_image_undo_group_end (image); + + gimp_display_shell_dnd_flush (shell, image); + } +} + +static void +gimp_display_shell_drop_vectors (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image = gimp_display_get_image (shell->display); + GimpItem *new_item; + + GIMP_LOG (DND, NULL); + + if (shell->display->gimp->busy) + return; + + if (! image) + return; + + new_item = gimp_item_convert (GIMP_ITEM (viewable), + image, G_TYPE_FROM_INSTANCE (viewable)); + + if (new_item) + { + GimpVectors *new_vectors = GIMP_VECTORS (new_item); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("Drop New Path")); + + gimp_image_add_vectors (image, new_vectors, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + gimp_image_undo_group_end (image); + + gimp_display_shell_dnd_flush (shell, image); + } +} + +static void +gimp_display_shell_drop_svg (GtkWidget *widget, + gint x, + gint y, + const guchar *svg_data, + gsize svg_data_len, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image = gimp_display_get_image (shell->display); + GError *error = NULL; + + GIMP_LOG (DND, NULL); + + if (shell->display->gimp->busy) + return; + + if (! image) + return; + + if (! gimp_vectors_import_buffer (image, + (const gchar *) svg_data, svg_data_len, + TRUE, FALSE, + GIMP_IMAGE_ACTIVE_PARENT, -1, + NULL, &error)) + { + gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), + GIMP_MESSAGE_ERROR, + error->message); + g_clear_error (&error); + } + else + { + gimp_display_shell_dnd_flush (shell, image); + } +} + +static void +gimp_display_shell_dnd_fill (GimpDisplayShell *shell, + GimpFillOptions *options, + const gchar *undo_desc) +{ + GimpImage *image = gimp_display_get_image (shell->display); + GimpDrawable *drawable; + + if (shell->display->gimp->busy) + return; + + if (! image) + return; + + drawable = gimp_image_get_active_drawable (image); + + if (! drawable) + return; + + if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable))) + { + gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), + GIMP_MESSAGE_ERROR, + _("Cannot modify the pixels of layer groups.")); + return; + } + + if (gimp_item_is_content_locked (GIMP_ITEM (drawable))) + { + gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), + GIMP_MESSAGE_ERROR, + _("The active layer's pixels are locked.")); + return; + } + + /* FIXME: there should be a virtual method for this that the + * GimpTextLayer can override. + */ + if (gimp_fill_options_get_style (options) == GIMP_FILL_STYLE_SOLID && + gimp_item_is_text_layer (GIMP_ITEM (drawable))) + { + GimpRGB color; + + gimp_context_get_foreground (GIMP_CONTEXT (options), &color); + + gimp_text_layer_set (GIMP_TEXT_LAYER (drawable), NULL, + "color", &color, + NULL); + } + else + { + gimp_drawable_edit_fill (drawable, options, undo_desc); + } + + gimp_display_shell_dnd_flush (shell, image); +} + +static void +gimp_display_shell_drop_pattern (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpFillOptions *options = gimp_fill_options_new (shell->display->gimp, + NULL, FALSE); + + GIMP_LOG (DND, NULL); + + gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN); + gimp_context_set_pattern (GIMP_CONTEXT (options), GIMP_PATTERN (viewable)); + + gimp_display_shell_dnd_fill (shell, options, + C_("undo-type", "Drop pattern to layer")); + + g_object_unref (options); +} + +static void +gimp_display_shell_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpFillOptions *options = gimp_fill_options_new (shell->display->gimp, + NULL, FALSE); + + GIMP_LOG (DND, NULL); + + gimp_fill_options_set_style (options, GIMP_FILL_STYLE_SOLID); + gimp_context_set_foreground (GIMP_CONTEXT (options), color); + + gimp_display_shell_dnd_fill (shell, options, + C_("undo-type", "Drop color to layer")); + + g_object_unref (options); +} + +static void +gimp_display_shell_drop_buffer (GtkWidget *widget, + gint drop_x, + gint drop_y, + GimpViewable *viewable, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image = gimp_display_get_image (shell->display); + GimpDrawable *drawable; + GimpBuffer *buffer; + GimpPasteType paste_type; + gint x, y, width, height; + + GIMP_LOG (DND, NULL); + + if (shell->display->gimp->busy) + return; + + if (! image) + { + image = gimp_image_new_from_buffer (shell->display->gimp, + GIMP_BUFFER (viewable)); + gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (image); + + return; + } + + paste_type = GIMP_PASTE_TYPE_FLOATING; + + drawable = gimp_image_get_active_drawable (image); + + if (drawable) + { + if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable))) + { + gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), + GIMP_MESSAGE_INFO, + _("Pasted as new layer because the " + "target is a layer group.")); + + paste_type = GIMP_PASTE_TYPE_NEW_LAYER; + } + else if (gimp_item_is_content_locked (GIMP_ITEM (drawable))) + { + gimp_message_literal (shell->display->gimp, G_OBJECT (shell->display), + GIMP_MESSAGE_ERROR, + _("Pasted as new layer because the " + "target's pixels are locked.")); + + paste_type = GIMP_PASTE_TYPE_NEW_LAYER; + } + } + + buffer = GIMP_BUFFER (viewable); + + gimp_display_shell_untransform_viewport ( + shell, + ! gimp_display_shell_get_infinite_canvas (shell), + &x, &y, &width, &height); + + /* FIXME: popup a menu for selecting "Paste Into" */ + + gimp_edit_paste (image, drawable, GIMP_OBJECT (buffer), + paste_type, x, y, width, height); + + gimp_display_shell_dnd_flush (shell, image); +} + +static void +gimp_display_shell_drop_uri_list (GtkWidget *widget, + gint x, + gint y, + GList *uri_list, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image; + GimpContext *context; + GList *list; + gboolean open_as_layers; + + /* If the app is already being torn down, shell->display might be + * NULL here. Play it safe. + */ + if (! shell->display) + return; + + image = gimp_display_get_image (shell->display); + context = gimp_get_user_context (shell->display->gimp); + + GIMP_LOG (DND, NULL); + + open_as_layers = (image != NULL); + + if (image) + g_object_ref (image); + + for (list = uri_list; list; list = g_list_next (list)) + { + GFile *file = g_file_new_for_uri (list->data); + GimpPDBStatusType status; + GError *error = NULL; + gboolean warn = FALSE; + + if (! shell->display) + { + /* It seems as if GIMP is being torn down for quitting. Bail out. */ + g_object_unref (file); + g_clear_object (&image); + return; + } + + if (open_as_layers) + { + GList *new_layers; + + new_layers = file_open_layers (shell->display->gimp, context, + GIMP_PROGRESS (shell->display), + image, FALSE, + file, GIMP_RUN_INTERACTIVE, NULL, + &status, &error); + + if (new_layers) + { + gint x = 0; + gint y = 0; + gint width = gimp_image_get_width (image); + gint height = gimp_image_get_height (image); + + if (gimp_display_get_image (shell->display)) + { + gimp_display_shell_untransform_viewport ( + shell, + ! gimp_display_shell_get_infinite_canvas (shell), + &x, &y, &width, &height); + } + + gimp_image_add_layers (image, new_layers, + GIMP_IMAGE_ACTIVE_PARENT, -1, + x, y, width, height, + _("Drop layers")); + + g_list_free (new_layers); + } + else if (status != GIMP_PDB_CANCEL) + { + warn = TRUE; + } + } + else if (gimp_display_get_image (shell->display)) + { + /* open any subsequent images in a new display */ + GimpImage *new_image; + + new_image = file_open_with_display (shell->display->gimp, context, + NULL, + file, FALSE, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget), + &status, &error); + + if (! new_image && status != GIMP_PDB_CANCEL) + warn = TRUE; + } + else + { + /* open the first image in the empty display */ + image = file_open_with_display (shell->display->gimp, context, + GIMP_PROGRESS (shell->display), + file, FALSE, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget), + &status, &error); + + if (image) + { + g_object_ref (image); + } + else if (status != GIMP_PDB_CANCEL) + { + warn = TRUE; + } + } + + /* Something above might have run a few rounds of the main loop. Check + * that shell->display is still there, otherwise ignore this as the app + * is being torn down for quitting. + */ + if (warn && shell->display) + { + gimp_message (shell->display->gimp, G_OBJECT (shell->display), + GIMP_MESSAGE_ERROR, + _("Opening '%s' failed:\n\n%s"), + gimp_file_get_utf8_name (file), error->message); + g_clear_error (&error); + } + + g_object_unref (file); + } + + if (image) + gimp_display_shell_dnd_flush (shell, image); + + g_clear_object (&image); +} + +static void +gimp_display_shell_drop_component (GtkWidget *widget, + gint x, + gint y, + GimpImage *image, + GimpChannelType component, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *dest_image = gimp_display_get_image (shell->display); + GimpChannel *channel; + GimpItem *new_item; + const gchar *desc; + + GIMP_LOG (DND, NULL); + + if (shell->display->gimp->busy) + return; + + if (! dest_image) + { + dest_image = gimp_image_new_from_component (image->gimp, + image, component); + gimp_create_display (dest_image->gimp, dest_image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (dest_image); + + return; + } + + channel = gimp_channel_new_from_component (image, component, NULL, NULL); + + new_item = gimp_item_convert (GIMP_ITEM (channel), + dest_image, GIMP_TYPE_LAYER); + g_object_unref (channel); + + if (new_item) + { + GimpLayer *new_layer = GIMP_LAYER (new_item); + + gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component, + NULL, NULL, &desc, NULL); + gimp_object_take_name (GIMP_OBJECT (new_layer), + g_strdup_printf (_("%s Channel Copy"), desc)); + + gimp_image_undo_group_start (dest_image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("Drop New Layer")); + + gimp_display_shell_dnd_position_item (shell, image, new_item); + + gimp_image_add_layer (dest_image, new_layer, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + gimp_image_undo_group_end (dest_image); + + gimp_display_shell_dnd_flush (shell, dest_image); + } +} + +static void +gimp_display_shell_drop_pixbuf (GtkWidget *widget, + gint x, + gint y, + GdkPixbuf *pixbuf, + gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image = gimp_display_get_image (shell->display); + GimpLayer *new_layer; + gboolean has_alpha = FALSE; + + GIMP_LOG (DND, NULL); + + if (shell->display->gimp->busy) + return; + + if (! image) + { + image = gimp_image_new_from_pixbuf (shell->display->gimp, pixbuf, + _("Dropped Buffer")); + gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (image); + + return; + } + + if (gdk_pixbuf_get_n_channels (pixbuf) == 2 || + gdk_pixbuf_get_n_channels (pixbuf) == 4) + { + has_alpha = TRUE; + } + + new_layer = + gimp_layer_new_from_pixbuf (pixbuf, image, + gimp_image_get_layer_format (image, has_alpha), + _("Dropped Buffer"), + GIMP_OPACITY_OPAQUE, + gimp_image_get_default_new_layer_mode (image)); + + if (new_layer) + { + GimpItem *new_item = GIMP_ITEM (new_layer); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("Drop New Layer")); + + gimp_display_shell_dnd_position_item (shell, image, new_item); + + gimp_image_add_layer (image, new_layer, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + gimp_image_undo_group_end (image); + + gimp_display_shell_dnd_flush (shell, image); + } +} diff --git a/app/display/gimpdisplayshell-dnd.h b/app/display/gimpdisplayshell-dnd.h new file mode 100644 index 0000000..d1f6e99 --- /dev/null +++ b/app/display/gimpdisplayshell-dnd.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_DND_H__ +#define __GIMP_DISPLAY_SHELL_DND_H__ + + +void gimp_display_shell_dnd_init (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_DND_H__ */ diff --git a/app/display/gimpdisplayshell-draw.c b/app/display/gimpdisplayshell-draw.c new file mode 100644 index 0000000..d6a63d5 --- /dev/null +++ b/app/display/gimpdisplayshell-draw.c @@ -0,0 +1,256 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp-cairo.h" +#include "core/gimp-utils.h" +#include "core/gimpimage.h" + +#include "gimpcanvas.h" +#include "gimpcanvas-style.h" +#include "gimpcanvaspath.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-draw.h" +#include "gimpdisplayshell-render.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-transform.h" +#include "gimpdisplayxfer.h" + +#ifdef GDK_WINDOWING_QUARTZ +#import <AppKit/AppKit.h> +#endif + +/* #define GIMP_DISPLAY_RENDER_ENABLE_SCALING 1 */ + + +/* public functions */ + +void +gimp_display_shell_draw_selection_out (GimpDisplayShell *shell, + cairo_t *cr, + GimpSegment *segs, + gint n_segs) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (cr != NULL); + g_return_if_fail (segs != NULL && n_segs > 0); + + gimp_canvas_set_selection_out_style (shell->canvas, cr, + shell->offset_x, shell->offset_y); + + gimp_cairo_segments (cr, segs, n_segs); + cairo_stroke (cr); +} + +void +gimp_display_shell_draw_selection_in (GimpDisplayShell *shell, + cairo_t *cr, + cairo_pattern_t *mask, + gint index) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (cr != NULL); + g_return_if_fail (mask != NULL); + + gimp_canvas_set_selection_in_style (shell->canvas, cr, index, + shell->offset_x, shell->offset_y); + + cairo_mask (cr, mask); +} + +void +gimp_display_shell_draw_checkerboard (GimpDisplayShell *shell, + cairo_t *cr) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (cr != NULL); + + image = gimp_display_get_image (shell->display); + + if (G_UNLIKELY (! shell->checkerboard)) + { + GimpCheckSize check_size; + GimpCheckType check_type; + guchar check_light; + guchar check_dark; + GimpRGB light; + GimpRGB dark; + + g_object_get (shell->display->config, + "transparency-size", &check_size, + "transparency-type", &check_type, + NULL); + + gimp_checks_get_shades (check_type, &check_light, &check_dark); + gimp_rgb_set_uchar (&light, check_light, check_light, check_light); + gimp_rgb_set_uchar (&dark, check_dark, check_dark, check_dark); + + shell->checkerboard = + gimp_cairo_checkerboard_create (cr, + 1 << (check_size + 2), &light, &dark); + } + + cairo_translate (cr, - shell->offset_x, - shell->offset_y); + + if (gimp_image_get_component_visible (image, GIMP_CHANNEL_ALPHA)) + cairo_set_source (cr, shell->checkerboard); + else + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + + cairo_paint (cr); +} + +void +gimp_display_shell_draw_image (GimpDisplayShell *shell, + cairo_t *cr, + gint x, + gint y, + gint w, + gint h) +{ + gdouble chunk_width; + gdouble chunk_height; + gdouble scale = 1.0; + gint n_rows; + gint n_cols; + gint r, c; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (gimp_display_get_image (shell->display)); + g_return_if_fail (cr != NULL); + + /* display the image in RENDER_BUF_WIDTH x RENDER_BUF_HEIGHT + * maximally-sized image-space chunks. adjust the screen-space + * chunk size as necessary, to accommodate for the display + * transform and window scale factor. + */ + chunk_width = GIMP_DISPLAY_RENDER_BUF_WIDTH; + chunk_height = GIMP_DISPLAY_RENDER_BUF_HEIGHT; + +#ifdef GIMP_DISPLAY_RENDER_ENABLE_SCALING + /* if we had this future API, things would look pretty on hires (retina) */ + scale *= + gdk_window_get_scale_factor ( + gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (shell)))); +#elif defined(GDK_WINDOWING_QUARTZ) + /* gtk2/osx retina support */ + if ([ + [NSScreen mainScreen] + respondsToSelector: @selector(backingScaleFactor) + ]) { + for (NSScreen * screen in [NSScreen screens]) { + float s = [screen backingScaleFactor]; + if (s > scale) scale = s; + } + } +#endif + + scale = MIN (scale, GIMP_DISPLAY_RENDER_MAX_SCALE); + scale *= MAX (shell->scale_x, shell->scale_y); + + if (scale != shell->scale_x) + chunk_width = (chunk_width - 1.0) * (shell->scale_x / scale); + if (scale != shell->scale_y) + chunk_height = (chunk_height - 1.0) * (shell->scale_y / scale); + + if (shell->rotate_untransform) + { + gdouble a = shell->rotate_angle * G_PI / 180.0; + + chunk_width = chunk_height = (MIN (chunk_width, chunk_height) - 1.0) / + (fabs (sin (a)) + fabs (cos (a))); + } + + /* divide the painted area to evenly-sized chunks */ + n_rows = ceil (h / floor (chunk_height)); + n_cols = ceil (w / floor (chunk_width)); + + for (r = 0; r < n_rows; r++) + { + gint y1 = y + (2 * r * h + n_rows) / (2 * n_rows); + gint y2 = y + (2 * (r + 1) * h + n_rows) / (2 * n_rows); + + for (c = 0; c < n_cols; c++) + { + gint x1 = x + (2 * c * w + n_cols) / (2 * n_cols); + gint x2 = x + (2 * (c + 1) * w + n_cols) / (2 * n_cols); + gdouble ix1, iy1; + gdouble ix2, iy2; + gint ix, iy; + gint iw, ih; + + /* map chunk from screen space to scaled image space */ + gimp_display_shell_untransform_bounds_with_scale ( + shell, scale, + x1, y1, x2, y2, + &ix1, &iy1, &ix2, &iy2); + + ix = floor (ix1); + iy = floor (iy1); + iw = ceil (ix2) - ix; + ih = ceil (iy2) - iy; + + cairo_save (cr); + + /* clip to chunk bounds, in screen space */ + cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1); + cairo_clip (cr); + + /* transform to scaled image space, and apply uneven scaling */ + if (shell->rotate_transform) + cairo_transform (cr, shell->rotate_transform); + cairo_translate (cr, -shell->offset_x, -shell->offset_y); + cairo_scale (cr, shell->scale_x / scale, shell->scale_y / scale); + + /* render image */ + gimp_display_shell_render (shell, cr, ix, iy, iw, ih, scale); + + cairo_restore (cr); + + /* if the GIMP_BRICK_WALL environment variable is defined, + * show chunk bounds + */ + { + static gint brick_wall = -1; + + if (brick_wall < 0) + brick_wall = (g_getenv ("GIMP_BRICK_WALL") != NULL); + + if (brick_wall) + { + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1); + cairo_stroke (cr); + } + } + } + } +} diff --git a/app/display/gimpdisplayshell-draw.h b/app/display/gimpdisplayshell-draw.h new file mode 100644 index 0000000..05dd2c1 --- /dev/null +++ b/app/display/gimpdisplayshell-draw.h @@ -0,0 +1,41 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_DRAW_H__ +#define __GIMP_DISPLAY_SHELL_DRAW_H__ + + +void gimp_display_shell_draw_selection_out (GimpDisplayShell *shell, + cairo_t *cr, + GimpSegment *segs, + gint n_segs); +void gimp_display_shell_draw_selection_in (GimpDisplayShell *shell, + cairo_t *cr, + cairo_pattern_t *mask, + gint index); + +void gimp_display_shell_draw_checkerboard (GimpDisplayShell *shell, + cairo_t *cr); +void gimp_display_shell_draw_image (GimpDisplayShell *shell, + cairo_t *cr, + gint x, + gint y, + gint w, + gint h); + + +#endif /* __GIMP_DISPLAY_SHELL_DRAW_H__ */ diff --git a/app/display/gimpdisplayshell-expose.c b/app/display/gimpdisplayshell-expose.c new file mode 100644 index 0000000..2320a90 --- /dev/null +++ b/app/display/gimpdisplayshell-expose.c @@ -0,0 +1,76 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" + + +void +gimp_display_shell_expose_area (GimpDisplayShell *shell, + gint x, + gint y, + gint w, + gint h) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gtk_widget_queue_draw_area (shell->canvas, x, y, w, h); +} + +void +gimp_display_shell_expose_region (GimpDisplayShell *shell, + cairo_region_t *region) +{ + GdkWindow *window; + gint n_rectangles; + gint i; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (region != NULL); + + if (! gtk_widget_get_realized (shell->canvas)) + return; + + window = gtk_widget_get_window (shell->canvas); + n_rectangles = cairo_region_num_rectangles (region); + + for (i = 0; i < n_rectangles; i++) + { + cairo_rectangle_int_t rectangle; + + cairo_region_get_rectangle (region, i, &rectangle); + + gdk_window_invalidate_rect (window, + (GdkRectangle *) &rectangle, + TRUE); + } +} + +void +gimp_display_shell_expose_full (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gtk_widget_queue_draw (shell->canvas); +} diff --git a/app/display/gimpdisplayshell-expose.h b/app/display/gimpdisplayshell-expose.h new file mode 100644 index 0000000..36d3519 --- /dev/null +++ b/app/display/gimpdisplayshell-expose.h @@ -0,0 +1,32 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_EXPOSE_H__ +#define __GIMP_DISPLAY_SHELL_EXPOSE_H__ + + +void gimp_display_shell_expose_area (GimpDisplayShell *shell, + gint x, + gint y, + gint w, + gint h); +void gimp_display_shell_expose_region (GimpDisplayShell *shell, + cairo_region_t *region); +void gimp_display_shell_expose_full (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_EXPOSE_H__ */ diff --git a/app/display/gimpdisplayshell-filter-dialog.c b/app/display/gimpdisplayshell-filter-dialog.c new file mode 100644 index 0000000..720529c --- /dev/null +++ b/app/display/gimpdisplayshell-filter-dialog.c @@ -0,0 +1,151 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1999 Manish Singh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpviewable.h" + +#include "widgets/gimpcolordisplayeditor.h" +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpviewabledialog.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-filter.h" +#include "gimpdisplayshell-filter-dialog.h" + +#include "gimp-intl.h" + + +typedef struct +{ + GimpDisplayShell *shell; + GtkWidget *dialog; + + GimpColorDisplayStack *old_stack; +} ColorDisplayDialog; + + +/* local function prototypes */ + +static void gimp_display_shell_filter_dialog_response (GtkWidget *widget, + gint response_id, + ColorDisplayDialog *cdd); + +static void gimp_display_shell_filter_dialog_free (ColorDisplayDialog *cdd); + + +/* public functions */ + +GtkWidget * +gimp_display_shell_filter_dialog_new (GimpDisplayShell *shell) +{ + GimpImage *image; + ColorDisplayDialog *cdd; + GtkWidget *editor; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + image = gimp_display_get_image (shell->display); + + cdd = g_slice_new0 (ColorDisplayDialog); + + cdd->shell = shell; + cdd->dialog = gimp_viewable_dialog_new (GIMP_VIEWABLE (image), + gimp_get_user_context (shell->display->gimp), + _("Color Display Filters"), + "gimp-display-filters", + GIMP_ICON_DISPLAY_FILTER, + _("Configure Color Display Filters"), + GTK_WIDGET (cdd->shell), + gimp_standard_help_func, + GIMP_HELP_DISPLAY_FILTER_DIALOG, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (cdd->dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_destroy_with_parent (GTK_WINDOW (cdd->dialog), TRUE); + + g_object_weak_ref (G_OBJECT (cdd->dialog), + (GWeakNotify) gimp_display_shell_filter_dialog_free, cdd); + + g_signal_connect (cdd->dialog, "response", + G_CALLBACK (gimp_display_shell_filter_dialog_response), + cdd); + + if (shell->filter_stack) + { + cdd->old_stack = gimp_color_display_stack_clone (shell->filter_stack); + + g_object_weak_ref (G_OBJECT (cdd->dialog), + (GWeakNotify) g_object_unref, cdd->old_stack); + } + else + { + GimpColorDisplayStack *stack = gimp_color_display_stack_new (); + + gimp_display_shell_filter_set (shell, stack); + g_object_unref (stack); + } + + editor = gimp_color_display_editor_new (shell->display->gimp, + shell->filter_stack, + gimp_display_shell_get_color_config (shell), + GIMP_COLOR_MANAGED (shell)); + gtk_container_set_border_width (GTK_CONTAINER (editor), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (cdd->dialog))), + editor, TRUE, TRUE, 0); + gtk_widget_show (editor); + + return cdd->dialog; +} + + +/* private functions */ + +static void +gimp_display_shell_filter_dialog_response (GtkWidget *widget, + gint response_id, + ColorDisplayDialog *cdd) +{ + if (response_id != GTK_RESPONSE_OK) + gimp_display_shell_filter_set (cdd->shell, cdd->old_stack); + + gtk_widget_destroy (GTK_WIDGET (cdd->dialog)); +} + +static void +gimp_display_shell_filter_dialog_free (ColorDisplayDialog *cdd) +{ + g_slice_free (ColorDisplayDialog, cdd); +} diff --git a/app/display/gimpdisplayshell-filter-dialog.h b/app/display/gimpdisplayshell-filter-dialog.h new file mode 100644 index 0000000..9f73a8b --- /dev/null +++ b/app/display/gimpdisplayshell-filter-dialog.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1999 Manish Singh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_FILTER_DIALOG_H__ +#define __GIMP_DISPLAY_SHELL_FILTER_DIALOG_H__ + + +GtkWidget * gimp_display_shell_filter_dialog_new (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_FILTER_DIALOG_H__ */ diff --git a/app/display/gimpdisplayshell-filter.c b/app/display/gimpdisplayshell-filter.c new file mode 100644 index 0000000..1cf60e8 --- /dev/null +++ b/app/display/gimpdisplayshell-filter.c @@ -0,0 +1,116 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1999 Manish Singh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-filter.h" +#include "gimpdisplayshell-profile.h" + + +/* local function prototypes */ + +static void gimp_display_shell_filter_changed (GimpColorDisplayStack *stack, + GimpDisplayShell *shell); + + +/* public functions */ + +void +gimp_display_shell_filter_set (GimpDisplayShell *shell, + GimpColorDisplayStack *stack) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (stack == NULL || GIMP_IS_COLOR_DISPLAY_STACK (stack)); + + if (stack == shell->filter_stack) + return; + + if (shell->filter_stack) + { + g_signal_handlers_disconnect_by_func (shell->filter_stack, + gimp_display_shell_filter_changed, + shell); + } + + g_set_object (&shell->filter_stack, stack); + + if (shell->filter_stack) + { + g_signal_connect (shell->filter_stack, "changed", + G_CALLBACK (gimp_display_shell_filter_changed), + shell); + } + + gimp_display_shell_filter_changed (NULL, shell); +} + +gboolean +gimp_display_shell_has_filter (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + if (shell->filter_stack) + { + GList *iter; + + for (iter = shell->filter_stack->filters; iter; iter = g_list_next (iter)) + { + if (gimp_color_display_get_enabled (GIMP_COLOR_DISPLAY (iter->data))) + return TRUE; + } + } + + return FALSE; +} + + +/* private functions */ + +static gboolean +gimp_display_shell_filter_changed_idle (gpointer data) +{ + GimpDisplayShell *shell = data; + + gimp_display_shell_profile_update (shell); + gimp_display_shell_expose_full (shell); + + shell->filter_idle_id = 0; + + return FALSE; +} + +static void +gimp_display_shell_filter_changed (GimpColorDisplayStack *stack, + GimpDisplayShell *shell) +{ + if (shell->filter_idle_id) + g_source_remove (shell->filter_idle_id); + + shell->filter_idle_id = + g_idle_add_full (G_PRIORITY_LOW, + gimp_display_shell_filter_changed_idle, + shell, NULL); +} diff --git a/app/display/gimpdisplayshell-filter.h b/app/display/gimpdisplayshell-filter.h new file mode 100644 index 0000000..7543a8b --- /dev/null +++ b/app/display/gimpdisplayshell-filter.h @@ -0,0 +1,28 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1999 Manish Singh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_FILTER_H__ +#define __GIMP_DISPLAY_SHELL_FILTER_H__ + + +void gimp_display_shell_filter_set (GimpDisplayShell *shell, + GimpColorDisplayStack *stack); + +gboolean gimp_display_shell_has_filter (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_FILTER_H__ */ diff --git a/app/display/gimpdisplayshell-grab.c b/app/display/gimpdisplayshell-grab.c new file mode 100644 index 0000000..d3bb550 --- /dev/null +++ b/app/display/gimpdisplayshell-grab.c @@ -0,0 +1,124 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdisplayshell-grab.c + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-grab.h" + + +gboolean +gimp_display_shell_pointer_grab (GimpDisplayShell *shell, + const GdkEvent *event, + GdkEventMask event_mask) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + g_return_val_if_fail (shell->pointer_grabbed == FALSE, FALSE); + + if (event) + { + GdkGrabStatus status; + + status = gdk_pointer_grab (gtk_widget_get_window (shell->canvas), + FALSE, event_mask, NULL, NULL, + gdk_event_get_time (event)); + + if (status != GDK_GRAB_SUCCESS) + { + g_printerr ("%s: gdk_pointer_grab failed with status %d\n", + G_STRFUNC, status); + return FALSE; + } + + shell->pointer_grab_time = gdk_event_get_time (event); + } + + gtk_grab_add (shell->canvas); + + shell->pointer_grabbed = TRUE; + + return TRUE; +} + +void +gimp_display_shell_pointer_ungrab (GimpDisplayShell *shell, + const GdkEvent *event) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->pointer_grabbed == TRUE); + + gtk_grab_remove (shell->canvas); + + if (event) + { + gdk_display_pointer_ungrab (gtk_widget_get_display (shell->canvas), + shell->pointer_grab_time); + + shell->pointer_grab_time = 0; + } + + shell->pointer_grabbed = FALSE; +} + +gboolean +gimp_display_shell_keyboard_grab (GimpDisplayShell *shell, + const GdkEvent *event) +{ + GdkGrabStatus status; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (shell->keyboard_grabbed == FALSE, FALSE); + + status = gdk_keyboard_grab (gtk_widget_get_window (shell->canvas), + FALSE, + gdk_event_get_time (event)); + + if (status != GDK_GRAB_SUCCESS) + { + g_printerr ("%s: gdk_keyboard_grab failed with status %d\n", + G_STRFUNC, status); + return FALSE; + } + + shell->keyboard_grabbed = TRUE; + shell->keyboard_grab_time = gdk_event_get_time (event); + + return TRUE; +} + +void +gimp_display_shell_keyboard_ungrab (GimpDisplayShell *shell, + const GdkEvent *event) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (event != NULL); + g_return_if_fail (shell->keyboard_grabbed == TRUE); + + gdk_display_keyboard_ungrab (gtk_widget_get_display (shell->canvas), + shell->keyboard_grab_time); + + shell->keyboard_grabbed = FALSE; + shell->keyboard_grab_time = 0; +} diff --git a/app/display/gimpdisplayshell-grab.h b/app/display/gimpdisplayshell-grab.h new file mode 100644 index 0000000..915cf34 --- /dev/null +++ b/app/display/gimpdisplayshell-grab.h @@ -0,0 +1,36 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdisplayshell-grab.h + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_GRAB_H__ +#define __GIMP_DISPLAY_SHELL_GRAB_H__ + + +gboolean gimp_display_shell_pointer_grab (GimpDisplayShell *shell, + const GdkEvent *event, + GdkEventMask event_mask); +void gimp_display_shell_pointer_ungrab (GimpDisplayShell *shell, + const GdkEvent *event); + +gboolean gimp_display_shell_keyboard_grab (GimpDisplayShell *shell, + const GdkEvent *event); +void gimp_display_shell_keyboard_ungrab (GimpDisplayShell *shell, + const GdkEvent *event); + + +#endif /* __GIMP_DISPLAY_SHELL_GRAB_H__ */ diff --git a/app/display/gimpdisplayshell-handlers.c b/app/display/gimpdisplayshell-handlers.c new file mode 100644 index 0000000..7680467 --- /dev/null +++ b/app/display/gimpdisplayshell-handlers.c @@ -0,0 +1,1239 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpdisplayoptions.h" +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimp-cairo.h" +#include "core/gimpguide.h" +#include "core/gimpimage.h" +#include "core/gimpimage-grid.h" +#include "core/gimpimage-guides.h" +#include "core/gimpimage-quick-mask.h" +#include "core/gimpimage-sample-points.h" +#include "core/gimpitem.h" +#include "core/gimpitemstack.h" +#include "core/gimpsamplepoint.h" +#include "core/gimptreehandler.h" + +#include "vectors/gimpvectors.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvascanvasboundary.h" +#include "gimpcanvasguide.h" +#include "gimpcanvaslayerboundary.h" +#include "gimpcanvaspath.h" +#include "gimpcanvasproxygroup.h" +#include "gimpcanvassamplepoint.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-callbacks.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-handlers.h" +#include "gimpdisplayshell-icon.h" +#include "gimpdisplayshell-profile.h" +#include "gimpdisplayshell-rulers.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-selection.h" +#include "gimpdisplayshell-title.h" +#include "gimpimagewindow.h" +#include "gimpstatusbar.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_display_shell_clean_dirty_handler (GimpImage *image, + GimpDirtyMask dirty_mask, + GimpDisplayShell *shell); +static void gimp_display_shell_undo_event_handler (GimpImage *image, + GimpUndoEvent event, + GimpUndo *undo, + GimpDisplayShell *shell); +static void gimp_display_shell_grid_notify_handler (GimpGrid *grid, + GParamSpec *pspec, + GimpDisplayShell *shell); +static void gimp_display_shell_name_changed_handler (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_selection_invalidate_handler + (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_component_visibility_changed_handler + (GimpImage *image, + GimpChannelType channel, + GimpDisplayShell *shell); +static void gimp_display_shell_size_changed_detailed_handler + (GimpImage *image, + gint previous_origin_x, + gint previous_origin_y, + gint previous_width, + gint previous_height, + GimpDisplayShell *shell); +static void gimp_display_shell_resolution_changed_handler (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_quick_mask_changed_handler (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_guide_add_handler (GimpImage *image, + GimpGuide *guide, + GimpDisplayShell *shell); +static void gimp_display_shell_guide_remove_handler (GimpImage *image, + GimpGuide *guide, + GimpDisplayShell *shell); +static void gimp_display_shell_guide_move_handler (GimpImage *image, + GimpGuide *guide, + GimpDisplayShell *shell); +static void gimp_display_shell_sample_point_add_handler (GimpImage *image, + GimpSamplePoint *sample_point, + GimpDisplayShell *shell); +static void gimp_display_shell_sample_point_remove_handler(GimpImage *image, + GimpSamplePoint *sample_point, + GimpDisplayShell *shell); +static void gimp_display_shell_sample_point_move_handler (GimpImage *image, + GimpSamplePoint *sample_point, + GimpDisplayShell *shell); +static void gimp_display_shell_invalidate_preview_handler (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_mode_changed_handler (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_precision_changed_handler (GimpImage *image, + GimpDisplayShell *shell); +static void gimp_display_shell_profile_changed_handler (GimpColorManaged *image, + GimpDisplayShell *shell); +static void gimp_display_shell_saved_handler (GimpImage *image, + GFile *file, + GimpDisplayShell *shell); +static void gimp_display_shell_exported_handler (GimpImage *image, + GFile *file, + GimpDisplayShell *shell); + +static void gimp_display_shell_active_vectors_handler (GimpImage *image, + GimpDisplayShell *shell); + +static void gimp_display_shell_vectors_freeze_handler (GimpVectors *vectors, + GimpDisplayShell *shell); +static void gimp_display_shell_vectors_thaw_handler (GimpVectors *vectors, + GimpDisplayShell *shell); +static void gimp_display_shell_vectors_visible_handler (GimpVectors *vectors, + GimpDisplayShell *shell); +static void gimp_display_shell_vectors_add_handler (GimpContainer *container, + GimpVectors *vectors, + GimpDisplayShell *shell); +static void gimp_display_shell_vectors_remove_handler (GimpContainer *container, + GimpVectors *vectors, + GimpDisplayShell *shell); + +static void gimp_display_shell_check_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_title_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_nav_size_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_monitor_res_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_padding_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_ants_speed_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_quality_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_color_config_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell); +static void gimp_display_shell_display_changed_handler (GimpContext *context, + GimpDisplay *display, + GimpDisplayShell *shell); + + +/* public functions */ + +void +gimp_display_shell_connect (GimpDisplayShell *shell) +{ + GimpImage *image; + GimpContainer *vectors; + GimpDisplayConfig *config; + GimpColorConfig *color_config; + GimpContext *user_context; + GList *list; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_DISPLAY (shell->display)); + + image = gimp_display_get_image (shell->display); + + g_return_if_fail (GIMP_IS_IMAGE (image)); + + vectors = gimp_image_get_vectors (image); + + config = shell->display->config; + color_config = GIMP_CORE_CONFIG (config)->color_management; + + user_context = gimp_get_user_context (shell->display->gimp); + + g_signal_connect (image, "clean", + G_CALLBACK (gimp_display_shell_clean_dirty_handler), + shell); + g_signal_connect (image, "dirty", + G_CALLBACK (gimp_display_shell_clean_dirty_handler), + shell); + g_signal_connect (image, "undo-event", + G_CALLBACK (gimp_display_shell_undo_event_handler), + shell); + + g_signal_connect (gimp_image_get_grid (image), "notify", + G_CALLBACK (gimp_display_shell_grid_notify_handler), + shell); + g_object_set (shell->grid, "grid", gimp_image_get_grid (image), NULL); + + g_signal_connect (image, "name-changed", + G_CALLBACK (gimp_display_shell_name_changed_handler), + shell); + g_signal_connect (image, "selection-invalidate", + G_CALLBACK (gimp_display_shell_selection_invalidate_handler), + shell); + g_signal_connect (image, "component-visibility-changed", + G_CALLBACK (gimp_display_shell_component_visibility_changed_handler), + shell); + g_signal_connect (image, "size-changed-detailed", + G_CALLBACK (gimp_display_shell_size_changed_detailed_handler), + shell); + g_signal_connect (image, "resolution-changed", + G_CALLBACK (gimp_display_shell_resolution_changed_handler), + shell); + g_signal_connect (image, "quick-mask-changed", + G_CALLBACK (gimp_display_shell_quick_mask_changed_handler), + shell); + + g_signal_connect (image, "guide-added", + G_CALLBACK (gimp_display_shell_guide_add_handler), + shell); + g_signal_connect (image, "guide-removed", + G_CALLBACK (gimp_display_shell_guide_remove_handler), + shell); + g_signal_connect (image, "guide-moved", + G_CALLBACK (gimp_display_shell_guide_move_handler), + shell); + for (list = gimp_image_get_guides (image); + list; + list = g_list_next (list)) + { + gimp_display_shell_guide_add_handler (image, list->data, shell); + } + + g_signal_connect (image, "sample-point-added", + G_CALLBACK (gimp_display_shell_sample_point_add_handler), + shell); + g_signal_connect (image, "sample-point-removed", + G_CALLBACK (gimp_display_shell_sample_point_remove_handler), + shell); + g_signal_connect (image, "sample-point-moved", + G_CALLBACK (gimp_display_shell_sample_point_move_handler), + shell); + for (list = gimp_image_get_sample_points (image); + list; + list = g_list_next (list)) + { + gimp_display_shell_sample_point_add_handler (image, list->data, shell); + } + + g_signal_connect (image, "invalidate-preview", + G_CALLBACK (gimp_display_shell_invalidate_preview_handler), + shell); + g_signal_connect (image, "mode-changed", + G_CALLBACK (gimp_display_shell_mode_changed_handler), + shell); + g_signal_connect (image, "precision-changed", + G_CALLBACK (gimp_display_shell_precision_changed_handler), + shell); + g_signal_connect (image, "profile-changed", + G_CALLBACK (gimp_display_shell_profile_changed_handler), + shell); + g_signal_connect (image, "saved", + G_CALLBACK (gimp_display_shell_saved_handler), + shell); + g_signal_connect (image, "exported", + G_CALLBACK (gimp_display_shell_exported_handler), + shell); + + g_signal_connect (image, "active-vectors-changed", + G_CALLBACK (gimp_display_shell_active_vectors_handler), + shell); + + shell->vectors_freeze_handler = + gimp_tree_handler_connect (vectors, "freeze", + G_CALLBACK (gimp_display_shell_vectors_freeze_handler), + shell); + shell->vectors_thaw_handler = + gimp_tree_handler_connect (vectors, "thaw", + G_CALLBACK (gimp_display_shell_vectors_thaw_handler), + shell); + shell->vectors_visible_handler = + gimp_tree_handler_connect (vectors, "visibility-changed", + G_CALLBACK (gimp_display_shell_vectors_visible_handler), + shell); + + g_signal_connect (vectors, "add", + G_CALLBACK (gimp_display_shell_vectors_add_handler), + shell); + g_signal_connect (vectors, "remove", + G_CALLBACK (gimp_display_shell_vectors_remove_handler), + shell); + for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (vectors)); + list; + list = g_list_next (list)) + { + gimp_display_shell_vectors_add_handler (vectors, list->data, shell); + } + + g_signal_connect (config, + "notify::transparency-size", + G_CALLBACK (gimp_display_shell_check_notify_handler), + shell); + g_signal_connect (config, + "notify::transparency-type", + G_CALLBACK (gimp_display_shell_check_notify_handler), + shell); + + g_signal_connect (config, + "notify::image-title-format", + G_CALLBACK (gimp_display_shell_title_notify_handler), + shell); + g_signal_connect (config, + "notify::image-status-format", + G_CALLBACK (gimp_display_shell_title_notify_handler), + shell); + g_signal_connect (config, + "notify::navigation-preview-size", + G_CALLBACK (gimp_display_shell_nav_size_notify_handler), + shell); + g_signal_connect (config, + "notify::monitor-resolution-from-windowing-system", + G_CALLBACK (gimp_display_shell_monitor_res_notify_handler), + shell); + g_signal_connect (config, + "notify::monitor-xresolution", + G_CALLBACK (gimp_display_shell_monitor_res_notify_handler), + shell); + g_signal_connect (config, + "notify::monitor-yresolution", + G_CALLBACK (gimp_display_shell_monitor_res_notify_handler), + shell); + + g_signal_connect (config->default_view, + "notify::padding-mode", + G_CALLBACK (gimp_display_shell_padding_notify_handler), + shell); + g_signal_connect (config->default_view, + "notify::padding-color", + G_CALLBACK (gimp_display_shell_padding_notify_handler), + shell); + g_signal_connect (config->default_fullscreen_view, + "notify::padding-mode", + G_CALLBACK (gimp_display_shell_padding_notify_handler), + shell); + g_signal_connect (config->default_fullscreen_view, + "notify::padding-color", + G_CALLBACK (gimp_display_shell_padding_notify_handler), + shell); + + g_signal_connect (config, + "notify::marching-ants-speed", + G_CALLBACK (gimp_display_shell_ants_speed_notify_handler), + shell); + + g_signal_connect (config, + "notify::zoom-quality", + G_CALLBACK (gimp_display_shell_quality_notify_handler), + shell); + + g_signal_connect (color_config, "notify", + G_CALLBACK (gimp_display_shell_color_config_notify_handler), + shell); + + g_signal_connect (user_context, "display-changed", + G_CALLBACK (gimp_display_shell_display_changed_handler), + shell); + + gimp_display_shell_active_vectors_handler (image, shell); + gimp_display_shell_invalidate_preview_handler (image, shell); + gimp_display_shell_quick_mask_changed_handler (image, shell); + gimp_display_shell_profile_changed_handler (GIMP_COLOR_MANAGED (image), + shell); + gimp_display_shell_color_config_notify_handler (G_OBJECT (color_config), + NULL, /* sync all */ + shell); + + gimp_canvas_layer_boundary_set_layer (GIMP_CANVAS_LAYER_BOUNDARY (shell->layer_boundary), + gimp_image_get_active_layer (image)); + + gimp_canvas_canvas_boundary_set_image (GIMP_CANVAS_CANVAS_BOUNDARY (shell->canvas_boundary), + image); + + if (shell->show_all) + { + gimp_image_inc_show_all_count (image); + + gimp_image_flush (image); + } +} + +void +gimp_display_shell_disconnect (GimpDisplayShell *shell) +{ + GimpImage *image; + GimpContainer *vectors; + GimpDisplayConfig *config; + GimpColorConfig *color_config; + GimpContext *user_context; + GList *list; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_DISPLAY (shell->display)); + + image = gimp_display_get_image (shell->display); + + g_return_if_fail (GIMP_IS_IMAGE (image)); + + vectors = gimp_image_get_vectors (image); + + config = shell->display->config; + color_config = GIMP_CORE_CONFIG (config)->color_management; + + user_context = gimp_get_user_context (shell->display->gimp); + + gimp_display_shell_icon_update_stop (shell); + + gimp_canvas_layer_boundary_set_layer (GIMP_CANVAS_LAYER_BOUNDARY (shell->layer_boundary), + NULL); + + gimp_canvas_canvas_boundary_set_image (GIMP_CANVAS_CANVAS_BOUNDARY (shell->canvas_boundary), + NULL); + + g_signal_handlers_disconnect_by_func (user_context, + gimp_display_shell_display_changed_handler, + shell); + + g_signal_handlers_disconnect_by_func (color_config, + gimp_display_shell_color_config_notify_handler, + shell); + shell->color_config_set = FALSE; + + g_signal_handlers_disconnect_by_func (config, + gimp_display_shell_quality_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config, + gimp_display_shell_ants_speed_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config->default_fullscreen_view, + gimp_display_shell_padding_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config->default_view, + gimp_display_shell_padding_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config, + gimp_display_shell_monitor_res_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config, + gimp_display_shell_nav_size_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config, + gimp_display_shell_title_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (config, + gimp_display_shell_check_notify_handler, + shell); + + g_signal_handlers_disconnect_by_func (vectors, + gimp_display_shell_vectors_remove_handler, + shell); + g_signal_handlers_disconnect_by_func (vectors, + gimp_display_shell_vectors_add_handler, + shell); + + gimp_tree_handler_disconnect (shell->vectors_visible_handler); + shell->vectors_visible_handler = NULL; + + gimp_tree_handler_disconnect (shell->vectors_thaw_handler); + shell->vectors_thaw_handler = NULL; + + gimp_tree_handler_disconnect (shell->vectors_freeze_handler); + shell->vectors_freeze_handler = NULL; + + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_active_vectors_handler, + shell); + + for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (vectors)); + list; + list = g_list_next (list)) + { + gimp_canvas_proxy_group_remove_item (GIMP_CANVAS_PROXY_GROUP (shell->vectors), + list->data); + } + + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_exported_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_saved_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_profile_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_precision_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_mode_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_invalidate_preview_handler, + shell); + + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_guide_add_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_guide_remove_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_guide_move_handler, + shell); + for (list = gimp_image_get_guides (image); + list; + list = g_list_next (list)) + { + gimp_canvas_proxy_group_remove_item (GIMP_CANVAS_PROXY_GROUP (shell->guides), + list->data); + } + + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_sample_point_add_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_sample_point_remove_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_sample_point_move_handler, + shell); + for (list = gimp_image_get_sample_points (image); + list; + list = g_list_next (list)) + { + gimp_canvas_proxy_group_remove_item (GIMP_CANVAS_PROXY_GROUP (shell->sample_points), + list->data); + } + + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_quick_mask_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_resolution_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_component_visibility_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_size_changed_detailed_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_selection_invalidate_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_name_changed_handler, + shell); + g_signal_handlers_disconnect_by_func (gimp_image_get_grid (image), + gimp_display_shell_grid_notify_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_undo_event_handler, + shell); + g_signal_handlers_disconnect_by_func (image, + gimp_display_shell_clean_dirty_handler, + shell); + + if (shell->show_all) + { + gimp_image_dec_show_all_count (image); + + gimp_image_flush (image); + } +} + + +/* private functions */ + +static void +gimp_display_shell_clean_dirty_handler (GimpImage *image, + GimpDirtyMask dirty_mask, + GimpDisplayShell *shell) +{ + gimp_display_shell_title_update (shell); +} + +static void +gimp_display_shell_undo_event_handler (GimpImage *image, + GimpUndoEvent event, + GimpUndo *undo, + GimpDisplayShell *shell) +{ + gimp_display_shell_title_update (shell); +} + +static void +gimp_display_shell_grid_notify_handler (GimpGrid *grid, + GParamSpec *pspec, + GimpDisplayShell *shell) +{ + g_object_set (shell->grid, "grid", grid, NULL); +} + +static void +gimp_display_shell_name_changed_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + gimp_display_shell_title_update (shell); +} + +static void +gimp_display_shell_selection_invalidate_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + gimp_display_shell_selection_undraw (shell); +} + +static void +gimp_display_shell_resolution_changed_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + gimp_display_shell_scale_update (shell); + + if (shell->dot_for_dot) + { + if (shell->unit != GIMP_UNIT_PIXEL) + { + gimp_display_shell_rulers_update (shell); + } + + gimp_display_shell_scaled (shell); + } + else + { + /* A resolution change has the same effect as a size change from + * a display shell point of view. Force a redraw of the display + * so that we don't get any display garbage. + */ + + GimpDisplayConfig *config = shell->display->config; + gboolean resize_window; + + /* Resize windows only in multi-window mode */ + resize_window = (config->resize_windows_on_resize && + ! GIMP_GUI_CONFIG (config)->single_window_mode); + + gimp_display_shell_scale_resize (shell, resize_window, FALSE); + } +} + +static void +gimp_display_shell_quick_mask_changed_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + GtkImage *gtk_image; + gboolean quick_mask_state; + + gtk_image = GTK_IMAGE (gtk_bin_get_child (GTK_BIN (shell->quick_mask_button))); + + g_signal_handlers_block_by_func (shell->quick_mask_button, + gimp_display_shell_quick_mask_toggled, + shell); + + quick_mask_state = gimp_image_get_quick_mask_state (image); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (shell->quick_mask_button), + quick_mask_state); + + if (quick_mask_state) + gtk_image_set_from_icon_name (gtk_image, GIMP_ICON_QUICK_MASK_ON, + GTK_ICON_SIZE_MENU); + else + gtk_image_set_from_icon_name (gtk_image, GIMP_ICON_QUICK_MASK_OFF, + GTK_ICON_SIZE_MENU); + + g_signal_handlers_unblock_by_func (shell->quick_mask_button, + gimp_display_shell_quick_mask_toggled, + shell); +} + +static void +gimp_display_shell_guide_add_handler (GimpImage *image, + GimpGuide *guide, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->guides); + GimpCanvasItem *item; + GimpGuideStyle style; + + style = gimp_guide_get_style (guide); + item = gimp_canvas_guide_new (shell, + gimp_guide_get_orientation (guide), + gimp_guide_get_position (guide), + style); + + gimp_canvas_proxy_group_add_item (group, guide, item); + g_object_unref (item); +} + +static void +gimp_display_shell_guide_remove_handler (GimpImage *image, + GimpGuide *guide, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->guides); + + gimp_canvas_proxy_group_remove_item (group, guide); +} + +static void +gimp_display_shell_guide_move_handler (GimpImage *image, + GimpGuide *guide, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->guides); + GimpCanvasItem *item; + + item = gimp_canvas_proxy_group_get_item (group, guide); + + gimp_canvas_guide_set (item, + gimp_guide_get_orientation (guide), + gimp_guide_get_position (guide)); +} + +static void +gimp_display_shell_sample_point_add_handler (GimpImage *image, + GimpSamplePoint *sample_point, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->sample_points); + GimpCanvasItem *item; + GList *list; + gint x; + gint y; + gint i; + + gimp_sample_point_get_position (sample_point, &x, &y); + + item = gimp_canvas_sample_point_new (shell, x, y, 0, TRUE); + + gimp_canvas_proxy_group_add_item (group, sample_point, item); + g_object_unref (item); + + for (list = gimp_image_get_sample_points (image), i = 1; + list; + list = g_list_next (list), i++) + { + GimpSamplePoint *sample_point = list->data; + + item = gimp_canvas_proxy_group_get_item (group, sample_point); + + if (item) + g_object_set (item, + "index", i, + NULL); + } +} + +static void +gimp_display_shell_sample_point_remove_handler (GimpImage *image, + GimpSamplePoint *sample_point, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->sample_points); + GList *list; + gint i; + + gimp_canvas_proxy_group_remove_item (group, sample_point); + + for (list = gimp_image_get_sample_points (image), i = 1; + list; + list = g_list_next (list), i++) + { + GimpSamplePoint *sample_point = list->data; + GimpCanvasItem *item; + + item = gimp_canvas_proxy_group_get_item (group, sample_point); + + if (item) + g_object_set (item, + "index", i, + NULL); + } +} + +static void +gimp_display_shell_sample_point_move_handler (GimpImage *image, + GimpSamplePoint *sample_point, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->sample_points); + GimpCanvasItem *item; + gint x; + gint y; + + item = gimp_canvas_proxy_group_get_item (group, sample_point); + + gimp_sample_point_get_position (sample_point, &x, &y); + + gimp_canvas_sample_point_set (item, x, y); +} + +static void +gimp_display_shell_component_visibility_changed_handler (GimpImage *image, + GimpChannelType channel, + GimpDisplayShell *shell) +{ + if (channel == GIMP_CHANNEL_ALPHA && shell->show_all) + gimp_display_shell_expose_full (shell); +} + +static void +gimp_display_shell_size_changed_detailed_handler (GimpImage *image, + gint previous_origin_x, + gint previous_origin_y, + gint previous_width, + gint previous_height, + GimpDisplayShell *shell) +{ + GimpDisplayConfig *config = shell->display->config; + gboolean resize_window; + + /* Resize windows only in multi-window mode */ + resize_window = (config->resize_windows_on_resize && + ! GIMP_GUI_CONFIG (config)->single_window_mode); + + if (resize_window) + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + /* If the window is resized just center the image in it when it + * has change size + */ + gimp_image_window_shrink_wrap (window, FALSE); + } + } + else + { + GimpImage *image = gimp_display_get_image (shell->display); + gint new_width = gimp_image_get_width (image); + gint new_height = gimp_image_get_height (image); + gint scaled_previous_origin_x; + gint scaled_previous_origin_y; + gboolean horizontally; + gboolean vertically; + + scaled_previous_origin_x = SCALEX (shell, previous_origin_x); + scaled_previous_origin_y = SCALEY (shell, previous_origin_y); + + horizontally = (SCALEX (shell, previous_width) > shell->disp_width && + SCALEX (shell, new_width) <= shell->disp_width); + vertically = (SCALEY (shell, previous_height) > shell->disp_height && + SCALEY (shell, new_height) <= shell->disp_height); + + gimp_display_shell_scroll_set_offset (shell, + shell->offset_x + scaled_previous_origin_x, + shell->offset_y + scaled_previous_origin_y); + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + gimp_display_shell_scroll_center_image (shell, + horizontally, vertically); + } + + /* The above calls might not lead to a call to + * gimp_display_shell_scroll_clamp_and_update() and + * gimp_display_shell_expose_full() in all cases because when + * scaling the old and new scroll offset might be the same. + * + * We need them to be called in all cases, so simply call them + * explicitly here at the end + */ + gimp_display_shell_scroll_clamp_and_update (shell); + + gimp_display_shell_expose_full (shell); + } +} + +static void +gimp_display_shell_invalidate_preview_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + gimp_display_shell_icon_update (shell); +} + +static void +gimp_display_shell_mode_changed_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + gimp_display_shell_profile_update (shell); +} + +static void +gimp_display_shell_precision_changed_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + gimp_display_shell_profile_update (shell); +} + +static void +gimp_display_shell_profile_changed_handler (GimpColorManaged *image, + GimpDisplayShell *shell) +{ + gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell)); +} + +static void +gimp_display_shell_saved_handler (GimpImage *image, + GFile *file, + GimpDisplayShell *shell) +{ + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_statusbar_push_temp (statusbar, GIMP_MESSAGE_INFO, + GIMP_ICON_DOCUMENT_SAVE, + _("Image saved to '%s'"), + gimp_file_get_utf8_name (file)); +} + +static void +gimp_display_shell_exported_handler (GimpImage *image, + GFile *file, + GimpDisplayShell *shell) +{ + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_statusbar_push_temp (statusbar, GIMP_MESSAGE_INFO, + GIMP_ICON_DOCUMENT_SAVE, + _("Image exported to '%s'"), + gimp_file_get_utf8_name (file)); +} + +static void +gimp_display_shell_active_vectors_handler (GimpImage *image, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors); + GimpVectors *active = gimp_image_get_active_vectors (image); + GList *list; + + for (list = gimp_image_get_vectors_iter (image); + list; + list = g_list_next (list)) + { + GimpVectors *vectors = list->data; + GimpCanvasItem *item; + + item = gimp_canvas_proxy_group_get_item (group, vectors); + + gimp_canvas_item_set_highlight (item, vectors == active); + } +} + +static void +gimp_display_shell_vectors_freeze_handler (GimpVectors *vectors, + GimpDisplayShell *shell) +{ + /* do nothing */ +} + +static void +gimp_display_shell_vectors_thaw_handler (GimpVectors *vectors, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors); + GimpCanvasItem *item; + + item = gimp_canvas_proxy_group_get_item (group, vectors); + + gimp_canvas_path_set (item, gimp_vectors_get_bezier (vectors)); +} + +static void +gimp_display_shell_vectors_visible_handler (GimpVectors *vectors, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors); + GimpCanvasItem *item; + + item = gimp_canvas_proxy_group_get_item (group, vectors); + + gimp_canvas_item_set_visible (item, + gimp_item_get_visible (GIMP_ITEM (vectors))); +} + +static void +gimp_display_shell_vectors_add_handler (GimpContainer *container, + GimpVectors *vectors, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors); + GimpCanvasItem *item; + + item = gimp_canvas_path_new (shell, + gimp_vectors_get_bezier (vectors), + 0, 0, + FALSE, + GIMP_PATH_STYLE_VECTORS); + gimp_canvas_item_set_visible (item, + gimp_item_get_visible (GIMP_ITEM (vectors))); + + gimp_canvas_proxy_group_add_item (group, vectors, item); + g_object_unref (item); +} + +static void +gimp_display_shell_vectors_remove_handler (GimpContainer *container, + GimpVectors *vectors, + GimpDisplayShell *shell) +{ + GimpCanvasProxyGroup *group = GIMP_CANVAS_PROXY_GROUP (shell->vectors); + + gimp_canvas_proxy_group_remove_item (group, vectors); +} + +static void +gimp_display_shell_check_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + GimpCanvasPaddingMode padding_mode; + GimpRGB padding_color; + + g_clear_pointer (&shell->checkerboard, cairo_pattern_destroy); + + gimp_display_shell_get_padding (shell, &padding_mode, &padding_color); + + switch (padding_mode) + { + case GIMP_CANVAS_PADDING_MODE_LIGHT_CHECK: + case GIMP_CANVAS_PADDING_MODE_DARK_CHECK: + gimp_display_shell_set_padding (shell, padding_mode, &padding_color); + break; + + default: + break; + } + + gimp_display_shell_expose_full (shell); +} + +static void +gimp_display_shell_title_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + gimp_display_shell_title_update (shell); +} + +static void +gimp_display_shell_nav_size_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + g_clear_pointer (&shell->nav_popup, gtk_widget_destroy); +} + +static void +gimp_display_shell_monitor_res_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + if (GIMP_DISPLAY_CONFIG (config)->monitor_res_from_gdk) + { + gimp_get_monitor_resolution (gtk_widget_get_screen (GTK_WIDGET (shell)), + gimp_widget_get_monitor (GTK_WIDGET (shell)), + &shell->monitor_xres, + &shell->monitor_yres); + } + else + { + shell->monitor_xres = GIMP_DISPLAY_CONFIG (config)->monitor_xres; + shell->monitor_yres = GIMP_DISPLAY_CONFIG (config)->monitor_yres; + } + + gimp_display_shell_scale_update (shell); + + if (! shell->dot_for_dot) + { + gimp_display_shell_scroll_clamp_and_update (shell); + + gimp_display_shell_scaled (shell); + + gimp_display_shell_expose_full (shell); + } +} + +static void +gimp_display_shell_padding_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + GimpDisplayConfig *display_config; + GimpImageWindow *window; + gboolean fullscreen; + GimpCanvasPaddingMode padding_mode; + GimpRGB padding_color; + + display_config = shell->display->config; + + window = gimp_display_shell_get_window (shell); + + if (window) + fullscreen = gimp_image_window_get_fullscreen (window); + else + fullscreen = FALSE; + + /* if the user did not set the padding mode for this display explicitly */ + if (! shell->fullscreen_options->padding_mode_set) + { + padding_mode = display_config->default_fullscreen_view->padding_mode; + padding_color = display_config->default_fullscreen_view->padding_color; + + if (fullscreen) + { + gimp_display_shell_set_padding (shell, padding_mode, &padding_color); + } + else + { + shell->fullscreen_options->padding_mode = padding_mode; + shell->fullscreen_options->padding_color = padding_color; + } + } + + /* if the user did not set the padding mode for this display explicitly */ + if (! shell->options->padding_mode_set) + { + padding_mode = display_config->default_view->padding_mode; + padding_color = display_config->default_view->padding_color; + + if (fullscreen) + { + shell->options->padding_mode = padding_mode; + shell->options->padding_color = padding_color; + } + else + { + gimp_display_shell_set_padding (shell, padding_mode, &padding_color); + } + } +} + +static void +gimp_display_shell_ants_speed_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + gimp_display_shell_selection_pause (shell); + gimp_display_shell_selection_resume (shell); +} + +static void +gimp_display_shell_quality_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + gimp_display_shell_expose_full (shell); +} + +static void +gimp_display_shell_color_config_notify_handler (GObject *config, + GParamSpec *param_spec, + GimpDisplayShell *shell) +{ + if (param_spec) + { + gboolean copy = TRUE; + + if (! strcmp (param_spec->name, "mode") || + ! strcmp (param_spec->name, "display-rendering-intent") || + ! strcmp (param_spec->name, "display-use-black-point-compensation") || + ! strcmp (param_spec->name, "printer-profile") || + ! strcmp (param_spec->name, "simulation-rendering-intent") || + ! strcmp (param_spec->name, "simulation-use-black-point-compensation") || + ! strcmp (param_spec->name, "simulation-gamut-check")) + { + if (shell->color_config_set) + copy = FALSE; + } + + if (copy) + { + GValue value = G_VALUE_INIT; + + g_value_init (&value, param_spec->value_type); + + g_object_get_property (config, + param_spec->name, &value); + g_object_set_property (G_OBJECT (shell->color_config), + param_spec->name, &value); + + g_value_unset (&value); + } + } + else + { + gimp_config_copy (GIMP_CONFIG (config), + GIMP_CONFIG (shell->color_config), + 0); + shell->color_config_set = FALSE; + } +} + +static void +gimp_display_shell_display_changed_handler (GimpContext *context, + GimpDisplay *display, + GimpDisplayShell *shell) +{ + if (shell->display == display) + gimp_display_shell_update_priority_rect (shell); +} diff --git a/app/display/gimpdisplayshell-handlers.h b/app/display/gimpdisplayshell-handlers.h new file mode 100644 index 0000000..49d743e --- /dev/null +++ b/app/display/gimpdisplayshell-handlers.h @@ -0,0 +1,26 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_HANDLERS_H__ +#define __GIMP_DISPLAY_SHELL_HANDLERS_H__ + + +void gimp_display_shell_connect (GimpDisplayShell *shell); +void gimp_display_shell_disconnect (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_HANDLERS_H__ */ diff --git a/app/display/gimpdisplayshell-icon.c b/app/display/gimpdisplayshell-icon.c new file mode 100644 index 0000000..6168820 --- /dev/null +++ b/app/display/gimpdisplayshell-icon.c @@ -0,0 +1,140 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-icon.h" + + +#define GIMP_DISPLAY_UPDATE_ICON_TIMEOUT 1000 + +static gboolean gimp_display_shell_icon_update_idle (gpointer data); + + +/* public functions */ + +void +gimp_display_shell_icon_update (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_icon_update_stop (shell); + + if (gimp_display_get_image (shell->display)) + shell->icon_idle_id = g_timeout_add_full (G_PRIORITY_LOW, + GIMP_DISPLAY_UPDATE_ICON_TIMEOUT, + gimp_display_shell_icon_update_idle, + shell, + NULL); + else + gimp_display_shell_icon_update_idle (shell); +} + +void +gimp_display_shell_icon_update_stop (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->icon_idle_id) + { + g_source_remove (shell->icon_idle_id); + shell->icon_idle_id = 0; + } +} + + +/* private functions */ + +static gboolean +gimp_display_shell_icon_update_idle (gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + GimpImage *image = gimp_display_get_image (shell->display); + GdkPixbuf *icon = NULL; + + shell->icon_idle_id = 0; + + if (image) + { + Gimp *gimp = gimp_display_get_gimp (shell->display); + GdkPixbuf *pixbuf; + gint width; + gint height; + gdouble factor = ((gdouble) gimp_image_get_height (image) / + (gdouble) gimp_image_get_width (image)); + + if (factor >= 1) + { + height = MAX (shell->icon_size, 1); + width = MAX (((gdouble) shell->icon_size) / factor, 1); + } + else + { + height = MAX (((gdouble) shell->icon_size) * factor, 1); + width = MAX (shell->icon_size, 1); + } + + pixbuf = gimp_viewable_get_pixbuf (GIMP_VIEWABLE (image), + gimp_get_user_context (gimp), + width, height); + + icon = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + shell->icon_size, shell->icon_size); + + memset (gdk_pixbuf_get_pixels (icon), 0, + gdk_pixbuf_get_height (icon) * + gdk_pixbuf_get_rowstride (icon)); + + gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height, + icon, + 0, shell->icon_size - height); + + pixbuf = gimp_widget_load_icon (GTK_WIDGET (shell), "gimp-wilber-outline", + shell->icon_size_small); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + gdk_pixbuf_composite (pixbuf, icon, + shell->icon_size - width, 0, + width, height, + shell->icon_size - width, 0.0, 1.0, 1.0, + GDK_INTERP_NEAREST, 255); + g_object_unref (pixbuf); + } + + g_object_set (shell, "icon", icon, NULL); + + if (icon) + g_object_unref (icon); + + return FALSE; +} diff --git a/app/display/gimpdisplayshell-icon.h b/app/display/gimpdisplayshell-icon.h new file mode 100644 index 0000000..6d2a673 --- /dev/null +++ b/app/display/gimpdisplayshell-icon.h @@ -0,0 +1,26 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_ICON_H__ +#define __GIMP_DISPLAY_SHELL_ICON_H__ + + +void gimp_display_shell_icon_update (GimpDisplayShell *shell); +void gimp_display_shell_icon_update_stop (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_ICON_H__ */ diff --git a/app/display/gimpdisplayshell-items.c b/app/display/gimpdisplayshell-items.c new file mode 100644 index 0000000..caa381a --- /dev/null +++ b/app/display/gimpdisplayshell-items.c @@ -0,0 +1,284 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdisplayshell-items.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include <libgimpmath/gimpmath.h> + +#include "display-types.h" + +#include "gimpcanvascanvasboundary.h" +#include "gimpcanvascursor.h" +#include "gimpcanvasgrid.h" +#include "gimpcanvaslayerboundary.h" +#include "gimpcanvaspassepartout.h" +#include "gimpcanvasproxygroup.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-items.h" +#include "gimpdisplayshell-transform.h" + + +/* local function prototypes */ + +static void gimp_display_shell_item_update (GimpCanvasItem *item, + cairo_region_t *region, + GimpDisplayShell *shell); +static void gimp_display_shell_unrotated_item_update (GimpCanvasItem *item, + cairo_region_t *region, + GimpDisplayShell *shell); + + +/* public functions */ + +void +gimp_display_shell_items_init (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + shell->canvas_item = gimp_canvas_group_new (shell); + + shell->passe_partout = gimp_canvas_passe_partout_new (shell, 0, 0, 0, 0); + gimp_canvas_item_set_visible (shell->passe_partout, FALSE); + gimp_display_shell_add_item (shell, shell->passe_partout); + g_object_unref (shell->passe_partout); + + shell->preview_items = gimp_canvas_group_new (shell); + gimp_display_shell_add_item (shell, shell->preview_items); + g_object_unref (shell->preview_items); + + shell->vectors = gimp_canvas_proxy_group_new (shell); + gimp_display_shell_add_item (shell, shell->vectors); + g_object_unref (shell->vectors); + + shell->grid = gimp_canvas_grid_new (shell, NULL); + gimp_canvas_item_set_visible (shell->grid, FALSE); + g_object_set (shell->grid, "grid-style", TRUE, NULL); + gimp_display_shell_add_item (shell, shell->grid); + g_object_unref (shell->grid); + + shell->guides = gimp_canvas_proxy_group_new (shell); + gimp_display_shell_add_item (shell, shell->guides); + g_object_unref (shell->guides); + + shell->sample_points = gimp_canvas_proxy_group_new (shell); + gimp_display_shell_add_item (shell, shell->sample_points); + g_object_unref (shell->sample_points); + + shell->canvas_boundary = gimp_canvas_canvas_boundary_new (shell); + gimp_canvas_item_set_visible (shell->canvas_boundary, FALSE); + gimp_display_shell_add_item (shell, shell->canvas_boundary); + g_object_unref (shell->canvas_boundary); + + shell->layer_boundary = gimp_canvas_layer_boundary_new (shell); + gimp_canvas_item_set_visible (shell->layer_boundary, FALSE); + gimp_display_shell_add_item (shell, shell->layer_boundary); + g_object_unref (shell->layer_boundary); + + shell->tool_items = gimp_canvas_group_new (shell); + gimp_display_shell_add_item (shell, shell->tool_items); + g_object_unref (shell->tool_items); + + g_signal_connect (shell->canvas_item, "update", + G_CALLBACK (gimp_display_shell_item_update), + shell); + + shell->unrotated_item = gimp_canvas_group_new (shell); + + shell->cursor = gimp_canvas_cursor_new (shell); + gimp_canvas_item_set_visible (shell->cursor, FALSE); + gimp_display_shell_add_unrotated_item (shell, shell->cursor); + g_object_unref (shell->cursor); + + g_signal_connect (shell->unrotated_item, "update", + G_CALLBACK (gimp_display_shell_unrotated_item_update), + shell); +} + +void +gimp_display_shell_items_free (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->canvas_item) + { + g_signal_handlers_disconnect_by_func (shell->canvas_item, + gimp_display_shell_item_update, + shell); + + g_clear_object (&shell->canvas_item); + + shell->passe_partout = NULL; + shell->preview_items = NULL; + shell->vectors = NULL; + shell->grid = NULL; + shell->guides = NULL; + shell->sample_points = NULL; + shell->canvas_boundary = NULL; + shell->layer_boundary = NULL; + shell->tool_items = NULL; + } + + if (shell->unrotated_item) + { + g_signal_handlers_disconnect_by_func (shell->unrotated_item, + gimp_display_shell_unrotated_item_update, + shell); + + g_clear_object (&shell->unrotated_item); + + shell->cursor = NULL; + } +} + +void +gimp_display_shell_add_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->canvas_item), item); +} + +void +gimp_display_shell_remove_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->canvas_item), item); +} + +void +gimp_display_shell_add_preview_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->preview_items), item); +} + +void +gimp_display_shell_remove_preview_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->preview_items), item); +} + +void +gimp_display_shell_add_unrotated_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->unrotated_item), item); +} + +void +gimp_display_shell_remove_unrotated_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->unrotated_item), item); +} + +void +gimp_display_shell_add_tool_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (shell->tool_items), item); +} + +void +gimp_display_shell_remove_tool_item (GimpDisplayShell *shell, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (shell->tool_items), item); +} + + +/* private functions */ + +static void +gimp_display_shell_item_update (GimpCanvasItem *item, + cairo_region_t *region, + GimpDisplayShell *shell) +{ + if (shell->rotate_transform) + { + gint n_rects; + gint i; + + n_rects = cairo_region_num_rectangles (region); + + for (i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + gdouble tx1, ty1; + gdouble tx2, ty2; + gint x1, y1, x2, y2; + + cairo_region_get_rectangle (region, i, &rect); + + gimp_display_shell_rotate_bounds (shell, + rect.x, rect.y, + rect.x + rect.width, + rect.y + rect.height, + &tx1, &ty1, &tx2, &ty2); + + x1 = floor (tx1 - 0.5); + y1 = floor (ty1 - 0.5); + x2 = ceil (tx2 + 0.5); + y2 = ceil (ty2 + 0.5); + + gimp_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1); + } + } + else + { + gimp_display_shell_expose_region (shell, region); + } +} + +static void +gimp_display_shell_unrotated_item_update (GimpCanvasItem *item, + cairo_region_t *region, + GimpDisplayShell *shell) +{ + gimp_display_shell_expose_region (shell, region); +} diff --git a/app/display/gimpdisplayshell-items.h b/app/display/gimpdisplayshell-items.h new file mode 100644 index 0000000..84f169e --- /dev/null +++ b/app/display/gimpdisplayshell-items.h @@ -0,0 +1,49 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdisplayshell-items.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_ITEMS_H__ +#define __GIMP_DISPLAY_SHELL_ITEMS_H__ + + +void gimp_display_shell_items_init (GimpDisplayShell *shell); +void gimp_display_shell_items_free (GimpDisplayShell *shell); + +void gimp_display_shell_add_item (GimpDisplayShell *shell, + GimpCanvasItem *item); +void gimp_display_shell_remove_item (GimpDisplayShell *shell, + GimpCanvasItem *item); + +void gimp_display_shell_add_preview_item (GimpDisplayShell *shell, + GimpCanvasItem *item); +void gimp_display_shell_remove_preview_item (GimpDisplayShell *shell, + GimpCanvasItem *item); + +void gimp_display_shell_add_unrotated_item (GimpDisplayShell *shell, + GimpCanvasItem *item); +void gimp_display_shell_remove_unrotated_item (GimpDisplayShell *shell, + GimpCanvasItem *item); + +void gimp_display_shell_add_tool_item (GimpDisplayShell *shell, + GimpCanvasItem *item); +void gimp_display_shell_remove_tool_item (GimpDisplayShell *shell, + GimpCanvasItem *item); + + +#endif /* __GIMP_DISPLAY_SHELL_ITEMS_H__ */ diff --git a/app/display/gimpdisplayshell-layer-select.c b/app/display/gimpdisplayshell-layer-select.c new file mode 100644 index 0000000..0618eea --- /dev/null +++ b/app/display/gimpdisplayshell-layer-select.c @@ -0,0 +1,305 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage.h" +#include "core/gimplayer.h" + +#include "widgets/gimpview.h" +#include "widgets/gimpviewrenderer.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-layer-select.h" + +#include "gimp-intl.h" + + +typedef struct +{ + GtkWidget *window; + GtkWidget *view; + GtkWidget *label; + + GimpImage *image; + GimpLayer *orig_layer; +} LayerSelect; + + +/* local function prototypes */ + +static LayerSelect * layer_select_new (GimpDisplayShell *shell, + GimpImage *image, + GimpLayer *layer, + gint view_size); +static void layer_select_destroy (LayerSelect *layer_select, + guint32 time); +static void layer_select_advance (LayerSelect *layer_select, + gint move); +static gboolean layer_select_events (GtkWidget *widget, + GdkEvent *event, + LayerSelect *layer_select); + + +/* public functions */ + +void +gimp_display_shell_layer_select_init (GimpDisplayShell *shell, + gint move, + guint32 time) +{ + LayerSelect *layer_select; + GimpImage *image; + GimpLayer *layer; + GdkGrabStatus status; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + layer = gimp_image_get_active_layer (image); + + if (! layer) + return; + + layer_select = layer_select_new (shell, image, layer, + image->gimp->config->layer_preview_size); + layer_select_advance (layer_select, move); + + gtk_window_set_screen (GTK_WINDOW (layer_select->window), + gtk_widget_get_screen (GTK_WIDGET (shell))); + + gtk_widget_show (layer_select->window); + + status = gdk_keyboard_grab (gtk_widget_get_window (layer_select->window), FALSE, time); + if (status != GDK_GRAB_SUCCESS) + g_printerr ("gdk_keyboard_grab failed with status %d\n", status); +} + + +/* private functions */ + +static LayerSelect * +layer_select_new (GimpDisplayShell *shell, + GimpImage *image, + GimpLayer *layer, + gint view_size) +{ + LayerSelect *layer_select; + GtkWidget *frame1; + GtkWidget *frame2; + GtkWidget *hbox; + GtkWidget *alignment; + + layer_select = g_slice_new0 (LayerSelect); + + layer_select->image = image; + layer_select->orig_layer = layer; + + layer_select->window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_role (GTK_WINDOW (layer_select->window), "gimp-layer-select"); + gtk_window_set_title (GTK_WINDOW (layer_select->window), _("Layer Select")); + gtk_window_set_position (GTK_WINDOW (layer_select->window), GTK_WIN_POS_MOUSE); + gtk_widget_set_events (layer_select->window, + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK); + + g_signal_connect (layer_select->window, "event", + G_CALLBACK (layer_select_events), + layer_select); + + frame1 = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame1), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (layer_select->window), frame1); + gtk_widget_show (frame1); + + frame2 = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame2), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (frame1), frame2); + gtk_widget_show (frame2); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 6); + gtk_container_add (GTK_CONTAINER (frame2), hbox); + gtk_widget_show (hbox); + + /* The view */ + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), alignment, FALSE, FALSE, 0); + gtk_widget_show (alignment); + + layer_select->view = + gimp_view_new_by_types (gimp_get_user_context (image->gimp), + GIMP_TYPE_VIEW, + GIMP_TYPE_LAYER, + view_size, 1, FALSE); + gimp_view_renderer_set_color_config (GIMP_VIEW (layer_select->view)->renderer, + gimp_display_shell_get_color_config (shell)); + gimp_view_set_viewable (GIMP_VIEW (layer_select->view), + GIMP_VIEWABLE (layer)); + gtk_container_add (GTK_CONTAINER (alignment), layer_select->view); + gtk_widget_show (layer_select->view); + gtk_widget_show (alignment); + + /* the layer name label */ + layer_select->label = gtk_label_new (gimp_object_get_name (layer)); + gtk_box_pack_start (GTK_BOX (hbox), layer_select->label, FALSE, FALSE, 0); + gtk_widget_show (layer_select->label); + + return layer_select; +} + +static void +layer_select_destroy (LayerSelect *layer_select, + guint32 time) +{ + gdk_display_keyboard_ungrab (gtk_widget_get_display (layer_select->window), + time); + + gtk_widget_destroy (layer_select->window); + + if (layer_select->orig_layer != + gimp_image_get_active_layer (layer_select->image)) + { + gimp_image_flush (layer_select->image); + } + + g_slice_free (LayerSelect, layer_select); +} + +static void +layer_select_advance (LayerSelect *layer_select, + gint move) +{ + GimpLayer *active_layer; + GimpLayer *next_layer; + GList *layers; + gint n_layers; + gint index; + + if (move == 0) + return; + + /* If there is a floating selection, allow no advancement */ + if (gimp_image_get_floating_selection (layer_select->image)) + return; + + active_layer = gimp_image_get_active_layer (layer_select->image); + + layers = gimp_image_get_layer_list (layer_select->image); + n_layers = g_list_length (layers); + + index = g_list_index (layers, active_layer); + index += move; + + if (index < 0) + index = n_layers - 1; + else if (index >= n_layers) + index = 0; + + next_layer = g_list_nth_data (layers, index); + + g_list_free (layers); + + if (next_layer && next_layer != active_layer) + { + active_layer = gimp_image_set_active_layer (layer_select->image, + next_layer); + + if (active_layer) + { + gimp_view_set_viewable (GIMP_VIEW (layer_select->view), + GIMP_VIEWABLE (active_layer)); + gtk_label_set_text (GTK_LABEL (layer_select->label), + gimp_object_get_name (active_layer)); + } + } +} + +static gboolean +layer_select_events (GtkWidget *widget, + GdkEvent *event, + LayerSelect *layer_select) +{ + GdkEventKey *kevent; + GdkEventButton *bevent; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + bevent = (GdkEventButton *) event; + + layer_select_destroy (layer_select, bevent->time); + break; + + case GDK_KEY_PRESS: + kevent = (GdkEventKey *) event; + + switch (kevent->keyval) + { + case GDK_KEY_Tab: + layer_select_advance (layer_select, 1); + break; + case GDK_KEY_ISO_Left_Tab: + layer_select_advance (layer_select, -1); + break; + } + return TRUE; + break; + + case GDK_KEY_RELEASE: + kevent = (GdkEventKey *) event; + + switch (kevent->keyval) + { + case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: + kevent->state &= ~GDK_MOD1_MASK; + break; + case GDK_KEY_Control_L: case GDK_KEY_Control_R: + kevent->state &= ~GDK_CONTROL_MASK; + break; + case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: + kevent->state &= ~GDK_SHIFT_MASK; + break; + } + + if (! (kevent->state & GDK_CONTROL_MASK)) + layer_select_destroy (layer_select, kevent->time); + + return TRUE; + break; + + default: + break; + } + + return FALSE; +} diff --git a/app/display/gimpdisplayshell-layer-select.h b/app/display/gimpdisplayshell-layer-select.h new file mode 100644 index 0000000..20546c6 --- /dev/null +++ b/app/display/gimpdisplayshell-layer-select.h @@ -0,0 +1,27 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_LAYER_SELECT_H__ +#define __GIMP_DISPLAY_SHELL_LAYER_SELECT_H__ + + +void gimp_display_shell_layer_select_init (GimpDisplayShell *shell, + gint move, + guint32 time); + + +#endif /* __GIMP_DISPLAY_SHELL_LAYER_SELECT_H__ */ diff --git a/app/display/gimpdisplayshell-profile.c b/app/display/gimpdisplayshell-profile.c new file mode 100644 index 0000000..71a6c94 --- /dev/null +++ b/app/display/gimpdisplayshell-profile.c @@ -0,0 +1,336 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpcoreconfig.h" + +#include "gegl/gimp-babl.h" + +#include "core/gimpimage.h" +#include "core/gimpprojectable.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-actions.h" +#include "gimpdisplayshell-filter.h" +#include "gimpdisplayshell-profile.h" +#include "gimpdisplayxfer.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_display_shell_profile_free (GimpDisplayShell *shell); + +static void gimp_display_shell_color_config_notify (GimpColorConfig *config, + const GParamSpec *pspec, + GimpDisplayShell *shell); + + +/* public functions */ + +void +gimp_display_shell_profile_init (GimpDisplayShell *shell) +{ + GimpColorConfig *color_config; + + color_config = GIMP_CORE_CONFIG (shell->display->config)->color_management; + + shell->color_config = gimp_config_duplicate (GIMP_CONFIG (color_config)); + + /* use after so we are called after the profile cache is invalidated + * in gimp_widget_get_color_transform() + */ + g_signal_connect_after (shell->color_config, "notify", + G_CALLBACK (gimp_display_shell_color_config_notify), + shell); +} + +void +gimp_display_shell_profile_finalize (GimpDisplayShell *shell) +{ + g_clear_object (&shell->color_config); + + gimp_display_shell_profile_free (shell); +} + +void +gimp_display_shell_profile_update (GimpDisplayShell *shell) +{ + GimpImage *image; + GimpColorProfile *src_profile; + const Babl *src_format; + GimpColorProfile *filter_profile; + const Babl *filter_format; + const Babl *dest_format; + + gimp_display_shell_profile_free (shell); + + image = gimp_display_get_image (shell->display); + + if (! image) + return; + + src_profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (shell)); + + if (! src_profile) + return; + + src_format = gimp_projectable_get_format (GIMP_PROJECTABLE (image)); + + if (gimp_display_shell_has_filter (shell)) + { + filter_format = shell->filter_format; + filter_profile = gimp_babl_format_get_color_profile (filter_format); + } + else + { + filter_format = src_format; + filter_profile = src_profile; + } + + if (! gimp_display_shell_profile_can_convert_to_u8 (shell)) + { + dest_format = shell->filter_format; + } + else + { + dest_format = babl_format ("R'G'B'A u8"); + } + +#if 0 + g_printerr ("src_profile: %s\n" + "src_format: %s\n" + "filter_profile: %s\n" + "filter_format: %s\n" + "dest_format: %s\n", + gimp_color_profile_get_label (src_profile), + babl_get_name (src_format), + gimp_color_profile_get_label (filter_profile), + babl_get_name (filter_format), + babl_get_name (dest_format)); +#endif + + if (! gimp_color_transform_can_gegl_copy (src_profile, filter_profile)) + { + shell->filter_transform = + gimp_color_transform_new (src_profile, + src_format, + filter_profile, + filter_format, + GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC, + GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION | + GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE); + } + + shell->profile_transform = + gimp_widget_get_color_transform (gtk_widget_get_toplevel (GTK_WIDGET (shell)), + gimp_display_shell_get_color_config (shell), + filter_profile, + filter_format, + dest_format); + + if (shell->filter_transform || shell->profile_transform) + { + gint w = GIMP_DISPLAY_RENDER_BUF_WIDTH; + gint h = GIMP_DISPLAY_RENDER_BUF_HEIGHT; + + shell->profile_data = + gegl_malloc (w * h * babl_format_get_bytes_per_pixel (src_format)); + + shell->profile_stride = + w * babl_format_get_bytes_per_pixel (src_format); + + shell->profile_buffer = + gegl_buffer_linear_new_from_data (shell->profile_data, + src_format, + GEGL_RECTANGLE (0, 0, w, h), + GEGL_AUTO_ROWSTRIDE, + (GDestroyNotify) gegl_free, + shell->profile_data); + } +} + +gboolean +gimp_display_shell_profile_can_convert_to_u8 (GimpDisplayShell *shell) +{ + GimpImage *image = gimp_display_get_image (shell->display); + + if (image) + { + GimpComponentType component_type; + + if (! gimp_display_shell_has_filter (shell)) + component_type = gimp_image_get_component_type (image); + else + component_type = gimp_babl_format_get_component_type (shell->filter_format); + + switch (component_type) + { + case GIMP_COMPONENT_TYPE_U8: +#if 0 + /* would like to convert directly for these too, but it + * produces inferior results, see bug 750874 + */ + case GIMP_COMPONENT_TYPE_U16: + case GIMP_COMPONENT_TYPE_U32: +#endif + return TRUE; + + default: + break; + } + } + + return FALSE; +} + + +/* private functions */ + +static void +gimp_display_shell_profile_free (GimpDisplayShell *shell) +{ + g_clear_object (&shell->profile_transform); + g_clear_object (&shell->filter_transform); + g_clear_object (&shell->profile_buffer); + shell->profile_data = NULL; + shell->profile_stride = 0; +} + +static void +gimp_display_shell_color_config_notify (GimpColorConfig *config, + const GParamSpec *pspec, + GimpDisplayShell *shell) +{ + if (! strcmp (pspec->name, "mode") || + ! strcmp (pspec->name, "display-rendering-intent") || + ! strcmp (pspec->name, "display-use-black-point-compensation") || + ! strcmp (pspec->name, "simulation-rendering-intent") || + ! strcmp (pspec->name, "simulation-use-black-point-compensation") || + ! strcmp (pspec->name, "simulation-gamut-check")) + { + gboolean managed = FALSE; + gboolean softproof = FALSE; + const gchar *action = NULL; + +#define SET_SENSITIVE(action, sensitive) \ + gimp_display_shell_set_action_sensitive (shell, action, sensitive); + +#define SET_ACTIVE(action, active) \ + gimp_display_shell_set_action_active (shell, action, active); + + switch (gimp_color_config_get_mode (config)) + { + case GIMP_COLOR_MANAGEMENT_OFF: + break; + + case GIMP_COLOR_MANAGEMENT_DISPLAY: + managed = TRUE; + break; + + case GIMP_COLOR_MANAGEMENT_SOFTPROOF: + managed = TRUE; + softproof = TRUE; + break; + } + + SET_ACTIVE ("view-color-management-enable", managed); + SET_ACTIVE ("view-color-management-softproof", softproof); + + switch (gimp_color_config_get_display_intent (config)) + { + case GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL: + action = "view-display-intent-perceptual"; + break; + + case GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC: + action = "view-display-intent-relative-colorimetric"; + break; + + case GIMP_COLOR_RENDERING_INTENT_SATURATION: + action = "view-display-intent-saturation"; + break; + + case GIMP_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: + action = "view-display-intent-absolute-colorimetric"; + break; + } + + SET_SENSITIVE ("view-display-intent-perceptual", managed); + SET_SENSITIVE ("view-display-intent-relative-colorimetric", managed); + SET_SENSITIVE ("view-display-intent-saturation", managed); + SET_SENSITIVE ("view-display-intent-absolute-colorimetric", managed); + + SET_ACTIVE (action, TRUE); + + SET_SENSITIVE ("view-display-black-point-compensation", managed); + SET_ACTIVE ("view-display-black-point-compensation", + gimp_color_config_get_display_bpc (config)); + + SET_SENSITIVE ("view-softproof-profile", softproof); + + switch (gimp_color_config_get_simulation_intent (config)) + { + case GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL: + action = "view-softproof-intent-perceptual"; + break; + + case GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC: + action = "view-softproof-intent-relative-colorimetric"; + break; + + case GIMP_COLOR_RENDERING_INTENT_SATURATION: + action = "view-softproof-intent-saturation"; + break; + + case GIMP_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: + action = "view-softproof-intent-absolute-colorimetric"; + break; + } + + SET_SENSITIVE ("view-softproof-intent-perceptual", softproof); + SET_SENSITIVE ("view-softproof-intent-relative-colorimetric", softproof); + SET_SENSITIVE ("view-softproof-intent-saturation", softproof); + SET_SENSITIVE ("view-softproof-intent-absolute-colorimetric", softproof); + + SET_ACTIVE (action, TRUE); + + SET_SENSITIVE ("view-softproof-black-point-compensation", softproof); + SET_ACTIVE ("view-softproof-black-point-compensation", + gimp_color_config_get_simulation_bpc (config)); + + SET_SENSITIVE ("view-softproof-gamut-check", softproof); + SET_ACTIVE ("view-softproof-gamut-check", + gimp_color_config_get_simulation_gamut_check (config)); + } + + gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell)); +} diff --git a/app/display/gimpdisplayshell-profile.h b/app/display/gimpdisplayshell-profile.h new file mode 100644 index 0000000..e390cac --- /dev/null +++ b/app/display/gimpdisplayshell-profile.h @@ -0,0 +1,30 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_PROFILE_H__ +#define __GIMP_DISPLAY_SHELL_PROFILE_H__ + + +void gimp_display_shell_profile_init (GimpDisplayShell *shell); +void gimp_display_shell_profile_finalize (GimpDisplayShell *shell); + +void gimp_display_shell_profile_update (GimpDisplayShell *shell); + +gboolean gimp_display_shell_profile_can_convert_to_u8 (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_PROFILE_H__ */ diff --git a/app/display/gimpdisplayshell-progress.c b/app/display/gimpdisplayshell-progress.c new file mode 100644 index 0000000..4314a84 --- /dev/null +++ b/app/display/gimpdisplayshell-progress.c @@ -0,0 +1,163 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimpprogress.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-progress.h" +#include "gimpstatusbar.h" + + +static GimpProgress * +gimp_display_shell_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + return gimp_progress_start (GIMP_PROGRESS (statusbar), cancellable, + "%s", message); +} + +static void +gimp_display_shell_progress_end (GimpProgress *progress) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_progress_end (GIMP_PROGRESS (statusbar)); +} + +static gboolean +gimp_display_shell_progress_is_active (GimpProgress *progress) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + return gimp_progress_is_active (GIMP_PROGRESS (statusbar)); +} + +static void +gimp_display_shell_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_progress_set_text_literal (GIMP_PROGRESS (statusbar), message); +} + +static void +gimp_display_shell_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_progress_set_value (GIMP_PROGRESS (statusbar), percentage); +} + +static gdouble +gimp_display_shell_progress_get_value (GimpProgress *progress) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + return gimp_progress_get_value (GIMP_PROGRESS (statusbar)); +} + +static void +gimp_display_shell_progress_pulse (GimpProgress *progress) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_progress_pulse (GIMP_PROGRESS (statusbar)); +} + +static guint32 +gimp_display_shell_progress_get_window_id (GimpProgress *progress) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (progress)); + + if (GTK_IS_WINDOW (toplevel)) + return gimp_window_get_native_id (GTK_WINDOW (toplevel)); + + return 0; +} + +static gboolean +gimp_display_shell_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (progress); + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + + switch (severity) + { + case GIMP_MESSAGE_ERROR: + case GIMP_MESSAGE_BUG_WARNING: + case GIMP_MESSAGE_BUG_CRITICAL: + /* error messages are never handled here */ + break; + + case GIMP_MESSAGE_WARNING: + /* warning messages go to the statusbar, if it's visible */ + if (! gimp_statusbar_get_visible (statusbar)) + break; + else + return gimp_progress_message (GIMP_PROGRESS (statusbar), gimp, + severity, domain, message); + + case GIMP_MESSAGE_INFO: + /* info messages go to the statusbar; + * if they are not handled there, they are swallowed + */ + gimp_progress_message (GIMP_PROGRESS (statusbar), gimp, + severity, domain, message); + return TRUE; + } + + return FALSE; +} + +void +gimp_display_shell_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_display_shell_progress_start; + iface->end = gimp_display_shell_progress_end; + iface->is_active = gimp_display_shell_progress_is_active; + iface->set_text = gimp_display_shell_progress_set_text; + iface->set_value = gimp_display_shell_progress_set_value; + iface->get_value = gimp_display_shell_progress_get_value; + iface->pulse = gimp_display_shell_progress_pulse; + iface->get_window_id = gimp_display_shell_progress_get_window_id; + iface->message = gimp_display_shell_progress_message; +} diff --git a/app/display/gimpdisplayshell-progress.h b/app/display/gimpdisplayshell-progress.h new file mode 100644 index 0000000..d697fa1 --- /dev/null +++ b/app/display/gimpdisplayshell-progress.h @@ -0,0 +1,28 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_PROGRESS_H__ +#define __GIMP_DISPLAY_SHELL_PROGRESS_H__ + + +#include "core/gimpprogress.h" + + +void gimp_display_shell_progress_iface_init (GimpProgressInterface *iface); + + +#endif /* __GIMP_DISPLAY_SHELL_PROGRESS_H__ */ diff --git a/app/display/gimpdisplayshell-render.c b/app/display/gimpdisplayshell-render.c new file mode 100644 index 0000000..a1525bf --- /dev/null +++ b/app/display/gimpdisplayshell-render.c @@ -0,0 +1,351 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "gegl/gimp-gegl-utils.h" + +#include "core/gimpdrawable.h" +#include "core/gimpimage.h" +#include "core/gimppickable.h" +#include "core/gimpprojectable.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-transform.h" +#include "gimpdisplayshell-filter.h" +#include "gimpdisplayshell-profile.h" +#include "gimpdisplayshell-render.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayxfer.h" + + +void +gimp_display_shell_render (GimpDisplayShell *shell, + cairo_t *cr, + gint x, + gint y, + gint w, + gint h, + gdouble scale) +{ + GimpImage *image; + GeglBuffer *buffer; +#ifdef USE_NODE_BLIT + GeglNode *node; +#endif + GeglAbyssPolicy abyss_policy; + cairo_surface_t *xfer; + gint xfer_src_x; + gint xfer_src_y; + gint mask_src_x = 0; + gint mask_src_y = 0; + gint cairo_stride; + guchar *cairo_data; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (cr != NULL); + g_return_if_fail (w > 0 && w <= GIMP_DISPLAY_RENDER_BUF_WIDTH); + g_return_if_fail (h > 0 && h <= GIMP_DISPLAY_RENDER_BUF_HEIGHT); + g_return_if_fail (scale > 0.0); + + image = gimp_display_get_image (shell->display); + buffer = gimp_pickable_get_buffer ( + gimp_display_shell_get_pickable (shell)); +#ifdef USE_NODE_BLIT + node = gimp_projectable_get_graph (GIMP_PROJECTABLE (image)); + + gimp_projectable_begin_render (GIMP_PROJECTABLE (image)); +#endif + + if (shell->show_all) + abyss_policy = GEGL_ABYSS_NONE; + else + abyss_policy = GEGL_ABYSS_CLAMP; + + xfer = gimp_display_xfer_get_surface (shell->xfer, w, h, + &xfer_src_x, &xfer_src_y); + + cairo_stride = cairo_image_surface_get_stride (xfer); + cairo_data = cairo_image_surface_get_data (xfer) + + xfer_src_y * cairo_stride + xfer_src_x * 4; + + if (shell->profile_transform || + gimp_display_shell_has_filter (shell)) + { + gboolean can_convert_to_u8; + + /* if there is a profile transform or a display filter, we need + * to use temp buffers + */ + + can_convert_to_u8 = gimp_display_shell_profile_can_convert_to_u8 (shell); + + /* create the filter buffer if we have filters, or can't convert + * to u8 directly + */ + if ((gimp_display_shell_has_filter (shell) || ! can_convert_to_u8) && + ! shell->filter_buffer) + { + gint fw = GIMP_DISPLAY_RENDER_BUF_WIDTH; + gint fh = GIMP_DISPLAY_RENDER_BUF_HEIGHT; + + shell->filter_data = + gegl_malloc (fw * fh * + babl_format_get_bytes_per_pixel (shell->filter_format)); + + shell->filter_stride = + fw * babl_format_get_bytes_per_pixel (shell->filter_format); + + shell->filter_buffer = + gegl_buffer_linear_new_from_data (shell->filter_data, + shell->filter_format, + GEGL_RECTANGLE (0, 0, fw, fh), + GEGL_AUTO_ROWSTRIDE, + (GDestroyNotify) gegl_free, + shell->filter_data); + } + + if (! gimp_display_shell_has_filter (shell) || shell->filter_transform) + { + /* if there are no filters, or there is a filter transform, + * load the projection pixels into the profile_buffer + */ +#ifndef USE_NODE_BLIT + gegl_buffer_get (buffer, + GEGL_RECTANGLE (x, y, w, h), scale, + gimp_projectable_get_format (GIMP_PROJECTABLE (image)), + shell->profile_data, shell->profile_stride, + abyss_policy); +#else + gegl_node_blit (node, + scale, GEGL_RECTANGLE (x, y, w, h), + gimp_projectable_get_format (GIMP_PROJECTABLE (image)), + shell->profile_data, shell->profile_stride, + GEGL_BLIT_CACHE); +#endif + } + else + { + /* otherwise, load the pixels directly into the filter_buffer + */ +#ifndef USE_NODE_BLIT + gegl_buffer_get (buffer, + GEGL_RECTANGLE (x, y, w, h), scale, + shell->filter_format, + shell->filter_data, shell->filter_stride, + abyss_policy); +#else + gegl_node_blit (node, + scale, GEGL_RECTANGLE (x, y, w, h), + shell->filter_format, + shell->filter_data, shell->filter_stride, + GEGL_BLIT_CACHE); +#endif + } + + /* if there is a filter transform, convert the pixels from + * the profile_buffer to the filter_buffer + */ + if (shell->filter_transform) + { + gimp_color_transform_process_buffer (shell->filter_transform, + shell->profile_buffer, + GEGL_RECTANGLE (0, 0, w, h), + shell->filter_buffer, + GEGL_RECTANGLE (0, 0, w, h)); + } + + /* if there are filters, apply them + */ + if (gimp_display_shell_has_filter (shell)) + { + GeglBuffer *filter_buffer; + + /* shift the filter_buffer so that the area passed to + * the filters is the real render area, allowing for + * position-dependent filters + */ + filter_buffer = g_object_new (GEGL_TYPE_BUFFER, + "source", shell->filter_buffer, + "shift-x", -x, + "shift-y", -y, + NULL); + + /* convert the filter_buffer in place + */ + gimp_color_display_stack_convert_buffer (shell->filter_stack, + filter_buffer, + GEGL_RECTANGLE (x, y, w, h)); + + g_object_unref (filter_buffer); + } + + /* if there is a profile transform... + */ + if (shell->profile_transform) + { + if (gimp_display_shell_has_filter (shell)) + { + /* if we have filters, convert the pixels in the filter_buffer + * in-place + */ + gimp_color_transform_process_buffer (shell->profile_transform, + shell->filter_buffer, + GEGL_RECTANGLE (0, 0, w, h), + shell->filter_buffer, + GEGL_RECTANGLE (0, 0, w, h)); + } + else if (! can_convert_to_u8) + { + /* otherwise, if we can't convert to u8 directly, convert + * the pixels from the profile_buffer to the filter_buffer + */ + gimp_color_transform_process_buffer (shell->profile_transform, + shell->profile_buffer, + GEGL_RECTANGLE (0, 0, w, h), + shell->filter_buffer, + GEGL_RECTANGLE (0, 0, w, h)); + } + else + { + GeglBuffer *buffer = + gegl_buffer_linear_new_from_data (cairo_data, + babl_format ("cairo-ARGB32"), + GEGL_RECTANGLE (0, 0, w, h), + cairo_stride, + NULL, NULL); + + /* otherwise, convert the profile_buffer directly into + * the cairo_buffer + */ + gimp_color_transform_process_buffer (shell->profile_transform, + shell->profile_buffer, + GEGL_RECTANGLE (0, 0, w, h), + buffer, + GEGL_RECTANGLE (0, 0, w, h)); + g_object_unref (buffer); + } + } + + /* finally, copy the filter buffer to the cairo-ARGB32 buffer, + * if necessary + */ + if (gimp_display_shell_has_filter (shell) || ! can_convert_to_u8) + { + gegl_buffer_get (shell->filter_buffer, + GEGL_RECTANGLE (0, 0, w, h), 1.0, + babl_format ("cairo-ARGB32"), + cairo_data, cairo_stride, + GEGL_ABYSS_NONE); + } + } + else + { + /* otherwise we can copy the projection pixels straight to the + * cairo-ARGB32 buffer + */ +#ifndef USE_NODE_BLIT + gegl_buffer_get (buffer, + GEGL_RECTANGLE (x, y, w, h), scale, + babl_format ("cairo-ARGB32"), + cairo_data, cairo_stride, + abyss_policy); +#else + gegl_node_blit (node, + scale, GEGL_RECTANGLE (x, y, w, h), + babl_format ("cairo-ARGB32"), + cairo_data, cairo_stride, + GEGL_BLIT_CACHE); +#endif + } + +#ifdef USE_NODE_BLIT + gimp_projectable_end_render (GIMP_PROJECTABLE (image)); +#endif + + if (shell->mask) + { + if (! shell->mask_surface) + { + shell->mask_surface = + cairo_image_surface_create (CAIRO_FORMAT_A8, + GIMP_DISPLAY_RENDER_BUF_WIDTH, + GIMP_DISPLAY_RENDER_BUF_HEIGHT); + } + + cairo_surface_mark_dirty (shell->mask_surface); + + cairo_stride = cairo_image_surface_get_stride (shell->mask_surface); + cairo_data = cairo_image_surface_get_data (shell->mask_surface) + + mask_src_y * cairo_stride + mask_src_x; + + gegl_buffer_get (shell->mask, + GEGL_RECTANGLE (x - floor (shell->mask_offset_x * scale), + y - floor (shell->mask_offset_y * scale), + w, h), + scale, + babl_format ("Y u8"), + cairo_data, cairo_stride, + GEGL_ABYSS_NONE); + + if (shell->mask_inverted) + { + gint mask_height = h; + + while (mask_height--) + { + gint mask_width = w; + guchar *d = cairo_data; + + while (mask_width--) + { + guchar inv = 255 - *d; + + *d++ = inv; + } + + cairo_data += cairo_stride; + } + } + } + + /* put it to the screen */ + cairo_set_source_surface (cr, xfer, + x - xfer_src_x, + y - xfer_src_y); + cairo_paint (cr); + + if (shell->mask) + { + gimp_cairo_set_source_rgba (cr, &shell->mask_color); + cairo_mask_surface (cr, shell->mask_surface, + x - mask_src_x, + y - mask_src_y); + } +} diff --git a/app/display/gimpdisplayshell-render.h b/app/display/gimpdisplayshell-render.h new file mode 100644 index 0000000..7b4a644 --- /dev/null +++ b/app/display/gimpdisplayshell-render.h @@ -0,0 +1,29 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_RENDER_H__ +#define __GIMP_DISPLAY_SHELL_RENDER_H__ + +void gimp_display_shell_render (GimpDisplayShell *shell, + cairo_t *cr, + gint x, + gint y, + gint w, + gint h, + gdouble scale); + +#endif /* __GIMP_DISPLAY_SHELL_RENDER_H__ */ diff --git a/app/display/gimpdisplayshell-rotate-dialog.c b/app/display/gimpdisplayshell-rotate-dialog.c new file mode 100644 index 0000000..c899235 --- /dev/null +++ b/app/display/gimpdisplayshell-rotate-dialog.c @@ -0,0 +1,293 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpviewable.h" + +#include "widgets/gimpdial.h" +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpviewabledialog.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-rotate.h" +#include "gimpdisplayshell-rotate-dialog.h" + +#include "gimp-intl.h" + + +#define RESPONSE_RESET 1 + + +typedef struct +{ + GimpDisplayShell *shell; + GtkAdjustment *rotate_adj; + gdouble old_angle; +} RotateDialogData; + + +/* local function prototypes */ + +static void gimp_display_shell_rotate_dialog_response (GtkWidget *widget, + gint response_id, + RotateDialogData *dialog); +static void gimp_display_shell_rotate_dialog_free (RotateDialogData *dialog); + +static void rotate_adjustment_changed (GtkAdjustment *adj, + RotateDialogData *dialog); +static void display_shell_rotated (GimpDisplayShell *shell, + RotateDialogData *dialog); + +static gboolean deg_to_rad (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data); +static gboolean rad_to_deg (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data); + + +/* public functions */ + +/** + * gimp_display_shell_rotate_dialog: + * @shell: the #GimpDisplayShell + * + * Constructs and displays a dialog allowing the user to enter a + * custom display rotate. + **/ +void +gimp_display_shell_rotate_dialog (GimpDisplayShell *shell) +{ + RotateDialogData *data; + GimpImage *image; + GtkWidget *toplevel; + GtkWidget *hbox; + GtkWidget *spin; + GtkWidget *dial; + GtkWidget *label; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->rotate_dialog) + { + gtk_window_present (GTK_WINDOW (shell->rotate_dialog)); + return; + } + + image = gimp_display_get_image (shell->display); + + data = g_slice_new (RotateDialogData); + + data->shell = shell; + data->old_angle = shell->rotate_angle; + + shell->rotate_dialog = + gimp_viewable_dialog_new (GIMP_VIEWABLE (image), + gimp_get_user_context (shell->display->gimp), + _("Rotate View"), "display-rotate", + GIMP_ICON_OBJECT_ROTATE_180, + _("Select Rotation Angle"), + GTK_WIDGET (shell), + gimp_standard_help_func, + GIMP_HELP_VIEW_ROTATE_OTHER, + + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (shell->rotate_dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_object_weak_ref (G_OBJECT (shell->rotate_dialog), + (GWeakNotify) gimp_display_shell_rotate_dialog_free, data); + + g_object_add_weak_pointer (G_OBJECT (shell->rotate_dialog), + (gpointer) &shell->rotate_dialog); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell)); + + gtk_window_set_transient_for (GTK_WINDOW (shell->rotate_dialog), + GTK_WINDOW (toplevel)); + gtk_window_set_destroy_with_parent (GTK_WINDOW (shell->rotate_dialog), TRUE); + + g_signal_connect (shell->rotate_dialog, "response", + G_CALLBACK (gimp_display_shell_rotate_dialog_response), + data); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell->rotate_dialog))), + hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Angle:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + data->rotate_adj = (GtkAdjustment *) + gtk_adjustment_new (shell->rotate_angle, 0.0, 360.0, 1, 15, 0); + spin = gimp_spin_button_new (data->rotate_adj, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE); + gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spin), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0); + gtk_widget_show (spin); + + label = gtk_label_new (_("degrees")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + dial = gimp_dial_new (); + g_object_set (dial, + "size", 32, + "background", GIMP_CIRCLE_BACKGROUND_PLAIN, + "draw-beta", FALSE, + NULL); + gtk_box_pack_start (GTK_BOX (hbox), dial, FALSE, FALSE, 0); + gtk_widget_show (dial); + + g_object_bind_property_full (data->rotate_adj, "value", + dial, "alpha", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE, + deg_to_rad, + rad_to_deg, + NULL, NULL); + + g_signal_connect (data->rotate_adj, "value-changed", + G_CALLBACK (rotate_adjustment_changed), + data); + g_signal_connect (shell, "rotated", + G_CALLBACK (display_shell_rotated), + data); + + gtk_widget_show (shell->rotate_dialog); +} + +static void +gimp_display_shell_rotate_dialog_response (GtkWidget *widget, + gint response_id, + RotateDialogData *dialog) +{ + switch (response_id) + { + case RESPONSE_RESET: + gtk_adjustment_set_value (dialog->rotate_adj, 0.0); + break; + + case GTK_RESPONSE_CANCEL: + gtk_adjustment_set_value (dialog->rotate_adj, dialog->old_angle); + /* fall thru */ + + default: + gtk_widget_destroy (dialog->shell->rotate_dialog); + break; + } +} + +static void +gimp_display_shell_rotate_dialog_free (RotateDialogData *dialog) +{ + g_signal_handlers_disconnect_by_func (dialog->shell, + display_shell_rotated, + dialog); + + g_slice_free (RotateDialogData, dialog); +} + +static void +rotate_adjustment_changed (GtkAdjustment *adj, + RotateDialogData *dialog) +{ + gdouble angle = gtk_adjustment_get_value (dialog->rotate_adj); + + g_signal_handlers_block_by_func (dialog->shell, + display_shell_rotated, + dialog); + + gimp_display_shell_rotate_to (dialog->shell, angle); + + g_signal_handlers_unblock_by_func (dialog->shell, + display_shell_rotated, + dialog); +} + +static void +display_shell_rotated (GimpDisplayShell *shell, + RotateDialogData *dialog) +{ + g_signal_handlers_block_by_func (dialog->rotate_adj, + rotate_adjustment_changed, + dialog); + + gtk_adjustment_set_value (dialog->rotate_adj, shell->rotate_angle); + + g_signal_handlers_unblock_by_func (dialog->rotate_adj, + rotate_adjustment_changed, + dialog); +} + +static gboolean +deg_to_rad (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + gdouble value = g_value_get_double (from_value); + + value = 360.0 - value; + + value *= G_PI / 180.0; + + g_value_set_double (to_value, value); + + return TRUE; +} + +static gboolean +rad_to_deg (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + gdouble value = g_value_get_double (from_value); + + value *= 180.0 / G_PI; + + value = 360.0 - value; + + g_value_set_double (to_value, value); + + return TRUE; +} diff --git a/app/display/gimpdisplayshell-rotate-dialog.h b/app/display/gimpdisplayshell-rotate-dialog.h new file mode 100644 index 0000000..5222d13 --- /dev/null +++ b/app/display/gimpdisplayshell-rotate-dialog.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_ROTATE_DIALOG_H__ +#define __GIMP_DISPLAY_SHELL_ROTATE_DIALOG_H__ + + +void gimp_display_shell_rotate_dialog (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_ROTATE_DIALOG_H__ */ diff --git a/app/display/gimpdisplayshell-rotate.c b/app/display/gimpdisplayshell-rotate.c new file mode 100644 index 0000000..d14f2a5 --- /dev/null +++ b/app/display/gimpdisplayshell-rotate.c @@ -0,0 +1,251 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-rotate.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-transform.h" + + +#define ANGLE_EPSILON 1e-3 + + +/* local function prototypes */ + +static void gimp_display_shell_save_viewport_center (GimpDisplayShell *shell, + gdouble *x, + gdouble *y); +static void gimp_display_shell_restore_viewport_center (GimpDisplayShell *shell, + gdouble x, + gdouble y); + + +/* public functions */ + +void +gimp_display_shell_flip (GimpDisplayShell *shell, + gboolean flip_horizontally, + gboolean flip_vertically) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + flip_horizontally = flip_horizontally ? TRUE : FALSE; + flip_vertically = flip_vertically ? TRUE : FALSE; + + if (flip_horizontally != shell->flip_horizontally || + flip_vertically != shell->flip_vertically) + { + gdouble cx, cy; + + /* Maintain the current center of the viewport. */ + gimp_display_shell_save_viewport_center (shell, &cx, &cy); + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + /* Adjust the rotation angle so that the image gets reflected across the + * horizontal, and/or vertical, axes in screen space, regardless of the + * current rotation. + */ + if (flip_horizontally == shell->flip_horizontally || + flip_vertically == shell->flip_vertically) + { + if (shell->rotate_angle != 0.0) + shell->rotate_angle = 360.0 - shell->rotate_angle; + } + + shell->flip_horizontally = flip_horizontally; + shell->flip_vertically = flip_vertically; + + gimp_display_shell_rotated (shell); + + gimp_display_shell_restore_viewport_center (shell, cx, cy); + + gimp_display_shell_expose_full (shell); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); + } +} + +void +gimp_display_shell_rotate (GimpDisplayShell *shell, + gdouble delta) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_rotate_to (shell, shell->rotate_angle + delta); +} + +void +gimp_display_shell_rotate_to (GimpDisplayShell *shell, + gdouble value) +{ + gdouble cx, cy; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + /* Maintain the current center of the viewport. */ + gimp_display_shell_save_viewport_center (shell, &cx, &cy); + + /* Make sure the angle is within the range [0, 360). */ + value = fmod (value, 360.0); + if (value < 0.0) + value += 360.0; + + shell->rotate_angle = value; + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + gimp_display_shell_scroll_clamp_and_update (shell); + + gimp_display_shell_rotated (shell); + + gimp_display_shell_restore_viewport_center (shell, cx, cy); + + gimp_display_shell_expose_full (shell); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); +} + +void +gimp_display_shell_rotate_drag (GimpDisplayShell *shell, + gdouble last_x, + gdouble last_y, + gdouble cur_x, + gdouble cur_y, + gboolean constrain) +{ + gdouble pivot_x, pivot_y; + gdouble src_x, src_y, src_angle; + gdouble dest_x, dest_y, dest_angle; + gdouble delta_angle; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + /* Rotate the image around the center of the viewport. */ + pivot_x = shell->disp_width / 2.0; + pivot_y = shell->disp_height / 2.0; + + src_x = last_x - pivot_x; + src_y = last_y - pivot_y; + src_angle = atan2 (src_y, src_x); + + dest_x = cur_x - pivot_x; + dest_y = cur_y - pivot_y; + dest_angle = atan2 (dest_y, dest_x); + + delta_angle = dest_angle - src_angle; + + shell->rotate_drag_angle += 180.0 * delta_angle / G_PI; + + gimp_display_shell_rotate_to (shell, + constrain ? + RINT (shell->rotate_drag_angle / 15.0) * 15.0 : + shell->rotate_drag_angle); +} + +void +gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + g_clear_pointer (&shell->rotate_transform, g_free); + g_clear_pointer (&shell->rotate_untransform, g_free); + + if (fabs (shell->rotate_angle) < ANGLE_EPSILON || + fabs (360.0 - shell->rotate_angle) < ANGLE_EPSILON) + shell->rotate_angle = 0.0; + + if ((shell->rotate_angle != 0.0 || + shell->flip_horizontally || + shell->flip_vertically) && + gimp_display_get_image (shell->display)) + { + gint image_width, image_height; + gdouble cx, cy; + + shell->rotate_transform = g_new (cairo_matrix_t, 1); + shell->rotate_untransform = g_new (cairo_matrix_t, 1); + + gimp_display_shell_scale_get_image_size (shell, + &image_width, &image_height); + + cx = -shell->offset_x + image_width / 2; + cy = -shell->offset_y + image_height / 2; + + cairo_matrix_init_translate (shell->rotate_transform, cx, cy); + + if (shell->rotate_angle != 0.0) + cairo_matrix_rotate (shell->rotate_transform, + shell->rotate_angle / 180.0 * G_PI); + + if (shell->flip_horizontally) + cairo_matrix_scale (shell->rotate_transform, -1.0, 1.0); + + if (shell->flip_vertically) + cairo_matrix_scale (shell->rotate_transform, 1.0, -1.0); + + cairo_matrix_translate (shell->rotate_transform, -cx, -cy); + + *shell->rotate_untransform = *shell->rotate_transform; + cairo_matrix_invert (shell->rotate_untransform); + } +} + + +/* private functions */ + +static void +gimp_display_shell_save_viewport_center (GimpDisplayShell *shell, + gdouble *x, + gdouble *y) +{ + gimp_display_shell_unrotate_xy_f (shell, + shell->disp_width / 2, + shell->disp_height / 2, + x, y); +} + +static void +gimp_display_shell_restore_viewport_center (GimpDisplayShell *shell, + gdouble x, + gdouble y) +{ + gimp_display_shell_rotate_xy_f (shell, x, y, &x, &y); + + x += shell->offset_x - shell->disp_width / 2; + y += shell->offset_y - shell->disp_height / 2; + + gimp_display_shell_scroll_set_offset (shell, RINT (x), RINT (y)); +} diff --git a/app/display/gimpdisplayshell-rotate.h b/app/display/gimpdisplayshell-rotate.h new file mode 100644 index 0000000..d1ffa05 --- /dev/null +++ b/app/display/gimpdisplayshell-rotate.h @@ -0,0 +1,40 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_ROTATE_H__ +#define __GIMP_DISPLAY_SHELL_ROTATE_H__ + + +void gimp_display_shell_flip (GimpDisplayShell *shell, + gboolean flip_horizontally, + gboolean flip_vertically); + +void gimp_display_shell_rotate (GimpDisplayShell *shell, + gdouble delta); +void gimp_display_shell_rotate_to (GimpDisplayShell *shell, + gdouble value); +void gimp_display_shell_rotate_drag (GimpDisplayShell *shell, + gdouble last_x, + gdouble last_y, + gdouble cur_x, + gdouble cur_y, + gboolean constrain); + +void gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_ROTATE_H__ */ diff --git a/app/display/gimpdisplayshell-rulers.c b/app/display/gimpdisplayshell-rulers.c new file mode 100644 index 0000000..79253c8 --- /dev/null +++ b/app/display/gimpdisplayshell-rulers.c @@ -0,0 +1,181 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimpimage.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-rulers.h" +#include "gimpdisplayshell-scale.h" + + +/** + * gimp_display_shell_rulers_update: + * @shell: + * + **/ +void +gimp_display_shell_rulers_update (GimpDisplayShell *shell) +{ + GimpImage *image; + gint image_width; + gint image_height; + gdouble offset_x = 0.0; + gdouble offset_y = 0.0; + gdouble scale_x = 1.0; + gdouble scale_y = 1.0; + gdouble resolution_x = 1.0; + gdouble resolution_y = 1.0; + gdouble horizontal_lower; + gdouble horizontal_upper; + gdouble horizontal_max_size; + gdouble vertical_lower; + gdouble vertical_upper; + gdouble vertical_max_size; + + if (! shell->display) + return; + + image = gimp_display_get_image (shell->display); + + if (image) + { + gint image_x, image_y; + gdouble res_x, res_y; + + gimp_display_shell_scale_get_image_bounds (shell, + &image_x, &image_y, + &image_width, &image_height); + + gimp_display_shell_get_rotated_scale (shell, &scale_x, &scale_y); + + image_width /= scale_x; + image_height /= scale_y; + + offset_x = shell->offset_x - image_x; + offset_y = shell->offset_y - image_y; + + gimp_image_get_resolution (image, &res_x, &res_y); + + if (shell->rotate_angle == 0.0 || res_x == res_y) + { + resolution_x = res_x; + resolution_y = res_y; + } + else + { + gdouble cos_a = cos (G_PI * shell->rotate_angle / 180.0); + gdouble sin_a = sin (G_PI * shell->rotate_angle / 180.0); + + if (shell->dot_for_dot) + { + resolution_x = 1.0 / sqrt (SQR (cos_a / res_x) + + SQR (sin_a / res_y)); + resolution_y = 1.0 / sqrt (SQR (cos_a / res_y) + + SQR (sin_a / res_x)); + } + else + { + resolution_x = sqrt (SQR (res_x * cos_a) + SQR (res_y * sin_a)); + resolution_y = sqrt (SQR (res_y * cos_a) + SQR (res_x * sin_a)); + } + } + } + else + { + image_width = shell->disp_width; + image_height = shell->disp_height; + } + + /* Initialize values */ + + horizontal_lower = 0; + vertical_lower = 0; + + if (image) + { + horizontal_upper = gimp_pixels_to_units (shell->disp_width / scale_x, + shell->unit, + resolution_x); + horizontal_max_size = gimp_pixels_to_units (MAX (image_width, + image_height), + shell->unit, + resolution_x); + + vertical_upper = gimp_pixels_to_units (shell->disp_height / scale_y, + shell->unit, + resolution_y); + vertical_max_size = gimp_pixels_to_units (MAX (image_width, + image_height), + shell->unit, + resolution_y); + } + else + { + horizontal_upper = image_width; + horizontal_max_size = MAX (image_width, image_height); + + vertical_upper = image_height; + vertical_max_size = MAX (image_width, image_height); + } + + + /* Adjust due to scrolling */ + + if (image) + { + offset_x *= horizontal_upper / shell->disp_width; + offset_y *= vertical_upper / shell->disp_height; + + horizontal_lower += offset_x; + horizontal_upper += offset_x; + + vertical_lower += offset_y; + vertical_upper += offset_y; + } + + /* Finally setup the actual rulers */ + + gimp_ruler_set_range (GIMP_RULER (shell->hrule), + horizontal_lower, + horizontal_upper, + horizontal_max_size); + + gimp_ruler_set_unit (GIMP_RULER (shell->hrule), + shell->unit); + + gimp_ruler_set_range (GIMP_RULER (shell->vrule), + vertical_lower, + vertical_upper, + vertical_max_size); + + gimp_ruler_set_unit (GIMP_RULER (shell->vrule), + shell->unit); +} diff --git a/app/display/gimpdisplayshell-rulers.h b/app/display/gimpdisplayshell-rulers.h new file mode 100644 index 0000000..117a8f0 --- /dev/null +++ b/app/display/gimpdisplayshell-rulers.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_RULERS_H__ +#define __GIMP_DISPLAY_SHELL_RULERS_H__ + + +void gimp_display_shell_rulers_update (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_RULERS_H__ */ diff --git a/app/display/gimpdisplayshell-scale-dialog.c b/app/display/gimpdisplayshell-scale-dialog.c new file mode 100644 index 0000000..f3d2eab --- /dev/null +++ b/app/display/gimpdisplayshell-scale-dialog.c @@ -0,0 +1,293 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpviewable.h" + +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpviewabledialog.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scale-dialog.h" + +#include "gimp-intl.h" + + +#define SCALE_EPSILON 0.0001 +#define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON) + + +typedef struct +{ + GimpDisplayShell *shell; + GimpZoomModel *model; + GtkAdjustment *scale_adj; + GtkAdjustment *num_adj; + GtkAdjustment *denom_adj; +} ScaleDialogData; + + +/* local function prototypes */ + +static void gimp_display_shell_scale_dialog_response (GtkWidget *widget, + gint response_id, + ScaleDialogData *dialog); +static void gimp_display_shell_scale_dialog_free (ScaleDialogData *dialog); + +static void update_zoom_values (GtkAdjustment *adj, + ScaleDialogData *dialog); + + + +/* public functions */ + +/** + * gimp_display_shell_scale_dialog: + * @shell: the #GimpDisplayShell + * + * Constructs and displays a dialog allowing the user to enter a + * custom display scale. + **/ +void +gimp_display_shell_scale_dialog (GimpDisplayShell *shell) +{ + ScaleDialogData *data; + GimpImage *image; + GtkWidget *toplevel; + GtkWidget *hbox; + GtkWidget *table; + GtkWidget *spin; + GtkWidget *label; + gint num, denom, row; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->scale_dialog) + { + gtk_window_present (GTK_WINDOW (shell->scale_dialog)); + return; + } + + if (SCALE_EQUALS (shell->other_scale, 0.0)) + { + /* other_scale not yet initialized */ + shell->other_scale = gimp_zoom_model_get_factor (shell->zoom); + } + + image = gimp_display_get_image (shell->display); + + data = g_slice_new (ScaleDialogData); + + data->shell = shell; + data->model = g_object_new (GIMP_TYPE_ZOOM_MODEL, + "value", fabs (shell->other_scale), + NULL); + + shell->scale_dialog = + gimp_viewable_dialog_new (GIMP_VIEWABLE (image), + gimp_get_user_context (shell->display->gimp), + _("Zoom Ratio"), "display_scale", + "zoom-original", + _("Select Zoom Ratio"), + GTK_WIDGET (shell), + gimp_standard_help_func, + GIMP_HELP_VIEW_ZOOM_OTHER, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (shell->scale_dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_object_weak_ref (G_OBJECT (shell->scale_dialog), + (GWeakNotify) gimp_display_shell_scale_dialog_free, data); + g_object_weak_ref (G_OBJECT (shell->scale_dialog), + (GWeakNotify) g_object_unref, data->model); + + g_object_add_weak_pointer (G_OBJECT (shell->scale_dialog), + (gpointer) &shell->scale_dialog); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell)); + + gtk_window_set_transient_for (GTK_WINDOW (shell->scale_dialog), + GTK_WINDOW (toplevel)); + gtk_window_set_destroy_with_parent (GTK_WINDOW (shell->scale_dialog), TRUE); + + g_signal_connect (shell->scale_dialog, "response", + G_CALLBACK (gimp_display_shell_scale_dialog_response), + data); + + table = gtk_table_new (2, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell->scale_dialog))), + table, TRUE, TRUE, 0); + gtk_widget_show (table); + + row = 0; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Zoom ratio:"), 0.0, 0.5, + hbox, 1, FALSE); + + gimp_zoom_model_get_fraction (data->model, &num, &denom); + + data->num_adj = (GtkAdjustment *) + gtk_adjustment_new (num, 1, 256, 1, 8, 0); + spin = gimp_spin_button_new (data->num_adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0); + gtk_widget_show (spin); + + label = gtk_label_new (":"); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + data->denom_adj = (GtkAdjustment *) + gtk_adjustment_new (denom, 1, 256, 1, 8, 0); + spin = gimp_spin_button_new (data->denom_adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0); + gtk_widget_show (spin); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Zoom:"), 0.0, 0.5, + hbox, 1, FALSE); + + data->scale_adj = (GtkAdjustment *) + gtk_adjustment_new (fabs (shell->other_scale) * 100, + 100.0 / 256.0, 25600.0, + 10, 50, 0); + spin = gimp_spin_button_new (data->scale_adj, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0); + gtk_widget_show (spin); + + label = gtk_label_new ("%"); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + g_signal_connect (data->scale_adj, "value-changed", + G_CALLBACK (update_zoom_values), data); + g_signal_connect (data->num_adj, "value-changed", + G_CALLBACK (update_zoom_values), data); + g_signal_connect (data->denom_adj, "value-changed", + G_CALLBACK (update_zoom_values), data); + + gtk_widget_show (shell->scale_dialog); +} + +static void +gimp_display_shell_scale_dialog_response (GtkWidget *widget, + gint response_id, + ScaleDialogData *dialog) +{ + if (response_id == GTK_RESPONSE_OK) + { + gdouble scale; + + scale = gtk_adjustment_get_value (dialog->scale_adj); + + gimp_display_shell_scale (dialog->shell, + GIMP_ZOOM_TO, + scale / 100.0, + GIMP_ZOOM_FOCUS_BEST_GUESS); + } + else + { + /* need to emit "scaled" to get the menu updated */ + gimp_display_shell_scaled (dialog->shell); + } + + dialog->shell->other_scale = - fabs (dialog->shell->other_scale); + + gtk_widget_destroy (dialog->shell->scale_dialog); +} + +static void +gimp_display_shell_scale_dialog_free (ScaleDialogData *dialog) +{ + g_slice_free (ScaleDialogData, dialog); +} + +static void +update_zoom_values (GtkAdjustment *adj, + ScaleDialogData *dialog) +{ + gint num, denom; + gdouble scale; + + g_signal_handlers_block_by_func (dialog->scale_adj, + update_zoom_values, + dialog); + g_signal_handlers_block_by_func (dialog->num_adj, + update_zoom_values, + dialog); + g_signal_handlers_block_by_func (dialog->denom_adj, + update_zoom_values, + dialog); + + if (adj == dialog->scale_adj) + { + scale = gtk_adjustment_get_value (dialog->scale_adj); + + gimp_zoom_model_zoom (dialog->model, GIMP_ZOOM_TO, scale / 100.0); + gimp_zoom_model_get_fraction (dialog->model, &num, &denom); + + gtk_adjustment_set_value (dialog->num_adj, num); + gtk_adjustment_set_value (dialog->denom_adj, denom); + } + else /* fraction adjustments */ + { + scale = (gtk_adjustment_get_value (dialog->num_adj) / + gtk_adjustment_get_value (dialog->denom_adj)); + + gtk_adjustment_set_value (dialog->scale_adj, scale * 100); + } + + g_signal_handlers_unblock_by_func (dialog->scale_adj, + update_zoom_values, + dialog); + g_signal_handlers_unblock_by_func (dialog->num_adj, + update_zoom_values, + dialog); + g_signal_handlers_unblock_by_func (dialog->denom_adj, + update_zoom_values, + dialog); +} diff --git a/app/display/gimpdisplayshell-scale-dialog.h b/app/display/gimpdisplayshell-scale-dialog.h new file mode 100644 index 0000000..eb83065 --- /dev/null +++ b/app/display/gimpdisplayshell-scale-dialog.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_SCALE_DIALOG_H__ +#define __GIMP_DISPLAY_SHELL_SCALE_DIALOG_H__ + + +void gimp_display_shell_scale_dialog (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_SCALE_DIALOG_H__ */ diff --git a/app/display/gimpdisplayshell-scale.c b/app/display/gimpdisplayshell-scale.c new file mode 100644 index 0000000..482a9fe --- /dev/null +++ b/app/display/gimpdisplayshell-scale.c @@ -0,0 +1,1449 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-rotate.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-transform.h" +#include "gimpimagewindow.h" + + +#define SCALE_TIMEOUT 2 +#define SCALE_EPSILON 0.0001 +#define ALMOST_CENTERED_THRESHOLD 2 + +#define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON) + + +/* local function prototypes */ + +static void gimp_display_shell_scale_get_screen_resolution + (GimpDisplayShell *shell, + gdouble *xres, + gdouble *yres); +static void gimp_display_shell_scale_get_image_size_for_scale + (GimpDisplayShell *shell, + gdouble scale, + gint *w, + gint *h); +static void gimp_display_shell_calculate_scale_x_and_y + (GimpDisplayShell *shell, + gdouble scale, + gdouble *scale_x, + gdouble *scale_y); + +static void gimp_display_shell_scale_to (GimpDisplayShell *shell, + gdouble scale, + gdouble viewport_x, + gdouble viewport_y); +static void gimp_display_shell_scale_fit_or_fill (GimpDisplayShell *shell, + gboolean fill); + +static gboolean gimp_display_shell_scale_image_starts_to_fit + (GimpDisplayShell *shell, + gdouble new_scale, + gdouble current_scale, + gboolean *vertically, + gboolean *horizontally); +static gboolean gimp_display_shell_scale_viewport_coord_almost_centered + (GimpDisplayShell *shell, + gint x, + gint y, + gboolean *horizontally, + gboolean *vertically); + +static void gimp_display_shell_scale_get_image_center_viewport + (GimpDisplayShell *shell, + gint *image_center_x, + gint *image_center_y); + + +static void gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell, + gdouble new_scale, + gdouble current_scale, + gdouble *x, + gdouble *y, + GimpZoomFocus zoom_focus); + + +/* public functions */ + +/** + * gimp_display_shell_scale_revert: + * @shell: the #GimpDisplayShell + * + * Reverts the display to the previously used scale. If no previous + * scale exist, then the call does nothing. + * + * Return value: %TRUE if the scale was reverted, otherwise %FALSE. + **/ +gboolean +gimp_display_shell_scale_revert (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + /* don't bother if no scale has been set */ + if (shell->last_scale < SCALE_EPSILON) + return FALSE; + + shell->last_scale_time = 0; + + gimp_display_shell_scale_by_values (shell, + shell->last_scale, + shell->last_offset_x, + shell->last_offset_y, + FALSE); /* don't resize the window */ + + return TRUE; +} + +/** + * gimp_display_shell_scale_can_revert: + * @shell: the #GimpDisplayShell + * + * Return value: %TRUE if a previous display scale exists, otherwise %FALSE. + **/ +gboolean +gimp_display_shell_scale_can_revert (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return (shell->last_scale > SCALE_EPSILON); +} + +/** + * gimp_display_shell_scale_save_revert_values: + * @shell: + * + * Handle the updating of the Revert Zoom variables. + **/ +void +gimp_display_shell_scale_save_revert_values (GimpDisplayShell *shell) +{ + guint now; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + now = time (NULL); + + if (now - shell->last_scale_time >= SCALE_TIMEOUT) + { + shell->last_scale = gimp_zoom_model_get_factor (shell->zoom); + shell->last_offset_x = shell->offset_x; + shell->last_offset_y = shell->offset_y; + } + + shell->last_scale_time = now; +} + +/** + * gimp_display_shell_scale_set_dot_for_dot: + * @shell: the #GimpDisplayShell + * @dot_for_dot: whether "Dot for Dot" should be enabled + * + * If @dot_for_dot is set to %TRUE then the "Dot for Dot" mode (where image and + * screen pixels are of the same size) is activated. Dually, the mode is + * disabled if @dot_for_dot is %FALSE. + **/ +void +gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell, + gboolean dot_for_dot) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (dot_for_dot != shell->dot_for_dot) + { + GimpDisplayConfig *config = shell->display->config; + gboolean resize_window; + + /* Resize windows only in multi-window mode */ + resize_window = (config->resize_windows_on_zoom && + ! GIMP_GUI_CONFIG (config)->single_window_mode); + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + shell->dot_for_dot = dot_for_dot; + + gimp_display_shell_scale_update (shell); + + gimp_display_shell_scale_resize (shell, resize_window, FALSE); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); + } +} + +/** + * gimp_display_shell_scale_get_image_size: + * @shell: + * @w: + * @h: + * + * Gets the size of the rendered image after it has been scaled. + * + **/ +void +gimp_display_shell_scale_get_image_size (GimpDisplayShell *shell, + gint *w, + gint *h) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_scale_get_image_size_for_scale (shell, + gimp_zoom_model_get_factor (shell->zoom), + w, h); +} + +/** + * gimp_display_shell_scale_get_image_bounds: + * @shell: + * @x: + * @y: + * @w: + * @h: + * + * Gets the screen-space boudning box of the image, after it has + * been transformed (i.e., scaled, rotated, and scrolled). + **/ +void +gimp_display_shell_scale_get_image_bounds (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h) +{ + GimpImage *image; + gdouble x1, y1; + gdouble x2, y2; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + gimp_display_shell_transform_bounds (shell, + 0, 0, + gimp_image_get_width (image), + gimp_image_get_height (image), + &x1, &y1, + &x2, &y2); + + x1 = ceil (x1); + y1 = ceil (y1); + x2 = floor (x2); + y2 = floor (y2); + + if (x) *x = x1 + shell->offset_x; + if (y) *y = y1 + shell->offset_y; + if (w) *w = x2 - x1; + if (h) *h = y2 - y1; +} + +/** + * gimp_display_shell_scale_get_image_unrotated_bounds: + * @shell: + * @x: + * @y: + * @w: + * @h: + * + * Gets the screen-space boudning box of the image, after it has + * been scaled and scrolled, but before it has been rotated. + **/ +void +gimp_display_shell_scale_get_image_unrotated_bounds (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + if (x) *x = -shell->offset_x; + if (y) *y = -shell->offset_y; + if (w) *w = floor (gimp_image_get_width (image) * shell->scale_x); + if (h) *h = floor (gimp_image_get_height (image) * shell->scale_y); +} + +/** + * gimp_display_shell_scale_get_image_bounding_box: + * @shell: + * @x: + * @y: + * @w: + * @h: + * + * Gets the screen-space boudning box of the image content, after it has + * been transformed (i.e., scaled, rotated, and scrolled). + **/ +void +gimp_display_shell_scale_get_image_bounding_box (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h) +{ + GeglRectangle bounding_box; + gdouble x1, y1; + gdouble x2, y2; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + bounding_box = gimp_display_shell_get_bounding_box (shell); + + gimp_display_shell_transform_bounds (shell, + bounding_box.x, + bounding_box.y, + bounding_box.x + bounding_box.width, + bounding_box.y + bounding_box.height, + &x1, &y1, + &x2, &y2); + + if (! shell->show_all) + { + x1 = ceil (x1); + y1 = ceil (y1); + x2 = floor (x2); + y2 = floor (y2); + } + else + { + x1 = floor (x1); + y1 = floor (y1); + x2 = ceil (x2); + y2 = ceil (y2); + } + + if (x) *x = x1 + shell->offset_x; + if (y) *y = y1 + shell->offset_y; + if (w) *w = x2 - x1; + if (h) *h = y2 - y1; +} + +/** + * gimp_display_shell_scale_get_image_unrotated_bounding_box: + * @shell: + * @x: + * @y: + * @w: + * @h: + * + * Gets the screen-space boudning box of the image content, after it has + * been scaled and scrolled, but before it has been rotated. + **/ +void +gimp_display_shell_scale_get_image_unrotated_bounding_box (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h) +{ + GeglRectangle bounding_box; + gdouble x1, y1; + gdouble x2, y2; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + bounding_box = gimp_display_shell_get_bounding_box (shell); + + x1 = bounding_box.x * shell->scale_x - + shell->offset_x; + y1 = bounding_box.y * shell->scale_y - + shell->offset_y; + + x2 = (bounding_box.x + bounding_box.width) * shell->scale_x - + shell->offset_x; + y2 = (bounding_box.y + bounding_box.height) * shell->scale_y - + shell->offset_y; + + if (! shell->show_all) + { + x1 = ceil (x1); + y1 = ceil (y1); + x2 = floor (x2); + y2 = floor (y2); + } + else + { + x1 = floor (x1); + y1 = floor (y1); + x2 = ceil (x2); + y2 = ceil (y2); + } + + if (x) *x = x1; + if (y) *y = y1; + if (w) *w = x2 - x1; + if (h) *h = y2 - y1; +} + +/** + * gimp_display_shell_scale_image_is_within_viewport: + * @shell: + * + * Returns: %TRUE if the (scaled) image is smaller than and within the + * viewport. + **/ +gboolean +gimp_display_shell_scale_image_is_within_viewport (GimpDisplayShell *shell, + gboolean *horizontally, + gboolean *vertically) +{ + gboolean horizontally_dummy, vertically_dummy; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + if (! horizontally) horizontally = &horizontally_dummy; + if (! vertically) vertically = &vertically_dummy; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + gint sx, sy; + gint sw, sh; + + gimp_display_shell_scale_get_image_bounding_box (shell, + &sx, &sy, &sw, &sh); + + sx -= shell->offset_x; + sy -= shell->offset_y; + + *horizontally = sx >= 0 && sx + sw <= shell->disp_width; + *vertically = sy >= 0 && sy + sh <= shell->disp_height; + } + else + { + *horizontally = FALSE; + *vertically = FALSE; + } + + return *vertically && *horizontally; +} + +/* We used to calculate the scale factor in the SCALEFACTOR_X() and + * SCALEFACTOR_Y() macros. But since these are rather frequently + * called and the values rarely change, we now store them in the + * shell and call this function whenever they need to be recalculated. + */ +void +gimp_display_shell_scale_update (GimpDisplayShell *shell) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + if (image) + { + gimp_display_shell_calculate_scale_x_and_y (shell, + gimp_zoom_model_get_factor (shell->zoom), + &shell->scale_x, + &shell->scale_y); + } + else + { + shell->scale_x = 1.0; + shell->scale_y = 1.0; + } +} + +/** + * gimp_display_shell_scale: + * @shell: the #GimpDisplayShell + * @zoom_type: whether to zoom in, out or to a specific scale + * @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO + * + * This function figures out the context of the zoom and behaves + * appropriately thereafter. + * + **/ +void +gimp_display_shell_scale (GimpDisplayShell *shell, + GimpZoomType zoom_type, + gdouble new_scale, + GimpZoomFocus zoom_focus) +{ + GimpDisplayConfig *config; + gdouble current_scale; + gboolean resize_window; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->canvas != NULL); + + current_scale = gimp_zoom_model_get_factor (shell->zoom); + + if (zoom_type != GIMP_ZOOM_TO) + new_scale = gimp_zoom_model_zoom_step (zoom_type, current_scale); + + if (SCALE_EQUALS (new_scale, current_scale)) + return; + + config = shell->display->config; + + /* Resize windows only in multi-window mode */ + resize_window = (config->resize_windows_on_zoom && + ! GIMP_GUI_CONFIG (config)->single_window_mode); + + if (resize_window) + { + /* If the window is resized on zoom, simply do the zoom and get + * things rolling + */ + gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale); + + gimp_display_shell_scale_resize (shell, TRUE, FALSE); + } + else + { + gdouble x, y; + gint image_center_x; + gint image_center_y; + + gimp_display_shell_scale_get_zoom_focus (shell, + new_scale, + current_scale, + &x, + &y, + zoom_focus); + gimp_display_shell_scale_get_image_center_viewport (shell, + &image_center_x, + &image_center_y); + + gimp_display_shell_scale_to (shell, new_scale, x, y); + + /* skip centering magic if pointer focus was requested */ + if (zoom_focus != GIMP_ZOOM_FOCUS_POINTER) + { + gboolean starts_fitting_horiz; + gboolean starts_fitting_vert; + gboolean zoom_focus_almost_centered_horiz; + gboolean zoom_focus_almost_centered_vert; + gboolean image_center_almost_centered_horiz; + gboolean image_center_almost_centered_vert; + + /* If an image axis started to fit due to zooming out or if + * the focus point is as good as in the center, center on + * that axis + */ + gimp_display_shell_scale_image_starts_to_fit (shell, + new_scale, + current_scale, + &starts_fitting_horiz, + &starts_fitting_vert); + + gimp_display_shell_scale_viewport_coord_almost_centered (shell, + x, + y, + &zoom_focus_almost_centered_horiz, + &zoom_focus_almost_centered_vert); + gimp_display_shell_scale_viewport_coord_almost_centered (shell, + image_center_x, + image_center_y, + &image_center_almost_centered_horiz, + &image_center_almost_centered_vert); + + gimp_display_shell_scroll_center_image (shell, + starts_fitting_horiz || + (zoom_focus_almost_centered_horiz && + image_center_almost_centered_horiz), + starts_fitting_vert || + (zoom_focus_almost_centered_vert && + image_center_almost_centered_vert)); + } + } +} + +/** + * gimp_display_shell_scale_to_rectangle: + * @shell: the #GimpDisplayShell + * @zoom_type: whether to zoom in or out + * @x: retangle's x in image coordinates + * @y: retangle's y in image coordinates + * @width: retangle's width in image coordinates + * @height: retangle's height in image coordinates + * @resize_window: whether the display window should be resized + * + * Scales and scrolls to a specific image rectangle + **/ +void +gimp_display_shell_scale_to_rectangle (GimpDisplayShell *shell, + GimpZoomType zoom_type, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gboolean resize_window) +{ + gdouble current_scale; + gdouble new_scale; + gdouble factor = 1.0; + gint offset_x = 0; + gint offset_y = 0; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_transform_bounds (shell, + x, y, + x + width, y + height, + &x, &y, + &width, &height); + + /* Convert scrolled (x1, y1, x2, y2) to unscrolled (x, y, width, height). */ + width -= x; + height -= y; + x += shell->offset_x; + y += shell->offset_y; + + width = MAX (1.0, width); + height = MAX (1.0, height); + + current_scale = gimp_zoom_model_get_factor (shell->zoom); + + switch (zoom_type) + { + case GIMP_ZOOM_IN: + factor = MIN ((shell->disp_width / width), + (shell->disp_height / height)); + break; + + case GIMP_ZOOM_OUT: + factor = MAX ((width / shell->disp_width), + (height / shell->disp_height)); + break; + + default: + g_return_if_reached (); + break; + } + + new_scale = current_scale * factor; + + switch (zoom_type) + { + case GIMP_ZOOM_IN: + /* move the center of the rectangle to the center of the + * viewport: + * + * new_offset = center of rectangle in new scale screen coords + * including offset + * - + * center of viewport in screen coords without + * offset + */ + offset_x = RINT (factor * (x + width / 2.0) - (shell->disp_width / 2)); + offset_y = RINT (factor * (y + height / 2.0) - (shell->disp_height / 2)); + break; + + case GIMP_ZOOM_OUT: + /* move the center of the viewport to the center of the + * rectangle: + * + * new_offset = center of viewport in new scale screen coords + * including offset + * - + * center of rectangle in screen coords without + * offset + */ + offset_x = RINT (factor * (shell->offset_x + shell->disp_width / 2) - + ((x + width / 2.0) - shell->offset_x)); + + offset_y = RINT (factor * (shell->offset_y + shell->disp_height / 2) - + ((y + height / 2.0) - shell->offset_y)); + break; + + default: + break; + } + + if (new_scale != current_scale || + offset_x != shell->offset_x || + offset_y != shell->offset_y) + { + gimp_display_shell_scale_by_values (shell, + new_scale, + offset_x, offset_y, + resize_window); + } +} + +/** + * gimp_display_shell_scale_fit_in: + * @shell: the #GimpDisplayShell + * + * Sets the scale such that the entire image precisely fits in the + * display area. + **/ +void +gimp_display_shell_scale_fit_in (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_scale_fit_or_fill (shell, + /* fill = */ FALSE); + } + +/** + * gimp_display_shell_scale_fill: + * @shell: the #GimpDisplayShell + * + * Sets the scale such that the entire display area is precisely + * filled by the image. + **/ +void +gimp_display_shell_scale_fill (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_scale_fit_or_fill (shell, + /* fill = */ TRUE); +} + +/** + * gimp_display_shell_scale_by_values: + * @shell: the #GimpDisplayShell + * @scale: the new scale + * @offset_x: the new X offset + * @offset_y: the new Y offset + * @resize_window: whether the display window should be resized + * + * Directly sets the image scale and image offsets used by the display. If + * @resize_window is %TRUE then the display window is resized to better + * accommodate the image, see gimp_display_shell_shrink_wrap(). + **/ +void +gimp_display_shell_scale_by_values (GimpDisplayShell *shell, + gdouble scale, + gint offset_x, + gint offset_y, + gboolean resize_window) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + /* Abort early if the values are all setup already. We don't + * want to inadvertently resize the window (bug #164281). + */ + if (SCALE_EQUALS (gimp_zoom_model_get_factor (shell->zoom), scale) && + shell->offset_x == offset_x && + shell->offset_y == offset_y) + return; + + gimp_display_shell_scale_save_revert_values (shell); + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale); + + shell->offset_x = offset_x; + shell->offset_y = offset_y; + + gimp_display_shell_rotate_update_transform (shell); + + gimp_display_shell_scale_resize (shell, resize_window, FALSE); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); +} + +void +gimp_display_shell_scale_drag (GimpDisplayShell *shell, + gdouble start_x, + gdouble start_y, + gdouble delta_x, + gdouble delta_y) +{ + gdouble scale; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + scale = gimp_zoom_model_get_factor (shell->zoom); + + gimp_display_shell_push_zoom_focus_pointer_pos (shell, start_x, start_y); + + if (delta_y > 0) + { + gimp_display_shell_scale (shell, + GIMP_ZOOM_TO, + scale * 1.1, + GIMP_ZOOM_FOCUS_POINTER); + } + else if (delta_y < 0) + { + gimp_display_shell_scale (shell, + GIMP_ZOOM_TO, + scale * 0.9, + GIMP_ZOOM_FOCUS_POINTER); + } +} + +/** + * gimp_display_shell_scale_shrink_wrap: + * @shell: the #GimpDisplayShell + * + * Convenience function with the same functionality as + * gimp_display_shell_scale_resize(@shell, TRUE, grow_only). + **/ +void +gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell, + gboolean grow_only) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_scale_resize (shell, TRUE, grow_only); +} + +/** + * gimp_display_shell_scale_resize: + * @shell: the #GimpDisplayShell + * @resize_window: whether the display window should be resized + * @grow_only: whether shrinking of the window is allowed or not + * + * Function commonly called after a change in display scale to make the changes + * visible to the user. If @resize_window is %TRUE then the display window is + * resized to accommodate the display image as per + * gimp_display_shell_shrink_wrap(). + **/ +void +gimp_display_shell_scale_resize (GimpDisplayShell *shell, + gboolean resize_window, + gboolean grow_only) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + if (resize_window) + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + gimp_image_window_shrink_wrap (window, grow_only); + } + } + + gimp_display_shell_scroll_clamp_and_update (shell); + gimp_display_shell_scaled (shell); + + gimp_display_shell_expose_full (shell); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); +} + +void +gimp_display_shell_set_initial_scale (GimpDisplayShell *shell, + gdouble scale, + gint *display_width, + gint *display_height) +{ + GimpImage *image; + GdkScreen *screen; + gint image_width; + gint image_height; + gint shell_width; + gint shell_height; + gint screen_width; + gint screen_height; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + screen = gtk_widget_get_screen (GTK_WIDGET (shell)); + + image_width = gimp_image_get_width (image); + image_height = gimp_image_get_height (image); + + screen_width = gdk_screen_get_width (screen) * 0.75; + screen_height = gdk_screen_get_height (screen) * 0.75; + + /* We need to zoom before we use SCALE[XY] */ + gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale); + + shell_width = SCALEX (shell, image_width); + shell_height = SCALEY (shell, image_height); + + if (shell->display->config->initial_zoom_to_fit) + { + /* Limit to the size of the screen... */ + if (shell_width > screen_width || shell_height > screen_height) + { + gdouble new_scale; + gdouble current = gimp_zoom_model_get_factor (shell->zoom); + + new_scale = current * MIN (((gdouble) screen_height) / shell_height, + ((gdouble) screen_width) / shell_width); + + new_scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, new_scale); + + /* Since zooming out might skip a zoom step we zoom in + * again and test if we are small enough. + */ + gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, + gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, + new_scale)); + + if (SCALEX (shell, image_width) > screen_width || + SCALEY (shell, image_height) > screen_height) + gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale); + + shell_width = SCALEX (shell, image_width); + shell_height = SCALEY (shell, image_height); + } + } + else + { + /* Set up size like above, but do not zoom to fit. Useful when + * working on large images. + */ + if (shell_width > screen_width) + shell_width = screen_width; + + if (shell_height > screen_height) + shell_height = screen_height; + } + + if (display_width) + *display_width = shell_width; + + if (display_height) + *display_height = shell_height; +} + +/** + * gimp_display_shell_get_rotated_scale: + * @shell: the #GimpDisplayShell + * @scale_x: horizontal scale output + * @scale_y: vertical scale output + * + * Returns the screen space horizontal and vertical scaling + * factors, taking rotation into account. + **/ +void +gimp_display_shell_get_rotated_scale (GimpDisplayShell *shell, + gdouble *scale_x, + gdouble *scale_y) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->rotate_angle == 0.0 || shell->scale_x == shell->scale_y) + { + if (scale_x) *scale_x = shell->scale_x; + if (scale_y) *scale_y = shell->scale_y; + } + else + { + gdouble a = G_PI * shell->rotate_angle / 180.0; + gdouble cos_a = cos (a); + gdouble sin_a = sin (a); + + if (scale_x) *scale_x = 1.0 / sqrt (SQR (cos_a / shell->scale_x) + + SQR (sin_a / shell->scale_y)); + + if (scale_y) *scale_y = 1.0 / sqrt (SQR (cos_a / shell->scale_y) + + SQR (sin_a / shell->scale_x)); + } +} + +/** + * gimp_display_shell_push_zoom_focus_pointer_pos: + * @shell: + * @x: + * @y: + * + * When the zoom focus mechanism asks for the pointer the next time, + * use @x and @y. + **/ +void +gimp_display_shell_push_zoom_focus_pointer_pos (GimpDisplayShell *shell, + gint x, + gint y) +{ + GdkPoint *point = g_slice_new (GdkPoint); + point->x = x; + point->y = y; + + g_queue_push_head (shell->zoom_focus_pointer_queue, + point); +} + + +/* private functions */ + +static void +gimp_display_shell_scale_get_screen_resolution (GimpDisplayShell *shell, + gdouble *xres, + gdouble *yres) +{ + gdouble x, y; + + if (shell->dot_for_dot) + { + gimp_image_get_resolution (gimp_display_get_image (shell->display), + &x, &y); + } + else + { + x = shell->monitor_xres; + y = shell->monitor_yres; + } + + if (xres) *xres = x; + if (yres) *yres = y; +} + +/** + * gimp_display_shell_scale_get_image_size_for_scale: + * @shell: + * @scale: + * @w: + * @h: + * + **/ +static void +gimp_display_shell_scale_get_image_size_for_scale (GimpDisplayShell *shell, + gdouble scale, + gint *w, + gint *h) +{ + GimpImage *image = gimp_display_get_image (shell->display); + gdouble scale_x; + gdouble scale_y; + + gimp_display_shell_calculate_scale_x_and_y (shell, scale, &scale_x, &scale_y); + + if (w) *w = scale_x * gimp_image_get_width (image); + if (h) *h = scale_y * gimp_image_get_height (image); +} + +/** + * gimp_display_shell_calculate_scale_x_and_y: + * @shell: + * @scale: + * @scale_x: + * @scale_y: + * + **/ +static void +gimp_display_shell_calculate_scale_x_and_y (GimpDisplayShell *shell, + gdouble scale, + gdouble *scale_x, + gdouble *scale_y) +{ + GimpImage *image = gimp_display_get_image (shell->display); + gdouble xres; + gdouble yres; + gdouble screen_xres; + gdouble screen_yres; + + gimp_image_get_resolution (image, &xres, &yres); + gimp_display_shell_scale_get_screen_resolution (shell, + &screen_xres, &screen_yres); + + if (scale_x) *scale_x = scale * screen_xres / xres; + if (scale_y) *scale_y = scale * screen_yres / yres; +} + +/** + * gimp_display_shell_scale_to: + * @shell: + * @scale: + * @viewport_x: + * @viewport_y: + * + * Zooms. The display offsets are adjusted so that the point specified + * by @x and @y doesn't change it's position on screen. + **/ +static void +gimp_display_shell_scale_to (GimpDisplayShell *shell, + gdouble scale, + gdouble viewport_x, + gdouble viewport_y) +{ + gdouble image_x, image_y; + gdouble new_viewport_x, new_viewport_y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->display) + return; + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + gimp_display_shell_untransform_xy_f (shell, + viewport_x, + viewport_y, + &image_x, + &image_y); + + /* Note that we never come here if we need to resize_windows_on_zoom + */ + gimp_display_shell_scale_by_values (shell, + scale, + shell->offset_x, + shell->offset_y, + FALSE); + + gimp_display_shell_transform_xy_f (shell, + image_x, + image_y, + &new_viewport_x, + &new_viewport_y); + + gimp_display_shell_scroll (shell, + new_viewport_x - viewport_x, + new_viewport_y - viewport_y); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); +} + +/** + * gimp_display_shell_scale_fit_or_fill: + * @shell: the #GimpDisplayShell + * @fill: whether to scale the image to fill the viewport, + * or fit inside the viewport + * + * A common implementation for gimp_display_shell_scale_{fit_in,fill}(). + **/ +static void +gimp_display_shell_scale_fit_or_fill (GimpDisplayShell *shell, + gboolean fill) +{ + GeglRectangle bounding_box; + gdouble image_x; + gdouble image_y; + gdouble image_width; + gdouble image_height; + gdouble current_scale; + gdouble zoom_factor; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + GimpImage *image = gimp_display_get_image (shell->display); + + bounding_box.x = 0; + bounding_box.y = 0; + bounding_box.width = gimp_image_get_width (image); + bounding_box.height = gimp_image_get_height (image); + } + else + { + bounding_box = gimp_display_shell_get_bounding_box (shell); + } + + gimp_display_shell_transform_bounds (shell, + bounding_box.x, + bounding_box.y, + bounding_box.x + bounding_box.width, + bounding_box.y + bounding_box.height, + &image_x, + &image_y, + &image_width, + &image_height); + + image_width -= image_x; + image_height -= image_y; + + current_scale = gimp_zoom_model_get_factor (shell->zoom); + + if (fill) + { + zoom_factor = MAX (shell->disp_width / image_width, + shell->disp_height / image_height); + } + else + { + zoom_factor = MIN (shell->disp_width / image_width, + shell->disp_height / image_height); + } + + gimp_display_shell_scale (shell, + GIMP_ZOOM_TO, + zoom_factor * current_scale, + GIMP_ZOOM_FOCUS_BEST_GUESS); + + gimp_display_shell_scroll_center_content (shell, TRUE, TRUE); +} + +static gboolean +gimp_display_shell_scale_image_starts_to_fit (GimpDisplayShell *shell, + gdouble new_scale, + gdouble current_scale, + gboolean *vertically, + gboolean *horizontally) +{ + gboolean vertically_dummy; + gboolean horizontally_dummy; + + if (! vertically) vertically = &vertically_dummy; + if (! horizontally) horizontally = &horizontally_dummy; + + /* The image can only start to fit if we zoom out */ + if (new_scale > current_scale || + gimp_display_shell_get_infinite_canvas (shell)) + { + *vertically = FALSE; + *horizontally = FALSE; + } + else + { + gint current_scale_width; + gint current_scale_height; + gint new_scale_width; + gint new_scale_height; + + gimp_display_shell_scale_get_image_size_for_scale (shell, + current_scale, + ¤t_scale_width, + ¤t_scale_height); + + gimp_display_shell_scale_get_image_size_for_scale (shell, + new_scale, + &new_scale_width, + &new_scale_height); + + *vertically = (current_scale_width > shell->disp_width && + new_scale_width <= shell->disp_width); + *horizontally = (current_scale_height > shell->disp_height && + new_scale_height <= shell->disp_height); + } + + return *vertically && *horizontally; +} + +static gboolean +gimp_display_shell_scale_image_stops_to_fit (GimpDisplayShell *shell, + gdouble new_scale, + gdouble current_scale, + gboolean *vertically, + gboolean *horizontally) +{ + return gimp_display_shell_scale_image_starts_to_fit (shell, + current_scale, + new_scale, + vertically, + horizontally); +} + +/** + * gimp_display_shell_scale_viewport_coord_almost_centered: + * @shell: + * @x: + * @y: + * @horizontally: + * @vertically: + * + **/ +static gboolean +gimp_display_shell_scale_viewport_coord_almost_centered (GimpDisplayShell *shell, + gint x, + gint y, + gboolean *horizontally, + gboolean *vertically) +{ + gboolean local_horizontally = FALSE; + gboolean local_vertically = FALSE; + gint center_x = shell->disp_width / 2; + gint center_y = shell->disp_height / 2; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + local_horizontally = (x > center_x - ALMOST_CENTERED_THRESHOLD && + x < center_x + ALMOST_CENTERED_THRESHOLD); + + local_vertically = (y > center_y - ALMOST_CENTERED_THRESHOLD && + y < center_y + ALMOST_CENTERED_THRESHOLD); + } + + if (horizontally) *horizontally = local_horizontally; + if (vertically) *vertically = local_vertically; + + return local_horizontally && local_vertically; +} + +static void +gimp_display_shell_scale_get_image_center_viewport (GimpDisplayShell *shell, + gint *image_center_x, + gint *image_center_y) +{ + gint sw, sh; + + gimp_display_shell_scale_get_image_size (shell, &sw, &sh); + + if (image_center_x) *image_center_x = -shell->offset_x + sw / 2; + if (image_center_y) *image_center_y = -shell->offset_y + sh / 2; +} + +/** + * gimp_display_shell_scale_get_zoom_focus: + * @shell: + * @new_scale: + * @x: + * @y: + * + * Calculates the viewport coordinate to focus on when zooming + * independently for each axis. + **/ +static void +gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell, + gdouble new_scale, + gdouble current_scale, + gdouble *x, + gdouble *y, + GimpZoomFocus zoom_focus) +{ + GtkWidget *window = GTK_WIDGET (gimp_display_shell_get_window (shell)); + GdkEvent *event; + gint image_center_x; + gint image_center_y; + gint other_x; + gint other_y; + + /* Calculate stops-to-fit focus point */ + gimp_display_shell_scale_get_image_center_viewport (shell, + &image_center_x, + &image_center_y); + + /* Calculate other focus point, default is the canvas center */ + other_x = shell->disp_width / 2; + other_y = shell->disp_height / 2; + + /* Center on the mouse position instead of the display center if + * one of the following conditions are fulfilled and pointer is + * within the canvas: + * + * (1) there's no current event (the action was triggered by an + * input controller) + * (2) the event originates from the canvas (a scroll event) + * (3) the event originates from the window (a key press event) + * + * Basically the only situation where we don't want to center on + * mouse position is if the action is being called from a menu. + */ + event = gtk_get_current_event (); + + if (! event || + gtk_get_event_widget (event) == shell->canvas || + gtk_get_event_widget (event) == window) + { + GdkPoint *point = g_queue_pop_head (shell->zoom_focus_pointer_queue); + gint canvas_pointer_x; + gint canvas_pointer_y; + + if (point) + { + canvas_pointer_x = point->x; + canvas_pointer_y = point->y; + + g_slice_free (GdkPoint, point); + } + else + { + gtk_widget_get_pointer (shell->canvas, + &canvas_pointer_x, + &canvas_pointer_y); + } + + if (canvas_pointer_x >= 0 && + canvas_pointer_y >= 0 && + canvas_pointer_x < shell->disp_width && + canvas_pointer_y < shell->disp_height) + { + other_x = canvas_pointer_x; + other_y = canvas_pointer_y; + } + } + + if (zoom_focus == GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS) + { + if (gimp_display_shell_scale_viewport_coord_almost_centered (shell, + image_center_x, + image_center_y, + NULL, + NULL)) + { + zoom_focus = GIMP_ZOOM_FOCUS_IMAGE_CENTER; + } + else + { + zoom_focus = GIMP_ZOOM_FOCUS_BEST_GUESS; + } + } + + switch (zoom_focus) + { + case GIMP_ZOOM_FOCUS_POINTER: + *x = other_x; + *y = other_y; + break; + + case GIMP_ZOOM_FOCUS_IMAGE_CENTER: + *x = image_center_x; + *y = image_center_y; + break; + + case GIMP_ZOOM_FOCUS_BEST_GUESS: + default: + { + gboolean within_horizontally, within_vertically; + gboolean stops_horizontally, stops_vertically; + + gimp_display_shell_scale_image_is_within_viewport (shell, + &within_horizontally, + &within_vertically); + + gimp_display_shell_scale_image_stops_to_fit (shell, + new_scale, + current_scale, + &stops_horizontally, + &stops_vertically); + + *x = within_horizontally && ! stops_horizontally ? image_center_x : other_x; + *y = within_vertically && ! stops_vertically ? image_center_y : other_y; + } + break; + } +} diff --git a/app/display/gimpdisplayshell-scale.h b/app/display/gimpdisplayshell-scale.h new file mode 100644 index 0000000..b97f9fb --- /dev/null +++ b/app/display/gimpdisplayshell-scale.h @@ -0,0 +1,108 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_SCALE_H__ +#define __GIMP_DISPLAY_SHELL_SCALE_H__ + + +gboolean gimp_display_shell_scale_revert (GimpDisplayShell *shell); +gboolean gimp_display_shell_scale_can_revert (GimpDisplayShell *shell); +void gimp_display_shell_scale_save_revert_values (GimpDisplayShell *shell); + +void gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell, + gboolean dot_for_dot); + +void gimp_display_shell_scale_get_image_size (GimpDisplayShell *shell, + gint *w, + gint *h); +void gimp_display_shell_scale_get_image_bounds (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h); +void gimp_display_shell_scale_get_image_unrotated_bounds + (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h); +void gimp_display_shell_scale_get_image_bounding_box + (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h); +void gimp_display_shell_scale_get_image_unrotated_bounding_box + (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h); +gboolean gimp_display_shell_scale_image_is_within_viewport + (GimpDisplayShell *shell, + gboolean *horizontally, + gboolean *vertically); + +void gimp_display_shell_scale_update (GimpDisplayShell *shell); + +void gimp_display_shell_scale (GimpDisplayShell *shell, + GimpZoomType zoom_type, + gdouble scale, + GimpZoomFocus zoom_focus); +void gimp_display_shell_scale_to_rectangle (GimpDisplayShell *shell, + GimpZoomType zoom_type, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gboolean resize_window); +void gimp_display_shell_scale_fit_in (GimpDisplayShell *shell); +void gimp_display_shell_scale_fill (GimpDisplayShell *shell); +void gimp_display_shell_scale_by_values (GimpDisplayShell *shell, + gdouble scale, + gint offset_x, + gint offset_y, + gboolean resize_window); + +void gimp_display_shell_scale_drag (GimpDisplayShell *shell, + gdouble start_x, + gdouble start_y, + gdouble delta_x, + gdouble delta_y); + +void gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell, + gboolean grow_only); +void gimp_display_shell_scale_resize (GimpDisplayShell *shell, + gboolean resize_window, + gboolean grow_only); +void gimp_display_shell_set_initial_scale (GimpDisplayShell *shell, + gdouble scale, + gint *display_width, + gint *display_height); + +void gimp_display_shell_get_rotated_scale (GimpDisplayShell *shell, + gdouble *scale_x, + gdouble *scale_y); + +/* debug API for testing */ + +void gimp_display_shell_push_zoom_focus_pointer_pos (GimpDisplayShell *shell, + gint x, + gint y); + + +#endif /* __GIMP_DISPLAY_SHELL_SCALE_H__ */ diff --git a/app/display/gimpdisplayshell-scroll.c b/app/display/gimpdisplayshell-scroll.c new file mode 100644 index 0000000..ec5084d --- /dev/null +++ b/app/display/gimpdisplayshell-scroll.c @@ -0,0 +1,529 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdlib.h> +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "core/gimpimage.h" + +#include "gimpcanvas.h" +#include "gimpdisplay.h" +#include "gimpdisplay-foreach.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-rotate.h" +#include "gimpdisplayshell-rulers.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-scrollbars.h" +#include "gimpdisplayshell-transform.h" + + +#define OVERPAN_FACTOR 0.5 + + +/** + * gimp_display_shell_scroll: + * @shell: + * @x_offset: + * @y_offset: + * + * This function scrolls the image in the shell's viewport. It does + * actual scrolling of the pixels, so only the newly scrolled-in parts + * are freshly redrawn. + * + * Use it for incremental actual panning. + **/ +void +gimp_display_shell_scroll (GimpDisplayShell *shell, + gint x_offset, + gint y_offset) +{ + gint old_x; + gint old_y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (x_offset == 0 && y_offset == 0) + return; + + old_x = shell->offset_x; + old_y = shell->offset_y; + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + shell->offset_x += x_offset; + shell->offset_y += y_offset; + + gimp_display_shell_scroll_clamp_and_update (shell); + + /* the actual changes in offset */ + x_offset = (shell->offset_x - old_x); + y_offset = (shell->offset_y - old_y); + + if (x_offset || y_offset) + { + gimp_display_shell_scrolled (shell); + + gimp_overlay_box_scroll (GIMP_OVERLAY_BOX (shell->canvas), + -x_offset, -y_offset); + + } + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); +} + +/** + * gimp_display_shell_scroll_set_offsets: + * @shell: + * @offset_x: + * @offset_y: + * + * This function scrolls the image in the shell's viewport. It redraws + * the entire canvas. + * + * Use it for setting the scroll offset on freshly scaled images or + * when the window is resized. For panning, use + * gimp_display_shell_scroll(). + **/ +void +gimp_display_shell_scroll_set_offset (GimpDisplayShell *shell, + gint offset_x, + gint offset_y) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->offset_x == offset_x && + shell->offset_y == offset_y) + return; + + gimp_display_shell_scale_save_revert_values (shell); + + /* freeze the active tool */ + gimp_display_shell_pause (shell); + + shell->offset_x = offset_x; + shell->offset_y = offset_y; + + gimp_display_shell_scroll_clamp_and_update (shell); + + gimp_display_shell_scrolled (shell); + + gimp_display_shell_expose_full (shell); + + /* re-enable the active tool */ + gimp_display_shell_resume (shell); +} + +/** + * gimp_display_shell_scroll_clamp_and_update: + * @shell: + * + * Helper function for calling two functions that are commonly called + * in pairs. + **/ +void +gimp_display_shell_scroll_clamp_and_update (GimpDisplayShell *shell) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + if (image) + { + if (! shell->show_all) + { + gint bounds_x; + gint bounds_y; + gint bounds_width; + gint bounds_height; + gint min_offset_x; + gint max_offset_x; + gint min_offset_y; + gint max_offset_y; + gint offset_x; + gint offset_y; + + gimp_display_shell_rotate_update_transform (shell); + + gimp_display_shell_scale_get_image_bounds (shell, + &bounds_x, + &bounds_y, + &bounds_width, + &bounds_height); + + if (shell->disp_width < bounds_width) + { + min_offset_x = bounds_x - + shell->disp_width * OVERPAN_FACTOR; + max_offset_x = bounds_x + bounds_width - + shell->disp_width * (1.0 - OVERPAN_FACTOR); + } + else + { + gint overpan_amount; + + overpan_amount = shell->disp_width - + bounds_width * (1.0 - OVERPAN_FACTOR); + + min_offset_x = bounds_x - + overpan_amount; + max_offset_x = bounds_x + bounds_width - shell->disp_width + + overpan_amount; + } + + if (shell->disp_height < bounds_height) + { + min_offset_y = bounds_y - + shell->disp_height * OVERPAN_FACTOR; + max_offset_y = bounds_y + bounds_height - + shell->disp_height * (1.0 - OVERPAN_FACTOR); + } + else + { + gint overpan_amount; + + overpan_amount = shell->disp_height - + bounds_height * (1.0 - OVERPAN_FACTOR); + + min_offset_y = bounds_y - + overpan_amount; + max_offset_y = bounds_y + bounds_height + + overpan_amount - shell->disp_height; + } + + /* Clamp */ + + offset_x = CLAMP (shell->offset_x, min_offset_x, max_offset_x); + offset_y = CLAMP (shell->offset_y, min_offset_y, max_offset_y); + + if (offset_x != shell->offset_x || offset_y != shell->offset_y) + { + shell->offset_x = offset_x; + shell->offset_y = offset_y; + + gimp_display_shell_rotate_update_transform (shell); + } + + /* Set scrollbar stepper sensitiity */ + + gimp_display_shell_scrollbars_update_steppers (shell, + min_offset_x, + max_offset_x, + min_offset_y, + max_offset_y); + } + else + { + /* Set scrollbar stepper sensitiity */ + + gimp_display_shell_scrollbars_update_steppers (shell, + G_MININT, + G_MAXINT, + G_MININT, + G_MAXINT); + } + } + else + { + shell->offset_x = 0; + shell->offset_y = 0; + } + + gimp_display_shell_scrollbars_update (shell); + gimp_display_shell_rulers_update (shell); +} + +/** + * gimp_display_shell_scroll_unoverscrollify: + * @shell: + * @in_offset_x: + * @in_offset_y: + * @out_offset_x: + * @out_offset_y: + * + * Takes a scroll offset and returns the offset that will not result + * in a scroll beyond the image border. If the image is already + * overscrolled, the return value is 0 for that given axis. + **/ +void +gimp_display_shell_scroll_unoverscrollify (GimpDisplayShell *shell, + gint in_offset_x, + gint in_offset_y, + gint *out_offset_x, + gint *out_offset_y) +{ + gint sw, sh; + gint out_offset_x_dummy, out_offset_y_dummy; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! out_offset_x) out_offset_x = &out_offset_x_dummy; + if (! out_offset_y) out_offset_y = &out_offset_y_dummy; + + *out_offset_x = in_offset_x; + *out_offset_y = in_offset_y; + + if (! shell->show_all) + { + gimp_display_shell_scale_get_image_size (shell, &sw, &sh); + + if (in_offset_x < 0) + { + *out_offset_x = MAX (in_offset_x, + MIN (0, 0 - shell->offset_x)); + } + else if (in_offset_x > 0) + { + gint min_offset = sw - shell->disp_width; + + *out_offset_x = MIN (in_offset_x, + MAX (0, min_offset - shell->offset_x)); + } + + if (in_offset_y < 0) + { + *out_offset_y = MAX (in_offset_y, + MIN (0, 0 - shell->offset_y)); + } + else if (in_offset_y > 0) + { + gint min_offset = sh - shell->disp_height; + + *out_offset_y = MIN (in_offset_y, + MAX (0, min_offset - shell->offset_y)); + } + } +} + +/** + * gimp_display_shell_scroll_center_image_xy: + * @shell: + * @image_x: + * @image_y: + * + * Center the viewport around the passed image coordinate + **/ +void +gimp_display_shell_scroll_center_image_xy (GimpDisplayShell *shell, + gdouble image_x, + gdouble image_y) +{ + gint viewport_x; + gint viewport_y; + + gimp_display_shell_transform_xy (shell, + image_x, image_y, + &viewport_x, &viewport_y); + + gimp_display_shell_scroll (shell, + viewport_x - shell->disp_width / 2, + viewport_y - shell->disp_height / 2); +} + +/** + * gimp_display_shell_scroll_center_image: + * @shell: + * @horizontally: + * @vertically: + * + * Centers the image in the display shell on the desired axes. + **/ +void +gimp_display_shell_scroll_center_image (GimpDisplayShell *shell, + gboolean horizontally, + gboolean vertically) +{ + gint image_x; + gint image_y; + gint image_width; + gint image_height; + gint center_x; + gint center_y; + gint offset_x = 0; + gint offset_y = 0; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->display || + ! gimp_display_get_image (shell->display) || + (! vertically && ! horizontally)) + return; + + gimp_display_shell_scale_get_image_bounds (shell, + &image_x, &image_y, + &image_width, &image_height); + + if (shell->disp_width > image_width) + { + image_x -= (shell->disp_width - image_width) / 2; + image_width = shell->disp_width; + } + + if (shell->disp_height > image_height) + { + image_y -= (shell->disp_height - image_height) / 2; + image_height = shell->disp_height; + } + + center_x = image_x + image_width / 2; + center_y = image_y + image_height / 2; + + if (horizontally) + offset_x = center_x - shell->disp_width / 2 - shell->offset_x; + + if (vertically) + offset_y = center_y - shell->disp_height / 2 - shell->offset_y; + + gimp_display_shell_scroll (shell, offset_x, offset_y); +} + +/** + * gimp_display_shell_scroll_center_image: + * @shell: + * @horizontally: + * @vertically: + * + * Centers the image content in the display shell on the desired axes. + **/ +void +gimp_display_shell_scroll_center_content (GimpDisplayShell *shell, + gboolean horizontally, + gboolean vertically) +{ + gint content_x; + gint content_y; + gint content_width; + gint content_height; + gint center_x; + gint center_y; + gint offset_x = 0; + gint offset_y = 0; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->display || + ! gimp_display_get_image (shell->display) || + (! vertically && ! horizontally)) + return; + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + gimp_display_shell_scale_get_image_bounds (shell, + &content_x, + &content_y, + &content_width, + &content_height); + } + else + { + gimp_display_shell_scale_get_image_bounding_box (shell, + &content_x, + &content_y, + &content_width, + &content_height); + } + + if (shell->disp_width > content_width) + { + content_x -= (shell->disp_width - content_width) / 2; + content_width = shell->disp_width; + } + + if (shell->disp_height > content_height) + { + content_y -= (shell->disp_height - content_height) / 2; + content_height = shell->disp_height; + } + + center_x = content_x + content_width / 2; + center_y = content_y + content_height / 2; + + if (horizontally) + offset_x = center_x - shell->disp_width / 2 - shell->offset_x; + + if (vertically) + offset_y = center_y - shell->disp_height / 2 - shell->offset_y; + + gimp_display_shell_scroll (shell, offset_x, offset_y); +} + +/** + * gimp_display_shell_scroll_get_scaled_viewport: + * @shell: + * @x: + * @y: + * @w: + * @h: + * + * Gets the viewport in screen coordinates, with origin at (0, 0) in + * the image. + **/ +void +gimp_display_shell_scroll_get_scaled_viewport (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + *x = shell->offset_x; + *y = shell->offset_y; + *w = shell->disp_width; + *h = shell->disp_height; +} + +/** + * gimp_display_shell_scroll_get_viewport: + * @shell: + * @x: + * @y: + * @w: + * @h: + * + * Gets the viewport in image coordinates. + **/ +void +gimp_display_shell_scroll_get_viewport (GimpDisplayShell *shell, + gdouble *x, + gdouble *y, + gdouble *w, + gdouble *h) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + *x = shell->offset_x / shell->scale_x; + *y = shell->offset_y / shell->scale_y; + *w = shell->disp_width / shell->scale_x; + *h = shell->disp_height / shell->scale_y; +} diff --git a/app/display/gimpdisplayshell-scroll.h b/app/display/gimpdisplayshell-scroll.h new file mode 100644 index 0000000..fba444b --- /dev/null +++ b/app/display/gimpdisplayshell-scroll.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_SCROLL_H__ +#define __GIMP_DISPLAY_SHELL_SCROLL_H__ + + +void gimp_display_shell_scroll (GimpDisplayShell *shell, + gint x_offset, + gint y_offset); +void gimp_display_shell_scroll_set_offset (GimpDisplayShell *shell, + gint offset_x, + gint offset_y); + +void gimp_display_shell_scroll_clamp_and_update (GimpDisplayShell *shell); + +void gimp_display_shell_scroll_unoverscrollify (GimpDisplayShell *shell, + gint in_offset_x, + gint in_offset_y, + gint *out_offset_x, + gint *out_offset_y); + +void gimp_display_shell_scroll_center_image_xy (GimpDisplayShell *shell, + gdouble image_x, + gdouble image_y); +void gimp_display_shell_scroll_center_image (GimpDisplayShell *shell, + gboolean horizontally, + gboolean vertically); +void gimp_display_shell_scroll_center_content (GimpDisplayShell *shell, + gboolean horizontally, + gboolean vertically); + +void gimp_display_shell_scroll_get_scaled_viewport (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *w, + gint *h); +void gimp_display_shell_scroll_get_viewport (GimpDisplayShell *shell, + gdouble *x, + gdouble *y, + gdouble *w, + gdouble *h); + + +#endif /* __GIMP_DISPLAY_SHELL_SCROLL_H__ */ diff --git a/app/display/gimpdisplayshell-scrollbars.c b/app/display/gimpdisplayshell-scrollbars.c new file mode 100644 index 0000000..3696e01 --- /dev/null +++ b/app/display/gimpdisplayshell-scrollbars.c @@ -0,0 +1,244 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimpimage.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scrollbars.h" + + +#define MINIMUM_STEP_AMOUNT 1.0 + + +/** + * gimp_display_shell_scrollbars_update: + * @shell: + * + **/ +void +gimp_display_shell_scrollbars_update (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->display) + return; + + /* Horizontal scrollbar */ + + g_object_freeze_notify (G_OBJECT (shell->hsbdata)); + + /* Update upper and lower value before we set the new value */ + gimp_display_shell_scrollbars_setup_horizontal (shell, shell->offset_x); + + g_object_set (shell->hsbdata, + "value", (gdouble) shell->offset_x, + "page-size", (gdouble) shell->disp_width, + "page-increment", (gdouble) shell->disp_width / 2, + NULL); + + g_object_thaw_notify (G_OBJECT (shell->hsbdata)); /* emits "changed" */ + + + /* Vertcal scrollbar */ + + g_object_freeze_notify (G_OBJECT (shell->vsbdata)); + + /* Update upper and lower value before we set the new value */ + gimp_display_shell_scrollbars_setup_vertical (shell, shell->offset_y); + + g_object_set (shell->vsbdata, + "value", (gdouble) shell->offset_y, + "page-size", (gdouble) shell->disp_height, + "page-increment", (gdouble) shell->disp_height / 2, + NULL); + + g_object_thaw_notify (G_OBJECT (shell->vsbdata)); /* emits "changed" */ +} + +/** + * gimp_display_shell_scrollbars_setup_horizontal: + * @shell: + * @value: + * + * Setup the limits of the horizontal scrollbar + **/ +void +gimp_display_shell_scrollbars_setup_horizontal (GimpDisplayShell *shell, + gdouble value) +{ + gint bounds_x; + gint bounds_width; + gint bounding_box_x; + gint bounding_box_width; + gint x1; + gint x2; + gdouble lower; + gdouble upper; + gdouble scale_x; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->display || ! gimp_display_get_image (shell->display)) + return; + + gimp_display_shell_scale_get_image_bounds (shell, + &bounds_x, NULL, + &bounds_width, NULL); + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + bounding_box_x = bounds_x; + bounding_box_width = bounds_width; + } + else + { + gimp_display_shell_scale_get_image_bounding_box ( + shell, + &bounding_box_x, NULL, + &bounding_box_width, NULL); + } + + x1 = bounding_box_x; + x2 = bounding_box_x + bounding_box_width; + + x1 = MIN (x1, bounds_x + bounds_width / 2 - shell->disp_width / 2); + x2 = MAX (x2, bounds_x + bounds_width / 2 + (shell->disp_width + 1) / 2); + + lower = MIN (value, x1); + upper = MAX (value + shell->disp_width, x2); + + gimp_display_shell_get_rotated_scale (shell, &scale_x, NULL); + + g_object_set (shell->hsbdata, + "lower", lower, + "upper", upper, + "step-increment", (gdouble) MAX (scale_x, MINIMUM_STEP_AMOUNT), + NULL); +} + +/** + * gimp_display_shell_scrollbars_setup_vertical: + * @shell: + * @value: + * + * Setup the limits of the vertical scrollbar + **/ +void +gimp_display_shell_scrollbars_setup_vertical (GimpDisplayShell *shell, + gdouble value) +{ + gint bounds_y; + gint bounds_height; + gint bounding_box_y; + gint bounding_box_height; + gint y1; + gint y2; + gdouble lower; + gdouble upper; + gdouble scale_y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (! shell->display || ! gimp_display_get_image (shell->display)) + return; + + gimp_display_shell_scale_get_image_bounds (shell, + NULL, &bounds_y, + NULL, &bounds_height); + + if (! gimp_display_shell_get_infinite_canvas (shell)) + { + bounding_box_y = bounds_y; + bounding_box_height = bounds_height; + } + else + { + gimp_display_shell_scale_get_image_bounding_box ( + shell, + NULL, &bounding_box_y, + NULL, &bounding_box_height); + } + + y1 = bounding_box_y; + y2 = bounding_box_y + bounding_box_height; + + y1 = MIN (y1, bounds_y + bounds_height / 2 - shell->disp_height / 2); + y2 = MAX (y2, bounds_y + bounds_height / 2 + (shell->disp_height + 1) / 2); + + lower = MIN (value, y1); + upper = MAX (value + shell->disp_height, y2); + + gimp_display_shell_get_rotated_scale (shell, NULL, &scale_y); + + g_object_set (shell->vsbdata, + "lower", lower, + "upper", upper, + "step-increment", (gdouble) MAX (scale_y, MINIMUM_STEP_AMOUNT), + NULL); +} + +/** + * gimp_display_shell_scrollbars_update_steppers: + * @shell: + * @min_offset_x: + * @max_offset_x: + * @min_offset_y: + * @max_offset_y: + * + * Sets the scrollbars' stepper sensitivity which is set differently + * from its adjustment limits because we support overscrolling. + **/ +void +gimp_display_shell_scrollbars_update_steppers (GimpDisplayShell *shell, + gint min_offset_x, + gint max_offset_x, + gint min_offset_y, + gint max_offset_y) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gtk_range_set_lower_stepper_sensitivity (GTK_RANGE (shell->hsb), + min_offset_x < shell->offset_x ? + GTK_SENSITIVITY_ON : + GTK_SENSITIVITY_OFF); + + gtk_range_set_upper_stepper_sensitivity (GTK_RANGE (shell->hsb), + max_offset_x > shell->offset_x ? + GTK_SENSITIVITY_ON : + GTK_SENSITIVITY_OFF); + + gtk_range_set_lower_stepper_sensitivity (GTK_RANGE (shell->vsb), + min_offset_y < shell->offset_y ? + GTK_SENSITIVITY_ON : + GTK_SENSITIVITY_OFF); + + gtk_range_set_upper_stepper_sensitivity (GTK_RANGE (shell->vsb), + max_offset_y > shell->offset_y ? + GTK_SENSITIVITY_ON : + GTK_SENSITIVITY_OFF); +} diff --git a/app/display/gimpdisplayshell-scrollbars.h b/app/display/gimpdisplayshell-scrollbars.h new file mode 100644 index 0000000..c15f802 --- /dev/null +++ b/app/display/gimpdisplayshell-scrollbars.h @@ -0,0 +1,36 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_SCROLLBARS_H__ +#define __GIMP_DISPLAY_SHELL_SCROLLBARS_H__ + + +void gimp_display_shell_scrollbars_update (GimpDisplayShell *shell); + +void gimp_display_shell_scrollbars_setup_horizontal (GimpDisplayShell *shell, + gdouble value); +void gimp_display_shell_scrollbars_setup_vertical (GimpDisplayShell *shell, + gdouble value); + +void gimp_display_shell_scrollbars_update_steppers (GimpDisplayShell *shell, + gint min_offset_x, + gint max_offset_x, + gint min_offset_y, + gint max_offset_y); + + +#endif /* __GIMP_DISPLAY_SHELL_SCROLLBARS_H__ */ diff --git a/app/display/gimpdisplayshell-selection.c b/app/display/gimpdisplayshell-selection.c new file mode 100644 index 0000000..3098ddc --- /dev/null +++ b/app/display/gimpdisplayshell-selection.c @@ -0,0 +1,505 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "core/gimp.h" +#include "core/gimp-cairo.h" +#include "core/gimpboundary.h" +#include "core/gimpchannel.h" +#include "core/gimpimage.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-draw.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-selection.h" +#include "gimpdisplayshell-transform.h" + + +struct _Selection +{ + GimpDisplayShell *shell; /* shell that owns the selection */ + + GimpSegment *segs_in; /* gdk segments of area boundary */ + gint n_segs_in; /* number of segments in segs_in */ + + GimpSegment *segs_out; /* gdk segments of area boundary */ + gint n_segs_out; /* number of segments in segs_out */ + + guint index; /* index of current stipple pattern */ + gint paused; /* count of pause requests */ + gboolean shell_visible; /* visility of the display shell */ + gboolean show_selection; /* is the selection visible? */ + guint timeout; /* timer for successive draws */ + cairo_pattern_t *segs_in_mask; /* cache for rendered segments */ +}; + + +/* local function prototypes */ + +static void selection_start (Selection *selection); +static void selection_stop (Selection *selection); + +static void selection_draw (Selection *selection, + cairo_t *cr); +static void selection_undraw (Selection *selection); + +static void selection_render_mask (Selection *selection); + +static void selection_zoom_segs (Selection *selection, + const GimpBoundSeg *src_segs, + GimpSegment *dest_segs, + gint n_segs); +static void selection_generate_segs (Selection *selection); +static void selection_free_segs (Selection *selection); + +static gboolean selection_start_timeout (Selection *selection); +static gboolean selection_timeout (Selection *selection); + +static gboolean selection_window_state_event (GtkWidget *shell, + GdkEventWindowState *event, + Selection *selection); +static gboolean selection_visibility_notify_event (GtkWidget *shell, + GdkEventVisibility *event, + Selection *selection); + + +/* public functions */ + +void +gimp_display_shell_selection_init (GimpDisplayShell *shell) +{ + Selection *selection; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection == NULL); + + selection = g_slice_new0 (Selection); + + selection->shell = shell; + selection->shell_visible = TRUE; + selection->show_selection = gimp_display_shell_get_show_selection (shell); + + shell->selection = selection; + + g_signal_connect (shell, "window-state-event", + G_CALLBACK (selection_window_state_event), + selection); + g_signal_connect (shell, "visibility-notify-event", + G_CALLBACK (selection_visibility_notify_event), + selection); +} + +void +gimp_display_shell_selection_free (GimpDisplayShell *shell) +{ + Selection *selection; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection != NULL); + + selection = shell->selection; + + selection_stop (selection); + + g_signal_handlers_disconnect_by_func (shell, + selection_window_state_event, + selection); + g_signal_handlers_disconnect_by_func (shell, + selection_visibility_notify_event, + selection); + + selection_free_segs (selection); + + g_slice_free (Selection, selection); + + shell->selection = NULL; +} + +void +gimp_display_shell_selection_undraw (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection != NULL); + + if (gimp_display_get_image (shell->display)) + { + selection_undraw (shell->selection); + } + else + { + selection_stop (shell->selection); + selection_free_segs (shell->selection); + } +} + +void +gimp_display_shell_selection_restart (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection != NULL); + + if (gimp_display_get_image (shell->display)) + { + selection_start (shell->selection); + } +} + +void +gimp_display_shell_selection_pause (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection != NULL); + + if (gimp_display_get_image (shell->display)) + { + if (shell->selection->paused == 0) + selection_stop (shell->selection); + + shell->selection->paused++; + } +} + +void +gimp_display_shell_selection_resume (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection != NULL); + + if (gimp_display_get_image (shell->display)) + { + shell->selection->paused--; + + if (shell->selection->paused == 0) + selection_start (shell->selection); + } +} + +void +gimp_display_shell_selection_set_show (GimpDisplayShell *shell, + gboolean show) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->selection != NULL); + + if (gimp_display_get_image (shell->display)) + { + Selection *selection = shell->selection; + + if (show != selection->show_selection) + { + selection_undraw (selection); + + selection->show_selection = show; + + selection_start (selection); + } + } +} + + +/* private functions */ + +static void +selection_start (Selection *selection) +{ + selection_stop (selection); + + /* If this selection is paused, do not start it */ + if (selection->paused == 0) + { + selection->timeout = g_idle_add ((GSourceFunc) selection_start_timeout, + selection); + } +} + +static void +selection_stop (Selection *selection) +{ + if (selection->timeout) + { + g_source_remove (selection->timeout); + selection->timeout = 0; + } +} + +static void +selection_draw (Selection *selection, + cairo_t *cr) +{ + if (selection->segs_in) + { + gimp_display_shell_draw_selection_in (selection->shell, cr, + selection->segs_in_mask, + selection->index % 8); + } +} + +static void +selection_undraw (Selection *selection) +{ + gint x, y, w, h; + + selection_stop (selection); + + if (gimp_display_shell_mask_bounds (selection->shell, &x, &y, &w, &h)) + { + /* expose will restart the selection */ + gimp_display_shell_expose_area (selection->shell, x, y, w, h); + } + else + { + selection_start (selection); + } +} + +static void +selection_render_mask (Selection *selection) +{ + GdkWindow *window; + cairo_surface_t *surface; + cairo_t *cr; + + window = gtk_widget_get_window (selection->shell->canvas); + surface = gdk_window_create_similar_surface (window, CAIRO_CONTENT_ALPHA, + gdk_window_get_width (window), + gdk_window_get_height (window)); + cr = cairo_create (surface); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_line_width (cr, 1.0); + + if (selection->shell->rotate_transform) + cairo_transform (cr, selection->shell->rotate_transform); + + gimp_cairo_segments (cr, + selection->segs_in, + selection->n_segs_in); + cairo_stroke (cr); + + selection->segs_in_mask = cairo_pattern_create_for_surface (surface); + + cairo_destroy (cr); + cairo_surface_destroy (surface); +} + +static void +selection_zoom_segs (Selection *selection, + const GimpBoundSeg *src_segs, + GimpSegment *dest_segs, + gint n_segs) +{ + const gint xclamp = selection->shell->disp_width + 1; + const gint yclamp = selection->shell->disp_height + 1; + gint i; + + gimp_display_shell_zoom_segments (selection->shell, + src_segs, dest_segs, n_segs, + 0.0, 0.0); + + for (i = 0; i < n_segs; i++) + { + if (! selection->shell->rotate_transform) + { + dest_segs[i].x1 = CLAMP (dest_segs[i].x1, -1, xclamp); + dest_segs[i].y1 = CLAMP (dest_segs[i].y1, -1, yclamp); + + dest_segs[i].x2 = CLAMP (dest_segs[i].x2, -1, xclamp); + dest_segs[i].y2 = CLAMP (dest_segs[i].y2, -1, yclamp); + } + + /* If this segment is a closing segment && the segments lie inside + * the region, OR if this is an opening segment and the segments + * lie outside the region... + * we need to transform it by one display pixel + */ + if (! src_segs[i].open) + { + /* If it is vertical */ + if (dest_segs[i].x1 == dest_segs[i].x2) + { + dest_segs[i].x1 -= 1; + dest_segs[i].x2 -= 1; + } + else + { + dest_segs[i].y1 -= 1; + dest_segs[i].y2 -= 1; + } + } + } +} + +static void +selection_generate_segs (Selection *selection) +{ + GimpImage *image = gimp_display_get_image (selection->shell->display); + const GimpBoundSeg *segs_in; + const GimpBoundSeg *segs_out; + + /* Ask the image for the boundary of its selected region... + * Then transform that information into a new buffer of GimpSegments + */ + gimp_channel_boundary (gimp_image_get_mask (image), + &segs_in, &segs_out, + &selection->n_segs_in, &selection->n_segs_out, + 0, 0, 0, 0); + + if (selection->n_segs_in) + { + selection->segs_in = g_new (GimpSegment, selection->n_segs_in); + selection_zoom_segs (selection, segs_in, + selection->segs_in, selection->n_segs_in); + + selection_render_mask (selection); + } + else + { + selection->segs_in = NULL; + } + + /* Possible secondary boundary representation */ + if (selection->n_segs_out) + { + selection->segs_out = g_new (GimpSegment, selection->n_segs_out); + selection_zoom_segs (selection, segs_out, + selection->segs_out, selection->n_segs_out); + } + else + { + selection->segs_out = NULL; + } +} + +static void +selection_free_segs (Selection *selection) +{ + g_clear_pointer (&selection->segs_in, g_free); + selection->n_segs_in = 0; + + g_clear_pointer (&selection->segs_out, g_free); + selection->segs_out = NULL; + + g_clear_pointer (&selection->segs_in_mask, cairo_pattern_destroy); +} + +static gboolean +selection_start_timeout (Selection *selection) +{ + selection_free_segs (selection); + selection->timeout = 0; + + if (! gimp_display_get_image (selection->shell->display)) + return FALSE; + + selection_generate_segs (selection); + + selection->index = 0; + + /* Draw the ants */ + if (selection->show_selection) + { + GimpDisplayConfig *config = selection->shell->display->config; + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (selection->shell->canvas)); + + selection_draw (selection, cr); + + if (selection->segs_out) + { + if (selection->shell->rotate_transform) + cairo_transform (cr, selection->shell->rotate_transform); + + gimp_display_shell_draw_selection_out (selection->shell, cr, + selection->segs_out, + selection->n_segs_out); + } + + cairo_destroy (cr); + + if (selection->segs_in && selection->shell_visible) + selection->timeout = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, + config->marching_ants_speed, + (GSourceFunc) selection_timeout, + selection, NULL); + } + + return FALSE; +} + +static gboolean +selection_timeout (Selection *selection) +{ + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (selection->shell->canvas)); + + selection->index++; + selection_draw (selection, cr); + + cairo_destroy (cr); + + return TRUE; +} + +static void +selection_set_shell_visible (Selection *selection, + gboolean shell_visible) +{ + if (selection->shell_visible != shell_visible) + { + selection->shell_visible = shell_visible; + + if (shell_visible) + selection_start (selection); + else + selection_stop (selection); + } +} + +static gboolean +selection_window_state_event (GtkWidget *shell, + GdkEventWindowState *event, + Selection *selection) +{ + selection_set_shell_visible (selection, + (event->new_window_state & (GDK_WINDOW_STATE_WITHDRAWN | + GDK_WINDOW_STATE_ICONIFIED)) == 0); + + return FALSE; +} + +static gboolean +selection_visibility_notify_event (GtkWidget *shell, + GdkEventVisibility *event, + Selection *selection) +{ + selection_set_shell_visible (selection, + event->state != GDK_VISIBILITY_FULLY_OBSCURED); + + return FALSE; +} diff --git a/app/display/gimpdisplayshell-selection.h b/app/display/gimpdisplayshell-selection.h new file mode 100644 index 0000000..2f63c80 --- /dev/null +++ b/app/display/gimpdisplayshell-selection.h @@ -0,0 +1,35 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_SELECTION_H__ +#define __GIMP_DISPLAY_SHELL_SELECTION_H__ + + +void gimp_display_shell_selection_init (GimpDisplayShell *shell); +void gimp_display_shell_selection_free (GimpDisplayShell *shell); + +void gimp_display_shell_selection_undraw (GimpDisplayShell *shell); +void gimp_display_shell_selection_restart (GimpDisplayShell *shell); + +void gimp_display_shell_selection_pause (GimpDisplayShell *shell); +void gimp_display_shell_selection_resume (GimpDisplayShell *shell); + +void gimp_display_shell_selection_set_show (GimpDisplayShell *shell, + gboolean show); + + +#endif /* __GIMP_DISPLAY_SHELL_SELECTION_H__ */ diff --git a/app/display/gimpdisplayshell-title.c b/app/display/gimpdisplayshell-title.c new file mode 100644 index 0000000..ff356ce --- /dev/null +++ b/app/display/gimpdisplayshell-title.c @@ -0,0 +1,564 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "libgimpbase/gimpbase.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "gegl/gimp-babl.h" + +#include "core/gimpcontainer.h" +#include "core/gimpdrawable.h" +#include "core/gimpimage.h" +#include "core/gimpimage-color-profile.h" +#include "core/gimpitem.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-title.h" +#include "gimpstatusbar.h" + +#include "about.h" + +#include "gimp-intl.h" + + +#define MAX_TITLE_BUF 512 + + +static gboolean gimp_display_shell_update_title_idle (gpointer data); +static gint gimp_display_shell_format_title (GimpDisplayShell *display, + gchar *title, + gint title_len, + const gchar *format); + + +/* public functions */ + +void +gimp_display_shell_title_update (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->title_idle_id) + g_source_remove (shell->title_idle_id); + + shell->title_idle_id = g_idle_add (gimp_display_shell_update_title_idle, + shell); +} + + +/* private functions */ + +static gboolean +gimp_display_shell_update_title_idle (gpointer data) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (data); + + shell->title_idle_id = 0; + + if (gimp_display_get_image (shell->display)) + { + GimpDisplayConfig *config = shell->display->config; + gchar title[MAX_TITLE_BUF]; + gchar status[MAX_TITLE_BUF]; + gint len; + + /* format the title */ + len = gimp_display_shell_format_title (shell, title, sizeof (title), + config->image_title_format); + + if (len) /* U+2013 EN DASH */ + len += g_strlcpy (title + len, " \342\200\223 ", sizeof (title) - len); + + g_strlcpy (title + len, GIMP_ACRONYM, sizeof (title) - len); + + /* format the statusbar */ + gimp_display_shell_format_title (shell, status, sizeof (status), + config->image_status_format); + + g_object_set (shell, + "title", title, + "status", status, + NULL); + } + else + { + g_object_set (shell, + "title", GIMP_NAME, + "status", " ", + NULL); + } + + return FALSE; +} + +static const gchar * +gimp_display_shell_title_image_type (GimpImage *image) +{ + const gchar *name = ""; + + gimp_enum_get_value (GIMP_TYPE_IMAGE_BASE_TYPE, + gimp_image_get_base_type (image), NULL, NULL, &name, NULL); + + return name; +} + +static const gchar * +gimp_display_shell_title_image_precision (GimpImage *image) +{ + const gchar *name = ""; + + gimp_enum_get_value (GIMP_TYPE_PRECISION, + gimp_image_get_precision (image), NULL, NULL, &name, NULL); + + return name; +} + +static gint print (gchar *buf, + gint len, + gint start, + const gchar *fmt, + ...) G_GNUC_PRINTF (4, 5); + +static gint +print (gchar *buf, + gint len, + gint start, + const gchar *fmt, + ...) +{ + va_list args; + gint printed; + + va_start (args, fmt); + + printed = g_vsnprintf (buf + start, len - start, fmt, args); + if (printed < 0) + printed = len - start; + + va_end (args); + + return printed; +} + +static gint +gimp_display_shell_format_title (GimpDisplayShell *shell, + gchar *title, + gint title_len, + const gchar *format) +{ + GimpImage *image; + GimpDrawable *drawable; + gint num, denom; + gint i = 0; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), 0); + + image = gimp_display_get_image (shell->display); + + if (! image) + { + title[0] = '\n'; + return 0; + } + + drawable = gimp_image_get_active_drawable (image); + + gimp_zoom_model_get_fraction (shell->zoom, &num, &denom); + + while (i < title_len && *format) + { + switch (*format) + { + case '%': + format++; + switch (*format) + { + case 0: + /* format string ends within %-sequence, print literal '%' */ + + case '%': + title[i++] = '%'; + break; + + case 'f': /* base filename */ + i += print (title, title_len, i, "%s", + gimp_image_get_display_name (image)); + break; + + case 'F': /* full filename */ + i += print (title, title_len, i, "%s", + gimp_image_get_display_path (image)); + break; + + case 'p': /* PDB id */ + i += print (title, title_len, i, "%d", gimp_image_get_ID (image)); + break; + + case 'i': /* instance */ + i += print (title, title_len, i, "%d", + gimp_display_get_instance (shell->display)); + break; + + case 't': /* image type */ + i += print (title, title_len, i, "%s %s", + gimp_display_shell_title_image_type (image), + gimp_display_shell_title_image_precision (image)); + break; + + case 'T': /* drawable type */ + if (drawable) + { + const Babl *format = gimp_drawable_get_format (drawable); + + i += print (title, title_len, i, "%s", + gimp_babl_format_get_description (format)); + } + break; + + case 's': /* user source zoom factor */ + i += print (title, title_len, i, "%d", denom); + break; + + case 'd': /* user destination zoom factor */ + i += print (title, title_len, i, "%d", num); + break; + + case 'z': /* user zoom factor (percentage) */ + { + gdouble scale = gimp_zoom_model_get_factor (shell->zoom); + + i += print (title, title_len, i, + scale >= 0.15 ? "%.0f" : "%.2f", 100.0 * scale); + } + break; + + case 'D': /* dirty flag */ + if (format[1] == 0) + { + /* format string ends within %D-sequence, print literal '%D' */ + i += print (title, title_len, i, "%%D"); + break; + } + if (gimp_image_is_dirty (image)) + title[i++] = format[1]; + format++; + break; + + case 'C': /* clean flag */ + if (format[1] == 0) + { + /* format string ends within %C-sequence, print literal '%C' */ + i += print (title, title_len, i, "%%C"); + break; + } + if (! gimp_image_is_dirty (image)) + title[i++] = format[1]; + format++; + break; + + case 'B': /* dirty flag (long) */ + if (gimp_image_is_dirty (image)) + i += print (title, title_len, i, "%s", _("(modified)")); + break; + + case 'A': /* clean flag (long) */ + if (! gimp_image_is_dirty (image)) + i += print (title, title_len, i, "%s", _("(clean)")); + break; + + case 'N': /* not-exported flag */ + if (format[1] == 0) + { + /* format string ends within %E-sequence, print literal '%E' */ + i += print (title, title_len, i, "%%N"); + break; + } + if (gimp_image_is_export_dirty (image)) + title[i++] = format[1]; + format++; + break; + + case 'E': /* exported flag */ + if (format[1] == 0) + { + /* format string ends within %E-sequence, print literal '%E' */ + i += print (title, title_len, i, "%%E"); + break; + } + if (! gimp_image_is_export_dirty (image)) + title[i++] = format[1]; + format++; + break; + + case 'm': /* memory used by image */ + { + GimpObject *object = GIMP_OBJECT (image); + gchar *str; + + str = g_format_size (gimp_object_get_memsize (object, NULL)); + i += print (title, title_len, i, "%s", str); + g_free (str); + } + break; + + case 'M': /* image size in megapixels */ + i += print (title, title_len, i, "%.1f", + (gdouble) gimp_image_get_width (image) * + (gdouble) gimp_image_get_height (image) / 1000000.0); + break; + + case 'l': /* number of layers */ + i += print (title, title_len, i, "%d", + gimp_image_get_n_layers (image)); + break; + + case 'L': /* number of layers (long) */ + { + gint num = gimp_image_get_n_layers (image); + + i += print (title, title_len, i, + ngettext ("%d layer", "%d layers", num), num); + } + break; + + case 'n': /* active drawable name */ + if (drawable) + { + gchar *desc; + + desc = gimp_viewable_get_description (GIMP_VIEWABLE (drawable), + NULL); + i += print (title, title_len, i, "%s", desc); + g_free (desc); + } + else + { + i += print (title, title_len, i, "%s", _("(none)")); + } + break; + + case 'P': /* active drawable PDB id */ + if (drawable) + i += print (title, title_len, i, "%d", + gimp_item_get_ID (GIMP_ITEM (drawable))); + else + i += print (title, title_len, i, "%s", _("(none)")); + break; + + case 'W': /* width in real-world units */ + if (shell->unit != GIMP_UNIT_PIXEL) + { + gdouble xres; + gdouble yres; + gchar unit_format[8]; + + gimp_image_get_resolution (image, &xres, &yres); + + g_snprintf (unit_format, sizeof (unit_format), "%%.%df", + gimp_unit_get_scaled_digits (shell->unit, xres)); + i += print (title, title_len, i, unit_format, + gimp_pixels_to_units (gimp_image_get_width (image), + shell->unit, xres)); + break; + } + /* else fallthru */ + + case 'w': /* width in pixels */ + i += print (title, title_len, i, "%d", + gimp_image_get_width (image)); + break; + + case 'H': /* height in real-world units */ + if (shell->unit != GIMP_UNIT_PIXEL) + { + gdouble xres; + gdouble yres; + gchar unit_format[8]; + + gimp_image_get_resolution (image, &xres, &yres); + + g_snprintf (unit_format, sizeof (unit_format), "%%.%df", + gimp_unit_get_scaled_digits (shell->unit, yres)); + i += print (title, title_len, i, unit_format, + gimp_pixels_to_units (gimp_image_get_height (image), + shell->unit, yres)); + break; + } + /* else fallthru */ + + case 'h': /* height in pixels */ + i += print (title, title_len, i, "%d", + gimp_image_get_height (image)); + break; + + case 'u': /* unit symbol */ + i += print (title, title_len, i, "%s", + gimp_unit_get_symbol (shell->unit)); + break; + + case 'U': /* unit abbreviation */ + i += print (title, title_len, i, "%s", + gimp_unit_get_abbreviation (shell->unit)); + break; + + case 'X': /* drawable width in real world units */ + if (drawable && shell->unit != GIMP_UNIT_PIXEL) + { + gdouble xres; + gdouble yres; + gchar unit_format[8]; + + gimp_image_get_resolution (image, &xres, &yres); + + g_snprintf (unit_format, sizeof (unit_format), "%%.%df", + gimp_unit_get_scaled_digits (shell->unit, xres)); + i += print (title, title_len, i, unit_format, + gimp_pixels_to_units (gimp_item_get_width + (GIMP_ITEM (drawable)), + shell->unit, xres)); + break; + } + /* else fallthru */ + + case 'x': /* drawable width in pixels */ + if (drawable) + i += print (title, title_len, i, "%d", + gimp_item_get_width (GIMP_ITEM (drawable))); + break; + + case 'Y': /* drawable height in real world units */ + if (drawable && shell->unit != GIMP_UNIT_PIXEL) + { + gdouble xres; + gdouble yres; + gchar unit_format[8]; + + gimp_image_get_resolution (image, &xres, &yres); + + g_snprintf (unit_format, sizeof (unit_format), "%%.%df", + gimp_unit_get_scaled_digits (shell->unit, yres)); + i += print (title, title_len, i, unit_format, + gimp_pixels_to_units (gimp_item_get_height + (GIMP_ITEM (drawable)), + shell->unit, yres)); + break; + } + /* else fallthru */ + + case 'y': /* drawable height in pixels */ + if (drawable) + i += print (title, title_len, i, "%d", + gimp_item_get_height (GIMP_ITEM (drawable))); + break; + + case 'o': /* image's color profile name */ + if (gimp_image_get_is_color_managed (image)) + { + GimpColorManaged *managed = GIMP_COLOR_MANAGED (image); + GimpColorProfile *profile; + + profile = gimp_color_managed_get_color_profile (managed); + + i += print (title, title_len, i, "%s", + gimp_color_profile_get_label (profile)); + } + else + { + i += print (title, title_len, i, "%s", + _("not color managed")); + } + break; + + case 'e': /* display's offsets in pixels */ + { + gdouble scale = gimp_zoom_model_get_factor (shell->zoom); + gdouble offset_x = shell->offset_x / scale; + gdouble offset_y = shell->offset_y / scale; + + i += print (title, title_len, i, + scale >= 0.15 ? "%.0fx%.0f" : "%.2fx%.2f", + offset_x, offset_y); + } + break; + + case 'r': /* view rotation angle in degrees */ + { + i += print (title, title_len, i, "%.1f", shell->rotate_angle); + } + break; + + case '\xc3': /* utf-8 extended char */ + { + format ++; + switch (*format) + { + case '\xbe': + /* line actually written at 23:55 on an Easter Sunday */ + i += print (title, title_len, i, "42"); + break; + + default: + /* in the case of an unhandled utf-8 extended char format + * leave the format string parsing as it was + */ + format--; + break; + } + } + break; + + /* Other cool things to be added: + * %r = xresolution + * %R = yresolution + * %ø = image's fractal dimension + * %þ = the answer to everything - (implemented) + */ + + default: + /* format string contains unknown %-sequence, print it literally */ + i += print (title, title_len, i, "%%%c", *format); + break; + } + break; + + default: + title[i++] = *format; + break; + } + + format++; + } + + title[MIN (i, title_len - 1)] = '\0'; + + return i; +} diff --git a/app/display/gimpdisplayshell-title.h b/app/display/gimpdisplayshell-title.h new file mode 100644 index 0000000..0a3ed21 --- /dev/null +++ b/app/display/gimpdisplayshell-title.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_TITLE_H__ +#define __GIMP_DISPLAY_SHELL_TITLE_H__ + + +void gimp_display_shell_title_update (GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_TITLE_H__ */ diff --git a/app/display/gimpdisplayshell-tool-events.c b/app/display/gimpdisplayshell-tool-events.c new file mode 100644 index 0000000..714f39f --- /dev/null +++ b/app/display/gimpdisplayshell-tool-events.c @@ -0,0 +1,2144 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" +#include "tools/tools-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "core/gimp.h" +#include "core/gimp-filter-history.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimage-pick-item.h" +#include "core/gimpitem.h" + +#include "widgets/gimpcontrollers.h" +#include "widgets/gimpcontrollerkeyboard.h" +#include "widgets/gimpcontrollermouse.h" +#include "widgets/gimpcontrollerwheel.h" +#include "widgets/gimpdeviceinfo.h" +#include "widgets/gimpdeviceinfo-coords.h" +#include "widgets/gimpdevicemanager.h" +#include "widgets/gimpdevices.h" +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpuimanager.h" +#include "widgets/gimpwidgets-utils.h" + +#include "tools/gimpguidetool.h" +#include "tools/gimpmovetool.h" +#include "tools/gimpsamplepointtool.h" +#include "tools/gimptoolcontrol.h" +#include "tools/tool_manager.h" + +#include "gimpcanvas.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-autoscroll.h" +#include "gimpdisplayshell-cursor.h" +#include "gimpdisplayshell-grab.h" +#include "gimpdisplayshell-layer-select.h" +#include "gimpdisplayshell-rotate.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-tool-events.h" +#include "gimpdisplayshell-transform.h" +#include "gimpimagewindow.h" +#include "gimpmotionbuffer.h" +#include "gimpstatusbar.h" + +#include "gimp-intl.h" +#include "gimp-log.h" + + +/* local function prototypes */ + +static gboolean gimp_display_shell_canvas_tool_events_internal (GtkWidget *canvas, + GdkEvent *event, + GimpDisplayShell *shell, + GdkEvent **next_event); + +static GdkModifierType + gimp_display_shell_key_to_state (gint key); +static GdkModifierType + gimp_display_shell_button_to_state (gint button); + +static void gimp_display_shell_proximity_in (GimpDisplayShell *shell); +static void gimp_display_shell_proximity_out (GimpDisplayShell *shell); + +static void gimp_display_shell_check_device_cursor (GimpDisplayShell *shell); + +static void gimp_display_shell_start_scrolling (GimpDisplayShell *shell, + const GdkEvent *event, + GdkModifierType state, + gint x, + gint y); +static void gimp_display_shell_stop_scrolling (GimpDisplayShell *shell, + const GdkEvent *event); +static void gimp_display_shell_handle_scrolling (GimpDisplayShell *shell, + GdkModifierType state, + gint x, + gint y); + +static void gimp_display_shell_space_pressed (GimpDisplayShell *shell, + const GdkEvent *event); +static void gimp_display_shell_released (GimpDisplayShell *shell, + const GdkEvent *event, + const GimpCoords *image_coords); + +static gboolean gimp_display_shell_tab_pressed (GimpDisplayShell *shell, + const GdkEventKey *event); + +static void gimp_display_shell_update_focus (GimpDisplayShell *shell, + gboolean focus_in, + const GimpCoords *image_coords, + GdkModifierType state); +static void gimp_display_shell_update_cursor (GimpDisplayShell *shell, + const GimpCoords *display_coords, + const GimpCoords *image_coords, + GdkModifierType state, + gboolean update_software_cursor); + +static gboolean gimp_display_shell_initialize_tool (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GdkModifierType state); + +static void gimp_display_shell_get_event_coords (GimpDisplayShell *shell, + const GdkEvent *event, + GimpCoords *display_coords, + GdkModifierType *state, + guint32 *time); +static void gimp_display_shell_untransform_event_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords, + gboolean *update_software_cursor); + +static GdkEvent * gimp_display_shell_compress_motion (GdkEvent *initial_event, + GdkEvent **next_event); + + +/* public functions */ + +gboolean +gimp_display_shell_events (GtkWidget *widget, + GdkEvent *event, + GimpDisplayShell *shell) +{ + Gimp *gimp; + gboolean set_display = FALSE; + + /* are we in destruction? */ + if (! shell->display || ! gimp_display_get_shell (shell->display)) + return TRUE; + + gimp = gimp_display_get_gimp (shell->display); + + switch (event->type) + { + case GDK_KEY_PRESS: + case GDK_KEY_RELEASE: + { + GdkEventKey *kevent = (GdkEventKey *) event; + + if (gimp->busy) + return TRUE; + + /* do not process most key events while BUTTON1 is down. We do this + * so tools keep the modifier state they were in when BUTTON1 was + * pressed and to prevent accelerators from being invoked. + */ + if (kevent->state & GDK_BUTTON1_MASK) + { + if (kevent->keyval == GDK_KEY_Shift_L || + kevent->keyval == GDK_KEY_Shift_R || + kevent->keyval == GDK_KEY_Control_L || + kevent->keyval == GDK_KEY_Control_R || + kevent->keyval == GDK_KEY_Alt_L || + kevent->keyval == GDK_KEY_Alt_R || + kevent->keyval == GDK_KEY_Meta_L || + kevent->keyval == GDK_KEY_Meta_R || + kevent->keyval == GDK_KEY_space || + kevent->keyval == GDK_KEY_KP_Space) + { + break; + } + + return TRUE; + } + + switch (kevent->keyval) + { + case GDK_KEY_Left: case GDK_KEY_Right: + case GDK_KEY_Up: case GDK_KEY_Down: + case GDK_KEY_space: + case GDK_KEY_KP_Space: + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: + case GDK_KEY_ISO_Left_Tab: + case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: + case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: case GDK_KEY_Control_R: + case GDK_KEY_Meta_L: case GDK_KEY_Meta_R: + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + case GDK_KEY_BackSpace: + case GDK_KEY_Escape: + break; + + default: + if (shell->space_release_pending || + shell->button1_release_pending || + shell->scrolling) + return TRUE; + break; + } + + set_display = TRUE; + break; + } + + case GDK_BUTTON_PRESS: + case GDK_SCROLL: + set_display = TRUE; + break; + + case GDK_FOCUS_CHANGE: + { + GdkEventFocus *fevent = (GdkEventFocus *) event; + + if (fevent->in && shell->display->config->activate_on_focus) + set_display = TRUE; + } + break; + + default: + break; + } + + /* Setting the context's display automatically sets the image, too */ + if (set_display) + gimp_context_set_display (gimp_get_user_context (gimp), shell->display); + + return FALSE; +} + +static gboolean +gimp_display_shell_canvas_no_image_events (GtkWidget *canvas, + GdkEvent *event, + GimpDisplayShell *shell) +{ + switch (event->type) + { + case GDK_2BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + if (bevent->button == 1) + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_activate_action (manager, "file", "file-open"); + } + return TRUE; + } + break; + + case GDK_BUTTON_PRESS: + if (gdk_event_triggers_context_menu (event)) + { + gimp_ui_manager_ui_popup (shell->popup_manager, + "/dummy-menubar/image-popup", + GTK_WIDGET (shell), + NULL, NULL, NULL, NULL); + return TRUE; + } + break; + + case GDK_KEY_PRESS: + { + GdkEventKey *kevent = (GdkEventKey *) event; + + if (kevent->keyval == GDK_KEY_Tab || + kevent->keyval == GDK_KEY_KP_Tab || + kevent->keyval == GDK_KEY_ISO_Left_Tab) + { + return gimp_display_shell_tab_pressed (shell, kevent); + } + } + break; + + default: + break; + } + + return FALSE; +} + +gboolean +gimp_display_shell_canvas_tool_events (GtkWidget *canvas, + GdkEvent *event, + GimpDisplayShell *shell) +{ + GdkEvent *next_event = NULL; + gboolean return_val; + + g_return_val_if_fail (gtk_widget_get_realized (canvas), FALSE); + + return_val = gimp_display_shell_canvas_tool_events_internal (canvas, + event, shell, + &next_event); + + if (next_event) + { + gtk_main_do_event (next_event); + + gdk_event_free (next_event); + } + + return return_val; +} + +void +gimp_display_shell_canvas_grab_notify (GtkWidget *canvas, + gboolean was_grabbed, + GimpDisplayShell *shell) +{ + GimpDisplay *display; + GimpImage *image; + Gimp *gimp; + + /* are we in destruction? */ + if (! shell->display || ! gimp_display_get_shell (shell->display)) + return; + + display = shell->display; + gimp = gimp_display_get_gimp (display); + image = gimp_display_get_image (display); + + if (! image) + return; + + GIMP_LOG (TOOL_EVENTS, "grab_notify (display %p): was_grabbed = %s", + display, was_grabbed ? "TRUE" : "FALSE"); + + if (! was_grabbed) + { + if (! gimp_image_is_empty (image)) + { + GimpTool *active_tool = tool_manager_get_active (gimp); + + if (active_tool && active_tool->focus_display == display) + { + tool_manager_modifier_state_active (gimp, 0, display); + } + } + } +} + +void +gimp_display_shell_buffer_stroke (GimpMotionBuffer *buffer, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpDisplayShell *shell) +{ + GimpDisplay *display = shell->display; + Gimp *gimp = gimp_display_get_gimp (display); + GimpTool *active_tool; + + active_tool = tool_manager_get_active (gimp); + + if (active_tool && + gimp_tool_control_is_active (active_tool->control)) + { + tool_manager_motion_active (gimp, + coords, time, state, + display); + } +} + +void +gimp_display_shell_buffer_hover (GimpMotionBuffer *buffer, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity, + GimpDisplayShell *shell) +{ + GimpDisplay *display = shell->display; + Gimp *gimp = gimp_display_get_gimp (display); + GimpTool *active_tool; + + active_tool = tool_manager_get_active (gimp); + + if (active_tool && + ! gimp_tool_control_is_active (active_tool->control)) + { + tool_manager_oper_update_active (gimp, + coords, state, proximity, + display); + } +} + +static gboolean +gimp_display_shell_ruler_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpDisplayShell *shell, + GimpOrientationType orientation) +{ + GimpDisplay *display = shell->display; + + if (display->gimp->busy) + return TRUE; + + if (! gimp_display_get_image (display)) + return TRUE; + + if (event->type == GDK_BUTTON_PRESS && event->button == 1) + { + GimpTool *active_tool = tool_manager_get_active (display->gimp); + + if (active_tool) + { + gimp_display_shell_update_focus (shell, TRUE, + NULL, event->state); + + if (gimp_display_shell_pointer_grab (shell, NULL, 0)) + { + if (gimp_display_shell_keyboard_grab (shell, + (GdkEvent *) event)) + { + if (event->state & gimp_get_toggle_behavior_mask ()) + { + gimp_sample_point_tool_start_new (active_tool, display); + } + else + { + gimp_guide_tool_start_new (active_tool, display, + orientation); + } + + return TRUE; + } + else + { + gimp_display_shell_pointer_ungrab (shell, NULL); + } + } + } + } + + return FALSE; +} + +gboolean +gimp_display_shell_hruler_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpDisplayShell *shell) +{ + return gimp_display_shell_ruler_button_press (widget, event, shell, + GIMP_ORIENTATION_HORIZONTAL); +} + +gboolean +gimp_display_shell_vruler_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpDisplayShell *shell) +{ + return gimp_display_shell_ruler_button_press (widget, event, shell, + GIMP_ORIENTATION_VERTICAL); +} + + +/* private functions */ + +static gboolean +gimp_display_shell_canvas_tool_events_internal (GtkWidget *canvas, + GdkEvent *event, + GimpDisplayShell *shell, + GdkEvent **next_event) +{ + GimpDisplay *display; + GimpImage *image; + Gimp *gimp; + GimpCoords display_coords; + GimpCoords image_coords; + GdkModifierType state; + guint32 time; + gboolean device_changed = FALSE; + gboolean return_val = FALSE; + gboolean update_sw_cursor = FALSE; + + *next_event = NULL; + + /* are we in destruction? */ + if (! shell->display || ! gimp_display_get_shell (shell->display)) + return TRUE; + + /* set the active display before doing any other canvas event processing */ + if (gimp_display_shell_events (canvas, event, shell)) + return TRUE; + + /* events on overlays have a different window, but these windows' + * user_data can still be the canvas, we need to check manually if + * the event's window and the canvas' window are different. + */ + if (event->any.window != gtk_widget_get_window (canvas)) + { + GtkWidget *event_widget; + + gdk_window_get_user_data (event->any.window, (gpointer) &event_widget); + + /* if the event came from a different window than the canvas', + * check if it came from a canvas child and bail out. + */ + if (gtk_widget_get_ancestor (event_widget, GIMP_TYPE_CANVAS)) + return FALSE; + } + + display = shell->display; + gimp = gimp_display_get_gimp (display); + image = gimp_display_get_image (display); + + if (! image) + return gimp_display_shell_canvas_no_image_events (canvas, event, shell); + + GIMP_LOG (TOOL_EVENTS, "event (display %p): %s", + display, gimp_print_event (event)); + + /* See bug 771444 */ + if (shell->pointer_grabbed && + event->type == GDK_MOTION_NOTIFY) + { + GimpDeviceManager *manager = gimp_devices_get_manager (gimp); + GimpDeviceInfo *info; + + info = gimp_device_manager_get_current_device (manager); + + if (info->device != event->motion.device) + return FALSE; + } + + /* Find out what device the event occurred upon */ + if (! gimp->busy && + ! shell->inferior_ignore_mode && + gimp_devices_check_change (gimp, event)) + { + gimp_display_shell_check_device_cursor (shell); + device_changed = TRUE; + } + + gimp_display_shell_get_event_coords (shell, event, + &display_coords, + &state, &time); + gimp_display_shell_untransform_event_coords (shell, + &display_coords, &image_coords, + &update_sw_cursor); + + /* If the device (and maybe the tool) has changed, update the new + * tool's state + */ + if (device_changed && gtk_widget_has_focus (canvas)) + { + gimp_display_shell_update_focus (shell, TRUE, + &image_coords, state); + } + + switch (event->type) + { + case GDK_ENTER_NOTIFY: + { + GdkEventCrossing *cevent = (GdkEventCrossing *) event; + + if (shell->inferior_ignore_mode && + cevent->subwindow == NULL && + cevent->mode == GDK_CROSSING_NORMAL) + { + shell->inferior_ignore_mode = FALSE; + gtk_widget_set_extension_events (shell->canvas, + GDK_EXTENSION_EVENTS_ALL); + } + + if (cevent->mode != GDK_CROSSING_NORMAL) + return TRUE; + + /* ignore enter notify while we have a grab */ + if (shell->pointer_grabbed) + return TRUE; + + gimp_display_shell_proximity_in (shell); + update_sw_cursor = TRUE; + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + } + break; + + case GDK_LEAVE_NOTIFY: + { + GdkEventCrossing *cevent = (GdkEventCrossing *) event; + + if (! shell->inferior_ignore_mode && + cevent->subwindow == NULL && + cevent->mode == GDK_CROSSING_NORMAL && + cevent->detail == GDK_NOTIFY_INFERIOR) + { + shell->inferior_ignore_mode = TRUE; + gtk_widget_set_extension_events (shell->canvas, + GDK_EXTENSION_EVENTS_NONE); + } + + if (cevent->mode != GDK_CROSSING_NORMAL) + return TRUE; + + /* ignore leave notify while we have a grab */ + if (shell->pointer_grabbed) + return TRUE; + + gimp_display_shell_proximity_out (shell); + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + } + break; + + case GDK_PROXIMITY_IN: + gimp_display_shell_proximity_in (shell); + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + break; + + case GDK_PROXIMITY_OUT: + gimp_display_shell_proximity_out (shell); + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + break; + + case GDK_FOCUS_CHANGE: + { + GdkEventFocus *fevent = (GdkEventFocus *) event; + + if (fevent->in) + { + if (G_UNLIKELY (! gtk_widget_has_focus (canvas))) + g_warning ("%s: FOCUS_IN but canvas has no focus", G_STRFUNC); + + /* ignore focus changes while we have a grab */ + if (shell->pointer_grabbed) + return TRUE; + + /* press modifier keys when the canvas gets the focus */ + gimp_display_shell_update_focus (shell, TRUE, + &image_coords, state); + } + else + { + if (G_UNLIKELY (gtk_widget_has_focus (canvas))) + g_warning ("%s: FOCUS_OUT but canvas has focus", G_STRFUNC); + + /* ignore focus changes while we have a grab */ + if (shell->pointer_grabbed) + return TRUE; + + /* release modifier keys when the canvas loses the focus */ + gimp_display_shell_update_focus (shell, FALSE, + &image_coords, 0); + } + } + break; + + case GDK_BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + GdkModifierType button_state; + + /* ignore new mouse events */ + if (gimp->busy || shell->scrolling || + shell->pointer_grabbed || + shell->button1_release_pending) + return TRUE; + + button_state = gimp_display_shell_button_to_state (bevent->button); + + state |= button_state; + + /* ignore new buttons while another button is down */ + if (((state & (GDK_BUTTON1_MASK)) && (state & (GDK_BUTTON2_MASK | + GDK_BUTTON3_MASK))) || + ((state & (GDK_BUTTON2_MASK)) && (state & (GDK_BUTTON1_MASK | + GDK_BUTTON3_MASK))) || + ((state & (GDK_BUTTON3_MASK)) && (state & (GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK)))) + return TRUE; + + /* focus the widget if it isn't; if the toplevel window + * already has focus, this will generate a FOCUS_IN on the + * canvas immediately, therefore we do this before logging + * the BUTTON_PRESS. + */ + if (! gtk_widget_has_focus (canvas)) + gtk_widget_grab_focus (canvas); + + /* if the toplevel window didn't have focus, the above + * gtk_widget_grab_focus() didn't set the canvas' HAS_FOCUS + * flags, and didn't trigger a FOCUS_IN, but the tool needs + * to be set up correctly regardless, so simply do the + * same things here, it's safe to do them redundantly. + */ + gimp_display_shell_update_focus (shell, TRUE, + &image_coords, state); + gimp_display_shell_update_cursor (shell, &display_coords, + &image_coords, state & ~button_state, + FALSE); + + if (gdk_event_triggers_context_menu (event)) + { + GimpUIManager *ui_manager; + const gchar *ui_path; + + ui_manager = tool_manager_get_popup_active (gimp, + &image_coords, state, + display, + &ui_path); + + if (ui_manager) + { + gimp_ui_manager_ui_popup (ui_manager, + ui_path, + GTK_WIDGET (shell), + NULL, NULL, NULL, NULL); + } + else + { + gimp_ui_manager_ui_popup (shell->popup_manager, + "/dummy-menubar/image-popup", + GTK_WIDGET (shell), + NULL, NULL, NULL, NULL); + } + } + else if (bevent->button == 1) + { + if (! gimp_display_shell_pointer_grab (shell, NULL, 0)) + return TRUE; + + if (! shell->space_release_pending) + if (! gimp_display_shell_keyboard_grab (shell, event)) + { + gimp_display_shell_pointer_ungrab (shell, NULL); + return TRUE; + } + + if (gimp_display_shell_initialize_tool (shell, + &image_coords, state)) + { + GimpCoords last_motion; + + /* Use the last evaluated velocity&direction instead of the + * button_press event's ones because the click is + * usually at the same spot as the last motion event + * which would give us bogus derivate dynamics. + */ + gimp_motion_buffer_begin_stroke (shell->motion_buffer, time, + &last_motion); + + image_coords.velocity = last_motion.velocity; + image_coords.direction = last_motion.direction; + + tool_manager_button_press_active (gimp, + &image_coords, + time, state, + GIMP_BUTTON_PRESS_NORMAL, + display); + } + } + else if (bevent->button == 2) + { + gimp_display_shell_start_scrolling (shell, NULL, state, + bevent->x, bevent->y); + } + + return_val = TRUE; + } + break; + + case GDK_2BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + GimpTool *active_tool; + + if (gimp->busy) + return TRUE; + + active_tool = tool_manager_get_active (gimp); + + if (bevent->button == 1 && + active_tool && + gimp_tool_control_is_active (active_tool->control) && + gimp_tool_control_get_wants_double_click (active_tool->control)) + { + tool_manager_button_press_active (gimp, + &image_coords, + time, state, + GIMP_BUTTON_PRESS_DOUBLE, + display); + } + + /* don't update the cursor again on double click */ + return TRUE; + } + break; + + case GDK_3BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + GimpTool *active_tool; + + if (gimp->busy) + return TRUE; + + active_tool = tool_manager_get_active (gimp); + + if (bevent->button == 1 && + active_tool && + gimp_tool_control_is_active (active_tool->control) && + gimp_tool_control_get_wants_triple_click (active_tool->control)) + { + tool_manager_button_press_active (gimp, + &image_coords, + time, state, + GIMP_BUTTON_PRESS_TRIPLE, + display); + } + + /* don't update the cursor again on triple click */ + return TRUE; + } + break; + + case GDK_BUTTON_RELEASE: + { + GdkEventButton *bevent = (GdkEventButton *) event; + GimpTool *active_tool; + + gimp_display_shell_autoscroll_stop (shell); + + if (bevent->button == 1 && shell->button1_release_pending) + { + gimp_display_shell_released (shell, event, NULL); + return TRUE; + } + + if (gimp->busy) + return TRUE; + + active_tool = tool_manager_get_active (gimp); + + state &= ~gimp_display_shell_button_to_state (bevent->button); + + if (bevent->button == 1) + { + if (! shell->pointer_grabbed || shell->scrolling) + return TRUE; + + if (! shell->space_release_pending) + gimp_display_shell_keyboard_ungrab (shell, event); + + if (active_tool && + (! gimp_image_is_empty (image) || + gimp_tool_control_get_handle_empty_image (active_tool->control))) + { + gimp_motion_buffer_end_stroke (shell->motion_buffer); + + if (gimp_tool_control_is_active (active_tool->control)) + { + tool_manager_button_release_active (gimp, + &image_coords, + time, state, + display); + } + } + + /* update the tool's modifier state because it didn't get + * key events while BUTTON1 was down + */ + if (gtk_widget_has_focus (canvas)) + gimp_display_shell_update_focus (shell, TRUE, + &image_coords, state); + else + gimp_display_shell_update_focus (shell, FALSE, + &image_coords, 0); + + gimp_display_shell_pointer_ungrab (shell, NULL); + } + else if (bevent->button == 2) + { + if (shell->scrolling) + gimp_display_shell_stop_scrolling (shell, NULL); + } + else if (bevent->button == 3) + { + /* nop */ + } + else + { + GdkEventButton *bevent = (GdkEventButton *) event; + GimpController *mouse = gimp_controllers_get_mouse (gimp); + + if (!(shell->scrolling || shell->pointer_grabbed) && + mouse && gimp_controller_mouse_button (GIMP_CONTROLLER_MOUSE (mouse), + bevent)) + { + return TRUE; + } + } + + return_val = TRUE; + } + break; + + case GDK_SCROLL: + { + GdkEventScroll *sevent = (GdkEventScroll *) event; + GimpController *wheel = gimp_controllers_get_wheel (gimp); + + if (! wheel || + ! gimp_controller_wheel_scroll (GIMP_CONTROLLER_WHEEL (wheel), + sevent)) + { + GdkScrollDirection direction = sevent->direction; + + if (state & gimp_get_toggle_behavior_mask ()) + { + switch (direction) + { + case GDK_SCROLL_UP: + gimp_display_shell_scale (shell, + GIMP_ZOOM_IN, + 0.0, + GIMP_ZOOM_FOCUS_POINTER); + break; + + case GDK_SCROLL_DOWN: + gimp_display_shell_scale (shell, + GIMP_ZOOM_OUT, + 0.0, + GIMP_ZOOM_FOCUS_POINTER); + break; + + default: + break; + } + } + else + { + GtkAdjustment *adj = NULL; + gdouble value; + + if (state & GDK_SHIFT_MASK) + switch (direction) + { + case GDK_SCROLL_UP: direction = GDK_SCROLL_LEFT; break; + case GDK_SCROLL_DOWN: direction = GDK_SCROLL_RIGHT; break; + case GDK_SCROLL_LEFT: direction = GDK_SCROLL_UP; break; + case GDK_SCROLL_RIGHT: direction = GDK_SCROLL_DOWN; break; + } + + switch (direction) + { + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + adj = shell->hsbdata; + break; + + case GDK_SCROLL_UP: + case GDK_SCROLL_DOWN: + adj = shell->vsbdata; + break; + } + + value = (gtk_adjustment_get_value (adj) + + ((direction == GDK_SCROLL_UP || + direction == GDK_SCROLL_LEFT) ? + -gtk_adjustment_get_page_increment (adj) / 2 : + gtk_adjustment_get_page_increment (adj) / 2)); + value = CLAMP (value, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_page_size (adj)); + + gtk_adjustment_set_value (adj, value); + } + } + + gimp_display_shell_untransform_event_coords (shell, + &display_coords, + &image_coords, + &update_sw_cursor); + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + + return_val = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + GdkEvent *compressed_motion = NULL; + GimpMotionMode motion_mode = GIMP_MOTION_MODE_EXACT; + GimpTool *active_tool; + + if (gimp->busy) + return TRUE; + + active_tool = tool_manager_get_active (gimp); + + if (active_tool) + motion_mode = gimp_tool_control_get_motion_mode (active_tool->control); + + if (shell->scrolling || + motion_mode == GIMP_MOTION_MODE_COMPRESS) + { + compressed_motion = gimp_display_shell_compress_motion (event, + next_event); + + if (compressed_motion && ! shell->scrolling) + { + gimp_display_shell_get_event_coords (shell, + compressed_motion, + &display_coords, + &state, &time); + gimp_display_shell_untransform_event_coords (shell, + &display_coords, + &image_coords, + NULL); + } + } + + /* call proximity_in() here because the pointer might already + * be in proximity when the canvas starts to receive events, + * like when a new image has been created into an empty + * display + */ + gimp_display_shell_proximity_in (shell); + update_sw_cursor = TRUE; + + if (shell->scrolling) + { + GdkEventMotion *me = (compressed_motion ? + (GdkEventMotion *) compressed_motion : + mevent); + + gimp_display_shell_handle_scrolling (shell, state, me->x, me->y); + } + else if (state & GDK_BUTTON1_MASK) + { + if (active_tool && + gimp_tool_control_is_active (active_tool->control) && + (! gimp_image_is_empty (image) || + gimp_tool_control_get_handle_empty_image (active_tool->control))) + { + GdkTimeCoord **history_events; + gint n_history_events; + guint32 last_motion_time; + + /* if the first mouse button is down, check for automatic + * scrolling... + */ + if ((mevent->x < 0 || + mevent->y < 0 || + mevent->x > shell->disp_width || + mevent->y > shell->disp_height) && + ! gimp_tool_control_get_scroll_lock (active_tool->control)) + { + gimp_display_shell_autoscroll_start (shell, state, mevent); + } + + /* gdk_device_get_history() has several quirks. First + * is that events with borderline timestamps at both + * ends are included. Because of that we need to add 1 + * to lower border. The second is due to poor X event + * resolution. We need to do -1 to ensure that the + * amount of events between timestamps is final or + * risk losing some. + */ + last_motion_time = + gimp_motion_buffer_get_last_motion_time (shell->motion_buffer); + + if (motion_mode == GIMP_MOTION_MODE_EXACT && + shell->display->config->use_event_history && + gdk_device_get_history (mevent->device, mevent->window, + last_motion_time + 1, + mevent->time - 1, + &history_events, + &n_history_events)) + { + GimpDeviceInfo *device; + gint i; + + device = gimp_device_info_get_by_device (mevent->device); + + for (i = 0; i < n_history_events; i++) + { + gimp_device_info_get_time_coords (device, + history_events[i], + &display_coords); + + gimp_display_shell_untransform_event_coords (shell, + &display_coords, + &image_coords, + NULL); + + /* Early removal of useless events saves CPU time. + */ + if (gimp_motion_buffer_motion_event (shell->motion_buffer, + &image_coords, + history_events[i]->time, + TRUE)) + { + gimp_motion_buffer_request_stroke (shell->motion_buffer, + state, + history_events[i]->time); + } + } + + gdk_device_free_history (history_events, n_history_events); + } + else + { + gboolean event_fill = (motion_mode == GIMP_MOTION_MODE_EXACT); + + /* Early removal of useless events saves CPU time. + */ + if (gimp_motion_buffer_motion_event (shell->motion_buffer, + &image_coords, + time, + event_fill)) + { + gimp_motion_buffer_request_stroke (shell->motion_buffer, + state, + time); + } + } + } + } + + if (! (state & + (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) + { + /* Early removal of useless events saves CPU time. + * Pass event_fill = FALSE since we are only hovering. + */ + if (gimp_motion_buffer_motion_event (shell->motion_buffer, + &image_coords, + time, + FALSE)) + { + gimp_motion_buffer_request_hover (shell->motion_buffer, + state, + shell->proximity); + } + } + + if (compressed_motion) + gdk_event_free (compressed_motion); + + return_val = TRUE; + } + break; + + case GDK_KEY_PRESS: + { + GdkEventKey *kevent = (GdkEventKey *) event; + GimpTool *active_tool; + + active_tool = tool_manager_get_active (gimp); + + if (state & GDK_BUTTON1_MASK) + { + if (kevent->keyval == GDK_KEY_Alt_L || + kevent->keyval == GDK_KEY_Alt_R || + kevent->keyval == GDK_KEY_Shift_L || + kevent->keyval == GDK_KEY_Shift_R || + kevent->keyval == GDK_KEY_Control_L || + kevent->keyval == GDK_KEY_Control_R || + kevent->keyval == GDK_KEY_Meta_L || + kevent->keyval == GDK_KEY_Meta_R) + { + GdkModifierType key; + + key = gimp_display_shell_key_to_state (kevent->keyval); + state |= key; + + if (active_tool && + gimp_tool_control_is_active (active_tool->control) && + ! gimp_image_is_empty (image)) + { + tool_manager_active_modifier_state_active (gimp, state, + display); + } + } + } + else + { + gboolean arrow_key = FALSE; + + tool_manager_focus_display_active (gimp, display); + + if (gimp_tool_control_get_wants_all_key_events (active_tool->control)) + { + if (tool_manager_key_press_active (gimp, kevent, display)) + { + /* FIXME: need to do some of the stuff below, like + * calling oper_update() + */ + + return TRUE; + } + } + + if (! gtk_widget_has_focus (shell->canvas)) + { + /* The event was in an overlay widget and not handled + * there, make sure the overlay widgets are keyboard + * navigatable by letting the generic widget handlers + * deal with the event. + */ + return FALSE; + } + + if (gimp_display_shell_key_to_state (kevent->keyval) == GDK_MOD1_MASK) + /* Make sure the picked layer is reset. */ + shell->picked_layer = NULL; + + switch (kevent->keyval) + { + case GDK_KEY_Left: + case GDK_KEY_Right: + case GDK_KEY_Up: + case GDK_KEY_Down: + arrow_key = TRUE; + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + case GDK_KEY_BackSpace: + case GDK_KEY_Escape: + if (! gimp_image_is_empty (image)) + return_val = tool_manager_key_press_active (gimp, + kevent, + display); + + if (! return_val) + { + GimpController *keyboard = gimp_controllers_get_keyboard (gimp); + + if (keyboard) + return_val = + gimp_controller_keyboard_key_press (GIMP_CONTROLLER_KEYBOARD (keyboard), + kevent); + } + + /* always swallow arrow keys, we don't want focus keynav */ + if (! return_val) + return_val = arrow_key; + break; + + case GDK_KEY_space: + case GDK_KEY_KP_Space: + if (shell->button1_release_pending) + shell->space_release_pending = TRUE; + else + gimp_display_shell_space_pressed (shell, event); + return_val = TRUE; + break; + + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: + case GDK_KEY_ISO_Left_Tab: + gimp_display_shell_tab_pressed (shell, kevent); + return_val = TRUE; + break; + + /* Update the state based on modifiers being pressed */ + case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: + case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: case GDK_KEY_Control_R: + case GDK_KEY_Meta_L: case GDK_KEY_Meta_R: + { + GdkModifierType key; + + key = gimp_display_shell_key_to_state (kevent->keyval); + state |= key; + + if (! gimp_image_is_empty (image)) + tool_manager_modifier_state_active (gimp, state, display); + } + break; + } + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + } + } + break; + + case GDK_KEY_RELEASE: + { + GdkEventKey *kevent = (GdkEventKey *) event; + GimpTool *active_tool; + + active_tool = tool_manager_get_active (gimp); + + if (gimp_display_shell_key_to_state (kevent->keyval) == GDK_MOD1_MASK && + shell->picked_layer) + { + GimpStatusbar *statusbar; + + statusbar = gimp_display_shell_get_statusbar (shell); + gimp_statusbar_pop_temp (statusbar); + + shell->picked_layer = NULL; + } + + if ((state & GDK_BUTTON1_MASK) && + (! shell->space_release_pending || + (kevent->keyval != GDK_KEY_space && + kevent->keyval != GDK_KEY_KP_Space))) + { + if (kevent->keyval == GDK_KEY_Alt_L || + kevent->keyval == GDK_KEY_Alt_R || + kevent->keyval == GDK_KEY_Shift_L || + kevent->keyval == GDK_KEY_Shift_R || + kevent->keyval == GDK_KEY_Control_L || + kevent->keyval == GDK_KEY_Control_R || + kevent->keyval == GDK_KEY_Meta_L || + kevent->keyval == GDK_KEY_Meta_R) + { + GdkModifierType key; + + key = gimp_display_shell_key_to_state (kevent->keyval); + state &= ~key; + + if (active_tool && + gimp_tool_control_is_active (active_tool->control) && + ! gimp_image_is_empty (image)) + { + tool_manager_active_modifier_state_active (gimp, state, + display); + } + } + } + else + { + tool_manager_focus_display_active (gimp, display); + + if (gimp_tool_control_get_wants_all_key_events (active_tool->control)) + { + if (tool_manager_key_release_active (gimp, kevent, display)) + { + /* FIXME: need to do some of the stuff below, like + * calling oper_update() + */ + + return TRUE; + } + } + + if (! gtk_widget_has_focus (shell->canvas)) + { + /* The event was in an overlay widget and not handled + * there, make sure the overlay widgets are keyboard + * navigatable by letting the generic widget handlers + * deal with the event. + */ + return FALSE; + } + + switch (kevent->keyval) + { + case GDK_KEY_space: + case GDK_KEY_KP_Space: + if ((state & GDK_BUTTON1_MASK)) + { + shell->button1_release_pending = TRUE; + shell->space_release_pending = FALSE; + /* We need to ungrab the pointer in order to catch + * button release events. + */ + if (shell->pointer_grabbed) + gimp_display_shell_pointer_ungrab (shell, event); + } + else + { + gimp_display_shell_released (shell, event, NULL); + } + return_val = TRUE; + break; + + /* Update the state based on modifiers being pressed */ + case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: + case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: case GDK_KEY_Control_R: + case GDK_KEY_Meta_L: case GDK_KEY_Meta_R: + { + GdkModifierType key; + + key = gimp_display_shell_key_to_state (kevent->keyval); + state &= ~key; + + /* For all modifier keys: call the tools + * modifier_state *and* oper_update method so tools + * can choose if they are interested in the press + * itself or only in the resulting state + */ + if (! gimp_image_is_empty (image)) + tool_manager_modifier_state_active (gimp, state, display); + } + break; + } + + tool_manager_oper_update_active (gimp, + &image_coords, state, + shell->proximity, + display); + } + } + break; + + default: + break; + } + + /* if we reached this point in gimp_busy mode, return now */ + if (gimp->busy) + return return_val; + + /* cursor update */ + gimp_display_shell_update_cursor (shell, &display_coords, &image_coords, + state, update_sw_cursor); + + return return_val; +} + +static GdkModifierType +gimp_display_shell_key_to_state (gint key) +{ + /* FIXME: need some proper GDK API to figure this */ + + switch (key) + { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + return GDK_MOD1_MASK; + + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + return GDK_SHIFT_MASK; + + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + return GDK_CONTROL_MASK; + +#ifdef GDK_WINDOWING_QUARTZ + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + return GDK_MOD2_MASK; +#endif + + default: + return 0; + } +} + +static GdkModifierType +gimp_display_shell_button_to_state (gint button) +{ + if (button == 1) + return GDK_BUTTON1_MASK; + else if (button == 2) + return GDK_BUTTON2_MASK; + else if (button == 3) + return GDK_BUTTON3_MASK; + + return 0; +} + +static void +gimp_display_shell_proximity_in (GimpDisplayShell *shell) +{ + if (! shell->proximity) + { + shell->proximity = TRUE; + + gimp_display_shell_check_device_cursor (shell); + } +} + +static void +gimp_display_shell_proximity_out (GimpDisplayShell *shell) +{ + if (shell->proximity) + { + shell->proximity = FALSE; + + gimp_display_shell_clear_software_cursor (shell); + } +} + +static void +gimp_display_shell_check_device_cursor (GimpDisplayShell *shell) +{ + GimpDeviceManager *manager; + GimpDeviceInfo *current_device; + + manager = gimp_devices_get_manager (shell->display->gimp); + + current_device = gimp_device_manager_get_current_device (manager); + + shell->draw_cursor = ! gimp_device_info_has_cursor (current_device); +} + +static void +gimp_display_shell_start_scrolling (GimpDisplayShell *shell, + const GdkEvent *event, + GdkModifierType state, + gint x, + gint y) +{ + g_return_if_fail (! shell->scrolling); + + gimp_display_shell_pointer_grab (shell, event, GDK_POINTER_MOTION_MASK); + + shell->scrolling = TRUE; + shell->scroll_start_x = x; + shell->scroll_start_y = y; + shell->scroll_last_x = x; + shell->scroll_last_y = y; + shell->rotating = (state & gimp_get_extend_selection_mask ()) ? TRUE : FALSE; + shell->rotate_drag_angle = shell->rotate_angle; + shell->scaling = (state & gimp_get_toggle_behavior_mask ()) ? TRUE : FALSE; + shell->layer_picking = (state & GDK_MOD1_MASK) ? TRUE : FALSE; + + if (shell->rotating) + { + gimp_display_shell_set_override_cursor (shell, + (GimpCursorType) GDK_EXCHANGE); + } + else if (shell->scaling) + { + gimp_display_shell_set_override_cursor (shell, + (GimpCursorType) GIMP_CURSOR_ZOOM); + } + else if (shell->layer_picking) + { + GimpImage *image = gimp_display_get_image (shell->display); + GimpLayer *layer; + GimpCoords image_coords; + GimpCoords display_coords; + guint32 time; + + gimp_display_shell_set_override_cursor (shell, + (GimpCursorType) GIMP_CURSOR_CROSSHAIR); + + gimp_display_shell_get_event_coords (shell, event, + &display_coords, + &state, &time); + gimp_display_shell_untransform_event_coords (shell, + &display_coords, &image_coords, + NULL); + layer = gimp_image_pick_layer (image, + (gint) image_coords.x, + (gint) image_coords.y, + shell->picked_layer); + + if (layer && ! gimp_image_get_floating_selection (image)) + { + if (layer != gimp_image_get_active_layer (image)) + { + GimpStatusbar *statusbar; + + gimp_image_set_active_layer (image, layer); + + statusbar = gimp_display_shell_get_statusbar (shell); + gimp_statusbar_push_temp (statusbar, GIMP_MESSAGE_INFO, + GIMP_ICON_LAYER, + _("Layer picked: '%s'"), + gimp_object_get_name (layer)); + } + shell->picked_layer = layer; + } + } + else + gimp_display_shell_set_override_cursor (shell, + (GimpCursorType) GDK_FLEUR); +} + +static void +gimp_display_shell_stop_scrolling (GimpDisplayShell *shell, + const GdkEvent *event) +{ + g_return_if_fail (shell->scrolling); + + gimp_display_shell_unset_override_cursor (shell); + + shell->scrolling = FALSE; + shell->scroll_start_x = 0; + shell->scroll_start_y = 0; + shell->scroll_last_x = 0; + shell->scroll_last_y = 0; + shell->rotating = FALSE; + shell->rotate_drag_angle = 0.0; + shell->scaling = FALSE; + shell->layer_picking = FALSE; + + /* We may have ungrabbed the pointer when space was released while + * mouse was down, to be able to catch a GDK_BUTTON_RELEASE event. + */ + if (shell->pointer_grabbed) + gimp_display_shell_pointer_ungrab (shell, event); +} + +static void +gimp_display_shell_handle_scrolling (GimpDisplayShell *shell, + GdkModifierType state, + gint x, + gint y) +{ + g_return_if_fail (shell->scrolling); + + if (shell->rotating) + { + gboolean constrain = (state & GDK_CONTROL_MASK) ? TRUE : FALSE; + + gimp_display_shell_rotate_drag (shell, + shell->scroll_last_x, + shell->scroll_last_y, + x, + y, + constrain); + } + else if (shell->scaling) + { + gimp_display_shell_scale_drag (shell, + shell->scroll_start_x, + shell->scroll_start_y, + shell->scroll_last_x - x, + shell->scroll_last_y - y); + } + else if (shell->layer_picking) + { + /* Do nothing. We only pick the layer on click. */ + } + else + { + gimp_display_shell_scroll (shell, + shell->scroll_last_x - x, + shell->scroll_last_y - y); + } + + shell->scroll_last_x = x; + shell->scroll_last_y = y; +} + +static void +gimp_display_shell_space_pressed (GimpDisplayShell *shell, + const GdkEvent *event) +{ + Gimp *gimp = gimp_display_get_gimp (shell->display); + + if (shell->space_release_pending || shell->scrolling) + return; + + if (! gimp_display_shell_keyboard_grab (shell, event)) + return; + + switch (shell->display->config->space_bar_action) + { + case GIMP_SPACE_BAR_ACTION_NONE: + break; + + case GIMP_SPACE_BAR_ACTION_PAN: + { + GimpDeviceManager *manager; + GimpDeviceInfo *current_device; + GimpCoords coords; + GdkModifierType state = 0; + + manager = gimp_devices_get_manager (gimp); + current_device = gimp_device_manager_get_current_device (manager); + + gimp_device_info_get_device_coords (current_device, + gtk_widget_get_window (shell->canvas), + &coords); + gdk_event_get_state (event, &state); + + gimp_display_shell_start_scrolling (shell, event, state, + coords.x, coords.y); + } + break; + + case GIMP_SPACE_BAR_ACTION_MOVE: + { + GimpTool *active_tool = tool_manager_get_active (gimp); + + if (active_tool || ! GIMP_IS_MOVE_TOOL (active_tool)) + { + GdkModifierType state; + + shell->space_shaded_tool = + gimp_object_get_name (active_tool->tool_info); + + gimp_context_set_tool (gimp_get_user_context (gimp), + gimp_get_tool_info (gimp, "gimp-move-tool")); + + gdk_event_get_state (event, &state); + + gimp_display_shell_update_focus (shell, TRUE, + NULL, state); + } + } + break; + } + + shell->space_release_pending = TRUE; +} + +static void +gimp_display_shell_released (GimpDisplayShell *shell, + const GdkEvent *event, + const GimpCoords *image_coords) +{ + Gimp *gimp = gimp_display_get_gimp (shell->display); + + if (! shell->space_release_pending && + ! shell->button1_release_pending) + return; + + switch (shell->display->config->space_bar_action) + { + case GIMP_SPACE_BAR_ACTION_NONE: + break; + + case GIMP_SPACE_BAR_ACTION_PAN: + gimp_display_shell_stop_scrolling (shell, event); + break; + + case GIMP_SPACE_BAR_ACTION_MOVE: + if (shell->space_shaded_tool) + { + gimp_context_set_tool (gimp_get_user_context (gimp), + gimp_get_tool_info (gimp, + shell->space_shaded_tool)); + shell->space_shaded_tool = NULL; + + if (gtk_widget_has_focus (shell->canvas)) + { + GdkModifierType state; + + gdk_event_get_state (event, &state); + + gimp_display_shell_update_focus (shell, TRUE, + image_coords, state); + } + else + { + gimp_display_shell_update_focus (shell, FALSE, + image_coords, 0); + } + } + break; + } + + gimp_display_shell_keyboard_ungrab (shell, event); + + shell->space_release_pending = FALSE; + shell->button1_release_pending = FALSE; +} + +static gboolean +gimp_display_shell_tab_pressed (GimpDisplayShell *shell, + const GdkEventKey *kevent) +{ + GimpImageWindow *window = gimp_display_shell_get_window (shell); + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + GimpImage *image = gimp_display_get_image (shell->display); + + if (kevent->state & GDK_CONTROL_MASK) + { + if (image && ! gimp_image_is_empty (image)) + { + if (kevent->keyval == GDK_KEY_Tab || + kevent->keyval == GDK_KEY_KP_Tab) + gimp_display_shell_layer_select_init (shell, + 1, kevent->time); + else + gimp_display_shell_layer_select_init (shell, + -1, kevent->time); + + return TRUE; + } + } + else if (kevent->state & GDK_MOD1_MASK) + { + if (image) + { + if (kevent->keyval == GDK_KEY_Tab || + kevent->keyval == GDK_KEY_KP_Tab) + gimp_ui_manager_activate_action (manager, "windows", + "windows-show-display-next"); + else + gimp_ui_manager_activate_action (manager, "windows", + "windows-show-display-previous"); + + return TRUE; + } + } + else + { + gimp_ui_manager_activate_action (manager, "windows", + "windows-hide-docks"); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_display_shell_update_focus (GimpDisplayShell *shell, + gboolean focus_in, + const GimpCoords *image_coords, + GdkModifierType state) +{ + Gimp *gimp = gimp_display_get_gimp (shell->display); + + if (focus_in) + { + tool_manager_focus_display_active (gimp, shell->display); + tool_manager_modifier_state_active (gimp, state, shell->display); + } + else + { + tool_manager_focus_display_active (gimp, NULL); + } + + if (image_coords) + tool_manager_oper_update_active (gimp, + image_coords, state, + shell->proximity, + shell->display); +} + +static void +gimp_display_shell_update_cursor (GimpDisplayShell *shell, + const GimpCoords *display_coords, + const GimpCoords *image_coords, + GdkModifierType state, + gboolean update_software_cursor) +{ + GimpDisplay *display = shell->display; + Gimp *gimp = gimp_display_get_gimp (display); + GimpImage *image = gimp_display_get_image (display); + GimpTool *active_tool; + + if (! shell->display->config->cursor_updating) + return; + + active_tool = tool_manager_get_active (gimp); + + if (active_tool) + { + if ((! gimp_image_is_empty (image) || + gimp_tool_control_get_handle_empty_image (active_tool->control)) && + ! (state & (GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK | + GDK_BUTTON3_MASK))) + { + tool_manager_cursor_update_active (gimp, + image_coords, state, + display); + } + else if (gimp_image_is_empty (image) && + ! gimp_tool_control_get_handle_empty_image (active_tool->control)) + { + gimp_display_shell_set_cursor (shell, + GIMP_CURSOR_MOUSE, + gimp_tool_control_get_tool_cursor (active_tool->control), + GIMP_CURSOR_MODIFIER_BAD); + } + } + else + { + gimp_display_shell_set_cursor (shell, + GIMP_CURSOR_MOUSE, + GIMP_TOOL_CURSOR_NONE, + GIMP_CURSOR_MODIFIER_BAD); + } + + if (update_software_cursor) + { + GimpCursorPrecision precision = GIMP_CURSOR_PRECISION_PIXEL_CENTER; + + if (active_tool) + precision = gimp_tool_control_get_precision (active_tool->control); + + gimp_display_shell_update_software_cursor (shell, + precision, + (gint) display_coords->x, + (gint) display_coords->y, + image_coords->x, + image_coords->y); + } +} + +static gboolean +gimp_display_shell_initialize_tool (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GdkModifierType state) +{ + GimpDisplay *display = shell->display; + GimpImage *image = gimp_display_get_image (display); + Gimp *gimp = gimp_display_get_gimp (display); + gboolean initialized = FALSE; + GimpTool *active_tool; + + active_tool = tool_manager_get_active (gimp); + + if (active_tool && + (! gimp_image_is_empty (image) || + gimp_tool_control_get_handle_empty_image (active_tool->control))) + { + /* initialize the current tool if it has no drawable */ + if (! active_tool->drawable) + { + initialized = tool_manager_initialize_active (gimp, display); + } + else if ((active_tool->drawable != + gimp_image_get_active_drawable (image)) && + (! gimp_tool_control_get_preserve (active_tool->control) && + (gimp_tool_control_get_dirty_mask (active_tool->control) & + GIMP_DIRTY_ACTIVE_DRAWABLE))) + { + GimpProcedure *procedure = g_object_get_data (G_OBJECT (active_tool), + "gimp-gegl-procedure"); + + if (image == gimp_item_get_image (GIMP_ITEM (active_tool->drawable))) + { + /* When changing between drawables if the *same* image, + * stop the tool using its dirty action, so it doesn't + * get committed on tool change, in case its dirty action + * is HALT. This is a pure "probably better this way" + * decision because the user is likely changing their + * mind or was simply on the wrong layer. See bug #776370. + * + * See also issues #1180 and #1202 for cases where we + * actually *don't* want to halt the tool here, but rather + * commit it, hence the use of the tool's dirty action. + */ + tool_manager_control_active ( + gimp, + gimp_tool_control_get_dirty_action (active_tool->control), + active_tool->display); + } + + if (procedure) + { + /* We can't just recreate an operation tool, we must + * make sure the right stuff gets set on it, so + * re-activate the procedure that created it instead of + * just calling gimp_context_tool_changed(). See + * GimpGeglProcedure and bug #776370. + */ + GimpImageWindow *window; + GimpUIManager *manager; + + window = gimp_display_shell_get_window (shell); + manager = gimp_image_window_get_ui_manager (window); + + gimp_filter_history_add (gimp, procedure); + gimp_ui_manager_activate_action (manager, "filters", + "filters-reshow"); + + /* the procedure already initialized the tool; don't + * reinitialize it below, since this can lead to errors. + */ + initialized = TRUE; + } + else + { + /* create a new one, deleting the current */ + gimp_context_tool_changed (gimp_get_user_context (gimp)); + } + + /* make sure the newly created tool has the right state */ + gimp_display_shell_update_focus (shell, TRUE, image_coords, state); + + if (! initialized) + initialized = tool_manager_initialize_active (gimp, display); + } + else + { + initialized = TRUE; + } + } + + return initialized; +} + +static void +gimp_display_shell_get_event_coords (GimpDisplayShell *shell, + const GdkEvent *event, + GimpCoords *display_coords, + GdkModifierType *state, + guint32 *time) +{ + Gimp *gimp = gimp_display_get_gimp (shell->display); + GimpDeviceManager *manager; + GimpDeviceInfo *current_device; + + manager = gimp_devices_get_manager (gimp); + current_device = gimp_device_manager_get_current_device (manager); + + gimp_device_info_get_event_coords (current_device, + gtk_widget_get_window (shell->canvas), + event, + display_coords); + + gimp_device_info_get_event_state (current_device, + gtk_widget_get_window (shell->canvas), + event, + state); + + *time = gdk_event_get_time (event); +} + +static void +gimp_display_shell_untransform_event_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords, + gboolean *update_software_cursor) +{ + Gimp *gimp = gimp_display_get_gimp (shell->display); + GimpTool *active_tool; + + /* GimpCoords passed to tools are ALWAYS in image coordinates */ + gimp_display_shell_untransform_coords (shell, + display_coords, + image_coords); + + active_tool = tool_manager_get_active (gimp); + + if (active_tool && gimp_tool_control_get_snap_to (active_tool->control)) + { + gint x, y, width, height; + + gimp_tool_control_get_snap_offsets (active_tool->control, + &x, &y, &width, &height); + + if (gimp_display_shell_snap_coords (shell, + image_coords, + x, y, width, height)) + { + if (update_software_cursor) + *update_software_cursor = TRUE; + } + } +} + +/* gimp_display_shell_compress_motion: + * + * This function walks the GDK event queue, seeking motion events at the + * front of the queue corresponding to the same widget as, and having + * similar characteristics to, `initial_event`. If it finds any it will + * remove them from the queue, and return the most recent motion event. + * Otherwise it will return NULL. + * + * If `*next_event` is non-NULL upon return, the caller must dispatch and + * free this event after handling the motion event. + * + * The gimp_display_shell_compress_motion function source may be re-used under + * the XFree86-style license. <adam@gimp.org> + */ +static GdkEvent * +gimp_display_shell_compress_motion (GdkEvent *initial_event, + GdkEvent **next_event) +{ + GdkEvent *last_motion = NULL; + GtkWidget *widget; + + *next_event = NULL; + + if (initial_event->any.type != GDK_MOTION_NOTIFY) + return NULL; + + widget = gtk_get_event_widget (initial_event); + + while (gdk_events_pending ()) + { + GdkEvent *event = gdk_event_get (); + + if (!event) + { + /* Do nothing */ + } + else if ((gtk_get_event_widget (event) == widget) && + (event->any.type == GDK_MOTION_NOTIFY) && + (event->any.window == initial_event->any.window) && + (event->motion.state == initial_event->motion.state) && + (event->motion.device == initial_event->motion.device)) + { + /* Discard previous motion event */ + if (last_motion) + gdk_event_free (last_motion); + + last_motion = event; + } + else + { + /* Let the caller dispatch the event */ + *next_event = event; + + break; + } + } + + return last_motion; +} diff --git a/app/display/gimpdisplayshell-tool-events.h b/app/display/gimpdisplayshell-tool-events.h new file mode 100644 index 0000000..4458a92 --- /dev/null +++ b/app/display/gimpdisplayshell-tool-events.h @@ -0,0 +1,52 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_TOOL_EVENTS_H__ +#define __GIMP_DISPLAY_SHELL_TOOL_EVENTS_H__ + + +gboolean gimp_display_shell_events (GtkWidget *widget, + GdkEvent *event, + GimpDisplayShell *shell); + +gboolean gimp_display_shell_canvas_tool_events (GtkWidget *widget, + GdkEvent *event, + GimpDisplayShell *shell); +void gimp_display_shell_canvas_grab_notify (GtkWidget *widget, + gboolean was_grabbed, + GimpDisplayShell *shell); + +void gimp_display_shell_buffer_stroke (GimpMotionBuffer *buffer, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpDisplayShell *shell); +void gimp_display_shell_buffer_hover (GimpMotionBuffer *buffer, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity, + GimpDisplayShell *shell); + +gboolean gimp_display_shell_hruler_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell); +gboolean gimp_display_shell_vruler_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell); + + +#endif /* __GIMP_DISPLAY_SHELL_TOOL_EVENT_H__ */ diff --git a/app/display/gimpdisplayshell-transform.c b/app/display/gimpdisplayshell-transform.c new file mode 100644 index 0000000..f9cd2e5 --- /dev/null +++ b/app/display/gimpdisplayshell-transform.c @@ -0,0 +1,1025 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpboundary.h" +#include "core/gimpdrawable.h" +#include "core/gimpimage.h" +#include "core/gimp-utils.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-transform.h" + + +/* local function prototypes */ + +static void gimp_display_shell_transform_xy_f_noround (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); + +/* public functions */ + +/** + * gimp_display_shell_zoom_coords: + * @shell: a #GimpDisplayShell + * @image_coords: image coordinates + * @display_coords: returns the corresponding display coordinates + * + * Zooms from image coordinates to display coordinates, so that + * objects can be rendered at the correct points on the display. + **/ +void +gimp_display_shell_zoom_coords (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GimpCoords *display_coords) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (image_coords != NULL); + g_return_if_fail (display_coords != NULL); + + *display_coords = *image_coords; + + display_coords->x = SCALEX (shell, image_coords->x); + display_coords->y = SCALEY (shell, image_coords->y); + + display_coords->x -= shell->offset_x; + display_coords->y -= shell->offset_y; +} + +/** + * gimp_display_shell_unzoom_coords: + * @shell: a #GimpDisplayShell + * @display_coords: display coordinates + * @image_coords: returns the corresponding image coordinates + * + * Zooms from display coordinates to image coordinates, so that + * points on the display can be mapped to points in the image. + **/ +void +gimp_display_shell_unzoom_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (display_coords != NULL); + g_return_if_fail (image_coords != NULL); + + *image_coords = *display_coords; + + image_coords->x += shell->offset_x; + image_coords->y += shell->offset_y; + + image_coords->x /= shell->scale_x; + image_coords->y /= shell->scale_y; +} + +/** + * gimp_display_shell_zoom_xy: + * @shell: + * @x: + * @y: + * @nx: + * @ny: + * + * Zooms an image coordinate to a shell coordinate. + **/ +void +gimp_display_shell_zoom_xy (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gint *nx, + gint *ny) +{ + gint64 tx; + gint64 ty; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + tx = x * shell->scale_x; + ty = y * shell->scale_y; + + tx -= shell->offset_x; + ty -= shell->offset_y; + + /* The projected coordinates might overflow a gint in the case of + * big images at high zoom levels, so we clamp them here to avoid + * problems. + */ + *nx = CLAMP (tx, G_MININT, G_MAXINT); + *ny = CLAMP (ty, G_MININT, G_MAXINT); +} + +/** + * gimp_display_shell_unzoom_xy: + * @shell: a #GimpDisplayShell + * @x: x coordinate in display coordinates + * @y: y coordinate in display coordinates + * @nx: returns x oordinate in image coordinates + * @ny: returns y coordinate in image coordinates + * @round: if %TRUE, round the results to the nearest integer; + * if %FALSE, simply cast them to @gint. + * + * Zoom from display coordinates to image coordinates, so that + * points on the display can be mapped to the corresponding points + * in the image. + **/ +void +gimp_display_shell_unzoom_xy (GimpDisplayShell *shell, + gint x, + gint y, + gint *nx, + gint *ny, + gboolean round) +{ + gint64 tx; + gint64 ty; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + if (round) + { + tx = SIGNED_ROUND (((gdouble) x + shell->offset_x) / shell->scale_x); + ty = SIGNED_ROUND (((gdouble) y + shell->offset_y) / shell->scale_y); + } + else + { + tx = ((gint64) x + shell->offset_x) / shell->scale_x; + ty = ((gint64) y + shell->offset_y) / shell->scale_y; + } + + *nx = CLAMP (tx, G_MININT, G_MAXINT); + *ny = CLAMP (ty, G_MININT, G_MAXINT); +} + +/** + * gimp_display_shell_zoom_xy_f: + * @shell: a #GimpDisplayShell + * @x: image x coordinate of point + * @y: image y coordinate of point + * @nx: returned shell canvas x coordinate + * @ny: returned shell canvas y coordinate + * + * Zooms from image coordinates to display shell canvas + * coordinates. + **/ +void +gimp_display_shell_zoom_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + *nx = SCALEX (shell, x) - shell->offset_x; + *ny = SCALEY (shell, y) - shell->offset_y; +} + +/** + * gimp_display_shell_unzoom_xy_f: + * @shell: a #GimpDisplayShell + * @x: x coordinate in display coordinates + * @y: y coordinate in display coordinates + * @nx: place to return x coordinate in image coordinates + * @ny: place to return y coordinate in image coordinates + * + * This function is identical to gimp_display_shell_unzoom_xy(), + * except that the input and output coordinates are doubles rather than + * ints, and consequently there is no option related to rounding. + **/ +void +gimp_display_shell_unzoom_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + *nx = (x + shell->offset_x) / shell->scale_x; + *ny = (y + shell->offset_y) / shell->scale_y; +} + +/** + * gimp_display_shell_zoom_segments: + * @shell: a #GimpDisplayShell + * @src_segs: array of segments in image coordinates + * @dest_segs: returns the corresponding segments in display coordinates + * @n_segs: number of segments + * + * Zooms from image coordinates to display coordinates, so that + * objects can be rendered at the correct points on the display. + **/ +void +gimp_display_shell_zoom_segments (GimpDisplayShell *shell, + const GimpBoundSeg *src_segs, + GimpSegment *dest_segs, + gint n_segs, + gdouble offset_x, + gdouble offset_y) +{ + gint i; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + for (i = 0; i < n_segs ; i++) + { + gdouble x1, x2; + gdouble y1, y2; + + x1 = src_segs[i].x1 + offset_x; + x2 = src_segs[i].x2 + offset_x; + y1 = src_segs[i].y1 + offset_y; + y2 = src_segs[i].y2 + offset_y; + + dest_segs[i].x1 = SCALEX (shell, x1) - shell->offset_x; + dest_segs[i].x2 = SCALEX (shell, x2) - shell->offset_x; + dest_segs[i].y1 = SCALEY (shell, y1) - shell->offset_y; + dest_segs[i].y2 = SCALEY (shell, y2) - shell->offset_y; + } +} + +/** + * gimp_display_shell_rotate_coords: + * @shell: a #GimpDisplayShell + * @image_coords: unrotated display coordinates + * @display_coords: returns the corresponding rotated display coordinates + * + * Rotates from unrotated display coordinates to rotated display + * coordinates, so that objects can be rendered at the correct points + * on the display. + **/ +void +gimp_display_shell_rotate_coords (GimpDisplayShell *shell, + const GimpCoords *unrotated_coords, + GimpCoords *rotated_coords) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (unrotated_coords != NULL); + g_return_if_fail (rotated_coords != NULL); + + *rotated_coords = *unrotated_coords; + + if (shell->rotate_transform) + cairo_matrix_transform_point (shell->rotate_transform, + &rotated_coords->x, + &rotated_coords->y); +} + +/** + * gimp_display_shell_unrotate_coords: + * @shell: a #GimpDisplayShell + * @display_coords: rotated display coordinates + * @image_coords: returns the corresponding unrotated display coordinates + * + * Rotates from rotated display coordinates to unrotated display coordinates. + **/ +void +gimp_display_shell_unrotate_coords (GimpDisplayShell *shell, + const GimpCoords *rotated_coords, + GimpCoords *unrotated_coords) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (rotated_coords != NULL); + g_return_if_fail (unrotated_coords != NULL); + + *unrotated_coords = *rotated_coords; + + if (shell->rotate_untransform) + cairo_matrix_transform_point (shell->rotate_untransform, + &unrotated_coords->x, + &unrotated_coords->y); +} + +/** + * gimp_display_shell_rotate_xy: + * @shell: + * @x: + * @y: + * @nx: + * @ny: + * + * Rotates an unrotated display coordinate to a rotated shell coordinate. + **/ +void +gimp_display_shell_rotate_xy (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gint *nx, + gint *ny) +{ + gint64 tx; + gint64 ty; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + if (shell->rotate_transform) + cairo_matrix_transform_point (shell->rotate_transform, &x, &y); + + tx = x; + ty = y; + + /* The projected coordinates might overflow a gint in the case of + * big images at high zoom levels, so we clamp them here to avoid + * problems. + */ + *nx = CLAMP (tx, G_MININT, G_MAXINT); + *ny = CLAMP (ty, G_MININT, G_MAXINT); +} + +/** + * gimp_display_shell_unrotate_xy: + * @shell: a #GimpDisplayShell + * @x: x coordinate in rotated display coordinates + * @y: y coordinate in rotated display coordinates + * @nx: returns x oordinate in unrotated display coordinates + * @ny: returns y coordinate in unrotated display coordinates + * + * Rotate from rotated display coordinates to unrotated display + * coordinates. + **/ +void +gimp_display_shell_unrotate_xy (GimpDisplayShell *shell, + gint x, + gint y, + gint *nx, + gint *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + if (shell->rotate_untransform) + { + gdouble fx = x; + gdouble fy = y; + + cairo_matrix_transform_point (shell->rotate_untransform, &fx, &fy); + + *nx = CLAMP (fx, G_MININT, G_MAXINT); + *ny = CLAMP (fy, G_MININT, G_MAXINT); + } + else + { + *nx = x; + *ny = y; + } +} + +/** + * gimp_display_shell_rotate_xy_f: + * @shell: a #GimpDisplayShell + * @x: image x coordinate of point + * @y: image y coordinate of point + * @nx: returned shell canvas x coordinate + * @ny: returned shell canvas y coordinate + * + * Rotates from untransformed display coordinates to rotated display + * coordinates. + **/ +void +gimp_display_shell_rotate_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + *nx = x; + *ny = y; + + if (shell->rotate_transform) + cairo_matrix_transform_point (shell->rotate_transform, nx, ny); +} + +/** + * gimp_display_shell_unrotate_xy_f: + * @shell: a #GimpDisplayShell + * @x: x coordinate in rotated display coordinates + * @y: y coordinate in rotated display coordinates + * @nx: place to return x coordinate in unrotated display coordinates + * @ny: place to return y coordinate in unrotated display coordinates + * + * This function is identical to gimp_display_shell_unrotate_xy(), + * except that the input and output coordinates are doubles rather + * than ints. + **/ +void +gimp_display_shell_unrotate_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + *nx = x; + *ny = y; + + if (shell->rotate_untransform) + cairo_matrix_transform_point (shell->rotate_untransform, nx, ny); +} + +void +gimp_display_shell_rotate_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2) +{ + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->rotate_transform) + { + gdouble tx1 = x1; + gdouble ty1 = y1; + gdouble tx2 = x1; + gdouble ty2 = y2; + gdouble tx3 = x2; + gdouble ty3 = y1; + gdouble tx4 = x2; + gdouble ty4 = y2; + + cairo_matrix_transform_point (shell->rotate_transform, &tx1, &ty1); + cairo_matrix_transform_point (shell->rotate_transform, &tx2, &ty2); + cairo_matrix_transform_point (shell->rotate_transform, &tx3, &ty3); + cairo_matrix_transform_point (shell->rotate_transform, &tx4, &ty4); + + *nx1 = MIN4 (tx1, tx2, tx3, tx4); + *ny1 = MIN4 (ty1, ty2, ty3, ty4); + *nx2 = MAX4 (tx1, tx2, tx3, tx4); + *ny2 = MAX4 (ty1, ty2, ty3, ty4); + } + else + { + *nx1 = x1; + *ny1 = y1; + *nx2 = x2; + *ny2 = y2; + } +} + +void +gimp_display_shell_unrotate_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->rotate_untransform) + { + gdouble tx1 = x1; + gdouble ty1 = y1; + gdouble tx2 = x1; + gdouble ty2 = y2; + gdouble tx3 = x2; + gdouble ty3 = y1; + gdouble tx4 = x2; + gdouble ty4 = y2; + + cairo_matrix_transform_point (shell->rotate_untransform, &tx1, &ty1); + cairo_matrix_transform_point (shell->rotate_untransform, &tx2, &ty2); + cairo_matrix_transform_point (shell->rotate_untransform, &tx3, &ty3); + cairo_matrix_transform_point (shell->rotate_untransform, &tx4, &ty4); + + *nx1 = MIN4 (tx1, tx2, tx3, tx4); + *ny1 = MIN4 (ty1, ty2, ty3, ty4); + *nx2 = MAX4 (tx1, tx2, tx3, tx4); + *ny2 = MAX4 (ty1, ty2, ty3, ty4); + } + else + { + *nx1 = x1; + *ny1 = y1; + *nx2 = x2; + *ny2 = y2; + } +} + +/** + * gimp_display_shell_transform_coords: + * @shell: a #GimpDisplayShell + * @image_coords: image coordinates + * @display_coords: returns the corresponding display coordinates + * + * Transforms from image coordinates to display coordinates, so that + * objects can be rendered at the correct points on the display. + **/ +void +gimp_display_shell_transform_coords (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GimpCoords *display_coords) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (image_coords != NULL); + g_return_if_fail (display_coords != NULL); + + *display_coords = *image_coords; + + display_coords->x = SCALEX (shell, image_coords->x); + display_coords->y = SCALEY (shell, image_coords->y); + + display_coords->x -= shell->offset_x; + display_coords->y -= shell->offset_y; + + if (shell->rotate_transform) + cairo_matrix_transform_point (shell->rotate_transform, + &display_coords->x, + &display_coords->y); +} + +/** + * gimp_display_shell_untransform_coords: + * @shell: a #GimpDisplayShell + * @display_coords: display coordinates + * @image_coords: returns the corresponding image coordinates + * + * Transforms from display coordinates to image coordinates, so that + * points on the display can be mapped to points in the image. + **/ +void +gimp_display_shell_untransform_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (display_coords != NULL); + g_return_if_fail (image_coords != NULL); + + *image_coords = *display_coords; + + if (shell->rotate_untransform) + cairo_matrix_transform_point (shell->rotate_untransform, + &image_coords->x, + &image_coords->y); + + image_coords->x += shell->offset_x; + image_coords->y += shell->offset_y; + + image_coords->x /= shell->scale_x; + image_coords->y /= shell->scale_y; + + image_coords->xscale = shell->scale_x; + image_coords->yscale = shell->scale_y; + image_coords->angle = shell->rotate_angle / 360.0; + image_coords->reflect = shell->flip_horizontally ^ shell->flip_vertically; + + if (shell->flip_vertically) + image_coords->angle += 0.5; +} + +/** + * gimp_display_shell_transform_xy: + * @shell: + * @x: + * @y: + * @nx: + * @ny: + * + * Transforms an image coordinate to a shell coordinate. + **/ +void +gimp_display_shell_transform_xy (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gint *nx, + gint *ny) +{ + gint64 tx; + gint64 ty; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + tx = x * shell->scale_x; + ty = y * shell->scale_y; + + tx -= shell->offset_x; + ty -= shell->offset_y; + + if (shell->rotate_transform) + { + gdouble fx = tx; + gdouble fy = ty; + + cairo_matrix_transform_point (shell->rotate_transform, &fx, &fy); + + tx = fx; + ty = fy; + } + + /* The projected coordinates might overflow a gint in the case of + * big images at high zoom levels, so we clamp them here to avoid + * problems. + */ + *nx = CLAMP (tx, G_MININT, G_MAXINT); + *ny = CLAMP (ty, G_MININT, G_MAXINT); +} + +/** + * gimp_display_shell_untransform_xy: + * @shell: a #GimpDisplayShell + * @x: x coordinate in display coordinates + * @y: y coordinate in display coordinates + * @nx: returns x oordinate in image coordinates + * @ny: returns y coordinate in image coordinates + * @round: if %TRUE, round the results to the nearest integer; + * if %FALSE, simply cast them to @gint. + * + * Transform from display coordinates to image coordinates, so that + * points on the display can be mapped to the corresponding points + * in the image. + **/ +void +gimp_display_shell_untransform_xy (GimpDisplayShell *shell, + gint x, + gint y, + gint *nx, + gint *ny, + gboolean round) +{ + gint64 tx; + gint64 ty; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + if (shell->rotate_untransform) + { + gdouble fx = x; + gdouble fy = y; + + cairo_matrix_transform_point (shell->rotate_untransform, &fx, &fy); + + x = fx; + y = fy; + } + + if (round) + { + tx = SIGNED_ROUND (((gdouble) x + shell->offset_x) / shell->scale_x); + ty = SIGNED_ROUND (((gdouble) y + shell->offset_y) / shell->scale_y); + } + else + { + tx = ((gint64) x + shell->offset_x) / shell->scale_x; + ty = ((gint64) y + shell->offset_y) / shell->scale_y; + } + + *nx = CLAMP (tx, G_MININT, G_MAXINT); + *ny = CLAMP (ty, G_MININT, G_MAXINT); +} + +/** + * gimp_display_shell_transform_xy_f: + * @shell: a #GimpDisplayShell + * @x: image x coordinate of point + * @y: image y coordinate of point + * @nx: returned shell canvas x coordinate + * @ny: returned shell canvas y coordinate + * + * Transforms from image coordinates to display shell canvas + * coordinates. + **/ +void +gimp_display_shell_transform_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + *nx = SCALEX (shell, x) - shell->offset_x; + *ny = SCALEY (shell, y) - shell->offset_y; + + if (shell->rotate_transform) + cairo_matrix_transform_point (shell->rotate_transform, nx, ny); +} + +/** + * gimp_display_shell_untransform_xy_f: + * @shell: a #GimpDisplayShell + * @x: x coordinate in display coordinates + * @y: y coordinate in display coordinates + * @nx: place to return x coordinate in image coordinates + * @ny: place to return y coordinate in image coordinates + * + * This function is identical to gimp_display_shell_untransform_xy(), + * except that the input and output coordinates are doubles rather than + * ints, and consequently there is no option related to rounding. + **/ +void +gimp_display_shell_untransform_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx != NULL); + g_return_if_fail (ny != NULL); + + if (shell->rotate_untransform) + cairo_matrix_transform_point (shell->rotate_untransform, &x, &y); + + *nx = (x + shell->offset_x) / shell->scale_x; + *ny = (y + shell->offset_y) / shell->scale_y; +} + +void +gimp_display_shell_transform_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx1 != NULL); + g_return_if_fail (ny1 != NULL); + g_return_if_fail (nx2 != NULL); + g_return_if_fail (ny2 != NULL); + + if (shell->rotate_transform) + { + gdouble tx1, ty1; + gdouble tx2, ty2; + gdouble tx3, ty3; + gdouble tx4, ty4; + + gimp_display_shell_transform_xy_f_noround (shell, x1, y1, &tx1, &ty1); + gimp_display_shell_transform_xy_f_noround (shell, x1, y2, &tx2, &ty2); + gimp_display_shell_transform_xy_f_noround (shell, x2, y1, &tx3, &ty3); + gimp_display_shell_transform_xy_f_noround (shell, x2, y2, &tx4, &ty4); + + *nx1 = MIN4 (tx1, tx2, tx3, tx4); + *ny1 = MIN4 (ty1, ty2, ty3, ty4); + *nx2 = MAX4 (tx1, tx2, tx3, tx4); + *ny2 = MAX4 (ty1, ty2, ty3, ty4); + } + else + { + gimp_display_shell_transform_xy_f_noround (shell, x1, y1, nx1, ny1); + gimp_display_shell_transform_xy_f_noround (shell, x2, y2, nx2, ny2); + } +} + +void +gimp_display_shell_untransform_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (nx1 != NULL); + g_return_if_fail (ny1 != NULL); + g_return_if_fail (nx2 != NULL); + g_return_if_fail (ny2 != NULL); + + if (shell->rotate_untransform) + { + gdouble tx1, ty1; + gdouble tx2, ty2; + gdouble tx3, ty3; + gdouble tx4, ty4; + + gimp_display_shell_untransform_xy_f (shell, x1, y1, &tx1, &ty1); + gimp_display_shell_untransform_xy_f (shell, x1, y2, &tx2, &ty2); + gimp_display_shell_untransform_xy_f (shell, x2, y1, &tx3, &ty3); + gimp_display_shell_untransform_xy_f (shell, x2, y2, &tx4, &ty4); + + *nx1 = MIN4 (tx1, tx2, tx3, tx4); + *ny1 = MIN4 (ty1, ty2, ty3, ty4); + *nx2 = MAX4 (tx1, tx2, tx3, tx4); + *ny2 = MAX4 (ty1, ty2, ty3, ty4); + } + else + { + gimp_display_shell_untransform_xy_f (shell, x1, y1, nx1, ny1); + gimp_display_shell_untransform_xy_f (shell, x2, y2, nx2, ny2); + } +} + +/* transforms a bounding box from image-space, uniformly scaled by a factor of + * 'scale', to display-space. this is equivalent to, but more accurate than, + * dividing the input by 'scale', and using + * gimp_display_shell_transform_bounds(), in particular, in that if 'scale' + * equals 'shell->scale_x' or 'shell->scale_y', there is no loss in accuracy + * in the corresponding dimension due to scaling (although there might be loss + * of accuracy due to rotation or translation.) + */ +void +gimp_display_shell_transform_bounds_with_scale (GimpDisplayShell *shell, + gdouble scale, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2) +{ + gdouble factor_x; + gdouble factor_y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (scale > 0.0); + g_return_if_fail (nx1 != NULL); + g_return_if_fail (ny1 != NULL); + g_return_if_fail (nx2 != NULL); + g_return_if_fail (ny2 != NULL); + + factor_x = shell->scale_x / scale; + factor_y = shell->scale_y / scale; + + x1 = x1 * factor_x - shell->offset_x; + y1 = y1 * factor_y - shell->offset_y; + x2 = x2 * factor_x - shell->offset_x; + y2 = y2 * factor_y - shell->offset_y; + + gimp_display_shell_rotate_bounds (shell, + x1, y1, x2, y2, + nx1, ny1, nx2, ny2); +} + +/* transforms a bounding box from display-space to image-space, uniformly + * scaled by a factor of 'scale'. this is equivalent to, but more accurate + * than, using gimp_display_shell_untransform_bounds(), and multiplying the + * output by 'scale', in particular, in that if 'scale' equals 'shell->scale_x' + * or 'shell->scale_y', there is no loss in accuracy in the corresponding + * dimension due to scaling (although there might be loss of accuracy due to + * rotation or translation.) + */ +void +gimp_display_shell_untransform_bounds_with_scale (GimpDisplayShell *shell, + gdouble scale, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2) +{ + gdouble factor_x; + gdouble factor_y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (scale > 0.0); + g_return_if_fail (nx1 != NULL); + g_return_if_fail (ny1 != NULL); + g_return_if_fail (nx2 != NULL); + g_return_if_fail (ny2 != NULL); + + factor_x = scale / shell->scale_x; + factor_y = scale / shell->scale_y; + + gimp_display_shell_unrotate_bounds (shell, + x1, y1, x2, y2, + nx1, ny1, nx2, ny2); + + *nx1 = (*nx1 + shell->offset_x) * factor_x; + *ny1 = (*ny1 + shell->offset_y) * factor_y; + *nx2 = (*nx2 + shell->offset_x) * factor_x; + *ny2 = (*ny2 + shell->offset_y) * factor_y; +} + +/** + * gimp_display_shell_untransform_viewport: + * @shell: a #GimpDisplayShell + * @clip: whether to clip the result to the image bounds + * @x: returns image x coordinate of display upper left corner + * @y: returns image y coordinate of display upper left corner + * @width: returns width of display measured in image coordinates + * @height: returns height of display measured in image coordinates + * + * This function calculates the part of the image, in image coordinates, + * that corresponds to the display viewport. + **/ +void +gimp_display_shell_untransform_viewport (GimpDisplayShell *shell, + gboolean clip, + gint *x, + gint *y, + gint *width, + gint *height) +{ + gdouble x1, y1, x2, y2; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_untransform_bounds (shell, + 0, 0, + shell->disp_width, shell->disp_height, + &x1, &y1, + &x2, &y2); + + x1 = floor (x1); + y1 = floor (y1); + x2 = ceil (x2); + y2 = ceil (y2); + + if (clip) + { + GimpImage *image = gimp_display_get_image (shell->display); + + x1 = MAX (x1, 0); + y1 = MAX (y1, 0); + x2 = MIN (x2, gimp_image_get_width (image)); + y2 = MIN (y2, gimp_image_get_height (image)); + } + + if (x) *x = x1; + if (y) *y = y1; + if (width) *width = x2 - x1; + if (height) *height = y2 - y1; +} + + +/* private functions */ + +/* Same as gimp_display_shell_transform_xy_f(), but doesn't do any rounding + * for the transformed coordinates. + */ +static void +gimp_display_shell_transform_xy_f_noround (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny) +{ + *nx = shell->scale_x * x - shell->offset_x; + *ny = shell->scale_y * y - shell->offset_y; + + if (shell->rotate_transform) + cairo_matrix_transform_point (shell->rotate_transform, nx, ny); +} diff --git a/app/display/gimpdisplayshell-transform.h b/app/display/gimpdisplayshell-transform.h new file mode 100644 index 0000000..1877b13 --- /dev/null +++ b/app/display/gimpdisplayshell-transform.h @@ -0,0 +1,200 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_TRANSFORM_H__ +#define __GIMP_DISPLAY_SHELL_TRANSFORM_H__ + + +/* zoom: functions to transform from image space to unrotated display + * space and back, taking into account scroll offset and scale + */ + +void gimp_display_shell_zoom_coords (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GimpCoords *display_coords); +void gimp_display_shell_unzoom_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords); + +void gimp_display_shell_zoom_xy (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gint *nx, + gint *ny); +void gimp_display_shell_unzoom_xy (GimpDisplayShell *shell, + gint x, + gint y, + gint *nx, + gint *ny, + gboolean round); + +void gimp_display_shell_zoom_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); +void gimp_display_shell_unzoom_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); + +void gimp_display_shell_zoom_segments (GimpDisplayShell *shell, + const GimpBoundSeg *src_segs, + GimpSegment *dest_segs, + gint n_segs, + gdouble offset_x, + gdouble offset_y); + + +/* rotate: functions to transform from unrotated and unflipped but + * zoomed display space to rotated and filpped display space and back + */ + +void gimp_display_shell_rotate_coords (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GimpCoords *display_coords); +void gimp_display_shell_unrotate_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords); + +void gimp_display_shell_rotate_xy (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gint *nx, + gint *ny); +void gimp_display_shell_unrotate_xy (GimpDisplayShell *shell, + gint x, + gint y, + gint *nx, + gint *ny); + +void gimp_display_shell_rotate_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); +void gimp_display_shell_unrotate_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); + +void gimp_display_shell_rotate_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2); +void gimp_display_shell_unrotate_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2); + + +/* transform: functions to transform from image space to rotated + * display space and back, taking into account scroll offset, scale, + * rotation and flipping + */ + +void gimp_display_shell_transform_coords (GimpDisplayShell *shell, + const GimpCoords *image_coords, + GimpCoords *display_coords); +void gimp_display_shell_untransform_coords (GimpDisplayShell *shell, + const GimpCoords *display_coords, + GimpCoords *image_coords); + +void gimp_display_shell_transform_xy (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gint *nx, + gint *ny); +void gimp_display_shell_untransform_xy (GimpDisplayShell *shell, + gint x, + gint y, + gint *nx, + gint *ny, + gboolean round); + +void gimp_display_shell_transform_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); +void gimp_display_shell_untransform_xy_f (GimpDisplayShell *shell, + gdouble x, + gdouble y, + gdouble *nx, + gdouble *ny); + +void gimp_display_shell_transform_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2); +void gimp_display_shell_untransform_bounds (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2); + +void gimp_display_shell_transform_bounds_with_scale (GimpDisplayShell *shell, + gdouble scale, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2); +void gimp_display_shell_untransform_bounds_with_scale (GimpDisplayShell *shell, + gdouble scale, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble *nx1, + gdouble *ny1, + gdouble *nx2, + gdouble *ny2); + +void gimp_display_shell_untransform_viewport (GimpDisplayShell *shell, + gboolean clip, + gint *x, + gint *y, + gint *width, + gint *height); + + +#endif /* __GIMP_DISPLAY_SHELL_TRANSFORM_H__ */ diff --git a/app/display/gimpdisplayshell-utils.c b/app/display/gimpdisplayshell-utils.c new file mode 100644 index 0000000..8e85e71 --- /dev/null +++ b/app/display/gimpdisplayshell-utils.c @@ -0,0 +1,220 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-utils.h" +#include "core/gimpimage.h" +#include "core/gimpunit.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-utils.h" + +#include "gimp-intl.h" + +void +gimp_display_shell_get_constrained_line_params (GimpDisplayShell *shell, + gdouble *offset_angle, + gdouble *xres, + gdouble *yres) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (offset_angle != NULL); + g_return_if_fail (xres != NULL); + g_return_if_fail (yres != NULL); + + if (shell->flip_horizontally ^ shell->flip_vertically) + *offset_angle = +shell->rotate_angle; + else + *offset_angle = -shell->rotate_angle; + + *xres = 1.0; + *yres = 1.0; + + if (! shell->dot_for_dot) + { + GimpImage *image = gimp_display_get_image (shell->display); + + if (image) + gimp_image_get_resolution (image, xres, yres); + } +} + +void +gimp_display_shell_constrain_line (GimpDisplayShell *shell, + gdouble start_x, + gdouble start_y, + gdouble *end_x, + gdouble *end_y, + gint n_snap_lines) +{ + gdouble offset_angle; + gdouble xres, yres; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (end_x != NULL); + g_return_if_fail (end_y != NULL); + + gimp_display_shell_get_constrained_line_params (shell, + &offset_angle, + &xres, &yres); + + gimp_constrain_line (start_x, start_y, + end_x, end_y, + n_snap_lines, + offset_angle, + xres, yres); +} + +gdouble +gimp_display_shell_constrain_angle (GimpDisplayShell *shell, + gdouble angle, + gint n_snap_lines) +{ + gdouble x, y; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), 0.0); + + x = cos (angle); + y = sin (angle); + + gimp_display_shell_constrain_line (shell, + 0.0, 0.0, + &x, &y, + n_snap_lines); + + return atan2 (y, x); +} + +/** + * gimp_display_shell_get_line_status: + * @status: initial status text. + * @separator: separator text between the line information and @status. + * @shell: #GimpDisplayShell this status text will be displayed for. + * @x1: abscissa of first point. + * @y1: ordinate of first point. + * @x2: abscissa of second point. + * @y2: ordinate of second point. + * + * Utility function to prepend the status message with a distance and + * angle value. Obviously this is only to be used for tools when it + * makes sense, and in particular when there is a concept of line. For + * instance when shift-clicking a painting tool or in the blend tool, + * etc. + * This utility prevents code duplication but also ensures a common + * display for every tool where such a status is needed. It will take + * into account the shell unit settings and will use the ideal digit + * precision according to current image resolution. + * + * Return value: a newly allocated string containing the enhanced status. + **/ +gchar * +gimp_display_shell_get_line_status (GimpDisplayShell *shell, + const gchar *status, + const gchar *separator, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + GimpImage *image; + gchar *enhanced_status; + gdouble xres; + gdouble yres; + gdouble dx, dy, pixel_dist; + gdouble angle; + + image = gimp_display_get_image (shell->display); + if (! image) + { + /* This makes no sense to add line information when no image is + * attached to the display. */ + return g_strdup (status); + } + + if (shell->unit == GIMP_UNIT_PIXEL) + xres = yres = 1.0; + else + gimp_image_get_resolution (image, &xres, &yres); + + dx = x2 - x1; + dy = y2 - y1; + pixel_dist = sqrt (SQR (dx) + SQR (dy)); + + if (dx) + { + angle = gimp_rad_to_deg (atan ((dy/yres) / (dx/xres))); + if (dx > 0) + { + if (dy > 0) + angle = 360.0 - angle; + else if (dy < 0) + angle = -angle; + } + else + { + angle = 180.0 - angle; + } + } + else if (dy) + { + angle = dy > 0 ? 270.0 : 90.0; + } + else + { + angle = 0.0; + } + + if (shell->unit == GIMP_UNIT_PIXEL) + { + enhanced_status = g_strdup_printf ("%.1f %s, %.2f\302\260%s%s", + pixel_dist, _("pixels"), angle, + separator, status); + } + else + { + gdouble inch_dist; + gdouble unit_dist; + gint digits = 0; + + /* The distance in unit. */ + inch_dist = sqrt (SQR (dx / xres) + SQR (dy / yres)); + unit_dist = gimp_unit_get_factor (shell->unit) * inch_dist; + + /* The ideal digit precision for unit in current resolution. */ + if (inch_dist) + digits = gimp_unit_get_scaled_digits (shell->unit, + pixel_dist / inch_dist); + + enhanced_status = g_strdup_printf ("%.*f %s, %.2f\302\260%s%s", + digits, unit_dist, + gimp_unit_get_symbol (shell->unit), + angle, separator, status); + + } + + return enhanced_status; +} diff --git a/app/display/gimpdisplayshell-utils.h b/app/display/gimpdisplayshell-utils.h new file mode 100644 index 0000000..3eb52e3 --- /dev/null +++ b/app/display/gimpdisplayshell-utils.h @@ -0,0 +1,45 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_UTILS_H__ +#define __GIMP_DISPLAY_SHELL_UTILS_H__ + + +void gimp_display_shell_get_constrained_line_params (GimpDisplayShell *shell, + gdouble *offset_angle, + gdouble *xres, + gdouble *yres); +void gimp_display_shell_constrain_line (GimpDisplayShell *shell, + gdouble start_x, + gdouble start_y, + gdouble *end_x, + gdouble *end_y, + gint n_snap_lines); +gdouble gimp_display_shell_constrain_angle (GimpDisplayShell *shell, + gdouble angle, + gint n_snap_lines); + +gchar * gimp_display_shell_get_line_status (GimpDisplayShell *shell, + const gchar *status, + const gchar *separator, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + + +#endif /* __GIMP_DISPLAY_SHELL_UTILS_H__ */ diff --git a/app/display/gimpdisplayshell.c b/app/display/gimpdisplayshell.c new file mode 100644 index 0000000..6db94e4 --- /dev/null +++ b/app/display/gimpdisplayshell.c @@ -0,0 +1,2140 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <stdlib.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" +#include "tools/tools-types.h" + +#include "config/gimpcoreconfig.h" +#include "config/gimpdisplayconfig.h" +#include "config/gimpdisplayoptions.h" + +#include "core/gimp.h" +#include "core/gimp-utils.h" +#include "core/gimpchannel.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimage-grid.h" +#include "core/gimpimage-guides.h" +#include "core/gimpimage-snap.h" +#include "core/gimppickable.h" +#include "core/gimpprojectable.h" +#include "core/gimpprojection.h" +#include "core/gimpmarshal.h" +#include "core/gimptemplate.h" + +#include "widgets/gimpdevices.h" +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpuimanager.h" +#include "widgets/gimpwidgets-utils.h" + +#include "tools/tool_manager.h" + +#include "gimpcanvas.h" +#include "gimpcanvascanvasboundary.h" +#include "gimpcanvaslayerboundary.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-callbacks.h" +#include "gimpdisplayshell-cursor.h" +#include "gimpdisplayshell-dnd.h" +#include "gimpdisplayshell-expose.h" +#include "gimpdisplayshell-filter.h" +#include "gimpdisplayshell-handlers.h" +#include "gimpdisplayshell-items.h" +#include "gimpdisplayshell-profile.h" +#include "gimpdisplayshell-progress.h" +#include "gimpdisplayshell-render.h" +#include "gimpdisplayshell-rotate.h" +#include "gimpdisplayshell-rulers.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-scrollbars.h" +#include "gimpdisplayshell-selection.h" +#include "gimpdisplayshell-title.h" +#include "gimpdisplayshell-tool-events.h" +#include "gimpdisplayshell-transform.h" +#include "gimpimagewindow.h" +#include "gimpmotionbuffer.h" +#include "gimpstatusbar.h" + +#include "about.h" +#include "gimp-log.h" +#include "gimp-priorities.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_POPUP_MANAGER, + PROP_INITIAL_SCREEN, + PROP_INITIAL_MONITOR, + PROP_DISPLAY, + PROP_UNIT, + PROP_TITLE, + PROP_STATUS, + PROP_ICON, + PROP_SHOW_ALL, + PROP_INFINITE_CANVAS +}; + +enum +{ + SCALED, + SCROLLED, + ROTATED, + RECONNECT, + LAST_SIGNAL +}; + + +typedef struct _GimpDisplayShellOverlay GimpDisplayShellOverlay; + +struct _GimpDisplayShellOverlay +{ + gdouble image_x; + gdouble image_y; + GimpHandleAnchor anchor; + gint spacing_x; + gint spacing_y; +}; + + +/* local function prototypes */ + +static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface); + +static void gimp_display_shell_constructed (GObject *object); +static void gimp_display_shell_dispose (GObject *object); +static void gimp_display_shell_finalize (GObject *object); +static void gimp_display_shell_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_display_shell_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_display_shell_unrealize (GtkWidget *widget); +static void gimp_display_shell_unmap (GtkWidget *widget); +static void gimp_display_shell_screen_changed (GtkWidget *widget, + GdkScreen *previous); +static gboolean gimp_display_shell_popup_menu (GtkWidget *widget); + +static void gimp_display_shell_real_scaled (GimpDisplayShell *shell); +static void gimp_display_shell_real_scrolled (GimpDisplayShell *shell); +static void gimp_display_shell_real_rotated (GimpDisplayShell *shell); + +static const guint8 * + gimp_display_shell_get_icc_profile(GimpColorManaged *managed, + gsize *len); +static GimpColorProfile * + gimp_display_shell_get_color_profile(GimpColorManaged *managed); +static void gimp_display_shell_profile_changed(GimpColorManaged *managed); + +static void gimp_display_shell_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data); +static void gimp_display_shell_zoom_button_callback + (GimpDisplayShell *shell, + GtkWidget *zoom_button); +static void gimp_display_shell_sync_config (GimpDisplayShell *shell, + GimpDisplayConfig *config); + +static void gimp_display_shell_remove_overlay (GtkWidget *canvas, + GtkWidget *child, + GimpDisplayShell *shell); +static void gimp_display_shell_transform_overlay (GimpDisplayShell *shell, + GtkWidget *child, + gdouble *x, + gdouble *y); + + +G_DEFINE_TYPE_WITH_CODE (GimpDisplayShell, gimp_display_shell, + GTK_TYPE_EVENT_BOX, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_display_shell_progress_iface_init) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED, + gimp_color_managed_iface_init)) + + +#define parent_class gimp_display_shell_parent_class + +static guint display_shell_signals[LAST_SIGNAL] = { 0 }; + + +static const gchar display_rc_style[] = + "style \"check-button-style\"\n" + "{\n" + " GtkToggleButton::child-displacement-x = 0\n" + " GtkToggleButton::child-displacement-y = 0\n" + "}\n" + "widget \"*\" style \"check-button-style\""; + +static void +gimp_display_shell_class_init (GimpDisplayShellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + display_shell_signals[SCALED] = + g_signal_new ("scaled", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDisplayShellClass, scaled), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + display_shell_signals[SCROLLED] = + g_signal_new ("scrolled", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDisplayShellClass, scrolled), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + display_shell_signals[ROTATED] = + g_signal_new ("rotated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDisplayShellClass, rotated), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + display_shell_signals[RECONNECT] = + g_signal_new ("reconnect", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDisplayShellClass, reconnect), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->constructed = gimp_display_shell_constructed; + object_class->dispose = gimp_display_shell_dispose; + object_class->finalize = gimp_display_shell_finalize; + object_class->set_property = gimp_display_shell_set_property; + object_class->get_property = gimp_display_shell_get_property; + + widget_class->unrealize = gimp_display_shell_unrealize; + widget_class->unmap = gimp_display_shell_unmap; + widget_class->screen_changed = gimp_display_shell_screen_changed; + widget_class->popup_menu = gimp_display_shell_popup_menu; + + klass->scaled = gimp_display_shell_real_scaled; + klass->scrolled = gimp_display_shell_real_scrolled; + klass->rotated = gimp_display_shell_real_rotated; + klass->reconnect = NULL; + + g_object_class_install_property (object_class, PROP_POPUP_MANAGER, + g_param_spec_object ("popup-manager", + NULL, NULL, + GIMP_TYPE_UI_MANAGER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_INITIAL_SCREEN, + g_param_spec_object ("initial-screen", + NULL, NULL, + GDK_TYPE_SCREEN, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_INITIAL_MONITOR, + g_param_spec_int ("initial-monitor", + NULL, NULL, + 0, 16, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DISPLAY, + g_param_spec_object ("display", NULL, NULL, + GIMP_TYPE_DISPLAY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_UNIT, + gimp_param_spec_unit ("unit", NULL, NULL, + TRUE, FALSE, + GIMP_UNIT_PIXEL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_TITLE, + g_param_spec_string ("title", NULL, NULL, + GIMP_NAME, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_STATUS, + g_param_spec_string ("status", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ICON, + g_param_spec_object ("icon", NULL, NULL, + GDK_TYPE_PIXBUF, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SHOW_ALL, + g_param_spec_boolean ("show-all", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_INFINITE_CANVAS, + g_param_spec_boolean ("infinite-canvas", + NULL, NULL, + FALSE, + GIMP_PARAM_READABLE)); + + gtk_rc_parse_string (display_rc_style); +} + +static void +gimp_color_managed_iface_init (GimpColorManagedInterface *iface) +{ + iface->get_icc_profile = gimp_display_shell_get_icc_profile; + iface->get_color_profile = gimp_display_shell_get_color_profile; + iface->profile_changed = gimp_display_shell_profile_changed; +} + +static void +gimp_display_shell_init (GimpDisplayShell *shell) +{ + shell->options = g_object_new (GIMP_TYPE_DISPLAY_OPTIONS, NULL); + shell->fullscreen_options = g_object_new (GIMP_TYPE_DISPLAY_OPTIONS_FULLSCREEN, NULL); + shell->no_image_options = g_object_new (GIMP_TYPE_DISPLAY_OPTIONS_NO_IMAGE, NULL); + + shell->zoom = gimp_zoom_model_new (); + shell->dot_for_dot = TRUE; + shell->scale_x = 1.0; + shell->scale_y = 1.0; + + shell->show_image = TRUE; + + shell->show_all = FALSE; + + gimp_display_shell_items_init (shell); + + shell->icon_size = 128; + shell->icon_size_small = 96; + + shell->cursor_handedness = GIMP_HANDEDNESS_RIGHT; + shell->current_cursor = (GimpCursorType) -1; + shell->tool_cursor = GIMP_TOOL_CURSOR_NONE; + shell->cursor_modifier = GIMP_CURSOR_MODIFIER_NONE; + shell->override_cursor = (GimpCursorType) -1; + + shell->filter_format = babl_format ("R'G'B'A float"); + + shell->motion_buffer = gimp_motion_buffer_new (); + + g_signal_connect (shell->motion_buffer, "stroke", + G_CALLBACK (gimp_display_shell_buffer_stroke), + shell); + g_signal_connect (shell->motion_buffer, "hover", + G_CALLBACK (gimp_display_shell_buffer_hover), + shell); + + shell->zoom_focus_pointer_queue = g_queue_new (); + + gtk_widget_set_events (GTK_WIDGET (shell), (GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_FOCUS_CHANGE_MASK | + GDK_VISIBILITY_NOTIFY_MASK | + GDK_SCROLL_MASK)); + + /* zoom model callback */ + g_signal_connect_swapped (shell->zoom, "zoomed", + G_CALLBACK (gimp_display_shell_scale_update), + shell); + + /* active display callback */ + g_signal_connect (shell, "button-press-event", + G_CALLBACK (gimp_display_shell_events), + shell); + g_signal_connect (shell, "button-release-event", + G_CALLBACK (gimp_display_shell_events), + shell); + g_signal_connect (shell, "key-press-event", + G_CALLBACK (gimp_display_shell_events), + shell); + + gimp_help_connect (GTK_WIDGET (shell), gimp_standard_help_func, + GIMP_HELP_IMAGE_WINDOW, NULL); +} + +static void +gimp_display_shell_constructed (GObject *object) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object); + GimpDisplayConfig *config; + GimpImage *image; + GtkWidget *main_vbox; + GtkWidget *upper_hbox; + GtkWidget *right_vbox; + GtkWidget *lower_hbox; + GtkWidget *inner_table; + GtkWidget *gtk_image; + GimpAction *action; + gint image_width; + gint image_height; + gint shell_width; + gint shell_height; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_UI_MANAGER (shell->popup_manager)); + gimp_assert (GIMP_IS_DISPLAY (shell->display)); + + config = shell->display->config; + image = gimp_display_get_image (shell->display); + + gimp_display_shell_profile_init (shell); + + if (image) + { + image_width = gimp_image_get_width (image); + image_height = gimp_image_get_height (image); + } + else + { + /* These values are arbitrary. The width is determined by the + * menubar and the height is chosen to give a window aspect + * ratio of roughly 3:1 (as requested by the UI team). + */ + image_width = GIMP_DEFAULT_IMAGE_WIDTH; + image_height = GIMP_DEFAULT_IMAGE_HEIGHT / 3; + } + + shell->dot_for_dot = config->default_dot_for_dot; + + if (config->monitor_res_from_gdk) + { + gimp_get_monitor_resolution (shell->initial_screen, + shell->initial_monitor, + &shell->monitor_xres, &shell->monitor_yres); + } + else + { + shell->monitor_xres = config->monitor_xres; + shell->monitor_yres = config->monitor_yres; + } + + /* adjust the initial scale -- so that window fits on screen. */ + if (image) + { + gimp_display_shell_set_initial_scale (shell, 1.0, //scale, + &shell_width, &shell_height); + } + else + { + shell_width = -1; + shell_height = image_height; + } + + gimp_display_shell_sync_config (shell, config); + + /* GtkTable widgets are not able to shrink a row/column correctly if + * widgets are attached with GTK_EXPAND even if those widgets have + * other rows/columns in their rowspan/colspan where they could + * nicely expand without disturbing the row/column which is supposed + * to shrink. --Mitch + * + * Changed the packing to use hboxes and vboxes which behave nicer: + * + * shell + * | + * +-- main_vbox + * | + * +-- upper_hbox + * | | + * | +-- inner_table + * | | | + * | | +-- origin + * | | +-- hruler + * | | +-- vruler + * | | +-- canvas + * | | + * | +-- right_vbox + * | | + * | +-- zoom_on_resize_button + * | +-- vscrollbar + * | + * +-- lower_hbox + * | | + * | +-- quick_mask + * | +-- hscrollbar + * | +-- navbutton + * | + * +-- statusbar + * + * Note that we separate "shell" and "main_vbox", so that we can make + * "shell" a GtkEventBox, giving it its own window. This isolates our + * events from those of our ancestors, avoiding some potential slowdowns, + * and making things generally smoother. See bug #778966. + */ + + /* first, set up the container hierarchy *********************************/ + + /* the root vbox */ + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (shell), main_vbox); + gtk_widget_show (main_vbox); + + /* a hbox for the inner_table and the vertical scrollbar */ + upper_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (main_vbox), upper_hbox, TRUE, TRUE, 0); + gtk_widget_show (upper_hbox); + + /* the table containing origin, rulers and the canvas */ + inner_table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacing (GTK_TABLE (inner_table), 0, 0); + gtk_table_set_row_spacing (GTK_TABLE (inner_table), 0, 0); + gtk_box_pack_start (GTK_BOX (upper_hbox), inner_table, TRUE, TRUE, 0); + gtk_widget_show (inner_table); + + /* the vbox containing the color button and the vertical scrollbar */ + right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); + gtk_box_pack_start (GTK_BOX (upper_hbox), right_vbox, FALSE, FALSE, 0); + gtk_widget_show (right_vbox); + + /* the hbox containing the quickmask button, vertical scrollbar and + * the navigation button + */ + lower_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1); + gtk_box_pack_start (GTK_BOX (main_vbox), lower_hbox, FALSE, FALSE, 0); + gtk_widget_show (lower_hbox); + + /* create the scrollbars *************************************************/ + + /* the horizontal scrollbar */ + shell->hsbdata = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, image_width, + 1, 1, image_width)); + shell->hsb = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, shell->hsbdata); + gtk_widget_set_can_focus (shell->hsb, FALSE); + + /* the vertical scrollbar */ + shell->vsbdata = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, image_height, + 1, 1, image_height)); + shell->vsb = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, shell->vsbdata); + gtk_widget_set_can_focus (shell->vsb, FALSE); + + /* create the contents of the inner_table ********************************/ + + /* the menu popup button */ + shell->origin = gtk_event_box_new (); + + gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_RIGHT, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (shell->origin), gtk_image); + gtk_widget_show (gtk_image); + + g_signal_connect (shell->origin, "button-press-event", + G_CALLBACK (gimp_display_shell_origin_button_press), + shell); + + gimp_help_set_help_data (shell->origin, + _("Access the image menu"), + GIMP_HELP_IMAGE_WINDOW_ORIGIN); + + shell->canvas = gimp_canvas_new (config); + gtk_widget_set_size_request (shell->canvas, shell_width, shell_height); + gtk_container_set_border_width (GTK_CONTAINER (shell->canvas), 10); + + g_signal_connect (shell->canvas, "remove", + G_CALLBACK (gimp_display_shell_remove_overlay), + shell); + + gimp_display_shell_dnd_init (shell); + gimp_display_shell_selection_init (shell); + + /* the horizontal ruler */ + shell->hrule = gimp_ruler_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_events (GTK_WIDGET (shell->hrule), + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + + gimp_ruler_add_track_widget (GIMP_RULER (shell->hrule), shell->canvas); + g_signal_connect (shell->hrule, "button-press-event", + G_CALLBACK (gimp_display_shell_hruler_button_press), + shell); + + gimp_help_set_help_data (shell->hrule, NULL, GIMP_HELP_IMAGE_WINDOW_RULER); + + /* the vertical ruler */ + shell->vrule = gimp_ruler_new (GTK_ORIENTATION_VERTICAL); + gtk_widget_set_events (GTK_WIDGET (shell->vrule), + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + + gimp_ruler_add_track_widget (GIMP_RULER (shell->vrule), shell->canvas); + g_signal_connect (shell->vrule, "button-press-event", + G_CALLBACK (gimp_display_shell_vruler_button_press), + shell); + + gimp_help_set_help_data (shell->vrule, NULL, GIMP_HELP_IMAGE_WINDOW_RULER); + + /* set the rulers as track widgets for each other, so we don't end up + * with one ruler wrongly being stuck a few pixels off while we are + * hovering the other + */ + gimp_ruler_add_track_widget (GIMP_RULER (shell->hrule), shell->vrule); + gimp_ruler_add_track_widget (GIMP_RULER (shell->vrule), shell->hrule); + + gimp_devices_add_widget (shell->display->gimp, shell->hrule); + gimp_devices_add_widget (shell->display->gimp, shell->vrule); + + g_signal_connect (shell->canvas, "grab-notify", + G_CALLBACK (gimp_display_shell_canvas_grab_notify), + shell); + + g_signal_connect (shell->canvas, "realize", + G_CALLBACK (gimp_display_shell_canvas_realize), + shell); + g_signal_connect (shell->canvas, "realize", + G_CALLBACK (gimp_display_shell_canvas_realize_after), + shell); + g_signal_connect (shell->canvas, "size-allocate", + G_CALLBACK (gimp_display_shell_canvas_size_allocate), + shell); + g_signal_connect (shell->canvas, "expose-event", + G_CALLBACK (gimp_display_shell_canvas_expose), + shell); + + g_signal_connect (shell->canvas, "enter-notify-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "leave-notify-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "proximity-in-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "proximity-out-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "focus-in-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "focus-out-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "button-press-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "button-release-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "scroll-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "motion-notify-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "key-press-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + g_signal_connect (shell->canvas, "key-release-event", + G_CALLBACK (gimp_display_shell_canvas_tool_events), + shell); + + /* create the contents of the right_vbox *********************************/ + + shell->zoom_button = g_object_new (GTK_TYPE_CHECK_BUTTON, + "draw-indicator", FALSE, + "relief", GTK_RELIEF_NONE, + "width-request", 18, + "height-request", 18, + NULL); + gtk_widget_set_can_focus (shell->zoom_button, FALSE); + + gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_ZOOM_FOLLOW_WINDOW, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (shell->zoom_button), gtk_image); + gtk_widget_show (gtk_image); + + gimp_help_set_help_data (shell->zoom_button, + _("Zoom image when window size changes"), + GIMP_HELP_IMAGE_WINDOW_ZOOM_FOLLOW_BUTTON); + + g_signal_connect_swapped (shell->zoom_button, "toggled", + G_CALLBACK (gimp_display_shell_zoom_button_callback), + shell); + + /* create the contents of the lower_hbox *********************************/ + + /* the quick mask button */ + shell->quick_mask_button = g_object_new (GTK_TYPE_CHECK_BUTTON, + "draw-indicator", FALSE, + "relief", GTK_RELIEF_NONE, + "width-request", 18, + "height-request", 18, + NULL); + gtk_widget_set_can_focus (shell->quick_mask_button, FALSE); + + gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_QUICK_MASK_OFF, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (shell->quick_mask_button), gtk_image); + gtk_widget_show (gtk_image); + + action = gimp_ui_manager_find_action (shell->popup_manager, + "quick-mask", "quick-mask-toggle"); + if (action) + gimp_widget_set_accel_help (shell->quick_mask_button, action); + else + gimp_help_set_help_data (shell->quick_mask_button, + _("Toggle Quick Mask"), + GIMP_HELP_IMAGE_WINDOW_QUICK_MASK_BUTTON); + + g_signal_connect (shell->quick_mask_button, "toggled", + G_CALLBACK (gimp_display_shell_quick_mask_toggled), + shell); + g_signal_connect (shell->quick_mask_button, "button-press-event", + G_CALLBACK (gimp_display_shell_quick_mask_button_press), + shell); + + /* the navigation window button */ + shell->nav_ebox = gtk_event_box_new (); + + gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_NAVIGATION, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (shell->nav_ebox), gtk_image); + gtk_widget_show (gtk_image); + + g_signal_connect (shell->nav_ebox, "button-press-event", + G_CALLBACK (gimp_display_shell_navigation_button_press), + shell); + + gimp_help_set_help_data (shell->nav_ebox, + _("Navigate the image display"), + GIMP_HELP_IMAGE_WINDOW_NAV_BUTTON); + + /* the statusbar ********************************************************/ + + shell->statusbar = gimp_statusbar_new (); + gimp_statusbar_set_shell (GIMP_STATUSBAR (shell->statusbar), shell); + gimp_help_set_help_data (shell->statusbar, NULL, + GIMP_HELP_IMAGE_WINDOW_STATUS_BAR); + gtk_box_pack_end (GTK_BOX (main_vbox), shell->statusbar, FALSE, FALSE, 0); + + /* pack all the widgets **************************************************/ + + /* fill the inner_table */ + gtk_table_attach (GTK_TABLE (inner_table), shell->origin, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach (GTK_TABLE (inner_table), shell->hrule, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach (GTK_TABLE (inner_table), shell->vrule, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + gtk_table_attach (GTK_TABLE (inner_table), shell->canvas, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + /* fill the right_vbox */ + gtk_box_pack_start (GTK_BOX (right_vbox), + shell->zoom_button, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (right_vbox), + shell->vsb, TRUE, TRUE, 0); + + /* fill the lower_hbox */ + gtk_box_pack_start (GTK_BOX (lower_hbox), + shell->quick_mask_button, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (lower_hbox), + shell->hsb, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (lower_hbox), + shell->nav_ebox, FALSE, FALSE, 0); + + /* show everything that is always shown ***********************************/ + + gtk_widget_show (GTK_WIDGET (shell->canvas)); + + if (image) + { + gimp_display_shell_connect (shell); + + /* After connecting to the image we want to center it. Since we + * not even finished creating the display shell, we can safely + * assume we will get a size-allocate later. + */ + shell->size_allocate_center_image = TRUE; + } + else + { +#if 0 + /* Disabled because it sets GDK_POINTER_MOTION_HINT on + * shell->canvas. For info see Bug 677375 + */ + gimp_help_set_help_data (shell->canvas, + _("Drop image files here to open them"), + NULL); +#endif + + gimp_statusbar_empty (GIMP_STATUSBAR (shell->statusbar)); + } + + /* make sure the information is up-to-date */ + gimp_display_shell_scale_update (shell); + + gimp_display_shell_set_show_all (shell, config->default_show_all); +} + +static void +gimp_display_shell_dispose (GObject *object) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object); + + if (shell->display && gimp_display_get_shell (shell->display)) + gimp_display_shell_disconnect (shell); + + shell->popup_manager = NULL; + + if (shell->selection) + gimp_display_shell_selection_free (shell); + + gimp_display_shell_filter_set (shell, NULL); + + if (shell->filter_idle_id) + { + g_source_remove (shell->filter_idle_id); + shell->filter_idle_id = 0; + } + + g_clear_pointer (&shell->mask_surface, cairo_surface_destroy); + g_clear_pointer (&shell->checkerboard, cairo_pattern_destroy); + + gimp_display_shell_profile_finalize (shell); + + g_clear_object (&shell->filter_buffer); + shell->filter_data = NULL; + shell->filter_stride = 0; + + g_clear_object (&shell->mask); + + gimp_display_shell_items_free (shell); + + g_clear_object (&shell->motion_buffer); + + g_clear_pointer (&shell->zoom_focus_pointer_queue, g_queue_free); + + if (shell->title_idle_id) + { + g_source_remove (shell->title_idle_id); + shell->title_idle_id = 0; + } + + if (shell->fill_idle_id) + { + g_source_remove (shell->fill_idle_id); + shell->fill_idle_id = 0; + } + + g_clear_pointer (&shell->nav_popup, gtk_widget_destroy); + + if (shell->blink_timeout_id) + { + g_source_remove (shell->blink_timeout_id); + shell->blink_timeout_id = 0; + } + + shell->display = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_display_shell_finalize (GObject *object) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object); + + g_clear_object (&shell->zoom); + g_clear_pointer (&shell->rotate_transform, g_free); + g_clear_pointer (&shell->rotate_untransform, g_free); + g_clear_object (&shell->options); + g_clear_object (&shell->fullscreen_options); + g_clear_object (&shell->no_image_options); + g_clear_pointer (&shell->title, g_free); + g_clear_pointer (&shell->status, g_free); + g_clear_object (&shell->icon); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_display_shell_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object); + + switch (property_id) + { + case PROP_POPUP_MANAGER: + shell->popup_manager = g_value_get_object (value); + break; + case PROP_INITIAL_SCREEN: + shell->initial_screen = g_value_get_object (value); + break; + case PROP_INITIAL_MONITOR: + shell->initial_monitor = g_value_get_int (value); + break; + case PROP_DISPLAY: + shell->display = g_value_get_object (value); + break; + case PROP_UNIT: + gimp_display_shell_set_unit (shell, g_value_get_int (value)); + break; + case PROP_TITLE: + g_free (shell->title); + shell->title = g_value_dup_string (value); + break; + case PROP_STATUS: + g_free (shell->status); + shell->status = g_value_dup_string (value); + break; + case PROP_ICON: + if (shell->icon) + g_object_unref (shell->icon); + shell->icon = g_value_dup_object (value); + break; + case PROP_SHOW_ALL: + gimp_display_shell_set_show_all (shell, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_display_shell_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (object); + + switch (property_id) + { + case PROP_POPUP_MANAGER: + g_value_set_object (value, shell->popup_manager); + break; + case PROP_INITIAL_SCREEN: + g_value_set_object (value, shell->initial_screen); + break; + case PROP_INITIAL_MONITOR: + g_value_set_int (value, shell->initial_monitor); + break; + case PROP_DISPLAY: + g_value_set_object (value, shell->display); + break; + case PROP_UNIT: + g_value_set_int (value, shell->unit); + break; + case PROP_TITLE: + g_value_set_string (value, shell->title); + break; + case PROP_STATUS: + g_value_set_string (value, shell->status); + break; + case PROP_ICON: + g_value_set_object (value, shell->icon); + break; + case PROP_SHOW_ALL: + g_value_set_boolean (value, shell->show_all); + break; + case PROP_INFINITE_CANVAS: + g_value_set_boolean (value, + gimp_display_shell_get_infinite_canvas (shell)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_display_shell_unrealize (GtkWidget *widget) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget); + + if (shell->nav_popup) + gtk_widget_unrealize (shell->nav_popup); + + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +gimp_display_shell_unmap (GtkWidget *widget) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget); + + gimp_display_shell_selection_undraw (shell); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_display_shell_screen_changed (GtkWidget *widget, + GdkScreen *previous) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget); + + if (GTK_WIDGET_CLASS (parent_class)->screen_changed) + GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous); + + if (shell->display->config->monitor_res_from_gdk) + { + gimp_get_monitor_resolution (gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + &shell->monitor_xres, + &shell->monitor_yres); + } + else + { + shell->monitor_xres = shell->display->config->monitor_xres; + shell->monitor_yres = shell->display->config->monitor_yres; + } +} + +static gboolean +gimp_display_shell_popup_menu (GtkWidget *widget) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (widget); + + gimp_context_set_display (gimp_get_user_context (shell->display->gimp), + shell->display); + + gimp_ui_manager_ui_popup (shell->popup_manager, "/dummy-menubar/image-popup", + GTK_WIDGET (shell), + gimp_display_shell_menu_position, + shell->origin, + NULL, NULL); + + return TRUE; +} + +static void +gimp_display_shell_real_scaled (GimpDisplayShell *shell) +{ + GimpContext *user_context; + + if (! shell->display) + return; + + gimp_display_shell_title_update (shell); + + user_context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (user_context)) + { + gimp_display_shell_update_priority_rect (shell); + + gimp_ui_manager_update (shell->popup_manager, shell->display); + } +} + +static void +gimp_display_shell_real_scrolled (GimpDisplayShell *shell) +{ + GimpContext *user_context; + + if (! shell->display) + return; + + gimp_display_shell_title_update (shell); + + user_context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (user_context)) + { + gimp_display_shell_update_priority_rect (shell); + + } +} + +static void +gimp_display_shell_real_rotated (GimpDisplayShell *shell) +{ + GimpContext *user_context; + + if (! shell->display) + return; + + gimp_display_shell_title_update (shell); + + user_context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (user_context)) + { + gimp_display_shell_update_priority_rect (shell); + + gimp_ui_manager_update (shell->popup_manager, shell->display); + } +} + +static const guint8 * +gimp_display_shell_get_icc_profile (GimpColorManaged *managed, + gsize *len) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (managed); + GimpImage *image = gimp_display_get_image (shell->display); + + if (image) + return gimp_color_managed_get_icc_profile (GIMP_COLOR_MANAGED (image), len); + + return NULL; +} + +static GimpColorProfile * +gimp_display_shell_get_color_profile (GimpColorManaged *managed) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (managed); + GimpImage *image = gimp_display_get_image (shell->display); + + if (image) + return gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (image)); + + return NULL; +} + +static void +gimp_display_shell_profile_changed (GimpColorManaged *managed) +{ + GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (managed); + + gimp_display_shell_profile_update (shell); + gimp_display_shell_expose_full (shell); +} + +static void +gimp_display_shell_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + gimp_button_menu_position (GTK_WIDGET (data), menu, GTK_POS_RIGHT, x, y); +} + +static void +gimp_display_shell_zoom_button_callback (GimpDisplayShell *shell, + GtkWidget *zoom_button) +{ + shell->zoom_on_resize = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (zoom_button)); + + if (shell->zoom_on_resize && + gimp_display_shell_scale_image_is_within_viewport (shell, NULL, NULL)) + { + /* Implicitly make a View -> Fit Image in Window */ + gimp_display_shell_scale_fit_in (shell); + } +} + +static void +gimp_display_shell_sync_config (GimpDisplayShell *shell, + GimpDisplayConfig *config) +{ + gimp_config_sync (G_OBJECT (config->default_view), + G_OBJECT (shell->options), 0); + gimp_config_sync (G_OBJECT (config->default_fullscreen_view), + G_OBJECT (shell->fullscreen_options), 0); +} + +static void +gimp_display_shell_remove_overlay (GtkWidget *canvas, + GtkWidget *child, + GimpDisplayShell *shell) +{ + shell->children = g_list_remove (shell->children, child); +} + +static void +gimp_display_shell_transform_overlay (GimpDisplayShell *shell, + GtkWidget *child, + gdouble *x, + gdouble *y) +{ + GimpDisplayShellOverlay *overlay; + GtkRequisition requisition; + + overlay = g_object_get_data (G_OBJECT (child), "image-coords-overlay"); + + gimp_display_shell_transform_xy_f (shell, + overlay->image_x, + overlay->image_y, + x, y); + + gtk_widget_size_request (child, &requisition); + + switch (overlay->anchor) + { + case GIMP_HANDLE_ANCHOR_CENTER: + *x -= requisition.width / 2; + *y -= requisition.height / 2; + break; + + case GIMP_HANDLE_ANCHOR_NORTH: + *x -= requisition.width / 2; + *y += overlay->spacing_y; + break; + + case GIMP_HANDLE_ANCHOR_NORTH_WEST: + *x += overlay->spacing_x; + *y += overlay->spacing_y; + break; + + case GIMP_HANDLE_ANCHOR_NORTH_EAST: + *x -= requisition.width + overlay->spacing_x; + *y += overlay->spacing_y; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH: + *x -= requisition.width / 2; + *y -= requisition.height + overlay->spacing_y; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_WEST: + *x += overlay->spacing_x; + *y -= requisition.height + overlay->spacing_y; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_EAST: + *x -= requisition.width + overlay->spacing_x; + *y -= requisition.height + overlay->spacing_y; + break; + + case GIMP_HANDLE_ANCHOR_WEST: + *x += overlay->spacing_x; + *y -= requisition.height / 2; + break; + + case GIMP_HANDLE_ANCHOR_EAST: + *x -= requisition.width + overlay->spacing_x; + *y -= requisition.height / 2; + break; + } +} + + +/* public functions */ + +GtkWidget * +gimp_display_shell_new (GimpDisplay *display, + GimpUnit unit, + gdouble scale, + GimpUIManager *popup_manager, + GdkScreen *screen, + gint monitor) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY (display), NULL); + g_return_val_if_fail (GIMP_IS_UI_MANAGER (popup_manager), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + return g_object_new (GIMP_TYPE_DISPLAY_SHELL, + "popup-manager", popup_manager, + "initial-screen", screen, + "initial-monitor", monitor, + "display", display, + "unit", unit, + NULL); +} + +void +gimp_display_shell_add_overlay (GimpDisplayShell *shell, + GtkWidget *child, + gdouble image_x, + gdouble image_y, + GimpHandleAnchor anchor, + gint spacing_x, + gint spacing_y) +{ + GimpDisplayShellOverlay *overlay; + gdouble x, y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GTK_IS_WIDGET (shell)); + + overlay = g_new0 (GimpDisplayShellOverlay, 1); + + overlay->image_x = image_x; + overlay->image_y = image_y; + overlay->anchor = anchor; + overlay->spacing_x = spacing_x; + overlay->spacing_y = spacing_y; + + g_object_set_data_full (G_OBJECT (child), "image-coords-overlay", overlay, + (GDestroyNotify) g_free); + + shell->children = g_list_prepend (shell->children, child); + + gimp_display_shell_transform_overlay (shell, child, &x, &y); + + gimp_overlay_box_add_child (GIMP_OVERLAY_BOX (shell->canvas), child, 0.0, 0.0); + gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas), + child, x, y); +} + +void +gimp_display_shell_move_overlay (GimpDisplayShell *shell, + GtkWidget *child, + gdouble image_x, + gdouble image_y, + GimpHandleAnchor anchor, + gint spacing_x, + gint spacing_y) +{ + GimpDisplayShellOverlay *overlay; + gdouble x, y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GTK_IS_WIDGET (shell)); + + overlay = g_object_get_data (G_OBJECT (child), "image-coords-overlay"); + + g_return_if_fail (overlay != NULL); + + overlay->image_x = image_x; + overlay->image_y = image_y; + overlay->anchor = anchor; + overlay->spacing_x = spacing_x; + overlay->spacing_y = spacing_y; + + gimp_display_shell_transform_overlay (shell, child, &x, &y); + + gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas), + child, x, y); +} + +GimpImageWindow * +gimp_display_shell_get_window (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return GIMP_IMAGE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (shell), + GIMP_TYPE_IMAGE_WINDOW)); +} + +GimpStatusbar * +gimp_display_shell_get_statusbar (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return GIMP_STATUSBAR (shell->statusbar); +} + +GimpColorConfig * +gimp_display_shell_get_color_config (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return shell->color_config; +} + +void +gimp_display_shell_present (GimpDisplayShell *shell) +{ + GimpImageWindow *window; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + window = gimp_display_shell_get_window (shell); + + if (window) + { + gimp_image_window_set_active_shell (window, shell); + + gtk_window_present (GTK_WINDOW (window)); + } +} + +void +gimp_display_shell_reconnect (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_DISPLAY (shell->display)); + g_return_if_fail (gimp_display_get_image (shell->display) != NULL); + + if (shell->fill_idle_id) + { + g_source_remove (shell->fill_idle_id); + shell->fill_idle_id = 0; + } + + g_signal_emit (shell, display_shell_signals[RECONNECT], 0); + + gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell)); + + gimp_display_shell_scroll_clamp_and_update (shell); + + gimp_display_shell_scaled (shell); + + gimp_display_shell_expose_full (shell); +} + +static gboolean +gimp_display_shell_blink (GimpDisplayShell *shell) +{ + shell->blink_timeout_id = 0; + + if (shell->blink) + { + shell->blink = FALSE; + } + else + { + shell->blink = TRUE; + + shell->blink_timeout_id = + g_timeout_add (100, (GSourceFunc) gimp_display_shell_blink, shell); + } + + gimp_display_shell_expose_full (shell); + + return FALSE; +} + +void +gimp_display_shell_empty (GimpDisplayShell *shell) +{ + GimpContext *user_context; + GimpImageWindow *window; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_DISPLAY (shell->display)); + g_return_if_fail (gimp_display_get_image (shell->display) == NULL); + + window = gimp_display_shell_get_window (shell); + + if (shell->fill_idle_id) + { + g_source_remove (shell->fill_idle_id); + shell->fill_idle_id = 0; + } + + gimp_display_shell_selection_undraw (shell); + + gimp_display_shell_unset_cursor (shell); + + gimp_display_shell_filter_set (shell, NULL); + + gimp_display_shell_sync_config (shell, shell->display->config); + + gimp_display_shell_appearance_update (shell); + gimp_image_window_update_tabs (window); +#if 0 + gimp_help_set_help_data (shell->canvas, + _("Drop image files here to open them"), NULL); +#endif + + gimp_statusbar_empty (GIMP_STATUSBAR (shell->statusbar)); + + shell->flip_horizontally = FALSE; + shell->flip_vertically = FALSE; + shell->rotate_angle = 0.0; + gimp_display_shell_rotate_update_transform (shell); + + gimp_display_shell_expose_full (shell); + + user_context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (user_context)) + gimp_ui_manager_update (shell->popup_manager, shell->display); + + shell->blink_timeout_id = + g_timeout_add (1403230, (GSourceFunc) gimp_display_shell_blink, shell); +} + +static gboolean +gimp_display_shell_fill_idle (GimpDisplayShell *shell) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell)); + + shell->fill_idle_id = 0; + + if (GTK_IS_WINDOW (toplevel)) + { + gimp_display_shell_scale_shrink_wrap (shell, TRUE); + + gtk_window_present (GTK_WINDOW (toplevel)); + } + + return FALSE; +} + +void +gimp_display_shell_fill (GimpDisplayShell *shell, + GimpImage *image, + GimpUnit unit, + gdouble scale) +{ + GimpDisplayConfig *config; + GimpImageWindow *window; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GIMP_IS_DISPLAY (shell->display)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + config = shell->display->config; + window = gimp_display_shell_get_window (shell); + + shell->show_image = TRUE; + + shell->dot_for_dot = config->default_dot_for_dot; + + gimp_display_shell_set_unit (shell, unit); + gimp_display_shell_set_initial_scale (shell, scale, NULL, NULL); + gimp_display_shell_scale_update (shell); + + gimp_display_shell_sync_config (shell, config); + + gimp_image_window_suspend_keep_pos (window); + gimp_display_shell_appearance_update (shell); + gimp_image_window_resume_keep_pos (window); + + gimp_image_window_update_tabs (window); +#if 0 + gimp_help_set_help_data (shell->canvas, NULL, NULL); +#endif + + gimp_statusbar_fill (GIMP_STATUSBAR (shell->statusbar)); + + /* make sure a size-allocate always occurs, even when the rulers and + * scrollbars are hidden. see issue #4968. + */ + shell->size_allocate_center_image = TRUE; + gtk_widget_queue_resize (GTK_WIDGET (shell->canvas)); + + if (shell->blink_timeout_id) + { + g_source_remove (shell->blink_timeout_id); + shell->blink_timeout_id = 0; + } + + shell->fill_idle_id = + g_idle_add_full (GIMP_PRIORITY_DISPLAY_SHELL_FILL_IDLE, + (GSourceFunc) gimp_display_shell_fill_idle, shell, + NULL); + + gimp_display_shell_set_show_all (shell, config->default_show_all); +} + +void +gimp_display_shell_scaled (GimpDisplayShell *shell) +{ + GList *list; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_rotate_update_transform (shell); + + for (list = shell->children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + gdouble x, y; + + gimp_display_shell_transform_overlay (shell, child, &x, &y); + + gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas), + child, x, y); + } + + g_signal_emit (shell, display_shell_signals[SCALED], 0); +} + +void +gimp_display_shell_scrolled (GimpDisplayShell *shell) +{ + GList *list; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_rotate_update_transform (shell); + + for (list = shell->children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + gdouble x, y; + + gimp_display_shell_transform_overlay (shell, child, &x, &y); + + gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (shell->canvas), + child, x, y); + } + + g_signal_emit (shell, display_shell_signals[SCROLLED], 0); +} + +void +gimp_display_shell_rotated (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + gimp_display_shell_rotate_update_transform (shell); + + g_signal_emit (shell, display_shell_signals[ROTATED], 0); +} + +void +gimp_display_shell_set_unit (GimpDisplayShell *shell, + GimpUnit unit) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell->unit != unit) + { + shell->unit = unit; + + gimp_display_shell_rulers_update (shell); + + gimp_display_shell_scaled (shell); + + g_object_notify (G_OBJECT (shell), "unit"); + } +} + +GimpUnit +gimp_display_shell_get_unit (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), GIMP_UNIT_PIXEL); + + return shell->unit; +} + +gboolean +gimp_display_shell_snap_coords (GimpDisplayShell *shell, + GimpCoords *coords, + gint snap_offset_x, + gint snap_offset_y, + gint snap_width, + gint snap_height) +{ + GimpImage *image; + gboolean snap_to_guides = FALSE; + gboolean snap_to_grid = FALSE; + gboolean snap_to_canvas = FALSE; + gboolean snap_to_vectors = FALSE; + gboolean snapped = FALSE; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + g_return_val_if_fail (coords != NULL, FALSE); + + image = gimp_display_get_image (shell->display); + + if (gimp_display_shell_get_snap_to_guides (shell) && + gimp_image_get_guides (image)) + { + snap_to_guides = TRUE; + } + + if (gimp_display_shell_get_snap_to_grid (shell) && + gimp_image_get_grid (image)) + { + snap_to_grid = TRUE; + } + + snap_to_canvas = gimp_display_shell_get_snap_to_canvas (shell); + + if (gimp_display_shell_get_snap_to_vectors (shell) && + gimp_image_get_active_vectors (image)) + { + snap_to_vectors = TRUE; + } + + if (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors) + { + gint snap_distance; + gdouble tx, ty; + + snap_distance = shell->display->config->snap_distance; + + if (snap_width > 0 && snap_height > 0) + { + snapped = gimp_image_snap_rectangle (image, + coords->x + snap_offset_x, + coords->y + snap_offset_y, + coords->x + snap_offset_x + + snap_width, + coords->y + snap_offset_y + + snap_height, + &tx, + &ty, + FUNSCALEX (shell, snap_distance), + FUNSCALEY (shell, snap_distance), + snap_to_guides, + snap_to_grid, + snap_to_canvas, + snap_to_vectors); + } + else + { + snapped = gimp_image_snap_point (image, + coords->x + snap_offset_x, + coords->y + snap_offset_y, + &tx, + &ty, + FUNSCALEX (shell, snap_distance), + FUNSCALEY (shell, snap_distance), + snap_to_guides, + snap_to_grid, + snap_to_canvas, + snap_to_vectors); + } + + if (snapped) + { + coords->x = tx - snap_offset_x; + coords->y = ty - snap_offset_y; + } + } + + return snapped; +} + +gboolean +gimp_display_shell_mask_bounds (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *width, + gint *height) +{ + GimpImage *image; + GimpLayer *layer; + gint x1, y1; + gint x2, y2; + gdouble x1_f, y1_f; + gdouble x2_f, y2_f; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + g_return_val_if_fail (x != NULL, FALSE); + g_return_val_if_fail (y != NULL, FALSE); + g_return_val_if_fail (width != NULL, FALSE); + g_return_val_if_fail (height != NULL, FALSE); + + image = gimp_display_get_image (shell->display); + + /* If there is a floating selection, handle things differently */ + if ((layer = gimp_image_get_floating_selection (image))) + { + gint fs_x; + gint fs_y; + gint fs_width; + gint fs_height; + + gimp_item_get_offset (GIMP_ITEM (layer), &fs_x, &fs_y); + fs_width = gimp_item_get_width (GIMP_ITEM (layer)); + fs_height = gimp_item_get_height (GIMP_ITEM (layer)); + + if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)), + x, y, width, height)) + { + *x = fs_x; + *y = fs_y; + *width = fs_width; + *height = fs_height; + } + else + { + gimp_rectangle_union (*x, *y, *width, *height, + fs_x, fs_y, fs_width, fs_height, + x, y, width, height); + } + } + else if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)), + x, y, width, height)) + { + return FALSE; + } + + x1 = *x; + y1 = *y; + x2 = *x + *width; + y2 = *y + *height; + + gimp_display_shell_transform_bounds (shell, + x1, y1, x2, y2, + &x1_f, &y1_f, &x2_f, &y2_f); + + /* Make sure the extents are within bounds */ + x1 = CLAMP (floor (x1_f), 0, shell->disp_width); + y1 = CLAMP (floor (y1_f), 0, shell->disp_height); + x2 = CLAMP (ceil (x2_f), 0, shell->disp_width); + y2 = CLAMP (ceil (y2_f), 0, shell->disp_height); + + *x = x1; + *y = y1; + *width = x2 - x1; + *height = y2 - y1; + + return (*width > 0) && (*height > 0); +} + +void +gimp_display_shell_set_show_image (GimpDisplayShell *shell, + gboolean show_image) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (show_image != shell->show_image) + { + shell->show_image = show_image; + + gimp_display_shell_expose_full (shell); + } +} + +void +gimp_display_shell_set_show_all (GimpDisplayShell *shell, + gboolean show_all) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (show_all != shell->show_all) + { + shell->show_all = show_all; + + if (shell->display && gimp_display_get_image (shell->display)) + { + GimpImage *image = gimp_display_get_image (shell->display); + GimpContext *user_context; + + if (show_all) + gimp_image_inc_show_all_count (image); + else + gimp_image_dec_show_all_count (image); + + gimp_image_flush (image); + + gimp_display_update_bounding_box (shell->display); + + gimp_display_shell_update_show_canvas (shell); + + gimp_display_shell_scroll_clamp_and_update (shell); + gimp_display_shell_scrollbars_update (shell); + + gimp_display_shell_expose_full (shell); + + user_context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (user_context)) + { + gimp_display_shell_update_priority_rect (shell); + + gimp_ui_manager_update (shell->popup_manager, shell->display); + } + } + + g_object_notify (G_OBJECT (shell), "show-all"); + g_object_notify (G_OBJECT (shell), "infinite-canvas"); + } +} + + +GimpPickable * +gimp_display_shell_get_pickable (GimpDisplayShell *shell) +{ + GimpImage *image; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + image = gimp_display_get_image (shell->display); + + if (image) + { + if (! shell->show_all) + return GIMP_PICKABLE (image); + else + return GIMP_PICKABLE (gimp_image_get_projection (image)); + } + + return NULL; +} + +GimpPickable * +gimp_display_shell_get_canvas_pickable (GimpDisplayShell *shell) +{ + GimpImage *image; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + image = gimp_display_get_image (shell->display); + + if (image) + { + if (! gimp_display_shell_get_infinite_canvas (shell)) + return GIMP_PICKABLE (image); + else + return GIMP_PICKABLE (gimp_image_get_projection (image)); + } + + return NULL; +} + +GeglRectangle +gimp_display_shell_get_bounding_box (GimpDisplayShell *shell) +{ + GeglRectangle bounding_box = {}; + GimpImage *image; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), bounding_box); + + image = gimp_display_get_image (shell->display); + + if (image) + { + if (! shell->show_all) + { + bounding_box.width = gimp_image_get_width (image); + bounding_box.height = gimp_image_get_height (image); + } + else + { + bounding_box = gimp_projectable_get_bounding_box ( + GIMP_PROJECTABLE (image)); + } + } + + return bounding_box; +} + +gboolean +gimp_display_shell_get_infinite_canvas (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE); + + return shell->show_all && + ! gimp_display_shell_get_padding_in_show_all (shell); +} + +void +gimp_display_shell_update_priority_rect (GimpDisplayShell *shell) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + image = gimp_display_get_image (shell->display); + + if (image) + { + GimpProjection *projection = gimp_image_get_projection (image); + gint x, y; + gint width, height; + + gimp_display_shell_untransform_viewport (shell, ! shell->show_all, + &x, &y, &width, &height); + gimp_projection_set_priority_rect (projection, x, y, width, height); + } +} + +void +gimp_display_shell_flush (GimpDisplayShell *shell, + gboolean now) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (now) + { + gdk_window_process_updates (gtk_widget_get_window (shell->canvas), + FALSE); + } + else + { + GimpImageWindow *window = gimp_display_shell_get_window (shell); + GimpContext *context; + + gimp_display_shell_title_update (shell); + + gimp_canvas_layer_boundary_set_layer (GIMP_CANVAS_LAYER_BOUNDARY (shell->layer_boundary), + gimp_image_get_active_layer (gimp_display_get_image (shell->display))); + + gimp_canvas_canvas_boundary_set_image (GIMP_CANVAS_CANVAS_BOUNDARY (shell->canvas_boundary), + gimp_display_get_image (shell->display)); + + if (window && gimp_image_window_get_active_shell (window) == shell) + { + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_update (manager, shell->display); + } + + context = gimp_get_user_context (shell->display->gimp); + + if (shell->display == gimp_context_get_display (context)) + { + gimp_ui_manager_update (shell->popup_manager, shell->display); + } + } +} + +/** + * gimp_display_shell_pause: + * @shell: a display shell + * + * This function increments the pause count or the display shell. + * If it was zero coming in, then the function pauses the active tool, + * so that operations on the display can take place without corrupting + * anything that the tool has drawn. It "undraws" the current tool + * drawing, and must be followed by gimp_display_shell_resume() after + * the operation in question is completed. + **/ +void +gimp_display_shell_pause (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + shell->paused_count++; + + if (shell->paused_count == 1) + { + /* pause the currently active tool */ + tool_manager_control_active (shell->display->gimp, + GIMP_TOOL_ACTION_PAUSE, + shell->display); + } +} + +/** + * gimp_display_shell_resume: + * @shell: a display shell + * + * This function decrements the pause count for the display shell. + * If this brings it to zero, then the current tool is resumed. + * It is an error to call this function without having previously + * called gimp_display_shell_pause(). + **/ +void +gimp_display_shell_resume (GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (shell->paused_count > 0); + + shell->paused_count--; + + if (shell->paused_count == 0) + { + /* start the currently active tool */ + tool_manager_control_active (shell->display->gimp, + GIMP_TOOL_ACTION_RESUME, + shell->display); + } +} + +/** + * gimp_display_shell_set_highlight: + * @shell: a #GimpDisplayShell + * @highlight: a rectangle in image coordinates that should be brought out + * @opacity: how much to hide the unselected area + * + * This function sets an area of the image that should be + * accentuated. The actual implementation is to dim all pixels outside + * this rectangle. Passing %NULL for @highlight unsets the rectangle. + **/ +void +gimp_display_shell_set_highlight (GimpDisplayShell *shell, + const GdkRectangle *highlight, + gdouble opacity) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (highlight) + { + gimp_canvas_item_begin_change (shell->passe_partout); + + gimp_canvas_rectangle_set (shell->passe_partout, + highlight->x, + highlight->y, + highlight->width, + highlight->height); + g_object_set (shell->passe_partout, "opacity", opacity, NULL); + + gimp_canvas_item_set_visible (shell->passe_partout, TRUE); + + gimp_canvas_item_end_change (shell->passe_partout); + } + else + { + gimp_canvas_item_set_visible (shell->passe_partout, FALSE); + } +} + +/** + * gimp_display_shell_set_mask: + * @shell: a #GimpDisplayShell + * @mask: a #GimpDrawable (1 byte per pixel) + * @color: the color to use for drawing the mask + * @inverted: #TRUE if the mask should be drawn inverted + * + * Previews a mask originating at offset_x, offset_x. Depending on + * @inverted, pixels that are selected or not selected are tinted with + * the given color. + **/ +void +gimp_display_shell_set_mask (GimpDisplayShell *shell, + GeglBuffer *mask, + gint offset_x, + gint offset_y, + const GimpRGB *color, + gboolean inverted) +{ + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (mask == NULL || GEGL_IS_BUFFER (mask)); + g_return_if_fail (mask == NULL || color != NULL); + + if (mask) + g_object_ref (mask); + + if (shell->mask) + g_object_unref (shell->mask); + + shell->mask = mask; + + shell->mask_offset_x = offset_x; + shell->mask_offset_y = offset_y; + + if (mask) + shell->mask_color = *color; + + shell->mask_inverted = inverted; + + gimp_display_shell_expose_full (shell); +} diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h new file mode 100644 index 0000000..1da9d8f --- /dev/null +++ b/app/display/gimpdisplayshell.h @@ -0,0 +1,340 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_SHELL_H__ +#define __GIMP_DISPLAY_SHELL_H__ + + +/* Apply to a float the same rounding mode used in the renderer */ +#define PROJ_ROUND(coord) ((gint) RINT (coord)) +#define PROJ_ROUND64(coord) ((gint64) RINT (coord)) + +/* scale values */ +#define SCALEX(s,x) PROJ_ROUND ((x) * (s)->scale_x) +#define SCALEY(s,y) PROJ_ROUND ((y) * (s)->scale_y) + +/* unscale values */ +#define UNSCALEX(s,x) ((gint) ((x) / (s)->scale_x)) +#define UNSCALEY(s,y) ((gint) ((y) / (s)->scale_y)) +/* (and float-returning versions) */ +#define FUNSCALEX(s,x) ((x) / (s)->scale_x) +#define FUNSCALEY(s,y) ((y) / (s)->scale_y) + + +#define GIMP_TYPE_DISPLAY_SHELL (gimp_display_shell_get_type ()) +#define GIMP_DISPLAY_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DISPLAY_SHELL, GimpDisplayShell)) +#define GIMP_DISPLAY_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DISPLAY_SHELL, GimpDisplayShellClass)) +#define GIMP_IS_DISPLAY_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DISPLAY_SHELL)) +#define GIMP_IS_DISPLAY_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DISPLAY_SHELL)) +#define GIMP_DISPLAY_SHELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DISPLAY_SHELL, GimpDisplayShellClass)) + + +typedef struct _GimpDisplayShellClass GimpDisplayShellClass; + +struct _GimpDisplayShell +{ + GtkEventBox parent_instance; + + GimpDisplay *display; + + GimpUIManager *popup_manager; + GdkScreen *initial_screen; + gint initial_monitor; + + GimpDisplayOptions *options; + GimpDisplayOptions *fullscreen_options; + GimpDisplayOptions *no_image_options; + + GimpUnit unit; + + gint offset_x; /* offset of display image */ + gint offset_y; + + gdouble scale_x; /* horizontal scale factor */ + gdouble scale_y; /* vertical scale factor */ + + gboolean flip_horizontally; + gboolean flip_vertically; + gdouble rotate_angle; + cairo_matrix_t *rotate_transform; + cairo_matrix_t *rotate_untransform; + + gdouble monitor_xres; + gdouble monitor_yres; + gboolean dot_for_dot; /* ignore monitor resolution */ + + GimpZoomModel *zoom; + + gdouble last_scale; /* scale used when reverting zoom */ + guint last_scale_time; /* time when last_scale was set */ + gint last_offset_x; /* offsets used when reverting zoom */ + gint last_offset_y; + + gdouble other_scale; /* scale factor entered in Zoom->Other*/ + + gint disp_width; /* width of drawing area */ + gint disp_height; /* height of drawing area */ + + gboolean proximity; /* is a device in proximity */ + + gboolean show_image; /* whether to show the image */ + + gboolean show_all; /* show the entire image */ + + Selection *selection; /* Selection (marching ants) */ + + GList *children; + + GtkWidget *canvas; /* GimpCanvas widget */ + + GtkAdjustment *hsbdata; /* adjustments */ + GtkAdjustment *vsbdata; + GtkWidget *hsb; /* scroll bars */ + GtkWidget *vsb; + + GtkWidget *hrule; /* rulers */ + GtkWidget *vrule; + + GtkWidget *origin; /* NW: origin */ + GtkWidget *quick_mask_button;/* SW: quick mask button */ + GtkWidget *zoom_button; /* NE: zoom toggle button */ + GtkWidget *nav_ebox; /* SE: navigation event box */ + + GtkWidget *statusbar; /* statusbar */ + + GimpCanvasItem *canvas_item; /* items drawn on the canvas */ + GimpCanvasItem *unrotated_item; /* unrotated items for e.g. cursor */ + GimpCanvasItem *passe_partout; /* item for the highlight */ + GimpCanvasItem *preview_items; /* item for previews */ + GimpCanvasItem *vectors; /* item proxy of vectors */ + GimpCanvasItem *grid; /* item proxy of the grid */ + GimpCanvasItem *guides; /* item proxies of guides */ + GimpCanvasItem *sample_points; /* item proxies of sample points */ + GimpCanvasItem *canvas_boundary; /* item for the cabvas boundary */ + GimpCanvasItem *layer_boundary; /* item for the layer boundary */ + GimpCanvasItem *tool_items; /* tools items, below the cursor */ + GimpCanvasItem *cursor; /* item for the software cursor */ + + guint title_idle_id; /* title update idle ID */ + gchar *title; /* current title */ + gchar *status; /* current default statusbar content */ + + gint icon_size; /* size of the icon pixbuf */ + gint icon_size_small; /* size of the icon's wilber pixbuf */ + guint icon_idle_id; /* ID of the idle-function */ + GdkPixbuf *icon; /* icon */ + + guint fill_idle_id; /* display_shell_fill() idle ID */ + + GimpHandedness cursor_handedness;/* Handedness for cursor display */ + GimpCursorType current_cursor; /* Currently installed main cursor */ + GimpToolCursorType tool_cursor; /* Current Tool cursor */ + GimpCursorModifier cursor_modifier; /* Cursor modifier (plus, minus, ...) */ + + GimpCursorType override_cursor; /* Overriding cursor */ + gboolean using_override_cursor; + gboolean draw_cursor; /* should we draw software cursor ? */ + + GtkWidget *close_dialog; /* close dialog */ + GtkWidget *scale_dialog; /* scale (zoom) dialog */ + GtkWidget *rotate_dialog; /* rotate dialog */ + GtkWidget *nav_popup; /* navigation popup */ + + GimpColorConfig *color_config; /* color management settings */ + gboolean color_config_set; /* settings changed from defaults */ + + GimpColorTransform *profile_transform; + GeglBuffer *profile_buffer; /* buffer for profile transform */ + guchar *profile_data; /* profile_buffer's pixels */ + gint profile_stride; /* profile_buffer's stride */ + + GimpColorDisplayStack *filter_stack; /* color display conversion stuff */ + guint filter_idle_id; + + GimpColorTransform *filter_transform; + const Babl *filter_format; /* filter_buffer's format */ + GeglBuffer *filter_buffer; /* buffer for display filters */ + guchar *filter_data; /* filter_buffer's pixels */ + gint filter_stride; /* filter_buffer's stride */ + + GimpDisplayXfer *xfer; /* manages image buffer transfers */ + cairo_surface_t *mask_surface; /* buffer for rendering the mask */ + cairo_pattern_t *checkerboard; /* checkerboard pattern */ + + gint paused_count; + + GimpTreeHandler *vectors_freeze_handler; + GimpTreeHandler *vectors_thaw_handler; + GimpTreeHandler *vectors_visible_handler; + + gboolean zoom_on_resize; + + gboolean size_allocate_from_configure_event; + gboolean size_allocate_center_image; + + /* the state of gimp_display_shell_tool_events() */ + gboolean pointer_grabbed; + guint32 pointer_grab_time; + + gboolean keyboard_grabbed; + guint32 keyboard_grab_time; + + gboolean inferior_ignore_mode; + + /* Two states are possible when the shell is grabbed: it can be + * grabbed with space (or space+button1 which is the same state), + * then if space is released but button1 was still pressed, we wait + * for button1 to be released as well. + */ + gboolean space_release_pending; + gboolean button1_release_pending; + const gchar *space_shaded_tool; + + gboolean scrolling; + gint scroll_start_x; + gint scroll_start_y; + gint scroll_last_x; + gint scroll_last_y; + gboolean rotating; + gdouble rotate_drag_angle; + gboolean scaling; + gpointer scroll_info; + gboolean layer_picking; + GimpLayer *picked_layer; + + GeglBuffer *mask; + gint mask_offset_x; + gint mask_offset_y; + GimpRGB mask_color; + gboolean mask_inverted; + + GimpMotionBuffer *motion_buffer; + + GQueue *zoom_focus_pointer_queue; + + gboolean blink; + guint blink_timeout_id; +}; + +struct _GimpDisplayShellClass +{ + GtkEventBoxClass parent_class; + + void (* scaled) (GimpDisplayShell *shell); + void (* scrolled) (GimpDisplayShell *shell); + void (* rotated) (GimpDisplayShell *shell); + void (* reconnect) (GimpDisplayShell *shell); +}; + + +GType gimp_display_shell_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_display_shell_new (GimpDisplay *display, + GimpUnit unit, + gdouble scale, + GimpUIManager *popup_manager, + GdkScreen *screen, + gint monitor); + +void gimp_display_shell_add_overlay (GimpDisplayShell *shell, + GtkWidget *child, + gdouble image_x, + gdouble image_y, + GimpHandleAnchor anchor, + gint spacing_x, + gint spacing_y); +void gimp_display_shell_move_overlay (GimpDisplayShell *shell, + GtkWidget *child, + gdouble image_x, + gdouble image_y, + GimpHandleAnchor anchor, + gint spacing_x, + gint spacing_y); + +GimpImageWindow * gimp_display_shell_get_window (GimpDisplayShell *shell); +GimpStatusbar * gimp_display_shell_get_statusbar (GimpDisplayShell *shell); + +GimpColorConfig * gimp_display_shell_get_color_config + (GimpDisplayShell *shell); + +void gimp_display_shell_present (GimpDisplayShell *shell); + +void gimp_display_shell_reconnect (GimpDisplayShell *shell); + +void gimp_display_shell_empty (GimpDisplayShell *shell); +void gimp_display_shell_fill (GimpDisplayShell *shell, + GimpImage *image, + GimpUnit unit, + gdouble scale); + +void gimp_display_shell_scaled (GimpDisplayShell *shell); +void gimp_display_shell_scrolled (GimpDisplayShell *shell); +void gimp_display_shell_rotated (GimpDisplayShell *shell); + +void gimp_display_shell_set_unit (GimpDisplayShell *shell, + GimpUnit unit); +GimpUnit gimp_display_shell_get_unit (GimpDisplayShell *shell); + +gboolean gimp_display_shell_snap_coords (GimpDisplayShell *shell, + GimpCoords *coords, + gint snap_offset_x, + gint snap_offset_y, + gint snap_width, + gint snap_height); + +gboolean gimp_display_shell_mask_bounds (GimpDisplayShell *shell, + gint *x, + gint *y, + gint *width, + gint *height); + +void gimp_display_shell_set_show_image + (GimpDisplayShell *shell, + gboolean show_image); + +void gimp_display_shell_set_show_all (GimpDisplayShell *shell, + gboolean show_all); + +GimpPickable * gimp_display_shell_get_pickable (GimpDisplayShell *shell); +GimpPickable * gimp_display_shell_get_canvas_pickable + (GimpDisplayShell *shell); +GeglRectangle gimp_display_shell_get_bounding_box + (GimpDisplayShell *shell); +gboolean gimp_display_shell_get_infinite_canvas + (GimpDisplayShell *shell); + +void gimp_display_shell_update_priority_rect + (GimpDisplayShell *shell); + +void gimp_display_shell_flush (GimpDisplayShell *shell, + gboolean now); + +void gimp_display_shell_pause (GimpDisplayShell *shell); +void gimp_display_shell_resume (GimpDisplayShell *shell); + +void gimp_display_shell_set_highlight (GimpDisplayShell *shell, + const GdkRectangle *highlight, + double opacity); +void gimp_display_shell_set_mask (GimpDisplayShell *shell, + GeglBuffer *mask, + gint offset_x, + gint offset_y, + const GimpRGB *color, + gboolean inverted); + + +#endif /* __GIMP_DISPLAY_SHELL_H__ */ diff --git a/app/display/gimpdisplayxfer.c b/app/display/gimpdisplayxfer.c new file mode 100644 index 0000000..1dcd13c --- /dev/null +++ b/app/display/gimpdisplayxfer.c @@ -0,0 +1,289 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "gimpdisplayxfer.h" + + +#define NUM_PAGES 2 + +typedef struct _RTree RTree; +typedef struct _RTreeNode RTreeNode; + +struct _RTreeNode +{ + RTreeNode *children[2]; + RTreeNode *next; + gint x, y, w, h; +}; + +struct _RTree +{ + RTreeNode root; + RTreeNode *available; +}; + +struct _GimpDisplayXfer +{ + /* track subregions of render_surface for efficient uploads */ + RTree rtree; + cairo_surface_t *render_surface[NUM_PAGES]; + gint page; +}; + + +gint GIMP_DISPLAY_RENDER_BUF_WIDTH = 256; +gint GIMP_DISPLAY_RENDER_BUF_HEIGHT = 256; + + +static RTreeNode * +rtree_node_create (RTree *rtree, + RTreeNode **prev, + gint x, + gint y, + gint w, + gint h) +{ + RTreeNode *node; + + gimp_assert (x >= 0 && x+w <= rtree->root.w); + gimp_assert (y >= 0 && y+h <= rtree->root.h); + + if (w <= 0 || h <= 0) + return NULL; + + node = g_slice_alloc (sizeof (*node)); + + node->children[0] = NULL; + node->children[1] = NULL; + node->x = x; + node->y = y; + node->w = w; + node->h = h; + + node->next = *prev; + *prev = node; + + return node; +} + +static void +rtree_node_destroy (RTree *rtree, + RTreeNode *node) +{ + gint i; + + for (i = 0; i < 2; i++) + { + if (node->children[i]) + rtree_node_destroy (rtree, node->children[i]); + } + + g_slice_free (RTreeNode, node); +} + +static RTreeNode * +rtree_node_insert (RTree *rtree, + RTreeNode **prev, + RTreeNode *node, + gint w, + gint h) +{ + *prev = node->next; + + if (((node->w - w) | (node->h - h)) > 1) + { + gint ww = node->w - w; + gint hh = node->h - h; + + if (ww >= hh) + { + node->children[0] = rtree_node_create (rtree, prev, + node->x + w, node->y, + ww, node->h); + node->children[1] = rtree_node_create (rtree, prev, + node->x, node->y + h, + w, hh); + } + else + { + node->children[0] = rtree_node_create (rtree, prev, + node->x, node->y + h, + node->w, hh); + node->children[1] = rtree_node_create (rtree, prev, + node->x + w, node->y, + ww, h); + } + } + + return node; +} + +static RTreeNode * +rtree_insert (RTree *rtree, + gint w, + gint h) +{ + RTreeNode *node, **prev; + + for (prev = &rtree->available; (node = *prev); prev = &node->next) + if (node->w >= w && node->h >= h) + return rtree_node_insert (rtree, prev, node, w, h); + + return NULL; +} + +static void +rtree_init (RTree *rtree, + gint w, + gint h) +{ + rtree->root.x = 0; + rtree->root.y = 0; + rtree->root.w = w; + rtree->root.h = h; + rtree->root.children[0] = NULL; + rtree->root.children[1] = NULL; + rtree->root.next = NULL; + rtree->available = &rtree->root; +} + +static void +rtree_reset (RTree *rtree) +{ + gint i; + + for (i = 0; i < 2; i++) + { + if (rtree->root.children[i] == NULL) + continue; + + rtree_node_destroy (rtree, rtree->root.children[i]); + rtree->root.children[i] = NULL; + } + + rtree->root.next = NULL; + rtree->available = &rtree->root; +} + +static void +xfer_destroy (void *data) +{ + GimpDisplayXfer *xfer = data; + gint i; + + for (i = 0; i < NUM_PAGES; i++) + cairo_surface_destroy (xfer->render_surface[i]); + + rtree_reset (&xfer->rtree); + g_free (xfer); +} + +GimpDisplayXfer * +gimp_display_xfer_realize (GtkWidget *widget) +{ + GdkScreen *screen; + GimpDisplayXfer *xfer; + const gchar *env; + + env = g_getenv ("GIMP_DISPLAY_RENDER_BUF_SIZE"); + if (env) + { + gint width = atoi (env); + gint height = width; + + env = strchr (env, 'x'); + if (env) + height = atoi (env + 1); + + if (width > 0 && width <= 8192 && + height > 0 && height <= 8192) + { + GIMP_DISPLAY_RENDER_BUF_WIDTH = width; + GIMP_DISPLAY_RENDER_BUF_HEIGHT = height; + } + } + + screen = gtk_widget_get_screen (widget); + xfer = g_object_get_data (G_OBJECT (screen), "gimp-display-xfer"); + + if (xfer == NULL) + { + cairo_t *cr; + gint w = GIMP_DISPLAY_RENDER_BUF_WIDTH; + gint h = GIMP_DISPLAY_RENDER_BUF_HEIGHT; + int n; + + xfer = g_new (GimpDisplayXfer, 1); + rtree_init (&xfer->rtree, w, h); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + for (n = 0; n < NUM_PAGES; n++) + { + xfer->render_surface[n] = + cairo_surface_create_similar_image (cairo_get_target (cr), + CAIRO_FORMAT_ARGB32, w, h); + cairo_surface_mark_dirty (xfer->render_surface[n]); + } + cairo_destroy (cr); + xfer->page = 0; + + g_object_set_data_full (G_OBJECT (screen), + "gimp-display-xfer", + xfer, xfer_destroy); + } + + return xfer; +} + +cairo_surface_t * +gimp_display_xfer_get_surface (GimpDisplayXfer *xfer, + gint w, + gint h, + gint *src_x, + gint *src_y) +{ + RTreeNode *node; + + gimp_assert (w <= GIMP_DISPLAY_RENDER_BUF_WIDTH && + h <= GIMP_DISPLAY_RENDER_BUF_HEIGHT); + + node = rtree_insert (&xfer->rtree, w, h); + if (node == NULL) + { + xfer->page = (xfer->page + 1) % NUM_PAGES; + cairo_surface_flush (xfer->render_surface[xfer->page]); + rtree_reset (&xfer->rtree); + cairo_surface_mark_dirty (xfer->render_surface[xfer->page]); /* XXX */ + node = rtree_insert (&xfer->rtree, w, h); + gimp_assert (node != NULL); + } + + *src_x = node->x; + *src_y = node->y; + + return xfer->render_surface[xfer->page]; +} diff --git a/app/display/gimpdisplayxfer.h b/app/display/gimpdisplayxfer.h new file mode 100644 index 0000000..20656b9 --- /dev/null +++ b/app/display/gimpdisplayxfer.h @@ -0,0 +1,37 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_DISPLAY_XFER_H__ +#define __GIMP_DISPLAY_XFER_H__ + + +extern gint GIMP_DISPLAY_RENDER_BUF_WIDTH; +extern gint GIMP_DISPLAY_RENDER_BUF_HEIGHT; + +#define GIMP_DISPLAY_RENDER_MAX_SCALE 4.0 + + +GimpDisplayXfer * gimp_display_xfer_realize (GtkWidget *widget); + +cairo_surface_t * gimp_display_xfer_get_surface (GimpDisplayXfer *xfer, + gint w, + gint h, + gint *src_x, + gint *src_y); + + +#endif /* __GIMP_DISPLAY_XFER_H__ */ diff --git a/app/display/gimpimagewindow.c b/app/display/gimpimagewindow.c new file mode 100644 index 0000000..fd5c501 --- /dev/null +++ b/app/display/gimpimagewindow.c @@ -0,0 +1,2428 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#ifdef G_OS_WIN32 +#include <windef.h> +#include <winbase.h> +#include <windows.h> +#endif + +#ifdef GDK_WINDOWING_QUARTZ +#import <AppKit/AppKit.h> +#include <gdk/gdkquartz.h> +#endif /* !GDK_WINDOWING_QUARTZ */ + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpprogress.h" +#include "core/gimpcontainer.h" + +#include "widgets/gimpactiongroup.h" +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpdock.h" +#include "widgets/gimpdockbook.h" +#include "widgets/gimpdockcolumns.h" +#include "widgets/gimpdockcontainer.h" +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpmenufactory.h" +#include "widgets/gimpsessioninfo.h" +#include "widgets/gimpsessioninfo-aux.h" +#include "widgets/gimpsessionmanaged.h" +#include "widgets/gimpsessioninfo-dock.h" +#include "widgets/gimptoolbox.h" +#include "widgets/gimpuimanager.h" +#include "widgets/gimpview.h" +#include "widgets/gimpviewrenderer.h" +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplay.h" +#include "gimpdisplay-foreach.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-close.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-tool-events.h" +#include "gimpdisplayshell-transform.h" +#include "gimpimagewindow.h" +#include "gimpstatusbar.h" + +#include "gimp-log.h" +#include "gimp-priorities.h" + +#include "gimp-intl.h" + + +#define GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID "gimp-empty-image-window" +#define GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID "gimp-single-image-window" + +/* The width of the left and right dock areas */ +#define GIMP_IMAGE_WINDOW_LEFT_DOCKS_WIDTH "left-docks-width" +#define GIMP_IMAGE_WINDOW_RIGHT_DOCKS_WIDTH "right-docks-width" + +/* deprecated property: GtkPaned position of the right docks area */ +#define GIMP_IMAGE_WINDOW_RIGHT_DOCKS_POS "right-docks-position" + +/* Whether the window's maximized or not */ +#define GIMP_IMAGE_WINDOW_MAXIMIZED "maximized" + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 +#define NSWindowCollectionBehaviorFullScreenAuxiliary (1 << 8) +#endif + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_DIALOG_FACTORY, + PROP_INITIAL_SCREEN, + PROP_INITIAL_MONITOR +}; + + +typedef struct _GimpImageWindowPrivate GimpImageWindowPrivate; + +struct _GimpImageWindowPrivate +{ + Gimp *gimp; + GimpUIManager *menubar_manager; + GimpDialogFactory *dialog_factory; + + GList *shells; + GimpDisplayShell *active_shell; + + GtkWidget *main_vbox; + GtkWidget *menubar; + GtkWidget *hbox; + GtkWidget *left_hpane; + GtkWidget *left_docks; + GtkWidget *right_hpane; + GtkWidget *notebook; + GtkWidget *right_docks; + + GdkWindowState window_state; + + const gchar *entry_id; + + GdkScreen *initial_screen; + gint initial_monitor; + + gint suspend_keep_pos; + + gint update_ui_manager_idle_id; +}; + +typedef struct +{ + gint canvas_x; + gint canvas_y; + gint window_x; + gint window_y; +} PosCorrectionData; + + +#define GIMP_IMAGE_WINDOW_GET_PRIVATE(window) \ + ((GimpImageWindowPrivate *) gimp_image_window_get_instance_private ((GimpImageWindow *) (window))) + + +/* local function prototypes */ + +static void gimp_image_window_dock_container_iface_init + (GimpDockContainerInterface + *iface); +static void gimp_image_window_session_managed_iface_init + (GimpSessionManagedInterface + *iface); +static void gimp_image_window_constructed (GObject *object); +static void gimp_image_window_dispose (GObject *object); +static void gimp_image_window_finalize (GObject *object); +static void gimp_image_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_image_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_image_window_map (GtkWidget *widget); +static gboolean gimp_image_window_delete_event (GtkWidget *widget, + GdkEventAny *event); +static gboolean gimp_image_window_configure_event (GtkWidget *widget, + GdkEventConfigure *event); +static gboolean gimp_image_window_window_state_event (GtkWidget *widget, + GdkEventWindowState *event); +static void gimp_image_window_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_image_window_monitor_changed (GimpWindow *window, + GdkScreen *screen, + gint monitor); + +static GList * gimp_image_window_get_docks (GimpDockContainer *dock_container); +static GimpDialogFactory * + gimp_image_window_dock_container_get_dialog_factory + (GimpDockContainer *dock_container); +static GimpUIManager * + gimp_image_window_dock_container_get_ui_manager + (GimpDockContainer *dock_container); +static void gimp_image_window_add_dock (GimpDockContainer *dock_container, + GimpDock *dock, + GimpSessionInfoDock *dock_info); +static GimpAlignmentType + gimp_image_window_get_dock_side (GimpDockContainer *dock_container, + GimpDock *dock); +static GList * gimp_image_window_get_aux_info (GimpSessionManaged *session_managed); +static void gimp_image_window_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info); + +static void gimp_image_window_config_notify (GimpImageWindow *window, + GParamSpec *pspec, + GimpGuiConfig *config); +static void gimp_image_window_session_clear (GimpImageWindow *window); +static void gimp_image_window_session_apply (GimpImageWindow *window, + const gchar *entry_id, + GdkScreen *screen, + gint monitor); +static void gimp_image_window_session_update (GimpImageWindow *window, + GimpDisplay *new_display, + const gchar *new_entry_id, + GdkScreen *screen, + gint monitor); +static const gchar * + gimp_image_window_config_to_entry_id (GimpGuiConfig *config); +static void gimp_image_window_show_tooltip (GimpUIManager *manager, + const gchar *tooltip, + GimpImageWindow *window); +static void gimp_image_window_hide_tooltip (GimpUIManager *manager, + GimpImageWindow *window); +static gboolean gimp_image_window_update_ui_manager_idle + (GimpImageWindow *window); +static void gimp_image_window_update_ui_manager (GimpImageWindow *window); + +static void gimp_image_window_shell_size_allocate (GimpDisplayShell *shell, + GtkAllocation *allocation, + PosCorrectionData *data); +static gboolean gimp_image_window_shell_events (GtkWidget *widget, + GdkEvent *event, + GimpImageWindow *window); + +static void gimp_image_window_switch_page (GtkNotebook *notebook, + gpointer page, + gint page_num, + GimpImageWindow *window); +static void gimp_image_window_page_removed (GtkNotebook *notebook, + GtkWidget *widget, + gint page_num, + GimpImageWindow *window); +static void gimp_image_window_page_reordered (GtkNotebook *notebook, + GtkWidget *widget, + gint page_num, + GimpImageWindow *window); +static void gimp_image_window_disconnect_from_active_shell + (GimpImageWindow *window); + +static void gimp_image_window_image_notify (GimpDisplay *display, + const GParamSpec *pspec, + GimpImageWindow *window); +static void gimp_image_window_shell_scaled (GimpDisplayShell *shell, + GimpImageWindow *window); +static void gimp_image_window_shell_rotated (GimpDisplayShell *shell, + GimpImageWindow *window); +static void gimp_image_window_shell_title_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpImageWindow *window); +static void gimp_image_window_shell_icon_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpImageWindow *window); +static GtkWidget * + gimp_image_window_create_tab_label (GimpImageWindow *window, + GimpDisplayShell *shell); +static void gimp_image_window_update_tab_labels (GimpImageWindow *window); + + +G_DEFINE_TYPE_WITH_CODE (GimpImageWindow, gimp_image_window, GIMP_TYPE_WINDOW, + G_ADD_PRIVATE (GimpImageWindow) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCK_CONTAINER, + gimp_image_window_dock_container_iface_init) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_SESSION_MANAGED, + gimp_image_window_session_managed_iface_init)) + +#define parent_class gimp_image_window_parent_class + + +static const gchar image_window_rc_style[] = + "style \"fullscreen-menubar-style\"\n" + "{\n" + " GtkMenuBar::shadow-type = none\n" + " GtkMenuBar::internal-padding = 0\n" + "}\n" + "widget \"*.gimp-menubar-fullscreen\" style \"fullscreen-menubar-style\"\n"; + +static void +gimp_image_window_class_init (GimpImageWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpWindowClass *window_class = GIMP_WINDOW_CLASS (klass); + + object_class->constructed = gimp_image_window_constructed; + object_class->dispose = gimp_image_window_dispose; + object_class->finalize = gimp_image_window_finalize; + object_class->set_property = gimp_image_window_set_property; + object_class->get_property = gimp_image_window_get_property; + + widget_class->map = gimp_image_window_map; + widget_class->delete_event = gimp_image_window_delete_event; + widget_class->configure_event = gimp_image_window_configure_event; + widget_class->window_state_event = gimp_image_window_window_state_event; + widget_class->style_set = gimp_image_window_style_set; + + window_class->monitor_changed = gimp_image_window_monitor_changed; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DIALOG_FACTORY, + g_param_spec_object ("dialog-factory", + NULL, NULL, + GIMP_TYPE_DIALOG_FACTORY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_INITIAL_SCREEN, + g_param_spec_object ("initial-screen", + NULL, NULL, + GDK_TYPE_SCREEN, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_INITIAL_MONITOR, + g_param_spec_int ("initial-monitor", + NULL, NULL, + 0, 16, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + gtk_rc_parse_string (image_window_rc_style); +} + +static void +gimp_image_window_init (GimpImageWindow *window) +{ + static gint role_serial = 1; + gchar *role; + + role = g_strdup_printf ("gimp-image-window-%d", role_serial++); + gtk_window_set_role (GTK_WINDOW (window), role); + g_free (role); + + gtk_window_set_resizable (GTK_WINDOW (window), TRUE); +} + +static void +gimp_image_window_dock_container_iface_init (GimpDockContainerInterface *iface) +{ + iface->get_docks = gimp_image_window_get_docks; + iface->get_dialog_factory = gimp_image_window_dock_container_get_dialog_factory; + iface->get_ui_manager = gimp_image_window_dock_container_get_ui_manager; + iface->add_dock = gimp_image_window_add_dock; + iface->get_dock_side = gimp_image_window_get_dock_side; +} + +static void +gimp_image_window_session_managed_iface_init (GimpSessionManagedInterface *iface) +{ + iface->get_aux_info = gimp_image_window_get_aux_info; + iface->set_aux_info = gimp_image_window_set_aux_info; +} + +static void +gimp_image_window_constructed (GObject *object) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (object); + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpMenuFactory *menu_factory; + GimpGuiConfig *config; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (private->gimp)); + gimp_assert (GIMP_IS_DIALOG_FACTORY (private->dialog_factory)); + + menu_factory = gimp_dialog_factory_get_menu_factory (private->dialog_factory); + + private->menubar_manager = gimp_menu_factory_manager_new (menu_factory, + "<Image>", + window, + FALSE); + + g_signal_connect_object (private->dialog_factory, "dock-window-added", + G_CALLBACK (gimp_image_window_update_ui_manager), + window, G_CONNECT_SWAPPED); + g_signal_connect_object (private->dialog_factory, "dock-window-removed", + G_CALLBACK (gimp_image_window_update_ui_manager), + window, G_CONNECT_SWAPPED); + + gtk_window_add_accel_group (GTK_WINDOW (window), + gimp_ui_manager_get_accel_group (private->menubar_manager)); + + g_signal_connect (private->menubar_manager, "show-tooltip", + G_CALLBACK (gimp_image_window_show_tooltip), + window); + g_signal_connect (private->menubar_manager, "hide-tooltip", + G_CALLBACK (gimp_image_window_hide_tooltip), + window); + + config = GIMP_GUI_CONFIG (private->gimp->config); + + /* Create the window toplevel container */ + private->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (window), private->main_vbox); + gtk_widget_show (private->main_vbox); + + /* Create the menubar */ +#ifndef GDK_WINDOWING_QUARTZ + private->menubar = gimp_ui_manager_get_widget (private->menubar_manager, + "/image-menubar"); +#endif /* !GDK_WINDOWING_QUARTZ */ + if (private->menubar) + { + gtk_box_pack_start (GTK_BOX (private->main_vbox), + private->menubar, FALSE, FALSE, 0); + + /* make sure we can activate accels even if the menubar is invisible + * (see https://bugzilla.gnome.org/show_bug.cgi?id=137151) + */ + g_signal_connect (private->menubar, "can-activate-accel", + G_CALLBACK (gtk_true), + NULL); + + /* active display callback */ + g_signal_connect (private->menubar, "button-press-event", + G_CALLBACK (gimp_image_window_shell_events), + window); + g_signal_connect (private->menubar, "button-release-event", + G_CALLBACK (gimp_image_window_shell_events), + window); + g_signal_connect (private->menubar, "key-press-event", + G_CALLBACK (gimp_image_window_shell_events), + window); + } + + /* Create the hbox that contains docks and images */ + private->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (private->main_vbox), private->hbox, + TRUE, TRUE, 0); + gtk_widget_show (private->hbox); + + /* Create the left pane */ + private->left_hpane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start (GTK_BOX (private->hbox), private->left_hpane, + TRUE, TRUE, 0); + gtk_widget_show (private->left_hpane); + + /* Create the left dock columns widget */ + private->left_docks = + gimp_dock_columns_new (gimp_get_user_context (private->gimp), + private->dialog_factory, + private->menubar_manager); + gtk_paned_pack1 (GTK_PANED (private->left_hpane), private->left_docks, + FALSE, FALSE); + gtk_widget_set_visible (private->left_docks, config->single_window_mode); + + /* Create the right pane */ + private->right_hpane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_paned_pack2 (GTK_PANED (private->left_hpane), private->right_hpane, + TRUE, FALSE); + gtk_widget_show (private->right_hpane); + + /* Create notebook that contains images */ + private->notebook = gtk_notebook_new (); + gtk_notebook_set_scrollable (GTK_NOTEBOOK (private->notebook), TRUE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (private->notebook), GTK_POS_TOP); + + gtk_paned_pack1 (GTK_PANED (private->right_hpane), private->notebook, + TRUE, TRUE); + + g_signal_connect (private->notebook, "switch-page", + G_CALLBACK (gimp_image_window_switch_page), + window); + g_signal_connect (private->notebook, "page-removed", + G_CALLBACK (gimp_image_window_page_removed), + window); + g_signal_connect (private->notebook, "page-reordered", + G_CALLBACK (gimp_image_window_page_reordered), + window); + gtk_widget_show (private->notebook); + + /* Create the right dock columns widget */ + private->right_docks = + gimp_dock_columns_new (gimp_get_user_context (private->gimp), + private->dialog_factory, + private->menubar_manager); + gtk_paned_pack2 (GTK_PANED (private->right_hpane), private->right_docks, + FALSE, FALSE); + gtk_widget_set_visible (private->right_docks, config->single_window_mode); + + g_signal_connect_object (config, "notify::single-window-mode", + G_CALLBACK (gimp_image_window_config_notify), + window, G_CONNECT_SWAPPED); + g_signal_connect_object (config, "notify::show-tabs", + G_CALLBACK (gimp_image_window_config_notify), + window, G_CONNECT_SWAPPED); + g_signal_connect_object (config, "notify::hide-docks", + G_CALLBACK (gimp_image_window_config_notify), + window, G_CONNECT_SWAPPED); + g_signal_connect_object (config, "notify::tabs-position", + G_CALLBACK (gimp_image_window_config_notify), + window, G_CONNECT_SWAPPED); + + gimp_image_window_session_update (window, + NULL /*new_display*/, + gimp_image_window_config_to_entry_id (config), + private->initial_screen, + private->initial_monitor); +} + +static void +gimp_image_window_dispose (GObject *object) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (object); + + if (private->dialog_factory) + { + g_signal_handlers_disconnect_by_func (private->dialog_factory, + gimp_image_window_update_ui_manager, + object); + private->dialog_factory = NULL; + } + + g_clear_object (&private->menubar_manager); + + if (private->update_ui_manager_idle_id) + { + g_source_remove (private->update_ui_manager_idle_id); + private->update_ui_manager_idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_image_window_finalize (GObject *object) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (object); + + if (private->shells) + { + g_list_free (private->shells); + private->shells = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_image_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (object); + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); + break; + case PROP_DIALOG_FACTORY: + private->dialog_factory = g_value_get_object (value); + break; + case PROP_INITIAL_SCREEN: + private->initial_screen = g_value_get_object (value); + break; + case PROP_INITIAL_MONITOR: + private->initial_monitor = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (object); + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + case PROP_DIALOG_FACTORY: + g_value_set_object (value, private->dialog_factory); + break; + case PROP_INITIAL_SCREEN: + g_value_set_object (value, private->initial_screen); + break; + case PROP_INITIAL_MONITOR: + g_value_set_int (value, private->initial_monitor); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_window_map (GtkWidget *widget) +{ +#ifdef GDK_WINDOWING_QUARTZ + GdkWindow *gdk_window; + NSWindow *ns_window; +#endif /* !GDK_WINDOWING_QUARTZ */ + + GTK_WIDGET_CLASS (parent_class)->map (widget); + +#ifdef GDK_WINDOWING_QUARTZ + gdk_window = gtk_widget_get_window (GTK_WIDGET (widget)); + ns_window = gdk_quartz_window_get_nswindow (gdk_window); + + /* Disable the new-style full screen mode. For now only the "old-style" + * full screen mode, via the "View" menu, is supported. In the future, and + * as soon as GTK+ has proper support for this, we will migrate to the + * new-style full screen mode. + */ + ns_window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; +#endif /* !GDK_WINDOWING_QUARTZ */ +} + +static gboolean +gimp_image_window_delete_event (GtkWidget *widget, + GdkEventAny *event) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget); + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpGuiConfig *config = GIMP_GUI_CONFIG (private->gimp->config); + + if (config->single_window_mode) + gimp_ui_manager_activate_action (gimp_image_window_get_ui_manager (window), + "file", "file-quit"); + else if (shell) + gimp_display_shell_close (shell, FALSE); + + return TRUE; +} + +static gboolean +gimp_image_window_configure_event (GtkWidget *widget, + GdkEventConfigure *event) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget); + GtkAllocation allocation; + gint current_width; + gint current_height; + + gtk_widget_get_allocation (widget, &allocation); + + /* Grab the size before we run the parent implementation */ + current_width = allocation.width; + current_height = allocation.height; + + /* Run the parent implementation */ + if (GTK_WIDGET_CLASS (parent_class)->configure_event) + GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event); + + /* If the window size has changed, make sure additoinal logic is run + * in the display shell's size-allocate + */ + if (event->width != current_width || + event->height != current_height) + { + /* FIXME multiple shells */ + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + + if (shell && gimp_display_get_image (shell->display)) + shell->size_allocate_from_configure_event = TRUE; + } + + return TRUE; +} + +static gboolean +gimp_image_window_window_state_event (GtkWidget *widget, + GdkEventWindowState *event) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget); + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + + if (! shell) + return FALSE; + + private->window_state = event->new_window_state; + + if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) + { + gboolean fullscreen = gimp_image_window_get_fullscreen (window); + + GIMP_LOG (WM, "Image window '%s' [%p] set fullscreen %s", + gtk_window_get_title (GTK_WINDOW (widget)), + widget, + fullscreen ? "TRUE" : "FALSE"); + + if (private->menubar) + gtk_widget_set_name (private->menubar, + fullscreen ? "gimp-menubar-fullscreen" : NULL); + + gimp_image_window_suspend_keep_pos (window); + gimp_display_shell_appearance_update (shell); + gimp_image_window_resume_keep_pos (window); + } + + if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) + { + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (shell); + gboolean iconified = gimp_image_window_is_iconified (window); + + GIMP_LOG (WM, "Image window '%s' [%p] set %s", + gtk_window_get_title (GTK_WINDOW (widget)), + widget, + iconified ? "iconified" : "uniconified"); + + if (iconified) + { + if (gimp_displays_get_num_visible (private->gimp) == 0) + { + GIMP_LOG (WM, "No displays visible any longer"); + + gimp_dialog_factory_hide_with_display (private->dialog_factory); + } + } + else + { + gimp_dialog_factory_show_with_display (private->dialog_factory); + } + + if (gimp_progress_is_active (GIMP_PROGRESS (statusbar))) + { + if (iconified) + gimp_statusbar_override_window_title (statusbar); + else + gtk_window_set_title (GTK_WINDOW (window), shell->title); + } + } + + return FALSE; +} + +static void +gimp_image_window_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (widget); + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + GimpStatusbar *statusbar = NULL; + GtkRequisition requisition = { 0, }; + GdkGeometry geometry = { 0, }; + GdkWindowHints geometry_mask = 0; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + if (! shell) + return; + + statusbar = gimp_display_shell_get_statusbar (shell); + + gtk_widget_size_request (GTK_WIDGET (statusbar), &requisition); + + geometry.min_height = 23; + + geometry.min_width = requisition.width; + geometry.min_height += requisition.height; + + if (private->menubar) + { + gtk_widget_size_request (private->menubar, &requisition); + + geometry.min_height += requisition.height; + } + + geometry_mask = GDK_HINT_MIN_SIZE; + + /* Only set user pos on the empty display because it gets a pos + * set by gimp. All other displays should be placed by the window + * manager. See https://bugzilla.gnome.org/show_bug.cgi?id=559580 + */ + if (! gimp_display_get_image (shell->display)) + geometry_mask |= GDK_HINT_USER_POS; + + gtk_window_set_geometry_hints (GTK_WINDOW (widget), NULL, + &geometry, geometry_mask); + + gimp_dialog_factory_set_has_min_size (GTK_WINDOW (widget), TRUE); +} + +static void +gimp_image_window_monitor_changed (GimpWindow *window, + GdkScreen *screen, + gint monitor) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GList *list; + + for (list = private->shells; list; list = g_list_next (list)) + { + /* hack, this should live here, and screen_changed call + * monitor_changed + */ + g_signal_emit_by_name (list->data, "screen-changed", + gtk_widget_get_screen (list->data)); + + /* make it fetch the new monitor's resolution */ + gimp_display_shell_scale_update (GIMP_DISPLAY_SHELL (list->data)); + + /* make it fetch the right monitor profile */ + gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (list->data)); + } +} + +static GList * +gimp_image_window_get_docks (GimpDockContainer *dock_container) +{ + GimpImageWindowPrivate *private; + GList *iter; + GList *all_docks = NULL; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (dock_container), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (dock_container); + + for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->left_docks)); + iter; + iter = g_list_next (iter)) + { + all_docks = g_list_append (all_docks, GIMP_DOCK (iter->data)); + } + + for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->right_docks)); + iter; + iter = g_list_next (iter)) + { + all_docks = g_list_append (all_docks, GIMP_DOCK (iter->data)); + } + + return all_docks; +} + +static GimpDialogFactory * +gimp_image_window_dock_container_get_dialog_factory (GimpDockContainer *dock_container) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (dock_container); + + return private->dialog_factory; +} + +static GimpUIManager * +gimp_image_window_dock_container_get_ui_manager (GimpDockContainer *dock_container) +{ + GimpImageWindow *window = GIMP_IMAGE_WINDOW (dock_container); + + return gimp_image_window_get_ui_manager (window); +} + +void +gimp_image_window_add_dock (GimpDockContainer *dock_container, + GimpDock *dock, + GimpSessionInfoDock *dock_info) +{ + GimpImageWindow *window; + GimpDisplayShell *active_shell; + GimpImageWindowPrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (dock_container)); + + window = GIMP_IMAGE_WINDOW (dock_container); + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + if (dock_info->side == GIMP_ALIGN_LEFT) + { + gimp_dock_columns_add_dock (GIMP_DOCK_COLUMNS (private->left_docks), + dock, + -1 /*index*/); + } + else + { + gimp_dock_columns_add_dock (GIMP_DOCK_COLUMNS (private->right_docks), + dock, + -1 /*index*/); + } + + active_shell = gimp_image_window_get_active_shell (window); + if (active_shell) + gimp_display_shell_appearance_update (active_shell); +} + +static GimpAlignmentType +gimp_image_window_get_dock_side (GimpDockContainer *dock_container, + GimpDock *dock) +{ + GimpAlignmentType side = -1; + GimpImageWindowPrivate *private; + GList *iter; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (dock_container), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (dock_container); + + for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->left_docks)); + iter && side == -1; + iter = g_list_next (iter)) + { + GimpDock *dock_iter = GIMP_DOCK (iter->data); + + if (dock_iter == dock) + side = GIMP_ALIGN_LEFT; + } + + for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->right_docks)); + iter && side == -1; + iter = g_list_next (iter)) + { + GimpDock *dock_iter = GIMP_DOCK (iter->data); + + if (dock_iter == dock) + side = GIMP_ALIGN_RIGHT; + } + + return side; +} + +static GList * +gimp_image_window_get_aux_info (GimpSessionManaged *session_managed) +{ + GList *aux_info = NULL; + GimpImageWindowPrivate *private; + GimpGuiConfig *config; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (session_managed), NULL); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (session_managed); + config = GIMP_GUI_CONFIG (private->gimp->config); + + if (config->single_window_mode) + { + GimpSessionInfoAux *aux; + GtkAllocation allocation; + gchar widthbuf[128]; + + g_snprintf (widthbuf, sizeof (widthbuf), "%d", + gtk_paned_get_position (GTK_PANED (private->left_hpane))); + aux = gimp_session_info_aux_new (GIMP_IMAGE_WINDOW_LEFT_DOCKS_WIDTH, + widthbuf); + aux_info = g_list_append (aux_info, aux); + + gtk_widget_get_allocation (private->right_hpane, &allocation); + + g_snprintf (widthbuf, sizeof (widthbuf), "%d", + allocation.width - + gtk_paned_get_position (GTK_PANED (private->right_hpane))); + aux = gimp_session_info_aux_new (GIMP_IMAGE_WINDOW_RIGHT_DOCKS_WIDTH, + widthbuf); + aux_info = g_list_append (aux_info, aux); + + aux = gimp_session_info_aux_new (GIMP_IMAGE_WINDOW_MAXIMIZED, + gimp_image_window_is_maximized (GIMP_IMAGE_WINDOW (session_managed)) ? + "yes" : "no"); + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} + +static void +gimp_image_window_set_right_docks_width (GtkPaned *paned, + GtkAllocation *allocation, + void *data) +{ + gint width = GPOINTER_TO_INT (data); + + g_return_if_fail (GTK_IS_PANED (paned)); + + if (width > 0) + gtk_paned_set_position (paned, allocation->width - width); + else + gtk_paned_set_position (paned, - width); + + g_signal_handlers_disconnect_by_func (paned, + gimp_image_window_set_right_docks_width, + data); +} + +static void +gimp_image_window_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info) +{ + GimpImageWindowPrivate *private; + GList *iter; + gint left_docks_width = G_MININT; + gint right_docks_width = G_MININT; + gboolean wait_with_right_docks = FALSE; + gboolean maximized = FALSE; +#ifdef G_OS_WIN32 + STARTUPINFO StartupInfo; + + GetStartupInfo (&StartupInfo); +#endif + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (session_managed)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (session_managed); + + for (iter = aux_info; iter; iter = g_list_next (iter)) + { + GimpSessionInfoAux *aux = iter->data; + gint *width = NULL; + + if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_LEFT_DOCKS_WIDTH)) + width = &left_docks_width; + else if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_RIGHT_DOCKS_WIDTH)) + width = &right_docks_width; + else if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_RIGHT_DOCKS_POS)) + width = &right_docks_width; + else if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_MAXIMIZED)) + if (! g_ascii_strcasecmp (aux->value, "yes")) + maximized = TRUE; + + if (width) + sscanf (aux->value, "%d", width); + + /* compat handling for right docks */ + if (! strcmp (aux->name, GIMP_IMAGE_WINDOW_RIGHT_DOCKS_POS)) + { + /* negate the value because negative docks pos means docks width, + * also use the negativenes of a real docks pos as condition below. + */ + *width = - *width; + } + } + + if (left_docks_width != G_MININT && + gtk_paned_get_position (GTK_PANED (private->left_hpane)) != + left_docks_width) + { + gtk_paned_set_position (GTK_PANED (private->left_hpane), left_docks_width); + + /* We can't set the position of the right docks, because it will + * be undesirably adjusted when its get a new size + * allocation. We must wait until after the size allocation. + */ + wait_with_right_docks = TRUE; + } + + if (right_docks_width != G_MININT && + gtk_paned_get_position (GTK_PANED (private->right_hpane)) != + right_docks_width) + { + if (wait_with_right_docks || right_docks_width > 0) + { + /* We must wait for a size allocation before we can set the + * position + */ + g_signal_connect_data (private->right_hpane, "size-allocate", + G_CALLBACK (gimp_image_window_set_right_docks_width), + GINT_TO_POINTER (right_docks_width), NULL, + G_CONNECT_AFTER); + } + else + { + /* We can set the position directly, because we didn't + * change the left hpane position, and we got the old compat + * dock pos property. + */ + gtk_paned_set_position (GTK_PANED (private->right_hpane), + - right_docks_width); + } + } + +#ifdef G_OS_WIN32 + /* On Windows, user can provide startup hints to have a program + * maximized/minimized on startup. This can be done through command + * line: `start /max gimp-2.9.exe` or with the shortcut's "run" + * property. + * When such a hint is given, we should follow it and bypass the + * session's information. + */ + if (StartupInfo.wShowWindow == SW_SHOWMAXIMIZED) + gtk_window_maximize (GTK_WINDOW (session_managed)); + else if (StartupInfo.wShowWindow == SW_SHOWMINIMIZED || + StartupInfo.wShowWindow == SW_SHOWMINNOACTIVE || + StartupInfo.wShowWindow == SW_MINIMIZE) + /* XXX Iconification does not seem to work. I see the + * window being iconified and immediately re-raised. + * I leave this piece of code for later improvement. */ + gtk_window_iconify (GTK_WINDOW (session_managed)); + else + /* Another show property not relevant to min/max. + * Defaults is: SW_SHOWNORMAL + */ +#endif + if (maximized) + gtk_window_maximize (GTK_WINDOW (session_managed)); + else + gtk_window_unmaximize (GTK_WINDOW (session_managed)); +} + + +/* public functions */ + +GimpImageWindow * +gimp_image_window_new (Gimp *gimp, + GimpImage *image, + GimpDialogFactory *dialog_factory, + GdkScreen *screen, + gint monitor) +{ + GimpImageWindow *window; + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + window = g_object_new (GIMP_TYPE_IMAGE_WINDOW, + "gimp", gimp, + "dialog-factory", dialog_factory, + "initial-screen", screen, + "initial-monitor", monitor, + /* The window position will be overridden by the + * dialog factory, it is only really used on first + * startup. + */ + image ? NULL : "window-position", + GTK_WIN_POS_CENTER, + NULL); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + gimp->image_windows = g_list_append (gimp->image_windows, window); + + if (! GIMP_GUI_CONFIG (private->gimp->config)->single_window_mode) + { + GdkScreen *pointer_screen; + gint pointer_monitor; + + pointer_monitor = gimp_get_monitor_at_pointer (&pointer_screen); + + /* If we are supposed to go to a monitor other than where the + * pointer is, place the window on that monitor manually, + * otherwise simply let the window manager place the window on + * the poiner's monitor. + */ + if (pointer_screen != screen || + pointer_monitor != monitor) + { + GdkRectangle rect; + gchar geom[32]; + + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + /* FIXME: image window placement + * + * This is ugly beyond description but better than showing + * the window on the wrong monitor + */ + g_snprintf (geom, sizeof (geom), "%+d%+d", + rect.x + 300, rect.y + 30); + gtk_window_parse_geometry (GTK_WINDOW (window), geom); + } + } + + return window; +} + +void +gimp_image_window_destroy (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + private->gimp->image_windows = g_list_remove (private->gimp->image_windows, + window); + + gtk_widget_destroy (GTK_WIDGET (window)); +} + +GimpUIManager * +gimp_image_window_get_ui_manager (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return private->menubar_manager; +} + +GimpDockColumns * +gimp_image_window_get_left_docks (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return GIMP_DOCK_COLUMNS (private->left_docks); +} + +GimpDockColumns * +gimp_image_window_get_right_docks (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return GIMP_DOCK_COLUMNS (private->right_docks); +} + +void +gimp_image_window_add_shell (GimpImageWindow *window, + GimpDisplayShell *shell) +{ + GimpImageWindowPrivate *private; + GtkWidget *tab_label; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + g_return_if_fail (g_list_find (private->shells, shell) == NULL); + + private->shells = g_list_append (private->shells, shell); + + tab_label = gimp_image_window_create_tab_label (window, shell); + + gtk_notebook_append_page (GTK_NOTEBOOK (private->notebook), + GTK_WIDGET (shell), tab_label); + gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (private->notebook), + GTK_WIDGET (shell), TRUE); + + gtk_widget_show (GTK_WIDGET (shell)); + + /* make it fetch the right monitor profile */ + gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (shell)); +} + +GimpDisplayShell * +gimp_image_window_get_shell (GimpImageWindow *window, + gint index) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), NULL); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return g_list_nth_data (private->shells, index); +} + +void +gimp_image_window_remove_shell (GimpImageWindow *window, + GimpDisplayShell *shell) +{ + GimpImageWindowPrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + g_return_if_fail (g_list_find (private->shells, shell) != NULL); + + private->shells = g_list_remove (private->shells, shell); + + gtk_container_remove (GTK_CONTAINER (private->notebook), + GTK_WIDGET (shell)); +} + +gint +gimp_image_window_get_n_shells (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), 0); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return g_list_length (private->shells); +} + +void +gimp_image_window_set_active_shell (GimpImageWindow *window, + GimpDisplayShell *shell) +{ + GimpImageWindowPrivate *private; + gint page_num; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + g_return_if_fail (g_list_find (private->shells, shell)); + + page_num = gtk_notebook_page_num (GTK_NOTEBOOK (private->notebook), + GTK_WIDGET (shell)); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (private->notebook), page_num); +} + +GimpDisplayShell * +gimp_image_window_get_active_shell (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), NULL); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return private->active_shell; +} + +void +gimp_image_window_set_fullscreen (GimpImageWindow *window, + gboolean fullscreen) +{ + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + if (fullscreen != gimp_image_window_get_fullscreen (window)) + { + if (fullscreen) + gtk_window_fullscreen (GTK_WINDOW (window)); + else + gtk_window_unfullscreen (GTK_WINDOW (window)); + } +} + +gboolean +gimp_image_window_get_fullscreen (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return (private->window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0; +} + +void +gimp_image_window_set_show_menubar (GimpImageWindow *window, + gboolean show) +{ + GimpImageWindowPrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + if (private->menubar) + gtk_widget_set_visible (private->menubar, show); +} + +gboolean +gimp_image_window_get_show_menubar (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + if (private->menubar) + return gtk_widget_get_visible (private->menubar); + + return FALSE; +} + +gboolean +gimp_image_window_is_iconified (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return (private->window_state & GDK_WINDOW_STATE_ICONIFIED) != 0; +} + +gboolean +gimp_image_window_is_maximized (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + return (private->window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0; +} + +/** + * gimp_image_window_has_toolbox: + * @window: + * + * Returns: %TRUE if the image window contains a GimpToolbox. + **/ +gboolean +gimp_image_window_has_toolbox (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + GList *iter = NULL; + + g_return_val_if_fail (GIMP_IS_IMAGE_WINDOW (window), FALSE); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->left_docks)); + iter; + iter = g_list_next (iter)) + { + if (GIMP_IS_TOOLBOX (iter->data)) + return TRUE; + } + + for (iter = gimp_dock_columns_get_docks (GIMP_DOCK_COLUMNS (private->right_docks)); + iter; + iter = g_list_next (iter)) + { + if (GIMP_IS_TOOLBOX (iter->data)) + return TRUE; + } + + return FALSE; +} + +void +gimp_image_window_shrink_wrap (GimpImageWindow *window, + gboolean grow_only) +{ + GimpDisplayShell *active_shell; + GtkWidget *widget; + GtkAllocation allocation; + GdkScreen *screen; + GdkRectangle rect; + gint monitor; + gint disp_width, disp_height; + gint width, height; + gint max_auto_width, max_auto_height; + gint border_width, border_height; + gboolean resize = FALSE; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + if (! gtk_widget_get_realized (GTK_WIDGET (window))) + return; + + /* FIXME this so needs cleanup and shell/window separation */ + + active_shell = gimp_image_window_get_active_shell (window); + + if (!active_shell) + return; + + widget = GTK_WIDGET (window); + screen = gtk_widget_get_screen (widget); + + gtk_widget_get_allocation (widget, &allocation); + + monitor = gdk_screen_get_monitor_at_window (screen, + gtk_widget_get_window (widget)); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + if (! gimp_display_shell_get_infinite_canvas (active_shell)) + { + gimp_display_shell_scale_get_image_size (active_shell, + &width, &height); + } + else + { + gimp_display_shell_scale_get_image_bounding_box (active_shell, + NULL, NULL, + &width, &height); + } + + disp_width = active_shell->disp_width; + disp_height = active_shell->disp_height; + + + /* As long as the disp_width/disp_height is larger than 1 we + * can reliably depend on it to calculate the + * border_width/border_height because that means there is enough + * room in the top-level for the canvas as well as the rulers and + * scrollbars. If it is 1 or smaller it is likely that the rulers + * and scrollbars are overlapping each other and thus we cannot use + * the normal approach to border size, so special case that. + */ + if (disp_width > 1 || !active_shell->vsb) + { + border_width = allocation.width - disp_width; + } + else + { + GtkAllocation vsb_allocation; + + gtk_widget_get_allocation (active_shell->vsb, &vsb_allocation); + + border_width = allocation.width - disp_width + vsb_allocation.width; + } + + if (disp_height > 1 || !active_shell->hsb) + { + border_height = allocation.height - disp_height; + } + else + { + GtkAllocation hsb_allocation; + + gtk_widget_get_allocation (active_shell->hsb, &hsb_allocation); + + border_height = allocation.height - disp_height + hsb_allocation.height; + } + + + max_auto_width = (rect.width - border_width) * 0.75; + max_auto_height = (rect.height - border_height) * 0.75; + + /* If one of the display dimensions has changed and one of the + * dimensions fits inside the screen + */ + if (((width + border_width) < rect.width || + (height + border_height) < rect.height) && + (width != disp_width || + height != disp_height)) + { + width = ((width + border_width) < rect.width) ? width : max_auto_width; + height = ((height + border_height) < rect.height) ? height : max_auto_height; + + resize = TRUE; + } + + /* If the projected dimension is greater than current, but less than + * 3/4 of the screen size, expand automagically + */ + else if ((width > disp_width || + height > disp_height) && + (disp_width < max_auto_width || + disp_height < max_auto_height)) + { + width = MIN (max_auto_width, width); + height = MIN (max_auto_height, height); + + resize = TRUE; + } + + if (resize) + { + GimpStatusbar *statusbar = gimp_display_shell_get_statusbar (active_shell); + gint statusbar_width; + + gtk_widget_get_size_request (GTK_WIDGET (statusbar), + &statusbar_width, NULL); + + if (width < statusbar_width) + width = statusbar_width; + + width = width + border_width; + height = height + border_height; + + if (grow_only) + { + if (width < allocation.width) + width = allocation.width; + + if (height < allocation.height) + height = allocation.height; + } + + gtk_window_resize (GTK_WINDOW (window), width, height); + } + + /* A wrap always means that we should center the image too. If the + * window changes size another center will be done in + * GimpDisplayShell::configure_event(). + */ + /* FIXME multiple shells */ + gimp_display_shell_scroll_center_content (active_shell, TRUE, TRUE); +} + +static GtkWidget * +gimp_image_window_get_first_dockbook (GimpDockColumns *columns) +{ + GList *dock_iter; + + for (dock_iter = gimp_dock_columns_get_docks (columns); + dock_iter; + dock_iter = g_list_next (dock_iter)) + { + GimpDock *dock = GIMP_DOCK (dock_iter->data); + GList *dockbooks = gimp_dock_get_dockbooks (dock); + + if (dockbooks) + return GTK_WIDGET (dockbooks->data); + } + + return NULL; +} + +/** + * gimp_image_window_get_default_dockbook: + * @window: + * + * Gets the default dockbook, which is the dockbook in which new + * dockables should be put in single-window mode. + * + * Returns: The default dockbook for new dockables, or NULL if no + * dockbook were available. + **/ +GtkWidget * +gimp_image_window_get_default_dockbook (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + GimpDockColumns *dock_columns; + GtkWidget *dockbook = NULL; + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + /* First try the first dockbook in the right docks */ + dock_columns = GIMP_DOCK_COLUMNS (private->right_docks); + dockbook = gimp_image_window_get_first_dockbook (dock_columns); + + /* Then the left docks */ + if (! dockbook) + { + dock_columns = GIMP_DOCK_COLUMNS (private->left_docks); + dockbook = gimp_image_window_get_first_dockbook (dock_columns); + } + + return dockbook; +} + +/** + * gimp_image_window_keep_canvas_pos: + * @window: + * + * Stores the coordinates of the current image canvas origin relatively + * its GtkWindow; and on the first size-allocate sets the offsets in + * the shell so that the image origin remains the same (even on another + * GtkWindow). + * + * Example use case: The user hides docks attached to the side of image + * windows. You want the image to remain fixed on the screen though, + * so you use this function to keep the image fixed after the docks + * have been hidden. + **/ +void +gimp_image_window_keep_canvas_pos (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + GimpDisplayShell *shell; + gint canvas_x; + gint canvas_y; + gint window_x; + gint window_y; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + if (private->suspend_keep_pos > 0) + return; + + shell = gimp_image_window_get_active_shell (window); + + gimp_display_shell_transform_xy (shell, 0.0, 0.0, &canvas_x, &canvas_y); + + if (gtk_widget_translate_coordinates (GTK_WIDGET (shell->canvas), + GTK_WIDGET (window), + canvas_x, canvas_y, + &window_x, &window_y)) + { + PosCorrectionData *data = g_new0 (PosCorrectionData, 1); + + data->canvas_x = canvas_x; + data->canvas_y = canvas_y; + data->window_x = window_x; + data->window_y = window_y; + + g_signal_connect_data (shell, "size-allocate", + G_CALLBACK (gimp_image_window_shell_size_allocate), + data, (GClosureNotify) g_free, + G_CONNECT_AFTER); + } +} + +void +gimp_image_window_suspend_keep_pos (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + private->suspend_keep_pos++; +} + +void +gimp_image_window_resume_keep_pos (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + g_return_if_fail (private->suspend_keep_pos > 0); + + private->suspend_keep_pos--; +} + +/** + * gimp_image_window_update_tabs: + * @window: the Image Window to update. + * + * Holds the logics of whether shell tabs are to be shown or not in the + * Image Window @window. This function should be called after every + * change to @window where one might expect tab visibility to change. + * + * No direct call to gtk_notebook_set_show_tabs() should ever be made. + * If we change the logics of tab hiding, we should only change this + * procedure instead. + **/ +void +gimp_image_window_update_tabs (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private; + GimpGuiConfig *config; + GtkPositionType position; + + g_return_if_fail (GIMP_IS_IMAGE_WINDOW (window)); + + private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + config = GIMP_GUI_CONFIG (private->gimp->config); + + /* Tab visibility. */ + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook), + config->single_window_mode && + config->show_tabs && + ! config->hide_docks && + ((private->active_shell && + private->active_shell->display && + gimp_display_get_image (private->active_shell->display)) || + g_list_length (private->shells) > 1)); + + /* Tab position. */ + switch (config->tabs_position) + { + case GIMP_POSITION_TOP: + position = GTK_POS_TOP; + break; + case GIMP_POSITION_BOTTOM: + position = GTK_POS_BOTTOM; + break; + case GIMP_POSITION_LEFT: + position = GTK_POS_LEFT; + break; + case GIMP_POSITION_RIGHT: + position = GTK_POS_RIGHT; + break; + default: + /* If we have any strange value, just reset to default. */ + position = GTK_POS_TOP; + break; + } + + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (private->notebook), position); +} + + +/* private functions */ + +static void +gimp_image_window_show_tooltip (GimpUIManager *manager, + const gchar *tooltip, + GimpImageWindow *window) +{ + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + GimpStatusbar *statusbar = NULL; + + if (! shell) + return; + + statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_statusbar_push (statusbar, "menu-tooltip", + NULL, "%s", tooltip); +} + +static void +gimp_image_window_config_notify (GimpImageWindow *window, + GParamSpec *pspec, + GimpGuiConfig *config) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + /* Dock column visibility */ + if (strcmp (pspec->name, "single-window-mode") == 0 || + strcmp (pspec->name, "hide-docks") == 0 || + strcmp (pspec->name, "show-tabs") == 0 || + strcmp (pspec->name, "tabs-position") == 0) + { + if (strcmp (pspec->name, "single-window-mode") == 0 || + strcmp (pspec->name, "hide-docks") == 0) + { + gboolean show_docks = (config->single_window_mode && + ! config->hide_docks); + + gimp_image_window_keep_canvas_pos (window); + gtk_widget_set_visible (private->left_docks, show_docks); + gtk_widget_set_visible (private->right_docks, show_docks); + + /* If docks are being shown, and we are in multi-window-mode, + * and this is the window of the active display, try to set + * the keyboard focus to this window because it might have + * been stolen by a dock. See bug #567333. + */ + if (strcmp (pspec->name, "hide-docks") == 0 && + ! config->single_window_mode && + ! config->hide_docks) + { + GimpDisplayShell *shell; + GimpContext *user_context; + + shell = gimp_image_window_get_active_shell (window); + user_context = gimp_get_user_context (private->gimp); + + if (gimp_context_get_display (user_context) == shell->display) + { + GdkWindow *w = gtk_widget_get_window (GTK_WIDGET (window)); + + if (w) + gdk_window_focus (w, gtk_get_current_event_time ()); + } + } + } + + gimp_image_window_update_tabs (window); + } + + /* Session management */ + if (strcmp (pspec->name, "single-window-mode") == 0) + { + gimp_image_window_session_update (window, + NULL /*new_display*/, + gimp_image_window_config_to_entry_id (config), + gtk_widget_get_screen (GTK_WIDGET (window)), + gimp_widget_get_monitor (GTK_WIDGET (window))); + } +} + +static void +gimp_image_window_hide_tooltip (GimpUIManager *manager, + GimpImageWindow *window) +{ + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + GimpStatusbar *statusbar = NULL; + + if (! shell) + return; + + statusbar = gimp_display_shell_get_statusbar (shell); + + gimp_statusbar_pop (statusbar, "menu-tooltip"); +} + +static gboolean +gimp_image_window_update_ui_manager_idle (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + gimp_assert (private->active_shell != NULL); + + gimp_ui_manager_update (private->menubar_manager, + private->active_shell->display); + + private->update_ui_manager_idle_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +gimp_image_window_update_ui_manager (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + if (! private->update_ui_manager_idle_id) + { + private->update_ui_manager_idle_id = + g_idle_add_full (GIMP_PRIORITY_IMAGE_WINDOW_UPDATE_UI_MANAGER_IDLE, + (GSourceFunc) gimp_image_window_update_ui_manager_idle, + window, + NULL); + } +} + +static void +gimp_image_window_shell_size_allocate (GimpDisplayShell *shell, + GtkAllocation *allocation, + PosCorrectionData *data) +{ + GimpImageWindow *window = gimp_display_shell_get_window (shell); + gint new_window_x; + gint new_window_y; + + if (gtk_widget_translate_coordinates (GTK_WIDGET (shell->canvas), + GTK_WIDGET (window), + data->canvas_x, data->canvas_y, + &new_window_x, &new_window_y)) + { + gint off_x = new_window_x - data->window_x; + gint off_y = new_window_y - data->window_y; + + if (off_x || off_y) + gimp_display_shell_scroll (shell, off_x, off_y); + } + + g_signal_handlers_disconnect_by_func (shell, + gimp_image_window_shell_size_allocate, + data); +} + +static gboolean +gimp_image_window_shell_events (GtkWidget *widget, + GdkEvent *event, + GimpImageWindow *window) +{ + GimpDisplayShell *shell = gimp_image_window_get_active_shell (window); + + if (! shell) + return FALSE; + + return gimp_display_shell_events (widget, event, shell); +} + +static void +gimp_image_window_switch_page (GtkNotebook *notebook, + gpointer page, + gint page_num, + GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpDisplayShell *shell; + GimpDisplay *active_display; + + shell = GIMP_DISPLAY_SHELL (gtk_notebook_get_nth_page (notebook, page_num)); + + if (shell == private->active_shell) + return; + + gimp_image_window_disconnect_from_active_shell (window); + + GIMP_LOG (WM, "GimpImageWindow %p, private->active_shell = %p; \n", + window, shell); + private->active_shell = shell; + + gimp_window_set_primary_focus_widget (GIMP_WINDOW (window), + shell->canvas); + + active_display = private->active_shell->display; + + g_signal_connect (active_display, "notify::image", + G_CALLBACK (gimp_image_window_image_notify), + window); + + g_signal_connect (private->active_shell, "scaled", + G_CALLBACK (gimp_image_window_shell_scaled), + window); + g_signal_connect (private->active_shell, "rotated", + G_CALLBACK (gimp_image_window_shell_rotated), + window); + g_signal_connect (private->active_shell, "notify::title", + G_CALLBACK (gimp_image_window_shell_title_notify), + window); + g_signal_connect (private->active_shell, "notify::icon", + G_CALLBACK (gimp_image_window_shell_icon_notify), + window); + + gtk_window_set_title (GTK_WINDOW (window), shell->title); + gtk_window_set_icon (GTK_WINDOW (window), shell->icon); + + gimp_display_shell_appearance_update (private->active_shell); + + if (gtk_widget_get_window (GTK_WIDGET (window))) + { + /* we are fully initialized, use the window's current monitor + */ + gimp_image_window_session_update (window, + active_display, + NULL /*new_entry_id*/, + gtk_widget_get_screen (GTK_WIDGET (window)), + gimp_widget_get_monitor (GTK_WIDGET (window))); + } + else + { + /* we are in construction, use the initial monitor; calling + * gimp_widget_get_monitor() would get us the monitor where the + * pointer is + */ + gimp_image_window_session_update (window, + active_display, + NULL /*new_entry_id*/, + private->initial_screen, + private->initial_monitor); + } + + gimp_context_set_display (gimp_get_user_context (private->gimp), + active_display); + + gimp_image_window_update_ui_manager (window); + + gimp_image_window_update_tab_labels (window); +} + +static void +gimp_image_window_page_removed (GtkNotebook *notebook, + GtkWidget *widget, + gint page_num, + GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + if (GTK_WIDGET (private->active_shell) == widget) + { + GIMP_LOG (WM, "GimpImageWindow %p, private->active_shell = %p; \n", + window, NULL); + gimp_image_window_disconnect_from_active_shell (window); + private->active_shell = NULL; + } +} + +static void +gimp_image_window_page_reordered (GtkNotebook *notebook, + GtkWidget *widget, + gint page_num, + GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpContainer *displays = private->gimp->displays; + gint index = g_list_index (private->shells, widget); + + if (index != page_num) + { + private->shells = g_list_remove (private->shells, widget); + private->shells = g_list_insert (private->shells, widget, page_num); + } + + /* We need to reorder the displays as well in order to update the + * numbered accelerators (alt-1, alt-2, etc.). + */ + gimp_container_reorder (displays, + GIMP_OBJECT (GIMP_DISPLAY_SHELL (widget)->display), + page_num); + + gtk_notebook_reorder_child (notebook, widget, page_num); +} + +static void +gimp_image_window_disconnect_from_active_shell (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpDisplay *active_display = NULL; + + if (! private->active_shell) + return; + + active_display = private->active_shell->display; + + if (active_display) + g_signal_handlers_disconnect_by_func (active_display, + gimp_image_window_image_notify, + window); + + g_signal_handlers_disconnect_by_func (private->active_shell, + gimp_image_window_shell_scaled, + window); + g_signal_handlers_disconnect_by_func (private->active_shell, + gimp_image_window_shell_rotated, + window); + g_signal_handlers_disconnect_by_func (private->active_shell, + gimp_image_window_shell_title_notify, + window); + g_signal_handlers_disconnect_by_func (private->active_shell, + gimp_image_window_shell_icon_notify, + window); + + if (private->menubar_manager) + gimp_image_window_hide_tooltip (private->menubar_manager, window); + + if (private->update_ui_manager_idle_id) + { + g_source_remove (private->update_ui_manager_idle_id); + private->update_ui_manager_idle_id = 0; + } +} + +static void +gimp_image_window_image_notify (GimpDisplay *display, + const GParamSpec *pspec, + GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GtkWidget *tab_label; + GList *children; + GtkWidget *view; + + gimp_image_window_session_update (window, + display, + NULL /*new_entry_id*/, + gtk_widget_get_screen (GTK_WIDGET (window)), + gimp_widget_get_monitor (GTK_WIDGET (window))); + + tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (private->notebook), + GTK_WIDGET (gimp_display_get_shell (display))); + children = gtk_container_get_children (GTK_CONTAINER (tab_label)); + view = GTK_WIDGET (children->data); + g_list_free (children); + + gimp_view_set_viewable (GIMP_VIEW (view), + GIMP_VIEWABLE (gimp_display_get_image (display))); + + gimp_image_window_update_ui_manager (window); +} + +static void +gimp_image_window_session_clear (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GtkWidget *widget = GTK_WIDGET (window); + + if (gimp_dialog_factory_from_widget (widget, NULL)) + gimp_dialog_factory_remove_dialog (private->dialog_factory, + widget); +} + +static void +gimp_image_window_session_apply (GimpImageWindow *window, + const gchar *entry_id, + GdkScreen *screen, + gint monitor) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GimpSessionInfo *session_info = NULL; + gint width = -1; + gint height = -1; + + gtk_window_unfullscreen (GTK_WINDOW (window)); + + /* get the NIW size before adding the display to the dialog + * factory so the window's current size doesn't affect the + * stored session info entry. + */ + session_info = + gimp_dialog_factory_find_session_info (private->dialog_factory, entry_id); + + if (session_info) + { + width = gimp_session_info_get_width (session_info); + height = gimp_session_info_get_height (session_info); + } + else + { + GtkAllocation allocation; + + gtk_widget_get_allocation (GTK_WIDGET (window), &allocation); + + width = allocation.width; + height = allocation.height; + } + + gimp_dialog_factory_add_foreign (private->dialog_factory, + entry_id, + GTK_WIDGET (window), + screen, + monitor); + + gtk_window_unmaximize (GTK_WINDOW (window)); + gtk_window_resize (GTK_WINDOW (window), width, height); +} + +static void +gimp_image_window_session_update (GimpImageWindow *window, + GimpDisplay *new_display, + const gchar *new_entry_id, + GdkScreen *screen, + gint monitor) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + + /* Handle changes to the entry id */ + if (new_entry_id) + { + if (! private->entry_id) + { + /* We're initializing. If we're in single-window mode, this + * will be the only window, so start to session manage + * it. If we're in multi-window mode, we will find out if we + * should session manage ourselves when we get a display + */ + if (strcmp (new_entry_id, GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID) == 0) + { + gimp_image_window_session_apply (window, new_entry_id, + screen, monitor); + } + } + else if (strcmp (private->entry_id, new_entry_id) != 0) + { + /* The entry id changed, immediately and always stop session + * managing the old entry + */ + gimp_image_window_session_clear (window); + + if (strcmp (new_entry_id, GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID) == 0) + { + /* If there is only one imageless display, we shall + * become the empty image window + */ + if (private->active_shell && + private->active_shell->display && + ! gimp_display_get_image (private->active_shell->display) && + g_list_length (private->shells) <= 1) + { + gimp_image_window_session_apply (window, new_entry_id, + screen, monitor); + } + } + else if (strcmp (new_entry_id, GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID) == 0) + { + /* As soon as we become the single image window, we + * shall session manage ourself until single-window mode + * is exited + */ + gimp_image_window_session_apply (window, new_entry_id, + screen, monitor); + } + } + + private->entry_id = new_entry_id; + } + + /* Handle changes to the displays. When in single-window mode, we + * just keep session managing the single image window. We only need + * to care about the multi-window mode case here + */ + if (new_display && + strcmp (private->entry_id, GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID) == 0) + { + if (gimp_display_get_image (new_display)) + { + /* As soon as we have an image we should not affect the size of the + * empty image window + */ + gimp_image_window_session_clear (window); + } + else if (! gimp_display_get_image (new_display) && + g_list_length (private->shells) <= 1) + { + /* As soon as we have no image (and no other shells that may + * contain images) we should become the empty image window + */ + gimp_image_window_session_apply (window, private->entry_id, + screen, monitor); + } + } +} + +static const gchar * +gimp_image_window_config_to_entry_id (GimpGuiConfig *config) +{ + return (config->single_window_mode ? + GIMP_SINGLE_IMAGE_WINDOW_ENTRY_ID : + GIMP_EMPTY_IMAGE_WINDOW_ENTRY_ID); +} + +static void +gimp_image_window_shell_scaled (GimpDisplayShell *shell, + GimpImageWindow *window) +{ + /* update the <Image>/View/Zoom menu */ + gimp_image_window_update_ui_manager (window); +} + +static void +gimp_image_window_shell_rotated (GimpDisplayShell *shell, + GimpImageWindow *window) +{ + /* update the <Image>/View/Rotate menu */ + gimp_image_window_update_ui_manager (window); +} + +static void +gimp_image_window_shell_title_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpImageWindow *window) +{ + gtk_window_set_title (GTK_WINDOW (window), shell->title); +} + +static void +gimp_image_window_shell_icon_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpImageWindow *window) +{ + gtk_window_set_icon (GTK_WINDOW (window), shell->icon); +} + +static void +gimp_image_window_shell_close_button_callback (GimpDisplayShell *shell) +{ + if (shell) + gimp_display_shell_close (shell, FALSE); +} + +static GtkWidget * +gimp_image_window_create_tab_label (GimpImageWindow *window, + GimpDisplayShell *shell) +{ + GtkWidget *hbox; + GtkWidget *view; + GimpImage *image; + GtkWidget *button; + GtkWidget *gtk_image; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_widget_show (hbox); + + view = gimp_view_new_by_types (gimp_get_user_context (shell->display->gimp), + GIMP_TYPE_VIEW, GIMP_TYPE_IMAGE, + GIMP_VIEW_SIZE_LARGE, 0, FALSE); + gtk_widget_set_size_request (view, GIMP_VIEW_SIZE_LARGE, -1); + gimp_view_renderer_set_color_config (GIMP_VIEW (view)->renderer, + gimp_display_shell_get_color_config (shell)); + gtk_box_pack_start (GTK_BOX (hbox), view, FALSE, FALSE, 0); + gtk_widget_show (view); + + image = gimp_display_get_image (shell->display); + if (image) + gimp_view_set_viewable (GIMP_VIEW (view), GIMP_VIEWABLE (image)); + + button = gtk_button_new (); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + gtk_image = gtk_image_new_from_icon_name (GIMP_ICON_CLOSE, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (button), gtk_image); + gtk_widget_show (gtk_image); + + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_image_window_shell_close_button_callback), + shell); + + g_object_set_data (G_OBJECT (hbox), "close-button", button); + + return hbox; +} + +static void +gimp_image_window_update_tab_labels (GimpImageWindow *window) +{ + GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); + GList *children; + GList *list; + + children = gtk_container_get_children (GTK_CONTAINER (private->notebook)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *shell = list->data; + GtkWidget *tab_widget; + GtkWidget *close_button; + + tab_widget = gtk_notebook_get_tab_label (GTK_NOTEBOOK (private->notebook), + shell); + + close_button = g_object_get_data (G_OBJECT (tab_widget), "close-button"); + + if (gimp_context_get_display (gimp_get_user_context (private->gimp)) == + GIMP_DISPLAY_SHELL (shell)->display) + { + gtk_widget_show (close_button); + } + else + { + gtk_widget_hide (close_button); + } + } + + g_list_free (children); +} diff --git a/app/display/gimpimagewindow.h b/app/display/gimpimagewindow.h new file mode 100644 index 0000000..121dc71 --- /dev/null +++ b/app/display/gimpimagewindow.h @@ -0,0 +1,100 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_IMAGE_WINDOW_H__ +#define __GIMP_IMAGE_WINDOW_H__ + + +#include "widgets/gimpwindow.h" + + +#define GIMP_TYPE_IMAGE_WINDOW (gimp_image_window_get_type ()) +#define GIMP_IMAGE_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_WINDOW, GimpImageWindow)) +#define GIMP_IMAGE_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_WINDOW, GimpImageWindowClass)) +#define GIMP_IS_IMAGE_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_WINDOW)) +#define GIMP_IS_IMAGE_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_WINDOW)) +#define GIMP_IMAGE_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_WINDOW, GimpImageWindowClass)) + + +typedef struct _GimpImageWindowClass GimpImageWindowClass; + +struct _GimpImageWindow +{ + GimpWindow parent_instance; +}; + +struct _GimpImageWindowClass +{ + GimpWindowClass parent_class; +}; + + +GType gimp_image_window_get_type (void) G_GNUC_CONST; + +GimpImageWindow * gimp_image_window_new (Gimp *gimp, + GimpImage *image, + GimpDialogFactory *dialog_factory, + GdkScreen *screen, + gint monitor); +void gimp_image_window_destroy (GimpImageWindow *window); + +GimpUIManager * gimp_image_window_get_ui_manager (GimpImageWindow *window); +GimpDockColumns * gimp_image_window_get_left_docks (GimpImageWindow *window); +GimpDockColumns * gimp_image_window_get_right_docks (GimpImageWindow *window); + +void gimp_image_window_add_shell (GimpImageWindow *window, + GimpDisplayShell *shell); +GimpDisplayShell * gimp_image_window_get_shell (GimpImageWindow *window, + gint index); +void gimp_image_window_remove_shell (GimpImageWindow *window, + GimpDisplayShell *shell); + +gint gimp_image_window_get_n_shells (GimpImageWindow *window); + +void gimp_image_window_set_active_shell (GimpImageWindow *window, + GimpDisplayShell *shell); +GimpDisplayShell * gimp_image_window_get_active_shell (GimpImageWindow *window); + +void gimp_image_window_set_fullscreen (GimpImageWindow *window, + gboolean fullscreen); +gboolean gimp_image_window_get_fullscreen (GimpImageWindow *window); + +void gimp_image_window_set_show_menubar (GimpImageWindow *window, + gboolean show); +gboolean gimp_image_window_get_show_menubar (GimpImageWindow *window); + +void gimp_image_window_set_show_statusbar (GimpImageWindow *window, + gboolean show); +gboolean gimp_image_window_get_show_statusbar (GimpImageWindow *window); + +gboolean gimp_image_window_is_iconified (GimpImageWindow *window); +gboolean gimp_image_window_is_maximized (GimpImageWindow *window); + +gboolean gimp_image_window_has_toolbox (GimpImageWindow *window); + +void gimp_image_window_shrink_wrap (GimpImageWindow *window, + gboolean grow_only); + +GtkWidget * gimp_image_window_get_default_dockbook (GimpImageWindow *window); + +void gimp_image_window_keep_canvas_pos (GimpImageWindow *window); +void gimp_image_window_suspend_keep_pos (GimpImageWindow *window); +void gimp_image_window_resume_keep_pos (GimpImageWindow *window); + +void gimp_image_window_update_tabs (GimpImageWindow *window); + +#endif /* __GIMP_IMAGE_WINDOW_H__ */ diff --git a/app/display/gimpmotionbuffer.c b/app/display/gimpmotionbuffer.c new file mode 100644 index 0000000..549f6f0 --- /dev/null +++ b/app/display/gimpmotionbuffer.c @@ -0,0 +1,594 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpcoords.h" +#include "core/gimpcoords-interpolate.h" +#include "core/gimpmarshal.h" + +#include "gimpmotionbuffer.h" + + +/* Velocity unit is screen pixels per millisecond we pass to tools as 1. */ +#define VELOCITY_UNIT 3.0 +#define EVENT_FILL_PRECISION 6.0 +#define DIRECTION_RADIUS (1.0 / MAX (scale_x, scale_y)) +#define SMOOTH_FACTOR 0.3 + + +enum +{ + PROP_0 +}; + +enum +{ + STROKE, + HOVER, + LAST_SIGNAL +}; + + +/* local function prototypes */ + +static void gimp_motion_buffer_dispose (GObject *object); +static void gimp_motion_buffer_finalize (GObject *object); +static void gimp_motion_buffer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_motion_buffer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_motion_buffer_push_event_history (GimpMotionBuffer *buffer, + const GimpCoords *coords); +static void gimp_motion_buffer_pop_event_queue (GimpMotionBuffer *buffer, + GimpCoords *coords); + +static void gimp_motion_buffer_interpolate_stroke (GimpMotionBuffer *buffer, + GimpCoords *coords); +static gboolean gimp_motion_buffer_event_queue_timeout (GimpMotionBuffer *buffer); + + +G_DEFINE_TYPE (GimpMotionBuffer, gimp_motion_buffer, GIMP_TYPE_OBJECT) + +#define parent_class gimp_motion_buffer_parent_class + +static guint motion_buffer_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_motion_buffer_class_init (GimpMotionBufferClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + motion_buffer_signals[STROKE] = + g_signal_new ("stroke", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpMotionBufferClass, stroke), + NULL, NULL, + gimp_marshal_VOID__POINTER_UINT_FLAGS, + G_TYPE_NONE, 3, + G_TYPE_POINTER, + G_TYPE_UINT, + GDK_TYPE_MODIFIER_TYPE); + + motion_buffer_signals[HOVER] = + g_signal_new ("hover", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpMotionBufferClass, hover), + NULL, NULL, + gimp_marshal_VOID__POINTER_FLAGS_BOOLEAN, + G_TYPE_NONE, 3, + G_TYPE_POINTER, + GDK_TYPE_MODIFIER_TYPE, + G_TYPE_BOOLEAN); + + object_class->dispose = gimp_motion_buffer_dispose; + object_class->finalize = gimp_motion_buffer_finalize; + object_class->set_property = gimp_motion_buffer_set_property; + object_class->get_property = gimp_motion_buffer_get_property; +} + +static void +gimp_motion_buffer_init (GimpMotionBuffer *buffer) +{ + buffer->event_history = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); + buffer->event_queue = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); +} + +static void +gimp_motion_buffer_dispose (GObject *object) +{ + GimpMotionBuffer *buffer = GIMP_MOTION_BUFFER (object); + + if (buffer->event_delay_timeout) + { + g_source_remove (buffer->event_delay_timeout); + buffer->event_delay_timeout = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_motion_buffer_finalize (GObject *object) +{ + GimpMotionBuffer *buffer = GIMP_MOTION_BUFFER (object); + + if (buffer->event_history) + { + g_array_free (buffer->event_history, TRUE); + buffer->event_history = NULL; + } + + if (buffer->event_queue) + { + g_array_free (buffer->event_queue, TRUE); + buffer->event_queue = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_motion_buffer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_motion_buffer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +GimpMotionBuffer * +gimp_motion_buffer_new (void) +{ + return g_object_new (GIMP_TYPE_MOTION_BUFFER, + NULL); +} + +void +gimp_motion_buffer_begin_stroke (GimpMotionBuffer *buffer, + guint32 time, + GimpCoords *last_motion) +{ + g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer)); + g_return_if_fail (last_motion != NULL); + + buffer->last_read_motion_time = time; + + *last_motion = buffer->last_coords; +} + +void +gimp_motion_buffer_end_stroke (GimpMotionBuffer *buffer) +{ + g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer)); + + if (buffer->event_delay_timeout) + { + g_source_remove (buffer->event_delay_timeout); + buffer->event_delay_timeout = 0; + } + + gimp_motion_buffer_event_queue_timeout (buffer); +} + +/** + * gimp_motion_buffer_motion_event: + * @buffer: + * @coords: + * @time: + * @event_fill: + * + * This function evaluates the event to decide if the change is big + * enough to need handling and returns FALSE, if change is less than + * set filter level taking a whole lot of load off any draw tools that + * have no use for these events anyway. If the event is seen fit at + * first look, it is evaluated for speed and smoothed. Due to lousy + * time resolution of events pretty strong smoothing is applied to + * timestamps for sensible speed result. This function is also ideal + * for other event adjustment like pressure curve or calculating other + * derived dynamics factors like angular velocity calculation from + * tilt values, to allow for even more dynamic brushes. Calculated + * distance to last event is stored in GimpCoords because its a + * sideproduct of velocity calculation and is currently calculated in + * each tool. If they were to use this distance, more resources on + * recalculating the same value would be saved. + * + * Return value: %TRUE if the motion was significant enough to be + * processed, %FALSE otherwise. + **/ +gboolean +gimp_motion_buffer_motion_event (GimpMotionBuffer *buffer, + GimpCoords *coords, + guint32 time, + gboolean event_fill) +{ + gdouble delta_time = 0.001; + gdouble delta_x = 0.0; + gdouble delta_y = 0.0; + gdouble distance = 1.0; + gdouble scale_x = coords->xscale; + gdouble scale_y = coords->yscale; + + g_return_val_if_fail (GIMP_IS_MOTION_BUFFER (buffer), FALSE); + g_return_val_if_fail (coords != NULL, FALSE); + + /* the last_read_motion_time most be set unconditionally, so set + * it early + */ + buffer->last_read_motion_time = time; + + delta_time = (buffer->last_motion_delta_time * (1 - SMOOTH_FACTOR) + + (time - buffer->last_motion_time) * SMOOTH_FACTOR); + + if (buffer->last_motion_time == 0) + { + /* First pair is invalid to do any velocity calculation, so we + * apply a constant value. + */ + coords->velocity = 1.0; + } + else + { + GimpCoords last_dir_event = buffer->last_coords; + gdouble filter; + gdouble dist; + gdouble delta_dir; + gdouble dir_delta_x = 0.0; + gdouble dir_delta_y = 0.0; + + delta_x = last_dir_event.x - coords->x; + delta_y = last_dir_event.y - coords->y; + + /* Events with distances less than the screen resolution are + * not worth handling. + */ + filter = MIN (1.0 / scale_x, 1.0 / scale_y) / 2.0; + + if (fabs (delta_x) < filter && + fabs (delta_y) < filter) + { + return FALSE; + } + + distance = dist = sqrt (SQR (delta_x) + SQR (delta_y)); + + /* If even smoothed time resolution does not allow to guess for + * speed, use last velocity. + */ + if (delta_time == 0) + { + coords->velocity = buffer->last_coords.velocity; + } + else + { + /* We need to calculate the velocity in screen coordinates + * for human interaction + */ + gdouble screen_distance = (distance * MIN (scale_x, scale_y)); + + /* Calculate raw valocity */ + coords->velocity = ((screen_distance / delta_time) / VELOCITY_UNIT); + + /* Adding velocity dependent smoothing, feels better in tools. */ + coords->velocity = (buffer->last_coords.velocity * + (1 - MIN (SMOOTH_FACTOR, coords->velocity)) + + coords->velocity * + MIN (SMOOTH_FACTOR, coords->velocity)); + + /* Speed needs upper limit */ + coords->velocity = MIN (coords->velocity, 1.0); + } + + if (((fabs (delta_x) > DIRECTION_RADIUS) && + (fabs (delta_y) > DIRECTION_RADIUS)) || + (buffer->event_history->len < 4)) + { + dir_delta_x = delta_x; + dir_delta_y = delta_y; + } + else + { + gint x = CLAMP ((buffer->event_history->len - 1), 3, 15); + + while (((fabs (dir_delta_x) < DIRECTION_RADIUS) || + (fabs (dir_delta_y) < DIRECTION_RADIUS)) && + (x >= 0)) + { + last_dir_event = g_array_index (buffer->event_history, + GimpCoords, x); + + dir_delta_x = last_dir_event.x - coords->x; + dir_delta_y = last_dir_event.y - coords->y; + + x--; + } + } + + if ((fabs (dir_delta_x) < DIRECTION_RADIUS) || + (fabs (dir_delta_y) < DIRECTION_RADIUS)) + { + coords->direction = buffer->last_coords.direction; + } + else + { + coords->direction = gimp_coords_direction (&last_dir_event, coords); + } + + coords->direction = coords->direction - floor (coords->direction); + + delta_dir = coords->direction - buffer->last_coords.direction; + + if (delta_dir < -0.5) + { + coords->direction = (0.5 * coords->direction + + 0.5 * (buffer->last_coords.direction - 1.0)); + } + else if (delta_dir > 0.5) + { + coords->direction = (0.5 * coords->direction + + 0.5 * (buffer->last_coords.direction + 1.0)); + } + else + { + coords->direction = (0.5 * coords->direction + + 0.5 * buffer->last_coords.direction); + } + + coords->direction = coords->direction - floor (coords->direction); + + /* do event fill for devices that do not provide enough events */ + if (distance >= EVENT_FILL_PRECISION && + event_fill && + buffer->event_history->len >= 2) + { + if (buffer->event_delay) + { + gimp_motion_buffer_interpolate_stroke (buffer, coords); + } + else + { + buffer->event_delay = TRUE; + gimp_motion_buffer_push_event_history (buffer, coords); + } + } + else + { + if (buffer->event_delay) + buffer->event_delay = FALSE; + + gimp_motion_buffer_push_event_history (buffer, coords); + } + +#ifdef EVENT_VERBOSE + g_printerr ("DIST: %f, DT:%f, Vel:%f, Press:%f,smooth_dd:%f, POS: (%f, %f)\n", + distance, + delta_time, + buffer->last_coords.velocity, + coords->pressure, + distance - dist, + coords->x, + coords->y); +#endif + } + + g_array_append_val (buffer->event_queue, *coords); + + buffer->last_coords = *coords; + buffer->last_motion_time = time; + buffer->last_motion_delta_time = delta_time; + buffer->last_motion_delta_x = delta_x; + buffer->last_motion_delta_y = delta_y; + buffer->last_motion_distance = distance; + + return TRUE; +} + +guint32 +gimp_motion_buffer_get_last_motion_time (GimpMotionBuffer *buffer) +{ + g_return_val_if_fail (GIMP_IS_MOTION_BUFFER (buffer), 0); + + return buffer->last_read_motion_time; +} + +void +gimp_motion_buffer_request_stroke (GimpMotionBuffer *buffer, + GdkModifierType state, + guint32 time) +{ + GdkModifierType event_state; + gint keep = 0; + + g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer)); + + if (buffer->event_delay) + { + /* If we are in delay we use LAST state, not current */ + event_state = buffer->last_active_state; + + keep = 1; /* Holding one event in buf */ + } + else + { + /* Save the state */ + event_state = state; + } + + if (buffer->event_delay_timeout) + { + g_source_remove (buffer->event_delay_timeout); + buffer->event_delay_timeout = 0; + } + + buffer->last_active_state = state; + + while (buffer->event_queue->len > keep) + { + GimpCoords buf_coords; + + gimp_motion_buffer_pop_event_queue (buffer, &buf_coords); + + g_signal_emit (buffer, motion_buffer_signals[STROKE], 0, + &buf_coords, time, event_state); + } + + if (buffer->event_delay) + { + buffer->event_delay_timeout = + g_timeout_add (50, + (GSourceFunc) gimp_motion_buffer_event_queue_timeout, + buffer); + } +} + +void +gimp_motion_buffer_request_hover (GimpMotionBuffer *buffer, + GdkModifierType state, + gboolean proximity) +{ + g_return_if_fail (GIMP_IS_MOTION_BUFFER (buffer)); + + if (buffer->event_queue->len > 0) + { + GimpCoords buf_coords = g_array_index (buffer->event_queue, + GimpCoords, + buffer->event_queue->len - 1); + + g_signal_emit (buffer, motion_buffer_signals[HOVER], 0, + &buf_coords, state, proximity); + + g_array_set_size (buffer->event_queue, 0); + } +} + + +/* private functions */ + +static void +gimp_motion_buffer_push_event_history (GimpMotionBuffer *buffer, + const GimpCoords *coords) +{ + if (buffer->event_history->len == 4) + g_array_remove_index (buffer->event_history, 0); + + g_array_append_val (buffer->event_history, *coords); +} + +static void +gimp_motion_buffer_pop_event_queue (GimpMotionBuffer *buffer, + GimpCoords *coords) +{ + *coords = g_array_index (buffer->event_queue, GimpCoords, 0); + + g_array_remove_index (buffer->event_queue, 0); +} + +static void +gimp_motion_buffer_interpolate_stroke (GimpMotionBuffer *buffer, + GimpCoords *coords) +{ + GimpCoords catmull[4]; + GArray *ret_coords; + gint i = buffer->event_history->len - 1; + + /* Note that there must be exactly one event in buffer or bad things + * can happen. This must never get called under other circumstances. + */ + ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); + + catmull[0] = g_array_index (buffer->event_history, GimpCoords, i - 1); + catmull[1] = g_array_index (buffer->event_history, GimpCoords, i); + catmull[2] = g_array_index (buffer->event_queue, GimpCoords, 0); + catmull[3] = *coords; + + gimp_coords_interpolate_catmull (catmull, EVENT_FILL_PRECISION / 2, + ret_coords, NULL); + + /* Push the last actual event in history */ + gimp_motion_buffer_push_event_history (buffer, + &g_array_index (buffer->event_queue, + GimpCoords, 0)); + + g_array_set_size (buffer->event_queue, 0); + + g_array_append_vals (buffer->event_queue, + &g_array_index (ret_coords, GimpCoords, 0), + ret_coords->len); + + g_array_free (ret_coords, TRUE); +} + +static gboolean +gimp_motion_buffer_event_queue_timeout (GimpMotionBuffer *buffer) +{ + buffer->event_delay = FALSE; + buffer->event_delay_timeout = 0; + + if (buffer->event_queue->len > 0) + { + GimpCoords last_coords = g_array_index (buffer->event_queue, + GimpCoords, + buffer->event_queue->len - 1); + + gimp_motion_buffer_push_event_history (buffer, &last_coords); + + gimp_motion_buffer_request_stroke (buffer, + buffer->last_active_state, + buffer->last_read_motion_time); + } + + return FALSE; +} diff --git a/app/display/gimpmotionbuffer.h b/app/display/gimpmotionbuffer.h new file mode 100644 index 0000000..c7aee64 --- /dev/null +++ b/app/display/gimpmotionbuffer.h @@ -0,0 +1,99 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmotionbuffer.h + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_MOTION_BUFFER_H__ +#define __GIMP_MOTION_BUFFER_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_MOTION_BUFFER (gimp_motion_buffer_get_type ()) +#define GIMP_MOTION_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MOTION_BUFFER, GimpMotionBuffer)) +#define GIMP_MOTION_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MOTION_BUFFER, GimpMotionBufferClass)) +#define GIMP_IS_MOTION_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MOTION_BUFFER)) +#define GIMP_IS_MOTION_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MOTION_BUFFER)) +#define GIMP_MOTION_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MOTION_BUFFER, GimpMotionBufferClass)) + + +typedef struct _GimpMotionBufferClass GimpMotionBufferClass; + +struct _GimpMotionBuffer +{ + GimpObject parent_instance; + + guint32 last_read_motion_time; + + guint32 last_motion_time; /* previous time of a forwarded motion event */ + gdouble last_motion_delta_time; + gdouble last_motion_delta_x; + gdouble last_motion_delta_y; + gdouble last_motion_distance; + + GimpCoords last_coords; /* last motion event */ + + GArray *event_history; + GArray *event_queue; + gboolean event_delay; /* TRUE if there's an unsent event in + * the history buffer + */ + + gint event_delay_timeout; + GdkModifierType last_active_state; +}; + +struct _GimpMotionBufferClass +{ + GimpObjectClass parent_class; + + void (* stroke) (GimpMotionBuffer *buffer, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + void (* hover) (GimpMotionBuffer *buffer, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +}; + + +GType gimp_motion_buffer_get_type (void) G_GNUC_CONST; + +GimpMotionBuffer * gimp_motion_buffer_new (void); + +void gimp_motion_buffer_begin_stroke (GimpMotionBuffer *buffer, + guint32 time, + GimpCoords *last_motion); +void gimp_motion_buffer_end_stroke (GimpMotionBuffer *buffer); + +gboolean gimp_motion_buffer_motion_event (GimpMotionBuffer *buffer, + GimpCoords *coords, + guint32 time, + gboolean event_fill); +guint32 gimp_motion_buffer_get_last_motion_time (GimpMotionBuffer *buffer); + +void gimp_motion_buffer_request_stroke (GimpMotionBuffer *buffer, + GdkModifierType state, + guint32 time); +void gimp_motion_buffer_request_hover (GimpMotionBuffer *buffer, + GdkModifierType state, + gboolean proximity); + + +#endif /* __GIMP_MOTION_BUFFER_H__ */ diff --git a/app/display/gimpmultiwindowstrategy.c b/app/display/gimpmultiwindowstrategy.c new file mode 100644 index 0000000..ca5cb22 --- /dev/null +++ b/app/display/gimpmultiwindowstrategy.c @@ -0,0 +1,89 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmultiwindowstrategy.c + * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimp.h" + +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpwindowstrategy.h" + +#include "gimpmultiwindowstrategy.h" + + +static void gimp_multi_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface); +static GtkWidget * gimp_multi_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers); + + +G_DEFINE_TYPE_WITH_CODE (GimpMultiWindowStrategy, gimp_multi_window_strategy, GIMP_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_WINDOW_STRATEGY, + gimp_multi_window_strategy_window_strategy_iface_init)) + +#define parent_class gimp_multi_window_strategy_parent_class + + +static void +gimp_multi_window_strategy_class_init (GimpMultiWindowStrategyClass *klass) +{ +} + +static void +gimp_multi_window_strategy_init (GimpMultiWindowStrategy *strategy) +{ +} + +static void +gimp_multi_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface) +{ + iface->show_dockable_dialog = gimp_multi_window_strategy_show_dockable_dialog; +} + +static GtkWidget * +gimp_multi_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers) +{ + return gimp_dialog_factory_dialog_raise (factory, screen, monitor, + identifiers, -1); +} + +GimpObject * +gimp_multi_window_strategy_get_singleton (void) +{ + static GimpObject *singleton = NULL; + + if (! singleton) + singleton = g_object_new (GIMP_TYPE_MULTI_WINDOW_STRATEGY, NULL); + + return singleton; +} diff --git a/app/display/gimpmultiwindowstrategy.h b/app/display/gimpmultiwindowstrategy.h new file mode 100644 index 0000000..4df8873 --- /dev/null +++ b/app/display/gimpmultiwindowstrategy.h @@ -0,0 +1,54 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmultiwindowstrategy.h + * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_MULTI_WINDOW_STRATEGY_H__ +#define __GIMP_MULTI_WINDOW_STRATEGY_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_MULTI_WINDOW_STRATEGY (gimp_multi_window_strategy_get_type ()) +#define GIMP_MULTI_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MULTI_WINDOW_STRATEGY, GimpMultiWindowStrategy)) +#define GIMP_MULTI_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MULTI_WINDOW_STRATEGY, GimpMultiWindowStrategyClass)) +#define GIMP_IS_MULTI_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MULTI_WINDOW_STRATEGY)) +#define GIMP_IS_MULTI_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MULTI_WINDOW_STRATEGY)) +#define GIMP_MULTI_WINDOW_STRATEGY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MULTI_WINDOW_STRATEGY, GimpMultiWindowStrategyClass)) + + +typedef struct _GimpMultiWindowStrategyClass GimpMultiWindowStrategyClass; + +struct _GimpMultiWindowStrategy +{ + GimpObject parent_instance; +}; + +struct _GimpMultiWindowStrategyClass +{ + GimpObjectClass parent_class; +}; + + +GType gimp_multi_window_strategy_get_type (void) G_GNUC_CONST; + +GimpObject * gimp_multi_window_strategy_get_singleton (void); + + +#endif /* __GIMP_MULTI_WINDOW_STRATEGY_H__ */ diff --git a/app/display/gimpnavigationeditor.c b/app/display/gimpnavigationeditor.c new file mode 100644 index 0000000..a518882 --- /dev/null +++ b/app/display/gimpnavigationeditor.c @@ -0,0 +1,890 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpnavigationeditor.c + * Copyright (C) 2001 Michael Natterer <mitch@gimp.org> + * + * partly based on app/nav_window + * Copyright (C) 1999 Andy Thomas <alt@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimageproxy.h" + +#include "widgets/gimpdocked.h" +#include "widgets/gimphelp-ids.h" +#include "widgets/gimpmenufactory.h" +#include "widgets/gimpnavigationview.h" +#include "widgets/gimpuimanager.h" +#include "widgets/gimpviewrenderer.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-scale.h" +#include "gimpdisplayshell-scroll.h" +#include "gimpdisplayshell-transform.h" +#include "gimpnavigationeditor.h" + +#include "gimp-intl.h" + + +#define UPDATE_DELAY 300 /* From GtkRange in GTK+ 2.22 */ + + +static void gimp_navigation_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_navigation_editor_dispose (GObject *object); + +static void gimp_navigation_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static GtkWidget * gimp_navigation_editor_new_private (GimpMenuFactory *menu_factory, + GimpDisplayShell *shell); + +static void gimp_navigation_editor_set_shell (GimpNavigationEditor *editor, + GimpDisplayShell *shell); +static gboolean gimp_navigation_editor_button_release (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell); +static void gimp_navigation_editor_marker_changed (GimpNavigationView *view, + gdouble center_x, + gdouble center_y, + gdouble width, + gdouble height, + GimpNavigationEditor *editor); +static void gimp_navigation_editor_zoom (GimpNavigationView *view, + GimpZoomType direction, + GimpNavigationEditor *editor); +static void gimp_navigation_editor_scroll (GimpNavigationView *view, + GdkScrollDirection direction, + GimpNavigationEditor *editor); + +static void gimp_navigation_editor_zoom_adj_changed (GtkAdjustment *adj, + GimpNavigationEditor *editor); + +static void gimp_navigation_editor_shell_infinite_canvas_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpNavigationEditor *editor); +static void gimp_navigation_editor_shell_scaled (GimpDisplayShell *shell, + GimpNavigationEditor *editor); +static void gimp_navigation_editor_shell_scrolled (GimpDisplayShell *shell, + GimpNavigationEditor *editor); +static void gimp_navigation_editor_shell_rotated (GimpDisplayShell *shell, + GimpNavigationEditor *editor); +static void gimp_navigation_editor_shell_reconnect (GimpDisplayShell *shell, + GimpNavigationEditor *editor); + +static void gimp_navigation_editor_viewable_size_changed (GimpViewable *viewable, + GimpNavigationEditor *editor); + +static void gimp_navigation_editor_options_show_canvas_notify (GimpDisplayOptions *options, + const GParamSpec *pspec, + GimpNavigationEditor *editor); + +static void gimp_navigation_editor_update_marker (GimpNavigationEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpNavigationEditor, gimp_navigation_editor, + GIMP_TYPE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_navigation_editor_docked_iface_init)) + +#define parent_class gimp_navigation_editor_parent_class + + +static void +gimp_navigation_editor_class_init (GimpNavigationEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_navigation_editor_dispose; +} + +static void +gimp_navigation_editor_docked_iface_init (GimpDockedInterface *iface) +{ + iface->set_context = gimp_navigation_editor_set_context; +} + +static void +gimp_navigation_editor_init (GimpNavigationEditor *editor) +{ + GtkWidget *frame; + + editor->context = NULL; + editor->shell = NULL; + editor->scale_timeout = 0; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + editor->view = gimp_view_new_by_types (NULL, + GIMP_TYPE_NAVIGATION_VIEW, + GIMP_TYPE_IMAGE_PROXY, + GIMP_VIEW_SIZE_MEDIUM, 0, TRUE); + gtk_container_add (GTK_CONTAINER (frame), editor->view); + gtk_widget_show (editor->view); + + g_signal_connect (editor->view, "marker-changed", + G_CALLBACK (gimp_navigation_editor_marker_changed), + editor); + g_signal_connect (editor->view, "zoom", + G_CALLBACK (gimp_navigation_editor_zoom), + editor); + g_signal_connect (editor->view, "scroll", + G_CALLBACK (gimp_navigation_editor_scroll), + editor); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); +} + +static void +gimp_navigation_editor_dispose (GObject *object) +{ + GimpNavigationEditor *editor = GIMP_NAVIGATION_EDITOR (object); + + if (editor->shell) + gimp_navigation_editor_set_shell (editor, NULL); + + if (editor->scale_timeout) + { + g_source_remove (editor->scale_timeout); + editor->scale_timeout = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_navigation_editor_display_changed (GimpContext *context, + GimpDisplay *display, + GimpNavigationEditor *editor) +{ + GimpDisplayShell *shell = NULL; + + if (display && gimp_display_get_image (display)) + shell = gimp_display_get_shell (display); + + gimp_navigation_editor_set_shell (editor, shell); +} + +static void +gimp_navigation_editor_image_chaged (GimpContext *context, + GimpImage *image, + GimpNavigationEditor *editor) +{ + GimpDisplay *display = gimp_context_get_display (context); + GimpDisplayShell *shell = NULL; + + if (display && image) + shell = gimp_display_get_shell (display); + + gimp_navigation_editor_set_shell (editor, shell); +} + +static void +gimp_navigation_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpNavigationEditor *editor = GIMP_NAVIGATION_EDITOR (docked); + GimpDisplay *display = NULL; + + if (editor->context) + { + g_signal_handlers_disconnect_by_func (editor->context, + gimp_navigation_editor_display_changed, + editor); + g_signal_handlers_disconnect_by_func (editor->context, + gimp_navigation_editor_image_chaged, + editor); + } + + editor->context = context; + + if (editor->context) + { + g_signal_connect (context, "display-changed", + G_CALLBACK (gimp_navigation_editor_display_changed), + editor); + /* make sure to also call gimp_navigation_editor_set_shell() when the + * last image is closed, even though the display isn't changed, so that + * the editor is properly cleared. + */ + g_signal_connect (context, "image-changed", + G_CALLBACK (gimp_navigation_editor_image_chaged), + editor); + + display = gimp_context_get_display (context); + } + + gimp_view_renderer_set_context (GIMP_VIEW (editor->view)->renderer, + context); + + gimp_navigation_editor_display_changed (editor->context, + display, + editor); +} + + +/* public functions */ + +GtkWidget * +gimp_navigation_editor_new (GimpMenuFactory *menu_factory) +{ + return gimp_navigation_editor_new_private (menu_factory, NULL); +} + +void +gimp_navigation_editor_popup (GimpDisplayShell *shell, + GtkWidget *widget, + gint click_x, + gint click_y) +{ + GtkStyle *style = gtk_widget_get_style (widget); + GimpNavigationEditor *editor; + GimpNavigationView *view; + GdkScreen *screen; + gint x, y; + gint view_marker_center_x, view_marker_center_y; + gint view_marker_width, view_marker_height; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (! shell->nav_popup) + { + GtkWidget *frame; + + shell->nav_popup = gtk_window_new (GTK_WINDOW_POPUP); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (shell->nav_popup), frame); + gtk_widget_show (frame); + + editor = + GIMP_NAVIGATION_EDITOR (gimp_navigation_editor_new_private (NULL, + shell)); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (editor)); + gtk_widget_show (GTK_WIDGET (editor)); + + g_signal_connect (editor->view, "button-release-event", + G_CALLBACK (gimp_navigation_editor_button_release), + shell); + } + else + { + GtkWidget *bin = gtk_bin_get_child (GTK_BIN (shell->nav_popup)); + + editor = GIMP_NAVIGATION_EDITOR (gtk_bin_get_child (GTK_BIN (bin))); + } + + view = GIMP_NAVIGATION_VIEW (editor->view); + + /* Set poup screen */ + screen = gtk_widget_get_screen (widget); + gtk_window_set_screen (GTK_WINDOW (shell->nav_popup), screen); + + gimp_navigation_view_get_local_marker (view, + &view_marker_center_x, + &view_marker_center_y, + &view_marker_width, + &view_marker_height); + /* Position the popup */ + { + gint x_origin, y_origin; + gint popup_width, popup_height; + gint border_width, border_height; + gint screen_click_x, screen_click_y; + + gdk_window_get_origin (gtk_widget_get_window (widget), + &x_origin, &y_origin); + + screen_click_x = x_origin + click_x; + screen_click_y = y_origin + click_y; + border_width = 2 * style->xthickness; + border_height = 2 * style->ythickness; + popup_width = GIMP_VIEW (view)->renderer->width - 2 * border_width; + popup_height = GIMP_VIEW (view)->renderer->height - 2 * border_height; + + x = screen_click_x - + border_width - + view_marker_center_x; + + y = screen_click_y - + border_height - + view_marker_center_y; + + /* When the image is zoomed out and overscrolled, the above + * calculation risks positioning the popup far far away from the + * click coordinate. We don't want that, so perform some clamping. + */ + x = CLAMP (x, screen_click_x - popup_width, screen_click_x); + y = CLAMP (y, screen_click_y - popup_height, screen_click_y); + + /* If the popup doesn't fit into the screen, we have a problem. + * We move the popup onscreen and risk that the pointer is not + * in the square representing the viewable area anymore. Moving + * the pointer will make the image scroll by a large amount, + * but then it works as usual. Probably better than a popup that + * is completely unusable in the lower right of the screen. + * + * Warping the pointer would be another solution ... + */ + x = CLAMP (x, 0, gdk_screen_get_width (screen) - popup_width); + y = CLAMP (y, 0, gdk_screen_get_height (screen) - popup_height); + + gtk_window_move (GTK_WINDOW (shell->nav_popup), x, y); + } + + gtk_widget_show (shell->nav_popup); + gdk_flush (); + + /* fill in then grab pointer */ + gimp_navigation_view_set_motion_offset (view, 0, 0); + gimp_navigation_view_grab_pointer (view); +} + + +/* private functions */ + +static GtkWidget * +gimp_navigation_editor_new_private (GimpMenuFactory *menu_factory, + GimpDisplayShell *shell) +{ + GimpNavigationEditor *editor; + + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + g_return_val_if_fail (shell == NULL || GIMP_IS_DISPLAY_SHELL (shell), NULL); + g_return_val_if_fail (menu_factory || shell, NULL); + + if (shell) + { + Gimp *gimp = shell->display->gimp; + GimpDisplayConfig *config = shell->display->config; + GimpView *view; + + editor = g_object_new (GIMP_TYPE_NAVIGATION_EDITOR, NULL); + + view = GIMP_VIEW (editor->view); + + gimp_view_renderer_set_size (view->renderer, + config->nav_preview_size * 3, + view->renderer->border_width); + gimp_view_renderer_set_context (view->renderer, + gimp_get_user_context (gimp)); + gimp_view_renderer_set_color_config (view->renderer, + gimp_display_shell_get_color_config (shell)); + + gimp_navigation_editor_set_shell (editor, shell); + + } + else + { + GtkWidget *hscale; + GtkWidget *hbox; + + editor = g_object_new (GIMP_TYPE_NAVIGATION_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "<NavigationEditor>", + NULL); + + gtk_widget_set_size_request (editor->view, + GIMP_VIEW_SIZE_HUGE, + GIMP_VIEW_SIZE_HUGE); + gimp_view_set_expand (GIMP_VIEW (editor->view), TRUE); + + /* the editor buttons */ + + editor->zoom_out_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "view", + "view-zoom-out", NULL); + + editor->zoom_in_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "view", + "view-zoom-in", NULL); + + editor->zoom_100_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "view", + "view-zoom-1-1", NULL); + + editor->zoom_fit_in_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "view", + "view-zoom-fit-in", NULL); + + editor->zoom_fill_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "view", + "view-zoom-fill", NULL); + + editor->shrink_wrap_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "view", + "view-shrink-wrap", NULL); + + /* the zoom scale */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_end (GTK_BOX (editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + editor->zoom_adjustment = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -8.0, 8.0, 0.5, 1.0, 0.0)); + + g_signal_connect (editor->zoom_adjustment, "value-changed", + G_CALLBACK (gimp_navigation_editor_zoom_adj_changed), + editor); + + hscale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, + editor->zoom_adjustment); + gtk_scale_set_draw_value (GTK_SCALE (hscale), FALSE); + gtk_box_pack_start (GTK_BOX (hbox), hscale, TRUE, TRUE, 0); + gtk_widget_show (hscale); + + /* the zoom label */ + + editor->zoom_label = gtk_label_new ("100%"); + gtk_label_set_width_chars (GTK_LABEL (editor->zoom_label), 7); + gtk_box_pack_start (GTK_BOX (hbox), editor->zoom_label, FALSE, FALSE, 0); + gtk_widget_show (editor->zoom_label); + } + + gimp_view_renderer_set_background (GIMP_VIEW (editor->view)->renderer, + GIMP_ICON_TEXTURE); + + return GTK_WIDGET (editor); +} + +static void +gimp_navigation_editor_set_shell (GimpNavigationEditor *editor, + GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_NAVIGATION_EDITOR (editor)); + g_return_if_fail (! shell || GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell == editor->shell) + return; + + if (editor->shell) + { + g_signal_handlers_disconnect_by_func (editor->shell, + gimp_navigation_editor_shell_infinite_canvas_notify, + editor); + g_signal_handlers_disconnect_by_func (editor->shell, + gimp_navigation_editor_shell_scaled, + editor); + g_signal_handlers_disconnect_by_func (editor->shell, + gimp_navigation_editor_shell_scrolled, + editor); + g_signal_handlers_disconnect_by_func (editor->shell, + gimp_navigation_editor_shell_rotated, + editor); + g_signal_handlers_disconnect_by_func (editor->shell, + gimp_navigation_editor_shell_reconnect, + editor); + + g_signal_handlers_disconnect_by_func (editor->shell->options, + gimp_navigation_editor_options_show_canvas_notify, + editor); + g_signal_handlers_disconnect_by_func (editor->shell->fullscreen_options, + gimp_navigation_editor_options_show_canvas_notify, + editor); + } + else if (shell) + { + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + } + + editor->shell = shell; + + if (editor->shell) + { + GimpImage *image = gimp_display_get_image (shell->display); + + g_clear_object (&editor->image_proxy); + + if (image) + { + editor->image_proxy = gimp_image_proxy_new (image); + + g_signal_connect ( + editor->image_proxy, "size-changed", + G_CALLBACK (gimp_navigation_editor_viewable_size_changed), + editor); + } + + gimp_view_set_viewable (GIMP_VIEW (editor->view), + GIMP_VIEWABLE (editor->image_proxy)); + + g_signal_connect (editor->shell, "notify::infinite-canvas", + G_CALLBACK (gimp_navigation_editor_shell_infinite_canvas_notify), + editor); + g_signal_connect (editor->shell, "scaled", + G_CALLBACK (gimp_navigation_editor_shell_scaled), + editor); + g_signal_connect (editor->shell, "scrolled", + G_CALLBACK (gimp_navigation_editor_shell_scrolled), + editor); + g_signal_connect (editor->shell, "rotated", + G_CALLBACK (gimp_navigation_editor_shell_rotated), + editor); + g_signal_connect (editor->shell, "reconnect", + G_CALLBACK (gimp_navigation_editor_shell_reconnect), + editor); + + g_signal_connect (editor->shell->options, "notify::show-canvas-boundary", + G_CALLBACK (gimp_navigation_editor_options_show_canvas_notify), + editor); + g_signal_connect (editor->shell->fullscreen_options, "notify::show-canvas-boundary", + G_CALLBACK (gimp_navigation_editor_options_show_canvas_notify), + editor); + + gimp_navigation_editor_shell_scaled (editor->shell, editor); + } + else + { + gimp_view_set_viewable (GIMP_VIEW (editor->view), NULL); + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); + + g_clear_object (&editor->image_proxy); + } + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static gboolean +gimp_navigation_editor_button_release (GtkWidget *widget, + GdkEventButton *bevent, + GimpDisplayShell *shell) +{ + if (bevent->button == 1) + { + gtk_widget_hide (shell->nav_popup); + } + + return FALSE; +} + +static void +gimp_navigation_editor_marker_changed (GimpNavigationView *view, + gdouble center_x, + gdouble center_y, + gdouble width, + gdouble height, + GimpNavigationEditor *editor) +{ + GimpViewRenderer *renderer = GIMP_VIEW (editor->view)->renderer; + + if (editor->shell) + { + if (gimp_display_get_image (editor->shell->display)) + { + GeglRectangle bounding_box; + + bounding_box = gimp_image_proxy_get_bounding_box ( + GIMP_IMAGE_PROXY (renderer->viewable)); + + center_x += bounding_box.x; + center_y += bounding_box.y; + + gimp_display_shell_scroll_center_image_xy (editor->shell, + center_x, center_y); + } + } +} + +static void +gimp_navigation_editor_zoom (GimpNavigationView *view, + GimpZoomType direction, + GimpNavigationEditor *editor) +{ + g_return_if_fail (direction != GIMP_ZOOM_TO); + + if (editor->shell) + { + if (gimp_display_get_image (editor->shell->display)) + gimp_display_shell_scale (editor->shell, + direction, + 0.0, + GIMP_ZOOM_FOCUS_BEST_GUESS); + } +} + +static void +gimp_navigation_editor_scroll (GimpNavigationView *view, + GdkScrollDirection direction, + GimpNavigationEditor *editor) +{ + if (editor->shell) + { + GtkAdjustment *adj = NULL; + gdouble value; + + switch (direction) + { + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + adj = editor->shell->hsbdata; + break; + + case GDK_SCROLL_UP: + case GDK_SCROLL_DOWN: + adj = editor->shell->vsbdata; + break; + } + + gimp_assert (adj != NULL); + + value = gtk_adjustment_get_value (adj); + + switch (direction) + { + case GDK_SCROLL_LEFT: + case GDK_SCROLL_UP: + value -= gtk_adjustment_get_page_increment (adj) / 2; + break; + + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_DOWN: + value += gtk_adjustment_get_page_increment (adj) / 2; + break; + } + + value = CLAMP (value, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_page_size (adj)); + + gtk_adjustment_set_value (adj, value); + } +} + +static gboolean +gimp_navigation_editor_zoom_adj_changed_timeout (gpointer data) +{ + GimpNavigationEditor *editor = GIMP_NAVIGATION_EDITOR (data); + GtkAdjustment *adj = editor->zoom_adjustment; + + if (gimp_display_get_image (editor->shell->display)) + gimp_display_shell_scale (editor->shell, + GIMP_ZOOM_TO, + pow (2.0, gtk_adjustment_get_value (adj)), + GIMP_ZOOM_FOCUS_BEST_GUESS); + + editor->scale_timeout = 0; + + return FALSE; +} + +static void +gimp_navigation_editor_zoom_adj_changed (GtkAdjustment *adj, + GimpNavigationEditor *editor) +{ + if (editor->scale_timeout) + g_source_remove (editor->scale_timeout); + + editor->scale_timeout = + g_timeout_add (UPDATE_DELAY, + gimp_navigation_editor_zoom_adj_changed_timeout, + editor); +} + +static void +gimp_navigation_editor_shell_infinite_canvas_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpNavigationEditor *editor) +{ + gimp_navigation_editor_update_marker (editor); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static void +gimp_navigation_editor_shell_scaled (GimpDisplayShell *shell, + GimpNavigationEditor *editor) +{ + if (editor->zoom_label) + { + gchar *str; + + g_object_get (shell->zoom, + "percentage", &str, + NULL); + gtk_label_set_text (GTK_LABEL (editor->zoom_label), str); + g_free (str); + } + + if (editor->zoom_adjustment) + { + gdouble val; + + val = log (gimp_zoom_model_get_factor (shell->zoom)) / G_LN2; + + g_signal_handlers_block_by_func (editor->zoom_adjustment, + gimp_navigation_editor_zoom_adj_changed, + editor); + + gtk_adjustment_set_value (editor->zoom_adjustment, val); + + g_signal_handlers_unblock_by_func (editor->zoom_adjustment, + gimp_navigation_editor_zoom_adj_changed, + editor); + } + + gimp_navigation_editor_update_marker (editor); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static void +gimp_navigation_editor_shell_scrolled (GimpDisplayShell *shell, + GimpNavigationEditor *editor) +{ + gimp_navigation_editor_update_marker (editor); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static void +gimp_navigation_editor_shell_rotated (GimpDisplayShell *shell, + GimpNavigationEditor *editor) +{ + gimp_navigation_editor_update_marker (editor); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static void +gimp_navigation_editor_viewable_size_changed (GimpViewable *viewable, + GimpNavigationEditor *editor) +{ + gimp_navigation_editor_update_marker (editor); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static void +gimp_navigation_editor_options_show_canvas_notify (GimpDisplayOptions *options, + const GParamSpec *pspec, + GimpNavigationEditor *editor) +{ + gimp_navigation_editor_update_marker (editor); +} + +static void +gimp_navigation_editor_shell_reconnect (GimpDisplayShell *shell, + GimpNavigationEditor *editor) +{ + GimpImage *image = gimp_display_get_image (shell->display); + + g_clear_object (&editor->image_proxy); + + if (image) + { + editor->image_proxy = gimp_image_proxy_new (image); + + g_signal_connect ( + editor->image_proxy, "size-changed", + G_CALLBACK (gimp_navigation_editor_viewable_size_changed), + editor); + } + + gimp_view_set_viewable (GIMP_VIEW (editor->view), + GIMP_VIEWABLE (editor->image_proxy)); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} + +static void +gimp_navigation_editor_update_marker (GimpNavigationEditor *editor) +{ + GimpViewRenderer *renderer = GIMP_VIEW (editor->view)->renderer; + GimpDisplayShell *shell = editor->shell; + + if (renderer->dot_for_dot != shell->dot_for_dot) + gimp_view_renderer_set_dot_for_dot (renderer, shell->dot_for_dot); + + if (renderer->viewable) + { + GimpNavigationView *view = GIMP_NAVIGATION_VIEW (editor->view); + GimpImage *image; + GeglRectangle bounding_box; + gdouble x, y; + gdouble w, h; + + image = gimp_image_proxy_get_image ( + GIMP_IMAGE_PROXY (renderer->viewable)); + + gimp_image_proxy_set_show_all ( + GIMP_IMAGE_PROXY (renderer->viewable), + gimp_display_shell_get_infinite_canvas (shell)); + + bounding_box = gimp_image_proxy_get_bounding_box ( + GIMP_IMAGE_PROXY (renderer->viewable)); + + gimp_display_shell_scroll_get_viewport (shell, &x, &y, &w, &h); + gimp_display_shell_untransform_xy_f (shell, + shell->disp_width / 2, + shell->disp_height / 2, + &x, &y); + + x -= bounding_box.x; + y -= bounding_box.y; + + gimp_navigation_view_set_marker (view, + x, y, w, h, + shell->flip_horizontally, + shell->flip_vertically, + shell->rotate_angle); + + gimp_navigation_view_set_canvas ( + view, + gimp_display_shell_get_infinite_canvas (shell) && + gimp_display_shell_get_show_canvas (shell), + -bounding_box.x, -bounding_box.y, + gimp_image_get_width (image), gimp_image_get_height (image)); + } +} diff --git a/app/display/gimpnavigationeditor.h b/app/display/gimpnavigationeditor.h new file mode 100644 index 0000000..8e876c2 --- /dev/null +++ b/app/display/gimpnavigationeditor.h @@ -0,0 +1,79 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpnavigationeditor.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * partly based on app/nav_window + * Copyright (C) 1999 Andy Thomas <alt@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_NAVIGATION_EDITOR_H__ +#define __GIMP_NAVIGATION_EDITOR_H__ + + +#include "widgets/gimpeditor.h" + + +#define GIMP_TYPE_NAVIGATION_EDITOR (gimp_navigation_editor_get_type ()) +#define GIMP_NAVIGATION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_NAVIGATION_EDITOR, GimpNavigationEditor)) +#define GIMP_NAVIGATION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_NAVIGATION_EDITOR, GimpNavigationEditorClass)) +#define GIMP_IS_NAVIGATION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_NAVIGATION_EDITOR)) +#define GIMP_IS_NAVIGATION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_NAVIGATION_EDITOR)) +#define GIMP_NAVIGATION_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_NAVIGATION_EDITOR, GimpNavigationEditorClass)) + + +typedef struct _GimpNavigationEditorClass GimpNavigationEditorClass; + +struct _GimpNavigationEditor +{ + GimpEditor parent_instance; + + GimpContext *context; + GimpDisplayShell *shell; + + GimpImageProxy *image_proxy; + + GtkWidget *view; + GtkWidget *zoom_label; + GtkAdjustment *zoom_adjustment; + + GtkWidget *zoom_out_button; + GtkWidget *zoom_in_button; + GtkWidget *zoom_100_button; + GtkWidget *zoom_fit_in_button; + GtkWidget *zoom_fill_button; + GtkWidget *shrink_wrap_button; + + guint scale_timeout; +}; + +struct _GimpNavigationEditorClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_navigation_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_navigation_editor_new (GimpMenuFactory *menu_factory); +void gimp_navigation_editor_popup (GimpDisplayShell *shell, + GtkWidget *widget, + gint click_x, + gint click_y); + + +#endif /* __GIMP_NAVIGATION_EDITOR_H__ */ diff --git a/app/display/gimpscalecombobox.c b/app/display/gimpscalecombobox.c new file mode 100644 index 0000000..3a5bba0 --- /dev/null +++ b/app/display/gimpscalecombobox.c @@ -0,0 +1,562 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpscalecombobox.c + * Copyright (C) 2004, 2008 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "stdlib.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include "gdk/gdkkeysyms.h" + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpscalecombobox.h" + + +#define MAX_ITEMS 10 + +enum +{ + COLUMN_SCALE, + COLUMN_LABEL, + COLUMN_PERSISTENT, + N_COLUMNS +}; + +enum +{ + ENTRY_ACTIVATED, + LAST_SIGNAL +}; + + +static void gimp_scale_combo_box_constructed (GObject *object); +static void gimp_scale_combo_box_finalize (GObject *object); + +static void gimp_scale_combo_box_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box); +static void gimp_scale_combo_box_entry_activate (GtkWidget *entry, + GimpScaleComboBox *combo_box); +static gboolean gimp_scale_combo_box_entry_key_press (GtkWidget *entry, + GdkEventKey *event, + GimpScaleComboBox *combo_box); + +static void gimp_scale_combo_box_scale_iter_set (GtkListStore *store, + GtkTreeIter *iter, + gdouble scale, + gboolean persistent); + + +G_DEFINE_TYPE (GimpScaleComboBox, gimp_scale_combo_box, + GTK_TYPE_COMBO_BOX) + +#define parent_class gimp_scale_combo_box_parent_class + +static guint scale_combo_box_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_scale_combo_box_class_init (GimpScaleComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + scale_combo_box_signals[ENTRY_ACTIVATED] = + g_signal_new ("entry-activated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpScaleComboBoxClass, entry_activated), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->constructed = gimp_scale_combo_box_constructed; + object_class->finalize = gimp_scale_combo_box_finalize; + + widget_class->style_set = gimp_scale_combo_box_style_set; + + klass->entry_activated = NULL; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_double ("label-scale", + NULL, NULL, + 0.0, + G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READABLE)); +} + +static void +gimp_scale_combo_box_init (GimpScaleComboBox *combo_box) +{ + combo_box->scale = 1.0; + combo_box->last_path = NULL; +} + +static void +gimp_scale_combo_box_constructed (GObject *object) +{ + GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object); + GtkWidget *entry; + GtkListStore *store; + GtkCellLayout *layout; + GtkCellRenderer *cell; + GtkTreeIter iter; + GtkBorder border = { 0, 0, 0, 0 }; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + store = gtk_list_store_new (N_COLUMNS, + G_TYPE_DOUBLE, /* SCALE */ + G_TYPE_STRING, /* LABEL */ + G_TYPE_BOOLEAN); /* PERSISTENT */ + + gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store)); + g_object_unref (store); + + gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo_box), + COLUMN_LABEL); + + entry = gtk_bin_get_child (GTK_BIN (combo_box)); + + g_object_set (entry, + "xalign", 1.0, + "width-chars", 5, + "truncate-multiline", TRUE, + "inner-border", &border, + NULL); + + layout = GTK_CELL_LAYOUT (combo_box); + + cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, + "xalign", 1.0, + NULL); + + gtk_cell_layout_clear (layout); + gtk_cell_layout_pack_start (layout, cell, TRUE); + gtk_cell_layout_set_attributes (layout, cell, + "text", COLUMN_LABEL, + NULL); + + for (i = 8; i > 0; i /= 2) + { + gtk_list_store_append (store, &iter); + gimp_scale_combo_box_scale_iter_set (store, &iter, i, TRUE); + } + + for (i = 2; i <= 8; i *= 2) + { + gtk_list_store_append (store, &iter); + gimp_scale_combo_box_scale_iter_set (store, &iter, 1.0 / i, TRUE); + } + + g_signal_connect (combo_box, "changed", + G_CALLBACK (gimp_scale_combo_box_changed), + NULL); + + g_signal_connect (entry, "activate", + G_CALLBACK (gimp_scale_combo_box_entry_activate), + combo_box); + g_signal_connect (entry, "key-press-event", + G_CALLBACK (gimp_scale_combo_box_entry_key_press), + combo_box); +} + +static void +gimp_scale_combo_box_finalize (GObject *object) +{ + GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object); + + if (combo_box->last_path) + { + gtk_tree_path_free (combo_box->last_path); + combo_box->last_path = NULL; + } + + if (combo_box->mru) + { + g_list_free_full (combo_box->mru, + (GDestroyNotify) gtk_tree_row_reference_free); + combo_box->mru = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_scale_combo_box_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GtkWidget *entry; + GtkRcStyle *rc_style; + PangoContext *context; + PangoFontDescription *font_desc; + gint font_size; + gdouble label_scale; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, "label-scale", &label_scale, NULL); + + entry = gtk_bin_get_child (GTK_BIN (widget)); + + rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (entry)); + + if (rc_style->font_desc) + pango_font_description_free (rc_style->font_desc); + + context = gtk_widget_get_pango_context (widget); + font_desc = pango_context_get_font_description (context); + rc_style->font_desc = pango_font_description_copy (font_desc); + + font_size = pango_font_description_get_size (rc_style->font_desc); + pango_font_description_set_size (rc_style->font_desc, label_scale * font_size); + + gtk_widget_modify_style (GTK_WIDGET (entry), rc_style); +} + +static void +gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box) +{ + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) + { + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + gdouble scale; + + gtk_tree_model_get (model, &iter, + COLUMN_SCALE, &scale, + -1); + if (scale > 0.0) + { + combo_box->scale = scale; + + if (combo_box->last_path) + gtk_tree_path_free (combo_box->last_path); + + combo_box->last_path = gtk_tree_model_get_path (model, &iter); + } + } +} + +static gboolean +gimp_scale_combo_box_parse_text (const gchar *text, + gdouble *scale) +{ + gchar *end; + gdouble left_number; + gdouble right_number; + + /* try to parse a number */ + left_number = strtod (text, &end); + + if (end == text) + return FALSE; + else + text = end; + + /* skip over whitespace */ + while (g_unichar_isspace (g_utf8_get_char (text))) + text = g_utf8_next_char (text); + + if (*text == '\0' || *text == '%') + { + *scale = left_number / 100.0; + return TRUE; + } + + /* check for a valid separator */ + if (*text != '/' && *text != ':') + { + *scale = left_number; + return TRUE; + } + + text = g_utf8_next_char (text); + + /* skip over whitespace */ + while (g_unichar_isspace (g_utf8_get_char (text))) + text = g_utf8_next_char (text); + + /* try to parse another number */ + right_number = strtod (text, &end); + + if (end == text) + return FALSE; + + if (right_number == 0.0) + return FALSE; + + *scale = left_number / right_number; + return TRUE; +} + +static void +gimp_scale_combo_box_entry_activate (GtkWidget *entry, + GimpScaleComboBox *combo_box) +{ + const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry)); + gdouble scale; + + if (gimp_scale_combo_box_parse_text (text, &scale) && + scale >= 1.0 / 256.0 && + scale <= 256.0) + { + gimp_scale_combo_box_set_scale (combo_box, scale); + } + else + { + gtk_widget_error_bell (entry); + + gimp_scale_combo_box_set_scale (combo_box, combo_box->scale); + } + + g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0); +} + +static gboolean +gimp_scale_combo_box_entry_key_press (GtkWidget *entry, + GdkEventKey *event, + GimpScaleComboBox *combo_box) +{ + if (event->keyval == GDK_KEY_Escape) + { + gimp_scale_combo_box_set_scale (combo_box, combo_box->scale); + + g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0); + + return TRUE; + } + + if (event->keyval == GDK_KEY_Tab || + event->keyval == GDK_KEY_KP_Tab || + event->keyval == GDK_KEY_ISO_Left_Tab) + { + gimp_scale_combo_box_entry_activate (entry, combo_box); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_scale_combo_box_scale_iter_set (GtkListStore *store, + GtkTreeIter *iter, + gdouble scale, + gboolean persistent) +{ + gchar label[32]; + +#ifdef G_OS_WIN32 + + /* use a normal space until pango's windows backend uses harfbuzz, + * see bug #735505 + */ +#define PERCENT_SPACE " " + +#else + + /* use U+2009 THIN SPACE to separate the percent sign from the number */ +#define PERCENT_SPACE "\342\200\211" + +#endif + + if (scale > 1.0) + g_snprintf (label, sizeof (label), + "%d" PERCENT_SPACE "%%", (gint) ROUND (100.0 * scale)); + else + g_snprintf (label, sizeof (label), + "%.3g" PERCENT_SPACE "%%", 100.0 * scale); + + gtk_list_store_set (store, iter, + COLUMN_SCALE, scale, + COLUMN_LABEL, label, + COLUMN_PERSISTENT, persistent, + -1); +} + +static void +gimp_scale_combo_box_mru_add (GimpScaleComboBox *combo_box, + GtkTreeIter *iter) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + GtkTreePath *path = gtk_tree_model_get_path (model, iter); + GList *list; + gboolean found; + + for (list = combo_box->mru, found = FALSE; list && !found; list = list->next) + { + GtkTreePath *this = gtk_tree_row_reference_get_path (list->data); + + if (gtk_tree_path_compare (this, path) == 0) + { + if (list->prev) + { + combo_box->mru = g_list_remove_link (combo_box->mru, list); + combo_box->mru = g_list_concat (list, combo_box->mru); + } + + found = TRUE; + } + + gtk_tree_path_free (this); + } + + if (! found) + combo_box->mru = g_list_prepend (combo_box->mru, + gtk_tree_row_reference_new (model, path)); + + gtk_tree_path_free (path); +} + +static void +gimp_scale_combo_box_mru_remove_last (GimpScaleComboBox *combo_box) +{ + GtkTreeModel *model; + GtkTreePath *path; + GList *last; + GtkTreeIter iter; + + if (! combo_box->mru) + return; + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + + last = g_list_last (combo_box->mru); + path = gtk_tree_row_reference_get_path (last->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + gtk_tree_row_reference_free (last->data); + combo_box->mru = g_list_delete_link (combo_box->mru, last); + } + + gtk_tree_path_free (path); +} + + +/** + * gimp_scale_combo_box_new: + * + * Return value: a new #GimpScaleComboBox. + **/ +GtkWidget * +gimp_scale_combo_box_new (void) +{ + return g_object_new (GIMP_TYPE_SCALE_COMBO_BOX, + "has-entry", TRUE, + NULL); +} + +void +gimp_scale_combo_box_set_scale (GimpScaleComboBox *combo_box, + gdouble scale) +{ + GtkTreeModel *model; + GtkListStore *store; + GtkWidget *entry; + GtkTreeIter iter; + gboolean iter_valid; + gboolean persistent; + gint n_digits; + + g_return_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box)); + g_return_if_fail (scale > 0.0); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + store = GTK_LIST_STORE (model); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gdouble this; + + gtk_tree_model_get (model, &iter, + COLUMN_SCALE, &this, + -1); + + if (fabs (this - scale) < 0.0001) + break; + } + + if (! iter_valid) + { + GtkTreeIter sibling; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &sibling); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &sibling)) + { + gdouble this; + + gtk_tree_model_get (model, &sibling, + COLUMN_SCALE, &this, + -1); + + if (this < scale) + break; + } + + gtk_list_store_insert_before (store, &iter, iter_valid ? &sibling : NULL); + gimp_scale_combo_box_scale_iter_set (store, &iter, scale, FALSE); + } + + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter); + + gtk_tree_model_get (model, &iter, + COLUMN_PERSISTENT, &persistent, + -1); + if (! persistent) + { + gimp_scale_combo_box_mru_add (combo_box, &iter); + + if (gtk_tree_model_iter_n_children (model, NULL) > MAX_ITEMS) + gimp_scale_combo_box_mru_remove_last (combo_box); + } + + /* Update entry size appropriately. */ + entry = gtk_bin_get_child (GTK_BIN (combo_box)); + n_digits = (gint) floor (log10 (scale) + 1); + + g_object_set (entry, + "width-chars", MAX (5, n_digits + 4), + NULL); +} + +gdouble +gimp_scale_combo_box_get_scale (GimpScaleComboBox *combo_box) +{ + g_return_val_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box), 1.0); + + return combo_box->scale; +} diff --git a/app/display/gimpscalecombobox.h b/app/display/gimpscalecombobox.h new file mode 100644 index 0000000..1c35e2f --- /dev/null +++ b/app/display/gimpscalecombobox.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpscalecombobox.h + * Copyright (C) 2004, 2008 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_SCALE_COMBO_BOX_H__ +#define __GIMP_SCALE_COMBO_BOX_H__ + + +#define GIMP_TYPE_SCALE_COMBO_BOX (gimp_scale_combo_box_get_type ()) +#define GIMP_SCALE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SCALE_COMBO_BOX, GimpScaleComboBox)) +#define GIMP_SCALE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SCALE_COMBO_BOX, GimpScaleComboBoxClass)) +#define GIMP_IS_SCALE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SCALE_COMBO_BOX)) +#define GIMP_IS_SCALE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SCALE_COMBO_BOX)) +#define GIMP_SCALE_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SCALE_COMBO_BOX, GimpScaleComboBoxClass)) + + +typedef struct _GimpScaleComboBoxClass GimpScaleComboBoxClass; + +struct _GimpScaleComboBoxClass +{ + GtkComboBoxClass parent_instance; + + void (* entry_activated) (GimpScaleComboBox *combo_box); +}; + +struct _GimpScaleComboBox +{ + GtkComboBox parent_instance; + + gdouble scale; + GtkTreePath *last_path; + GList *mru; +}; + + +GType gimp_scale_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_scale_combo_box_new (void); +void gimp_scale_combo_box_set_scale (GimpScaleComboBox *combo_box, + gdouble scale); +gdouble gimp_scale_combo_box_get_scale (GimpScaleComboBox *combo_box); + + +#endif /* __GIMP_SCALE_COMBO_BOX_H__ */ diff --git a/app/display/gimpsinglewindowstrategy.c b/app/display/gimpsinglewindowstrategy.c new file mode 100644 index 0000000..e840dfa --- /dev/null +++ b/app/display/gimpsinglewindowstrategy.c @@ -0,0 +1,157 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsinglewindowstrategy.c + * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimp.h" + +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpdock.h" +#include "widgets/gimpdockbook.h" +#include "widgets/gimpdockcolumns.h" +#include "widgets/gimpwindowstrategy.h" + +#include "gimpimagewindow.h" +#include "gimpsinglewindowstrategy.h" + + +static void gimp_single_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface); +static GtkWidget * gimp_single_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers); + + +G_DEFINE_TYPE_WITH_CODE (GimpSingleWindowStrategy, gimp_single_window_strategy, GIMP_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_WINDOW_STRATEGY, + gimp_single_window_strategy_window_strategy_iface_init)) + +#define parent_class gimp_single_window_strategy_parent_class + + +static void +gimp_single_window_strategy_class_init (GimpSingleWindowStrategyClass *klass) +{ +} + +static void +gimp_single_window_strategy_init (GimpSingleWindowStrategy *strategy) +{ +} + +static void +gimp_single_window_strategy_window_strategy_iface_init (GimpWindowStrategyInterface *iface) +{ + iface->show_dockable_dialog = gimp_single_window_strategy_show_dockable_dialog; +} + +static GtkWidget * +gimp_single_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers) +{ + GList *windows = gimp_get_image_windows (gimp); + GtkWidget *widget = NULL; + GimpImageWindow *window; + + g_return_val_if_fail (windows != NULL, NULL); + + /* In single-window mode, there should only be one window... */ + window = GIMP_IMAGE_WINDOW (windows->data); + + if (strcmp ("gimp-toolbox", identifiers) == 0) + { + /* Only allow one toolbox... */ + if (! gimp_image_window_has_toolbox (window)) + { + GimpDockColumns *columns; + GimpUIManager *ui_manager = gimp_image_window_get_ui_manager (window); + + widget = gimp_dialog_factory_dialog_new (factory, + screen, + monitor, + ui_manager, + "gimp-toolbox", + -1 /*view_size*/, + FALSE /*present*/); + gtk_widget_show (widget); + + columns = gimp_image_window_get_left_docks (window); + gimp_dock_columns_add_dock (columns, + GIMP_DOCK (widget), + -1 /*index*/); + } + } + else if (gimp_dialog_factory_find_widget (factory, identifiers)) + { + /* if the dialog is already open, simply raise it */ + return gimp_dialog_factory_dialog_raise (factory, screen, monitor, + identifiers, -1); + } + else + { + GtkWidget *dockbook; + + dockbook = gimp_image_window_get_default_dockbook (window); + + if (! dockbook) + { + GimpDockColumns *dock_columns; + + /* No dock, need to add one */ + dock_columns = gimp_image_window_get_right_docks (window); + gimp_dock_columns_prepare_dockbook (dock_columns, + -1 /*index*/, + &dockbook); + } + + widget = gimp_dockbook_add_from_dialog_factory (GIMP_DOCKBOOK (dockbook), + identifiers, + -1 /*index*/); + } + + + g_list_free (windows); + + return widget; +} + +GimpObject * +gimp_single_window_strategy_get_singleton (void) +{ + static GimpObject *singleton = NULL; + + if (! singleton) + singleton = g_object_new (GIMP_TYPE_SINGLE_WINDOW_STRATEGY, NULL); + + return singleton; +} diff --git a/app/display/gimpsinglewindowstrategy.h b/app/display/gimpsinglewindowstrategy.h new file mode 100644 index 0000000..5b145c7 --- /dev/null +++ b/app/display/gimpsinglewindowstrategy.h @@ -0,0 +1,54 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsinglewindowstrategy.h + * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_SINGLE_WINDOW_STRATEGY_H__ +#define __GIMP_SINGLE_WINDOW_STRATEGY_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_SINGLE_WINDOW_STRATEGY (gimp_single_window_strategy_get_type ()) +#define GIMP_SINGLE_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SINGLE_WINDOW_STRATEGY, GimpSingleWindowStrategy)) +#define GIMP_SINGLE_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SINGLE_WINDOW_STRATEGY, GimpSingleWindowStrategyClass)) +#define GIMP_IS_SINGLE_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SINGLE_WINDOW_STRATEGY)) +#define GIMP_IS_SINGLE_WINDOW_STRATEGY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SINGLE_WINDOW_STRATEGY)) +#define GIMP_SINGLE_WINDOW_STRATEGY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SINGLE_WINDOW_STRATEGY, GimpSingleWindowStrategyClass)) + + +typedef struct _GimpSingleWindowStrategyClass GimpSingleWindowStrategyClass; + +struct _GimpSingleWindowStrategy +{ + GimpObject parent_instance; +}; + +struct _GimpSingleWindowStrategyClass +{ + GimpObjectClass parent_class; +}; + + +GType gimp_single_window_strategy_get_type (void) G_GNUC_CONST; + +GimpObject * gimp_single_window_strategy_get_singleton (void); + + +#endif /* __GIMP_SINGLE_WINDOW_STRATEGY_H__ */ diff --git a/app/display/gimpstatusbar.c b/app/display/gimpstatusbar.c new file mode 100644 index 0000000..0c0ca14 --- /dev/null +++ b/app/display/gimpstatusbar.c @@ -0,0 +1,1747 @@ +/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995 + * Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "config/gimpdisplayconfig.h" + +#include "core/gimpimage.h" +#include "core/gimpprogress.h" + +#include "widgets/gimpuimanager.h" +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scale.h" +#include "gimpimagewindow.h" +#include "gimpscalecombobox.h" +#include "gimpstatusbar.h" + +#include "gimp-intl.h" + + +/* maximal width of the string holding the cursor-coordinates */ +#define CURSOR_LEN 256 + +/* the spacing of the hbox */ +#define HBOX_SPACING 1 + +/* spacing between the icon and the statusbar label */ +#define ICON_SPACING 2 + +/* timeout (in milliseconds) for temporary statusbar messages */ +#define MESSAGE_TIMEOUT 8000 + +/* minimal interval (in microseconds) between progress updates */ +#define MIN_PROGRESS_UPDATE_INTERVAL 50000 + + +typedef struct _GimpStatusbarMsg GimpStatusbarMsg; + +struct _GimpStatusbarMsg +{ + guint context_id; + gchar *icon_name; + gchar *text; +}; + + +static void gimp_statusbar_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_statusbar_dispose (GObject *object); +static void gimp_statusbar_finalize (GObject *object); + +static void gimp_statusbar_screen_changed (GtkWidget *widget, + GdkScreen *previous); +static void gimp_statusbar_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_statusbar_hbox_size_request (GtkWidget *widget, + GtkRequisition *requisition, + GimpStatusbar *statusbar); + +static GimpProgress * + gimp_statusbar_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_statusbar_progress_end (GimpProgress *progress); +static gboolean gimp_statusbar_progress_is_active (GimpProgress *progress); +static void gimp_statusbar_progress_set_text (GimpProgress *progress, + const gchar *message); +static void gimp_statusbar_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_statusbar_progress_get_value (GimpProgress *progress); +static void gimp_statusbar_progress_pulse (GimpProgress *progress); +static gboolean gimp_statusbar_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message); +static void gimp_statusbar_progress_canceled (GtkWidget *button, + GimpStatusbar *statusbar); + +static gboolean gimp_statusbar_label_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpStatusbar *statusbar); + +static void gimp_statusbar_update (GimpStatusbar *statusbar); +static void gimp_statusbar_unit_changed (GimpUnitComboBox *combo, + GimpStatusbar *statusbar); +static void gimp_statusbar_scale_changed (GimpScaleComboBox *combo, + GimpStatusbar *statusbar); +static void gimp_statusbar_scale_activated (GimpScaleComboBox *combo, + GimpStatusbar *statusbar); +static gboolean gimp_statusbar_rotate_pressed (GtkWidget *event_box, + GdkEvent *event, + GimpStatusbar *statusbar); +static gboolean gimp_statusbar_horiz_flip_pressed (GtkWidget *event_box, + GdkEvent *event, + GimpStatusbar *statusbar); +static gboolean gimp_statusbar_vert_flip_pressed (GtkWidget *event_box, + GdkEvent *event, + GimpStatusbar *statusbar); +static void gimp_statusbar_shell_scaled (GimpDisplayShell *shell, + GimpStatusbar *statusbar); +static void gimp_statusbar_shell_rotated (GimpDisplayShell *shell, + GimpStatusbar *statusbar); +static void gimp_statusbar_shell_status_notify(GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpStatusbar *statusbar); +static guint gimp_statusbar_get_context_id (GimpStatusbar *statusbar, + const gchar *context); +static gboolean gimp_statusbar_temp_timeout (GimpStatusbar *statusbar); + +static void gimp_statusbar_add_message (GimpStatusbar *statusbar, + guint context_id, + const gchar *icon_name, + const gchar *format, + va_list args, + gboolean move_to_front) G_GNUC_PRINTF (4, 0); +static void gimp_statusbar_remove_message (GimpStatusbar *statusbar, + guint context_id); +static void gimp_statusbar_msg_free (GimpStatusbarMsg *msg); + +static gchar * gimp_statusbar_vprintf (const gchar *format, + va_list args) G_GNUC_PRINTF (1, 0); + +static GdkPixbuf * gimp_statusbar_load_icon (GimpStatusbar *statusbar, + const gchar *icon_name); + + +G_DEFINE_TYPE_WITH_CODE (GimpStatusbar, gimp_statusbar, GTK_TYPE_STATUSBAR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_statusbar_progress_iface_init)) + +#define parent_class gimp_statusbar_parent_class + + +static void +gimp_statusbar_class_init (GimpStatusbarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_statusbar_dispose; + object_class->finalize = gimp_statusbar_finalize; + + widget_class->screen_changed = gimp_statusbar_screen_changed; + widget_class->style_set = gimp_statusbar_style_set; +} + +static void +gimp_statusbar_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_statusbar_progress_start; + iface->end = gimp_statusbar_progress_end; + iface->is_active = gimp_statusbar_progress_is_active; + iface->set_text = gimp_statusbar_progress_set_text; + iface->set_value = gimp_statusbar_progress_set_value; + iface->get_value = gimp_statusbar_progress_get_value; + iface->pulse = gimp_statusbar_progress_pulse; + iface->message = gimp_statusbar_progress_message; +} + +static void +gimp_statusbar_init (GimpStatusbar *statusbar) +{ + GtkWidget *hbox; + GtkWidget *hbox2; + GtkWidget *image; + GtkWidget *label; + GimpUnitStore *store; + GList *children; + + statusbar->shell = NULL; + statusbar->messages = NULL; + statusbar->context_ids = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + statusbar->seq_context_id = 1; + + statusbar->temp_context_id = + gimp_statusbar_get_context_id (statusbar, "gimp-statusbar-temp"); + + statusbar->cursor_format_str[0] = '\0'; + statusbar->cursor_format_str_f[0] = '\0'; + statusbar->length_format_str[0] = '\0'; + + statusbar->progress_active = FALSE; + statusbar->progress_shown = FALSE; + + /* remove the message label from the message area */ + hbox = gtk_statusbar_get_message_area (GTK_STATUSBAR (statusbar)); + gtk_box_set_spacing (GTK_BOX (hbox), HBOX_SPACING); + + children = gtk_container_get_children (GTK_CONTAINER (hbox)); + statusbar->label = g_object_ref (children->data); + g_list_free (children); + + gtk_container_remove (GTK_CONTAINER (hbox), statusbar->label); + + g_signal_connect (hbox, "size-request", + G_CALLBACK (gimp_statusbar_hbox_size_request), + statusbar); + + statusbar->cursor_label = gtk_label_new ("8888, 8888"); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->cursor_label, FALSE, FALSE, 0); + gtk_widget_show (statusbar->cursor_label); + + store = gimp_unit_store_new (2); + statusbar->unit_combo = gimp_unit_combo_box_new_with_model (store); + g_object_unref (store); + + /* see issue #2642 */ + gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (statusbar->unit_combo), 1); + + gtk_widget_set_can_focus (statusbar->unit_combo, FALSE); + g_object_set (statusbar->unit_combo, "focus-on-click", FALSE, NULL); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->unit_combo, FALSE, FALSE, 0); + gtk_widget_show (statusbar->unit_combo); + + g_signal_connect (statusbar->unit_combo, "changed", + G_CALLBACK (gimp_statusbar_unit_changed), + statusbar); + + statusbar->scale_combo = gimp_scale_combo_box_new (); + gtk_widget_set_can_focus (statusbar->scale_combo, FALSE); + g_object_set (statusbar->scale_combo, "focus-on-click", FALSE, NULL); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->scale_combo, FALSE, FALSE, 0); + gtk_widget_show (statusbar->scale_combo); + + g_signal_connect (statusbar->scale_combo, "changed", + G_CALLBACK (gimp_statusbar_scale_changed), + statusbar); + + g_signal_connect (statusbar->scale_combo, "entry-activated", + G_CALLBACK (gimp_statusbar_scale_activated), + statusbar); + + /* Shell transform status */ + statusbar->rotate_widget = gtk_event_box_new (); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->rotate_widget, + FALSE, FALSE, 1); + gtk_widget_show (statusbar->rotate_widget); + + statusbar->rotate_label = gtk_label_new (NULL); + gtk_container_add (GTK_CONTAINER (statusbar->rotate_widget), + statusbar->rotate_label); + gtk_widget_show (statusbar->rotate_label); + + g_signal_connect (statusbar->rotate_widget, "button-press-event", + G_CALLBACK (gimp_statusbar_rotate_pressed), + statusbar); + + statusbar->horizontal_flip_icon = gtk_event_box_new (); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->horizontal_flip_icon, + FALSE, FALSE, 1); + gtk_widget_show (statusbar->horizontal_flip_icon); + + image = gtk_image_new_from_icon_name ("gimp-flip-horizontal", + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (statusbar->horizontal_flip_icon), image); + gtk_widget_show (image); + + g_signal_connect (statusbar->horizontal_flip_icon, "button-press-event", + G_CALLBACK (gimp_statusbar_horiz_flip_pressed), + statusbar); + + statusbar->vertical_flip_icon = gtk_event_box_new (); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->vertical_flip_icon, + FALSE, FALSE, 1); + gtk_widget_show (statusbar->vertical_flip_icon); + + image = gtk_image_new_from_icon_name ("gimp-flip-vertical", + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (statusbar->vertical_flip_icon), image); + gtk_widget_show (image); + + g_signal_connect (statusbar->vertical_flip_icon, "button-press-event", + G_CALLBACK (gimp_statusbar_vert_flip_pressed), + statusbar); + + /* put the label back into the message area */ + gtk_box_pack_start (GTK_BOX (hbox), statusbar->label, TRUE, TRUE, 1); + + g_object_unref (statusbar->label); + + g_signal_connect_after (statusbar->label, "expose-event", + G_CALLBACK (gimp_statusbar_label_expose), + statusbar); + + statusbar->progressbar = g_object_new (GTK_TYPE_PROGRESS_BAR, + "text-xalign", 0.0, + "text-yalign", 0.5, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->progressbar, TRUE, TRUE, 0); + /* don't show the progress bar */ + + /* construct the cancel button's contents manually because we + * always want image and label regardless of settings, and we want + * a menu size image. + */ + statusbar->cancel_button = gtk_button_new (); + gtk_widget_set_can_focus (statusbar->cancel_button, FALSE); + gtk_button_set_relief (GTK_BUTTON (statusbar->cancel_button), + GTK_RELIEF_NONE); + gtk_widget_set_sensitive (statusbar->cancel_button, FALSE); + gtk_box_pack_end (GTK_BOX (hbox), + statusbar->cancel_button, FALSE, FALSE, 0); + /* don't show the cancel button */ + + hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (statusbar->cancel_button), hbox2); + gtk_widget_show (hbox2); + + image = gtk_image_new_from_icon_name ("gtk-cancel", GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (hbox2), image, FALSE, FALSE, 2); + gtk_widget_show (image); + + label = gtk_label_new ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 2); + gtk_widget_show (label); + + g_signal_connect (statusbar->cancel_button, "clicked", + G_CALLBACK (gimp_statusbar_progress_canceled), + statusbar); +} + +static void +gimp_statusbar_dispose (GObject *object) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (object); + + if (statusbar->temp_timeout_id) + { + g_source_remove (statusbar->temp_timeout_id); + statusbar->temp_timeout_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_statusbar_finalize (GObject *object) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (object); + + g_clear_object (&statusbar->icon); + g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref); + + g_slist_free_full (statusbar->messages, + (GDestroyNotify) gimp_statusbar_msg_free); + statusbar->messages = NULL; + + g_clear_pointer (&statusbar->context_ids, g_hash_table_destroy); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_statusbar_screen_changed (GtkWidget *widget, + GdkScreen *previous) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (widget); + + if (GTK_WIDGET_CLASS (parent_class)->screen_changed) + GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous); + + g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref); +} + +static void +gimp_statusbar_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref); +} + +static void +gimp_statusbar_hbox_size_request (GtkWidget *widget, + GtkRequisition *requisition, + GimpStatusbar *statusbar) +{ + GtkRequisition child_requisition; + gint width = 0; + + /* also consider the children which can be invisible */ + + gtk_widget_size_request (statusbar->cursor_label, &child_requisition); + width += child_requisition.width; + requisition->height = MAX (requisition->height, + child_requisition.height); + + gtk_widget_size_request (statusbar->unit_combo, &child_requisition); + width += child_requisition.width; + requisition->height = MAX (requisition->height, + child_requisition.height); + + gtk_widget_size_request (statusbar->scale_combo, &child_requisition); + width += child_requisition.width; + requisition->height = MAX (requisition->height, + child_requisition.height); + + gtk_widget_size_request (statusbar->progressbar, &child_requisition); + requisition->height = MAX (requisition->height, + child_requisition.height); + + gtk_widget_size_request (statusbar->label, &child_requisition); + requisition->height = MAX (requisition->height, + child_requisition.height); + + gtk_widget_size_request (statusbar->cancel_button, &child_requisition); + requisition->height = MAX (requisition->height, + child_requisition.height); + + requisition->width = MAX (requisition->width, width + 32); +} + +static GimpProgress * +gimp_statusbar_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + if (! statusbar->progress_active) + { + GtkWidget *bar = statusbar->progressbar; + GtkAllocation allocation; + + statusbar->progress_active = TRUE; + statusbar->progress_value = 0.0; + statusbar->progress_last_update_time = g_get_monotonic_time (); + + gimp_statusbar_push (statusbar, "progress", NULL, "%s", message); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0); + gtk_widget_set_sensitive (statusbar->cancel_button, cancellable); + + if (cancellable) + { + if (message) + { + gchar *tooltip = g_strdup_printf (_("Cancel <i>%s</i>"), message); + + gimp_help_set_help_data_with_markup (statusbar->cancel_button, + tooltip, NULL); + g_free (tooltip); + } + + gtk_widget_show (statusbar->cancel_button); + } + + gtk_widget_get_allocation (statusbar->label, &allocation); + + gtk_widget_show (statusbar->progressbar); + gtk_widget_hide (statusbar->label); + + /* This shit is needed so that the progress bar is drawn in the + * correct place in the cases where we suck completely and run + * an operation that blocks the GUI and doesn't let the main + * loop run. + */ + gtk_container_resize_children (GTK_CONTAINER (statusbar)); + gtk_widget_size_allocate (statusbar->progressbar, &allocation); + + if (! gtk_widget_get_visible (GTK_WIDGET (statusbar))) + { + gtk_widget_show (GTK_WIDGET (statusbar)); + statusbar->progress_shown = TRUE; + } + + gimp_widget_flush_expose (bar); + + gimp_statusbar_override_window_title (statusbar); + + return progress; + } + + return NULL; +} + +static void +gimp_statusbar_progress_end (GimpProgress *progress) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + if (statusbar->progress_active) + { + GtkWidget *bar = statusbar->progressbar; + + if (statusbar->progress_shown) + { + gtk_widget_hide (GTK_WIDGET (statusbar)); + statusbar->progress_shown = FALSE; + } + + statusbar->progress_active = FALSE; + statusbar->progress_value = 0.0; + + gtk_widget_hide (bar); + gtk_widget_show (statusbar->label); + + gimp_statusbar_pop (statusbar, "progress"); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0); + gtk_widget_set_sensitive (statusbar->cancel_button, FALSE); + gtk_widget_hide (statusbar->cancel_button); + + gimp_statusbar_restore_window_title (statusbar); + } +} + +static gboolean +gimp_statusbar_progress_is_active (GimpProgress *progress) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + return statusbar->progress_active; +} + +static void +gimp_statusbar_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + if (statusbar->progress_active) + { + GtkWidget *bar = statusbar->progressbar; + + gimp_statusbar_replace (statusbar, "progress", NULL, "%s", message); + + gimp_widget_flush_expose (bar); + + gimp_statusbar_override_window_title (statusbar); + } +} + +static void +gimp_statusbar_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + if (statusbar->progress_active) + { + guint64 time = g_get_monotonic_time (); + + if (time - statusbar->progress_last_update_time >= + MIN_PROGRESS_UPDATE_INTERVAL) + { + GtkWidget *bar = statusbar->progressbar; + GtkAllocation allocation; + gdouble diff; + + gtk_widget_get_allocation (bar, &allocation); + + statusbar->progress_value = percentage; + + diff = fabs (percentage - + gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (bar))); + + /* only update the progress bar if this causes a visible change */ + if (allocation.width * diff >= 1.0) + { + statusbar->progress_last_update_time = time; + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), + percentage); + + gimp_widget_flush_expose (bar); + } + } + } +} + +static gdouble +gimp_statusbar_progress_get_value (GimpProgress *progress) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + if (statusbar->progress_active) + return statusbar->progress_value; + + return 0.0; +} + +static void +gimp_statusbar_progress_pulse (GimpProgress *progress) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + + if (statusbar->progress_active) + { + guint64 time = g_get_monotonic_time (); + + if (time - statusbar->progress_last_update_time >= + MIN_PROGRESS_UPDATE_INTERVAL) + { + GtkWidget *bar = statusbar->progressbar; + + statusbar->progress_last_update_time = time; + + gtk_progress_bar_pulse (GTK_PROGRESS_BAR (bar)); + + gimp_widget_flush_expose (bar); + } + } +} + +static gboolean +gimp_statusbar_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) +{ + GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); + PangoLayout *layout; + const gchar *icon_name; + gboolean handle_msg = FALSE; + + /* don't accept a message if we are already displaying a more severe one */ + if (statusbar->temp_timeout_id && statusbar->temp_severity > severity) + return FALSE; + + /* we can only handle short one-liners */ + layout = gtk_widget_create_pango_layout (statusbar->label, message); + + icon_name = gimp_get_message_icon_name (severity); + + if (pango_layout_get_line_count (layout) == 1) + { + GtkAllocation label_allocation; + gint width; + + gtk_widget_get_allocation (statusbar->label, &label_allocation); + + pango_layout_get_pixel_size (layout, &width, NULL); + + if (width < label_allocation.width) + { + if (icon_name) + { + GdkPixbuf *pixbuf; + + pixbuf = gimp_statusbar_load_icon (statusbar, icon_name); + + width += ICON_SPACING + gdk_pixbuf_get_width (pixbuf); + + g_object_unref (pixbuf); + + handle_msg = (width < label_allocation.width); + } + else + { + handle_msg = TRUE; + } + } + } + + g_object_unref (layout); + + if (handle_msg) + gimp_statusbar_push_temp (statusbar, severity, icon_name, "%s", message); + + return handle_msg; +} + +static void +gimp_statusbar_progress_canceled (GtkWidget *button, + GimpStatusbar *statusbar) +{ + if (statusbar->progress_active) + gimp_progress_cancel (GIMP_PROGRESS (statusbar)); +} + +static void +gimp_statusbar_set_text (GimpStatusbar *statusbar, + const gchar *icon_name, + const gchar *text) +{ + if (statusbar->progress_active) + { + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (statusbar->progressbar), + text); + } + else + { + g_clear_object (&statusbar->icon); + + if (icon_name) + statusbar->icon = gimp_statusbar_load_icon (statusbar, icon_name); + + if (statusbar->icon) + { + PangoAttrList *attrs; + PangoAttribute *attr; + PangoRectangle rect; + gchar *tmp; + + tmp = g_strconcat (" ", text, NULL); + gtk_label_set_text (GTK_LABEL (statusbar->label), tmp); + g_free (tmp); + + rect.x = 0; + rect.y = 0; + rect.width = PANGO_SCALE * (gdk_pixbuf_get_width (statusbar->icon) + + ICON_SPACING); + rect.height = 0; + + attrs = pango_attr_list_new (); + + attr = pango_attr_shape_new (&rect, &rect); + attr->start_index = 0; + attr->end_index = 1; + pango_attr_list_insert (attrs, attr); + + gtk_label_set_attributes (GTK_LABEL (statusbar->label), attrs); + pango_attr_list_unref (attrs); + } + else + { + gtk_label_set_text (GTK_LABEL (statusbar->label), text); + gtk_label_set_attributes (GTK_LABEL (statusbar->label), NULL); + } + } +} + +static void +gimp_statusbar_update (GimpStatusbar *statusbar) +{ + GimpStatusbarMsg *msg = NULL; + + if (statusbar->messages) + msg = statusbar->messages->data; + + if (msg && msg->text) + { + gimp_statusbar_set_text (statusbar, msg->icon_name, msg->text); + } + else + { + gimp_statusbar_set_text (statusbar, NULL, ""); + } +} + + +/* public functions */ + +GtkWidget * +gimp_statusbar_new (void) +{ + return g_object_new (GIMP_TYPE_STATUSBAR, NULL); +} + +void +gimp_statusbar_set_shell (GimpStatusbar *statusbar, + GimpDisplayShell *shell) +{ + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell == statusbar->shell) + return; + + if (statusbar->shell) + { + g_signal_handlers_disconnect_by_func (statusbar->shell, + gimp_statusbar_shell_scaled, + statusbar); + g_signal_handlers_disconnect_by_func (statusbar->shell, + gimp_statusbar_shell_rotated, + statusbar); + g_signal_handlers_disconnect_by_func (statusbar->shell, + gimp_statusbar_shell_status_notify, + statusbar); + } + + statusbar->shell = shell; + + g_signal_connect_object (statusbar->shell, "scaled", + G_CALLBACK (gimp_statusbar_shell_scaled), + statusbar, 0); + g_signal_connect_object (statusbar->shell, "rotated", + G_CALLBACK (gimp_statusbar_shell_rotated), + statusbar, 0); + g_signal_connect_object (statusbar->shell, "notify::status", + G_CALLBACK (gimp_statusbar_shell_status_notify), + statusbar, 0); + gimp_statusbar_shell_rotated (shell, statusbar); +} + +gboolean +gimp_statusbar_get_visible (GimpStatusbar *statusbar) +{ + g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), FALSE); + + if (statusbar->progress_shown) + return FALSE; + + return gtk_widget_get_visible (GTK_WIDGET (statusbar)); +} + +void +gimp_statusbar_set_visible (GimpStatusbar *statusbar, + gboolean visible) +{ + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + if (statusbar->progress_shown) + { + if (visible) + { + statusbar->progress_shown = FALSE; + return; + } + } + + gtk_widget_set_visible (GTK_WIDGET (statusbar), visible); +} + +void +gimp_statusbar_empty (GimpStatusbar *statusbar) +{ + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + gtk_widget_hide (statusbar->cursor_label); + gtk_widget_hide (statusbar->unit_combo); + gtk_widget_hide (statusbar->scale_combo); + gtk_widget_hide (statusbar->rotate_widget); + gtk_widget_hide (statusbar->horizontal_flip_icon); + gtk_widget_hide (statusbar->vertical_flip_icon); +} + +void +gimp_statusbar_fill (GimpStatusbar *statusbar) +{ + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + gtk_widget_show (statusbar->cursor_label); + gtk_widget_show (statusbar->unit_combo); + gtk_widget_show (statusbar->scale_combo); + gtk_widget_show (statusbar->rotate_widget); + gimp_statusbar_shell_rotated (statusbar->shell, statusbar); +} + +void +gimp_statusbar_override_window_title (GimpStatusbar *statusbar) +{ + GtkWidget *toplevel; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar)); + + if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel))) + { + const gchar *message = gimp_statusbar_peek (statusbar, "progress"); + + if (message) + gtk_window_set_title (GTK_WINDOW (toplevel), message); + } +} + +void +gimp_statusbar_restore_window_title (GimpStatusbar *statusbar) +{ + GtkWidget *toplevel; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar)); + + if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel))) + { + g_object_notify (G_OBJECT (statusbar->shell), "title"); + } +} + +void +gimp_statusbar_push (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + ...) +{ + va_list args; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (context != NULL); + g_return_if_fail (format != NULL); + + va_start (args, format); + gimp_statusbar_push_valist (statusbar, context, icon_name, format, args); + va_end (args); +} + +void +gimp_statusbar_push_valist (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + va_list args) +{ + guint context_id; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (context != NULL); + g_return_if_fail (format != NULL); + + context_id = gimp_statusbar_get_context_id (statusbar, context); + + gimp_statusbar_add_message (statusbar, + context_id, + icon_name, format, args, + /* move_to_front = */ TRUE); +} + +void +gimp_statusbar_push_coords (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + GimpCursorPrecision precision, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help) +{ + GimpDisplayShell *shell; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (title != NULL); + g_return_if_fail (separator != NULL); + + if (help == NULL) + help = ""; + + shell = statusbar->shell; + + switch (precision) + { + case GIMP_CURSOR_PRECISION_PIXEL_CENTER: + x = (gint) x; + y = (gint) y; + break; + + case GIMP_CURSOR_PRECISION_PIXEL_BORDER: + x = RINT (x); + y = RINT (y); + break; + + case GIMP_CURSOR_PRECISION_SUBPIXEL: + break; + } + + if (shell->unit == GIMP_UNIT_PIXEL) + { + if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL) + { + gimp_statusbar_push (statusbar, context, + icon_name, + statusbar->cursor_format_str_f, + title, + x, + separator, + y, + help); + } + else + { + gimp_statusbar_push (statusbar, context, + icon_name, + statusbar->cursor_format_str, + title, + (gint) RINT (x), + separator, + (gint) RINT (y), + help); + } + } + else /* show real world units */ + { + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (gimp_display_get_image (shell->display), + &xres, &yres); + + gimp_statusbar_push (statusbar, context, + icon_name, + statusbar->cursor_format_str, + title, + gimp_pixels_to_units (x, shell->unit, xres), + separator, + gimp_pixels_to_units (y, shell->unit, yres), + help); + } +} + +void +gimp_statusbar_push_length (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *title, + GimpOrientationType axis, + gdouble value, + const gchar *help) +{ + GimpDisplayShell *shell; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (title != NULL); + + if (help == NULL) + help = ""; + + shell = statusbar->shell; + + if (shell->unit == GIMP_UNIT_PIXEL) + { + gimp_statusbar_push (statusbar, context, + icon_name, + statusbar->length_format_str, + title, + (gint) RINT (value), + help); + } + else /* show real world units */ + { + gdouble xres; + gdouble yres; + gdouble resolution; + + gimp_image_get_resolution (gimp_display_get_image (shell->display), + &xres, &yres); + + switch (axis) + { + case GIMP_ORIENTATION_HORIZONTAL: + resolution = xres; + break; + + case GIMP_ORIENTATION_VERTICAL: + resolution = yres; + break; + + default: + g_return_if_reached (); + break; + } + + gimp_statusbar_push (statusbar, context, + icon_name, + statusbar->length_format_str, + title, + gimp_pixels_to_units (value, shell->unit, resolution), + help); + } +} + +void +gimp_statusbar_replace (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + ...) +{ + va_list args; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (context != NULL); + g_return_if_fail (format != NULL); + + va_start (args, format); + gimp_statusbar_replace_valist (statusbar, context, icon_name, format, args); + va_end (args); +} + +void +gimp_statusbar_replace_valist (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + va_list args) +{ + guint context_id; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (context != NULL); + g_return_if_fail (format != NULL); + + context_id = gimp_statusbar_get_context_id (statusbar, context); + + gimp_statusbar_add_message (statusbar, + context_id, + icon_name, format, args, + /* move_to_front = */ FALSE); +} + +const gchar * +gimp_statusbar_peek (GimpStatusbar *statusbar, + const gchar *context) +{ + GSList *list; + guint context_id; + + g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), NULL); + g_return_val_if_fail (context != NULL, NULL); + + context_id = gimp_statusbar_get_context_id (statusbar, context); + + for (list = statusbar->messages; list; list = list->next) + { + GimpStatusbarMsg *msg = list->data; + + if (msg->context_id == context_id) + { + return msg->text; + } + } + + return NULL; +} + +void +gimp_statusbar_pop (GimpStatusbar *statusbar, + const gchar *context) +{ + guint context_id; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (context != NULL); + + context_id = gimp_statusbar_get_context_id (statusbar, context); + + gimp_statusbar_remove_message (statusbar, + context_id); +} + +void +gimp_statusbar_push_temp (GimpStatusbar *statusbar, + GimpMessageSeverity severity, + const gchar *icon_name, + const gchar *format, + ...) +{ + va_list args; + + va_start (args, format); + gimp_statusbar_push_temp_valist (statusbar, severity, icon_name, format, args); + va_end (args); +} + +void +gimp_statusbar_push_temp_valist (GimpStatusbar *statusbar, + GimpMessageSeverity severity, + const gchar *icon_name, + const gchar *format, + va_list args) +{ + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + g_return_if_fail (severity <= GIMP_MESSAGE_WARNING); + g_return_if_fail (format != NULL); + + /* don't accept a message if we are already displaying a more severe one */ + if (statusbar->temp_timeout_id && statusbar->temp_severity > severity) + return; + + if (statusbar->temp_timeout_id) + g_source_remove (statusbar->temp_timeout_id); + + statusbar->temp_timeout_id = + g_timeout_add (MESSAGE_TIMEOUT, + (GSourceFunc) gimp_statusbar_temp_timeout, statusbar); + + statusbar->temp_severity = severity; + + gimp_statusbar_add_message (statusbar, + statusbar->temp_context_id, + icon_name, format, args, + /* move_to_front = */ TRUE); + + if (severity >= GIMP_MESSAGE_WARNING) + gimp_widget_blink (statusbar->label); +} + +void +gimp_statusbar_pop_temp (GimpStatusbar *statusbar) +{ + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + if (statusbar->temp_timeout_id) + { + g_source_remove (statusbar->temp_timeout_id); + statusbar->temp_timeout_id = 0; + + gimp_statusbar_remove_message (statusbar, + statusbar->temp_context_id); + } +} + +void +gimp_statusbar_update_cursor (GimpStatusbar *statusbar, + GimpCursorPrecision precision, + gdouble x, + gdouble y) +{ + GimpDisplayShell *shell; + GimpImage *image; + gchar buffer[CURSOR_LEN]; + + g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); + + shell = statusbar->shell; + image = gimp_display_get_image (shell->display); + + if (! image || + x < 0 || + y < 0 || + x >= gimp_image_get_width (image) || + y >= gimp_image_get_height (image)) + { + gtk_widget_set_sensitive (statusbar->cursor_label, FALSE); + } + else + { + gtk_widget_set_sensitive (statusbar->cursor_label, TRUE); + } + + switch (precision) + { + case GIMP_CURSOR_PRECISION_PIXEL_CENTER: + x = (gint) x; + y = (gint) y; + break; + + case GIMP_CURSOR_PRECISION_PIXEL_BORDER: + x = RINT (x); + y = RINT (y); + break; + + case GIMP_CURSOR_PRECISION_SUBPIXEL: + break; + } + + if (shell->unit == GIMP_UNIT_PIXEL) + { + if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL) + { + g_snprintf (buffer, sizeof (buffer), + statusbar->cursor_format_str_f, + "", x, ", ", y, ""); + } + else + { + g_snprintf (buffer, sizeof (buffer), + statusbar->cursor_format_str, + "", (gint) RINT (x), ", ", (gint) RINT (y), ""); + } + } + else /* show real world units */ + { + GtkTreeModel *model; + GimpUnitStore *store; + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo)); + store = GIMP_UNIT_STORE (model); + + gimp_unit_store_set_pixel_values (store, x, y); + gimp_unit_store_get_values (store, shell->unit, &x, &y); + + g_snprintf (buffer, sizeof (buffer), + statusbar->cursor_format_str, + "", x, ", ", y, ""); + } + + gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), buffer); +} + +void +gimp_statusbar_clear_cursor (GimpStatusbar *statusbar) +{ + gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), ""); + gtk_widget_set_sensitive (statusbar->cursor_label, TRUE); +} + + +/* private functions */ + +static gboolean +gimp_statusbar_label_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpStatusbar *statusbar) +{ + if (statusbar->icon) + { + cairo_t *cr; + PangoRectangle rect; + gint x, y; + + cr = gdk_cairo_create (event->window); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_label_get_layout_offsets (GTK_LABEL (widget), &x, &y); + + pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (widget)), 0, + &rect); + + /* the rectangle width is negative when rendering right-to-left */ + x += PANGO_PIXELS (rect.x) + (rect.width < 0 ? + PANGO_PIXELS (rect.width) : 0); + y += PANGO_PIXELS (rect.y); + + gdk_cairo_set_source_pixbuf (cr, statusbar->icon, x, y); + cairo_paint (cr); + + cairo_destroy (cr); + } + + return FALSE; +} + +static void +gimp_statusbar_shell_scaled (GimpDisplayShell *shell, + GimpStatusbar *statusbar) +{ + static PangoLayout *layout = NULL; + + GimpImage *image = gimp_display_get_image (shell->display); + GtkTreeModel *model; + const gchar *text; + gint image_width; + gint image_height; + gdouble image_xres; + gdouble image_yres; + gint width; + + if (image) + { + image_width = gimp_image_get_width (image); + image_height = gimp_image_get_height (image); + gimp_image_get_resolution (image, &image_xres, &image_yres); + } + else + { + image_width = shell->disp_width; + image_height = shell->disp_height; + image_xres = shell->display->config->monitor_xres; + image_yres = shell->display->config->monitor_yres; + } + + g_signal_handlers_block_by_func (statusbar->scale_combo, + gimp_statusbar_scale_changed, statusbar); + gimp_scale_combo_box_set_scale (GIMP_SCALE_COMBO_BOX (statusbar->scale_combo), + gimp_zoom_model_get_factor (shell->zoom)); + g_signal_handlers_unblock_by_func (statusbar->scale_combo, + gimp_statusbar_scale_changed, statusbar); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo)); + gimp_unit_store_set_resolutions (GIMP_UNIT_STORE (model), + image_xres, image_yres); + + g_signal_handlers_block_by_func (statusbar->unit_combo, + gimp_statusbar_unit_changed, statusbar); + gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (statusbar->unit_combo), + shell->unit); + g_signal_handlers_unblock_by_func (statusbar->unit_combo, + gimp_statusbar_unit_changed, statusbar); + + if (shell->unit == GIMP_UNIT_PIXEL) + { + g_snprintf (statusbar->cursor_format_str, + sizeof (statusbar->cursor_format_str), + "%%s%%d%%s%%d%%s"); + g_snprintf (statusbar->cursor_format_str_f, + sizeof (statusbar->cursor_format_str_f), + "%%s%%.1f%%s%%.1f%%s"); + g_snprintf (statusbar->length_format_str, + sizeof (statusbar->length_format_str), + "%%s%%d%%s"); + } + else /* show real world units */ + { + gint w_digits; + gint h_digits; + + w_digits = gimp_unit_get_scaled_digits (shell->unit, image_xres); + h_digits = gimp_unit_get_scaled_digits (shell->unit, image_yres); + + g_snprintf (statusbar->cursor_format_str, + sizeof (statusbar->cursor_format_str), + "%%s%%.%df%%s%%.%df%%s", + w_digits, h_digits); + strcpy (statusbar->cursor_format_str_f, statusbar->cursor_format_str); + g_snprintf (statusbar->length_format_str, + sizeof (statusbar->length_format_str), + "%%s%%.%df%%s", MAX (w_digits, h_digits)); + } + + gimp_statusbar_update_cursor (statusbar, GIMP_CURSOR_PRECISION_SUBPIXEL, + -image_width, -image_height); + + text = gtk_label_get_text (GTK_LABEL (statusbar->cursor_label)); + + /* one static layout for all displays should be fine */ + if (! layout) + layout = gtk_widget_create_pango_layout (statusbar->cursor_label, NULL); + + pango_layout_set_text (layout, text, -1); + pango_layout_get_pixel_size (layout, &width, NULL); + + gtk_widget_set_size_request (statusbar->cursor_label, width, -1); + + gimp_statusbar_clear_cursor (statusbar); +} + +static void +gimp_statusbar_shell_rotated (GimpDisplayShell *shell, + GimpStatusbar *statusbar) +{ + if (shell->rotate_angle != 0.0) + { + /* Degree symbol U+00B0. There are no spaces between the value and the + * unit for angular rotation. + */ + gchar *text = g_strdup_printf (" %.2f\xC2\xB0", shell->rotate_angle); + + gtk_label_set_text (GTK_LABEL (statusbar->rotate_label), text); + g_free (text); + + gtk_widget_show (statusbar->rotate_widget); + } + else + { + gtk_widget_hide (statusbar->rotate_widget); + } + + if (shell->flip_horizontally) + gtk_widget_show (statusbar->horizontal_flip_icon); + else + gtk_widget_hide (statusbar->horizontal_flip_icon); + + if (shell->flip_vertically) + gtk_widget_show (statusbar->vertical_flip_icon); + else + gtk_widget_hide (statusbar->vertical_flip_icon); +} + +static void +gimp_statusbar_shell_status_notify (GimpDisplayShell *shell, + const GParamSpec *pspec, + GimpStatusbar *statusbar) +{ + gimp_statusbar_replace (statusbar, "title", + NULL, "%s", shell->status); +} + +static void +gimp_statusbar_unit_changed (GimpUnitComboBox *combo, + GimpStatusbar *statusbar) +{ + gimp_display_shell_set_unit (statusbar->shell, + gimp_unit_combo_box_get_active (combo)); +} + +static void +gimp_statusbar_scale_changed (GimpScaleComboBox *combo, + GimpStatusbar *statusbar) +{ + gimp_display_shell_scale (statusbar->shell, + GIMP_ZOOM_TO, + gimp_scale_combo_box_get_scale (combo), + GIMP_ZOOM_FOCUS_BEST_GUESS); +} + +static void +gimp_statusbar_scale_activated (GimpScaleComboBox *combo, + GimpStatusbar *statusbar) +{ + gtk_widget_grab_focus (statusbar->shell->canvas); +} + +static gboolean +gimp_statusbar_rotate_pressed (GtkWidget *event_box, + GdkEvent *event, + GimpStatusbar *statusbar) +{ + GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell); + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_activate_action (manager, "view", "view-rotate-other"); + return FALSE; +} + +static gboolean +gimp_statusbar_horiz_flip_pressed (GtkWidget *event_box, + GdkEvent *event, + GimpStatusbar *statusbar) +{ + GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell); + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_activate_action (manager, "view", "view-flip-horizontally"); + + return FALSE; +} + +static gboolean +gimp_statusbar_vert_flip_pressed (GtkWidget *event_box, + GdkEvent *event, + GimpStatusbar *statusbar) +{ + GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell); + GimpUIManager *manager = gimp_image_window_get_ui_manager (window); + + gimp_ui_manager_activate_action (manager, "view", "view-flip-vertically"); + + return FALSE; +} + +static guint +gimp_statusbar_get_context_id (GimpStatusbar *statusbar, + const gchar *context) +{ + guint id = GPOINTER_TO_UINT (g_hash_table_lookup (statusbar->context_ids, + context)); + + if (! id) + { + id = statusbar->seq_context_id++; + + g_hash_table_insert (statusbar->context_ids, + g_strdup (context), GUINT_TO_POINTER (id)); + } + + return id; +} + +static gboolean +gimp_statusbar_temp_timeout (GimpStatusbar *statusbar) +{ + gimp_statusbar_pop_temp (statusbar); + + return FALSE; +} + +static void +gimp_statusbar_add_message (GimpStatusbar *statusbar, + guint context_id, + const gchar *icon_name, + const gchar *format, + va_list args, + gboolean move_to_front) +{ + gchar *message; + GSList *list; + GimpStatusbarMsg *msg; + gint position; + + message = gimp_statusbar_vprintf (format, args); + + for (list = statusbar->messages; list; list = g_slist_next (list)) + { + msg = list->data; + + if (msg->context_id == context_id) + { + gboolean is_front_message = (list == statusbar->messages); + + if ((is_front_message || ! move_to_front) && + strcmp (msg->text, message) == 0 && + g_strcmp0 (msg->icon_name, icon_name) == 0) + { + g_free (message); + return; + } + + if (move_to_front) + { + statusbar->messages = g_slist_remove (statusbar->messages, msg); + gimp_statusbar_msg_free (msg); + + break; + } + else + { + g_free (msg->icon_name); + msg->icon_name = g_strdup (icon_name); + + g_free (msg->text); + msg->text = message; + + if (is_front_message) + gimp_statusbar_update (statusbar); + + return; + } + } + } + + msg = g_slice_new (GimpStatusbarMsg); + + msg->context_id = context_id; + msg->icon_name = g_strdup (icon_name); + msg->text = message; + + /* find the position at which to insert the new message */ + position = 0; + /* progress messages are always at the front of the list */ + if (! (statusbar->progress_active && + context_id == gimp_statusbar_get_context_id (statusbar, "progress"))) + { + if (statusbar->progress_active) + position++; + + /* temporary messages are in front of all other non-progress messages */ + if (statusbar->temp_timeout_id && + context_id != statusbar->temp_context_id) + position++; + } + + statusbar->messages = g_slist_insert (statusbar->messages, msg, position); + + if (position == 0) + gimp_statusbar_update (statusbar); +} + +static void +gimp_statusbar_remove_message (GimpStatusbar *statusbar, + guint context_id) +{ + GSList *list; + gboolean needs_update = FALSE; + + for (list = statusbar->messages; list; list = g_slist_next (list)) + { + GimpStatusbarMsg *msg = list->data; + + if (msg->context_id == context_id) + { + needs_update = (list == statusbar->messages); + + statusbar->messages = g_slist_remove (statusbar->messages, msg); + gimp_statusbar_msg_free (msg); + + break; + } + } + + if (needs_update) + gimp_statusbar_update (statusbar); +} + +static void +gimp_statusbar_msg_free (GimpStatusbarMsg *msg) +{ + g_free (msg->icon_name); + g_free (msg->text); + + g_slice_free (GimpStatusbarMsg, msg); +} + +static gchar * +gimp_statusbar_vprintf (const gchar *format, + va_list args) +{ + gchar *message; + gchar *newline; + + message = g_strdup_vprintf (format, args); + + /* guard us from multi-line strings */ + newline = strchr (message, '\r'); + if (newline) + *newline = '\0'; + + newline = strchr (message, '\n'); + if (newline) + *newline = '\0'; + + return message; +} + +static GdkPixbuf * +gimp_statusbar_load_icon (GimpStatusbar *statusbar, + const gchar *icon_name) +{ + GdkPixbuf *icon; + + if (G_UNLIKELY (! statusbar->icon_hash)) + { + statusbar->icon_hash = + g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + } + + icon = g_hash_table_lookup (statusbar->icon_hash, icon_name); + + if (icon) + return g_object_ref (icon); + + icon = gimp_widget_load_icon (statusbar->label, icon_name, 16); + + /* this is not optimal but so what */ + if (g_hash_table_size (statusbar->icon_hash) > 16) + g_hash_table_remove_all (statusbar->icon_hash); + + g_hash_table_insert (statusbar->icon_hash, + g_strdup (icon_name), g_object_ref (icon)); + + return icon; +} diff --git a/app/display/gimpstatusbar.h b/app/display/gimpstatusbar.h new file mode 100644 index 0000000..7d5f279 --- /dev/null +++ b/app/display/gimpstatusbar.h @@ -0,0 +1,158 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_STATUSBAR_H__ +#define __GIMP_STATUSBAR_H__ + +G_BEGIN_DECLS + + +/* maximal length of the format string for the cursor-coordinates */ +#define CURSOR_FORMAT_LENGTH 32 + + +#define GIMP_TYPE_STATUSBAR (gimp_statusbar_get_type ()) +#define GIMP_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STATUSBAR, GimpStatusbar)) +#define GIMP_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STATUSBAR, GimpStatusbarClass)) +#define GIMP_IS_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STATUSBAR)) +#define GIMP_IS_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STATUSBAR)) +#define GIMP_STATUSBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STATUSBAR, GimpStatusbarClass)) + +typedef struct _GimpStatusbarClass GimpStatusbarClass; + +struct _GimpStatusbar +{ + GtkStatusbar parent_instance; + + GimpDisplayShell *shell; + + GSList *messages; + GHashTable *context_ids; + guint seq_context_id; + + GdkPixbuf *icon; + GHashTable *icon_hash; + + guint temp_context_id; + guint temp_timeout_id; + GimpMessageSeverity temp_severity; + + gchar cursor_format_str[CURSOR_FORMAT_LENGTH]; + gchar cursor_format_str_f[CURSOR_FORMAT_LENGTH]; + gchar length_format_str[CURSOR_FORMAT_LENGTH]; + + GtkWidget *cursor_label; + GtkWidget *unit_combo; + GtkWidget *scale_combo; + GtkWidget *rotate_widget; + GtkWidget *rotate_label; + GtkWidget *horizontal_flip_icon; + GtkWidget *vertical_flip_icon; + GtkWidget *label; /* same as GtkStatusbar->label */ + + GtkWidget *progressbar; + GtkWidget *cancel_button; + gboolean progress_active; + gboolean progress_shown; + gdouble progress_value; + guint64 progress_last_update_time; +}; + +struct _GimpStatusbarClass +{ + GtkStatusbarClass parent_class; +}; + + +GType gimp_statusbar_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_statusbar_new (void); + +void gimp_statusbar_set_shell (GimpStatusbar *statusbar, + GimpDisplayShell *shell); + +gboolean gimp_statusbar_get_visible (GimpStatusbar *statusbar); +void gimp_statusbar_set_visible (GimpStatusbar *statusbar, + gboolean visible); +void gimp_statusbar_empty (GimpStatusbar *statusbar); +void gimp_statusbar_fill (GimpStatusbar *statusbar); + +void gimp_statusbar_override_window_title (GimpStatusbar *statusbar); +void gimp_statusbar_restore_window_title (GimpStatusbar *statusbar); + +void gimp_statusbar_push (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + ...) G_GNUC_PRINTF (4, 5); +void gimp_statusbar_push_valist (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + va_list args) G_GNUC_PRINTF (4, 0); +void gimp_statusbar_push_coords (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + GimpCursorPrecision precision, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help); +void gimp_statusbar_push_length (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *title, + GimpOrientationType axis, + gdouble value, + const gchar *help); +void gimp_statusbar_replace (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + ...) G_GNUC_PRINTF (4, 5); +void gimp_statusbar_replace_valist (GimpStatusbar *statusbar, + const gchar *context, + const gchar *icon_name, + const gchar *format, + va_list args) G_GNUC_PRINTF (4, 0); +const gchar * gimp_statusbar_peek (GimpStatusbar *statusbar, + const gchar *context); +void gimp_statusbar_pop (GimpStatusbar *statusbar, + const gchar *context); + +void gimp_statusbar_push_temp (GimpStatusbar *statusbar, + GimpMessageSeverity severity, + const gchar *icon_name, + const gchar *format, + ...) G_GNUC_PRINTF (4, 5); +void gimp_statusbar_push_temp_valist (GimpStatusbar *statusbar, + GimpMessageSeverity severity, + const gchar *icon_name, + const gchar *format, + va_list args) G_GNUC_PRINTF (4, 0); +void gimp_statusbar_pop_temp (GimpStatusbar *statusbar); + +void gimp_statusbar_update_cursor (GimpStatusbar *statusbar, + GimpCursorPrecision precision, + gdouble x, + gdouble y); +void gimp_statusbar_clear_cursor (GimpStatusbar *statusbar); + + +G_END_DECLS + +#endif /* __GIMP_STATUSBAR_H__ */ diff --git a/app/display/gimptoolcompass.c b/app/display/gimptoolcompass.c new file mode 100644 index 0000000..3d9045b --- /dev/null +++ b/app/display/gimptoolcompass.c @@ -0,0 +1,1218 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolcompass.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Measure tool + * Copyright (C) 1999-2003 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-utils.h" +#include "core/gimpimage.h" +#include "core/gimpmarshal.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvashandle.h" +#include "gimpcanvasline.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-appearance.h" +#include "gimpdisplayshell-transform.h" +#include "gimpdisplayshell-utils.h" +#include "gimptoolcompass.h" + +#include "gimp-intl.h" + + +#define ARC_RADIUS 30 +#define ARC_GAP (ARC_RADIUS / 2) +#define EPSILON 1e-6 + + +/* possible measure functions */ +typedef enum +{ + CREATING, + ADDING, + MOVING, + MOVING_ALL, + GUIDING, + FINISHED +} CompassFunction; + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_N_POINTS, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_X3, + PROP_Y3, + PROP_PIXEL_ANGLE, + PROP_UNIT_ANGLE, + PROP_EFFECTIVE_ORIENTATION +}; + +enum +{ + CREATE_GUIDES, + LAST_SIGNAL +}; + +struct _GimpToolCompassPrivate +{ + GimpCompassOrientation orientation; + gint n_points; + gint x[3]; + gint y[3]; + + GimpVector2 radius1; + GimpVector2 radius2; + gdouble display_angle; + gdouble pixel_angle; + gdouble unit_angle; + GimpCompassOrientation effective_orientation; + + CompassFunction function; + gdouble mouse_x; + gdouble mouse_y; + gint last_x; + gint last_y; + gint point; + + GimpCanvasItem *line1; + GimpCanvasItem *line2; + GimpCanvasItem *arc; + GimpCanvasItem *arc_line; + GimpCanvasItem *handles[3]; +}; + + +/* local function prototypes */ + +static void gimp_tool_compass_constructed (GObject *object); +static void gimp_tool_compass_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_compass_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_compass_changed (GimpToolWidget *widget); +static gint gimp_tool_compass_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_compass_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_compass_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_compass_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_compass_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_compass_leave_notify (GimpToolWidget *widget); +static void gimp_tool_compass_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_compass_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static gint gimp_tool_compass_get_point (GimpToolCompass *compass, + const GimpCoords *coords); +static void gimp_tool_compass_update_hilight (GimpToolCompass *compass); +static void gimp_tool_compass_update_angle (GimpToolCompass *compass, + GimpCompassOrientation orientation, + gboolean flip); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolCompass, gimp_tool_compass, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_compass_parent_class + +static guint compass_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_tool_compass_class_init (GimpToolCompassClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_compass_constructed; + object_class->set_property = gimp_tool_compass_set_property; + object_class->get_property = gimp_tool_compass_get_property; + + widget_class->changed = gimp_tool_compass_changed; + widget_class->button_press = gimp_tool_compass_button_press; + widget_class->button_release = gimp_tool_compass_button_release; + widget_class->motion = gimp_tool_compass_motion; + widget_class->hit = gimp_tool_compass_hit; + widget_class->hover = gimp_tool_compass_hover; + widget_class->leave_notify = gimp_tool_compass_leave_notify; + widget_class->motion_modifier = gimp_tool_compass_motion_modifier; + widget_class->get_cursor = gimp_tool_compass_get_cursor; + widget_class->update_on_scale = TRUE; + widget_class->update_on_rotate = TRUE; + + compass_signals[CREATE_GUIDES] = + g_signal_new ("create-guides", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolCompassClass, create_guides), + NULL, NULL, + gimp_marshal_VOID__INT_INT_BOOLEAN_BOOLEAN, + G_TYPE_NONE, 4, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + + g_object_class_install_property (object_class, PROP_ORIENTATION, + g_param_spec_enum ("orientation", NULL, NULL, + GIMP_TYPE_COMPASS_ORIENTATION, + GIMP_COMPASS_ORIENTATION_AUTO, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_N_POINTS, + g_param_spec_int ("n-points", NULL, NULL, + 1, 3, 1, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_int ("x1", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_int ("y1", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_int ("x2", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_int ("y2", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X3, + g_param_spec_int ("x3", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y3, + g_param_spec_int ("y3", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIXEL_ANGLE, + g_param_spec_double ("pixel-angle", NULL, NULL, + -G_PI, G_PI, 0.0, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_UNIT_ANGLE, + g_param_spec_double ("unit-angle", NULL, NULL, + -G_PI, G_PI, 0.0, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_EFFECTIVE_ORIENTATION, + g_param_spec_enum ("effective-orientation", NULL, NULL, + GIMP_TYPE_COMPASS_ORIENTATION, + GIMP_COMPASS_ORIENTATION_AUTO, + GIMP_PARAM_READABLE)); +} + +static void +gimp_tool_compass_init (GimpToolCompass *compass) +{ + compass->private = gimp_tool_compass_get_instance_private (compass); + + compass->private->point = -1; +} + +static void +gimp_tool_compass_constructed (GObject *object) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolCompassPrivate *private = compass->private; + GimpCanvasGroup *stroke_group; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->line1 = gimp_tool_widget_add_line (widget, + private->x[0], + private->y[0], + private->x[1], + private->y[1]); + + private->line2 = gimp_tool_widget_add_line (widget, + private->x[0], + private->y[0], + private->x[2], + private->y[2]); + + private->arc = gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CIRCLE, + private->x[0], + private->y[0], + ARC_RADIUS * 2 + 1, + ARC_RADIUS * 2 + 1, + GIMP_HANDLE_ANCHOR_CENTER); + + private->arc_line = gimp_tool_widget_add_line (widget, + private->x[0], + private->y[0], + private->x[0] + 10, + private->y[0]); + + gimp_tool_widget_pop_group (widget); + + for (i = 0; i < 3; i++) + { + private->handles[i] = + gimp_tool_widget_add_handle (widget, + i == 0 ? + GIMP_HANDLE_CIRCLE : GIMP_HANDLE_CROSS, + private->x[i], + private->y[i], + GIMP_CANVAS_HANDLE_SIZE_CROSS, + GIMP_CANVAS_HANDLE_SIZE_CROSS, + GIMP_HANDLE_ANCHOR_CENTER); + } + + gimp_tool_compass_changed (widget); +} + +static void +gimp_tool_compass_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (object); + GimpToolCompassPrivate *private = compass->private; + + switch (property_id) + { + case PROP_ORIENTATION: + private->orientation = g_value_get_enum (value); + break; + case PROP_N_POINTS: + private->n_points = g_value_get_int (value); + break; + case PROP_X1: + private->x[0] = g_value_get_int (value); + break; + case PROP_Y1: + private->y[0] = g_value_get_int (value); + break; + case PROP_X2: + private->x[1] = g_value_get_int (value); + break; + case PROP_Y2: + private->y[1] = g_value_get_int (value); + break; + case PROP_X3: + private->x[2] = g_value_get_int (value); + break; + case PROP_Y3: + private->y[2] = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_compass_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (object); + GimpToolCompassPrivate *private = compass->private; + + switch (property_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, private->orientation); + break; + case PROP_N_POINTS: + g_value_set_int (value, private->n_points); + break; + case PROP_X1: + g_value_set_int (value, private->x[0]); + break; + case PROP_Y1: + g_value_set_int (value, private->y[0]); + break; + case PROP_X2: + g_value_set_int (value, private->x[1]); + break; + case PROP_Y2: + g_value_set_int (value, private->y[1]); + break; + case PROP_X3: + g_value_set_int (value, private->x[2]); + break; + case PROP_Y3: + g_value_set_int (value, private->y[2]); + break; + case PROP_PIXEL_ANGLE: + g_value_set_double (value, private->pixel_angle); + break; + case PROP_UNIT_ANGLE: + g_value_set_double (value, private->unit_angle); + break; + case PROP_EFFECTIVE_ORIENTATION: + g_value_set_enum (value, private->effective_orientation); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_compass_changed (GimpToolWidget *widget) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + gdouble angle1; + gdouble angle2; + gint draw_arc = 0; + gboolean draw_arc_line = FALSE; + gdouble arc_line_display_length; + gdouble arc_line_length; + + gimp_tool_compass_update_angle (compass, private->orientation, FALSE); + + angle1 = -atan2 (private->radius1.y * shell->scale_y, + private->radius1.x * shell->scale_x); + angle2 = -private->display_angle; + + gimp_canvas_line_set (private->line1, + private->x[0], + private->y[0], + private->x[1], + private->y[1]); + gimp_canvas_item_set_visible (private->line1, private->n_points > 1); + if (private->n_points > 1 && + gimp_canvas_item_transform_distance (private->line1, + private->x[0], + private->y[0], + private->x[1], + private->y[1]) > ARC_RADIUS) + { + draw_arc++; + } + + + arc_line_display_length = ARC_RADIUS + + (GIMP_CANVAS_HANDLE_SIZE_CROSS >> 1) + + ARC_GAP; + arc_line_length = arc_line_display_length / + hypot (private->radius2.x * shell->scale_x, + private->radius2.y * shell->scale_y); + + if (private->n_points > 2) + { + gdouble length = gimp_canvas_item_transform_distance (private->line2, + private->x[0], + private->y[0], + private->x[2], + private->y[2]); + + if (length > ARC_RADIUS) + { + draw_arc++; + draw_arc_line = TRUE; + + if (length > arc_line_display_length) + { + gimp_canvas_line_set ( + private->line2, + private->x[0] + private->radius2.x * arc_line_length, + private->y[0] + private->radius2.y * arc_line_length, + private->x[2], + private->y[2]); + gimp_canvas_item_set_visible (private->line2, TRUE); + } + else + { + gimp_canvas_item_set_visible (private->line2, FALSE); + } + } + else + { + gimp_canvas_line_set (private->line2, + private->x[0], + private->y[0], + private->x[2], + private->y[2]); + gimp_canvas_item_set_visible (private->line2, TRUE); + } + } + else + { + gimp_canvas_item_set_visible (private->line2, FALSE); + } + + gimp_canvas_handle_set_position (private->arc, + private->x[0], private->y[0]); + gimp_canvas_handle_set_angles (private->arc, angle1, angle2); + gimp_canvas_item_set_visible (private->arc, + private->n_points > 1 && + draw_arc == private->n_points - 1 && + fabs (angle2) > EPSILON); + + arc_line_length = (ARC_RADIUS + (GIMP_CANVAS_HANDLE_SIZE_CROSS >> 1)) / + hypot (private->radius2.x * shell->scale_x, + private->radius2.y * shell->scale_y); + + gimp_canvas_line_set (private->arc_line, + private->x[0], + private->y[0], + private->x[0] + private->radius2.x * arc_line_length, + private->y[0] + private->radius2.y * arc_line_length); + gimp_canvas_item_set_visible (private->arc_line, + (private->n_points == 2 || draw_arc_line) && + fabs (angle2) > EPSILON); + + gimp_canvas_handle_set_position (private->handles[0], + private->x[0], private->y[0]); + gimp_canvas_item_set_visible (private->handles[0], + private->n_points > 0); + + gimp_canvas_handle_set_position (private->handles[1], + private->x[1], private->y[1]); + gimp_canvas_item_set_visible (private->handles[1], + private->n_points > 1); + + gimp_canvas_handle_set_position (private->handles[2], + private->x[2], private->y[2]); + gimp_canvas_item_set_visible (private->handles[2], + private->n_points > 2); + + gimp_tool_compass_update_hilight (compass); +} + +gint +gimp_tool_compass_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + + private->function = CREATING; + + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + /* if the cursor is in one of the handles, the new function will be + * moving or adding a new point or guide + */ + if (private->point != -1) + { + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask (); + + if (state & (toggle_mask | GDK_MOD1_MASK)) + { + gboolean create_hguide = (state & toggle_mask); + gboolean create_vguide = (state & GDK_MOD1_MASK); + + g_signal_emit (compass, compass_signals[CREATE_GUIDES], 0, + private->x[private->point], + private->y[private->point], + create_hguide, + create_vguide); + + private->function = GUIDING; + } + else + { + if (private->n_points == 1 || (state & extend_mask)) + private->function = ADDING; + else + private->function = MOVING; + } + } + + /* adding to the middle point makes no sense */ + if (private->point == 0 && + private->function == ADDING && + private->n_points == 3) + { + private->function = MOVING; + } + + /* if the function is still CREATING, we are outside the handles */ + if (private->function == CREATING) + { + if (private->n_points > 1 && (state & GDK_MOD1_MASK)) + { + private->function = MOVING_ALL; + + private->last_x = coords->x; + private->last_y = coords->y; + } + } + + if (private->function == CREATING) + { + /* set the first point and go into ADDING mode */ + g_object_set (compass, + "n-points", 1, + "x1", (gint) (coords->x + 0.5), + "y1", (gint) (coords->y + 0.5), + "x2", 0, + "y2", 0, + "x3", 0, + "y3", 0, + NULL); + + private->point = 0; + private->function = ADDING; + } + + return 1; +} + +void +gimp_tool_compass_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + + private->function = FINISHED; +} + +void +gimp_tool_compass_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + gint new_n_points; + gint new_x[3]; + gint new_y[3]; + gint dx, dy; + gint tmp; + + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + /* A few comments here, because this routine looks quite weird at first ... + * + * The goal is to keep point 0, called the start point, to be + * always the one in the middle or, if there are only two points, + * the one that is fixed. The angle is then always measured at + * this point. + */ + + new_n_points = private->n_points; + new_x[0] = private->x[0]; + new_y[0] = private->y[0]; + new_x[1] = private->x[1]; + new_y[1] = private->y[1]; + new_x[2] = private->x[2]; + new_y[2] = private->y[2]; + + switch (private->function) + { + case ADDING: + switch (private->point) + { + case 0: + /* we are adding to the start point */ + break; + + case 1: + /* we are adding to the end point, make it the new start point */ + new_x[0] = private->x[1]; + new_y[0] = private->y[1]; + + new_x[1] = private->x[0]; + new_y[1] = private->y[0]; + break; + + case 2: + /* we are adding to the third point, make it the new start point */ + new_x[1] = private->x[0]; + new_y[1] = private->y[0]; + new_x[0] = private->x[2]; + new_y[0] = private->y[2]; + break; + + default: + break; + } + + new_n_points = MIN (new_n_points + 1, 3); + + private->point = new_n_points - 1; + private->function = MOVING; + /* don't break here! */ + + case MOVING: + /* if we are moving the start point and only have two, make it + * the end point + */ + if (new_n_points == 2 && private->point == 0) + { + tmp = new_x[0]; + new_x[0] = new_x[1]; + new_x[1] = tmp; + + tmp = new_y[0]; + new_y[0] = new_y[1]; + new_y[1] = tmp; + + private->point = 1; + } + + new_x[private->point] = ROUND (coords->x); + new_y[private->point] = ROUND (coords->y); + + if (state & gimp_get_constrain_behavior_mask ()) + { + gdouble x = new_x[private->point]; + gdouble y = new_y[private->point]; + + gimp_display_shell_constrain_line (gimp_tool_widget_get_shell (widget), + new_x[0], new_y[0], + &x, &y, + GIMP_CONSTRAIN_LINE_15_DEGREES); + + new_x[private->point] = ROUND (x); + new_y[private->point] = ROUND (y); + } + + g_object_set (compass, + "n-points", new_n_points, + "x1", new_x[0], + "y1", new_y[0], + "x2", new_x[1], + "y2", new_y[1], + "x3", new_x[2], + "y3", new_y[2], + NULL); + break; + + case MOVING_ALL: + dx = ROUND (coords->x) - private->last_x; + dy = ROUND (coords->y) - private->last_y; + + g_object_set (compass, + "x1", new_x[0] + dx, + "y1", new_y[0] + dy, + "x2", new_x[1] + dx, + "y2", new_y[1] + dy, + "x3", new_x[2] + dx, + "y3", new_y[2] + dy, + NULL); + + private->last_x = ROUND (coords->x); + private->last_y = ROUND (coords->y); + break; + + default: + break; + } +} + +GimpHit +gimp_tool_compass_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + + if (gimp_tool_compass_get_point (compass, coords) >= 0) + return GIMP_HIT_DIRECT; + else + return GIMP_HIT_INDIRECT; +} + +void +gimp_tool_compass_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + gint point; + + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + point = gimp_tool_compass_get_point (compass, coords); + + if (point >= 0) + { + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask (); + gchar *status; + + if (state & toggle_mask) + { + if (state & GDK_MOD1_MASK) + { + status = gimp_suggest_modifiers (_("Click to place " + "vertical and " + "horizontal guides"), + 0, + NULL, NULL, NULL); + } + else + { + status = gimp_suggest_modifiers (_("Click to place a " + "horizontal guide"), + GDK_MOD1_MASK & ~state, + NULL, NULL, NULL); + } + } + else if (state & GDK_MOD1_MASK) + { + status = gimp_suggest_modifiers (_("Click to place a " + "vertical guide"), + toggle_mask & ~state, + NULL, NULL, NULL); + } + else if ((state & extend_mask) && + ! ((point == 0) && (private->n_points == 3))) + { + status = gimp_suggest_modifiers (_("Click-Drag to add a " + "new point"), + (toggle_mask | + GDK_MOD1_MASK) & ~state, + NULL, NULL, NULL); + } + else + { + if ((point == 0) && (private->n_points == 3)) + state |= extend_mask; + + status = gimp_suggest_modifiers (_("Click-Drag to move this " + "point"), + (extend_mask | + toggle_mask | + GDK_MOD1_MASK) & ~state, + NULL, NULL, NULL); + } + + gimp_tool_widget_set_status (widget, status); + + g_free (status); + } + else + { + if ((private->n_points > 1) && (state & GDK_MOD1_MASK)) + { + gimp_tool_widget_set_status (widget, + _("Click-Drag to move all points")); + } + else + { + gimp_tool_widget_set_status (widget, NULL); + } + } + + if (point != private->point) + { + private->point = point; + + gimp_tool_compass_update_hilight (compass); + } +} + +void +gimp_tool_compass_leave_notify (GimpToolWidget *widget) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + + if (private->point != -1) + { + private->point = -1; + + gimp_tool_compass_update_hilight (compass); + } + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static void +gimp_tool_compass_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + + if (key == gimp_get_constrain_behavior_mask () && + private->function == MOVING) + { + gint new_x[3]; + gint new_y[3]; + gdouble x = private->mouse_x; + gdouble y = private->mouse_y; + + new_x[0] = private->x[0]; + new_y[0] = private->y[0]; + new_x[1] = private->x[1]; + new_y[1] = private->y[1]; + new_x[2] = private->x[2]; + new_y[2] = private->y[2]; + + if (press) + { + gimp_display_shell_constrain_line (gimp_tool_widget_get_shell (widget), + private->x[0], private->y[0], + &x, &y, + GIMP_CONSTRAIN_LINE_15_DEGREES); + } + + new_x[private->point] = ROUND (x); + new_y[private->point] = ROUND (y); + + g_object_set (compass, + "x1", new_x[0], + "y1", new_y[0], + "x2", new_x[1], + "y2", new_y[1], + "x3", new_x[2], + "y3", new_y[2], + NULL); + } +} + +static gboolean +gimp_tool_compass_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolCompass *compass = GIMP_TOOL_COMPASS (widget); + GimpToolCompassPrivate *private = compass->private; + + if (private->point != -1) + { + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask (); + + if (state & toggle_mask) + { + if (state & GDK_MOD1_MASK) + { + *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT; + return TRUE; + } + else + { + *cursor = GIMP_CURSOR_SIDE_BOTTOM; + return TRUE; + } + } + else if (state & GDK_MOD1_MASK) + { + *cursor = GIMP_CURSOR_SIDE_RIGHT; + return TRUE; + } + else if ((state & extend_mask) && + ! ((private->point == 0) && + (private->n_points == 3))) + { + *modifier = GIMP_CURSOR_MODIFIER_PLUS; + return TRUE; + } + else + { + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + return TRUE; + } + } + else + { + if ((private->n_points > 1) && (state & GDK_MOD1_MASK)) + { + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + return TRUE; + } + } + + return FALSE; +} + +static gint +gimp_tool_compass_get_point (GimpToolCompass *compass, + const GimpCoords *coords) +{ + GimpToolCompassPrivate *private = compass->private; + gint i; + + for (i = 0; i < private->n_points; i++) + { + if (gimp_canvas_item_hit (private->handles[i], + coords->x, coords->y)) + { + return i; + } + } + + return -1; +} + +static void +gimp_tool_compass_update_hilight (GimpToolCompass *compass) +{ + GimpToolCompassPrivate *private = compass->private; + gint i; + + for (i = 0; i < private->n_points; i++) + { + if (private->handles[i]) + { + gimp_canvas_item_set_highlight (private->handles[i], + private->point == i); + } + } +} + +static void +gimp_tool_compass_update_angle (GimpToolCompass *compass, + GimpCompassOrientation orientation, + gboolean flip) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (compass); + GimpToolCompassPrivate *private = compass->private; + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + GimpImage *image = gimp_display_get_image (shell->display); + GimpVector2 radius1; + GimpVector2 radius2; + gdouble pixel_angle; + gdouble unit_angle; + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (image, &xres, &yres); + + private->radius1.x = private->x[1] - private->x[0]; + private->radius1.y = private->y[1] - private->y[0]; + + if (private->n_points == 3) + { + orientation = GIMP_COMPASS_ORIENTATION_AUTO; + + private->radius2.x = private->x[2] - private->x[0]; + private->radius2.y = private->y[2] - private->y[0]; + } + else + { + gdouble angle = -shell->rotate_angle * G_PI / 180.0; + + if (orientation == GIMP_COMPASS_ORIENTATION_VERTICAL) + angle -= G_PI / 2.0; + + if (flip) + angle += G_PI; + + if (shell->flip_horizontally) + angle = G_PI - angle; + if (shell->flip_vertically) + angle = -angle; + + private->radius2.x = cos (angle); + private->radius2.y = sin (angle); + + if (! shell->dot_for_dot) + { + private->radius2.x *= xres; + private->radius2.y *= yres; + + gimp_vector2_normalize (&private->radius2); + } + } + + radius1 = private->radius1; + radius2 = private->radius2; + + pixel_angle = atan2 (gimp_vector2_cross_product (&radius1, &radius2).x, + gimp_vector2_inner_product (&radius1, &radius2)); + + radius1.x /= xres; + radius1.y /= yres; + + radius2.x /= xres; + radius2.y /= yres; + + unit_angle = atan2 (gimp_vector2_cross_product (&radius1, &radius2).x, + gimp_vector2_inner_product (&radius1, &radius2)); + + if (shell->dot_for_dot) + private->display_angle = pixel_angle; + else + private->display_angle = unit_angle; + + if (private->n_points == 2) + { + if (! flip && fabs (private->display_angle) > G_PI / 2.0 + EPSILON) + { + gimp_tool_compass_update_angle (compass, orientation, TRUE); + + return; + } + else if (orientation == GIMP_COMPASS_ORIENTATION_AUTO) + { + if (fabs (private->display_angle) <= G_PI / 4.0 + EPSILON) + { + orientation = GIMP_COMPASS_ORIENTATION_HORIZONTAL; + } + else + { + gimp_tool_compass_update_angle (compass, + GIMP_COMPASS_ORIENTATION_VERTICAL, + FALSE); + + return; + } + } + } + + if (fabs (pixel_angle - private->pixel_angle) > EPSILON) + { + private->pixel_angle = pixel_angle; + + g_object_notify (G_OBJECT (compass), "pixel-angle"); + } + + if (fabs (unit_angle - private->unit_angle) > EPSILON) + { + private->unit_angle = unit_angle; + + g_object_notify (G_OBJECT (compass), "unit-angle"); + } + + if (orientation != private->effective_orientation) + { + private->effective_orientation = orientation; + + g_object_notify (G_OBJECT (compass), "effective-orientation"); + } +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_compass_new (GimpDisplayShell *shell, + GimpCompassOrientation orientation, + gint n_points, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_COMPASS, + "shell", shell, + "orientation", orientation, + "n-points", n_points, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); +} diff --git a/app/display/gimptoolcompass.h b/app/display/gimptoolcompass.h new file mode 100644 index 0000000..8e6fdfe --- /dev/null +++ b/app/display/gimptoolcompass.h @@ -0,0 +1,72 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolcompass.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_COMPASS_H__ +#define __GIMP_TOOL_COMPASS_H__ + + +#include "gimptoolwidget.h" + + +#define GIMP_TYPE_TOOL_COMPASS (gimp_tool_compass_get_type ()) +#define GIMP_TOOL_COMPASS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_COMPASS, GimpToolCompass)) +#define GIMP_TOOL_COMPASS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_COMPASS, GimpToolCompassClass)) +#define GIMP_IS_TOOL_COMPASS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_COMPASS)) +#define GIMP_IS_TOOL_COMPASS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_COMPASS)) +#define GIMP_TOOL_COMPASS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_COMPASS, GimpToolCompassClass)) + + +typedef struct _GimpToolCompass GimpToolCompass; +typedef struct _GimpToolCompassPrivate GimpToolCompassPrivate; +typedef struct _GimpToolCompassClass GimpToolCompassClass; + +struct _GimpToolCompass +{ + GimpToolWidget parent_instance; + + GimpToolCompassPrivate *private; +}; + +struct _GimpToolCompassClass +{ + GimpToolWidgetClass parent_class; + + void (* create_guides) (GimpToolCompass *compass, + gint x, + gint y, + gboolean horizontal, + gboolean vertical); +}; + + +GType gimp_tool_compass_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_compass_new (GimpDisplayShell *shell, + GimpCompassOrientation orinetation, + gint n_points, + gint x1, + gint y1, + gint x2, + gint y2, + gint y3, + gint x3); + + +#endif /* __GIMP_TOOL_COMPASS_H__ */ diff --git a/app/display/gimptooldialog.c b/app/display/gimptooldialog.c new file mode 100644 index 0000000..e0bc082 --- /dev/null +++ b/app/display/gimptooldialog.c @@ -0,0 +1,208 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooldialog.c + * Copyright (C) 2003 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimpobject.h" +#include "core/gimptoolinfo.h" + +#include "widgets/gimpdialogfactory.h" + +#include "gimpdisplayshell.h" +#include "gimptooldialog.h" + + +typedef struct _GimpToolDialogPrivate GimpToolDialogPrivate; + +struct _GimpToolDialogPrivate +{ + GimpDisplayShell *shell; +}; + +#define GET_PRIVATE(dialog) ((GimpToolDialogPrivate *) gimp_tool_dialog_get_instance_private ((GimpToolDialog *) (dialog))) + + +static void gimp_tool_dialog_dispose (GObject *object); + +static void gimp_tool_dialog_shell_unmap (GimpDisplayShell *shell, + GimpToolDialog *dialog); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolDialog, gimp_tool_dialog, + GIMP_TYPE_VIEWABLE_DIALOG) + + +static void +gimp_tool_dialog_class_init (GimpToolDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_tool_dialog_dispose; +} + +static void +gimp_tool_dialog_init (GimpToolDialog *dialog) +{ +} + +static void +gimp_tool_dialog_dispose (GObject *object) +{ + GimpToolDialogPrivate *private = GET_PRIVATE (object); + + if (private->shell) + { + g_object_remove_weak_pointer (G_OBJECT (private->shell), + (gpointer) &private->shell); + private->shell = NULL; + } + + G_OBJECT_CLASS (gimp_tool_dialog_parent_class)->dispose (object); +} + + +/** + * gimp_tool_dialog_new: + * @tool_info: a #GimpToolInfo + * @desc: a string to use in the dialog header or %NULL to use the help + * field from #GimpToolInfo + * @...: a %NULL-terminated valist of button parameters as described in + * gtk_dialog_new_with_buttons(). + * + * This function conveniently creates a #GimpViewableDialog using the + * information stored in @tool_info. It also registers the tool with + * the "toplevel" dialog factory. + * + * Return value: a new #GimpViewableDialog + **/ +GtkWidget * +gimp_tool_dialog_new (GimpToolInfo *tool_info, + GdkScreen *screen, + gint monitor, + const gchar *title, + const gchar *description, + const gchar *icon_name, + const gchar *help_id, + ...) +{ + GtkWidget *dialog; + gchar *identifier; + va_list args; + + g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL); + + if (! title) + title = tool_info->label; + + if (! description) + description = tool_info->tooltip; + + if (! help_id) + help_id = tool_info->help_id; + + if (! icon_name) + icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)); + + dialog = g_object_new (GIMP_TYPE_TOOL_DIALOG, + "title", title, + "role", gimp_object_get_name (tool_info), + "description", description, + "icon-name", icon_name, + "help-func", gimp_standard_help_func, + "help-id", help_id, + NULL); + + va_start (args, help_id); + gimp_dialog_add_buttons_valist (GIMP_DIALOG (dialog), args); + va_end (args); + + identifier = g_strconcat (gimp_object_get_name (tool_info), "-dialog", NULL); + + gimp_dialog_factory_add_foreign (gimp_dialog_factory_get_singleton (), + identifier, + dialog, + screen, + monitor); + + g_free (identifier); + + return dialog; +} + +void +gimp_tool_dialog_set_shell (GimpToolDialog *tool_dialog, + GimpDisplayShell *shell) +{ + GimpToolDialogPrivate *private = GET_PRIVATE (tool_dialog); + + g_return_if_fail (GIMP_IS_TOOL_DIALOG (tool_dialog)); + g_return_if_fail (shell == NULL || GIMP_IS_DISPLAY_SHELL (shell)); + + if (shell == private->shell) + return; + + if (private->shell) + { + g_object_remove_weak_pointer (G_OBJECT (private->shell), + (gpointer) &private->shell); + g_signal_handlers_disconnect_by_func (private->shell, + gimp_tool_dialog_shell_unmap, + tool_dialog); + + gtk_window_set_transient_for (GTK_WINDOW (tool_dialog), NULL); + } + + private->shell = shell; + + if (private->shell) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell)); + + gtk_window_set_transient_for (GTK_WINDOW (tool_dialog), + GTK_WINDOW (toplevel)); + + g_signal_connect_object (private->shell, "unmap", + G_CALLBACK (gimp_tool_dialog_shell_unmap), + tool_dialog, 0); + g_object_add_weak_pointer (G_OBJECT (private->shell), + (gpointer) &private->shell); + } +} + + +/* private functions */ + +static void +gimp_tool_dialog_shell_unmap (GimpDisplayShell *shell, + GimpToolDialog *dialog) +{ + /* the dialog being mapped while the shell is being unmapped + * happens when switching images in GimpImageWindow + */ + if (gtk_widget_get_mapped (GTK_WIDGET (dialog))) + g_signal_emit_by_name (dialog, "close"); +} diff --git a/app/display/gimptooldialog.h b/app/display/gimptooldialog.h new file mode 100644 index 0000000..13f437d --- /dev/null +++ b/app/display/gimptooldialog.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooldialog.h + * Copyright (C) 2003 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_DIALOG_H__ +#define __GIMP_TOOL_DIALOG_H__ + +#include "widgets/gimpviewabledialog.h" + + +#define GIMP_TYPE_TOOL_DIALOG (gimp_tool_dialog_get_type ()) +#define GIMP_TOOL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_DIALOG, GimpToolDialog)) +#define GIMP_TOOL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_DIALOG, GimpToolDialogClass)) +#define GIMP_IS_TOOL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_DIALOG)) +#define GIMP_IS_TOOL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_DIALOG)) +#define GIMP_TOOL_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_DIALOG, GimpToolDialogClass)) + + +typedef struct _GimpViewableDialogClass GimpToolDialogClass; + +struct _GimpToolDialog +{ + GimpViewableDialog parent_instance; +}; + + +GType gimp_tool_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tool_dialog_new (GimpToolInfo *tool_info, + GdkScreen *screen, + gint monitor, + const gchar *title, + const gchar *description, + const gchar *icon_name, + const gchar *help_id, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_tool_dialog_set_shell (GimpToolDialog *tool_dialog, + GimpDisplayShell *shell); + + +#endif /* __GIMP_TOOL_DIALOG_H__ */ diff --git a/app/display/gimptoolfocus.c b/app/display/gimptoolfocus.c new file mode 100644 index 0000000..a607f06 --- /dev/null +++ b/app/display/gimptoolfocus.c @@ -0,0 +1,1209 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolfocus.c + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvasgroup.h" +#include "gimpcanvashandle.h" +#include "gimpcanvaslimit.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-transform.h" +#include "gimpdisplayshell-utils.h" +#include "gimptoolfocus.h" + +#include "gimp-intl.h" + + +#define HANDLE_SIZE 12.0 +#define SNAP_DISTANCE 12.0 + +#define EPSILON 1e-6 + + +enum +{ + PROP_0, + PROP_TYPE, + PROP_X, + PROP_Y, + PROP_RADIUS, + PROP_ASPECT_RATIO, + PROP_ANGLE, + PROP_INNER_LIMIT, + PROP_MIDPOINT +}; + +enum +{ + LIMIT_OUTER, + LIMIT_INNER, + LIMIT_MIDDLE, + + N_LIMITS +}; + + +typedef enum +{ + HOVER_NONE, + HOVER_LIMIT, + HOVER_HANDLE, + HOVER_MOVE, + HOVER_ROTATE +} Hover; + + +typedef struct +{ + GimpCanvasItem *item; + + GtkOrientation orientation; + GimpVector2 dir; +} GimpToolFocusHandle; + +typedef struct +{ + GimpCanvasGroup *group; + GimpCanvasItem *item; + + gint n_handles; + GimpToolFocusHandle handles[4]; +} GimpToolFocusLimit; + +struct _GimpToolFocusPrivate +{ + GimpLimitType type; + + gdouble x; + gdouble y; + gdouble radius; + gdouble aspect_ratio; + gdouble angle; + + gdouble inner_limit; + gdouble midpoint; + + GimpToolFocusLimit limits[N_LIMITS]; + + Hover hover; + gint hover_limit; + gint hover_handle; + GimpCanvasItem *hover_item; + + GimpCanvasItem *last_hover_item; + + gdouble saved_x; + gdouble saved_y; + gdouble saved_radius; + gdouble saved_aspect_ratio; + gdouble saved_angle; + + gdouble saved_inner_limit; + gdouble saved_midpoint; + + gdouble orig_x; + gdouble orig_y; +}; + + +/* local function prototypes */ + +static void gimp_tool_focus_constructed (GObject *object); +static void gimp_tool_focus_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_focus_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_focus_changed (GimpToolWidget *widget); +static gint gimp_tool_focus_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_focus_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_focus_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_focus_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_focus_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_focus_leave_notify (GimpToolWidget *widget); +static void gimp_tool_focus_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static void gimp_tool_focus_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_focus_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static void gimp_tool_focus_update_hover (GimpToolFocus *focus, + const GimpCoords *coords, + gboolean proximity); + +static void gimp_tool_focus_update_highlight (GimpToolFocus *focus); +static void gimp_tool_focus_update_status (GimpToolFocus *focus, + GdkModifierType state); + +static void gimp_tool_focus_save (GimpToolFocus *focus); +static void gimp_tool_focus_restore (GimpToolFocus *focus); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolFocus, gimp_tool_focus, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_focus_parent_class + + +/* private functions */ + +static void +gimp_tool_focus_class_init (GimpToolFocusClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_focus_constructed; + object_class->set_property = gimp_tool_focus_set_property; + object_class->get_property = gimp_tool_focus_get_property; + + widget_class->changed = gimp_tool_focus_changed; + widget_class->button_press = gimp_tool_focus_button_press; + widget_class->button_release = gimp_tool_focus_button_release; + widget_class->motion = gimp_tool_focus_motion; + widget_class->hit = gimp_tool_focus_hit; + widget_class->hover = gimp_tool_focus_hover; + widget_class->leave_notify = gimp_tool_focus_leave_notify; + widget_class->motion_modifier = gimp_tool_focus_motion_modifier; + widget_class->hover_modifier = gimp_tool_focus_hover_modifier; + widget_class->get_cursor = gimp_tool_focus_get_cursor; + widget_class->update_on_scale = TRUE; + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", NULL, NULL, + GIMP_TYPE_LIMIT_TYPE, + GIMP_LIMIT_CIRCLE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RADIUS, + g_param_spec_double ("radius", NULL, NULL, + 0.0, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ASPECT_RATIO, + g_param_spec_double ("aspect-ratio", NULL, NULL, + -1.0, + +1.0, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ANGLE, + g_param_spec_double ("angle", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_INNER_LIMIT, + g_param_spec_double ("inner-limit", NULL, NULL, + 0.0, + 1.0, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_MIDPOINT, + g_param_spec_double ("midpoint", NULL, NULL, + 0.0, + 1.0, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_focus_init (GimpToolFocus *focus) +{ + GimpToolFocusPrivate *priv; + + priv = gimp_tool_focus_get_instance_private (focus); + + focus->priv = priv; + + priv->hover = HOVER_NONE; +} + +static void +gimp_tool_focus_constructed (GObject *object) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolFocusPrivate *priv = focus->priv; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + for (i = N_LIMITS - 1; i >= 0; i--) + { + priv->limits[i].group = gimp_tool_widget_add_group (widget); + + gimp_tool_widget_push_group (widget, priv->limits[i].group); + + priv->limits[i].item = gimp_tool_widget_add_limit ( + widget, + GIMP_LIMIT_CIRCLE, + 0.0, 0.0, + 0.0, + 1.0, + 0.0, + /* dashed = */ i == LIMIT_MIDDLE); + + if (i == LIMIT_OUTER || i == LIMIT_INNER) + { + gint j; + + priv->limits[i].n_handles = 4; + + for (j = priv->limits[i].n_handles - 1; j >= 0; j--) + { + priv->limits[i].handles[j].item = gimp_tool_widget_add_handle ( + widget, + GIMP_HANDLE_FILLED_CIRCLE, + 0.0, 0.0, HANDLE_SIZE, HANDLE_SIZE, + GIMP_HANDLE_ANCHOR_CENTER); + + priv->limits[i].handles[j].orientation = + j % 2 == 0 ? GTK_ORIENTATION_HORIZONTAL : + GTK_ORIENTATION_VERTICAL; + + priv->limits[i].handles[j].dir.x = cos (j * G_PI / 2.0); + priv->limits[i].handles[j].dir.y = sin (j * G_PI / 2.0); + } + } + + gimp_tool_widget_pop_group (widget); + } + + gimp_tool_focus_changed (widget); +} + +static void +gimp_tool_focus_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (object); + GimpToolFocusPrivate *priv = focus->priv; + + switch (property_id) + { + case PROP_TYPE: + priv->type = g_value_get_enum (value); + break; + + case PROP_X: + priv->x = g_value_get_double (value); + break; + + case PROP_Y: + priv->y = g_value_get_double (value); + break; + + case PROP_RADIUS: + priv->radius = g_value_get_double (value); + break; + + case PROP_ASPECT_RATIO: + priv->aspect_ratio = g_value_get_double (value); + break; + + case PROP_ANGLE: + priv->angle = g_value_get_double (value); + break; + + case PROP_INNER_LIMIT: + priv->inner_limit = g_value_get_double (value); + break; + + case PROP_MIDPOINT: + priv->midpoint = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_focus_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (object); + GimpToolFocusPrivate *priv = focus->priv; + + switch (property_id) + { + case PROP_TYPE: + g_value_set_enum (value, priv->type); + break; + + case PROP_X: + g_value_set_double (value, priv->x); + break; + + case PROP_Y: + g_value_set_double (value, priv->y); + break; + + case PROP_RADIUS: + g_value_set_double (value, priv->radius); + break; + + case PROP_ASPECT_RATIO: + g_value_set_double (value, priv->aspect_ratio); + break; + + case PROP_ANGLE: + g_value_set_double (value, priv->angle); + break; + + case PROP_INNER_LIMIT: + g_value_set_double (value, priv->inner_limit); + break; + + case PROP_MIDPOINT: + g_value_set_double (value, priv->midpoint); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_focus_changed (GimpToolWidget *widget) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + GimpToolFocusPrivate *priv = focus->priv; + gint i; + + for (i = 0; i < N_LIMITS; i++) + { + gimp_canvas_item_begin_change (priv->limits[i].item); + + g_object_set (priv->limits[i].item, + "type", priv->type, + "x", priv->x, + "y", priv->y, + "aspect-ratio", priv->aspect_ratio, + "angle", priv->angle, + NULL); + } + + g_object_set (priv->limits[LIMIT_OUTER].item, + "radius", priv->radius, + NULL); + + g_object_set (priv->limits[LIMIT_INNER].item, + "radius", priv->radius * priv->inner_limit, + NULL); + + g_object_set (priv->limits[LIMIT_MIDDLE].item, + "radius", priv->radius * (priv->inner_limit + + (1.0 - priv->inner_limit) * + priv->midpoint), + NULL); + + for (i = 0; i < N_LIMITS; i++) + { + gdouble rx, ry; + gdouble max_r = 0.0; + gint j; + + gimp_canvas_limit_get_radii (GIMP_CANVAS_LIMIT (priv->limits[i].item), + &rx, &ry); + + for (j = 0; j < priv->limits[i].n_handles; j++) + { + GimpVector2 p = priv->limits[i].handles[j].dir; + gdouble r; + + p.x *= rx; + p.y *= ry; + + gimp_vector2_rotate (&p, -priv->angle); + + p.x += priv->x; + p.y += priv->y; + + gimp_canvas_handle_set_position (priv->limits[i].handles[j].item, + p.x, p.y); + + r = gimp_canvas_item_transform_distance ( + priv->limits[i].handles[j].item, + priv->x, priv->y, + p.x, p.y); + + max_r = MAX (max_r, r); + } + + for (j = 0; j < priv->limits[i].n_handles; j++) + { + gimp_canvas_item_set_visible (priv->limits[i].handles[j].item, + priv->type != GIMP_LIMIT_HORIZONTAL && + priv->type != GIMP_LIMIT_VERTICAL && + max_r >= 1.5 * HANDLE_SIZE); + } + + gimp_canvas_item_end_change (priv->limits[i].item); + } +} + +static gint +gimp_tool_focus_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + GimpToolFocusPrivate *priv = focus->priv; + + if (press_type == GIMP_BUTTON_PRESS_NORMAL) + { + gimp_tool_focus_save (focus); + + priv->orig_x = coords->x; + priv->orig_y = coords->y; + + return TRUE; + } + + return FALSE; +} + +static void +gimp_tool_focus_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + + if (release_type == GIMP_BUTTON_RELEASE_CANCEL) + gimp_tool_focus_restore (focus); +} + +static void +gimp_tool_focus_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + GimpToolFocusPrivate *priv = focus->priv; + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + gboolean extend; + gboolean constrain; + + extend = state & gimp_get_extend_selection_mask (); + constrain = state & gimp_get_constrain_behavior_mask (); + + switch (priv->hover) + { + case HOVER_NONE: + break; + + case HOVER_LIMIT: + { + GimpCanvasItem *limit = priv->limits[priv->hover_limit].item; + gdouble radius; + gdouble outer_radius; + gdouble inner_radius; + gdouble x, y; + gdouble cx, cy; + + x = coords->x; + y = coords->y; + + gimp_canvas_limit_center_point (GIMP_CANVAS_LIMIT (limit), + x, y, + &cx, &cy); + + if (gimp_canvas_item_transform_distance (limit, + x, y, + cx, cy) <= SNAP_DISTANCE) + { + x = cx; + y = cy; + } + + if (fabs (fabs (priv->aspect_ratio) - 1.0) <= EPSILON) + { + if (priv->radius <= EPSILON) + { + g_object_set (focus, + "aspect-ratio", 0.0, + NULL); + } + else + { + break; + } + } + + radius = gimp_canvas_limit_boundary_radius (GIMP_CANVAS_LIMIT (limit), + x, y); + + outer_radius = priv->radius; + inner_radius = priv->radius * priv->inner_limit; + + switch (priv->hover_limit) + { + case LIMIT_OUTER: + { + outer_radius = radius; + + if (extend) + inner_radius = priv->inner_limit * radius; + else + outer_radius = MAX (outer_radius, inner_radius); + } + break; + + case LIMIT_INNER: + { + inner_radius = radius; + + if (extend) + { + if (priv->inner_limit > EPSILON) + outer_radius = inner_radius / priv->inner_limit; + else + inner_radius = 0.0; + } + else + { + inner_radius = MIN (inner_radius, outer_radius); + } + } + break; + + case LIMIT_MIDDLE: + { + if (extend) + { + if (priv->inner_limit > EPSILON || priv->midpoint > EPSILON) + { + outer_radius = radius / (priv->inner_limit + + (1.0 - priv->inner_limit) * + priv->midpoint); + inner_radius = priv->inner_limit * outer_radius; + } + else + { + radius = 0.0; + } + } + else + { + radius = CLAMP (radius, inner_radius, outer_radius); + } + + if (fabs (outer_radius - inner_radius) > EPSILON) + { + g_object_set (focus, + "midpoint", MAX ((radius - inner_radius) / + (outer_radius - inner_radius), + 0.0), + NULL); + } + } + break; + } + + g_object_set (focus, + "radius", outer_radius, + NULL); + + if (outer_radius > EPSILON) + { + g_object_set (focus, + "inner-limit", inner_radius / outer_radius, + NULL); + } + } + break; + + case HOVER_HANDLE: + { + GimpToolFocusHandle *handle; + GimpVector2 e; + GimpVector2 s; + GimpVector2 p; + gdouble rx, ry; + gdouble r; + + handle = &priv->limits[priv->hover_limit].handles[priv->hover_handle]; + + e = handle->dir; + + gimp_vector2_rotate (&e, -priv->angle); + + s = e; + + gimp_canvas_limit_get_radii ( + GIMP_CANVAS_LIMIT (priv->limits[priv->hover_limit].item), + &rx, &ry); + + if (handle->orientation == GTK_ORIENTATION_HORIZONTAL) + gimp_vector2_mul (&s, ry); + else + gimp_vector2_mul (&s, rx); + + p.x = coords->x - priv->x; + p.y = coords->y - priv->y; + + r = gimp_vector2_inner_product (&p, &e); + r = MAX (r, 0.0); + + p = e; + + gimp_vector2_mul (&p, r); + + if (extend) + { + if (handle->orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (rx <= EPSILON && ry > EPSILON) + break; + + ry = r * (1.0 - priv->aspect_ratio); + } + else + { + if (ry <= EPSILON && rx > EPSILON) + break; + + rx = r * (1.0 + priv->aspect_ratio); + } + } + else + { + if (gimp_canvas_item_transform_distance ( + priv->limits[priv->hover_limit].item, + s.x, s.y, + p.x, p.y) <= SNAP_DISTANCE * 0.75) + { + if (handle->orientation == GTK_ORIENTATION_HORIZONTAL) + r = ry; + else + r = rx; + } + } + + if (handle->orientation == GTK_ORIENTATION_HORIZONTAL) + rx = r; + else + ry = r; + + r = MAX (rx, ry); + + if (priv->hover_limit == LIMIT_INNER) + r /= priv->inner_limit; + + g_object_set (focus, + "radius", r, + NULL); + + if (! extend) + { + gdouble aspect_ratio; + + if (fabs (rx - ry) <= EPSILON) + aspect_ratio = 0.0; + else if (rx > ry) + aspect_ratio = 1.0 - ry / rx; + else + aspect_ratio = rx / ry - 1.0; + + g_object_set (focus, + "aspect-ratio", aspect_ratio, + NULL); + } + } + break; + + case HOVER_MOVE: + g_object_set (focus, + "x", priv->saved_x + (coords->x - priv->orig_x), + "y", priv->saved_y + (coords->y - priv->orig_y), + NULL); + break; + + case HOVER_ROTATE: + { + gdouble angle; + gdouble orig_angle; + + angle = atan2 (coords->y - priv->y, coords->x - priv->x); + orig_angle = atan2 (priv->orig_y - priv->y, priv->orig_x - priv->x); + + angle = priv->saved_angle + (angle - orig_angle); + + if (constrain) + angle = gimp_display_shell_constrain_angle (shell, angle, 12); + + g_object_set (focus, + "angle", angle, + NULL); + } + break; + } +} + +static GimpHit +gimp_tool_focus_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + GimpToolFocusPrivate *priv = focus->priv; + + gimp_tool_focus_update_hover (focus, coords, proximity); + + switch (priv->hover) + { + case HOVER_NONE: + return GIMP_HIT_NONE; + + case HOVER_LIMIT: + case HOVER_HANDLE: + return GIMP_HIT_DIRECT; + + case HOVER_MOVE: + case HOVER_ROTATE: + return GIMP_HIT_INDIRECT; + } + + g_return_val_if_reached (GIMP_HIT_NONE); +} + +static void +gimp_tool_focus_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + + gimp_tool_focus_update_hover (focus, coords, proximity); + + gimp_tool_focus_update_highlight (focus); + gimp_tool_focus_update_status (focus, state); +} + +static void +gimp_tool_focus_leave_notify (GimpToolWidget *widget) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + + gimp_tool_focus_update_hover (focus, NULL, FALSE); + + gimp_tool_focus_update_highlight (focus); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static void +gimp_tool_focus_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + + gimp_tool_focus_update_status (focus, state); +} + +static void +gimp_tool_focus_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + + gimp_tool_focus_update_status (focus, state); +} + +static gboolean +gimp_tool_focus_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget); + GimpToolFocusPrivate *priv = focus->priv; + + switch (priv->hover) + { + case HOVER_NONE: + return FALSE; + + case HOVER_LIMIT: + case HOVER_HANDLE: + *modifier = GIMP_CURSOR_MODIFIER_RESIZE; + return TRUE; + + case HOVER_MOVE: + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + return TRUE; + + case HOVER_ROTATE: + *modifier = GIMP_CURSOR_MODIFIER_ROTATE; + return TRUE; + } + + g_return_val_if_reached (FALSE); +} + +static void +gimp_tool_focus_update_hover (GimpToolFocus *focus, + const GimpCoords *coords, + gboolean proximity) +{ + GimpToolFocusPrivate *priv = focus->priv; + gdouble min_handle_dist = HANDLE_SIZE; + gint i; + + priv->hover = HOVER_NONE; + priv->hover_item = NULL; + + if (! proximity) + return; + + for (i = 0; i < N_LIMITS; i++) + { + gint j; + + for (j = 0; j < priv->limits[i].n_handles; j++) + { + GimpCanvasItem *handle = priv->limits[i].handles[j].item; + + if (gimp_canvas_item_get_visible (handle)) + { + gdouble x, y; + gdouble dist; + + g_object_get (handle, + "x", &x, + "y", &y, + NULL); + + dist = gimp_canvas_item_transform_distance (handle, + x, y, + coords->x, coords->y); + + if (dist < min_handle_dist) + { + min_handle_dist = dist; + + priv->hover = HOVER_HANDLE; + priv->hover_limit = i; + priv->hover_handle = j; + priv->hover_item = handle; + } + } + } + } + + if (priv->hover != HOVER_NONE) + return; + + if ( gimp_canvas_limit_is_inside ( + GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item), + coords->x, coords->y) && + ! gimp_canvas_limit_is_inside ( + GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item), + coords->x, coords->y)) + { + if (gimp_canvas_item_hit (priv->limits[LIMIT_MIDDLE].item, + coords->x, coords->y)) + { + priv->hover = HOVER_LIMIT; + priv->hover_limit = LIMIT_MIDDLE; + priv->hover_item = priv->limits[LIMIT_MIDDLE].item; + + return; + } + } + + if (! gimp_canvas_limit_is_inside ( + GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item), + coords->x, coords->y)) + { + if (gimp_canvas_item_hit (priv->limits[LIMIT_OUTER].item, + coords->x, coords->y)) + { + priv->hover = HOVER_LIMIT; + priv->hover_limit = LIMIT_OUTER; + priv->hover_item = priv->limits[LIMIT_OUTER].item; + + return; + } + } + + if (gimp_canvas_item_hit (priv->limits[LIMIT_INNER].item, + coords->x, coords->y)) + { + priv->hover = HOVER_LIMIT; + priv->hover_limit = LIMIT_INNER; + priv->hover_item = priv->limits[LIMIT_INNER].item; + + return; + } + + if (gimp_canvas_limit_is_inside ( + GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item), + coords->x, coords->y)) + { + priv->hover = HOVER_MOVE; + } + else + { + priv->hover = HOVER_ROTATE; + } +} + +static void +gimp_tool_focus_update_highlight (GimpToolFocus *focus) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (focus); + GimpToolFocusPrivate *priv = focus->priv; + gint i; + + if (priv->hover_item == priv->last_hover_item) + return; + + if (priv->last_hover_item) + gimp_canvas_item_set_highlight (priv->last_hover_item, FALSE); + + #define RAISE_ITEM(item) \ + G_STMT_START \ + { \ + g_object_ref (item); \ + \ + gimp_tool_widget_remove_item (widget, item); \ + gimp_tool_widget_add_item (widget, item); \ + \ + g_object_unref (item); \ + } \ + G_STMT_END + + for (i = N_LIMITS - 1; i >= 0; i--) + RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[i].group)); + + if (priv->hover_item) + { + gimp_canvas_item_set_highlight (priv->hover_item, TRUE); + + RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[priv->hover_limit].group)); + } + + #undef RAISE_ITEM + + priv->last_hover_item = priv->hover_item; +} + +static void +gimp_tool_focus_update_status (GimpToolFocus *focus, + GdkModifierType state) +{ + GimpToolFocusPrivate *priv = focus->priv; + GdkModifierType state_mask = 0; + const gchar *message = NULL; + const gchar *extend_selection_format = NULL; + const gchar *toggle_behavior_format = NULL; + gchar *status; + + switch (priv->hover) + { + case HOVER_NONE: + break; + + case HOVER_LIMIT: + if (! (state & gimp_get_extend_selection_mask ())) + { + if (priv->hover_limit == LIMIT_MIDDLE) + message = _("Click-Drag to change the midpoint"); + else + message = _("Click-Drag to resize the limit"); + + extend_selection_format = _("%s to resize the focus"); + state_mask |= gimp_get_extend_selection_mask (); + } + else + { + message = _("Click-Drag to resize the focus"); + } + break; + + case HOVER_HANDLE: + if (! (state & gimp_get_extend_selection_mask ())) + { + message = _("Click-Drag to change the aspect ratio"); + extend_selection_format = _("%s to resize the focus"); + state_mask |= gimp_get_extend_selection_mask (); + } + else + { + message = _("Click-Drag to resize the focus"); + } + break; + + case HOVER_MOVE: + message = _("Click-Drag to move the focus"); + break; + + case HOVER_ROTATE: + message = _("Click-Drag to rotate the focus"); + toggle_behavior_format = _("%s for constrained angles"); + state_mask |= gimp_get_constrain_behavior_mask (); + break; + } + + status = gimp_suggest_modifiers (message, + ~state & state_mask, + extend_selection_format, + toggle_behavior_format, + NULL); + + gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (focus), status); + + g_free (status); +} + +static void +gimp_tool_focus_save (GimpToolFocus *focus) +{ + GimpToolFocusPrivate *priv = focus->priv; + + priv->saved_x = priv->x; + priv->saved_y = priv->y; + priv->saved_radius = priv->radius; + priv->saved_aspect_ratio = priv->aspect_ratio; + priv->saved_angle = priv->angle; + + priv->saved_inner_limit = priv->inner_limit; + priv->saved_midpoint = priv->midpoint; +} + +static void +gimp_tool_focus_restore (GimpToolFocus *focus) +{ + GimpToolFocusPrivate *priv = focus->priv; + + g_object_set (focus, + "x", priv->saved_x, + "y", priv->saved_y, + "radius", priv->saved_radius, + "aspect-ratio", priv->saved_aspect_ratio, + "angle", priv->saved_angle, + + "inner_limit", priv->saved_inner_limit, + "midpoint", priv->saved_midpoint, + + NULL); +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_focus_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_FOCUS, + "shell", shell, + NULL); +} diff --git a/app/display/gimptoolfocus.h b/app/display/gimptoolfocus.h new file mode 100644 index 0000000..e2fc5bd --- /dev/null +++ b/app/display/gimptoolfocus.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolfocus.h + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_FOCUS_H__ +#define __GIMP_TOOL_FOCUS_H__ + + +#include "gimptoolwidget.h" + + +#define GIMP_TYPE_TOOL_FOCUS (gimp_tool_focus_get_type ()) +#define GIMP_TOOL_FOCUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_FOCUS, GimpToolFocus)) +#define GIMP_TOOL_FOCUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_FOCUS, GimpToolFocusClass)) +#define GIMP_IS_TOOL_FOCUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_FOCUS)) +#define GIMP_IS_TOOL_FOCUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_FOCUS)) +#define GIMP_TOOL_FOCUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_FOCUS, GimpToolFocusClass)) + + +typedef struct _GimpToolFocus GimpToolFocus; +typedef struct _GimpToolFocusPrivate GimpToolFocusPrivate; +typedef struct _GimpToolFocusClass GimpToolFocusClass; + +struct _GimpToolFocus +{ + GimpToolWidget parent_instance; + + GimpToolFocusPrivate *priv; +}; + +struct _GimpToolFocusClass +{ + GimpToolWidgetClass parent_class; +}; + + +GType gimp_tool_focus_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_focus_new (GimpDisplayShell *shell); + + +#endif /* __GIMP_TOOL_FOCUS_H__ */ diff --git a/app/display/gimptoolgui.c b/app/display/gimptoolgui.c new file mode 100644 index 0000000..28c40bf --- /dev/null +++ b/app/display/gimptoolgui.c @@ -0,0 +1,1037 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolgui.c + * Copyright (C) 2013 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpwidgets/gimpwidgets.h" + +#include "display-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimptoolinfo.h" + +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpoverlaybox.h" +#include "widgets/gimpoverlaydialog.h" +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplayshell.h" +#include "gimptooldialog.h" +#include "gimptoolgui.h" + + +enum +{ + RESPONSE, + LAST_SIGNAL +}; + + +typedef struct _ResponseEntry ResponseEntry; + +struct _ResponseEntry +{ + gint response_id; + gchar *button_text; + gint alternative_position; + gboolean sensitive; +}; + +typedef struct _GimpToolGuiPrivate GimpToolGuiPrivate; + +struct _GimpToolGuiPrivate +{ + GimpToolInfo *tool_info; + gchar *title; + gchar *description; + gchar *icon_name; + gchar *help_id; + GList *response_entries; + gint default_response; + gboolean focus_on_map; + + gboolean overlay; + gboolean auto_overlay; + + GimpDisplayShell *shell; + GimpViewable *viewable; + + GtkWidget *dialog; + GtkWidget *vbox; +}; + +#define GET_PRIVATE(gui) ((GimpToolGuiPrivate *) gimp_tool_gui_get_instance_private ((GimpToolGui *) (gui))) + + +static void gimp_tool_gui_dispose (GObject *object); +static void gimp_tool_gui_finalize (GObject *object); + +static void gimp_tool_gui_create_dialog (GimpToolGui *gui, + GdkScreen *screen, + gint monitor); +static void gimp_tool_gui_add_dialog_button (GimpToolGui *gui, + ResponseEntry *entry); +static void gimp_tool_gui_update_buttons (GimpToolGui *gui); +static void gimp_tool_gui_update_shell (GimpToolGui *gui); +static void gimp_tool_gui_update_viewable (GimpToolGui *gui); + +static void gimp_tool_gui_dialog_response (GtkWidget *dialog, + gint response_id, + GimpToolGui *gui); +static void gimp_tool_gui_canvas_resized (GtkWidget *canvas, + GtkAllocation *allocation, + GimpToolGui *gui); + +static ResponseEntry * response_entry_new (gint response_id, + const gchar *button_text); +static void response_entry_free (ResponseEntry *entry); +static ResponseEntry * response_entry_find (GList *entries, + gint response_id); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGui, gimp_tool_gui, GIMP_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0, }; + +#define parent_class gimp_tool_gui_parent_class + + +static void +gimp_tool_gui_class_init (GimpToolGuiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_tool_gui_dispose; + object_class->finalize = gimp_tool_gui_finalize; + + signals[RESPONSE] = + g_signal_new ("response", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpToolGuiClass, response), + NULL, NULL, + gimp_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); +} + +static void +gimp_tool_gui_init (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + + private->default_response = -1; + private->focus_on_map = TRUE; + + private->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + g_object_ref_sink (private->vbox); +} + +static void +gimp_tool_gui_dispose (GObject *object) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->tool_info); + + if (private->shell) + gimp_tool_gui_set_shell (GIMP_TOOL_GUI (object), NULL); + + if (private->viewable) + gimp_tool_gui_set_viewable (GIMP_TOOL_GUI (object), NULL); + + g_clear_object (&private->vbox); + + if (private->dialog) + { + if (gtk_widget_get_visible (private->dialog)) + gimp_tool_gui_hide (GIMP_TOOL_GUI (object)); + + if (private->overlay) + g_object_unref (private->dialog); + else + gtk_widget_destroy (private->dialog); + + private->dialog = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_tool_gui_finalize (GObject *object) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->title, g_free); + g_clear_pointer (&private->description, g_free); + g_clear_pointer (&private->icon_name, g_free); + g_clear_pointer (&private->help_id, g_free); + + if (private->response_entries) + { + g_list_free_full (private->response_entries, + (GDestroyNotify) response_entry_free); + private->response_entries = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/** + * gimp_tool_gui_new: + * @tool_info: a #GimpToolInfo + * @description: a string to use in the gui header or %NULL to use the help + * field from #GimpToolInfo + * @...: a %NULL-terminated valist of button parameters as described in + * gtk_gui_new_with_buttons(). + * + * This function creates a #GimpToolGui using the information stored + * in @tool_info. + * + * Return value: a new #GimpToolGui + **/ +GimpToolGui * +gimp_tool_gui_new (GimpToolInfo *tool_info, + const gchar *title, + const gchar *description, + const gchar *icon_name, + const gchar *help_id, + GdkScreen *screen, + gint monitor, + gboolean overlay, + ...) +{ + GimpToolGui *gui; + GimpToolGuiPrivate *private; + va_list args; + + g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL); + + gui = g_object_new (GIMP_TYPE_TOOL_GUI, NULL); + + private = GET_PRIVATE (gui); + + if (! title) + title = tool_info->label; + + if (! description) + description = tool_info->label; + + if (! icon_name) + icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)); + + if (! help_id) + help_id = tool_info->help_id; + + private->tool_info = g_object_ref (tool_info); + private->title = g_strdup (title); + private->description = g_strdup (description); + private->icon_name = g_strdup (icon_name); + private->help_id = g_strdup (help_id); + private->overlay = overlay; + + va_start (args, overlay); + + gimp_tool_gui_add_buttons_valist (gui, args); + + va_end (args); + + gimp_tool_gui_create_dialog (gui, screen, monitor); + + return gui; +} + +void +gimp_tool_gui_set_title (GimpToolGui *gui, + const gchar *title) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (title == private->title) + return; + + g_free (private->title); + private->title = g_strdup (title); + + if (! title) + title = private->tool_info->label; + + g_object_set (private->dialog, "title", title, NULL); +} + +void +gimp_tool_gui_set_description (GimpToolGui *gui, + const gchar *description) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (description == private->description) + return; + + g_free (private->description); + private->description = g_strdup (description); + + if (! description) + description = private->tool_info->tooltip; + + if (private->overlay) + { + /* TODO */ + } + else + { + g_object_set (private->dialog, "description", description, NULL); + } +} + +void +gimp_tool_gui_set_icon_name (GimpToolGui *gui, + const gchar *icon_name) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (icon_name == private->icon_name) + return; + + g_free (private->icon_name); + private->icon_name = g_strdup (icon_name); + + if (! icon_name) + icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (private->tool_info)); + + g_object_set (private->dialog, "icon-name", icon_name, NULL); +} + +void +gimp_tool_gui_set_help_id (GimpToolGui *gui, + const gchar *help_id) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (help_id == private->help_id) + return; + + g_free (private->help_id); + private->help_id = g_strdup (help_id); + + if (! help_id) + help_id = private->tool_info->help_id; + + if (private->overlay) + { + /* TODO */ + } + else + { + g_object_set (private->dialog, "help-id", help_id, NULL); + } +} + +void +gimp_tool_gui_set_shell (GimpToolGui *gui, + GimpDisplayShell *shell) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + g_return_if_fail (shell == NULL || GIMP_IS_DISPLAY_SHELL (shell)); + + private = GET_PRIVATE (gui); + + if (shell == private->shell) + return; + + if (private->shell) + { + g_object_remove_weak_pointer (G_OBJECT (private->shell), + (gpointer) &private->shell); + g_signal_handlers_disconnect_by_func (private->shell->canvas, + gimp_tool_gui_canvas_resized, + gui); + } + + private->shell = shell; + + if (private->shell) + { + g_signal_connect (private->shell->canvas, "size-allocate", + G_CALLBACK (gimp_tool_gui_canvas_resized), + gui); + g_object_add_weak_pointer (G_OBJECT (private->shell), + (gpointer) &private->shell); + } + + gimp_tool_gui_update_shell (gui); +} + +void +gimp_tool_gui_set_viewable (GimpToolGui *gui, + GimpViewable *viewable) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable)); + + private = GET_PRIVATE (gui); + + if (private->viewable == viewable) + return; + + if (private->viewable) + g_object_remove_weak_pointer (G_OBJECT (private->viewable), + (gpointer) &private->viewable); + + private->viewable = viewable; + + if (private->viewable) + g_object_add_weak_pointer (G_OBJECT (private->viewable), + (gpointer) &private->viewable); + + gimp_tool_gui_update_viewable (gui); +} + +GtkWidget * +gimp_tool_gui_get_dialog (GimpToolGui *gui) +{ + g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), NULL); + + return GET_PRIVATE (gui)->dialog; +} + +GtkWidget * +gimp_tool_gui_get_vbox (GimpToolGui *gui) +{ + g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), NULL); + + return GET_PRIVATE (gui)->vbox; +} + +gboolean +gimp_tool_gui_get_visible (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private; + + g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE); + + private = GET_PRIVATE (gui); + + if (private->overlay) + return gtk_widget_get_parent (private->dialog) != NULL; + else + return gtk_widget_get_visible (private->dialog); +} + +void +gimp_tool_gui_show (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + g_return_if_fail (private->shell != NULL); + + if (private->overlay) + { + if (! gtk_widget_get_parent (private->dialog)) + { + gimp_overlay_box_add_child (GIMP_OVERLAY_BOX (private->shell->canvas), + private->dialog, 1.0, 0.0); + gtk_widget_show (private->dialog); + } + } + else + { + if (gtk_widget_get_visible (private->dialog)) + gdk_window_show (gtk_widget_get_window (private->dialog)); + else + gtk_widget_show (private->dialog); + } +} + +void +gimp_tool_gui_hide (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (private->overlay) + { + if (gtk_widget_get_parent (private->dialog)) + { + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (private->dialog)), + private->dialog); + gtk_widget_hide (private->dialog); + } + } + else + { + if (gimp_dialog_factory_from_widget (private->dialog, NULL)) + { + gimp_dialog_factory_hide_dialog (private->dialog); + } + else + { + gtk_widget_hide (private->dialog); + } + } +} + +void +gimp_tool_gui_set_overlay (GimpToolGui *gui, + GdkScreen *screen, + gint monitor, + gboolean overlay) +{ + GimpToolGuiPrivate *private; + gboolean visible; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (private->overlay == overlay) + return; + + if (! private->dialog) + { + private->overlay = overlay; + return; + } + + visible = gtk_widget_get_visible (private->dialog); + + if (visible) + gimp_tool_gui_hide (gui); + + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (private->vbox)), + private->vbox); + + if (private->overlay) + g_object_unref (private->dialog); + else + gtk_widget_destroy (private->dialog); + + private->overlay = overlay; + + gimp_tool_gui_create_dialog (gui, screen, monitor); + + if (visible) + gimp_tool_gui_show (gui); +} + +gboolean +gimp_tool_gui_get_overlay (GimpToolGui *gui) +{ + g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE); + + return GET_PRIVATE (gui)->overlay; +} + +void +gimp_tool_gui_set_auto_overlay (GimpToolGui *gui, + gboolean auto_overlay) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (private->auto_overlay != auto_overlay) + { + private->auto_overlay = auto_overlay; + + if (private->shell) + gimp_tool_gui_canvas_resized (private->shell->canvas, NULL, gui); + } +} + +gboolean +gimp_tool_gui_get_auto_overlay (GimpToolGui *gui) +{ + g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE); + + return GET_PRIVATE (gui)->auto_overlay; +} + +void +gimp_tool_gui_set_focus_on_map (GimpToolGui *gui, + gboolean focus_on_map) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + if (private->focus_on_map == focus_on_map) + return; + + private->focus_on_map = focus_on_map ? TRUE : FALSE; + + if (! private->overlay) + { + gtk_window_set_focus_on_map (GTK_WINDOW (private->dialog), + private->focus_on_map); + } +} + +gboolean +gimp_tool_gui_get_focus_on_map (GimpToolGui *gui) +{ + g_return_val_if_fail (GIMP_IS_TOOL_GUI (gui), FALSE); + + return GET_PRIVATE (gui)->focus_on_map; +} + +void +gimp_tool_gui_add_buttons_valist (GimpToolGui *gui, + va_list args) +{ + const gchar *button_text; + gint response_id; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + while ((button_text = va_arg (args, const gchar *))) + { + response_id = va_arg (args, gint); + + gimp_tool_gui_add_button (gui, button_text, response_id); + } +} + +void +gimp_tool_gui_add_button (GimpToolGui *gui, + const gchar *button_text, + gint response_id) +{ + GimpToolGuiPrivate *private; + ResponseEntry *entry; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + g_return_if_fail (button_text != NULL); + + private = GET_PRIVATE (gui); + + entry = response_entry_new (response_id, button_text); + + private->response_entries = g_list_append (private->response_entries, + entry); + + if (private->dialog) + gimp_tool_gui_add_dialog_button (gui, entry); +} + +void +gimp_tool_gui_set_default_response (GimpToolGui *gui, + gint response_id) +{ + GimpToolGuiPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + g_return_if_fail (response_entry_find (private->response_entries, + response_id) != NULL); + + private->default_response = response_id; + + if (private->overlay) + { + gimp_overlay_dialog_set_default_response (GIMP_OVERLAY_DIALOG (private->dialog), + response_id); + } + else + { + gtk_dialog_set_default_response (GTK_DIALOG (private->dialog), + response_id); + } +} + +void +gimp_tool_gui_set_response_sensitive (GimpToolGui *gui, + gint response_id, + gboolean sensitive) +{ + GimpToolGuiPrivate *private; + ResponseEntry *entry; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + entry = response_entry_find (private->response_entries, response_id); + + if (! entry) + return; + + entry->sensitive = sensitive; + + if (private->overlay) + { + gimp_overlay_dialog_set_response_sensitive (GIMP_OVERLAY_DIALOG (private->dialog), + response_id, sensitive); + } + else + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (private->dialog), + response_id, sensitive); + } +} + +void +gimp_tool_gui_set_alternative_button_order (GimpToolGui *gui, + ...) +{ + GimpToolGuiPrivate *private; + va_list args; + gint response_id; + gint i; + + g_return_if_fail (GIMP_IS_TOOL_GUI (gui)); + + private = GET_PRIVATE (gui); + + va_start (args, gui); + + for (response_id = va_arg (args, gint), i = 0; + response_id != -1; + response_id = va_arg (args, gint), i++) + { + ResponseEntry *entry = response_entry_find (private->response_entries, + response_id); + + if (entry) + entry->alternative_position = i; + } + + va_end (args); + + gimp_tool_gui_update_buttons (gui); +} + + +/* private functions */ + +static void +gimp_tool_gui_create_dialog (GimpToolGui *gui, + GdkScreen *screen, + gint monitor) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + GList *list; + + if (private->overlay) + { + private->dialog = gimp_overlay_dialog_new (private->tool_info, + private->description, + NULL); + g_object_ref_sink (private->dialog); + + for (list = private->response_entries; list; list = g_list_next (list)) + { + ResponseEntry *entry = list->data; + + gimp_tool_gui_add_dialog_button (gui, entry); + } + + if (private->default_response != -1) + gimp_overlay_dialog_set_default_response (GIMP_OVERLAY_DIALOG (private->dialog), + private->default_response); + + gtk_container_set_border_width (GTK_CONTAINER (private->dialog), 6); + + gtk_container_set_border_width (GTK_CONTAINER (private->vbox), 0); + gtk_container_add (GTK_CONTAINER (private->dialog), private->vbox); + gtk_widget_show (private->vbox); + } + else + { + private->dialog = gimp_tool_dialog_new (private->tool_info, + screen, monitor, + private->title, + private->description, + private->icon_name, + private->help_id, + NULL); + + for (list = private->response_entries; list; list = g_list_next (list)) + { + ResponseEntry *entry = list->data; + + gimp_tool_gui_add_dialog_button (gui, entry); + } + + if (private->default_response != -1) + gtk_dialog_set_default_response (GTK_DIALOG (private->dialog), + private->default_response); + + gtk_window_set_focus_on_map (GTK_WINDOW (private->dialog), + private->focus_on_map); + + gtk_container_set_border_width (GTK_CONTAINER (private->vbox), 6); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (private->dialog))), + private->vbox, TRUE, TRUE, 0); + gtk_widget_show (private->vbox); + } + + gimp_tool_gui_update_buttons (gui); + + if (private->shell) + gimp_tool_gui_update_shell (gui); + + if (private->viewable) + gimp_tool_gui_update_viewable (gui); + + g_signal_connect_object (private->dialog, "response", + G_CALLBACK (gimp_tool_gui_dialog_response), + G_OBJECT (gui), 0); +} + +static void +gimp_tool_gui_add_dialog_button (GimpToolGui *gui, + ResponseEntry *entry) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + + if (private->overlay) + { + gimp_overlay_dialog_add_button (GIMP_OVERLAY_DIALOG (private->dialog), + entry->button_text, + entry->response_id); + + if (! entry->sensitive) + { + gimp_overlay_dialog_set_response_sensitive ( + GIMP_OVERLAY_DIALOG (private->dialog), + entry->response_id, FALSE); + } + } + else + { + gimp_dialog_add_button (GIMP_DIALOG (private->dialog), + entry->button_text, + entry->response_id); + + if (! entry->sensitive) + gtk_dialog_set_response_sensitive (GTK_DIALOG (private->dialog), + entry->response_id, + FALSE); + } +} + +static void +gimp_tool_gui_update_buttons (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + GList *list; + gint *ids; + gint n_ids; + gint n_alternatives = 0; + gint i; + + n_ids = g_list_length (private->response_entries); + ids = g_new0 (gint, n_ids); + + for (list = private->response_entries, i = 0; + list; + list = g_list_next (list), i++) + { + ResponseEntry *entry = list->data; + + if (entry->alternative_position >= 0 && + entry->alternative_position < n_ids) + { + ids[entry->alternative_position] = entry->response_id; + n_alternatives++; + } + } + + if (n_ids == n_alternatives) + { + if (private->overlay) + { + gimp_overlay_dialog_set_alternative_button_order (GIMP_OVERLAY_DIALOG (private->dialog), + n_ids, ids); + } + else + { + gtk_dialog_set_alternative_button_order_from_array (GTK_DIALOG (private->dialog), + n_ids, ids); + } + } + + g_free (ids); +} + +static void +gimp_tool_gui_update_shell (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + + if (private->overlay) + { + if (gtk_widget_get_parent (private->dialog)) + { + gimp_tool_gui_hide (gui); + + if (private->shell) + gimp_tool_gui_show (gui); + } + } + else + { + gimp_tool_dialog_set_shell (GIMP_TOOL_DIALOG (private->dialog), + private->shell); + } +} + +static void +gimp_tool_gui_update_viewable (GimpToolGui *gui) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + + if (! private->overlay) + { + GimpContext *context = NULL; + + if (private->tool_info) + context = GIMP_CONTEXT (private->tool_info->tool_options); + + gimp_viewable_dialog_set_viewable (GIMP_VIEWABLE_DIALOG (private->dialog), + private->viewable, context); + } +} + +static void +gimp_tool_gui_dialog_response (GtkWidget *dialog, + gint response_id, + GimpToolGui *gui) +{ + if (response_id == GIMP_RESPONSE_DETACH) + { + gimp_tool_gui_set_auto_overlay (gui, FALSE); + gimp_tool_gui_set_overlay (gui, + gtk_widget_get_screen (dialog), + gimp_widget_get_monitor (dialog), + FALSE); + } + else + { + g_signal_emit (gui, signals[RESPONSE], 0, + response_id); + } +} + +static void +gimp_tool_gui_canvas_resized (GtkWidget *canvas, + GtkAllocation *unused, + GimpToolGui *gui) +{ + GimpToolGuiPrivate *private = GET_PRIVATE (gui); + + if (private->auto_overlay) + { + GtkRequisition requisition; + GtkAllocation allocation; + gboolean overlay = FALSE; + + gtk_widget_size_request (private->vbox, &requisition); + gtk_widget_get_allocation (canvas, &allocation); + + if (allocation.width > 2 * requisition.width && + allocation.height > 3 * requisition.height) + { + overlay = TRUE; + } + + gimp_tool_gui_set_overlay (gui, + gtk_widget_get_screen (private->dialog), + gimp_widget_get_monitor (private->dialog), + overlay); + } +} + +static ResponseEntry * +response_entry_new (gint response_id, + const gchar *button_text) +{ + ResponseEntry *entry = g_slice_new0 (ResponseEntry); + + entry->response_id = response_id; + entry->button_text = g_strdup (button_text); + entry->alternative_position = -1; + entry->sensitive = TRUE; + + return entry; +} + +static void +response_entry_free (ResponseEntry *entry) +{ + g_free (entry->button_text); + + g_slice_free (ResponseEntry, entry); +} + +static ResponseEntry * +response_entry_find (GList *entries, + gint response_id) +{ + for (; entries; entries = g_list_next (entries)) + { + ResponseEntry *entry = entries->data; + + if (entry->response_id == response_id) + return entry; + } + + return NULL; +} diff --git a/app/display/gimptoolgui.h b/app/display/gimptoolgui.h new file mode 100644 index 0000000..7b099a5 --- /dev/null +++ b/app/display/gimptoolgui.h @@ -0,0 +1,115 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolgui.h + * Copyright (C) 2013 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_GUI_H__ +#define __GIMP_TOOL_GUI_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_TOOL_GUI (gimp_tool_gui_get_type ()) +#define GIMP_TOOL_GUI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_GUI, GimpToolGui)) +#define GIMP_TOOL_GUI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_GUI, GimpToolGuiClass)) +#define GIMP_IS_TOOL_GUI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_GUI)) +#define GIMP_IS_TOOL_GUI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_GUI)) +#define GIMP_TOOL_GUI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_GUI, GimpToolGuiClass)) + + +typedef struct _GimpToolGuiClass GimpToolGuiClass; + +struct _GimpToolGui +{ + GimpObject parent_instance; +}; + +struct _GimpToolGuiClass +{ + GimpObjectClass parent_instance; + + void (* response) (GimpToolGui *gui, + gint response_id); + +}; + + +GType gimp_tool_gui_get_type (void) G_GNUC_CONST; + +GimpToolGui * gimp_tool_gui_new (GimpToolInfo *tool_info, + const gchar *title, + const gchar *description, + const gchar *icon_name, + const gchar *help_id, + GdkScreen *screen, + gint monitor, + gboolean overlay, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_tool_gui_set_title (GimpToolGui *gui, + const gchar *title); +void gimp_tool_gui_set_description (GimpToolGui *gui, + const gchar *description); +void gimp_tool_gui_set_icon_name (GimpToolGui *gui, + const gchar *icon_name); +void gimp_tool_gui_set_help_id (GimpToolGui *gui, + const gchar *help_id); + +void gimp_tool_gui_set_shell (GimpToolGui *gui, + GimpDisplayShell *shell); +void gimp_tool_gui_set_viewable (GimpToolGui *gui, + GimpViewable *viewable); + +GtkWidget * gimp_tool_gui_get_dialog (GimpToolGui *gui); +GtkWidget * gimp_tool_gui_get_vbox (GimpToolGui *gui); + +gboolean gimp_tool_gui_get_visible (GimpToolGui *gui); +void gimp_tool_gui_show (GimpToolGui *gui); +void gimp_tool_gui_hide (GimpToolGui *gui); + +void gimp_tool_gui_set_overlay (GimpToolGui *gui, + GdkScreen *screen, + gint monitor, + gboolean overlay); +gboolean gimp_tool_gui_get_overlay (GimpToolGui *gui); + +void gimp_tool_gui_set_auto_overlay (GimpToolGui *gui, + gboolean auto_overlay); +gboolean gimp_tool_gui_get_auto_overlay (GimpToolGui *gui); + +void gimp_tool_gui_set_focus_on_map (GimpToolGui *gui, + gboolean focus_on_map); +gboolean gimp_tool_gui_get_focus_on_map (GimpToolGui *gui); + + +void gimp_tool_gui_add_buttons_valist (GimpToolGui *gui, + va_list args); +void gimp_tool_gui_add_button (GimpToolGui *gui, + const gchar *button_text, + gint response_id); +void gimp_tool_gui_set_default_response (GimpToolGui *gui, + gint response_id); +void gimp_tool_gui_set_response_sensitive (GimpToolGui *gui, + gint response_id, + gboolean sensitive); +void gimp_tool_gui_set_alternative_button_order (GimpToolGui *gui, + ...); + + +#endif /* __GIMP_TOOL_GUI_H__ */ diff --git a/app/display/gimptoolgyroscope.c b/app/display/gimptoolgyroscope.c new file mode 100644 index 0000000..4d6b175 --- /dev/null +++ b/app/display/gimptoolgyroscope.c @@ -0,0 +1,879 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolgyroscope.c + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-transform.h" +#include "gimptoolgyroscope.h" + +#include "gimp-intl.h" + + +#define EPSILON 1e-6 +#define DEG_TO_RAD (G_PI / 180.0) + + +typedef enum +{ + MODE_NONE, + MODE_PAN, + MODE_ROTATE, + MODE_ZOOM +} Mode; + +typedef enum +{ + CONSTRAINT_NONE, + CONSTRAINT_UNKNOWN, + CONSTRAINT_HORIZONTAL, + CONSTRAINT_VERTICAL +} Constraint; + +enum +{ + PROP_0, + PROP_YAW, + PROP_PITCH, + PROP_ROLL, + PROP_ZOOM, + PROP_INVERT, + PROP_SPEED, + PROP_PIVOT_X, + PROP_PIVOT_Y +}; + +struct _GimpToolGyroscopePrivate +{ + gdouble yaw; + gdouble pitch; + gdouble roll; + gdouble zoom; + + gdouble orig_yaw; + gdouble orig_pitch; + gdouble orig_roll; + gdouble orig_zoom; + + gboolean invert; + + gdouble speed; + + gdouble pivot_x; + gdouble pivot_y; + + Mode mode; + Constraint constraint; + + gdouble last_x; + gdouble last_y; + + gdouble last_angle; + gdouble curr_angle; + + gdouble last_zoom; +}; + + +/* local function prototypes */ + +static void gimp_tool_gyroscope_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_gyroscope_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint gimp_tool_gyroscope_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_gyroscope_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_gyroscope_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_gyroscope_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_gyroscope_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static gboolean gimp_tool_gyroscope_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +static void gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static void gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope, + GdkModifierType state); + +static void gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope); +static void gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope); + +static void gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope, + const GimpVector3 *axis); +static void gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector, + const GimpVector3 *axis); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGyroscope, gimp_tool_gyroscope, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_gyroscope_parent_class + + +static void +gimp_tool_gyroscope_class_init (GimpToolGyroscopeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->set_property = gimp_tool_gyroscope_set_property; + object_class->get_property = gimp_tool_gyroscope_get_property; + + widget_class->button_press = gimp_tool_gyroscope_button_press; + widget_class->button_release = gimp_tool_gyroscope_button_release; + widget_class->motion = gimp_tool_gyroscope_motion; + widget_class->hit = gimp_tool_gyroscope_hit; + widget_class->hover = gimp_tool_gyroscope_hover; + widget_class->key_press = gimp_tool_gyroscope_key_press; + widget_class->motion_modifier = gimp_tool_gyroscope_motion_modifier; + widget_class->get_cursor = gimp_tool_gyroscope_get_cursor; + + g_object_class_install_property (object_class, PROP_YAW, + g_param_spec_double ("yaw", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PITCH, + g_param_spec_double ("pitch", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ROLL, + g_param_spec_double ("roll", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ZOOM, + g_param_spec_double ("zoom", NULL, NULL, + 0.0, + +G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_INVERT, + g_param_spec_boolean ("invert", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SPEED, + g_param_spec_double ("speed", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_X, + g_param_spec_double ("pivot-x", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_Y, + g_param_spec_double ("pivot-y", NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_gyroscope_init (GimpToolGyroscope *gyroscope) +{ + gyroscope->private = gimp_tool_gyroscope_get_instance_private (gyroscope); +} + +static void +gimp_tool_gyroscope_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object); + GimpToolGyroscopePrivate *private = gyroscope->private; + + switch (property_id) + { + case PROP_YAW: + private->yaw = g_value_get_double (value); + break; + case PROP_PITCH: + private->pitch = g_value_get_double (value); + break; + case PROP_ROLL: + private->roll = g_value_get_double (value); + break; + case PROP_ZOOM: + private->zoom = g_value_get_double (value); + break; + case PROP_INVERT: + private->invert = g_value_get_boolean (value); + break; + case PROP_SPEED: + private->speed = g_value_get_double (value); + break; + case PROP_PIVOT_X: + private->pivot_x = g_value_get_double (value); + break; + case PROP_PIVOT_Y: + private->pivot_y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_gyroscope_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object); + GimpToolGyroscopePrivate *private = gyroscope->private; + + switch (property_id) + { + case PROP_YAW: + g_value_set_double (value, private->yaw); + break; + case PROP_PITCH: + g_value_set_double (value, private->pitch); + break; + case PROP_ROLL: + g_value_set_double (value, private->roll); + break; + case PROP_ZOOM: + g_value_set_double (value, private->zoom); + break; + case PROP_INVERT: + g_value_set_boolean (value, private->invert); + break; + case PROP_SPEED: + g_value_set_double (value, private->speed); + break; + case PROP_PIVOT_X: + g_value_set_double (value, private->pivot_x); + break; + case PROP_PIVOT_Y: + g_value_set_double (value, private->pivot_y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +gimp_tool_gyroscope_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); + GimpToolGyroscopePrivate *private = gyroscope->private; + + gimp_tool_gyroscope_save (gyroscope); + + if (state & GDK_MOD1_MASK) + { + private->mode = MODE_ZOOM; + + private->last_zoom = private->zoom; + } + else if (state & gimp_get_extend_selection_mask ()) + { + private->mode = MODE_ROTATE; + + private->last_angle = atan2 (coords->y - private->pivot_y, + coords->x - private->pivot_x); + private->curr_angle = private->last_angle; + } + else + { + private->mode = MODE_PAN; + + if (state & gimp_get_constrain_behavior_mask ()) + private->constraint = CONSTRAINT_UNKNOWN; + else + private->constraint = CONSTRAINT_NONE; + } + + private->last_x = coords->x; + private->last_y = coords->y; + + gimp_tool_gyroscope_update_status (gyroscope, state); + + return 1; +} + +static void +gimp_tool_gyroscope_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); + GimpToolGyroscopePrivate *private = gyroscope->private; + + if (release_type == GIMP_BUTTON_RELEASE_CANCEL) + gimp_tool_gyroscope_restore (gyroscope); + + private->mode = MODE_NONE; + + gimp_tool_gyroscope_update_status (gyroscope, state); +} + +static void +gimp_tool_gyroscope_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); + GimpToolGyroscopePrivate *private = gyroscope->private; + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + GimpVector3 axis = {}; + + switch (private->mode) + { + case MODE_PAN: + { + gdouble x1 = private->last_x; + gdouble y1 = private->last_y; + gdouble x2 = coords->x; + gdouble y2 = coords->y; + gdouble factor = 1.0 / private->zoom; + + if (private->constraint != CONSTRAINT_NONE) + { + gimp_display_shell_rotate_xy_f (shell, x1, y1, &x1, &y1); + gimp_display_shell_rotate_xy_f (shell, x2, y2, &x2, &y2); + + if (private->constraint == CONSTRAINT_UNKNOWN) + { + if (fabs (x2 - x1) > fabs (y2 - y1)) + private->constraint = CONSTRAINT_HORIZONTAL; + else if (fabs (y2 - y1) > fabs (x2 - x1)) + private->constraint = CONSTRAINT_VERTICAL; + } + + if (private->constraint == CONSTRAINT_HORIZONTAL) + y2 = y1; + else if (private->constraint == CONSTRAINT_VERTICAL) + x2 = x1; + + gimp_display_shell_unrotate_xy_f (shell, x1, y1, &x1, &y1); + gimp_display_shell_unrotate_xy_f (shell, x2, y2, &x2, &y2); + } + + if (private->invert) + factor = 1.0 / factor; + + gimp_vector3_set (&axis, y2 - y1, x2 - x1, 0.0); + gimp_vector3_mul (&axis, factor * private->speed); + } + break; + + case MODE_ROTATE: + { + gdouble angle; + + angle = atan2 (coords->y - private->pivot_y, + coords->x - private->pivot_x); + + private->curr_angle = angle; + + angle -= private->last_angle; + + if (state & gimp_get_constrain_behavior_mask ()) + angle = RINT (angle / (G_PI / 6.0)) * (G_PI / 6.0); + + gimp_vector3_set (&axis, 0.0, 0.0, angle); + + private->last_angle += angle; + } + break; + + case MODE_ZOOM: + { + gdouble x1, y1; + gdouble x2, y2; + gdouble zoom; + + gimp_display_shell_transform_xy_f (shell, + private->last_x, private->last_y, + &x1, &y1); + gimp_display_shell_transform_xy_f (shell, + coords->x, coords->y, + &x2, &y2); + + zoom = (y1 - y2) * shell->scale_y / 128.0; + + if (private->invert) + zoom = -zoom; + + private->last_zoom *= pow (2.0, zoom); + + zoom = log (private->last_zoom / private->zoom) / G_LN2; + + if (state & gimp_get_constrain_behavior_mask ()) + zoom = RINT (zoom * 2.0) / 2.0; + + g_object_set (gyroscope, + "zoom", private->zoom * pow (2.0, zoom), + NULL); + } + break; + + case MODE_NONE: + g_return_if_reached (); + } + + private->last_x = coords->x; + private->last_y = coords->y; + + gimp_tool_gyroscope_rotate (gyroscope, &axis); +} + +static GimpHit +gimp_tool_gyroscope_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + return GIMP_HIT_INDIRECT; +} + +static void +gimp_tool_gyroscope_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + gimp_tool_gyroscope_update_status (GIMP_TOOL_GYROSCOPE (widget), state); +} + +static gboolean +gimp_tool_gyroscope_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); + GimpToolGyroscopePrivate *private = gyroscope->private; + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + GimpVector3 axis = {}; + gboolean fast; + gboolean result = FALSE; + + fast = (kevent->state & gimp_get_constrain_behavior_mask ()); + + if (kevent->state & GDK_MOD1_MASK) + { + /* zoom */ + gdouble zoom = 0.0; + + switch (kevent->keyval) + { + case GDK_KEY_Up: + zoom = fast ? +1.0 : +1.0 / 8.0; + result = TRUE; + break; + + case GDK_KEY_Down: + zoom = fast ? -1.0 : -1.0 / 8.0; + result = TRUE; + break; + } + + if (private->invert) + zoom = -zoom; + + if (zoom) + { + g_object_set (gyroscope, + "zoom", private->zoom * pow (2.0, zoom), + NULL); + } + } + else if (kevent->state & gimp_get_extend_selection_mask ()) + { + /* rotate */ + gdouble angle = 0.0; + + switch (kevent->keyval) + { + case GDK_KEY_Left: + angle = fast ? +15.0 : +1.0; + result = TRUE; + break; + + case GDK_KEY_Right: + angle = fast ? -15.0 : -1.0; + result = TRUE; + break; + } + + if (shell->flip_horizontally ^ shell->flip_vertically) + angle = -angle; + + gimp_vector3_set (&axis, 0.0, 0.0, angle * DEG_TO_RAD); + } + else + { + /* pan */ + gdouble x0 = 0.0; + gdouble y0 = 0.0; + gdouble x = 0.0; + gdouble y = 0.0; + gdouble factor = 1.0 / private->zoom; + + if (private->invert) + factor = 1.0 / factor; + + switch (kevent->keyval) + { + case GDK_KEY_Left: + x = fast ? +15.0 : +1.0; + result = TRUE; + break; + + case GDK_KEY_Right: + x = fast ? -15.0 : -1.0; + result = TRUE; + break; + + case GDK_KEY_Up: + y = fast ? +15.0 : +1.0; + result = TRUE; + break; + + case GDK_KEY_Down: + y = fast ? -15.0 : -1.0; + result = TRUE; + break; + } + + gimp_display_shell_unrotate_xy_f (shell, x0, y0, &x0, &y0); + gimp_display_shell_unrotate_xy_f (shell, x, y, &x, &y); + + gimp_vector3_set (&axis, + (y - y0) * DEG_TO_RAD, + (x - x0) * DEG_TO_RAD, + 0.0); + gimp_vector3_mul (&axis, factor); + } + + gimp_tool_gyroscope_rotate (gyroscope, &axis); + + return result; +} + +static void +gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); + GimpToolGyroscopePrivate *private = gyroscope->private; + + gimp_tool_gyroscope_update_status (gyroscope, state); + + if (key == gimp_get_constrain_behavior_mask ()) + { + switch (private->mode) + { + case MODE_PAN: + if (state & gimp_get_constrain_behavior_mask ()) + private->constraint = CONSTRAINT_UNKNOWN; + else + private->constraint = CONSTRAINT_NONE; + break; + + case MODE_ROTATE: + if (! (state & gimp_get_constrain_behavior_mask ())) + private->last_angle = private->curr_angle; + break; + + case MODE_ZOOM: + if (! (state & gimp_get_constrain_behavior_mask ())) + private->last_zoom = private->zoom; + break; + + case MODE_NONE: + break; + } + } +} + +static gboolean +gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + if (state & GDK_MOD1_MASK) + *modifier = GIMP_CURSOR_MODIFIER_ZOOM; + else if (state & gimp_get_extend_selection_mask ()) + *modifier = GIMP_CURSOR_MODIFIER_ROTATE; + else + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + + return TRUE; +} + +static void +gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope, + GdkModifierType state) +{ + GimpToolGyroscopePrivate *private = gyroscope->private; + gchar *status; + + if (private->mode == MODE_ZOOM || + (private->mode == MODE_NONE && + state & GDK_MOD1_MASK)) + { + status = gimp_suggest_modifiers (_("Click-Drag to zoom"), + gimp_get_toggle_behavior_mask () & + ~state, + NULL, + _("%s for constrained steps"), + NULL); + } + else if (private->mode == MODE_ROTATE || + (private->mode == MODE_NONE && + state & gimp_get_extend_selection_mask ())) + { + status = gimp_suggest_modifiers (_("Click-Drag to rotate"), + gimp_get_toggle_behavior_mask () & + ~state, + NULL, + _("%s for constrained angles"), + NULL); + } + else + { + status = gimp_suggest_modifiers (_("Click-Drag to pan"), + (((gimp_get_extend_selection_mask () | + GDK_MOD1_MASK) * + (private->mode == MODE_NONE)) | + gimp_get_toggle_behavior_mask ()) & + ~state, + _("%s to rotate"), + _("%s for a constrained axis"), + _("%s to zoom")); + } + + gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (gyroscope), status); + + g_free (status); +} + +static void +gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope) +{ + GimpToolGyroscopePrivate *private = gyroscope->private; + + private->orig_yaw = private->yaw; + private->orig_pitch = private->pitch; + private->orig_roll = private->roll; + private->orig_zoom = private->zoom; +} + +static void +gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope) +{ + GimpToolGyroscopePrivate *private = gyroscope->private; + + g_object_set (gyroscope, + "yaw", private->orig_yaw, + "pitch", private->orig_pitch, + "roll", private->orig_roll, + "zoom", private->orig_zoom, + NULL); +} + +static void +gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope, + const GimpVector3 *axis) +{ + GimpToolGyroscopePrivate *private = gyroscope->private; + GimpVector3 real_axis; + GimpVector3 basis[2]; + gdouble yaw; + gdouble pitch; + gdouble roll; + gint i; + + if (gimp_vector3_length (axis) < EPSILON) + return; + + real_axis = *axis; + + if (private->invert) + gimp_vector3_neg (&real_axis); + + for (i = 0; i < 2; i++) + { + gimp_vector3_set (&basis[i], i == 0, i == 1, 0.0); + + if (private->invert) + gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis); + + gimp_tool_gyroscope_rotate_vector ( + &basis[i], &(GimpVector3) {0.0, private->yaw * DEG_TO_RAD, 0.0}); + gimp_tool_gyroscope_rotate_vector ( + &basis[i], &(GimpVector3) {private->pitch * DEG_TO_RAD, 0.0, 0.0}); + gimp_tool_gyroscope_rotate_vector ( + &basis[i], &(GimpVector3) {0.0, 0.0, private->roll * DEG_TO_RAD}); + + if (! private->invert) + gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis); + } + + roll = atan2 (basis[1].x, basis[1].y); + + for (i = 0; i < 2; i++) + { + gimp_tool_gyroscope_rotate_vector ( + &basis[i], &(GimpVector3) {0.0, 0.0, -roll}); + } + + pitch = atan2 (-basis[1].z, basis[1].y); + + for (i = 0; i < 1; i++) + { + gimp_tool_gyroscope_rotate_vector ( + &basis[i], &(GimpVector3) {-pitch, 0.0, 0.0}); + } + + yaw = atan2 (basis[0].z, basis[0].x); + + g_object_set (gyroscope, + "yaw", yaw / DEG_TO_RAD, + "pitch", pitch / DEG_TO_RAD, + "roll", roll / DEG_TO_RAD, + NULL); +} + +static void +gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector, + const GimpVector3 *axis) +{ + GimpVector3 normalized_axis; + GimpVector3 projection; + GimpVector3 u; + GimpVector3 v; + gdouble angle; + + angle = gimp_vector3_length (axis); + + if (angle < EPSILON) + return; + + normalized_axis = gimp_vector3_mul_val (*axis, 1.0 / angle); + + projection = gimp_vector3_mul_val ( + normalized_axis, + gimp_vector3_inner_product (vector, &normalized_axis)); + + u = gimp_vector3_sub_val (*vector, projection); + v = gimp_vector3_cross_product (&u, &normalized_axis); + + gimp_vector3_mul (&u, cos (angle)); + gimp_vector3_mul (&v, sin (angle)); + + gimp_vector3_add (vector, &u, &v); + gimp_vector3_add (vector, vector, &projection); +} + + +/* public functions */ + + +GimpToolWidget * +gimp_tool_gyroscope_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_GYROSCOPE, + "shell", shell, + NULL); +} diff --git a/app/display/gimptoolgyroscope.h b/app/display/gimptoolgyroscope.h new file mode 100644 index 0000000..3d89648 --- /dev/null +++ b/app/display/gimptoolgyroscope.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolgyroscope.h + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_GYROSCOPE_H__ +#define __GIMP_TOOL_GYROSCOPE_H__ + + +#include "gimptoolwidget.h" + + +#define GIMP_TYPE_TOOL_GYROSCOPE (gimp_tool_gyroscope_get_type ()) +#define GIMP_TOOL_GYROSCOPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_GYROSCOPE, GimpToolGyroscope)) +#define GIMP_TOOL_GYROSCOPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_GYROSCOPE, GimpToolGyroscopeClass)) +#define GIMP_IS_TOOL_GYROSCOPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_GYROSCOPE)) +#define GIMP_IS_TOOL_GYROSCOPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_GYROSCOPE)) +#define GIMP_TOOL_GYROSCOPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_GYROSCOPE, GimpToolGyroscopeClass)) + + +typedef struct _GimpToolGyroscope GimpToolGyroscope; +typedef struct _GimpToolGyroscopePrivate GimpToolGyroscopePrivate; +typedef struct _GimpToolGyroscopeClass GimpToolGyroscopeClass; + +struct _GimpToolGyroscope +{ + GimpToolWidget parent_instance; + + GimpToolGyroscopePrivate *private; +}; + +struct _GimpToolGyroscopeClass +{ + GimpToolWidgetClass parent_class; +}; + + +GType gimp_tool_gyroscope_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_gyroscope_new (GimpDisplayShell *shell); + + +#endif /* __GIMP_TOOL_GYROSCOPE_H__ */ diff --git a/app/display/gimptoolhandlegrid.c b/app/display/gimptoolhandlegrid.c new file mode 100644 index 0000000..cb5b53d --- /dev/null +++ b/app/display/gimptoolhandlegrid.c @@ -0,0 +1,1186 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolhandlegrid.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Based on GimpHandleTransformTool + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvashandle.h" +#include "gimpdisplayshell.h" +#include "gimptoolhandlegrid.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_HANDLE_MODE, + PROP_N_HANDLES, + PROP_ORIG_X1, + PROP_ORIG_Y1, + PROP_ORIG_X2, + PROP_ORIG_Y2, + PROP_ORIG_X3, + PROP_ORIG_Y3, + PROP_ORIG_X4, + PROP_ORIG_Y4, + PROP_TRANS_X1, + PROP_TRANS_Y1, + PROP_TRANS_X2, + PROP_TRANS_Y2, + PROP_TRANS_X3, + PROP_TRANS_Y3, + PROP_TRANS_X4, + PROP_TRANS_Y4 +}; + + +struct _GimpToolHandleGridPrivate +{ + GimpTransformHandleMode handle_mode; /* enum to be renamed */ + + gint n_handles; + GimpVector2 orig[4]; + GimpVector2 trans[4]; + + gint handle; + gdouble last_x; + gdouble last_y; + + gboolean hover; + gdouble mouse_x; + gdouble mouse_y; + + GimpCanvasItem *handles[5]; +}; + + +/* local function prototypes */ + +static void gimp_tool_handle_grid_constructed (GObject *object); +static void gimp_tool_handle_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_handle_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_handle_grid_changed (GimpToolWidget *widget); +static gint gimp_tool_handle_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_handle_grid_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_handle_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_handle_grid_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_handle_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget); +static gboolean gimp_tool_handle_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static gint gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid, + const GimpCoords *coords); +static void gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid); +static void gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid); + +static gboolean is_handle_position_valid (GimpToolHandleGrid *grid, + gint handle); +static void handle_micro_move (GimpToolHandleGrid *grid, + gint handle); + +static inline gdouble calc_angle (gdouble ax, + gdouble ay, + gdouble bx, + gdouble by); +static inline gdouble calc_len (gdouble a, + gdouble b); +static inline gdouble calc_lineintersect_ratio (gdouble p1x, + gdouble p1y, + gdouble p2x, + gdouble p2y, + gdouble q1x, + gdouble q1y, + gdouble q2x, + gdouble q2y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolHandleGrid, gimp_tool_handle_grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID) + +#define parent_class gimp_tool_handle_grid_parent_class + + +static void +gimp_tool_handle_grid_class_init (GimpToolHandleGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_handle_grid_constructed; + object_class->set_property = gimp_tool_handle_grid_set_property; + object_class->get_property = gimp_tool_handle_grid_get_property; + + widget_class->changed = gimp_tool_handle_grid_changed; + widget_class->button_press = gimp_tool_handle_grid_button_press; + widget_class->button_release = gimp_tool_handle_grid_button_release; + widget_class->motion = gimp_tool_handle_grid_motion; + widget_class->hit = gimp_tool_handle_grid_hit; + widget_class->hover = gimp_tool_handle_grid_hover; + widget_class->leave_notify = gimp_tool_handle_grid_leave_notify; + widget_class->get_cursor = gimp_tool_handle_grid_get_cursor; + + g_object_class_install_property (object_class, PROP_HANDLE_MODE, + g_param_spec_enum ("handle-mode", + NULL, NULL, + GIMP_TYPE_TRANSFORM_HANDLE_MODE, + GIMP_HANDLE_MODE_ADD_TRANSFORM, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_N_HANDLES, + g_param_spec_int ("n-handles", + NULL, NULL, + 0, 4, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_X1, + g_param_spec_double ("orig-x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_Y1, + g_param_spec_double ("orig-y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_X2, + g_param_spec_double ("orig-x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_Y2, + g_param_spec_double ("orig-y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_X3, + g_param_spec_double ("orig-x3", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_Y3, + g_param_spec_double ("orig-y3", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_X4, + g_param_spec_double ("orig-x4", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ORIG_Y4, + g_param_spec_double ("orig-y4", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_X1, + g_param_spec_double ("trans-x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_Y1, + g_param_spec_double ("trans-y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_X2, + g_param_spec_double ("trans-x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_Y2, + g_param_spec_double ("trans-y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_X3, + g_param_spec_double ("trans-x3", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_Y3, + g_param_spec_double ("trans-y3", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_X4, + g_param_spec_double ("trans-x4", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TRANS_Y4, + g_param_spec_double ("trans-y4", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_handle_grid_init (GimpToolHandleGrid *grid) +{ + grid->private = gimp_tool_handle_grid_get_instance_private (grid); +} + +static void +gimp_tool_handle_grid_constructed (GObject *object) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolHandleGridPrivate *private = grid->private; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + for (i = 0; i < 4; i++) + { + private->handles[i + 1] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CIRCLE, + 0, 0, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_HANDLE_ANCHOR_CENTER); + } + + gimp_tool_handle_grid_changed (widget); +} + +static void +gimp_tool_handle_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object); + GimpToolHandleGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_HANDLE_MODE: + private->handle_mode = g_value_get_enum (value); + break; + + case PROP_N_HANDLES: + private->n_handles = g_value_get_int (value); + break; + + case PROP_ORIG_X1: + private->orig[0].x = g_value_get_double (value); + break; + case PROP_ORIG_Y1: + private->orig[0].y = g_value_get_double (value); + break; + case PROP_ORIG_X2: + private->orig[1].x = g_value_get_double (value); + break; + case PROP_ORIG_Y2: + private->orig[1].y = g_value_get_double (value); + break; + case PROP_ORIG_X3: + private->orig[2].x = g_value_get_double (value); + break; + case PROP_ORIG_Y3: + private->orig[2].y = g_value_get_double (value); + break; + case PROP_ORIG_X4: + private->orig[3].x = g_value_get_double (value); + break; + case PROP_ORIG_Y4: + private->orig[3].y = g_value_get_double (value); + break; + + case PROP_TRANS_X1: + private->trans[0].x = g_value_get_double (value); + break; + case PROP_TRANS_Y1: + private->trans[0].y = g_value_get_double (value); + break; + case PROP_TRANS_X2: + private->trans[1].x = g_value_get_double (value); + break; + case PROP_TRANS_Y2: + private->trans[1].y = g_value_get_double (value); + break; + case PROP_TRANS_X3: + private->trans[2].x = g_value_get_double (value); + break; + case PROP_TRANS_Y3: + private->trans[2].y = g_value_get_double (value); + break; + case PROP_TRANS_X4: + private->trans[3].x = g_value_get_double (value); + break; + case PROP_TRANS_Y4: + private->trans[3].y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_handle_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object); + GimpToolHandleGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_N_HANDLES: + g_value_set_int (value, private->n_handles); + break; + + case PROP_HANDLE_MODE: + g_value_set_enum (value, private->handle_mode); + break; + + case PROP_ORIG_X1: + g_value_set_double (value, private->orig[0].x); + break; + case PROP_ORIG_Y1: + g_value_set_double (value, private->orig[0].y); + break; + case PROP_ORIG_X2: + g_value_set_double (value, private->orig[1].x); + break; + case PROP_ORIG_Y2: + g_value_set_double (value, private->orig[1].y); + break; + case PROP_ORIG_X3: + g_value_set_double (value, private->orig[2].x); + break; + case PROP_ORIG_Y3: + g_value_set_double (value, private->orig[2].y); + break; + case PROP_ORIG_X4: + g_value_set_double (value, private->orig[3].x); + break; + case PROP_ORIG_Y4: + g_value_set_double (value, private->orig[3].y); + break; + + case PROP_TRANS_X1: + g_value_set_double (value, private->trans[0].x); + break; + case PROP_TRANS_Y1: + g_value_set_double (value, private->trans[0].y); + break; + case PROP_TRANS_X2: + g_value_set_double (value, private->trans[1].x); + break; + case PROP_TRANS_Y2: + g_value_set_double (value, private->trans[1].y); + break; + case PROP_TRANS_X3: + g_value_set_double (value, private->trans[2].x); + break; + case PROP_TRANS_Y3: + g_value_set_double (value, private->trans[2].y); + break; + case PROP_TRANS_X4: + g_value_set_double (value, private->trans[3].x); + break; + case PROP_TRANS_Y4: + g_value_set_double (value, private->trans[3].y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_handle_grid_changed (GimpToolWidget *widget) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + gint i; + + GIMP_TOOL_WIDGET_CLASS (parent_class)->changed (widget); + + for (i = 0; i < 4; i++) + { + gimp_canvas_handle_set_position (private->handles[i + 1], + private->trans[i].x, + private->trans[i].y); + gimp_canvas_item_set_visible (private->handles[i + 1], + i < private->n_handles); + } + + gimp_tool_handle_grid_update_hilight (grid); +} + +static gint +gimp_tool_handle_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + gint n_handles = private->n_handles; + gint active_handle = private->handle - 1; + + switch (private->handle_mode) + { + case GIMP_HANDLE_MODE_ADD_TRANSFORM: + if (n_handles < 4 && active_handle == -1) + { + /* add handle */ + + GimpMatrix3 *matrix; + + active_handle = n_handles; + + private->trans[active_handle].x = coords->x; + private->trans[active_handle].y = coords->y; + private->n_handles++; + + if (! is_handle_position_valid (grid, active_handle)) + { + handle_micro_move (grid, active_handle); + } + + /* handle was added, calculate new original position */ + g_object_get (grid, + "transform", &matrix, + NULL); + + gimp_matrix3_invert (matrix); + gimp_matrix3_transform_point (matrix, + private->trans[active_handle].x, + private->trans[active_handle].y, + &private->orig[active_handle].x, + &private->orig[active_handle].y); + + g_free (matrix); + + private->handle = active_handle + 1; + + g_object_notify (G_OBJECT (grid), "n-handles"); + } + break; + + case GIMP_HANDLE_MODE_MOVE: + /* check for valid position and calculating of OX0...OY3 is + * done on button release + */ + break; + + case GIMP_HANDLE_MODE_REMOVE: + if (n_handles > 0 && + active_handle >= 0 && + active_handle < 4) + { + /* remove handle */ + + GimpVector2 temp = private->trans[active_handle]; + GimpVector2 tempo = private->orig[active_handle]; + gint i; + + n_handles--; + private->n_handles--; + + for (i = active_handle; i < n_handles; i++) + { + private->trans[i] = private->trans[i + 1]; + private->orig[i] = private->orig[i + 1]; + } + + private->trans[n_handles] = temp; + private->orig[n_handles] = tempo; + + g_object_notify (G_OBJECT (grid), "n-handles"); + } + break; + } + + private->last_x = coords->x; + private->last_y = coords->y; + + return private->handle; +} + +static void +gimp_tool_handle_grid_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + gint active_handle = private->handle - 1; + + if (private->handle_mode == GIMP_HANDLE_MODE_MOVE && + active_handle >= 0 && + active_handle < 4) + { + GimpMatrix3 *matrix; + + if (! is_handle_position_valid (grid, active_handle)) + { + handle_micro_move (grid, active_handle); + } + + /* handle was moved, calculate new original position */ + g_object_get (grid, + "transform", &matrix, + NULL); + + gimp_matrix3_invert (matrix); + gimp_matrix3_transform_point (matrix, + private->trans[active_handle].x, + private->trans[active_handle].y, + &private->orig[active_handle].x, + &private->orig[active_handle].y); + + g_free (matrix); + + gimp_tool_handle_grid_update_matrix (grid); + } +} + +void +gimp_tool_handle_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + gint n_handles = private->n_handles; + gint active_handle = private->handle - 1; + gdouble diff_x = coords->x - private->last_x; + gdouble diff_y = coords->y - private->last_y; + + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + if (active_handle >= 0 && active_handle < 4) + { + if (private->handle_mode == GIMP_HANDLE_MODE_MOVE) + { + private->trans[active_handle].x += diff_x; + private->trans[active_handle].y += diff_y; + + /* check for valid position and calculating of OX0...OY3 is + * done on button release hopefully this makes the code run + * faster Moving could be even faster if there was caching + * for the image preview + */ + gimp_canvas_handle_set_position (private->handles[active_handle + 1], + private->trans[active_handle].x, + private->trans[active_handle].y); + } + else if (private->handle_mode == GIMP_HANDLE_MODE_ADD_TRANSFORM) + { + gdouble angle, angle_sin, angle_cos, scale; + GimpVector2 fixed_handles[3]; + GimpVector2 oldpos[4]; + GimpVector2 newpos[4]; + gint i, j; + + for (i = 0, j = 0; i < 4; i++) + { + /* Find all visible handles that are not being moved */ + if (i < n_handles && i != active_handle) + { + fixed_handles[j] = private->trans[i]; + j++; + } + + newpos[i] = oldpos[i] = private->trans[i]; + } + + newpos[active_handle].x = oldpos[active_handle].x + diff_x; + newpos[active_handle].y = oldpos[active_handle].y + diff_y; + + switch (n_handles) + { + case 1: + /* move */ + for (i = 0; i < 4; i++) + { + newpos[i].x = oldpos[i].x + diff_x; + newpos[i].y = oldpos[i].y + diff_y; + } + break; + + case 2: + /* rotate and keep-aspect-scale */ + scale = + calc_len (newpos[active_handle].x - fixed_handles[0].x, + newpos[active_handle].y - fixed_handles[0].y) / + calc_len (oldpos[active_handle].x - fixed_handles[0].x, + oldpos[active_handle].y - fixed_handles[0].y); + + angle = calc_angle (oldpos[active_handle].x - fixed_handles[0].x, + oldpos[active_handle].y - fixed_handles[0].y, + newpos[active_handle].x - fixed_handles[0].x, + newpos[active_handle].y - fixed_handles[0].y); + + angle_sin = sin (angle); + angle_cos = cos (angle); + + for (i = 2; i < 4; i++) + { + newpos[i].x = + fixed_handles[0].x + + scale * (angle_cos * (oldpos[i].x - fixed_handles[0].x) + + angle_sin * (oldpos[i].y - fixed_handles[0].y)); + + newpos[i].y = + fixed_handles[0].y + + scale * (-angle_sin * (oldpos[i].x - fixed_handles[0].x) + + angle_cos * (oldpos[i].y - fixed_handles[0].y)); + } + break; + + case 3: + /* shear and non-aspect-scale */ + scale = calc_lineintersect_ratio (oldpos[3].x, + oldpos[3].y, + oldpos[active_handle].x, + oldpos[active_handle].y, + fixed_handles[0].x, + fixed_handles[0].y, + fixed_handles[1].x, + fixed_handles[1].y); + + newpos[3].x = oldpos[3].x + scale * diff_x; + newpos[3].y = oldpos[3].y + scale * diff_y; + break; + } + + for (i = 0; i < 4; i++) + { + private->trans[i] = newpos[i]; + } + + gimp_tool_handle_grid_update_matrix (grid); + } + } + + private->last_x = coords->x; + private->last_y = coords->y; +} + +static GimpHit +gimp_tool_handle_grid_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + + if (proximity) + { + gint handle = gimp_tool_handle_grid_get_handle (grid, coords); + + switch (private->handle_mode) + { + case GIMP_HANDLE_MODE_ADD_TRANSFORM: + if (handle > 0) + return GIMP_HIT_DIRECT; + else + return GIMP_HIT_INDIRECT; + break; + + case GIMP_HANDLE_MODE_MOVE: + case GIMP_HANDLE_MODE_REMOVE: + if (private->handle > 0) + return GIMP_HIT_DIRECT; + break; + } + } + + return GIMP_HIT_NONE; +} + +static void +gimp_tool_handle_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + gchar *status = NULL; + + private->hover = TRUE; + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + private->handle = gimp_tool_handle_grid_get_handle (grid, coords); + + if (proximity) + { + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask (); + + switch (private->handle_mode) + { + case GIMP_HANDLE_MODE_ADD_TRANSFORM: + if (private->handle > 0) + { + const gchar *s = NULL; + + switch (private->n_handles) + { + case 1: + s = _("Click-Drag to move"); + break; + case 2: + s = _("Click-Drag to rotate and scale"); + break; + case 3: + s = _("Click-Drag to shear and scale"); + break; + case 4: + s = _("Click-Drag to change perspective"); + break; + } + + status = gimp_suggest_modifiers (s, + extend_mask | toggle_mask, + NULL, NULL, NULL); + } + else + { + if (private->n_handles < 4) + status = g_strdup (_("Click to add a handle")); + } + break; + + case GIMP_HANDLE_MODE_MOVE: + if (private->handle > 0) + status = g_strdup (_("Click-Drag to move this handle")); + break; + + case GIMP_HANDLE_MODE_REMOVE: + if (private->handle > 0) + status = g_strdup (_("Click-Drag to remove this handle")); + break; + } + } + + gimp_tool_widget_set_status (widget, status); + g_free (status); + + gimp_tool_handle_grid_update_hilight (grid); +} + +static void +gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + + private->hover = FALSE; + private->handle = 0; + + gimp_tool_handle_grid_update_hilight (grid); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static gboolean +gimp_tool_handle_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget); + GimpToolHandleGridPrivate *private = grid->private; + + *cursor = GIMP_CURSOR_CROSSHAIR_SMALL; + *tool_cursor = GIMP_TOOL_CURSOR_NONE; + *modifier = GIMP_CURSOR_MODIFIER_NONE; + + switch (private->handle_mode) + { + case GIMP_HANDLE_MODE_ADD_TRANSFORM: + if (private->handle > 0) + { + switch (private->n_handles) + { + case 1: + *tool_cursor = GIMP_TOOL_CURSOR_MOVE; + break; + case 2: + *tool_cursor = GIMP_TOOL_CURSOR_ROTATE; + break; + case 3: + *tool_cursor = GIMP_TOOL_CURSOR_SHEAR; + break; + case 4: + *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE; + break; + } + } + else + { + if (private->n_handles < 4) + *modifier = GIMP_CURSOR_MODIFIER_PLUS; + else + *modifier = GIMP_CURSOR_MODIFIER_BAD; + } + break; + + case GIMP_HANDLE_MODE_MOVE: + if (private->handle > 0) + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + else + *modifier = GIMP_CURSOR_MODIFIER_BAD; + break; + + case GIMP_HANDLE_MODE_REMOVE: + if (private->handle > 0) + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + else + *modifier = GIMP_CURSOR_MODIFIER_BAD; + break; + } + + return TRUE; +} + +static gint +gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid, + const GimpCoords *coords) +{ + GimpToolHandleGridPrivate *private = grid->private; + gint i; + + for (i = 0; i < 4; i++) + { + if (private->handles[i + 1] && + gimp_canvas_item_hit (private->handles[i + 1], + coords->x, coords->y)) + { + return i + 1; + } + } + + return 0; +} + +static void +gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid) +{ + GimpToolHandleGridPrivate *private = grid->private; + gint i; + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *item = private->handles[i + 1]; + + if (item) + { + gdouble diameter = GIMP_CANVAS_HANDLE_SIZE_CIRCLE; + + if (private->hover) + { + diameter = gimp_canvas_handle_calc_size ( + item, + private->mouse_x, + private->mouse_y, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE); + } + + gimp_canvas_handle_set_size (item, diameter, diameter); + gimp_canvas_item_set_highlight (item, (i + 1) == private->handle); + } + } +} + +static void +gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid) +{ + GimpToolHandleGridPrivate *private = grid->private; + GimpMatrix3 transform; + gboolean transform_valid; + + gimp_matrix3_identity (&transform); + transform_valid = gimp_transform_matrix_generic (&transform, + private->orig, + private->trans); + + g_object_set (grid, + "transform", &transform, + "show-guides", transform_valid, + NULL); +} + +/* check if a handle is not on the connection line of two other handles */ +static gboolean +is_handle_position_valid (GimpToolHandleGrid *grid, + gint handle) +{ + GimpToolHandleGridPrivate *private = grid->private; + gint i, j, k; + + for (i = 0; i < 2; i++) + { + for (j = i + 1; j < 3; j++) + { + for (k = j + 1; i < 4; i++) + { + if (handle == i || + handle == j || + handle == k) + { + if ((private->trans[i].x - + private->trans[j].x) * + (private->trans[j].y - + private->trans[k].y) == + + (private->trans[j].x - + private->trans[k].x) * + (private->trans[i].y - + private->trans[j].y)) + { + return FALSE; + } + } + } + } + } + + return TRUE; +} + +/* three handles on a line causes problems. + * Let's move the new handle around a bit to find a better position */ +static void +handle_micro_move (GimpToolHandleGrid *grid, + gint handle) +{ + GimpToolHandleGridPrivate *private = grid->private; + gdouble posx = private->trans[handle].x; + gdouble posy = private->trans[handle].y; + gdouble dx, dy; + + for (dx = -0.1; dx < 0.11; dx += 0.1) + { + private->trans[handle].x = posx + dx; + + for (dy = -0.1; dy < 0.11; dy += 0.1) + { + private->trans[handle].y = posy + dy; + + if (is_handle_position_valid (grid, handle)) + { + return; + } + } + } +} + +/* finds the clockwise angle between the vectors given, 0-2π */ +static inline gdouble +calc_angle (gdouble ax, + gdouble ay, + gdouble bx, + gdouble by) +{ + gdouble angle; + gdouble direction; + gdouble length = sqrt ((ax * ax + ay * ay) * (bx * bx + by * by)); + + angle = acos ((ax * bx + ay * by) / length); + direction = ax * by - ay * bx; + + return ((direction < 0) ? angle : 2 * G_PI - angle); +} + +static inline gdouble +calc_len (gdouble a, + gdouble b) +{ + return sqrt (a * a + b * b); +} + +/* imagine two lines, one through the points p1 and p2, the other one + * through the points q1 and q2. Find the intersection point r. + * Calculate (distance p1 to r)/(distance p2 to r) + */ +static inline gdouble +calc_lineintersect_ratio (gdouble p1x, gdouble p1y, + gdouble p2x, gdouble p2y, + gdouble q1x, gdouble q1y, + gdouble q2x, gdouble q2y) +{ + gdouble denom, u; + + denom = (q2y - q1y) * (p2x - p1x) - (q2x - q1x) * (p2y - p1y); + if (denom == 0.0) + { + /* u is infinite, so u/(u-1) is 1 */ + return 1.0; + } + + u = (q2y - q1y) * (q1x - p1x) - (q1y - p1y) * (q2x - q1x); + u /= denom; + + return u / (u - 1); +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_handle_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_HANDLE_GRID, + "shell", shell, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "clip-guides", TRUE, + NULL); +} diff --git a/app/display/gimptoolhandlegrid.h b/app/display/gimptoolhandlegrid.h new file mode 100644 index 0000000..77d31dc --- /dev/null +++ b/app/display/gimptoolhandlegrid.h @@ -0,0 +1,62 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolhandlegrid.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_HANDLE_GRID_H__ +#define __GIMP_TOOL_HANDLE_GRID_H__ + + +#include "gimptooltransformgrid.h" + + +#define GIMP_TYPE_TOOL_HANDLE_GRID (gimp_tool_handle_grid_get_type ()) +#define GIMP_TOOL_HANDLE_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_HANDLE_GRID, GimpToolHandleGrid)) +#define GIMP_TOOL_HANDLE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_HANDLE_GRID, GimpToolHandleGridClass)) +#define GIMP_IS_TOOL_HANDLE_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_HANDLE_GRID)) +#define GIMP_IS_TOOL_HANDLE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_HANDLE_GRID)) +#define GIMP_TOOL_HANDLE_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_HANDLE_GRID, GimpToolHandleGridClass)) + + +typedef struct _GimpToolHandleGrid GimpToolHandleGrid; +typedef struct _GimpToolHandleGridPrivate GimpToolHandleGridPrivate; +typedef struct _GimpToolHandleGridClass GimpToolHandleGridClass; + +struct _GimpToolHandleGrid +{ + GimpToolTransformGrid parent_instance; + + GimpToolHandleGridPrivate *private; +}; + +struct _GimpToolHandleGridClass +{ + GimpToolTransformGridClass parent_class; +}; + + +GType gimp_tool_handle_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_handle_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + + +#endif /* __GIMP_TOOL_HANDLE_GRID_H__ */ diff --git a/app/display/gimptoolline.c b/app/display/gimptoolline.c new file mode 100644 index 0000000..271f926 --- /dev/null +++ b/app/display/gimptoolline.c @@ -0,0 +1,1796 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolline.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Major improvements for interactivity + * Copyright (C) 2014 Michael Henning <drawoc@darkrefraction.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-utils.h" +#include "core/gimpmarshal.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvasgroup.h" +#include "gimpcanvashandle.h" +#include "gimpcanvasline.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-cursor.h" +#include "gimpdisplayshell-utils.h" +#include "gimptoolline.h" + +#include "gimp-intl.h" + + +#define SHOW_LINE TRUE +#define GRAB_LINE_MASK GDK_MOD1_MASK +#define ENDPOINT_HANDLE_TYPE GIMP_HANDLE_CROSS +#define ENDPOINT_HANDLE_SIZE GIMP_CANVAS_HANDLE_SIZE_CROSS +#define SLIDER_HANDLE_TYPE GIMP_HANDLE_FILLED_DIAMOND +#define SLIDER_HANDLE_SIZE (ENDPOINT_HANDLE_SIZE * 2 / 3) +#define HANDLE_CIRCLE_SCALE 1.8 +#define LINE_VICINITY ((gint) (SLIDER_HANDLE_SIZE * HANDLE_CIRCLE_SCALE) / 2) +#define SLIDER_TEAR_DISTANCE (5 * LINE_VICINITY) + + +/* hover-only "handles" */ +#define HOVER_NEW_SLIDER (GIMP_TOOL_LINE_HANDLE_NONE - 1) + + +typedef enum +{ + GRAB_NONE, + GRAB_SELECTION, + GRAB_LINE +} GimpToolLineGrab; + +enum +{ + PROP_0, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_SLIDERS, + PROP_SELECTION, + PROP_STATUS_TITLE, +}; + +enum +{ + CAN_ADD_SLIDER, + ADD_SLIDER, + PREPARE_TO_REMOVE_SLIDER, + REMOVE_SLIDER, + SELECTION_CHANGED, + HANDLE_CLICKED, + LAST_SIGNAL +}; + +struct _GimpToolLinePrivate +{ + gdouble x1; + gdouble y1; + gdouble x2; + gdouble y2; + GArray *sliders; + gint selection; + gchar *status_title; + + gdouble saved_x1; + gdouble saved_y1; + gdouble saved_x2; + gdouble saved_y2; + gdouble saved_slider_value; + + gdouble mouse_x; + gdouble mouse_y; + gint hover; + gdouble new_slider_value; + gboolean remove_slider; + GimpToolLineGrab grab; + + GimpCanvasItem *line; + GimpCanvasItem *start_handle; + GimpCanvasItem *end_handle; + GimpCanvasItem *slider_group; + GArray *slider_handles; + GimpCanvasItem *handle_circle; +}; + + +/* local function prototypes */ + +static void gimp_tool_line_constructed (GObject *object); +static void gimp_tool_line_finalize (GObject *object); +static void gimp_tool_line_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_line_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_line_changed (GimpToolWidget *widget); +static void gimp_tool_line_focus_changed (GimpToolWidget *widget); +static gint gimp_tool_line_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_line_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_line_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_line_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_line_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_line_leave_notify (GimpToolWidget *widget); +static gboolean gimp_tool_line_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +static void gimp_tool_line_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_line_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static gint gimp_tool_line_get_hover (GimpToolLine *line, + const GimpCoords *coords, + GdkModifierType state); +static GimpControllerSlider * + gimp_tool_line_get_slider (GimpToolLine *line, + gint slider); +static GimpCanvasItem * + gimp_tool_line_get_handle (GimpToolLine *line, + gint handle); +static gdouble gimp_tool_line_project_point (GimpToolLine *line, + gdouble x, + gdouble y, + gboolean constrain, + gdouble *dist); + +static gboolean + gimp_tool_line_selection_motion (GimpToolLine *line, + gboolean constrain); + +static void gimp_tool_line_update_handles (GimpToolLine *line); +static void gimp_tool_line_update_circle (GimpToolLine *line); +static void gimp_tool_line_update_hilight (GimpToolLine *line); +static void gimp_tool_line_update_status (GimpToolLine *line, + GdkModifierType state, + gboolean proximity); + +static gboolean gimp_tool_line_handle_hit (GimpCanvasItem *handle, + gdouble x, + gdouble y, + gdouble *min_dist); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolLine, gimp_tool_line, GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_line_parent_class + +static guint line_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_tool_line_class_init (GimpToolLineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_line_constructed; + object_class->finalize = gimp_tool_line_finalize; + object_class->set_property = gimp_tool_line_set_property; + object_class->get_property = gimp_tool_line_get_property; + + widget_class->changed = gimp_tool_line_changed; + widget_class->focus_changed = gimp_tool_line_focus_changed; + widget_class->button_press = gimp_tool_line_button_press; + widget_class->button_release = gimp_tool_line_button_release; + widget_class->motion = gimp_tool_line_motion; + widget_class->hit = gimp_tool_line_hit; + widget_class->hover = gimp_tool_line_hover; + widget_class->leave_notify = gimp_tool_line_leave_notify; + widget_class->key_press = gimp_tool_line_key_press; + widget_class->motion_modifier = gimp_tool_line_motion_modifier; + widget_class->get_cursor = gimp_tool_line_get_cursor; + + line_signals[CAN_ADD_SLIDER] = + g_signal_new ("can-add-slider", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpToolLineClass, can_add_slider), + NULL, NULL, + gimp_marshal_BOOLEAN__DOUBLE, + G_TYPE_BOOLEAN, 1, + G_TYPE_DOUBLE); + + line_signals[ADD_SLIDER] = + g_signal_new ("add-slider", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpToolLineClass, add_slider), + NULL, NULL, + gimp_marshal_INT__DOUBLE, + G_TYPE_INT, 1, + G_TYPE_DOUBLE); + + line_signals[PREPARE_TO_REMOVE_SLIDER] = + g_signal_new ("prepare-to-remove-slider", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolLineClass, prepare_to_remove_slider), + NULL, NULL, + gimp_marshal_VOID__INT_BOOLEAN, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_BOOLEAN); + + line_signals[REMOVE_SLIDER] = + g_signal_new ("remove-slider", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolLineClass, remove_slider), + NULL, NULL, + gimp_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + line_signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolLineClass, selection_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + line_signals[HANDLE_CLICKED] = + g_signal_new ("handle-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpToolLineClass, handle_clicked), + NULL, NULL, + gimp_marshal_BOOLEAN__INT_UINT_ENUM, + G_TYPE_BOOLEAN, 3, + G_TYPE_INT, + G_TYPE_UINT, + GIMP_TYPE_BUTTON_PRESS_TYPE); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SLIDERS, + g_param_spec_boxed ("sliders", NULL, NULL, + G_TYPE_ARRAY, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SELECTION, + g_param_spec_int ("selection", NULL, NULL, + GIMP_TOOL_LINE_HANDLE_NONE, + G_MAXINT, + GIMP_TOOL_LINE_HANDLE_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_STATUS_TITLE, + g_param_spec_string ("status-title", + NULL, NULL, + _("Line: "), + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_line_init (GimpToolLine *line) +{ + GimpToolLinePrivate *private; + + private = line->private = gimp_tool_line_get_instance_private (line); + + private->sliders = g_array_new (FALSE, FALSE, sizeof (GimpControllerSlider)); + + private->selection = GIMP_TOOL_LINE_HANDLE_NONE; + private->hover = GIMP_TOOL_LINE_HANDLE_NONE; + private->grab = GRAB_NONE; + + private->slider_handles = g_array_new (FALSE, TRUE, + sizeof (GimpCanvasItem *)); +} + +static void +gimp_tool_line_constructed (GObject *object) +{ + GimpToolLine *line = GIMP_TOOL_LINE (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolLinePrivate *private = line->private; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + private->line = gimp_tool_widget_add_line (widget, + private->x1, + private->y1, + private->x2, + private->y2); + + gimp_canvas_item_set_visible (private->line, SHOW_LINE); + + private->start_handle = + gimp_tool_widget_add_handle (widget, + ENDPOINT_HANDLE_TYPE, + private->x1, + private->y1, + ENDPOINT_HANDLE_SIZE, + ENDPOINT_HANDLE_SIZE, + GIMP_HANDLE_ANCHOR_CENTER); + + private->end_handle = + gimp_tool_widget_add_handle (widget, + ENDPOINT_HANDLE_TYPE, + private->x2, + private->y2, + ENDPOINT_HANDLE_SIZE, + ENDPOINT_HANDLE_SIZE, + GIMP_HANDLE_ANCHOR_CENTER); + + private->slider_group = + gimp_canvas_group_new (gimp_tool_widget_get_shell (widget)); + gimp_tool_widget_add_item (widget, private->slider_group); + g_object_unref (private->slider_group); + + private->handle_circle = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CIRCLE, + private->x1, + private->y1, + ENDPOINT_HANDLE_SIZE * HANDLE_CIRCLE_SCALE, + ENDPOINT_HANDLE_SIZE * HANDLE_CIRCLE_SCALE, + GIMP_HANDLE_ANCHOR_CENTER); + + gimp_tool_line_changed (widget); +} + +static void +gimp_tool_line_finalize (GObject *object) +{ + GimpToolLine *line = GIMP_TOOL_LINE (object); + GimpToolLinePrivate *private = line->private; + + g_clear_pointer (&private->sliders, g_array_unref); + g_clear_pointer (&private->status_title, g_free); + g_clear_pointer (&private->slider_handles, g_array_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_tool_line_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolLine *line = GIMP_TOOL_LINE (object); + GimpToolLinePrivate *private = line->private; + + switch (property_id) + { + case PROP_X1: + private->x1 = g_value_get_double (value); + break; + case PROP_Y1: + private->y1 = g_value_get_double (value); + break; + case PROP_X2: + private->x2 = g_value_get_double (value); + break; + case PROP_Y2: + private->y2 = g_value_get_double (value); + break; + + case PROP_SLIDERS: + { + GArray *sliders = g_value_dup_boxed (value); + gboolean deselect; + + g_return_if_fail (sliders != NULL); + + deselect = + GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection) && + (sliders->len != private->sliders->len || + ! gimp_tool_line_get_slider (line, private->selection)->selectable); + + g_array_unref (private->sliders); + private->sliders = sliders; + + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->hover)) + private->hover = GIMP_TOOL_LINE_HANDLE_NONE; + + if (deselect) + gimp_tool_line_set_selection (line, GIMP_TOOL_LINE_HANDLE_NONE); + } + break; + + case PROP_SELECTION: + { + gint selection = g_value_get_int (value); + + g_return_if_fail (selection < (gint) private->sliders->len); + g_return_if_fail (selection < 0 || + gimp_tool_line_get_slider (line, + selection)->selectable); + + if (selection != private->selection) + { + private->selection = selection; + + if (private->grab == GRAB_SELECTION) + private->grab = GRAB_NONE; + + g_signal_emit (line, line_signals[SELECTION_CHANGED], 0); + } + } + break; + + case PROP_STATUS_TITLE: + g_free (private->status_title); + private->status_title = g_value_dup_string (value); + if (! private->status_title) + private->status_title = g_strdup (_("Line: ")); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_line_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolLine *line = GIMP_TOOL_LINE (object); + GimpToolLinePrivate *private = line->private; + + switch (property_id) + { + case PROP_X1: + g_value_set_double (value, private->x1); + break; + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + case PROP_X2: + g_value_set_double (value, private->x2); + break; + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + case PROP_SLIDERS: + g_value_set_boxed (value, private->sliders); + break; + + case PROP_SELECTION: + g_value_set_int (value, private->selection); + break; + + case PROP_STATUS_TITLE: + g_value_set_string (value, private->status_title); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_line_changed (GimpToolWidget *widget) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + gint i; + + gimp_canvas_line_set (private->line, + private->x1, + private->y1, + private->x2, + private->y2); + + gimp_canvas_handle_set_position (private->start_handle, + private->x1, + private->y1); + + gimp_canvas_handle_set_position (private->end_handle, + private->x2, + private->y2); + + /* remove excessive slider handles */ + for (i = private->sliders->len; i < private->slider_handles->len; i++) + { + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (private->slider_group), + gimp_tool_line_get_handle (line, i)); + } + + g_array_set_size (private->slider_handles, private->sliders->len); + + for (i = 0; i < private->sliders->len; i++) + { + gdouble value; + gdouble x; + gdouble y; + GimpCanvasItem **handle; + + value = gimp_tool_line_get_slider (line, i)->value; + + x = private->x1 + (private->x2 - private->x1) * value; + y = private->y1 + (private->y2 - private->y1) * value; + + handle = &g_array_index (private->slider_handles, GimpCanvasItem *, i); + + if (*handle) + { + gimp_canvas_handle_set_position (*handle, x, y); + } + else + { + *handle = gimp_canvas_handle_new (gimp_tool_widget_get_shell (widget), + SLIDER_HANDLE_TYPE, + GIMP_HANDLE_ANCHOR_CENTER, + x, + y, + SLIDER_HANDLE_SIZE, + SLIDER_HANDLE_SIZE); + + gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (private->slider_group), + *handle); + g_object_unref (*handle); + } + } + + gimp_tool_line_update_handles (line); + gimp_tool_line_update_circle (line); + gimp_tool_line_update_hilight (line); +} + +static void +gimp_tool_line_focus_changed (GimpToolWidget *widget) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + + gimp_tool_line_update_hilight (line); +} + +gboolean +gimp_tool_line_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + gboolean result = FALSE; + + private->grab = GRAB_NONE; + private->remove_slider = FALSE; + + private->saved_x1 = private->x1; + private->saved_y1 = private->y1; + private->saved_x2 = private->x2; + private->saved_y2 = private->y2; + + if (press_type != GIMP_BUTTON_PRESS_NORMAL && + private->hover > GIMP_TOOL_LINE_HANDLE_NONE && + private->selection > GIMP_TOOL_LINE_HANDLE_NONE) + { + g_signal_emit (line, line_signals[HANDLE_CLICKED], 0, + private->selection, state, press_type, &result); + + if (! result) + gimp_tool_widget_hover (widget, coords, state, TRUE); + } + + if (press_type == GIMP_BUTTON_PRESS_NORMAL || ! result) + { + private->saved_x1 = private->x1; + private->saved_y1 = private->y1; + private->saved_x2 = private->x2; + private->saved_y2 = private->y2; + + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->hover)) + { + private->saved_slider_value = + gimp_tool_line_get_slider (line, private->hover)->value; + } + + if (private->hover > GIMP_TOOL_LINE_HANDLE_NONE) + { + gimp_tool_line_set_selection (line, private->hover); + + private->grab = GRAB_SELECTION; + } + else if (private->hover == HOVER_NEW_SLIDER) + { + gint slider; + + g_signal_emit (line, line_signals[ADD_SLIDER], 0, + private->new_slider_value, &slider); + + g_return_val_if_fail (slider < (gint) private->sliders->len, FALSE); + + if (slider >= 0) + { + gimp_tool_line_set_selection (line, slider); + + private->saved_slider_value = + gimp_tool_line_get_slider (line, private->selection)->value; + + private->grab = GRAB_SELECTION; + } + } + else if (state & GRAB_LINE_MASK) + { + private->grab = GRAB_LINE; + } + + result = (private->grab != GRAB_NONE); + } + + if (! result) + { + private->hover = GIMP_TOOL_LINE_HANDLE_NONE; + + gimp_tool_line_set_selection (line, GIMP_TOOL_LINE_HANDLE_NONE); + } + + gimp_tool_line_update_handles (line); + gimp_tool_line_update_circle (line); + gimp_tool_line_update_status (line, state, TRUE); + + return result; +} + +void +gimp_tool_line_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + GimpToolLineGrab grab = private->grab; + + private->grab = GRAB_NONE; + + if (release_type == GIMP_BUTTON_RELEASE_CANCEL) + { + if (grab != GRAB_NONE) + { + if (grab == GRAB_SELECTION && + GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection)) + { + gimp_tool_line_get_slider (line, private->selection)->value = + private->saved_slider_value; + + if (private->remove_slider) + { + private->remove_slider = FALSE; + + g_signal_emit (line, line_signals[PREPARE_TO_REMOVE_SLIDER], 0, + private->selection, FALSE); + } + } + + g_object_set (line, + "x1", private->saved_x1, + "y1", private->saved_y1, + "x2", private->saved_x2, + "y2", private->saved_y2, + NULL); + } + } + else if (grab == GRAB_SELECTION) + { + if (private->remove_slider) + { + private->remove_slider = FALSE; + + g_signal_emit (line, line_signals[REMOVE_SLIDER], 0, + private->selection); + } + else if (release_type == GIMP_BUTTON_RELEASE_CLICK) + { + gboolean result; + + g_signal_emit (line, line_signals[HANDLE_CLICKED], 0, + private->selection, state, GIMP_BUTTON_PRESS_NORMAL, + &result); + } + } +} + +void +gimp_tool_line_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + gdouble diff_x = coords->x - private->mouse_x; + gdouble diff_y = coords->y - private->mouse_y; + + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + if (private->grab == GRAB_LINE) + { + g_object_set (line, + "x1", private->x1 + diff_x, + "y1", private->y1 + diff_y, + "x2", private->x2 + diff_x, + "y2", private->y2 + diff_y, + NULL); + } + else + { + gboolean constrain = (state & gimp_get_constrain_behavior_mask ()) != 0; + + gimp_tool_line_selection_motion (line, constrain); + } + + gimp_tool_line_update_status (line, state, TRUE); +} + +GimpHit +gimp_tool_line_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + + if (! (state & GRAB_LINE_MASK)) + { + gint hover = gimp_tool_line_get_hover (line, coords, state); + + if (hover != GIMP_TOOL_LINE_HANDLE_NONE) + return GIMP_HIT_DIRECT; + } + else + { + return GIMP_HIT_INDIRECT; + } + + return GIMP_HIT_NONE; +} + +void +gimp_tool_line_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + + private->mouse_x = coords->x; + private->mouse_y = coords->y; + + if (! (state & GRAB_LINE_MASK)) + private->hover = gimp_tool_line_get_hover (line, coords, state); + else + private->hover = GIMP_TOOL_LINE_HANDLE_NONE; + + gimp_tool_line_update_handles (line); + gimp_tool_line_update_circle (line); + gimp_tool_line_update_status (line, state, proximity); +} + +static void +gimp_tool_line_leave_notify (GimpToolWidget *widget) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + + private->hover = GIMP_TOOL_LINE_HANDLE_NONE; + + gimp_tool_line_update_handles (line); + gimp_tool_line_update_circle (line); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static gboolean +gimp_tool_line_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + GimpDisplayShell *shell; + gdouble pixels = 1.0; + gboolean move_line; + + move_line = kevent->state & GRAB_LINE_MASK; + + if (private->selection == GIMP_TOOL_LINE_HANDLE_NONE && ! move_line) + return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent); + + shell = gimp_tool_widget_get_shell (widget); + + if (kevent->state & gimp_get_extend_selection_mask ()) + pixels = 10.0; + + if (kevent->state & gimp_get_toggle_behavior_mask ()) + pixels = 50.0; + + switch (kevent->keyval) + { + case GDK_KEY_Left: + case GDK_KEY_Right: + case GDK_KEY_Up: + case GDK_KEY_Down: + /* move an endpoint (or both endpoints) */ + if (private->selection < 0 || move_line) + { + gdouble xdist, ydist; + gdouble dx, dy; + + xdist = FUNSCALEX (shell, pixels); + ydist = FUNSCALEY (shell, pixels); + + dx = 0.0; + dy = 0.0; + + switch (kevent->keyval) + { + case GDK_KEY_Left: dx = -xdist; break; + case GDK_KEY_Right: dx = +xdist; break; + case GDK_KEY_Up: dy = -ydist; break; + case GDK_KEY_Down: dy = +ydist; break; + } + + if (private->selection == GIMP_TOOL_LINE_HANDLE_START || move_line) + { + g_object_set (line, + "x1", private->x1 + dx, + "y1", private->y1 + dy, + NULL); + } + + if (private->selection == GIMP_TOOL_LINE_HANDLE_END || move_line) + { + g_object_set (line, + "x2", private->x2 + dx, + "y2", private->y2 + dy, + NULL); + } + } + /* move a slider */ + else + { + GimpControllerSlider *slider; + gdouble dist; + gdouble dvalue; + + slider = gimp_tool_line_get_slider (line, private->selection); + + if (! slider->movable) + break; + + dist = gimp_canvas_item_transform_distance (private->line, + private->x1, private->y1, + private->x2, private->y2); + + if (dist > 0.0) + dist = pixels / dist; + + dvalue = 0.0; + + switch (kevent->keyval) + { + case GDK_KEY_Left: + if (private->x1 < private->x2) dvalue = -dist; + else if (private->x1 > private->x2) dvalue = +dist; + break; + + case GDK_KEY_Right: + if (private->x1 < private->x2) dvalue = +dist; + else if (private->x1 > private->x2) dvalue = -dist; + break; + + case GDK_KEY_Up: + if (private->y1 < private->y2) dvalue = -dist; + else if (private->y1 > private->y2) dvalue = +dist; + break; + + case GDK_KEY_Down: + if (private->y1 < private->y2) dvalue = +dist; + else if (private->y1 > private->y2) dvalue = -dist; + break; + } + + if (dvalue != 0.0) + { + slider->value += dvalue; + slider->value = CLAMP (slider->value, slider->min, slider->max); + slider->value = CLAMP (slider->value, 0.0, 1.0); + + g_object_set (line, + "sliders", private->sliders, + NULL); + } + } + return TRUE; + + case GDK_KEY_BackSpace: + case GDK_KEY_Delete: + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection)) + { + if (gimp_tool_line_get_slider (line, private->selection)->removable) + { + g_signal_emit (line, line_signals[REMOVE_SLIDER], 0, + private->selection); + } + } + return TRUE; + } + + return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent); +} + +static void +gimp_tool_line_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + + if (key == gimp_get_constrain_behavior_mask ()) + { + gimp_tool_line_selection_motion (line, press); + + gimp_tool_line_update_status (line, state, TRUE); + } +} + +static gboolean +gimp_tool_line_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolLine *line = GIMP_TOOL_LINE (widget); + GimpToolLinePrivate *private = line->private; + + if (private->grab ==GRAB_LINE || (state & GRAB_LINE_MASK)) + { + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + + return TRUE; + } + else if (private->grab == GRAB_SELECTION || + private->hover > GIMP_TOOL_LINE_HANDLE_NONE) + { + const GimpControllerSlider *slider = NULL; + + if (private->grab == GRAB_SELECTION) + { + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection)) + slider = gimp_tool_line_get_slider (line, private->selection); + } + else if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->hover)) + { + slider = gimp_tool_line_get_slider (line, private->hover); + } + + if (private->grab == GRAB_SELECTION && slider && private->remove_slider) + { + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + + return TRUE; + } + else if (! slider || slider->movable) + { + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + + return TRUE; + } + } + else if (private->hover == HOVER_NEW_SLIDER) + { + *modifier = GIMP_CURSOR_MODIFIER_PLUS; + + return TRUE; + } + + return FALSE; +} + +static gint +gimp_tool_line_get_hover (GimpToolLine *line, + const GimpCoords *coords, + GdkModifierType state) +{ + GimpToolLinePrivate *private = line->private; + gint hover = GIMP_TOOL_LINE_HANDLE_NONE; + gdouble min_dist; + gint first_handle; + gint i; + + /* find the closest handle to the cursor */ + min_dist = G_MAXDOUBLE; + first_handle = private->sliders->len - 1; + + /* skip the sliders if the two endpoints are the same, in particular so + * that if the line is created during a button-press event (as in the + * blend tool), the end endpoint is dragged, instead of a slider. + */ + if (private->x1 == private->x2 && private->y1 == private->y2) + first_handle = -1; + + for (i = first_handle; i > GIMP_TOOL_LINE_HANDLE_NONE; i--) + { + GimpCanvasItem *handle; + + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (i)) + { + const GimpControllerSlider *slider; + + slider = gimp_tool_line_get_slider (line, i); + + if (! slider->visible || ! slider->selectable) + continue; + } + + handle = gimp_tool_line_get_handle (line, i); + + if (gimp_tool_line_handle_hit (handle, + private->mouse_x, + private->mouse_y, + &min_dist)) + { + hover = i; + } + } + + if (hover == GIMP_TOOL_LINE_HANDLE_NONE) + { + gboolean constrain; + gdouble value; + gdouble dist; + + constrain = (state & gimp_get_constrain_behavior_mask ()) != 0; + + value = gimp_tool_line_project_point (line, + private->mouse_x, + private->mouse_y, + constrain, + &dist); + + if (value >= 0.0 && value <= 1.0 && dist <= LINE_VICINITY) + { + gboolean can_add; + + g_signal_emit (line, line_signals[CAN_ADD_SLIDER], 0, + value, &can_add); + + if (can_add) + { + hover = HOVER_NEW_SLIDER; + private->new_slider_value = value; + } + } + } + + return hover; +} + +static GimpControllerSlider * +gimp_tool_line_get_slider (GimpToolLine *line, + gint slider) +{ + GimpToolLinePrivate *private = line->private; + + gimp_assert (slider >= 0 && slider < private->sliders->len); + + return &g_array_index (private->sliders, GimpControllerSlider, slider); +} + +static GimpCanvasItem * +gimp_tool_line_get_handle (GimpToolLine *line, + gint handle) +{ + GimpToolLinePrivate *private = line->private; + + switch (handle) + { + case GIMP_TOOL_LINE_HANDLE_NONE: + return NULL; + + case GIMP_TOOL_LINE_HANDLE_START: + return private->start_handle; + + case GIMP_TOOL_LINE_HANDLE_END: + return private->end_handle; + + default: + gimp_assert (handle >= 0 && + handle < (gint) private->slider_handles->len); + + return g_array_index (private->slider_handles, + GimpCanvasItem *, handle); + } +} + +static gdouble +gimp_tool_line_project_point (GimpToolLine *line, + gdouble x, + gdouble y, + gboolean constrain, + gdouble *dist) +{ + GimpToolLinePrivate *private = line->private; + gdouble length_sqr; + gdouble value = 0.0; + + length_sqr = SQR (private->x2 - private->x1) + + SQR (private->y2 - private->y1); + + /* don't calculate the projection for 0-length lines, since we'll just get + * NaN. + */ + if (length_sqr > 0.0) + { + value = (private->x2 - private->x1) * (x - private->x1) + + (private->y2 - private->y1) * (y - private->y1); + value /= length_sqr; + + if (dist) + { + gdouble px; + gdouble py; + + px = private->x1 + (private->x2 - private->x1) * value; + py = private->y1 + (private->y2 - private->y1) * value; + + *dist = gimp_canvas_item_transform_distance (private->line, + x, y, + px, py); + } + + if (constrain) + value = RINT (12.0 * value) / 12.0; + } + else + { + if (dist) + { + *dist = gimp_canvas_item_transform_distance (private->line, + x, y, + private->x1, private->y1); + } + } + + return value; +} + +static gboolean +gimp_tool_line_selection_motion (GimpToolLine *line, + gboolean constrain) +{ + GimpToolLinePrivate *private = line->private; + gdouble x = private->mouse_x; + gdouble y = private->mouse_y; + + if (private->grab != GRAB_SELECTION) + return FALSE; + + switch (private->selection) + { + case GIMP_TOOL_LINE_HANDLE_NONE: + gimp_assert_not_reached (); + + case GIMP_TOOL_LINE_HANDLE_START: + if (constrain) + { + gimp_display_shell_constrain_line ( + gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line)), + private->x2, private->y2, + &x, &y, + GIMP_CONSTRAIN_LINE_15_DEGREES); + } + + g_object_set (line, + "x1", x, + "y1", y, + NULL); + return TRUE; + + case GIMP_TOOL_LINE_HANDLE_END: + if (constrain) + { + gimp_display_shell_constrain_line ( + gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line)), + private->x1, private->y1, + &x, &y, + GIMP_CONSTRAIN_LINE_15_DEGREES); + } + + g_object_set (line, + "x2", x, + "y2", y, + NULL); + return TRUE; + + default: + { + GimpDisplayShell *shell; + GimpControllerSlider *slider; + gdouble value; + gdouble dist; + gboolean remove_slider; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line)); + + slider = gimp_tool_line_get_slider (line, private->selection); + + /* project the cursor position onto the line */ + value = gimp_tool_line_project_point (line, x, y, constrain, &dist); + + /* slider dragging */ + if (slider->movable) + { + value = CLAMP (value, slider->min, slider->max); + value = CLAMP (value, 0.0, 1.0); + + value = fabs (value); /* avoid negative zero */ + + slider->value = value; + + g_object_set (line, + "sliders", private->sliders, + NULL); + } + + /* slider tearing */ + remove_slider = slider->removable && dist > SLIDER_TEAR_DISTANCE; + + if (remove_slider != private->remove_slider) + { + private->remove_slider = remove_slider; + + g_signal_emit (line, line_signals[PREPARE_TO_REMOVE_SLIDER], 0, + private->selection, remove_slider); + + /* set the cursor modifier to a minus by talking to the shell + * directly -- eek! + */ + { + GimpCursorType cursor; + GimpToolCursorType tool_cursor; + GimpCursorModifier modifier; + + cursor = shell->current_cursor; + tool_cursor = shell->tool_cursor; + modifier = GIMP_CURSOR_MODIFIER_NONE; + + gimp_tool_line_get_cursor (GIMP_TOOL_WIDGET (line), NULL, 0, + &cursor, &tool_cursor, &modifier); + + gimp_display_shell_set_cursor (shell, cursor, tool_cursor, modifier); + } + + gimp_tool_line_update_handles (line); + gimp_tool_line_update_circle (line); + gimp_tool_line_update_status (line, + constrain ? + gimp_get_constrain_behavior_mask () : + 0, + TRUE); + } + + return TRUE; + } + } +} + +static void +gimp_tool_line_update_handles (GimpToolLine *line) +{ + GimpToolLinePrivate *private = line->private; + gdouble value; + gdouble dist; + gint i; + + value = gimp_tool_line_project_point (line, + private->mouse_x, + private->mouse_y, + FALSE, + &dist); + + for (i = 0; i < private->sliders->len; i++) + { + const GimpControllerSlider *slider; + GimpCanvasItem *handle; + gint size; + gint hit_radius; + gboolean show_autohidden; + gboolean visible; + + slider = gimp_tool_line_get_slider (line, i); + handle = gimp_tool_line_get_handle (line, i); + + size = slider->size * SLIDER_HANDLE_SIZE; + size = MAX (size, 1); + + hit_radius = (MAX (size, SLIDER_HANDLE_SIZE) * HANDLE_CIRCLE_SCALE) / 2; + + /* show a autohidden slider if it's selected, or if no other handle is + * grabbed or hovered-over, and the cursor is close enough to the line, + * between the slider's min and max values. + */ + show_autohidden = private->selection == i || + (private->grab == GRAB_NONE && + (private->hover <= GIMP_TOOL_LINE_HANDLE_NONE || + private->hover == i) && + dist <= hit_radius && + value >= slider->min && + value <= slider->max); + + visible = slider->visible && + (! slider->autohide || show_autohidden) && + ! (private->selection == i && private->remove_slider); + + handle = gimp_tool_line_get_handle (line, i); + + if (visible) + { + g_object_set (handle, + "type", slider->type, + "width", size, + "height", size, + NULL); + } + + gimp_canvas_item_set_visible (handle, visible); + } +} + +static void +gimp_tool_line_update_circle (GimpToolLine *line) +{ + GimpToolLinePrivate *private = line->private; + gboolean visible; + + visible = (private->grab == GRAB_NONE && + private->hover != GIMP_TOOL_LINE_HANDLE_NONE) || + (private->grab == GRAB_SELECTION && + private->remove_slider); + + if (visible) + { + gdouble x; + gdouble y; + gint width; + gint height; + gboolean dashed; + + if (private->grab == GRAB_NONE && private->hover == HOVER_NEW_SLIDER) + { + /* new slider */ + x = private->x1 + + (private->x2 - private->x1) * private->new_slider_value; + y = private->y1 + + (private->y2 - private->y1) * private->new_slider_value; + + width = height = SLIDER_HANDLE_SIZE; + + dashed = TRUE; + } + else + { + GimpCanvasItem *handle; + + if (private->grab == GRAB_SELECTION) + { + /* tear slider */ + handle = gimp_tool_line_get_handle (line, private->selection); + dashed = TRUE; + } + else + { + /* hover over handle */ + handle = gimp_tool_line_get_handle (line, private->hover); + dashed = FALSE; + } + + gimp_canvas_handle_get_position (handle, &x, &y); + gimp_canvas_handle_get_size (handle, &width, &height); + } + + width = MAX (width, SLIDER_HANDLE_SIZE); + height = MAX (height, SLIDER_HANDLE_SIZE); + + width *= HANDLE_CIRCLE_SCALE; + height *= HANDLE_CIRCLE_SCALE; + + gimp_canvas_handle_set_position (private->handle_circle, x, y); + gimp_canvas_handle_set_size (private->handle_circle, width, height); + + g_object_set (private->handle_circle, + "type", dashed ? GIMP_HANDLE_DASHED_CIRCLE : + GIMP_HANDLE_CIRCLE, + NULL); + } + + gimp_canvas_item_set_visible (private->handle_circle, visible); +} + +static void +gimp_tool_line_update_hilight (GimpToolLine *line) +{ + GimpToolLinePrivate *private = line->private; + gboolean focus; + gint i; + + focus = gimp_tool_widget_get_focus (GIMP_TOOL_WIDGET (line)); + + for (i = GIMP_TOOL_LINE_HANDLE_NONE + 1; + i < (gint) private->sliders->len; + i++) + { + GimpCanvasItem *handle; + + handle = gimp_tool_line_get_handle (line, i); + + gimp_canvas_item_set_highlight (handle, focus && i == private->selection); + } +} + +static void +gimp_tool_line_update_status (GimpToolLine *line, + GdkModifierType state, + gboolean proximity) +{ + GimpToolLinePrivate *private = line->private; + + if (proximity) + { + GimpDisplayShell *shell; + const gchar *toggle_behavior_format = NULL; + const gchar *message = NULL; + gchar *line_status = NULL; + gchar *status; + gint handle; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (line)); + + if (private->grab == GRAB_SELECTION) + handle = private->selection; + else + handle = private->hover; + + if (handle == GIMP_TOOL_LINE_HANDLE_START || + handle == GIMP_TOOL_LINE_HANDLE_END) + { + line_status = gimp_display_shell_get_line_status (shell, + _("Click-Drag to move the endpoint"), + ". ", + private->x1, + private->y1, + private->x2, + private->y2); + toggle_behavior_format = _("%s for constrained angles"); + } + else if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (handle) || + handle == HOVER_NEW_SLIDER) + { + if (private->grab == GRAB_SELECTION && private->remove_slider) + { + message = _("Release to remove the slider"); + } + else + { + toggle_behavior_format = _("%s for constrained values"); + + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (handle)) + { + if (gimp_tool_line_get_slider (line, handle)->movable) + { + if (gimp_tool_line_get_slider (line, handle)->removable) + { + if (private->grab == GRAB_SELECTION) + { + message = _("Click-Drag to move the slider; " + "drag away to remove the slider"); + } + else + { + message = _("Click-Drag to move or remove the slider"); + } + } + else + { + message = _("Click-Drag to move the slider"); + } + } + else + { + toggle_behavior_format = NULL; + + if (gimp_tool_line_get_slider (line, handle)->removable) + { + if (private->grab == GRAB_SELECTION) + { + message = _("Click-Drag away to remove the slider"); + } + else + { + message = _("Click-Drag to remove the slider"); + } + } + else + { + message = NULL; + } + } + } + else + { + message = _("Click or Click-Drag to add a new slider"); + } + } + } + else if (state & GDK_MOD1_MASK) + { + message = _("Click-Drag to move the line"); + } + + status = + gimp_suggest_modifiers (message ? message : (line_status ? line_status : ""), + ((toggle_behavior_format ? + gimp_get_constrain_behavior_mask () : 0) | + (private->grab == GRAB_NONE ? + GDK_MOD1_MASK : 0)) & + ~state, + NULL, + toggle_behavior_format, + _("%s to move the whole line")); + + if (message || line_status) + { + gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (line), status); + } + else + { + line_status = gimp_display_shell_get_line_status (shell, + private->status_title, + ". ", + private->x1, + private->y1, + private->x2, + private->y2); + gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (line), + line_status, + private->x2 - private->x1, + ", ", + private->y2 - private->y1, + status); + } + + g_free (status); + if (line_status) + g_free (line_status); + } + else + { + gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (line), NULL); + } +} + +static gboolean +gimp_tool_line_handle_hit (GimpCanvasItem *handle, + gdouble x, + gdouble y, + gdouble *min_dist) +{ + gdouble handle_x; + gdouble handle_y; + gint handle_width; + gint handle_height; + gint radius; + gdouble dist; + + gimp_canvas_handle_get_position (handle, &handle_x, &handle_y); + gimp_canvas_handle_get_size (handle, &handle_width, &handle_height); + + handle_width = MAX (handle_width, SLIDER_HANDLE_SIZE); + handle_height = MAX (handle_height, SLIDER_HANDLE_SIZE); + + radius = ((gint) (handle_width * HANDLE_CIRCLE_SCALE)) / 2; + radius = MAX (radius, LINE_VICINITY); + + dist = gimp_canvas_item_transform_distance (handle, + x, y, handle_x, handle_y); + + if (dist <= radius && dist < *min_dist) + { + *min_dist = dist; + + return TRUE; + } + else + { + return FALSE; + } +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_line_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_LINE, + "shell", shell, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); +} + +void +gimp_tool_line_set_sliders (GimpToolLine *line, + const GimpControllerSlider *sliders, + gint n_sliders) +{ + GimpToolLinePrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_LINE (line)); + g_return_if_fail (n_sliders == 0 || (n_sliders > 0 && sliders != NULL)); + + private = line->private; + + if (GIMP_TOOL_LINE_HANDLE_IS_SLIDER (private->selection) && + private->sliders->len != n_sliders) + { + gimp_tool_line_set_selection (line, GIMP_TOOL_LINE_HANDLE_NONE); + } + + g_array_set_size (private->sliders, n_sliders); + + memcpy (private->sliders->data, sliders, + n_sliders * sizeof (GimpControllerSlider)); + + g_object_set (line, + "sliders", private->sliders, + NULL); +} + +const GimpControllerSlider * +gimp_tool_line_get_sliders (GimpToolLine *line, + gint *n_sliders) +{ + GimpToolLinePrivate *private; + + g_return_val_if_fail (GIMP_IS_TOOL_LINE (line), NULL); + + private = line->private; + + if (n_sliders) *n_sliders = private->sliders->len; + + return (const GimpControllerSlider *) private->sliders->data; +} + +void +gimp_tool_line_set_selection (GimpToolLine *line, + gint handle) +{ + GimpToolLinePrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_LINE (line)); + + private = line->private; + + g_return_if_fail (handle >= GIMP_TOOL_LINE_HANDLE_NONE && + handle < (gint) private->sliders->len); + + g_object_set (line, + "selection", handle, + NULL); +} + +gint +gimp_tool_line_get_selection (GimpToolLine *line) +{ + GimpToolLinePrivate *private; + + g_return_val_if_fail (GIMP_IS_TOOL_LINE (line), GIMP_TOOL_LINE_HANDLE_NONE); + + private = line->private; + + return private->selection; +} diff --git a/app/display/gimptoolline.h b/app/display/gimptoolline.h new file mode 100644 index 0000000..80d046d --- /dev/null +++ b/app/display/gimptoolline.h @@ -0,0 +1,99 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolline.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_LINE_H__ +#define __GIMP_TOOL_LINE_H__ + + +#include "gimptoolwidget.h" + + +/* in the context of GimpToolLine, "handle" is a collective term for either an + * endpoint or a slider. a handle value may be either a (nonnegative) slider + * index, or one of the values below: + */ +#define GIMP_TOOL_LINE_HANDLE_NONE (-3) +#define GIMP_TOOL_LINE_HANDLE_START (-2) +#define GIMP_TOOL_LINE_HANDLE_END (-1) + +#define GIMP_TOOL_LINE_HANDLE_IS_SLIDER(handle) ((handle) >= 0) + + +#define GIMP_TYPE_TOOL_LINE (gimp_tool_line_get_type ()) +#define GIMP_TOOL_LINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_LINE, GimpToolLine)) +#define GIMP_TOOL_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_LINE, GimpToolLineClass)) +#define GIMP_IS_TOOL_LINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_LINE)) +#define GIMP_IS_TOOL_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_LINE)) +#define GIMP_TOOL_LINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_LINE, GimpToolLineClass)) + + +typedef struct _GimpToolLine GimpToolLine; +typedef struct _GimpToolLinePrivate GimpToolLinePrivate; +typedef struct _GimpToolLineClass GimpToolLineClass; + +struct _GimpToolLine +{ + GimpToolWidget parent_instance; + + GimpToolLinePrivate *private; +}; + +struct _GimpToolLineClass +{ + GimpToolWidgetClass parent_class; + + /* signals */ + gboolean (* can_add_slider) (GimpToolLine *line, + gdouble value); + gint (* add_slider) (GimpToolLine *line, + gdouble value); + void (* prepare_to_remove_slider) (GimpToolLine *line, + gint slider, + gboolean remove); + void (* remove_slider) (GimpToolLine *line, + gint slider); + void (* selection_changed) (GimpToolLine *line); + gboolean (* handle_clicked) (GimpToolLine *line, + gint handle, + GdkModifierType state, + GimpButtonPressType press_type); +}; + + +GType gimp_tool_line_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_line_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + +void gimp_tool_line_set_sliders (GimpToolLine *line, + const GimpControllerSlider *sliders, + gint n_sliders); +const GimpControllerSlider * gimp_tool_line_get_sliders (GimpToolLine *line, + gint *n_sliders); + +void gimp_tool_line_set_selection (GimpToolLine *line, + gint handle); +gint gimp_tool_line_get_selection (GimpToolLine *line); + + +#endif /* __GIMP_TOOL_LINE_H__ */ diff --git a/app/display/gimptoolpath.c b/app/display/gimptoolpath.c new file mode 100644 index 0000000..9e2cf54 --- /dev/null +++ b/app/display/gimptoolpath.c @@ -0,0 +1,1904 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpath.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Vector tool + * Copyright (C) 2003 Simon Budig <simon@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpbase/gimpbase.h" + +#include "display-types.h" + +#include "vectors/gimpanchor.h" +#include "vectors/gimpbezierstroke.h" +#include "vectors/gimpvectors.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "tools/gimptools-utils.h" + +#include "gimpcanvashandle.h" +#include "gimpcanvasitem-utils.h" +#include "gimpcanvasline.h" +#include "gimpcanvaspath.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimptoolpath.h" + +#include "gimp-intl.h" + + +#define TOGGLE_MASK gimp_get_extend_selection_mask () +#define MOVE_MASK GDK_MOD1_MASK +#define INSDEL_MASK gimp_get_toggle_behavior_mask () + + +/* possible vector functions */ +typedef enum +{ + VECTORS_SELECT_VECTOR, + VECTORS_CREATE_VECTOR, + VECTORS_CREATE_STROKE, + VECTORS_ADD_ANCHOR, + VECTORS_MOVE_ANCHOR, + VECTORS_MOVE_ANCHORSET, + VECTORS_MOVE_HANDLE, + VECTORS_MOVE_CURVE, + VECTORS_MOVE_STROKE, + VECTORS_MOVE_VECTORS, + VECTORS_INSERT_ANCHOR, + VECTORS_DELETE_ANCHOR, + VECTORS_CONNECT_STROKES, + VECTORS_DELETE_SEGMENT, + VECTORS_CONVERT_EDGE, + VECTORS_FINISHED +} GimpVectorFunction; + +enum +{ + PROP_0, + PROP_VECTORS, + PROP_EDIT_MODE, + PROP_POLYGONAL +}; + +enum +{ + BEGIN_CHANGE, + END_CHANGE, + ACTIVATE, + LAST_SIGNAL +}; + +struct _GimpToolPathPrivate +{ + GimpVectors *vectors; /* the current Vector data */ + GimpVectorMode edit_mode; + gboolean polygonal; + + GimpVectorFunction function; /* function we're performing */ + GimpAnchorFeatureType restriction; /* movement restriction */ + gboolean modifier_lock; /* can we toggle the Shift key? */ + GdkModifierType saved_state; /* modifier state at button_press */ + gdouble last_x; /* last x coordinate */ + gdouble last_y; /* last y coordinate */ + gboolean undo_motion; /* we need a motion to have an undo */ + gboolean have_undo; /* did we push an undo at */ + /* ..._button_press? */ + + GimpAnchor *cur_anchor; /* the current Anchor */ + GimpAnchor *cur_anchor2; /* secondary Anchor (end on_curve) */ + GimpStroke *cur_stroke; /* the current Stroke */ + gdouble cur_position; /* the current Position on a segment */ + + gint sel_count; /* number of selected anchors */ + GimpAnchor *sel_anchor; /* currently selected anchor, NULL */ + /* if multiple anchors are selected */ + GimpStroke *sel_stroke; /* selected stroke */ + + GimpVectorMode saved_mode; /* used by modifier_key() */ + + GimpCanvasItem *path; + GList *items; +}; + + +/* local function prototypes */ + +static void gimp_tool_path_constructed (GObject *object); +static void gimp_tool_path_dispose (GObject *object); +static void gimp_tool_path_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_path_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_path_changed (GimpToolWidget *widget); +static gint gimp_tool_path_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_path_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_path_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_path_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_path_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static gboolean gimp_tool_path_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +static gboolean gimp_tool_path_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static GimpVectorFunction + gimp_tool_path_get_function (GimpToolPath *path, + const GimpCoords *coords, + GdkModifierType state); + +static void gimp_tool_path_update_status (GimpToolPath *path, + GdkModifierType state, + gboolean proximity); + +static void gimp_tool_path_begin_change (GimpToolPath *path, + const gchar *desc); +static void gimp_tool_path_end_change (GimpToolPath *path, + gboolean success); + +static void gimp_tool_path_vectors_visible (GimpVectors *vectors, + GimpToolPath *path); +static void gimp_tool_path_vectors_freeze (GimpVectors *vectors, + GimpToolPath *path); +static void gimp_tool_path_vectors_thaw (GimpVectors *vectors, + GimpToolPath *path); +static void gimp_tool_path_verify_state (GimpToolPath *path); + +static void gimp_tool_path_move_selected_anchors + (GimpToolPath *path, + gdouble x, + gdouble y); +static void gimp_tool_path_delete_selected_anchors + (GimpToolPath *path); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolPath, gimp_tool_path, GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_path_parent_class + +static guint path_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_tool_path_class_init (GimpToolPathClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_path_constructed; + object_class->dispose = gimp_tool_path_dispose; + object_class->set_property = gimp_tool_path_set_property; + object_class->get_property = gimp_tool_path_get_property; + + widget_class->changed = gimp_tool_path_changed; + widget_class->focus_changed = gimp_tool_path_changed; + widget_class->button_press = gimp_tool_path_button_press; + widget_class->button_release = gimp_tool_path_button_release; + widget_class->motion = gimp_tool_path_motion; + widget_class->hit = gimp_tool_path_hit; + widget_class->hover = gimp_tool_path_hover; + widget_class->key_press = gimp_tool_path_key_press; + widget_class->get_cursor = gimp_tool_path_get_cursor; + + path_signals[BEGIN_CHANGE] = + g_signal_new ("begin-change", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolPathClass, begin_change), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + path_signals[END_CHANGE] = + g_signal_new ("end-change", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolPathClass, end_change), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, + G_TYPE_BOOLEAN); + + path_signals[ACTIVATE] = + g_signal_new ("activate", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolPathClass, activate), + NULL, NULL, + g_cclosure_marshal_VOID__FLAGS, + G_TYPE_NONE, 1, + GDK_TYPE_MODIFIER_TYPE); + + g_object_class_install_property (object_class, PROP_VECTORS, + g_param_spec_object ("vectors", NULL, NULL, + GIMP_TYPE_VECTORS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_EDIT_MODE, + g_param_spec_enum ("edit-mode", + _("Edit Mode"), + NULL, + GIMP_TYPE_VECTOR_MODE, + GIMP_VECTOR_MODE_DESIGN, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_POLYGONAL, + g_param_spec_boolean ("polygonal", + _("Polygonal"), + _("Restrict editing to polygons"), + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_path_init (GimpToolPath *path) +{ + path->private = gimp_tool_path_get_instance_private (path); +} + +static void +gimp_tool_path_constructed (GObject *object) +{ + GimpToolPath *path = GIMP_TOOL_PATH (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolPathPrivate *private = path->private; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + private->path = gimp_tool_widget_add_path (widget, NULL); + + gimp_tool_path_changed (widget); +} + +static void +gimp_tool_path_dispose (GObject *object) +{ + GimpToolPath *path = GIMP_TOOL_PATH (object); + + gimp_tool_path_set_vectors (path, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_tool_path_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolPath *path = GIMP_TOOL_PATH (object); + GimpToolPathPrivate *private = path->private; + + switch (property_id) + { + case PROP_VECTORS: + gimp_tool_path_set_vectors (path, g_value_get_object (value)); + break; + case PROP_EDIT_MODE: + private->edit_mode = g_value_get_enum (value); + break; + case PROP_POLYGONAL: + private->polygonal = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_path_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolPath *path = GIMP_TOOL_PATH (object); + GimpToolPathPrivate *private = path->private; + + switch (property_id) + { + case PROP_VECTORS: + g_value_set_object (value, private->vectors); + break; + case PROP_EDIT_MODE: + g_value_set_enum (value, private->edit_mode); + break; + case PROP_POLYGONAL: + g_value_set_boolean (value, private->polygonal); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +item_remove_func (GimpCanvasItem *item, + GimpToolWidget *widget) +{ + gimp_tool_widget_remove_item (widget, item); +} + +static void +gimp_tool_path_changed (GimpToolWidget *widget) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + GimpVectors *vectors = private->vectors; + + if (private->items) + { + g_list_foreach (private->items, (GFunc) item_remove_func, widget); + g_list_free (private->items); + private->items = NULL; + } + + if (vectors && gimp_vectors_get_bezier (vectors)) + { + GimpStroke *cur_stroke; + + gimp_canvas_path_set (private->path, + gimp_vectors_get_bezier (vectors)); + gimp_canvas_item_set_visible (private->path, + ! gimp_item_get_visible (GIMP_ITEM (vectors))); + + for (cur_stroke = gimp_vectors_stroke_get_next (vectors, NULL); + cur_stroke; + cur_stroke = gimp_vectors_stroke_get_next (vectors, cur_stroke)) + { + GimpCanvasItem *item; + GArray *coords; + GList *draw_anchors; + GList *list; + + /* anchor handles */ + draw_anchors = gimp_stroke_get_draw_anchors (cur_stroke); + + for (list = draw_anchors; list; list = g_list_next (list)) + { + GimpAnchor *cur_anchor = GIMP_ANCHOR (list->data); + + if (cur_anchor->type == GIMP_ANCHOR_ANCHOR) + { + item = + gimp_tool_widget_add_handle (widget, + cur_anchor->selected ? + GIMP_HANDLE_CIRCLE : + GIMP_HANDLE_FILLED_CIRCLE, + cur_anchor->position.x, + cur_anchor->position.y, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_HANDLE_ANCHOR_CENTER); + + private->items = g_list_prepend (private->items, item); + } + } + + g_list_free (draw_anchors); + + if (private->sel_count <= 2) + { + /* the lines to the control handles */ + coords = gimp_stroke_get_draw_lines (cur_stroke); + + if (coords) + { + if (coords->len % 2 == 0) + { + gint i; + + for (i = 0; i < coords->len; i += 2) + { + item = gimp_tool_widget_add_line + (widget, + g_array_index (coords, GimpCoords, i).x, + g_array_index (coords, GimpCoords, i).y, + g_array_index (coords, GimpCoords, i + 1).x, + g_array_index (coords, GimpCoords, i + 1).y); + + if (gimp_tool_widget_get_focus (widget)) + gimp_canvas_item_set_highlight (item, TRUE); + + private->items = g_list_prepend (private->items, item); + } + } + + g_array_free (coords, TRUE); + } + + /* control handles */ + draw_anchors = gimp_stroke_get_draw_controls (cur_stroke); + + for (list = draw_anchors; list; list = g_list_next (list)) + { + GimpAnchor *cur_anchor = GIMP_ANCHOR (list->data); + + item = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + cur_anchor->position.x, + cur_anchor->position.y, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE - 3, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE - 3, + GIMP_HANDLE_ANCHOR_CENTER); + + private->items = g_list_prepend (private->items, item); + } + + g_list_free (draw_anchors); + } + } + } + else + { + gimp_canvas_path_set (private->path, NULL); + } +} + +static gboolean +gimp_tool_path_check_writable (GimpToolPath *path) +{ + GimpToolPathPrivate *private = path->private; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (path); + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + + if (gimp_item_is_content_locked (GIMP_ITEM (private->vectors)) || + gimp_item_is_position_locked (GIMP_ITEM (private->vectors))) + { + gimp_tool_widget_message_literal (GIMP_TOOL_WIDGET (path), + _("The active path is locked.")); + + /* FIXME: this should really be done by the tool */ + gimp_tools_blink_lock_box (shell->display->gimp, + GIMP_ITEM (private->vectors)); + + private->function = VECTORS_FINISHED; + + return FALSE; + } + + return TRUE; +} + +gboolean +gimp_tool_path_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + + /* do nothing if we are in a FINISHED state */ + if (private->function == VECTORS_FINISHED) + return 0; + + g_return_val_if_fail (private->vectors != NULL || + private->function == VECTORS_SELECT_VECTOR || + private->function == VECTORS_CREATE_VECTOR, 0); + + private->undo_motion = FALSE; + + /* save the current modifier state */ + + private->saved_state = state; + + + /* select a vectors object */ + + if (private->function == VECTORS_SELECT_VECTOR) + { + GimpVectors *vectors; + + if (gimp_canvas_item_on_vectors (private->path, + coords, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + NULL, NULL, NULL, NULL, NULL, &vectors)) + { + gimp_tool_path_set_vectors (path, vectors); + } + + private->function = VECTORS_FINISHED; + } + + + /* create a new vector from scratch */ + + if (private->function == VECTORS_CREATE_VECTOR) + { + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + GimpImage *image = gimp_display_get_image (shell->display); + GimpVectors *vectors; + + vectors = gimp_vectors_new (image, _("Unnamed")); + g_object_ref_sink (vectors); + + /* Undo step gets added implicitly */ + private->have_undo = TRUE; + + private->undo_motion = TRUE; + + gimp_tool_path_set_vectors (path, vectors); + g_object_unref (vectors); + + private->function = VECTORS_CREATE_STROKE; + } + + + gimp_vectors_freeze (private->vectors); + + /* create a new stroke */ + + if (private->function == VECTORS_CREATE_STROKE && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Add Stroke")); + private->undo_motion = TRUE; + + private->cur_stroke = gimp_bezier_stroke_new (); + gimp_vectors_stroke_add (private->vectors, private->cur_stroke); + g_object_unref (private->cur_stroke); + + private->sel_stroke = private->cur_stroke; + private->cur_anchor = NULL; + private->sel_anchor = NULL; + private->function = VECTORS_ADD_ANCHOR; + } + + + /* add an anchor to an existing stroke */ + + if (private->function == VECTORS_ADD_ANCHOR && + gimp_tool_path_check_writable (path)) + { + GimpCoords position = GIMP_COORDS_DEFAULT_VALUES; + + position.x = coords->x; + position.y = coords->y; + + gimp_tool_path_begin_change (path, _("Add Anchor")); + private->undo_motion = TRUE; + + private->cur_anchor = gimp_bezier_stroke_extend (private->sel_stroke, + &position, + private->sel_anchor, + EXTEND_EDITABLE); + + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + + if (! private->polygonal) + private->function = VECTORS_MOVE_HANDLE; + else + private->function = VECTORS_MOVE_ANCHOR; + + private->cur_stroke = private->sel_stroke; + } + + + /* insertion of an anchor in a curve segment */ + + if (private->function == VECTORS_INSERT_ANCHOR && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Insert Anchor")); + private->undo_motion = TRUE; + + private->cur_anchor = gimp_stroke_anchor_insert (private->cur_stroke, + private->cur_anchor, + private->cur_position); + if (private->cur_anchor) + { + if (private->polygonal) + { + gimp_stroke_anchor_convert (private->cur_stroke, + private->cur_anchor, + GIMP_ANCHOR_FEATURE_EDGE); + } + + private->function = VECTORS_MOVE_ANCHOR; + } + else + { + private->function = VECTORS_FINISHED; + } + } + + + /* move a handle */ + + if (private->function == VECTORS_MOVE_HANDLE && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Drag Handle")); + + if (private->cur_anchor->type == GIMP_ANCHOR_ANCHOR) + { + if (! private->cur_anchor->selected) + { + gimp_vectors_anchor_select (private->vectors, + private->cur_stroke, + private->cur_anchor, + TRUE, TRUE); + private->undo_motion = TRUE; + } + + gimp_canvas_item_on_vectors_handle (private->path, + private->vectors, coords, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_ANCHOR_CONTROL, TRUE, + &private->cur_anchor, + &private->cur_stroke); + if (! private->cur_anchor) + private->function = VECTORS_FINISHED; + } + } + + + /* move an anchor */ + + if (private->function == VECTORS_MOVE_ANCHOR && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Drag Anchor")); + + if (! private->cur_anchor->selected) + { + gimp_vectors_anchor_select (private->vectors, + private->cur_stroke, + private->cur_anchor, + TRUE, TRUE); + private->undo_motion = TRUE; + } + } + + + /* move multiple anchors */ + + if (private->function == VECTORS_MOVE_ANCHORSET && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Drag Anchors")); + + if (state & TOGGLE_MASK) + { + gimp_vectors_anchor_select (private->vectors, + private->cur_stroke, + private->cur_anchor, + !private->cur_anchor->selected, + FALSE); + private->undo_motion = TRUE; + + if (private->cur_anchor->selected == FALSE) + private->function = VECTORS_FINISHED; + } + } + + + /* move a curve segment directly */ + + if (private->function == VECTORS_MOVE_CURVE && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Drag Curve")); + + /* the magic numbers are taken from the "feel good" parameter + * from gimp_bezier_stroke_point_move_relative in gimpbezierstroke.c. */ + if (private->cur_position < 5.0 / 6.0) + { + gimp_vectors_anchor_select (private->vectors, + private->cur_stroke, + private->cur_anchor, TRUE, TRUE); + private->undo_motion = TRUE; + } + + if (private->cur_position > 1.0 / 6.0) + { + gimp_vectors_anchor_select (private->vectors, + private->cur_stroke, + private->cur_anchor2, TRUE, + (private->cur_position >= 5.0 / 6.0)); + private->undo_motion = TRUE; + } + + } + + + /* connect two strokes */ + + if (private->function == VECTORS_CONNECT_STROKES && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Connect Strokes")); + private->undo_motion = TRUE; + + gimp_stroke_connect_stroke (private->sel_stroke, + private->sel_anchor, + private->cur_stroke, + private->cur_anchor); + + if (private->cur_stroke != private->sel_stroke && + gimp_stroke_is_empty (private->cur_stroke)) + { + gimp_vectors_stroke_remove (private->vectors, + private->cur_stroke); + } + + private->sel_anchor = private->cur_anchor; + private->cur_stroke = private->sel_stroke; + + gimp_vectors_anchor_select (private->vectors, + private->sel_stroke, + private->sel_anchor, TRUE, TRUE); + + private->function = VECTORS_FINISHED; + } + + + /* move a stroke or all strokes of a vectors object */ + + if ((private->function == VECTORS_MOVE_STROKE || + private->function == VECTORS_MOVE_VECTORS) && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Drag Path")); + + /* Work is being done in gimp_tool_path_motion()... */ + } + + + /* convert an anchor to something that looks like an edge */ + + if (private->function == VECTORS_CONVERT_EDGE && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Convert Edge")); + private->undo_motion = TRUE; + + gimp_stroke_anchor_convert (private->cur_stroke, + private->cur_anchor, + GIMP_ANCHOR_FEATURE_EDGE); + + if (private->cur_anchor->type == GIMP_ANCHOR_ANCHOR) + { + gimp_vectors_anchor_select (private->vectors, + private->cur_stroke, + private->cur_anchor, TRUE, TRUE); + + private->function = VECTORS_MOVE_ANCHOR; + } + else + { + private->cur_stroke = NULL; + private->cur_anchor = NULL; + + /* avoid doing anything stupid */ + private->function = VECTORS_FINISHED; + } + } + + + /* removal of a node in a stroke */ + + if (private->function == VECTORS_DELETE_ANCHOR && + gimp_tool_path_check_writable (path)) + { + gimp_tool_path_begin_change (path, _("Delete Anchor")); + private->undo_motion = TRUE; + + gimp_stroke_anchor_delete (private->cur_stroke, + private->cur_anchor); + + if (gimp_stroke_is_empty (private->cur_stroke)) + gimp_vectors_stroke_remove (private->vectors, + private->cur_stroke); + + private->cur_stroke = NULL; + private->cur_anchor = NULL; + private->function = VECTORS_FINISHED; + } + + + /* deleting a segment (opening up a stroke) */ + + if (private->function == VECTORS_DELETE_SEGMENT && + gimp_tool_path_check_writable (path)) + { + GimpStroke *new_stroke; + + gimp_tool_path_begin_change (path, _("Delete Segment")); + private->undo_motion = TRUE; + + new_stroke = gimp_stroke_open (private->cur_stroke, + private->cur_anchor); + if (new_stroke) + { + gimp_vectors_stroke_add (private->vectors, new_stroke); + g_object_unref (new_stroke); + } + + private->cur_stroke = NULL; + private->cur_anchor = NULL; + private->function = VECTORS_FINISHED; + } + + private->last_x = coords->x; + private->last_y = coords->y; + + gimp_vectors_thaw (private->vectors); + + return 1; +} + +void +gimp_tool_path_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + + private->function = VECTORS_FINISHED; + + if (private->have_undo) + { + if (! private->undo_motion || + release_type == GIMP_BUTTON_RELEASE_CANCEL) + { + gimp_tool_path_end_change (path, FALSE); + } + else + { + gimp_tool_path_end_change (path, TRUE); + } + } +} + +void +gimp_tool_path_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + GimpCoords position = GIMP_COORDS_DEFAULT_VALUES; + GimpAnchor *anchor; + + if (private->function == VECTORS_FINISHED) + return; + + position.x = coords->x; + position.y = coords->y; + + gimp_vectors_freeze (private->vectors); + + if ((private->saved_state & TOGGLE_MASK) != (state & TOGGLE_MASK)) + private->modifier_lock = FALSE; + + if (! private->modifier_lock) + { + if (state & TOGGLE_MASK) + { + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + } + else + { + private->restriction = GIMP_ANCHOR_FEATURE_NONE; + } + } + + switch (private->function) + { + case VECTORS_MOVE_ANCHOR: + case VECTORS_MOVE_HANDLE: + anchor = private->cur_anchor; + + if (anchor) + { + gimp_stroke_anchor_move_absolute (private->cur_stroke, + private->cur_anchor, + &position, + private->restriction); + private->undo_motion = TRUE; + } + break; + + case VECTORS_MOVE_CURVE: + if (private->polygonal) + { + gimp_tool_path_move_selected_anchors (path, + coords->x - private->last_x, + coords->y - private->last_y); + private->undo_motion = TRUE; + } + else + { + gimp_stroke_point_move_absolute (private->cur_stroke, + private->cur_anchor, + private->cur_position, + &position, + private->restriction); + private->undo_motion = TRUE; + } + break; + + case VECTORS_MOVE_ANCHORSET: + gimp_tool_path_move_selected_anchors (path, + coords->x - private->last_x, + coords->y - private->last_y); + private->undo_motion = TRUE; + break; + + case VECTORS_MOVE_STROKE: + if (private->cur_stroke) + { + gimp_stroke_translate (private->cur_stroke, + coords->x - private->last_x, + coords->y - private->last_y); + private->undo_motion = TRUE; + } + else if (private->sel_stroke) + { + gimp_stroke_translate (private->sel_stroke, + coords->x - private->last_x, + coords->y - private->last_y); + private->undo_motion = TRUE; + } + break; + + case VECTORS_MOVE_VECTORS: + gimp_item_translate (GIMP_ITEM (private->vectors), + coords->x - private->last_x, + coords->y - private->last_y, FALSE); + private->undo_motion = TRUE; + break; + + default: + break; + } + + gimp_vectors_thaw (private->vectors); + + private->last_x = coords->x; + private->last_y = coords->y; +} + +GimpHit +gimp_tool_path_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + + switch (gimp_tool_path_get_function (path, coords, state)) + { + case VECTORS_SELECT_VECTOR: + case VECTORS_MOVE_ANCHOR: + case VECTORS_MOVE_ANCHORSET: + case VECTORS_MOVE_HANDLE: + case VECTORS_MOVE_CURVE: + case VECTORS_MOVE_STROKE: + case VECTORS_DELETE_ANCHOR: + case VECTORS_DELETE_SEGMENT: + case VECTORS_INSERT_ANCHOR: + case VECTORS_CONNECT_STROKES: + case VECTORS_CONVERT_EDGE: + return GIMP_HIT_DIRECT; + + case VECTORS_CREATE_VECTOR: + case VECTORS_CREATE_STROKE: + case VECTORS_ADD_ANCHOR: + case VECTORS_MOVE_VECTORS: + return GIMP_HIT_INDIRECT; + + case VECTORS_FINISHED: + return GIMP_HIT_NONE; + } + + return GIMP_HIT_NONE; +} + +void +gimp_tool_path_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + + private->function = gimp_tool_path_get_function (path, coords, state); + + gimp_tool_path_update_status (path, state, proximity); +} + +static gboolean +gimp_tool_path_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + GimpDisplayShell *shell; + gdouble xdist, ydist; + gdouble pixels = 1.0; + + if (! private->vectors) + return FALSE; + + shell = gimp_tool_widget_get_shell (widget); + + if (kevent->state & gimp_get_extend_selection_mask ()) + pixels = 10.0; + + if (kevent->state & gimp_get_toggle_behavior_mask ()) + pixels = 50.0; + + switch (kevent->keyval) + { + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + g_signal_emit (path, path_signals[ACTIVATE], 0, + kevent->state); + break; + + case GDK_KEY_BackSpace: + case GDK_KEY_Delete: + gimp_tool_path_delete_selected_anchors (path); + break; + + case GDK_KEY_Left: + case GDK_KEY_Right: + case GDK_KEY_Up: + case GDK_KEY_Down: + xdist = FUNSCALEX (shell, pixels); + ydist = FUNSCALEY (shell, pixels); + + gimp_tool_path_begin_change (path, _("Move Anchors")); + gimp_vectors_freeze (private->vectors); + + switch (kevent->keyval) + { + case GDK_KEY_Left: + gimp_tool_path_move_selected_anchors (path, -xdist, 0); + break; + + case GDK_KEY_Right: + gimp_tool_path_move_selected_anchors (path, xdist, 0); + break; + + case GDK_KEY_Up: + gimp_tool_path_move_selected_anchors (path, 0, -ydist); + break; + + case GDK_KEY_Down: + gimp_tool_path_move_selected_anchors (path, 0, ydist); + break; + + default: + break; + } + + gimp_vectors_thaw (private->vectors); + gimp_tool_path_end_change (path, TRUE); + break; + + case GDK_KEY_Escape: + if (private->edit_mode != GIMP_VECTOR_MODE_DESIGN) + g_object_set (private, + "vectors-edit-mode", GIMP_VECTOR_MODE_DESIGN, + NULL); + break; + + default: + return FALSE; + } + + return TRUE; +} + +static gboolean +gimp_tool_path_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + + *tool_cursor = GIMP_TOOL_CURSOR_PATHS; + *modifier = GIMP_CURSOR_MODIFIER_NONE; + + switch (private->function) + { + case VECTORS_SELECT_VECTOR: + *tool_cursor = GIMP_TOOL_CURSOR_HAND; + break; + + case VECTORS_CREATE_VECTOR: + case VECTORS_CREATE_STROKE: + *modifier = GIMP_CURSOR_MODIFIER_CONTROL; + break; + + case VECTORS_ADD_ANCHOR: + case VECTORS_INSERT_ANCHOR: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_PLUS; + break; + + case VECTORS_DELETE_ANCHOR: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + break; + + case VECTORS_DELETE_SEGMENT: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT; + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + break; + + case VECTORS_MOVE_HANDLE: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_CONTROL; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_CONVERT_EDGE: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_CONTROL; + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + break; + + case VECTORS_MOVE_ANCHOR: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_MOVE_CURVE: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_MOVE_STROKE: + case VECTORS_MOVE_VECTORS: + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_MOVE_ANCHORSET: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_CONNECT_STROKES: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT; + *modifier = GIMP_CURSOR_MODIFIER_JOIN; + break; + + default: + *modifier = GIMP_CURSOR_MODIFIER_BAD; + break; + } + + return TRUE; +} + +static GimpVectorFunction +gimp_tool_path_get_function (GimpToolPath *path, + const GimpCoords *coords, + GdkModifierType state) +{ + GimpToolPathPrivate *private = path->private; + GimpAnchor *anchor = NULL; + GimpAnchor *anchor2 = NULL; + GimpStroke *stroke = NULL; + gdouble position = -1; + gboolean on_handle = FALSE; + gboolean on_curve = FALSE; + gboolean on_vectors = FALSE; + GimpVectorFunction function = VECTORS_FINISHED; + + private->modifier_lock = FALSE; + + /* are we hovering the current vectors on the current display? */ + if (private->vectors) + { + on_handle = gimp_canvas_item_on_vectors_handle (private->path, + private->vectors, + coords, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_ANCHOR_ANCHOR, + private->sel_count > 2, + &anchor, &stroke); + + if (! on_handle) + on_curve = gimp_canvas_item_on_vectors_curve (private->path, + private->vectors, + coords, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + NULL, + &position, &anchor, + &anchor2, &stroke); + } + + if (! on_handle && ! on_curve) + { + on_vectors = gimp_canvas_item_on_vectors (private->path, + coords, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + NULL, NULL, NULL, NULL, NULL, + NULL); + } + + private->cur_position = position; + private->cur_anchor = anchor; + private->cur_anchor2 = anchor2; + private->cur_stroke = stroke; + + switch (private->edit_mode) + { + case GIMP_VECTOR_MODE_DESIGN: + if (! private->vectors) + { + if (on_vectors) + { + function = VECTORS_SELECT_VECTOR; + } + else + { + function = VECTORS_CREATE_VECTOR; + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + private->modifier_lock = TRUE; + } + } + else if (on_handle) + { + if (anchor->type == GIMP_ANCHOR_ANCHOR) + { + if (state & TOGGLE_MASK) + { + function = VECTORS_MOVE_ANCHORSET; + } + else + { + if (private->sel_count >= 2 && anchor->selected) + function = VECTORS_MOVE_ANCHORSET; + else + function = VECTORS_MOVE_ANCHOR; + } + } + else + { + function = VECTORS_MOVE_HANDLE; + + if (state & TOGGLE_MASK) + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + else + private->restriction = GIMP_ANCHOR_FEATURE_NONE; + } + } + else if (on_curve) + { + if (gimp_stroke_point_is_movable (stroke, anchor, position)) + { + function = VECTORS_MOVE_CURVE; + + if (state & TOGGLE_MASK) + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + else + private->restriction = GIMP_ANCHOR_FEATURE_NONE; + } + else + { + function = VECTORS_FINISHED; + } + } + else + { + if (private->sel_stroke && + private->sel_anchor && + gimp_stroke_is_extendable (private->sel_stroke, + private->sel_anchor) && + ! (state & TOGGLE_MASK)) + function = VECTORS_ADD_ANCHOR; + else + function = VECTORS_CREATE_STROKE; + + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + private->modifier_lock = TRUE; + } + + break; + + case GIMP_VECTOR_MODE_EDIT: + if (! private->vectors) + { + if (on_vectors) + { + function = VECTORS_SELECT_VECTOR; + } + else + { + function = VECTORS_FINISHED; + } + } + else if (on_handle) + { + if (anchor->type == GIMP_ANCHOR_ANCHOR) + { + if (! (state & TOGGLE_MASK) && + private->sel_anchor && + private->sel_anchor != anchor && + gimp_stroke_is_extendable (private->sel_stroke, + private->sel_anchor) && + gimp_stroke_is_extendable (stroke, anchor)) + { + function = VECTORS_CONNECT_STROKES; + } + else + { + if (state & TOGGLE_MASK) + { + function = VECTORS_DELETE_ANCHOR; + } + else + { + if (private->polygonal) + function = VECTORS_MOVE_ANCHOR; + else + function = VECTORS_MOVE_HANDLE; + } + } + } + else + { + if (state & TOGGLE_MASK) + function = VECTORS_CONVERT_EDGE; + else + function = VECTORS_MOVE_HANDLE; + } + } + else if (on_curve) + { + if (state & TOGGLE_MASK) + { + function = VECTORS_DELETE_SEGMENT; + } + else if (gimp_stroke_anchor_is_insertable (stroke, anchor, position)) + { + function = VECTORS_INSERT_ANCHOR; + } + else + { + function = VECTORS_FINISHED; + } + } + else + { + function = VECTORS_FINISHED; + } + + break; + + case GIMP_VECTOR_MODE_MOVE: + if (! private->vectors) + { + if (on_vectors) + { + function = VECTORS_SELECT_VECTOR; + } + else + { + function = VECTORS_FINISHED; + } + } + else if (on_handle || on_curve) + { + if (state & TOGGLE_MASK) + { + function = VECTORS_MOVE_VECTORS; + } + else + { + function = VECTORS_MOVE_STROKE; + } + } + else + { + if (on_vectors) + { + function = VECTORS_SELECT_VECTOR; + } + else + { + function = VECTORS_MOVE_VECTORS; + } + } + break; + } + + return function; +} + +static void +gimp_tool_path_update_status (GimpToolPath *path, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPathPrivate *private = path->private; + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask (); + const gchar *status = NULL; + gboolean free_status = FALSE; + + if (! proximity) + { + gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (path), NULL); + return; + } + + switch (private->function) + { + case VECTORS_SELECT_VECTOR: + status = _("Click to pick path to edit"); + break; + + case VECTORS_CREATE_VECTOR: + status = _("Click to create a new path"); + break; + + case VECTORS_CREATE_STROKE: + status = _("Click to create a new component of the path"); + break; + + case VECTORS_ADD_ANCHOR: + status = gimp_suggest_modifiers (_("Click or Click-Drag to create " + "a new anchor"), + extend_mask & ~state, + NULL, NULL, NULL); + free_status = TRUE; + break; + + case VECTORS_MOVE_ANCHOR: + if (private->edit_mode != GIMP_VECTOR_MODE_EDIT) + { + status = gimp_suggest_modifiers (_("Click-Drag to move the " + "anchor around"), + toggle_mask & ~state, + NULL, NULL, NULL); + free_status = TRUE; + } + else + status = _("Click-Drag to move the anchor around"); + break; + + case VECTORS_MOVE_ANCHORSET: + status = _("Click-Drag to move the anchors around"); + break; + + case VECTORS_MOVE_HANDLE: + if (private->restriction != GIMP_ANCHOR_FEATURE_SYMMETRIC) + { + status = gimp_suggest_modifiers (_("Click-Drag to move the " + "handle around"), + extend_mask & ~state, + NULL, NULL, NULL); + } + else + { + status = gimp_suggest_modifiers (_("Click-Drag to move the " + "handles around symmetrically"), + extend_mask & ~state, + NULL, NULL, NULL); + } + free_status = TRUE; + break; + + case VECTORS_MOVE_CURVE: + if (private->polygonal) + status = gimp_suggest_modifiers (_("Click-Drag to move the " + "anchors around"), + extend_mask & ~state, + NULL, NULL, NULL); + else + status = gimp_suggest_modifiers (_("Click-Drag to change the " + "shape of the curve"), + extend_mask & ~state, + _("%s: symmetrical"), NULL, NULL); + free_status = TRUE; + break; + + case VECTORS_MOVE_STROKE: + status = gimp_suggest_modifiers (_("Click-Drag to move the " + "component around"), + extend_mask & ~state, + NULL, NULL, NULL); + free_status = TRUE; + break; + + case VECTORS_MOVE_VECTORS: + status = _("Click-Drag to move the path around"); + break; + + case VECTORS_INSERT_ANCHOR: + status = gimp_suggest_modifiers (_("Click-Drag to insert an anchor " + "on the path"), + extend_mask & ~state, + NULL, NULL, NULL); + free_status = TRUE; + break; + + case VECTORS_DELETE_ANCHOR: + status = _("Click to delete this anchor"); + break; + + case VECTORS_CONNECT_STROKES: + status = _("Click to connect this anchor " + "with the selected endpoint"); + break; + + case VECTORS_DELETE_SEGMENT: + status = _("Click to open up the path"); + break; + + case VECTORS_CONVERT_EDGE: + status = _("Click to make this node angular"); + break; + + case VECTORS_FINISHED: + status = _("Clicking here does nothing, try clicking on path elements."); + break; + } + + gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (path), status); + + if (free_status) + g_free ((gchar *) status); +} + +static void +gimp_tool_path_begin_change (GimpToolPath *path, + const gchar *desc) +{ + GimpToolPathPrivate *private = path->private; + + g_return_if_fail (private->vectors != NULL); + + /* don't push two undos */ + if (private->have_undo) + return; + + g_signal_emit (path, path_signals[BEGIN_CHANGE], 0, + desc); + + private->have_undo = TRUE; +} + +static void +gimp_tool_path_end_change (GimpToolPath *path, + gboolean success) +{ + GimpToolPathPrivate *private = path->private; + + private->have_undo = FALSE; + private->undo_motion = FALSE; + + g_signal_emit (path, path_signals[END_CHANGE], 0, + success); +} + +static void +gimp_tool_path_vectors_visible (GimpVectors *vectors, + GimpToolPath *path) +{ + GimpToolPathPrivate *private = path->private; + + gimp_canvas_item_set_visible (private->path, + ! gimp_item_get_visible (GIMP_ITEM (vectors))); +} + +static void +gimp_tool_path_vectors_freeze (GimpVectors *vectors, + GimpToolPath *path) +{ +} + +static void +gimp_tool_path_vectors_thaw (GimpVectors *vectors, + GimpToolPath *path) +{ + /* Ok, the vector might have changed externally (e.g. Undo) we need + * to validate our internal state. + */ + gimp_tool_path_verify_state (path); + gimp_tool_path_changed (GIMP_TOOL_WIDGET (path)); +} + +static void +gimp_tool_path_verify_state (GimpToolPath *path) +{ + GimpToolPathPrivate *private = path->private; + GimpStroke *cur_stroke = NULL; + gboolean cur_anchor_valid = FALSE; + gboolean cur_stroke_valid = FALSE; + + private->sel_count = 0; + private->sel_anchor = NULL; + private->sel_stroke = NULL; + + if (! private->vectors) + { + private->cur_position = -1; + private->cur_anchor = NULL; + private->cur_stroke = NULL; + return; + } + + while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors, + cur_stroke))) + { + GList *anchors; + GList *list; + + /* anchor handles */ + anchors = gimp_stroke_get_draw_anchors (cur_stroke); + + if (cur_stroke == private->cur_stroke) + cur_stroke_valid = TRUE; + + for (list = anchors; list; list = g_list_next (list)) + { + GimpAnchor *cur_anchor = list->data; + + if (cur_anchor == private->cur_anchor) + cur_anchor_valid = TRUE; + + if (cur_anchor->type == GIMP_ANCHOR_ANCHOR && + cur_anchor->selected) + { + private->sel_count++; + if (private->sel_count == 1) + { + private->sel_anchor = cur_anchor; + private->sel_stroke = cur_stroke; + } + else + { + private->sel_anchor = NULL; + private->sel_stroke = NULL; + } + } + } + + g_list_free (anchors); + + anchors = gimp_stroke_get_draw_controls (cur_stroke); + + for (list = anchors; list; list = g_list_next (list)) + { + GimpAnchor *cur_anchor = list->data; + + if (cur_anchor == private->cur_anchor) + cur_anchor_valid = TRUE; + } + + g_list_free (anchors); + } + + if (! cur_stroke_valid) + private->cur_stroke = NULL; + + if (! cur_anchor_valid) + private->cur_anchor = NULL; +} + +static void +gimp_tool_path_move_selected_anchors (GimpToolPath *path, + gdouble x, + gdouble y) +{ + GimpToolPathPrivate *private = path->private; + GimpAnchor *cur_anchor; + GimpStroke *cur_stroke = NULL; + GList *anchors; + GList *list; + GimpCoords offset = { 0.0, }; + + offset.x = x; + offset.y = y; + + while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors, + cur_stroke))) + { + /* anchors */ + anchors = gimp_stroke_get_draw_anchors (cur_stroke); + + for (list = anchors; list; list = g_list_next (list)) + { + cur_anchor = GIMP_ANCHOR (list->data); + + if (cur_anchor->selected) + gimp_stroke_anchor_move_relative (cur_stroke, + cur_anchor, + &offset, + GIMP_ANCHOR_FEATURE_NONE); + } + + g_list_free (anchors); + } +} + +static void +gimp_tool_path_delete_selected_anchors (GimpToolPath *path) +{ + GimpToolPathPrivate *private = path->private; + GimpAnchor *cur_anchor; + GimpStroke *cur_stroke = NULL; + GList *anchors; + GList *list; + gboolean have_undo = FALSE; + + gimp_vectors_freeze (private->vectors); + + while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors, + cur_stroke))) + { + /* anchors */ + anchors = gimp_stroke_get_draw_anchors (cur_stroke); + + for (list = anchors; list; list = g_list_next (list)) + { + cur_anchor = GIMP_ANCHOR (list->data); + + if (cur_anchor->selected) + { + if (! have_undo) + { + gimp_tool_path_begin_change (path, _("Delete Anchors")); + have_undo = TRUE; + } + + gimp_stroke_anchor_delete (cur_stroke, cur_anchor); + + if (gimp_stroke_is_empty (cur_stroke)) + { + gimp_vectors_stroke_remove (private->vectors, cur_stroke); + cur_stroke = NULL; + } + } + } + + g_list_free (anchors); + } + + if (have_undo) + gimp_tool_path_end_change (path, TRUE); + + gimp_vectors_thaw (private->vectors); +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_path_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_PATH, + "shell", shell, + NULL); +} + +void +gimp_tool_path_set_vectors (GimpToolPath *path, + GimpVectors *vectors) +{ + GimpToolPathPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_PATH (path)); + g_return_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors)); + + private = path->private; + + if (vectors == private->vectors) + return; + + if (private->vectors) + { + g_signal_handlers_disconnect_by_func (private->vectors, + gimp_tool_path_vectors_visible, + path); + g_signal_handlers_disconnect_by_func (private->vectors, + gimp_tool_path_vectors_freeze, + path); + g_signal_handlers_disconnect_by_func (private->vectors, + gimp_tool_path_vectors_thaw, + path); + + g_object_unref (private->vectors); + } + + private->vectors = vectors; + private->function = VECTORS_FINISHED; + gimp_tool_path_verify_state (path); + + if (private->vectors) + { + g_object_ref (private->vectors); + + g_signal_connect_object (private->vectors, "visibility-changed", + G_CALLBACK (gimp_tool_path_vectors_visible), + path, 0); + g_signal_connect_object (private->vectors, "freeze", + G_CALLBACK (gimp_tool_path_vectors_freeze), + path, 0); + g_signal_connect_object (private->vectors, "thaw", + G_CALLBACK (gimp_tool_path_vectors_thaw), + path, 0); + } + + g_object_notify (G_OBJECT (path), "vectors"); +} diff --git a/app/display/gimptoolpath.h b/app/display/gimptoolpath.h new file mode 100644 index 0000000..861d5e8 --- /dev/null +++ b/app/display/gimptoolpath.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpath.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_PATH_H__ +#define __GIMP_TOOL_PATH_H__ + + +#include "gimptoolwidget.h" + + +#define GIMP_TYPE_TOOL_PATH (gimp_tool_path_get_type ()) +#define GIMP_TOOL_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PATH, GimpToolPath)) +#define GIMP_TOOL_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PATH, GimpToolPathClass)) +#define GIMP_IS_TOOL_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PATH)) +#define GIMP_IS_TOOL_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PATH)) +#define GIMP_TOOL_PATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PATH, GimpToolPathClass)) + + +typedef struct _GimpToolPath GimpToolPath; +typedef struct _GimpToolPathPrivate GimpToolPathPrivate; +typedef struct _GimpToolPathClass GimpToolPathClass; + +struct _GimpToolPath +{ + GimpToolWidget parent_instance; + + GimpToolPathPrivate *private; +}; + +struct _GimpToolPathClass +{ + GimpToolWidgetClass parent_class; + + void (* begin_change) (GimpToolPath *path, + const gchar *desc); + void (* end_change) (GimpToolPath *path, + gboolean success); + void (* activate) (GimpToolPath *path, + GdkModifierType state); +}; + + +GType gimp_tool_path_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_path_new (GimpDisplayShell *shell); + +void gimp_tool_path_set_vectors (GimpToolPath *path, + GimpVectors *vectors); + + +#endif /* __GIMP_TOOL_PATH_H__ */ diff --git a/app/display/gimptoolpolygon.c b/app/display/gimptoolpolygon.c new file mode 100644 index 0000000..4f2c20b --- /dev/null +++ b/app/display/gimptoolpolygon.c @@ -0,0 +1,1495 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpolygon.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Based on GimpFreeSelectTool + * + * Major improvement to support polygonal segments + * Copyright (C) 2008 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-utils.h" +#include "core/gimpmarshal.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvashandle.h" +#include "gimpcanvasline.h" +#include "gimpcanvaspolygon.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-utils.h" +#include "gimptoolpolygon.h" + +#include "gimp-intl.h" + + +#define POINT_GRAB_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE / 2) +#define POINT_SHOW_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE * 7) +#define N_ITEMS_PER_ALLOC 1024 +#define INVALID_INDEX (-1) +#define NO_CLICK_TIME_AVAILABLE 0 + + +enum +{ + CHANGE_COMPLETE, + LAST_SIGNAL +}; + + +struct _GimpToolPolygonPrivate +{ + /* Index of grabbed segment index. */ + gint grabbed_segment_index; + + /* We need to keep track of a number of points when we move a + * segment vertex + */ + GimpVector2 *saved_points_lower_segment; + GimpVector2 *saved_points_higher_segment; + gint max_n_saved_points_lower_segment; + gint max_n_saved_points_higher_segment; + + /* Keeps track whether or not a modification of the polygon has been + * made between _button_press and _button_release + */ + gboolean polygon_modified; + + /* Point which is used to draw the polygon but which is not part of + * it yet + */ + GimpVector2 pending_point; + gboolean show_pending_point; + + /* The points of the polygon */ + GimpVector2 *points; + gint max_n_points; + + /* The number of points actually in use */ + gint n_points; + + + /* Any int array containing the indices for the points in the + * polygon that connects different segments together + */ + gint *segment_indices; + gint max_n_segment_indices; + + /* The number of segment indices actually in use */ + gint n_segment_indices; + + /* Is the polygon closed? */ + gboolean polygon_closed; + + /* Whether or not to constrain the angle for newly created polygonal + * segments. + */ + gboolean constrain_angle; + + /* Whether or not to suppress handles (so that new segments can be + * created immediately after an existing segment vertex. + */ + gboolean suppress_handles; + + /* Last _oper_update or _motion coords */ + gboolean hover; + GimpVector2 last_coords; + + /* A double-click commits the selection, keep track of last + * click-time and click-position. + */ + guint32 last_click_time; + GimpCoords last_click_coord; + + /* Are we in a click-drag-release? */ + gboolean button_down; + + GimpCanvasItem *polygon; + GimpCanvasItem *pending_line; + GimpCanvasItem *closing_line; + GPtrArray *handles; +}; + + +/* local function prototypes */ + +static void gimp_tool_polygon_constructed (GObject *object); +static void gimp_tool_polygon_finalize (GObject *object); +static void gimp_tool_polygon_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_polygon_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_polygon_changed (GimpToolWidget *widget); +static gint gimp_tool_polygon_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_polygon_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_polygon_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_polygon_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_polygon_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_polygon_leave_notify (GimpToolWidget *widget); +static gboolean gimp_tool_polygon_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +static void gimp_tool_polygon_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static void gimp_tool_polygon_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_polygon_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static void gimp_tool_polygon_change_complete (GimpToolPolygon *polygon); + +static gint gimp_tool_polygon_get_segment_index (GimpToolPolygon *polygon, + const GimpCoords *coords); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolPolygon, gimp_tool_polygon, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_polygon_parent_class + +static guint polygon_signals[LAST_SIGNAL] = { 0, }; + +static const GimpVector2 vector2_zero = { 0.0, 0.0 }; + + +static void +gimp_tool_polygon_class_init (GimpToolPolygonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_polygon_constructed; + object_class->finalize = gimp_tool_polygon_finalize; + object_class->set_property = gimp_tool_polygon_set_property; + object_class->get_property = gimp_tool_polygon_get_property; + + widget_class->changed = gimp_tool_polygon_changed; + widget_class->button_press = gimp_tool_polygon_button_press; + widget_class->button_release = gimp_tool_polygon_button_release; + widget_class->motion = gimp_tool_polygon_motion; + widget_class->hit = gimp_tool_polygon_hit; + widget_class->hover = gimp_tool_polygon_hover; + widget_class->leave_notify = gimp_tool_polygon_leave_notify; + widget_class->key_press = gimp_tool_polygon_key_press; + widget_class->motion_modifier = gimp_tool_polygon_motion_modifier; + widget_class->hover_modifier = gimp_tool_polygon_hover_modifier; + widget_class->get_cursor = gimp_tool_polygon_get_cursor; + + polygon_signals[CHANGE_COMPLETE] = + g_signal_new ("change-complete", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolPolygonClass, change_complete), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +gimp_tool_polygon_init (GimpToolPolygon *polygon) +{ + polygon->private = gimp_tool_polygon_get_instance_private (polygon); + + polygon->private->grabbed_segment_index = INVALID_INDEX; + polygon->private->last_click_time = NO_CLICK_TIME_AVAILABLE; +} + +static void +gimp_tool_polygon_constructed (GObject *object) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolPolygonPrivate *private = polygon->private; + GimpCanvasGroup *stroke_group; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->polygon = gimp_tool_widget_add_polygon (widget, NULL, + NULL, 0, FALSE); + + private->pending_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0); + private->closing_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0); + + gimp_tool_widget_pop_group (widget); + + private->handles = g_ptr_array_new (); + + gimp_tool_polygon_changed (widget); +} + +static void +gimp_tool_polygon_finalize (GObject *object) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (object); + GimpToolPolygonPrivate *private = polygon->private; + + g_free (private->points); + g_free (private->segment_indices); + g_free (private->saved_points_lower_segment); + g_free (private->saved_points_higher_segment); + + g_clear_pointer (&private->handles, g_ptr_array_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_tool_polygon_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_polygon_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_polygon_get_segment (GimpToolPolygon *polygon, + GimpVector2 **points, + gint *n_points, + gint segment_start, + gint segment_end) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + *points = &priv->points[priv->segment_indices[segment_start]]; + *n_points = priv->segment_indices[segment_end] - + priv->segment_indices[segment_start] + + 1; +} + +static void +gimp_tool_polygon_get_segment_point (GimpToolPolygon *polygon, + gdouble *start_point_x, + gdouble *start_point_y, + gint segment_index) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + *start_point_x = priv->points[priv->segment_indices[segment_index]].x; + *start_point_y = priv->points[priv->segment_indices[segment_index]].y; +} + +static gboolean +gimp_tool_polygon_should_close (GimpToolPolygon *polygon, + guint32 time, + const GimpCoords *coords) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (polygon); + GimpToolPolygonPrivate *priv = polygon->private; + gboolean double_click = FALSE; + gdouble dist; + + if (priv->polygon_modified || + priv->n_segment_indices < 1 || + priv->n_points < 3 || + priv->polygon_closed) + return FALSE; + + dist = gimp_canvas_item_transform_distance_square (priv->polygon, + coords->x, + coords->y, + priv->points[0].x, + priv->points[0].y); + + /* Handle double-click. It must be within GTK+ global double-click + * time since last click, and it must be within GTK+ global + * double-click distance away from the last point + */ + if (time != NO_CLICK_TIME_AVAILABLE) + { + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (shell)); + gint double_click_time; + gint double_click_distance; + gint click_time_passed; + gdouble dist_from_last_point; + + click_time_passed = time - priv->last_click_time; + + dist_from_last_point = + gimp_canvas_item_transform_distance_square (priv->polygon, + coords->x, + coords->y, + priv->last_click_coord.x, + priv->last_click_coord.y); + + g_object_get (settings, + "gtk-double-click-time", &double_click_time, + "gtk-double-click-distance", &double_click_distance, + NULL); + + double_click = click_time_passed < double_click_time && + dist_from_last_point < double_click_distance; + } + + return ((! priv->suppress_handles && dist < POINT_GRAB_THRESHOLD_SQ) || + double_click); +} + +static void +gimp_tool_polygon_revert_to_last_segment (GimpToolPolygon *polygon) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + priv->n_points = priv->segment_indices[priv->n_segment_indices - 1] + 1; +} + +static void +gimp_tool_polygon_remove_last_segment (GimpToolPolygon *polygon) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + if (priv->polygon_closed) + { + priv->polygon_closed = FALSE; + + gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon)); + + return; + } + + if (priv->n_segment_indices > 0) + { + GimpCanvasItem *handle; + + priv->n_segment_indices--; + + handle = g_ptr_array_index (priv->handles, + priv->n_segment_indices); + + gimp_tool_widget_remove_item (GIMP_TOOL_WIDGET (polygon), handle); + g_ptr_array_remove (priv->handles, handle); + } + + if (priv->n_segment_indices <= 0) + { + priv->grabbed_segment_index = INVALID_INDEX; + priv->show_pending_point = FALSE; + priv->n_points = 0; + priv->n_segment_indices = 0; + + gimp_tool_widget_response (GIMP_TOOL_WIDGET (polygon), + GIMP_TOOL_WIDGET_RESPONSE_CANCEL); + } + else + { + gimp_tool_polygon_revert_to_last_segment (polygon); + + gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon)); + } +} + +static void +gimp_tool_polygon_add_point (GimpToolPolygon *polygon, + gdouble x, + gdouble y) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + if (priv->n_points >= priv->max_n_points) + { + priv->max_n_points += N_ITEMS_PER_ALLOC; + + priv->points = g_realloc (priv->points, + sizeof (GimpVector2) * priv->max_n_points); + } + + priv->points[priv->n_points].x = x; + priv->points[priv->n_points].y = y; + + priv->n_points++; +} + +static void +gimp_tool_polygon_add_segment_index (GimpToolPolygon *polygon, + gint index) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + if (priv->n_segment_indices >= priv->max_n_segment_indices) + { + priv->max_n_segment_indices += N_ITEMS_PER_ALLOC; + + priv->segment_indices = g_realloc (priv->segment_indices, + sizeof (GimpVector2) * + priv->max_n_segment_indices); + } + + priv->segment_indices[priv->n_segment_indices] = index; + + g_ptr_array_add (priv->handles, + gimp_tool_widget_add_handle (GIMP_TOOL_WIDGET (polygon), + GIMP_HANDLE_CROSS, + 0, 0, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + GIMP_HANDLE_ANCHOR_CENTER)); + + priv->n_segment_indices++; +} + +static gboolean +gimp_tool_polygon_is_point_grabbed (GimpToolPolygon *polygon) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + return priv->grabbed_segment_index != INVALID_INDEX; +} + +static void +gimp_tool_polygon_fit_segment (GimpToolPolygon *polygon, + GimpVector2 *dest_points, + GimpVector2 dest_start_target, + GimpVector2 dest_end_target, + const GimpVector2 *source_points, + gint n_points) +{ + GimpVector2 origo_translation_offset; + GimpVector2 untranslation_offset; + gdouble rotation; + gdouble scale; + + /* Handle some quick special cases */ + if (n_points <= 0) + { + return; + } + else if (n_points == 1) + { + dest_points[0] = dest_end_target; + return; + } + else if (n_points == 2) + { + dest_points[0] = dest_start_target; + dest_points[1] = dest_end_target; + return; + } + + /* Copy from source to dest; we work on the dest data */ + memcpy (dest_points, source_points, sizeof (GimpVector2) * n_points); + + /* Transform the destination end point */ + { + GimpVector2 *dest_end; + GimpVector2 origo_translated_end_target; + gdouble target_rotation; + gdouble current_rotation; + gdouble target_length; + gdouble current_length; + + dest_end = &dest_points[n_points - 1]; + + /* Transate to origin */ + gimp_vector2_sub (&origo_translation_offset, + &vector2_zero, + &dest_points[0]); + gimp_vector2_add (dest_end, + dest_end, + &origo_translation_offset); + + /* Calculate origo_translated_end_target */ + gimp_vector2_sub (&origo_translated_end_target, + &dest_end_target, + &dest_start_target); + + /* Rotate */ + target_rotation = atan2 (vector2_zero.y - origo_translated_end_target.y, + vector2_zero.x - origo_translated_end_target.x); + current_rotation = atan2 (vector2_zero.y - dest_end->y, + vector2_zero.x - dest_end->x); + rotation = current_rotation - target_rotation; + + gimp_vector2_rotate (dest_end, rotation); + + + /* Scale */ + target_length = gimp_vector2_length (&origo_translated_end_target); + current_length = gimp_vector2_length (dest_end); + scale = target_length / current_length; + + gimp_vector2_mul (dest_end, scale); + + + /* Untranslate */ + gimp_vector2_sub (&untranslation_offset, + &dest_end_target, + dest_end); + gimp_vector2_add (dest_end, + dest_end, + &untranslation_offset); + } + + /* Do the same transformation for the rest of the points */ + { + gint i; + + for (i = 0; i < n_points - 1; i++) + { + /* Translate */ + gimp_vector2_add (&dest_points[i], + &origo_translation_offset, + &dest_points[i]); + + /* Rotate */ + gimp_vector2_rotate (&dest_points[i], + rotation); + + /* Scale */ + gimp_vector2_mul (&dest_points[i], + scale); + + /* Untranslate */ + gimp_vector2_add (&dest_points[i], + &dest_points[i], + &untranslation_offset); + } + } +} + +static void +gimp_tool_polygon_move_segment_vertex_to (GimpToolPolygon *polygon, + gint segment_index, + gdouble new_x, + gdouble new_y) +{ + GimpToolPolygonPrivate *priv = polygon->private; + GimpVector2 cursor_point = { new_x, new_y }; + GimpVector2 *dest; + GimpVector2 *dest_start_target; + GimpVector2 *dest_end_target; + gint n_points; + + /* Handle the segment before the grabbed point */ + if (segment_index > 0) + { + gimp_tool_polygon_get_segment (polygon, + &dest, + &n_points, + priv->grabbed_segment_index - 1, + priv->grabbed_segment_index); + + dest_start_target = &dest[0]; + dest_end_target = &cursor_point; + + gimp_tool_polygon_fit_segment (polygon, + dest, + *dest_start_target, + *dest_end_target, + priv->saved_points_lower_segment, + n_points); + } + + /* Handle the segment after the grabbed point */ + if (segment_index < priv->n_segment_indices - 1) + { + gimp_tool_polygon_get_segment (polygon, + &dest, + &n_points, + priv->grabbed_segment_index, + priv->grabbed_segment_index + 1); + + dest_start_target = &cursor_point; + dest_end_target = &dest[n_points - 1]; + + gimp_tool_polygon_fit_segment (polygon, + dest, + *dest_start_target, + *dest_end_target, + priv->saved_points_higher_segment, + n_points); + } + + /* Handle when there only is one point */ + if (segment_index == 0 && + priv->n_segment_indices == 1) + { + priv->points[0].x = new_x; + priv->points[0].y = new_y; + } +} + +static void +gimp_tool_polygon_revert_to_saved_state (GimpToolPolygon *polygon) +{ + GimpToolPolygonPrivate *priv = polygon->private; + GimpVector2 *dest; + gint n_points; + + /* Without a point grab we have no sensible information to fall back + * on, bail out + */ + if (! gimp_tool_polygon_is_point_grabbed (polygon)) + { + return; + } + + if (priv->grabbed_segment_index > 0) + { + gimp_tool_polygon_get_segment (polygon, + &dest, + &n_points, + priv->grabbed_segment_index - 1, + priv->grabbed_segment_index); + + memcpy (dest, + priv->saved_points_lower_segment, + sizeof (GimpVector2) * n_points); + } + + if (priv->grabbed_segment_index < priv->n_segment_indices - 1) + { + gimp_tool_polygon_get_segment (polygon, + &dest, + &n_points, + priv->grabbed_segment_index, + priv->grabbed_segment_index + 1); + + memcpy (dest, + priv->saved_points_higher_segment, + sizeof (GimpVector2) * n_points); + } + + if (priv->grabbed_segment_index == 0 && + priv->n_segment_indices == 1) + { + priv->points[0] = *priv->saved_points_lower_segment; + } +} + +static void +gimp_tool_polygon_prepare_for_move (GimpToolPolygon *polygon) +{ + GimpToolPolygonPrivate *priv = polygon->private; + GimpVector2 *source; + gint n_points; + + if (priv->grabbed_segment_index > 0) + { + gimp_tool_polygon_get_segment (polygon, + &source, + &n_points, + priv->grabbed_segment_index - 1, + priv->grabbed_segment_index); + + if (n_points > priv->max_n_saved_points_lower_segment) + { + priv->max_n_saved_points_lower_segment = n_points; + + priv->saved_points_lower_segment = + g_realloc (priv->saved_points_lower_segment, + sizeof (GimpVector2) * n_points); + } + + memcpy (priv->saved_points_lower_segment, + source, + sizeof (GimpVector2) * n_points); + } + + if (priv->grabbed_segment_index < priv->n_segment_indices - 1) + { + gimp_tool_polygon_get_segment (polygon, + &source, + &n_points, + priv->grabbed_segment_index, + priv->grabbed_segment_index + 1); + + if (n_points > priv->max_n_saved_points_higher_segment) + { + priv->max_n_saved_points_higher_segment = n_points; + + priv->saved_points_higher_segment = + g_realloc (priv->saved_points_higher_segment, + sizeof (GimpVector2) * n_points); + } + + memcpy (priv->saved_points_higher_segment, + source, + sizeof (GimpVector2) * n_points); + } + + /* A special-case when there only is one point */ + if (priv->grabbed_segment_index == 0 && + priv->n_segment_indices == 1) + { + if (priv->max_n_saved_points_lower_segment == 0) + { + priv->max_n_saved_points_lower_segment = 1; + + priv->saved_points_lower_segment = g_new0 (GimpVector2, 1); + } + + *priv->saved_points_lower_segment = priv->points[0]; + } +} + +static void +gimp_tool_polygon_update_motion (GimpToolPolygon *polygon, + gdouble new_x, + gdouble new_y) +{ + GimpToolPolygonPrivate *priv = polygon->private; + + if (gimp_tool_polygon_is_point_grabbed (polygon)) + { + priv->polygon_modified = TRUE; + + if (priv->constrain_angle && + priv->n_segment_indices > 1) + { + gdouble start_point_x; + gdouble start_point_y; + gint segment_index; + + /* Base constraints on the last segment vertex if we move + * the first one, otherwise base on the previous segment + * vertex + */ + if (priv->grabbed_segment_index == 0) + { + segment_index = priv->n_segment_indices - 1; + } + else + { + segment_index = priv->grabbed_segment_index - 1; + } + + gimp_tool_polygon_get_segment_point (polygon, + &start_point_x, + &start_point_y, + segment_index); + + gimp_display_shell_constrain_line ( + gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (polygon)), + start_point_x, + start_point_y, + &new_x, + &new_y, + GIMP_CONSTRAIN_LINE_15_DEGREES); + } + + gimp_tool_polygon_move_segment_vertex_to (polygon, + priv->grabbed_segment_index, + new_x, + new_y); + + /* We also must update the pending point if we are moving the + * first point + */ + if (priv->grabbed_segment_index == 0) + { + priv->pending_point.x = new_x; + priv->pending_point.y = new_y; + } + } + else + { + /* Don't show the pending point while we are adding points */ + priv->show_pending_point = FALSE; + + gimp_tool_polygon_add_point (polygon, new_x, new_y); + } +} + +static void +gimp_tool_polygon_status_update (GimpToolPolygon *polygon, + const GimpCoords *coords, + gboolean proximity) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (polygon); + GimpToolPolygonPrivate *priv = polygon->private; + + if (proximity) + { + const gchar *status_text = NULL; + + if (gimp_tool_polygon_is_point_grabbed (polygon)) + { + if (gimp_tool_polygon_should_close (polygon, + NO_CLICK_TIME_AVAILABLE, + coords)) + { + status_text = _("Click to close shape"); + } + else + { + status_text = _("Click-Drag to move segment vertex"); + } + } + else if (priv->polygon_closed) + { + status_text = _("Return commits, Escape cancels, Backspace re-opens shape"); + } + else if (priv->n_segment_indices >= 3) + { + status_text = _("Return commits, Escape cancels, Backspace removes last segment"); + } + else + { + status_text = _("Click-Drag adds a free segment, Click adds a polygonal segment"); + } + + if (status_text) + { + gimp_tool_widget_set_status (widget, status_text); + } + } + else + { + gimp_tool_widget_set_status (widget, NULL); + } +} + +static void +gimp_tool_polygon_changed (GimpToolWidget *widget) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *private = polygon->private; + gboolean hovering_first_point = FALSE; + gboolean handles_wants_to_show = FALSE; + GimpCoords coords = { private->last_coords.x, + private->last_coords.y, + /* pad with 0 */ }; + gint i; + + gimp_canvas_polygon_set_points (private->polygon, + private->points, private->n_points); + + if (private->show_pending_point) + { + GimpVector2 last = private->points[private->n_points - 1]; + + gimp_canvas_line_set (private->pending_line, + last.x, + last.y, + private->pending_point.x, + private->pending_point.y); + } + + gimp_canvas_item_set_visible (private->pending_line, + private->show_pending_point); + + if (private->polygon_closed) + { + GimpVector2 first = private->points[0]; + GimpVector2 last = private->points[private->n_points - 1]; + + gimp_canvas_line_set (private->closing_line, + first.x, first.y, + last.x, last.y); + } + + gimp_canvas_item_set_visible (private->closing_line, + private->polygon_closed); + + hovering_first_point = + gimp_tool_polygon_should_close (polygon, + NO_CLICK_TIME_AVAILABLE, + &coords); + + /* We always show the handle for the first point, even with button1 + * down, since releasing the button on the first point will close + * the polygon, so it's a significant state which we must give + * feedback for + */ + handles_wants_to_show = (hovering_first_point || ! private->button_down); + + for (i = 0; i < private->n_segment_indices; i++) + { + GimpCanvasItem *handle; + GimpVector2 *point; + + handle = g_ptr_array_index (private->handles, i); + point = &private->points[private->segment_indices[i]]; + + if (private->hover && + handles_wants_to_show && + ! private->suppress_handles && + + /* If the first point is hovered while button1 is held down, + * only draw the first handle, the other handles are not + * relevant (see comment a few lines up) + */ + (i == 0 || + ! (private->button_down || hovering_first_point))) + { + gdouble dist; + GimpHandleType handle_type = -1; + + dist = + gimp_canvas_item_transform_distance_square (handle, + private->last_coords.x, + private->last_coords.y, + point->x, + point->y); + + /* If the cursor is over the point, fill, if it's just + * close, draw an outline + */ + if (dist < POINT_GRAB_THRESHOLD_SQ) + handle_type = GIMP_HANDLE_FILLED_CIRCLE; + else if (dist < POINT_SHOW_THRESHOLD_SQ) + handle_type = GIMP_HANDLE_CIRCLE; + + if (handle_type != -1) + { + gint size; + + gimp_canvas_item_begin_change (handle); + + gimp_canvas_handle_set_position (handle, point->x, point->y); + + g_object_set (handle, + "type", handle_type, + NULL); + + size = gimp_canvas_handle_calc_size (handle, + private->last_coords.x, + private->last_coords.y, + GIMP_CANVAS_HANDLE_SIZE_CIRCLE, + 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE); + if (FALSE) + gimp_canvas_handle_set_size (handle, size, size); + + if (dist < POINT_GRAB_THRESHOLD_SQ) + gimp_canvas_item_set_highlight (handle, TRUE); + else + gimp_canvas_item_set_highlight (handle, FALSE); + + gimp_canvas_item_set_visible (handle, TRUE); + + gimp_canvas_item_end_change (handle); + } + else + { + gimp_canvas_item_set_visible (handle, FALSE); + } + } + else + { + gimp_canvas_item_set_visible (handle, FALSE); + } + } +} + +static gboolean +gimp_tool_polygon_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + if (gimp_tool_polygon_is_point_grabbed (polygon)) + { + gimp_tool_polygon_prepare_for_move (polygon); + } + else if (priv->polygon_closed) + { + if (press_type == GIMP_BUTTON_PRESS_DOUBLE && + gimp_canvas_item_hit (priv->polygon, coords->x, coords->y)) + { + gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CONFIRM); + } + + return 0; + } + else + { + GimpVector2 point_to_add; + + /* Note that we add the pending point (unless it is the first + * point we add) because the pending point is setup correctly + * with regards to angle constraints. + */ + if (priv->n_points > 0) + { + point_to_add = priv->pending_point; + } + else + { + point_to_add.x = coords->x; + point_to_add.y = coords->y; + } + + /* No point was grabbed, add a new point and mark this as a + * segment divider. For a line segment, this will be the only + * new point. For a free segment, this will be the first point + * of the free segment. + */ + gimp_tool_polygon_add_point (polygon, + point_to_add.x, + point_to_add.y); + gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1); + } + + priv->button_down = TRUE; + + gimp_tool_polygon_changed (widget); + + return 1; +} + +static void +gimp_tool_polygon_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + g_object_ref (widget); + + priv->button_down = FALSE; + + switch (release_type) + { + case GIMP_BUTTON_RELEASE_CLICK: + case GIMP_BUTTON_RELEASE_NO_MOTION: + /* If a click was made, we don't consider the polygon modified */ + priv->polygon_modified = FALSE; + + /* First finish of the line segment if no point was grabbed */ + if (! gimp_tool_polygon_is_point_grabbed (polygon)) + { + /* Revert any free segment points that might have been added */ + gimp_tool_polygon_revert_to_last_segment (polygon); + } + + /* After the segments are up to date and we have handled + * double-click, see if it's committing time + */ + if (gimp_tool_polygon_should_close (polygon, time, coords)) + { + /* We can get a click notification even though the end point + * has been moved a few pixels. Since a move will change the + * free selection, revert it before doing the commit. + */ + gimp_tool_polygon_revert_to_saved_state (polygon); + + priv->polygon_closed = TRUE; + + gimp_tool_polygon_change_complete (polygon); + } + + priv->last_click_time = time; + priv->last_click_coord = *coords; + break; + + case GIMP_BUTTON_RELEASE_NORMAL: + /* First finish of the free segment if no point was grabbed */ + if (! gimp_tool_polygon_is_point_grabbed (polygon)) + { + /* The points are all setup, just make a segment */ + gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1); + } + + /* After the segments are up to date, see if it's committing time */ + if (gimp_tool_polygon_should_close (polygon, + NO_CLICK_TIME_AVAILABLE, + coords)) + { + priv->polygon_closed = TRUE; + } + + gimp_tool_polygon_change_complete (polygon); + break; + + case GIMP_BUTTON_RELEASE_CANCEL: + if (gimp_tool_polygon_is_point_grabbed (polygon)) + gimp_tool_polygon_revert_to_saved_state (polygon); + else + gimp_tool_polygon_remove_last_segment (polygon); + break; + + default: + break; + } + + /* Reset */ + priv->polygon_modified = FALSE; + + gimp_tool_polygon_changed (widget); + + g_object_unref (widget); +} + +static void +gimp_tool_polygon_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + priv->last_coords.x = coords->x; + priv->last_coords.y = coords->y; + + gimp_tool_polygon_update_motion (polygon, + coords->x, + coords->y); + + gimp_tool_polygon_changed (widget); +} + +static GimpHit +gimp_tool_polygon_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + if ((priv->n_points > 0 && ! priv->polygon_closed) || + gimp_tool_polygon_get_segment_index (polygon, coords) != INVALID_INDEX) + { + return GIMP_HIT_DIRECT; + } + else if (priv->polygon_closed && + gimp_canvas_item_hit (priv->polygon, coords->x, coords->y)) + { + return GIMP_HIT_INDIRECT; + } + + return GIMP_HIT_NONE; +} + +static void +gimp_tool_polygon_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + gboolean hovering_first_point; + + priv->grabbed_segment_index = gimp_tool_polygon_get_segment_index (polygon, + coords); + priv->hover = TRUE; + + hovering_first_point = + gimp_tool_polygon_should_close (polygon, + NO_CLICK_TIME_AVAILABLE, + coords); + + priv->last_coords.x = coords->x; + priv->last_coords.y = coords->y; + + if (priv->n_points == 0 || + priv->polygon_closed || + (gimp_tool_polygon_is_point_grabbed (polygon) && + ! hovering_first_point) || + ! proximity) + { + priv->show_pending_point = FALSE; + } + else + { + priv->show_pending_point = TRUE; + + if (hovering_first_point) + { + priv->pending_point = priv->points[0]; + } + else + { + priv->pending_point.x = coords->x; + priv->pending_point.y = coords->y; + + if (priv->constrain_angle && priv->n_points > 0) + { + gdouble start_point_x; + gdouble start_point_y; + + /* the last point is the line's start point */ + gimp_tool_polygon_get_segment_point (polygon, + &start_point_x, + &start_point_y, + priv->n_segment_indices - 1); + + gimp_display_shell_constrain_line ( + gimp_tool_widget_get_shell (widget), + start_point_x, start_point_y, + &priv->pending_point.x, + &priv->pending_point.y, + GIMP_CONSTRAIN_LINE_15_DEGREES); + } + } + } + + gimp_tool_polygon_status_update (polygon, coords, proximity); + + gimp_tool_polygon_changed (widget); +} + +static void +gimp_tool_polygon_leave_notify (GimpToolWidget *widget) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + priv->grabbed_segment_index = INVALID_INDEX; + priv->hover = FALSE; + priv->show_pending_point = FALSE; + + gimp_tool_polygon_changed (widget); +} + +static gboolean +gimp_tool_polygon_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + switch (kevent->keyval) + { + case GDK_KEY_BackSpace: + gimp_tool_polygon_remove_last_segment (polygon); + + if (priv->n_segment_indices > 0) + gimp_tool_polygon_change_complete (polygon); + return TRUE; + + default: + break; + } + + return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent); +} + +static void +gimp_tool_polygon_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ? + TRUE : FALSE); + + /* If we didn't came here due to a mouse release, immediately update + * the position of the thing we move. + */ + if (state & GDK_BUTTON1_MASK) + { + gimp_tool_polygon_update_motion (polygon, + priv->last_coords.x, + priv->last_coords.y); + } + + gimp_tool_polygon_changed (widget); +} + +static void +gimp_tool_polygon_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + GimpToolPolygonPrivate *priv = polygon->private; + + priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ? + TRUE : FALSE); + + priv->suppress_handles = ((state & gimp_get_extend_selection_mask ()) ? + TRUE : FALSE); + + gimp_tool_polygon_changed (widget); +} + +static gboolean +gimp_tool_polygon_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget); + + if (gimp_tool_polygon_is_point_grabbed (polygon) && + ! gimp_tool_polygon_should_close (polygon, + NO_CLICK_TIME_AVAILABLE, + coords)) + { + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + + return TRUE; + } + + return FALSE; +} + +static void +gimp_tool_polygon_change_complete (GimpToolPolygon *polygon) +{ + g_signal_emit (polygon, polygon_signals[CHANGE_COMPLETE], 0); +} + +static gint +gimp_tool_polygon_get_segment_index (GimpToolPolygon *polygon, + const GimpCoords *coords) +{ + GimpToolPolygonPrivate *priv = polygon->private; + gint segment_index = INVALID_INDEX; + + if (! priv->suppress_handles) + { + gdouble shortest_dist = POINT_GRAB_THRESHOLD_SQ; + gint i; + + for (i = 0; i < priv->n_segment_indices; i++) + { + gdouble dist; + GimpVector2 *point; + + point = &priv->points[priv->segment_indices[i]]; + + dist = gimp_canvas_item_transform_distance_square (priv->polygon, + coords->x, + coords->y, + point->x, + point->y); + + if (dist < shortest_dist) + { + shortest_dist = dist; + + segment_index = i; + } + } + } + + return segment_index; +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_polygon_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_POLYGON, + "shell", shell, + NULL); +} + +gboolean +gimp_tool_polygon_is_closed (GimpToolPolygon *polygon) +{ + GimpToolPolygonPrivate *private; + + g_return_val_if_fail (GIMP_IS_TOOL_POLYGON (polygon), FALSE); + + private = polygon->private; + + return private->polygon_closed; +} + +void +gimp_tool_polygon_get_points (GimpToolPolygon *polygon, + const GimpVector2 **points, + gint *n_points) +{ + GimpToolPolygonPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_POLYGON (polygon)); + + private = polygon->private; + + if (points) *points = private->points; + if (n_points) *n_points = private->n_points; +} diff --git a/app/display/gimptoolpolygon.h b/app/display/gimptoolpolygon.h new file mode 100644 index 0000000..fa3560a --- /dev/null +++ b/app/display/gimptoolpolygon.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpolygon.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_POLYGON_H__ +#define __GIMP_TOOL_POLYGON_H__ + + +#include "gimptoolwidget.h" + + +#define GIMP_TYPE_TOOL_POLYGON (gimp_tool_polygon_get_type ()) +#define GIMP_TOOL_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_POLYGON, GimpToolPolygon)) +#define GIMP_TOOL_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_POLYGON, GimpToolPolygonClass)) +#define GIMP_IS_TOOL_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_POLYGON)) +#define GIMP_IS_TOOL_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_POLYGON)) +#define GIMP_TOOL_POLYGON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_POLYGON, GimpToolPolygonClass)) + + +typedef struct _GimpToolPolygon GimpToolPolygon; +typedef struct _GimpToolPolygonPrivate GimpToolPolygonPrivate; +typedef struct _GimpToolPolygonClass GimpToolPolygonClass; + +struct _GimpToolPolygon +{ + GimpToolWidget parent_instance; + + GimpToolPolygonPrivate *private; +}; + +struct _GimpToolPolygonClass +{ + GimpToolWidgetClass parent_class; + + /* signals */ + void (* change_complete) (GimpToolPolygon *polygon); +}; + + +GType gimp_tool_polygon_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_polygon_new (GimpDisplayShell *shell); + +gboolean gimp_tool_polygon_is_closed (GimpToolPolygon *polygon); +void gimp_tool_polygon_get_points (GimpToolPolygon *polygon, + const GimpVector2 **points, + gint *n_points); + + +#endif /* __GIMP_TOOL_POLYGON_H__ */ diff --git a/app/display/gimptoolrectangle.c b/app/display/gimptoolrectangle.c new file mode 100644 index 0000000..c1192db --- /dev/null +++ b/app/display/gimptoolrectangle.c @@ -0,0 +1,4256 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolrectangle.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Based on GimpRectangleTool + * Copyright (C) 2007 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpitem.h" +#include "core/gimpmarshal.h" +#include "core/gimppickable.h" +#include "core/gimppickable-auto-shrink.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvasarc.h" +#include "gimpcanvascorner.h" +#include "gimpcanvashandle.h" +#include "gimpcanvasitem-utils.h" +#include "gimpcanvasrectangle.h" +#include "gimpcanvasrectangleguides.h" +#include "gimpdisplay.h" +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-scroll.h" +#include "gimptoolrectangle.h" + +#include "gimp-intl.h" + + +/* speed of key movement */ +#define ARROW_VELOCITY 25 + +#define MAX_HANDLE_SIZE 50 +#define MIN_HANDLE_SIZE 15 +#define NARROW_MODE_HANDLE_SIZE 15 +#define NARROW_MODE_THRESHOLD 45 + + +enum +{ + PROP_0, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_CONSTRAINT, + PROP_PRECISION, + PROP_NARROW_MODE, + PROP_FORCE_NARROW_MODE, + PROP_DRAW_ELLIPSE, + PROP_ROUND_CORNERS, + PROP_CORNER_RADIUS, + PROP_STATUS_TITLE, + + PROP_HIGHLIGHT, + PROP_HIGHLIGHT_OPACITY, + PROP_GUIDE, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_FIXED_RULE_ACTIVE, + PROP_FIXED_RULE, + PROP_DESIRED_FIXED_WIDTH, + PROP_DESIRED_FIXED_HEIGHT, + PROP_DESIRED_FIXED_SIZE_WIDTH, + PROP_DESIRED_FIXED_SIZE_HEIGHT, + PROP_ASPECT_NUMERATOR, + PROP_ASPECT_DENOMINATOR, + PROP_FIXED_CENTER +}; + +enum +{ + CHANGE_COMPLETE, + LAST_SIGNAL +}; + +typedef enum +{ + CLAMPED_NONE = 0, + CLAMPED_LEFT = 1 << 0, + CLAMPED_RIGHT = 1 << 1, + CLAMPED_TOP = 1 << 2, + CLAMPED_BOTTOM = 1 << 3 +} ClampedSide; + +typedef enum +{ + SIDE_TO_RESIZE_NONE, + SIDE_TO_RESIZE_LEFT, + SIDE_TO_RESIZE_RIGHT, + SIDE_TO_RESIZE_TOP, + SIDE_TO_RESIZE_BOTTOM, + SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY, + SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY, +} SideToResize; + + +#define FEQUAL(a,b) (fabs ((a) - (b)) < 0.0001) +#define PIXEL_FEQUAL(a,b) (fabs ((a) - (b)) < 0.5) + + +struct _GimpToolRectanglePrivate +{ + /* The following members are "constants", that is, variables that are setup + * during gimp_tool_rectangle_button_press and then only read. + */ + + /* Whether or not the rectangle currently being rubber-banded is the + * first one created with this instance, this determines if we can + * undo it on button_release. + */ + gboolean is_first; + + /* Whether or not the rectangle currently being rubber-banded was + * created from scratch. + */ + gboolean is_new; + + /* Holds the coordinate that should be used as the "other side" when + * fixed-center is turned off. + */ + gdouble other_side_x; + gdouble other_side_y; + + /* Holds the coordinate to be used as center when fixed-center is used. */ + gdouble center_x_on_fixed_center; + gdouble center_y_on_fixed_center; + + /* True when the rectangle is being adjusted (moved or + * rubber-banded). + */ + gboolean rect_adjusting; + + + /* The rest of the members are internal state variables, that is, variables + * that might change during the manipulation session of the rectangle. Make + * sure these variables are in consistent states. + */ + + /* Coordinates of upper left and lower right rectangle corners. */ + gdouble x1, y1; + gdouble x2, y2; + + /* Integer coordinats of upper left corner and size. We must + * calculate this separately from the gdouble ones because sometimes + * we don't want to affect the integer size (e.g. when moving the + * rectangle), but that will be the case if we always calculate the + * integer coordinates based on rounded values of the gdouble + * coordinates even if the gdouble width remains constant. + * + * TODO: Change the internal double-representation of the rectangle + * to x,y width,height instead of x1,y1 x2,y2. That way we don't + * need to keep a separate representation of the integer version of + * the rectangle; rounding width an height will yield consistent + * results and not depend on position of the rectangle. + */ + gint x1_int, y1_int; + gint width_int, height_int; + + /* How to constrain the rectangle. */ + GimpRectangleConstraint constraint; + + /* What precision the rectangle will appear to have externally (it + * will always be double internally) + */ + GimpRectanglePrecision precision; + + /* Previous coordinate applied to the rectangle. */ + gdouble lastx; + gdouble lasty; + + /* Width and height of corner handles. */ + gint corner_handle_w; + gint corner_handle_h; + + /* Width and height of side handles. */ + gint top_and_bottom_handle_w; + gint left_and_right_handle_h; + + /* Whether or not the rectangle is in a 'narrow situation' i.e. it is + * too small for reasonable sized handle to be inside. In this case + * we put handles on the outside. + */ + gboolean narrow_mode; + + /* This boolean allows to always set narrow mode */ + gboolean force_narrow_mode; + + /* Whether or not to draw an ellipse inside the rectangle */ + gboolean draw_ellipse; + + /* Whether to draw round corners */ + gboolean round_corners; + gdouble corner_radius; + + /* The title for the statusbar coords */ + gchar *status_title; + + /* For saving in case of cancellation. */ + gdouble saved_x1; + gdouble saved_y1; + gdouble saved_x2; + gdouble saved_y2; + + gint suppress_updates; + + GimpRectangleFunction function; + + /* The following values are externally synced with GimpRectangleOptions */ + + gboolean highlight; + gdouble highlight_opacity; + GimpGuidesType guide; + + gdouble x; + gdouble y; + gdouble width; + gdouble height; + + gboolean fixed_rule_active; + GimpRectangleFixedRule fixed_rule; + gdouble desired_fixed_width; + gdouble desired_fixed_height; + gdouble desired_fixed_size_width; + gdouble desired_fixed_size_height; + gdouble aspect_numerator; + gdouble aspect_denominator; + gboolean fixed_center; + + /* Canvas items for drawing the GUI */ + + GimpCanvasItem *guides; + GimpCanvasItem *rectangle; + GimpCanvasItem *ellipse; + GimpCanvasItem *corners[4]; + GimpCanvasItem *center; + GimpCanvasItem *creating_corners[4]; + GimpCanvasItem *handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS]; + GimpCanvasItem *highlight_handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS]; +}; + + +/* local function prototypes */ + +static void gimp_tool_rectangle_constructed (GObject *object); +static void gimp_tool_rectangle_finalize (GObject *object); +static void gimp_tool_rectangle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_rectangle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_tool_rectangle_notify (GObject *object, + GParamSpec *pspec); + +static void gimp_tool_rectangle_changed (GimpToolWidget *widget); +static gint gimp_tool_rectangle_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_rectangle_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_rectangle_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_rectangle_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_rectangle_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_rectangle_leave_notify (GimpToolWidget *widget); +static gboolean gimp_tool_rectangle_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +static void gimp_tool_rectangle_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_rectangle_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static void gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle); + +static void gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle); +static void gimp_tool_rectangle_update_handle_sizes + (GimpToolRectangle *rectangle); +static void gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle); + +static void gimp_tool_rectangle_synthesize_motion + (GimpToolRectangle *rectangle, + gint function, + gdouble new_x, + gdouble new_y); + +static GimpRectangleFunction + gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle, + const GimpCoords *coords, + gboolean proximity); +static void gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle); + +static gboolean gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle, + const GimpCoords *coords); + +static gboolean gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle, + const GimpCoords *coords, + GimpHandleAnchor anchor); + +static GimpHandleAnchor gimp_tool_rectangle_get_anchor + (GimpRectangleFunction function); +static gboolean gimp_tool_rectangle_rect_rubber_banding_func + (GimpToolRectangle *rectangle); +static gboolean gimp_tool_rectangle_rect_adjusting_func + (GimpToolRectangle *rectangle); + +static void gimp_tool_rectangle_get_other_side (GimpToolRectangle *rectangle, + gdouble **other_x, + gdouble **other_y); +static void gimp_tool_rectangle_get_other_side_coord + (GimpToolRectangle *rectangle, + gdouble *other_side_x, + gdouble *other_side_y); +static void gimp_tool_rectangle_set_other_side_coord + (GimpToolRectangle *rectangle, + gdouble other_side_x, + gdouble other_side_y); + +static void gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle, + gdouble coord_x, + gdouble coord_y); +static void gimp_tool_rectangle_setup_snap_offsets + (GimpToolRectangle *rectangle, + const GimpCoords *coords); + +static void gimp_tool_rectangle_clamp (GimpToolRectangle *rectangle, + ClampedSide *clamped_sides, + GimpRectangleConstraint constraint, + gboolean symmetrically); +static void gimp_tool_rectangle_clamp_width (GimpToolRectangle *rectangle, + ClampedSide *clamped_sides, + GimpRectangleConstraint constraint, + gboolean symmetrically); +static void gimp_tool_rectangle_clamp_height (GimpToolRectangle *rectangle, + ClampedSide *clamped_sides, + GimpRectangleConstraint constraint, + gboolean symmetrically); + +static void gimp_tool_rectangle_keep_inside (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint); +static void gimp_tool_rectangle_keep_inside_horizontally + (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint); +static void gimp_tool_rectangle_keep_inside_vertically + (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint); + +static void gimp_tool_rectangle_apply_fixed_width + (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint, + gdouble width); +static void gimp_tool_rectangle_apply_fixed_height + (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint, + gdouble height); + +static void gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle, + gdouble aspect, + gint clamped_sides); + +static void gimp_tool_rectangle_update_with_coord + (GimpToolRectangle *rectangle, + gdouble new_x, + gdouble new_y); +static void gimp_tool_rectangle_apply_fixed_rule(GimpToolRectangle *rectangle); + +static void gimp_tool_rectangle_get_constraints (GimpToolRectangle *rectangle, + gint *min_x, + gint *min_y, + gint *max_x, + gint *max_y, + GimpRectangleConstraint constraint); + +static void gimp_tool_rectangle_handle_general_clamping + (GimpToolRectangle *rectangle); +static void gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle); +static void gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle, + gdouble coord_x_input, + gdouble coord_y_input, + gdouble *coord_x_output, + gdouble *coord_y_output); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolRectangle, gimp_tool_rectangle, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_rectangle_parent_class + +static guint rectangle_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_tool_rectangle_class_init (GimpToolRectangleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_rectangle_constructed; + object_class->finalize = gimp_tool_rectangle_finalize; + object_class->set_property = gimp_tool_rectangle_set_property; + object_class->get_property = gimp_tool_rectangle_get_property; + object_class->notify = gimp_tool_rectangle_notify; + + widget_class->changed = gimp_tool_rectangle_changed; + widget_class->button_press = gimp_tool_rectangle_button_press; + widget_class->button_release = gimp_tool_rectangle_button_release; + widget_class->motion = gimp_tool_rectangle_motion; + widget_class->hit = gimp_tool_rectangle_hit; + widget_class->hover = gimp_tool_rectangle_hover; + widget_class->leave_notify = gimp_tool_rectangle_leave_notify; + widget_class->key_press = gimp_tool_rectangle_key_press; + widget_class->motion_modifier = gimp_tool_rectangle_motion_modifier; + widget_class->get_cursor = gimp_tool_rectangle_get_cursor; + widget_class->update_on_scale = TRUE; + + rectangle_signals[CHANGE_COMPLETE] = + g_signal_new ("change-complete", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolRectangleClass, change_complete), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAINT, + g_param_spec_enum ("constraint", + NULL, NULL, + GIMP_TYPE_RECTANGLE_CONSTRAINT, + GIMP_RECTANGLE_CONSTRAIN_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PRECISION, + g_param_spec_enum ("precision", + NULL, NULL, + GIMP_TYPE_RECTANGLE_PRECISION, + GIMP_RECTANGLE_PRECISION_INT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_NARROW_MODE, + g_param_spec_boolean ("narrow-mode", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FORCE_NARROW_MODE, + g_param_spec_boolean ("force-narrow-mode", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DRAW_ELLIPSE, + g_param_spec_boolean ("draw-ellipse", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ROUND_CORNERS, + g_param_spec_boolean ("round-corners", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CORNER_RADIUS, + g_param_spec_double ("corner-radius", + NULL, NULL, + 0.0, 10000.0, 10.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_STATUS_TITLE, + g_param_spec_string ("status-title", + NULL, NULL, + _("Rectangle: "), + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HIGHLIGHT, + g_param_spec_boolean ("highlight", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HIGHLIGHT_OPACITY, + g_param_spec_double ("highlight-opacity", + NULL, NULL, + 0.0, 1.0, 0.5, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_GUIDE, + g_param_spec_enum ("guide", + NULL, NULL, + GIMP_TYPE_GUIDES_TYPE, + GIMP_GUIDES_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_double ("width", + NULL, NULL, + 0.0, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_double ("height", + NULL, NULL, + 0.0, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FIXED_RULE_ACTIVE, + g_param_spec_boolean ("fixed-rule-active", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FIXED_RULE, + g_param_spec_enum ("fixed-rule", + NULL, NULL, + GIMP_TYPE_RECTANGLE_FIXED_RULE, + GIMP_RECTANGLE_FIXED_ASPECT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DESIRED_FIXED_WIDTH, + g_param_spec_double ("desired-fixed-width", + NULL, NULL, + 0.0, GIMP_MAX_IMAGE_SIZE, + 100.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DESIRED_FIXED_HEIGHT, + g_param_spec_double ("desired-fixed-height", + NULL, NULL, + 0.0, GIMP_MAX_IMAGE_SIZE, + 100.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_WIDTH, + g_param_spec_double ("desired-fixed-size-width", + NULL, NULL, + 0.0, GIMP_MAX_IMAGE_SIZE, + 100.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_HEIGHT, + g_param_spec_double ("desired-fixed-size-height", + NULL, NULL, + 0.0, GIMP_MAX_IMAGE_SIZE, + 100.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ASPECT_NUMERATOR, + g_param_spec_double ("aspect-numerator", + NULL, NULL, + 0.0, GIMP_MAX_IMAGE_SIZE, + 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ASPECT_DENOMINATOR, + g_param_spec_double ("aspect-denominator", + NULL, NULL, + 0.0, GIMP_MAX_IMAGE_SIZE, + 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FIXED_CENTER, + g_param_spec_boolean ("fixed-center", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_rectangle_init (GimpToolRectangle *rectangle) +{ + rectangle->private = gimp_tool_rectangle_get_instance_private (rectangle); + + rectangle->private->function = GIMP_TOOL_RECTANGLE_CREATING; + rectangle->private->is_first = TRUE; +} + +static void +gimp_tool_rectangle_constructed (GObject *object) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolRectanglePrivate *private = rectangle->private; + GimpCanvasGroup *stroke_group; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->guides = gimp_tool_widget_add_rectangle_guides (widget, + 0, 0, 10, 10, + GIMP_GUIDES_NONE); + + private->rectangle = gimp_tool_widget_add_rectangle (widget, + 0, 0, 10, 10, + FALSE); + + private->ellipse = gimp_tool_widget_add_arc (widget, + 0, 0, 10, 10, + 0.0, 2 * G_PI, + FALSE); + + for (i = 0; i < 4; i++) + private->corners[i] = gimp_tool_widget_add_arc (widget, + 0, 0, 10, 10, + 0.0, 2 * G_PI, + FALSE); + + gimp_tool_widget_pop_group (widget); + + private->center = gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CROSS, + 0, 0, + GIMP_CANVAS_HANDLE_SIZE_SMALL, + GIMP_CANVAS_HANDLE_SIZE_SMALL, + GIMP_HANDLE_ANCHOR_CENTER); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->creating_corners[0] = + gimp_tool_widget_add_corner (widget, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_NORTH_WEST, + 10, 10, + FALSE); + + private->creating_corners[1] = + gimp_tool_widget_add_corner (widget, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_NORTH_EAST, + 10, 10, + FALSE); + + private->creating_corners[2] = + gimp_tool_widget_add_corner (widget, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_SOUTH_WEST, + 10, 10, + FALSE); + + private->creating_corners[3] = + gimp_tool_widget_add_corner (widget, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_SOUTH_EAST, + 10, 10, + FALSE); + + gimp_tool_widget_pop_group (widget); + + for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT; + i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM; + i++) + { + GimpHandleAnchor anchor; + + anchor = gimp_tool_rectangle_get_anchor (i); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->handles[i] = gimp_tool_widget_add_corner (widget, + 0, 0, 10, 10, + anchor, + 10, 10, + FALSE); + + gimp_tool_widget_pop_group (widget); + + private->highlight_handles[i] = gimp_tool_widget_add_corner (widget, + 0, 0, 10, 10, + anchor, + 10, 10, + FALSE); + gimp_canvas_item_set_highlight (private->highlight_handles[i], TRUE); + } + + gimp_tool_rectangle_changed (widget); +} + +static void +gimp_tool_rectangle_finalize (GObject *object) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object); + GimpToolRectanglePrivate *private = rectangle->private; + + g_clear_pointer (&private->status_title, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_tool_rectangle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object); + GimpToolRectanglePrivate *private = rectangle->private; + + switch (property_id) + { + case PROP_X1: + private->x1 = g_value_get_double (value); + break; + case PROP_Y1: + private->y1 = g_value_get_double (value); + break; + case PROP_X2: + private->x2 = g_value_get_double (value); + break; + case PROP_Y2: + private->y2 = g_value_get_double (value); + break; + + case PROP_CONSTRAINT: + private->constraint = g_value_get_enum (value); + break; + case PROP_PRECISION: + private->precision = g_value_get_enum (value); + break; + + case PROP_NARROW_MODE: + private->narrow_mode = g_value_get_boolean (value); + break; + case PROP_FORCE_NARROW_MODE: + private->force_narrow_mode = g_value_get_boolean (value); + break; + case PROP_DRAW_ELLIPSE: + private->draw_ellipse = g_value_get_boolean (value); + break; + case PROP_ROUND_CORNERS: + private->round_corners = g_value_get_boolean (value); + break; + case PROP_CORNER_RADIUS: + private->corner_radius = g_value_get_double (value); + break; + + case PROP_STATUS_TITLE: + g_free (private->status_title); + private->status_title = g_value_dup_string (value); + if (! private->status_title) + private->status_title = g_strdup (_("Rectangle: ")); + break; + + case PROP_HIGHLIGHT: + private->highlight = g_value_get_boolean (value); + break; + case PROP_HIGHLIGHT_OPACITY: + private->highlight_opacity = g_value_get_double (value); + break; + case PROP_GUIDE: + private->guide = g_value_get_enum (value); + break; + + case PROP_X: + private->x = g_value_get_double (value); + break; + case PROP_Y: + private->y = g_value_get_double (value); + break; + case PROP_WIDTH: + private->width = g_value_get_double (value); + break; + case PROP_HEIGHT: + private->height = g_value_get_double (value); + break; + + case PROP_FIXED_RULE_ACTIVE: + private->fixed_rule_active = g_value_get_boolean (value); + break; + case PROP_FIXED_RULE: + private->fixed_rule = g_value_get_enum (value); + break; + case PROP_DESIRED_FIXED_WIDTH: + private->desired_fixed_width = g_value_get_double (value); + break; + case PROP_DESIRED_FIXED_HEIGHT: + private->desired_fixed_height = g_value_get_double (value); + break; + case PROP_DESIRED_FIXED_SIZE_WIDTH: + private->desired_fixed_size_width = g_value_get_double (value); + break; + case PROP_DESIRED_FIXED_SIZE_HEIGHT: + private->desired_fixed_size_height = g_value_get_double (value); + break; + case PROP_ASPECT_NUMERATOR: + private->aspect_numerator = g_value_get_double (value); + break; + case PROP_ASPECT_DENOMINATOR: + private->aspect_denominator = g_value_get_double (value); + break; + + case PROP_FIXED_CENTER: + private->fixed_center = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_rectangle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object); + GimpToolRectanglePrivate *private = rectangle->private; + + switch (property_id) + { + case PROP_X1: + g_value_set_double (value, private->x1); + break; + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + case PROP_X2: + g_value_set_double (value, private->x2); + break; + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + case PROP_CONSTRAINT: + g_value_set_enum (value, private->constraint); + break; + case PROP_PRECISION: + g_value_set_enum (value, private->precision); + break; + + case PROP_NARROW_MODE: + g_value_set_boolean (value, private->narrow_mode); + break; + case PROP_FORCE_NARROW_MODE: + g_value_set_boolean (value, private->force_narrow_mode); + break; + case PROP_DRAW_ELLIPSE: + g_value_set_boolean (value, private->draw_ellipse); + break; + case PROP_ROUND_CORNERS: + g_value_set_boolean (value, private->round_corners); + break; + case PROP_CORNER_RADIUS: + g_value_set_double (value, private->corner_radius); + break; + + case PROP_STATUS_TITLE: + g_value_set_string (value, private->status_title); + break; + + case PROP_HIGHLIGHT: + g_value_set_boolean (value, private->highlight); + break; + case PROP_HIGHLIGHT_OPACITY: + g_value_set_double (value, private->highlight_opacity); + break; + case PROP_GUIDE: + g_value_set_enum (value, private->guide); + break; + + case PROP_X: + g_value_set_double (value, private->x); + break; + case PROP_Y: + g_value_set_double (value, private->y); + break; + case PROP_WIDTH: + g_value_set_double (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_double (value, private->height); + break; + + case PROP_FIXED_RULE_ACTIVE: + g_value_set_boolean (value, private->fixed_rule_active); + break; + case PROP_FIXED_RULE: + g_value_set_enum (value, private->fixed_rule); + break; + case PROP_DESIRED_FIXED_WIDTH: + g_value_set_double (value, private->desired_fixed_width); + break; + case PROP_DESIRED_FIXED_HEIGHT: + g_value_set_double (value, private->desired_fixed_height); + break; + case PROP_DESIRED_FIXED_SIZE_WIDTH: + g_value_set_double (value, private->desired_fixed_size_width); + break; + case PROP_DESIRED_FIXED_SIZE_HEIGHT: + g_value_set_double (value, private->desired_fixed_size_height); + break; + case PROP_ASPECT_NUMERATOR: + g_value_set_double (value, private->aspect_numerator); + break; + case PROP_ASPECT_DENOMINATOR: + g_value_set_double (value, private->aspect_denominator); + break; + + case PROP_FIXED_CENTER: + g_value_set_boolean (value, private->fixed_center); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_rectangle_notify (GObject *object, + GParamSpec *pspec) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (object); + GimpToolRectanglePrivate *private = rectangle->private; + + if (G_OBJECT_CLASS (parent_class)->notify) + G_OBJECT_CLASS (parent_class)->notify (object, pspec); + + if (! strcmp (pspec->name, "x1") || + ! strcmp (pspec->name, "y1") || + ! strcmp (pspec->name, "x2") || + ! strcmp (pspec->name, "y2")) + { + gimp_tool_rectangle_update_int_rect (rectangle); + + gimp_tool_rectangle_update_options (rectangle); + } + else if (! strcmp (pspec->name, "x") && + ! PIXEL_FEQUAL (private->x1, private->x)) + { + gimp_tool_rectangle_synthesize_motion (rectangle, + GIMP_TOOL_RECTANGLE_MOVING, + private->x, + private->y1); + } + else if (! strcmp (pspec->name, "y") && + ! PIXEL_FEQUAL (private->y1, private->y)) + { + gimp_tool_rectangle_synthesize_motion (rectangle, + GIMP_TOOL_RECTANGLE_MOVING, + private->x1, + private->y); + } + else if (! strcmp (pspec->name, "width") && + ! PIXEL_FEQUAL (private->x2 - private->x1, private->width)) + { + /* Calculate x2, y2 that will create a rectangle of given width, + * for the current options. + */ + gdouble x2; + + if (private->fixed_center) + { + x2 = private->center_x_on_fixed_center + + private->width / 2; + } + else + { + x2 = private->x1 + private->width; + } + + gimp_tool_rectangle_synthesize_motion (rectangle, + GIMP_TOOL_RECTANGLE_RESIZING_RIGHT, + x2, + private->y2); + } + else if (! strcmp (pspec->name, "height") && + ! PIXEL_FEQUAL (private->y2 - private->y1, private->height)) + { + /* Calculate x2, y2 that will create a rectangle of given + * height, for the current options. + */ + gdouble y2; + + if (private->fixed_center) + { + y2 = private->center_y_on_fixed_center + + private->height / 2; + } + else + { + y2 = private->y1 + private->height; + } + + gimp_tool_rectangle_synthesize_motion (rectangle, + GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM, + private->x2, + y2); + } + else if (! strcmp (pspec->name, "desired-fixed-size-width")) + { + /* We are only interested in when width and height swaps, so + * it's enough to only check e.g. for width. + */ + + gdouble width = private->x2 - private->x1; + gdouble height = private->y2 - private->y1; + + /* Depending on a bunch of conditions, we might want to + * immedieately switch width and height of the pending + * rectangle. + */ + if (private->fixed_rule_active && +#if 0 + tool->button_press_state == 0 && + tool->active_modifier_state == 0 && +#endif + FEQUAL (private->desired_fixed_size_width, height) && + FEQUAL (private->desired_fixed_size_height, width)) + { + gdouble x = private->x1; + gdouble y = private->y1; + + gimp_tool_rectangle_synthesize_motion (rectangle, + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT, + private->x2, + private->y2); + + /* For some reason these needs to be set separately... */ + g_object_set (rectangle, + "x", x, + NULL); + g_object_set (rectangle, + "y", y, + NULL); + } + } + else if (! strcmp (pspec->name, "aspect-numerator")) + { + /* We are only interested in when numerator and denominator + * swaps, so it's enough to only check e.g. for numerator. + */ + + double width = private->x2 - private->x1; + double height = private->y2 - private->y1; + gdouble new_inverse_ratio = private->aspect_denominator / + private->aspect_numerator; + gdouble lower_ratio; + gdouble higher_ratio; + + /* The ratio of the Fixed: Aspect ratio rule and the pending + * rectangle is very rarely exactly the same so use an + * interval. For small rectangles the below code will + * automatically yield a more generous accepted ratio interval + * which is exactly what we want. + */ + if (width > height && height > 1.0) + { + lower_ratio = width / (height + 1.0); + higher_ratio = width / (height - 1.0); + } + else + { + lower_ratio = (width - 1.0) / height; + higher_ratio = (width + 1.0) / height; + } + + /* Depending on a bunch of conditions, we might want to + * immedieately switch width and height of the pending + * rectangle. + */ + if (private->fixed_rule_active && +#if 0 + tool->button_press_state == 0 && + tool->active_modifier_state == 0 && +#endif + lower_ratio < new_inverse_ratio && + higher_ratio > new_inverse_ratio) + { + gdouble new_x2 = private->x1 + private->y2 - private->y1; + gdouble new_y2 = private->y1 + private->x2 - private->x1; + + gimp_tool_rectangle_synthesize_motion (rectangle, + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT, + new_x2, + new_y2); + } + } +} + +static void +gimp_tool_rectangle_changed (GimpToolWidget *widget) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + gdouble x1, y1, x2, y2; + gint handle_width; + gint handle_height; + gint i; + + gimp_tool_rectangle_update_handle_sizes (rectangle); + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + + gimp_canvas_rectangle_guides_set (private->guides, + x1, y1, + x2 - x1, + y2 - y1, + private->guide, 4); + + gimp_canvas_rectangle_set (private->rectangle, + x1, y1, + x2 - x1, + y2 - y1); + + if (private->draw_ellipse) + { + gimp_canvas_arc_set (private->ellipse, + (x1 + x2) / 2.0, + (y1 + y2) / 2.0, + (x2 - x1) / 2.0, + (y2 - y1) / 2.0, + 0.0, 2 * G_PI); + gimp_canvas_item_set_visible (private->ellipse, TRUE); + } + else + { + gimp_canvas_item_set_visible (private->ellipse, FALSE); + } + + if (private->round_corners && private->corner_radius > 0.0) + { + gdouble radius; + + radius = MIN (private->corner_radius, + MIN ((x2 - x1) / 2.0, (y2 - y1) / 2.0)); + + gimp_canvas_arc_set (private->corners[0], + x1 + radius, + y1 + radius, + radius, radius, + G_PI / 2.0, G_PI / 2.0); + + gimp_canvas_arc_set (private->corners[1], + x2 - radius, + y1 + radius, + radius, radius, + 0.0, G_PI / 2.0); + + gimp_canvas_arc_set (private->corners[2], + x1 + radius, + y2 - radius, + radius, radius, + G_PI, G_PI / 2.0); + + gimp_canvas_arc_set (private->corners[3], + x2 - radius, + y2 - radius, + radius, radius, + G_PI * 1.5, G_PI / 2.0); + + for (i = 0; i < 4; i++) + gimp_canvas_item_set_visible (private->corners[i], TRUE); + } + else + { + for (i = 0; i < 4; i++) + gimp_canvas_item_set_visible (private->corners[i], FALSE); + } + + gimp_canvas_item_set_visible (private->center, FALSE); + + for (i = 0; i < 4; i++) + { + gimp_canvas_item_set_visible (private->creating_corners[i], FALSE); + gimp_canvas_item_set_highlight (private->creating_corners[i], FALSE); + } + + for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT; + i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM; + i++) + { + gimp_canvas_item_set_visible (private->handles[i], FALSE); + gimp_canvas_item_set_visible (private->highlight_handles[i], FALSE); + } + + handle_width = private->corner_handle_w; + handle_height = private->corner_handle_h; + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_MOVING: + if (private->rect_adjusting) + { + /* Mark the center because we snap to it */ + gimp_canvas_handle_set_position (private->center, + (x1 + x2) / 2.0, + (y1 + y2) / 2.0); + gimp_canvas_item_set_visible (private->center, TRUE); + break; + } + + /* else fallthrough */ + + case GIMP_TOOL_RECTANGLE_DEAD: + case GIMP_TOOL_RECTANGLE_CREATING: + case GIMP_TOOL_RECTANGLE_AUTO_SHRINK: + for (i = 0; i < 4; i++) + { + gimp_canvas_corner_set (private->creating_corners[i], + x1, y1, x2 - x1, y2 - y1, + handle_width, handle_height, + private->narrow_mode); + gimp_canvas_item_set_visible (private->creating_corners[i], TRUE); + } + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + handle_width = private->top_and_bottom_handle_w; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + handle_height = private->left_and_right_handle_h; + break; + + default: + break; + } + + if (handle_width >= 3 && + handle_height >= 3 && + private->function >= GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT && + private->function <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM) + { + GimpCanvasItem *corner; + + if (private->rect_adjusting) + corner = private->handles[private->function]; + else + corner = private->highlight_handles[private->function]; + + gimp_canvas_corner_set (corner, + x1, y1, x2 - x1, y2 - y1, + handle_width, handle_height, + private->narrow_mode); + gimp_canvas_item_set_visible (corner, TRUE); + } + + if (private->highlight && ! private->rect_adjusting) + { + GdkRectangle rect; + + rect.x = x1; + rect.y = y1; + rect.width = x2 - x1; + rect.height = y2 - y1; + + gimp_display_shell_set_highlight (shell, &rect, private->highlight_opacity); + } + else + { + gimp_display_shell_set_highlight (shell, NULL, 0.0); + } +} + +gint +gimp_tool_rectangle_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + gdouble snapped_x, snapped_y; + gint snap_x, snap_y; + + /* save existing shape in case of cancellation */ + private->saved_x1 = private->x1; + private->saved_y1 = private->y1; + private->saved_x2 = private->x2; + private->saved_y2 = private->y2; + + gimp_tool_rectangle_setup_snap_offsets (rectangle, coords); + gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL); + + snapped_x = coords->x + snap_x; + snapped_y = coords->y + snap_y; + + private->lastx = snapped_x; + private->lasty = snapped_y; + + if (private->function == GIMP_TOOL_RECTANGLE_CREATING) + { + /* Remember that this rectangle was created from scratch. */ + private->is_new = TRUE; + + private->x1 = private->x2 = snapped_x; + private->y1 = private->y2 = snapped_y; + + /* Unless forced, created rectangles should not be started in + * narrow-mode + */ + if (private->force_narrow_mode) + private->narrow_mode = TRUE; + else + private->narrow_mode = FALSE; + + /* If the rectangle is being modified we want the center on + * fixed_center to be at the center of the currently existing + * rectangle, otherwise we want the point where the user clicked + * to be the center on fixed_center. + */ + private->center_x_on_fixed_center = snapped_x; + private->center_y_on_fixed_center = snapped_y; + + /* When the user toggles modifier keys, we want to keep track of + * what coordinates the "other side" should have. If we are + * creating a rectangle, use the current mouse coordinates as + * the coordinate of the "other side", otherwise use the + * immediate "other side" for that. + */ + private->other_side_x = snapped_x; + private->other_side_y = snapped_y; + } + else + { + /* This rectangle was not created from scratch. */ + private->is_new = FALSE; + + private->center_x_on_fixed_center = (private->x1 + private->x2) / 2; + private->center_y_on_fixed_center = (private->y1 + private->y2) / 2; + + gimp_tool_rectangle_get_other_side_coord (rectangle, + &private->other_side_x, + &private->other_side_y); + } + + gimp_tool_rectangle_update_int_rect (rectangle); + + /* Is the rectangle being rubber-banded? */ + private->rect_adjusting = gimp_tool_rectangle_rect_adjusting_func (rectangle); + + gimp_tool_rectangle_changed (widget); + + gimp_tool_rectangle_update_status (rectangle); + + return 1; +} + +void +gimp_tool_rectangle_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + gint response = 0; + + gimp_tool_widget_set_status (widget, NULL); + + /* On button release, we are not rubber-banding the rectangle any longer. */ + private->rect_adjusting = FALSE; + + gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0); + + switch (release_type) + { + case GIMP_BUTTON_RELEASE_NO_MOTION: + /* If the first created rectangle was not expanded, halt the + * tool... + */ + if (gimp_tool_rectangle_rectangle_is_first (rectangle)) + { + response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL; + break; + } + + /* ...else fallthrough and treat a long click without movement + * like a normal change + */ + + case GIMP_BUTTON_RELEASE_NORMAL: + /* If a normal click-drag-release actually created a rectangle + * with content... + */ + if (private->x1 != private->x2 && + private->y1 != private->y2) + { + gimp_tool_rectangle_change_complete (rectangle); + break; + } + + /* ...else fallthrough and undo the operation, we can't have + * zero-extent rectangles + */ + + case GIMP_BUTTON_RELEASE_CANCEL: + private->x1 = private->saved_x1; + private->y1 = private->saved_y1; + private->x2 = private->saved_x2; + private->y2 = private->saved_y2; + + gimp_tool_rectangle_update_int_rect (rectangle); + + /* If the first created rectangle was canceled, halt the tool */ + if (gimp_tool_rectangle_rectangle_is_first (rectangle)) + response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL; + break; + + case GIMP_BUTTON_RELEASE_CLICK: + /* When a dead area is clicked, don't execute. */ + if (private->function != GIMP_TOOL_RECTANGLE_DEAD) + response = GIMP_TOOL_WIDGET_RESPONSE_CONFIRM; + break; + } + + /* We must update this. */ + private->center_x_on_fixed_center = (private->x1 + private->x2) / 2; + private->center_y_on_fixed_center = (private->y1 + private->y2) / 2; + + gimp_tool_rectangle_update_options (rectangle); + + gimp_tool_rectangle_changed (widget); + + private->is_first = FALSE; + + /* emit response at the end, so everything is up to date even if + * a signal handler decides hot to shut down the rectangle + */ + if (response != 0) + gimp_tool_widget_response (widget, response); +} + +void +gimp_tool_rectangle_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + gdouble snapped_x; + gdouble snapped_y; + gint snap_x, snap_y; + + /* Motion events should be ignored when we're just waiting for the + * button release event to execute or if the user has grabbed a dead + * area of the rectangle. + */ + if (private->function == GIMP_TOOL_RECTANGLE_EXECUTING || + private->function == GIMP_TOOL_RECTANGLE_DEAD) + return; + + /* Handle snapping. */ + gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL); + + snapped_x = coords->x + snap_x; + snapped_y = coords->y + snap_y; + + /* This is the core rectangle shape updating function: */ + gimp_tool_rectangle_update_with_coord (rectangle, snapped_x, snapped_y); + + gimp_tool_rectangle_update_status (rectangle); + + if (private->function == GIMP_TOOL_RECTANGLE_CREATING) + { + GimpRectangleFunction function = GIMP_TOOL_RECTANGLE_CREATING; + gdouble dx = snapped_x - private->lastx; + gdouble dy = snapped_y - private->lasty; + + /* When the user starts to move the cursor, set the current + * function to one of the corner-grabbed functions, depending on + * in what direction the user starts dragging the rectangle. + */ + if (dx < 0) + { + function = (dy < 0 ? + GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT : + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT); + } + else if (dx > 0) + { + function = (dy < 0 ? + GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT : + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT); + } + else if (dy < 0) + { + function = (dx < 0 ? + GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT : + GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT); + } + else if (dy > 0) + { + function = (dx < 0 ? + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT : + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT); + } + + gimp_tool_rectangle_set_function (rectangle, function); + + if (private->fixed_rule_active && + private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE) + { + /* For fixed size, set the function to moving immediately since the + * rectangle can not be resized anyway. + */ + + /* We fake a coord update to get the right size. */ + gimp_tool_rectangle_update_with_coord (rectangle, + snapped_x, + snapped_y); + + gimp_tool_widget_set_snap_offsets (widget, + -(private->x2 - private->x1) / 2, + -(private->y2 - private->y1) / 2, + private->x2 - private->x1, + private->y2 - private->y1); + + gimp_tool_rectangle_set_function (rectangle, + GIMP_TOOL_RECTANGLE_MOVING); + } + } + + gimp_tool_rectangle_update_options (rectangle); + + private->lastx = snapped_x; + private->lasty = snapped_y; +} + +GimpHit +gimp_tool_rectangle_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + GimpRectangleFunction function; + + if (private->suppress_updates) + { + function = gimp_tool_rectangle_get_function (rectangle); + } + else + { + function = gimp_tool_rectangle_calc_function (rectangle, + coords, proximity); + } + + switch (function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + return GIMP_HIT_DIRECT; + + case GIMP_TOOL_RECTANGLE_CREATING: + case GIMP_TOOL_RECTANGLE_MOVING: + return GIMP_HIT_INDIRECT; + + case GIMP_TOOL_RECTANGLE_DEAD: + case GIMP_TOOL_RECTANGLE_AUTO_SHRINK: + case GIMP_TOOL_RECTANGLE_EXECUTING: + default: + return GIMP_HIT_NONE; + } +} + +void +gimp_tool_rectangle_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + GimpRectangleFunction function; + + if (private->suppress_updates) + { + private->suppress_updates--; + return; + } + + function = gimp_tool_rectangle_calc_function (rectangle, coords, proximity); + + gimp_tool_rectangle_set_function (rectangle, function); +} + +static void +gimp_tool_rectangle_leave_notify (GimpToolWidget *widget) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + + gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_DEAD); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static gboolean +gimp_tool_rectangle_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + gint dx = 0; + gint dy = 0; + gdouble new_x = 0; + gdouble new_y = 0; + + switch (kevent->keyval) + { + case GDK_KEY_Up: + dy = -1; + break; + case GDK_KEY_Left: + dx = -1; + break; + case GDK_KEY_Right: + dx = 1; + break; + case GDK_KEY_Down: + dy = 1; + break; + + default: + return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent); + } + + /* If the shift key is down, move by an accelerated increment */ + if (kevent->state & gimp_get_extend_selection_mask ()) + { + dx *= ARROW_VELOCITY; + dy *= ARROW_VELOCITY; + } + + gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0); + + /* Resize the rectangle if the mouse is over a handle, otherwise move it */ + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_MOVING: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + new_x = private->x1 + dx; + new_y = private->y1 + dy; + private->lastx = new_x; + private->lasty = new_y; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + new_x = private->x2 + dx; + new_y = private->y1 + dy; + private->lastx = new_x; + private->lasty = new_y; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + new_x = private->x1 + dx; + new_y = private->y2 + dy; + private->lastx = new_x; + private->lasty = new_y; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + new_x = private->x2 + dx; + new_y = private->y2 + dy; + private->lastx = new_x; + private->lasty = new_y; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + new_x = private->x1 + dx; + private->lastx = new_x; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + new_x = private->x2 + dx; + private->lastx = new_x; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + new_y = private->y1 + dy; + private->lasty = new_y; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + new_y = private->y2 + dy; + private->lasty = new_y; + break; + + default: + return TRUE; + } + + gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y); + + private->center_x_on_fixed_center = (private->x1 + private->x2) / 2; + private->center_y_on_fixed_center = (private->y1 + private->y2) / 2; + + gimp_tool_rectangle_update_options (rectangle); + + gimp_tool_rectangle_change_complete (rectangle); + + /* Evil hack to suppress oper updates. We do this because we don't + * want the rectangle tool to change function while the rectangle + * is being resized or moved using the keyboard. + */ + private->suppress_updates = 2; + + return TRUE; +} + +static void +gimp_tool_rectangle_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + gboolean button1_down; + + button1_down = (state & GDK_BUTTON1_MASK); + + if (key == gimp_get_extend_selection_mask ()) + { +#if 0 + /* Here we want to handle manually when to update the rectangle, so we + * don't want gimp_tool_rectangle_options_notify to do anything. + */ + g_signal_handlers_block_by_func (options, + gimp_tool_rectangle_options_notify, + rectangle); +#endif + + g_object_set (rectangle, + "fixed-rule-active", ! private->fixed_rule_active, + NULL); + +#if 0 + g_signal_handlers_unblock_by_func (options, + gimp_tool_rectangle_options_notify, + rectangle); +#endif + + /* Only change the shape if the mouse is still down (i.e. the user is + * still editing the rectangle. + */ + if (button1_down) + { + if (! private->fixed_rule_active) + { + /* Reset anchor point */ + gimp_tool_rectangle_set_other_side_coord (rectangle, + private->other_side_x, + private->other_side_y); + } + + gimp_tool_rectangle_update_with_coord (rectangle, + private->lastx, + private->lasty); + } + } + + if (key == gimp_get_toggle_behavior_mask ()) + { + g_object_set (rectangle, + "fixed-center", ! private->fixed_center, + NULL); + + if (private->fixed_center) + { + gimp_tool_rectangle_update_with_coord (rectangle, + private->lastx, + private->lasty); + + /* Only emit the rectangle-changed signal if the button is + * not down. If it is down, the signal will and shall be + * emitted on _button_release instead. + */ + if (! button1_down) + { + gimp_tool_rectangle_change_complete (rectangle); + } + } + else if (button1_down) + { + /* If we are leaving fixed_center mode we want to set the + * "other side" where it should be. Don't do anything if we + * came here by a mouse-click though, since then the user + * has confirmed the shape and we don't want to modify it + * afterwards. + */ + gimp_tool_rectangle_set_other_side_coord (rectangle, + private->other_side_x, + private->other_side_y); + } + } + + gimp_tool_rectangle_update_options (rectangle); +} + +static gboolean +gimp_tool_rectangle_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget); + GimpToolRectanglePrivate *private = rectangle->private; + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_CREATING: + *cursor = GIMP_CURSOR_CROSSHAIR_SMALL; + break; + case GIMP_TOOL_RECTANGLE_MOVING: + *cursor = GIMP_CURSOR_MOVE; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + *cursor = GIMP_CURSOR_CORNER_TOP_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + *cursor = GIMP_CURSOR_CORNER_TOP_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + *cursor = GIMP_CURSOR_CORNER_BOTTOM_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + *cursor = GIMP_CURSOR_SIDE_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + *cursor = GIMP_CURSOR_SIDE_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + *cursor = GIMP_CURSOR_SIDE_TOP; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + *cursor = GIMP_CURSOR_SIDE_BOTTOM; + break; + + default: + return FALSE; + } + + return TRUE; +} + +static void +gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle) +{ + g_signal_emit (rectangle, rectangle_signals[CHANGE_COMPLETE], 0); +} + +static void +gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gdouble x1, y1; + gdouble x2, y2; + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + +#if 0 + g_signal_handlers_block_by_func (options, + gimp_tool_rectangle_options_notify, + rect_tool); +#endif + + g_object_freeze_notify (G_OBJECT (rectangle)); + + if (! FEQUAL (private->x, x1)) + g_object_set (rectangle, "x", x1, NULL); + + if (! FEQUAL (private->y, y1)) + g_object_set (rectangle, "y", y1, NULL); + + if (! FEQUAL (private->width, x2 - x1)) + g_object_set (rectangle, "width", x2 - x1, NULL); + + if (! FEQUAL (private->height, y2 - y1)) + g_object_set (rectangle, "height", y2 - y1, NULL); + + g_object_thaw_notify (G_OBJECT (rectangle)); + +#if 0 + g_signal_handlers_unblock_by_func (options, + gimp_tool_rectangle_options_notify, + rect_tool); +#endif +} + +static void +gimp_tool_rectangle_update_handle_sizes (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpDisplayShell *shell; + gint visible_rectangle_width; + gint visible_rectangle_height; + gint rectangle_width; + gint rectangle_height; + gdouble pub_x1, pub_y1; + gdouble pub_x2, pub_y2; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + + gimp_tool_rectangle_get_public_rect (rectangle, + &pub_x1, &pub_y1, &pub_x2, &pub_y2); + { + /* Calculate rectangles of the selection rectangle and the display + * shell, with origin at (0, 0) of image, and in screen coordinate + * scale. + */ + gint x1 = pub_x1 * shell->scale_x; + gint y1 = pub_y1 * shell->scale_y; + gint w1 = (pub_x2 - pub_x1) * shell->scale_x; + gint h1 = (pub_y2 - pub_y1) * shell->scale_y; + + gint x2, y2, w2, h2; + + gimp_display_shell_scroll_get_scaled_viewport (shell, &x2, &y2, &w2, &h2); + + rectangle_width = w1; + rectangle_height = h1; + + /* Handle size calculations shall be based on the visible part of + * the rectangle, so calculate the size for the visible rectangle + * by intersecting with the viewport rectangle. + */ + gimp_rectangle_intersect (x1, y1, + w1, h1, + x2, y2, + w2, h2, + NULL, NULL, + &visible_rectangle_width, + &visible_rectangle_height); + + /* Determine if we are in narrow-mode or not. */ + if (private->force_narrow_mode) + private->narrow_mode = TRUE; + else + private->narrow_mode = (visible_rectangle_width < NARROW_MODE_THRESHOLD || + visible_rectangle_height < NARROW_MODE_THRESHOLD); + } + + if (private->narrow_mode) + { + /* Corner handles always have the same (on-screen) size in + * narrow-mode. + */ + private->corner_handle_w = NARROW_MODE_HANDLE_SIZE; + private->corner_handle_h = NARROW_MODE_HANDLE_SIZE; + + private->top_and_bottom_handle_w = CLAMP (rectangle_width, + MIN (rectangle_width - 2, + NARROW_MODE_HANDLE_SIZE), + G_MAXINT); + private->left_and_right_handle_h = CLAMP (rectangle_height, + MIN (rectangle_height - 2, + NARROW_MODE_HANDLE_SIZE), + G_MAXINT); + } + else + { + /* Calculate and clamp corner handle size. */ + + private->corner_handle_w = visible_rectangle_width / 4; + private->corner_handle_h = visible_rectangle_height / 4; + + private->corner_handle_w = CLAMP (private->corner_handle_w, + MIN_HANDLE_SIZE, + MAX_HANDLE_SIZE); + private->corner_handle_h = CLAMP (private->corner_handle_h, + MIN_HANDLE_SIZE, + MAX_HANDLE_SIZE); + + /* Calculate and clamp side handle size. */ + + private->top_and_bottom_handle_w = rectangle_width - 3 * private->corner_handle_w; + private->left_and_right_handle_h = rectangle_height - 3 * private->corner_handle_h; + + private->top_and_bottom_handle_w = CLAMP (private->top_and_bottom_handle_w, + MIN_HANDLE_SIZE, + G_MAXINT); + private->left_and_right_handle_h = CLAMP (private->left_and_right_handle_h, + MIN_HANDLE_SIZE, + G_MAXINT); + } +} + +static void +gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gdouble x1, y1, x2, y2; + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + + if (private->function == GIMP_TOOL_RECTANGLE_MOVING) + { + gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle), + _("Position: "), + x1, ", ", y1, + NULL); + } + else + { + gchar *aspect_text = NULL; + gint width = x2 - x1; + gint height = y2 - y1; + + if (width > 0.0 && height > 0.0) + { + aspect_text = g_strdup_printf (" (%.2f:1)", + (gdouble) width / (gdouble) height); + } + + gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle), + private->status_title, + width, " × ", height, + aspect_text); + g_free (aspect_text); + } +} + +static void +gimp_tool_rectangle_synthesize_motion (GimpToolRectangle *rectangle, + gint function, + gdouble new_x, + gdouble new_y) +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpRectangleFunction old_function; + + /* We don't want to synthesize motions if the tool control is active + * since that means the mouse button is down and the rectangle will + * get updated in _motion anyway. The reason we want to prevent this + * function from executing is that is emits the + * rectangle-changed-complete signal which we don't want in the + * middle of a rectangle change. + * + * In addition to that, we don't want to synthesize a motion if + * there is no pending rectangle because that doesn't make any + * sense. + */ + if (private->rect_adjusting) + return; + + old_function = private->function; + + gimp_tool_rectangle_set_function (rectangle, function); + + gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y); + + /* We must update this. */ + private->center_x_on_fixed_center = (private->x1 + private->x2) / 2; + private->center_y_on_fixed_center = (private->y1 + private->y2) / 2; + + gimp_tool_rectangle_update_options (rectangle); + + gimp_tool_rectangle_set_function (rectangle, old_function); + + gimp_tool_rectangle_change_complete (rectangle); +} + +static void +swap_doubles (gdouble *i, + gdouble *j) +{ + gdouble tmp; + + tmp = *i; + *i = *j; + *j = tmp; +} + +static GimpRectangleFunction +gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle, + const GimpCoords *coords, + gboolean proximity) +{ + if (! proximity) + { + return GIMP_TOOL_RECTANGLE_DEAD; + } + else if (gimp_tool_rectangle_coord_outside (rectangle, coords)) + { + /* The cursor is outside of the rectangle, clicking should + * create a new rectangle. + */ + return GIMP_TOOL_RECTANGLE_CREATING; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_NORTH_WEST)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_SOUTH_EAST)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_NORTH_EAST)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_SOUTH_WEST)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_WEST)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_LEFT; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_EAST)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_RIGHT; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_NORTH)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_TOP; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_SOUTH)) + { + return GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM; + } + else if (gimp_tool_rectangle_coord_on_handle (rectangle, + coords, + GIMP_HANDLE_ANCHOR_CENTER)) + { + return GIMP_TOOL_RECTANGLE_MOVING; + } + else + { + return GIMP_TOOL_RECTANGLE_DEAD; + } +} + +/* gimp_tool_rectangle_check_function() is needed to deal with + * situations where the user drags a corner or edge across one of the + * existing edges, thereby changing its function. Ugh. + */ +static void +gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle) + +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpRectangleFunction function = private->function; + + if (private->x2 < private->x1) + { + swap_doubles (&private->x1, &private->x2); + + switch (function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + function = GIMP_TOOL_RECTANGLE_RESIZING_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + function = GIMP_TOOL_RECTANGLE_RESIZING_LEFT; + break; + /* avoid annoying warnings about unhandled enums */ + default: + break; + } + } + + if (private->y2 < private->y1) + { + swap_doubles (&private->y1, &private->y2); + + switch (function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + function = GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM; + break; + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + function = GIMP_TOOL_RECTANGLE_RESIZING_TOP; + break; + default: + break; + } + } + + gimp_tool_rectangle_set_function (rectangle, function); +} + +/** + * gimp_tool_rectangle_coord_outside: + * + * Returns: %TRUE if the coord is outside the rectangle bounds + * including any outside handles. + */ +static gboolean +gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle, + const GimpCoords *coord) +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpDisplayShell *shell; + gboolean narrow_mode = private->narrow_mode; + gdouble x1, y1, x2, y2; + gdouble x1_b, y1_b, x2_b, y2_b; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + + x1_b = x1 - (narrow_mode ? private->corner_handle_w / shell->scale_x : 0); + x2_b = x2 + (narrow_mode ? private->corner_handle_w / shell->scale_x : 0); + y1_b = y1 - (narrow_mode ? private->corner_handle_h / shell->scale_y : 0); + y2_b = y2 + (narrow_mode ? private->corner_handle_h / shell->scale_y : 0); + + return (coord->x < x1_b || + coord->x > x2_b || + coord->y < y1_b || + coord->y > y2_b); +} + +/** + * gimp_tool_rectangle_coord_on_handle: + * + * Returns: %TRUE if the coord is on the handle that corresponds to + * @anchor. + */ +static gboolean +gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle, + const GimpCoords *coords, + GimpHandleAnchor anchor) +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpDisplayShell *shell; + gdouble x1, y1, x2, y2; + gdouble rect_w, rect_h; + gdouble handle_x = 0; + gdouble handle_y = 0; + gdouble handle_width = 0; + gdouble handle_height = 0; + gint narrow_mode_x_dir = 0; + gint narrow_mode_y_dir = 0; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + + rect_w = x2 - x1; + rect_h = y2 - y1; + + switch (anchor) + { + case GIMP_HANDLE_ANCHOR_NORTH_WEST: + handle_x = x1; + handle_y = y1; + handle_width = private->corner_handle_w; + handle_height = private->corner_handle_h; + + narrow_mode_x_dir = -1; + narrow_mode_y_dir = -1; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_EAST: + handle_x = x2; + handle_y = y2; + handle_width = private->corner_handle_w; + handle_height = private->corner_handle_h; + + narrow_mode_x_dir = 1; + narrow_mode_y_dir = 1; + break; + + case GIMP_HANDLE_ANCHOR_NORTH_EAST: + handle_x = x2; + handle_y = y1; + handle_width = private->corner_handle_w; + handle_height = private->corner_handle_h; + + narrow_mode_x_dir = 1; + narrow_mode_y_dir = -1; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH_WEST: + handle_x = x1; + handle_y = y2; + handle_width = private->corner_handle_w; + handle_height = private->corner_handle_h; + + narrow_mode_x_dir = -1; + narrow_mode_y_dir = 1; + break; + + case GIMP_HANDLE_ANCHOR_WEST: + handle_x = x1; + handle_y = y1 + rect_h / 2; + handle_width = private->corner_handle_w; + handle_height = private->left_and_right_handle_h; + + narrow_mode_x_dir = -1; + narrow_mode_y_dir = 0; + break; + + case GIMP_HANDLE_ANCHOR_EAST: + handle_x = x2; + handle_y = y1 + rect_h / 2; + handle_width = private->corner_handle_w; + handle_height = private->left_and_right_handle_h; + + narrow_mode_x_dir = 1; + narrow_mode_y_dir = 0; + break; + + case GIMP_HANDLE_ANCHOR_NORTH: + handle_x = x1 + rect_w / 2; + handle_y = y1; + handle_width = private->top_and_bottom_handle_w; + handle_height = private->corner_handle_h; + + narrow_mode_x_dir = 0; + narrow_mode_y_dir = -1; + break; + + case GIMP_HANDLE_ANCHOR_SOUTH: + handle_x = x1 + rect_w / 2; + handle_y = y2; + handle_width = private->top_and_bottom_handle_w; + handle_height = private->corner_handle_h; + + narrow_mode_x_dir = 0; + narrow_mode_y_dir = 1; + break; + + case GIMP_HANDLE_ANCHOR_CENTER: + handle_x = x1 + rect_w / 2; + handle_y = y1 + rect_h / 2; + + if (private->narrow_mode) + { + handle_width = rect_w * shell->scale_x; + handle_height = rect_h * shell->scale_y; + } + else + { + handle_width = rect_w * shell->scale_x - private->corner_handle_w * 2; + handle_height = rect_h * shell->scale_y - private->corner_handle_h * 2; + } + + narrow_mode_x_dir = 0; + narrow_mode_y_dir = 0; + break; + } + + if (private->narrow_mode) + { + handle_x += narrow_mode_x_dir * handle_width / shell->scale_x; + handle_y += narrow_mode_y_dir * handle_height / shell->scale_y; + } + + return gimp_canvas_item_on_handle (private->rectangle, + coords->x, coords->y, + GIMP_HANDLE_SQUARE, + handle_x, handle_y, + handle_width, handle_height, + anchor); +} + +static GimpHandleAnchor +gimp_tool_rectangle_get_anchor (GimpRectangleFunction function) +{ + switch (function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + return GIMP_HANDLE_ANCHOR_NORTH_WEST; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + return GIMP_HANDLE_ANCHOR_NORTH_EAST; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + return GIMP_HANDLE_ANCHOR_SOUTH_WEST; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + return GIMP_HANDLE_ANCHOR_SOUTH_EAST; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + return GIMP_HANDLE_ANCHOR_WEST; + + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + return GIMP_HANDLE_ANCHOR_EAST; + + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + return GIMP_HANDLE_ANCHOR_NORTH; + + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + return GIMP_HANDLE_ANCHOR_SOUTH; + + default: + return GIMP_HANDLE_ANCHOR_CENTER; + } +} + +static gboolean +gimp_tool_rectangle_rect_rubber_banding_func (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_CREATING: + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_AUTO_SHRINK: + return TRUE; + + case GIMP_TOOL_RECTANGLE_MOVING: + case GIMP_TOOL_RECTANGLE_DEAD: + default: + break; + } + + return FALSE; +} + +/** + * gimp_tool_rectangle_rect_adjusting_func: + * @rectangle: + * + * Returns: %TRUE if the current function is a rectangle adjusting + * function. + */ +static gboolean +gimp_tool_rectangle_rect_adjusting_func (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + return (gimp_tool_rectangle_rect_rubber_banding_func (rectangle) || + private->function == GIMP_TOOL_RECTANGLE_MOVING); +} + +/** + * gimp_tool_rectangle_get_other_side: + * @rectangle: A #GimpToolRectangle. + * @other_x: Pointer to double of the other-x double. + * @other_y: Pointer to double of the other-y double. + * + * Calculates pointers to member variables that hold the coordinates + * of the opposite side (either the opposite corner or literally the + * opposite side), based on the current function. The opposite of a + * corner needs two coordinates, the opposite of a side only needs + * one. + */ +static void +gimp_tool_rectangle_get_other_side (GimpToolRectangle *rectangle, + gdouble **other_x, + gdouble **other_y) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + *other_x = &private->x1; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + *other_x = &private->x2; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + default: + *other_x = NULL; + break; + } + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + *other_y = &private->y1; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + *other_y = &private->y2; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + default: + *other_y = NULL; + break; + } +} + +static void +gimp_tool_rectangle_get_other_side_coord (GimpToolRectangle *rectangle, + gdouble *other_side_x, + gdouble *other_side_y) +{ + gdouble *other_x = NULL; + gdouble *other_y = NULL; + + gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y); + + if (other_x) + *other_side_x = *other_x; + if (other_y) + *other_side_y = *other_y; +} + +static void +gimp_tool_rectangle_set_other_side_coord (GimpToolRectangle *rectangle, + gdouble other_side_x, + gdouble other_side_y) +{ + gdouble *other_x = NULL; + gdouble *other_y = NULL; + + gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y); + + if (other_x) + *other_x = other_side_x; + if (other_y) + *other_y = other_side_y; + + gimp_tool_rectangle_check_function (rectangle); + + gimp_tool_rectangle_update_int_rect (rectangle); +} + +/** + * gimp_tool_rectangle_apply_coord: + * @param: A #GimpToolRectangle. + * @coord_x: X of coord. + * @coord_y: Y of coord. + * + * Adjust the rectangle to the new position specified by passed + * coordinate, taking fixed_center into account, which means it + * expands the rectangle around the center point. + */ +static void +gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle, + gdouble coord_x, + gdouble coord_y) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + if (private->function == GIMP_TOOL_RECTANGLE_MOVING) + { + /* Preserve width and height while moving the grab-point to where the + * cursor is. + */ + gdouble w = private->x2 - private->x1; + gdouble h = private->y2 - private->y1; + + private->x1 = coord_x; + private->y1 = coord_y; + + private->x2 = private->x1 + w; + private->y2 = private->y1 + h; + + /* We are done already. */ + return; + } + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + private->x1 = coord_x; + + if (private->fixed_center) + private->x2 = 2 * private->center_x_on_fixed_center - private->x1; + + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + private->x2 = coord_x; + + if (private->fixed_center) + private->x1 = 2 * private->center_x_on_fixed_center - private->x2; + + break; + + default: + break; + } + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + private->y1 = coord_y; + + if (private->fixed_center) + private->y2 = 2 * private->center_y_on_fixed_center - private->y1; + + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + private->y2 = coord_y; + + if (private->fixed_center) + private->y1 = 2 * private->center_y_on_fixed_center - private->y2; + + break; + + default: + break; + } +} + +static void +gimp_tool_rectangle_setup_snap_offsets (GimpToolRectangle *rectangle, + const GimpCoords *coords) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (rectangle); + GimpToolRectanglePrivate *private = rectangle->private; + gdouble x1, y1, x2, y2; + gdouble coord_x, coord_y; + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + gimp_tool_rectangle_adjust_coord (rectangle, + coords->x, coords->y, + &coord_x, &coord_y); + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_CREATING: + gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + gimp_tool_widget_set_snap_offsets (widget, + x1 - coord_x, + y1 - coord_y, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + gimp_tool_widget_set_snap_offsets (widget, + x2 - coord_x, + y1 - coord_y, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + gimp_tool_widget_set_snap_offsets (widget, + x1 - coord_x, + y2 - coord_y, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + gimp_tool_widget_set_snap_offsets (widget, + x2 - coord_x, + y2 - coord_y, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + gimp_tool_widget_set_snap_offsets (widget, + x1 - coord_x, 0, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + gimp_tool_widget_set_snap_offsets (widget, + x2 - coord_x, 0, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + gimp_tool_widget_set_snap_offsets (widget, + 0, y1 - coord_y, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + gimp_tool_widget_set_snap_offsets (widget, + 0, y2 - coord_y, + 0, 0); + break; + + case GIMP_TOOL_RECTANGLE_MOVING: + gimp_tool_widget_set_snap_offsets (widget, + x1 - coord_x, + y1 - coord_y, + x2 - x1, + y2 - y1); + break; + + default: + break; + } +} + +/** + * gimp_tool_rectangle_clamp: + * @rectangle: A #GimpToolRectangle. + * @clamped_sides: Where to put contrainment information. + * @constraint: Constraint to use. + * @symmetrically: Whether or not to clamp symmetrically. + * + * Clamps rectangle inside specified bounds, providing information of + * where clamping was done. Can also clamp symmetrically. + */ +static void +gimp_tool_rectangle_clamp (GimpToolRectangle *rectangle, + ClampedSide *clamped_sides, + GimpRectangleConstraint constraint, + gboolean symmetrically) +{ + gimp_tool_rectangle_clamp_width (rectangle, + clamped_sides, + constraint, + symmetrically); + + gimp_tool_rectangle_clamp_height (rectangle, + clamped_sides, + constraint, + symmetrically); +} + +/** + * gimp_tool_rectangle_clamp_width: + * @rectangle: A #GimpToolRectangle. + * @clamped_sides: Where to put contrainment information. + * @constraint: Constraint to use. + * @symmetrically: Whether or not to clamp symmetrically. + * + * Clamps height of rectangle. Set symmetrically to true when using + * for fixed_center:ed rectangles, since that will clamp symmetrically + * which is just what is needed. + * + * When this function constrains, it puts what it constrains in + * @constraint. This information is essential when an aspect ratio is + * to be applied. + */ +static void +gimp_tool_rectangle_clamp_width (GimpToolRectangle *rectangle, + ClampedSide *clamped_sides, + GimpRectangleConstraint constraint, + gboolean symmetrically) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gint min_x; + gint max_x; + + if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE) + return; + + gimp_tool_rectangle_get_constraints (rectangle, + &min_x, NULL, + &max_x, NULL, + constraint); + if (private->x1 < min_x) + { + gdouble dx = min_x - private->x1; + + private->x1 += dx; + + if (symmetrically) + private->x2 -= dx; + + if (private->x2 < min_x) + private->x2 = min_x; + + if (clamped_sides) + *clamped_sides |= CLAMPED_LEFT; + } + + if (private->x2 > max_x) + { + gdouble dx = max_x - private->x2; + + private->x2 += dx; + + if (symmetrically) + private->x1 -= dx; + + if (private->x1 > max_x) + private->x1 = max_x; + + if (clamped_sides) + *clamped_sides |= CLAMPED_RIGHT; + } +} + +/** + * gimp_tool_rectangle_clamp_height: + * @rectangle: A #GimpToolRectangle. + * @clamped_sides: Where to put contrainment information. + * @constraint: Constraint to use. + * @symmetrically: Whether or not to clamp symmetrically. + * + * Clamps height of rectangle. Set symmetrically to true when using for + * fixed_center:ed rectangles, since that will clamp symmetrically which is just + * what is needed. + * + * When this function constrains, it puts what it constrains in + * @constraint. This information is essential when an aspect ratio is to be + * applied. + */ +static void +gimp_tool_rectangle_clamp_height (GimpToolRectangle *rectangle, + ClampedSide *clamped_sides, + GimpRectangleConstraint constraint, + gboolean symmetrically) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gint min_y; + gint max_y; + + if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE) + return; + + gimp_tool_rectangle_get_constraints (rectangle, + NULL, &min_y, + NULL, &max_y, + constraint); + if (private->y1 < min_y) + { + gdouble dy = min_y - private->y1; + + private->y1 += dy; + + if (symmetrically) + private->y2 -= dy; + + if (private->y2 < min_y) + private->y2 = min_y; + + if (clamped_sides) + *clamped_sides |= CLAMPED_TOP; + } + + if (private->y2 > max_y) + { + gdouble dy = max_y - private->y2; + + private->y2 += dy; + + if (symmetrically) + private->y1 -= dy; + + if (private->y1 > max_y) + private->y1 = max_y; + + if (clamped_sides) + *clamped_sides |= CLAMPED_BOTTOM; + } +} + +/** + * gimp_tool_rectangle_keep_inside: + * @rectangle: A #GimpToolRectangle. + * + * If the rectangle is outside of the canvas, move it into it. If the rectangle is + * larger than the canvas in any direction, make it fill the canvas in that direction. + */ +static void +gimp_tool_rectangle_keep_inside (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint) +{ + gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint); + gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint); +} + +/** + * gimp_tool_rectangle_keep_inside_horizontally: + * @rectangle: A #GimpToolRectangle. + * @constraint: Constraint to use. + * + * If the rectangle is outside of the given constraint horizontally, move it + * inside. If it is too big to fit inside, make it just as big as the width + * limit. + */ +static void +gimp_tool_rectangle_keep_inside_horizontally (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gint min_x; + gint max_x; + + if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE) + return; + + gimp_tool_rectangle_get_constraints (rectangle, + &min_x, NULL, + &max_x, NULL, + constraint); + + if (max_x - min_x < private->x2 - private->x1) + { + private->x1 = min_x; + private->x2 = max_x; + } + else + { + if (private->x1 < min_x) + { + gdouble dx = min_x - private->x1; + + private->x1 += dx; + private->x2 += dx; + } + if (private->x2 > max_x) + { + gdouble dx = max_x - private->x2; + + private->x1 += dx; + private->x2 += dx; + } + } +} + +/** + * gimp_tool_rectangle_keep_inside_vertically: + * @rectangle: A #GimpToolRectangle. + * @constraint: Constraint to use. + * + * If the rectangle is outside of the given constraint vertically, + * move it inside. If it is too big to fit inside, make it just as big + * as the width limit. + */ +static void +gimp_tool_rectangle_keep_inside_vertically (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gint min_y; + gint max_y; + + if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE) + return; + + gimp_tool_rectangle_get_constraints (rectangle, + NULL, &min_y, + NULL, &max_y, + constraint); + + if (max_y - min_y < private->y2 - private->y1) + { + private->y1 = min_y; + private->y2 = max_y; + } + else + { + if (private->y1 < min_y) + { + gdouble dy = min_y - private->y1; + + private->y1 += dy; + private->y2 += dy; + } + if (private->y2 > max_y) + { + gdouble dy = max_y - private->y2; + + private->y1 += dy; + private->y2 += dy; + } + } +} + +/** + * gimp_tool_rectangle_apply_fixed_width: + * @rectangle: A #GimpToolRectangle. + * @constraint: Constraint to use. + * @width: + * + * Makes the rectangle have a fixed_width, following the constrainment + * rules of fixed widths as well. Please refer to the rectangle tools + * spec. + */ +static void +gimp_tool_rectangle_apply_fixed_width (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint, + gdouble width) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + /* We always want to center around fixed_center here, since we want the + * anchor point to be directly on the opposite side. + */ + private->x1 = private->center_x_on_fixed_center - + width / 2; + private->x2 = private->x1 + width; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + /* We always want to center around fixed_center here, since we want the + * anchor point to be directly on the opposite side. + */ + private->x1 = private->center_x_on_fixed_center - + width / 2; + private->x2 = private->x1 + width; + break; + + default: + break; + } + + /* Width shall be kept even after constraints, so we move the + * rectangle sideways rather than adjusting a side. + */ + gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint); +} + +/** + * gimp_tool_rectangle_apply_fixed_height: + * @rectangle: A #GimpToolRectangle. + * @constraint: Constraint to use. + * @height: + * + * Makes the rectangle have a fixed_height, following the + * constrainment rules of fixed heights as well. Please refer to the + * rectangle tools spec. + */ +static void +gimp_tool_rectangle_apply_fixed_height (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint, + gdouble height) + +{ + GimpToolRectanglePrivate *private = rectangle->private; + + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + /* We always want to center around fixed_center here, since we + * want the anchor point to be directly on the opposite side. + */ + private->y1 = private->center_y_on_fixed_center - + height / 2; + private->y2 = private->y1 + height; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + /* We always want to center around fixed_center here, since we + * want the anchor point to be directly on the opposite side. + */ + private->y1 = private->center_y_on_fixed_center - + height / 2; + private->y2 = private->y1 + height; + break; + + default: + break; + } + + /* Width shall be kept even after constraints, so we move the + * rectangle sideways rather than adjusting a side. + */ + gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint); +} + +/** + * gimp_tool_rectangle_apply_aspect: + * @rectangle: A #GimpToolRectangle. + * @aspect: The desired aspect. + * @clamped_sides: Bitfield of sides that have been clamped. + * + * Adjust the rectangle to the desired aspect. + * + * Sometimes, a side must not be moved outwards, for example if a the + * RIGHT side has been clamped previously, we must not move the RIGHT + * side to the right, since that would violate the constraint + * again. The clamped_sides bitfield keeps track of sides that have + * previously been clamped. + * + * If fixed_center is used, the function adjusts the aspect by + * symmetrically adjusting the left and right, or top and bottom side. + */ +static void +gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle, + gdouble aspect, + gint clamped_sides) +{ + GimpToolRectanglePrivate *private = rectangle->private; + gdouble current_w; + gdouble current_h; + gdouble current_aspect; + SideToResize side_to_resize = SIDE_TO_RESIZE_NONE; + + current_w = private->x2 - private->x1; + current_h = private->y2 - private->y1; + + current_aspect = (gdouble) current_w / (gdouble) current_h; + + /* Do we have to do anything? */ + if (current_aspect == aspect) + return; + + if (private->fixed_center) + { + /* We may only adjust the sides symmetrically to get desired aspect. */ + if (current_aspect > aspect) + { + /* We prefer to use top and bottom (since that will make the + * cursor remain on the rectangle edge), unless that is what + * the user has grabbed. + */ + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + if (! (clamped_sides & CLAMPED_TOP) && + ! (clamped_sides & CLAMPED_BOTTOM)) + { + side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY; + } + else + { + side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY; + } + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + default: + side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY; + break; + } + } + else /* (current_aspect < aspect) */ + { + /* We prefer to use left and right (since that will make the + * cursor remain on the rectangle edge), unless that is what + * the user has grabbed. + */ + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + if (! (clamped_sides & CLAMPED_LEFT) && + ! (clamped_sides & CLAMPED_RIGHT)) + { + side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY; + } + else + { + side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY; + } + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + default: + side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY; + break; + } + } + } + else if (current_aspect > aspect) + { + /* We can safely pick LEFT or RIGHT, since using those sides + * will make the rectangle smaller, so we don't need to check + * for clamped_sides. We may only use TOP and BOTTOM if not + * those sides have been clamped, since using them will make the + * rectangle bigger. + */ + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + if (! (clamped_sides & CLAMPED_TOP)) + side_to_resize = SIDE_TO_RESIZE_TOP; + else + side_to_resize = SIDE_TO_RESIZE_LEFT; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + if (! (clamped_sides & CLAMPED_TOP)) + side_to_resize = SIDE_TO_RESIZE_TOP; + else + side_to_resize = SIDE_TO_RESIZE_RIGHT; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + if (! (clamped_sides & CLAMPED_BOTTOM)) + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + else + side_to_resize = SIDE_TO_RESIZE_LEFT; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + if (! (clamped_sides & CLAMPED_BOTTOM)) + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + else + side_to_resize = SIDE_TO_RESIZE_RIGHT; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + if (! (clamped_sides & CLAMPED_TOP) && + ! (clamped_sides & CLAMPED_BOTTOM)) + side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY; + else + side_to_resize = SIDE_TO_RESIZE_LEFT; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + if (! (clamped_sides & CLAMPED_TOP) && + ! (clamped_sides & CLAMPED_BOTTOM)) + side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY; + else + side_to_resize = SIDE_TO_RESIZE_RIGHT; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY; + break; + + case GIMP_TOOL_RECTANGLE_MOVING: + default: + if (! (clamped_sides & CLAMPED_BOTTOM)) + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + else if (! (clamped_sides & CLAMPED_RIGHT)) + side_to_resize = SIDE_TO_RESIZE_RIGHT; + else if (! (clamped_sides & CLAMPED_TOP)) + side_to_resize = SIDE_TO_RESIZE_TOP; + else if (! (clamped_sides & CLAMPED_LEFT)) + side_to_resize = SIDE_TO_RESIZE_LEFT; + break; + } + } + else /* (current_aspect < aspect) */ + { + /* We can safely pick TOP or BOTTOM, since using those sides + * will make the rectangle smaller, so we don't need to check + * for clamped_sides. We may only use LEFT and RIGHT if not + * those sides have been clamped, since using them will make the + * rectangle bigger. + */ + switch (private->function) + { + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT: + if (! (clamped_sides & CLAMPED_LEFT)) + side_to_resize = SIDE_TO_RESIZE_LEFT; + else + side_to_resize = SIDE_TO_RESIZE_TOP; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT: + if (! (clamped_sides & CLAMPED_RIGHT)) + side_to_resize = SIDE_TO_RESIZE_RIGHT; + else + side_to_resize = SIDE_TO_RESIZE_TOP; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT: + if (! (clamped_sides & CLAMPED_LEFT)) + side_to_resize = SIDE_TO_RESIZE_LEFT; + else + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT: + if (! (clamped_sides & CLAMPED_RIGHT)) + side_to_resize = SIDE_TO_RESIZE_RIGHT; + else + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_TOP: + if (! (clamped_sides & CLAMPED_LEFT) && + ! (clamped_sides & CLAMPED_RIGHT)) + side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY; + else + side_to_resize = SIDE_TO_RESIZE_TOP; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM: + if (! (clamped_sides & CLAMPED_LEFT) && + ! (clamped_sides & CLAMPED_RIGHT)) + side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY; + else + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + break; + + case GIMP_TOOL_RECTANGLE_RESIZING_LEFT: + case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT: + side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY; + break; + + case GIMP_TOOL_RECTANGLE_MOVING: + default: + if (! (clamped_sides & CLAMPED_BOTTOM)) + side_to_resize = SIDE_TO_RESIZE_BOTTOM; + else if (! (clamped_sides & CLAMPED_RIGHT)) + side_to_resize = SIDE_TO_RESIZE_RIGHT; + else if (! (clamped_sides & CLAMPED_TOP)) + side_to_resize = SIDE_TO_RESIZE_TOP; + else if (! (clamped_sides & CLAMPED_LEFT)) + side_to_resize = SIDE_TO_RESIZE_LEFT; + break; + } + } + + /* We now know what side(s) we should resize, so now we just solve + * the aspect equation for that side(s). + */ + switch (side_to_resize) + { + case SIDE_TO_RESIZE_NONE: + return; + + case SIDE_TO_RESIZE_LEFT: + private->x1 = private->x2 - aspect * current_h; + break; + + case SIDE_TO_RESIZE_RIGHT: + private->x2 = private->x1 + aspect * current_h; + break; + + case SIDE_TO_RESIZE_TOP: + private->y1 = private->y2 - current_w / aspect; + break; + + case SIDE_TO_RESIZE_BOTTOM: + private->y2 = private->y1 + current_w / aspect; + break; + + case SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY: + { + gdouble correct_h = current_w / aspect; + + private->y1 = private->center_y_on_fixed_center - correct_h / 2; + private->y2 = private->y1 + correct_h; + } + break; + + case SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY: + { + gdouble correct_w = current_h * aspect; + + private->x1 = private->center_x_on_fixed_center - correct_w / 2; + private->x2 = private->x1 + correct_w; + } + break; + } +} + +/** + * gimp_tool_rectangle_update_with_coord: + * @rectangle: A #GimpToolRectangle. + * @new_x: New X-coordinate in the context of the current function. + * @new_y: New Y-coordinate in the context of the current function. + * + * The core rectangle adjustment function. It updates the rectangle + * for the passed cursor coordinate, taking current function and tool + * options into account. It also updates the current + * private->function if necessary. + */ +static void +gimp_tool_rectangle_update_with_coord (GimpToolRectangle *rectangle, + gdouble new_x, + gdouble new_y) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + /* Move the corner or edge the user currently has grabbed. */ + gimp_tool_rectangle_apply_coord (rectangle, new_x, new_y); + + /* Update private->function. The function changes if the user + * "flips" the rectangle. + */ + gimp_tool_rectangle_check_function (rectangle); + + /* Clamp the rectangle if necessary */ + gimp_tool_rectangle_handle_general_clamping (rectangle); + + /* If the rectangle is being moved, do not run through any further + * rectangle adjusting functions since it's shape should not change + * then. + */ + if (private->function != GIMP_TOOL_RECTANGLE_MOVING) + { + gimp_tool_rectangle_apply_fixed_rule (rectangle); + } + + gimp_tool_rectangle_update_int_rect (rectangle); +} + +static void +gimp_tool_rectangle_apply_fixed_rule (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpRectangleConstraint constraint_to_use; + GimpDisplayShell *shell; + GimpImage *image; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + image = gimp_display_get_image (shell->display); + + /* Calculate what constraint to use when needed. */ + constraint_to_use = gimp_tool_rectangle_get_constraint (rectangle); + + if (private->fixed_rule_active && + private->fixed_rule == GIMP_RECTANGLE_FIXED_ASPECT) + { + gdouble aspect; + + aspect = CLAMP (private->aspect_numerator / + private->aspect_denominator, + 1.0 / gimp_image_get_height (image), + gimp_image_get_width (image)); + + if (constraint_to_use == GIMP_RECTANGLE_CONSTRAIN_NONE) + { + gimp_tool_rectangle_apply_aspect (rectangle, + aspect, + CLAMPED_NONE); + } + else + { + if (private->function != GIMP_TOOL_RECTANGLE_MOVING) + { + ClampedSide clamped_sides = CLAMPED_NONE; + + gimp_tool_rectangle_apply_aspect (rectangle, + aspect, + clamped_sides); + + /* After we have applied aspect, we might have taken the + * rectangle outside of constraint, so clamp and apply + * aspect again. We will get the right result this time, + * since 'clamped_sides' will be setup correctly now. + */ + gimp_tool_rectangle_clamp (rectangle, + &clamped_sides, + constraint_to_use, + private->fixed_center); + + gimp_tool_rectangle_apply_aspect (rectangle, + aspect, + clamped_sides); + } + else + { + gimp_tool_rectangle_apply_aspect (rectangle, + aspect, + CLAMPED_NONE); + + gimp_tool_rectangle_keep_inside (rectangle, + constraint_to_use); + } + } + } + else if (private->fixed_rule_active && + private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE) + { + gimp_tool_rectangle_apply_fixed_width (rectangle, + constraint_to_use, + private->desired_fixed_size_width); + gimp_tool_rectangle_apply_fixed_height (rectangle, + constraint_to_use, + private->desired_fixed_size_height); + } + else if (private->fixed_rule_active && + private->fixed_rule == GIMP_RECTANGLE_FIXED_WIDTH) + { + gimp_tool_rectangle_apply_fixed_width (rectangle, + constraint_to_use, + private->desired_fixed_width); + } + else if (private->fixed_rule_active && + private->fixed_rule == GIMP_RECTANGLE_FIXED_HEIGHT) + { + gimp_tool_rectangle_apply_fixed_height (rectangle, + constraint_to_use, + private->desired_fixed_height); + } +} + +/** + * gimp_tool_rectangle_get_constraints: + * @rectangle: A #GimpToolRectangle. + * @min_x: + * @min_y: + * @max_x: + * @max_y: Pointers of where to put constraints. NULL allowed. + * @constraint: Whether to return image or layer constraints. + * + * Calculates constraint coordinates for image or layer. + */ +static void +gimp_tool_rectangle_get_constraints (GimpToolRectangle *rectangle, + gint *min_x, + gint *min_y, + gint *max_x, + gint *max_y, + GimpRectangleConstraint constraint) +{ + GimpDisplayShell *shell; + GimpImage *image; + gint min_x_dummy; + gint min_y_dummy; + gint max_x_dummy; + gint max_y_dummy; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + image = gimp_display_get_image (shell->display); + + if (! min_x) min_x = &min_x_dummy; + if (! min_y) min_y = &min_y_dummy; + if (! max_x) max_x = &max_x_dummy; + if (! max_y) max_y = &max_y_dummy; + + *min_x = 0; + *min_y = 0; + *max_x = 0; + *max_y = 0; + + switch (constraint) + { + case GIMP_RECTANGLE_CONSTRAIN_IMAGE: + if (image) + { + *min_x = 0; + *min_y = 0; + *max_x = gimp_image_get_width (image); + *max_y = gimp_image_get_height (image); + } + break; + + case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE: + if (image) + { + GimpItem *item = GIMP_ITEM (gimp_image_get_active_drawable (image)); + + if (item) + { + gimp_item_get_offset (item, min_x, min_y); + *max_x = *min_x + gimp_item_get_width (item); + *max_y = *min_y + gimp_item_get_height (item); + } + } + break; + + default: + g_warning ("Invalid rectangle constraint.\n"); + return; + } +} + +/** + * gimp_tool_rectangle_handle_general_clamping: + * @rectangle: A #GimpToolRectangle. + * + * Make sure that constraints are applied to the rectangle, either by + * manually doing it, or by looking at the rectangle tool options and + * concluding it will be done later. + */ +static void +gimp_tool_rectangle_handle_general_clamping (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + GimpRectangleConstraint constraint; + + constraint = gimp_tool_rectangle_get_constraint (rectangle); + + /* fixed_aspect takes care of clamping by it self, so just return in + * case that is in use. Also return if no constraints should be + * enforced. + */ + if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE) + return; + + if (private->function != GIMP_TOOL_RECTANGLE_MOVING) + { + gimp_tool_rectangle_clamp (rectangle, + NULL, + constraint, + private->fixed_center); + } + else + { + gimp_tool_rectangle_keep_inside (rectangle, constraint); + } +} + +/** + * gimp_tool_rectangle_update_int_rect: + * @rectangle: + * + * Update integer representation of rectangle. + **/ +static void +gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle) +{ + GimpToolRectanglePrivate *private = rectangle->private; + + private->x1_int = SIGNED_ROUND (private->x1); + private->y1_int = SIGNED_ROUND (private->y1); + + if (gimp_tool_rectangle_rect_rubber_banding_func (rectangle)) + { + private->width_int = (gint) SIGNED_ROUND (private->x2) - private->x1_int; + private->height_int = (gint) SIGNED_ROUND (private->y2) - private->y1_int; + } +} + +/** + * gimp_tool_rectangle_adjust_coord: + * @rectangle: + * @ccoord_x_input: + * @ccoord_x_input: + * @ccoord_x_output: + * @ccoord_x_output: + * + * Transforms a coordinate to better fit the public behaviour of the + * rectangle. + */ +static void +gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle, + gdouble coord_x_input, + gdouble coord_y_input, + gdouble *coord_x_output, + gdouble *coord_y_output) +{ + GimpToolRectanglePrivate *priv = rectangle->private; + + switch (priv->precision) + { + case GIMP_RECTANGLE_PRECISION_INT: + *coord_x_output = RINT (coord_x_input); + *coord_y_output = RINT (coord_y_input); + break; + + case GIMP_RECTANGLE_PRECISION_DOUBLE: + default: + *coord_x_output = coord_x_input; + *coord_y_output = coord_y_input; + break; + } +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_rectangle_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_RECTANGLE, + "shell", shell, + NULL); +} + +GimpRectangleFunction +gimp_tool_rectangle_get_function (GimpToolRectangle *rectangle) +{ + g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), + GIMP_TOOL_RECTANGLE_DEAD); + + return rectangle->private->function; +} + +void +gimp_tool_rectangle_set_function (GimpToolRectangle *rectangle, + GimpRectangleFunction function) +{ + GimpToolRectanglePrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + + private = rectangle->private; + + if (private->function != function) + { + private->function = function; + + gimp_tool_rectangle_changed (GIMP_TOOL_WIDGET (rectangle)); + } +} + +void +gimp_tool_rectangle_set_constraint (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint) +{ + GimpToolRectanglePrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + + private = rectangle->private; + + if (constraint != private->constraint) + { + g_object_freeze_notify (G_OBJECT (rectangle)); + + private->constraint = constraint; + g_object_notify (G_OBJECT (rectangle), "constraint"); + + gimp_tool_rectangle_clamp (rectangle, NULL, constraint, FALSE); + + g_object_thaw_notify (G_OBJECT (rectangle)); + + gimp_tool_rectangle_change_complete (rectangle); + } +} + +GimpRectangleConstraint +gimp_tool_rectangle_get_constraint (GimpToolRectangle *rectangle) +{ + g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), 0); + + return rectangle->private->constraint; +} + +/** + * gimp_tool_rectangle_get_public_rect: + * @rectangle: + * @x1: + * @y1: + * @x2: + * @y2: + * + * This function returns the rectangle as it appears to be publicly + * (based on integer or double precision-mode). + **/ +void +gimp_tool_rectangle_get_public_rect (GimpToolRectangle *rectangle, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + GimpToolRectanglePrivate *priv; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + g_return_if_fail (x1 != NULL); + g_return_if_fail (y1 != NULL); + g_return_if_fail (x2 != NULL); + g_return_if_fail (y2 != NULL); + + priv = rectangle->private; + + switch (priv->precision) + { + case GIMP_RECTANGLE_PRECISION_INT: + *x1 = priv->x1_int; + *y1 = priv->y1_int; + *x2 = priv->x1_int + priv->width_int; + *y2 = priv->y1_int + priv->height_int; + break; + + case GIMP_RECTANGLE_PRECISION_DOUBLE: + default: + *x1 = priv->x1; + *y1 = priv->y1; + *x2 = priv->x2; + *y2 = priv->y2; + break; + } +} + +/** + * gimp_tool_rectangle_pending_size_set: + * @width_property: Option property to set to pending rectangle width. + * @height_property: Option property to set to pending rectangle height. + * + * Sets specified rectangle tool options properties to the width and + * height of the current pending rectangle. + */ +void +gimp_tool_rectangle_pending_size_set (GimpToolRectangle *rectangle, + GObject *object, + const gchar *width_property, + const gchar *height_property) +{ + GimpToolRectanglePrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + g_return_if_fail (width_property != NULL); + g_return_if_fail (height_property != NULL); + + private = rectangle->private; + + g_object_set (object, + width_property, MAX (private->x2 - private->x1, 1.0), + height_property, MAX (private->y2 - private->y1, 1.0), + NULL); +} + +/** + * gimp_tool_rectangle_constraint_size_set: + * @width_property: Option property to set to current constraint width. + * @height_property: Option property to set to current constraint height. + * + * Sets specified rectangle tool options properties to the width and + * height of the current constraint size. + */ +void +gimp_tool_rectangle_constraint_size_set (GimpToolRectangle *rectangle, + GObject *object, + const gchar *width_property, + const gchar *height_property) +{ + GimpDisplayShell *shell; + GimpContext *context; + GimpImage *image; + gdouble width; + gdouble height; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + context = gimp_get_user_context (shell->display->gimp); + image = gimp_context_get_image (context); + + if (! image) + { + width = 1.0; + height = 1.0; + } + else + { + GimpRectangleConstraint constraint; + + constraint = gimp_tool_rectangle_get_constraint (rectangle); + + switch (constraint) + { + case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE: + { + GimpItem *item = GIMP_ITEM (gimp_image_get_active_layer (image)); + + if (! item) + { + width = 1.0; + height = 1.0; + } + else + { + width = gimp_item_get_width (item); + height = gimp_item_get_height (item); + } + } + break; + + case GIMP_RECTANGLE_CONSTRAIN_IMAGE: + default: + { + width = gimp_image_get_width (image); + height = gimp_image_get_height (image); + } + break; + } + } + + g_object_set (object, + width_property, width, + height_property, height, + NULL); +} + +/** + * gimp_tool_rectangle_rectangle_is_first: + * @rectangle: + * + * Returns: %TRUE if the user is creating the first rectangle with + * this instance from scratch, %FALSE if modifying an existing + * rectangle, or creating a new rectangle, discarding the existing + * one. This function is only meaningful in _motion and + * _button_release. + */ +gboolean +gimp_tool_rectangle_rectangle_is_first (GimpToolRectangle *rectangle) +{ + g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE); + + return rectangle->private->is_first; +} + +/** + * gimp_tool_rectangle_rectangle_is_new: + * @rectangle: + * + * Returns: %TRUE if the user is creating a new rectangle from + * scratch, %FALSE if modifying n previously existing rectangle. This + * function is only meaningful in _motion and _button_release. + */ +gboolean +gimp_tool_rectangle_rectangle_is_new (GimpToolRectangle *rectangle) +{ + g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE); + + return rectangle->private->is_new; +} + +/** + * gimp_tool_rectangle_point_in_rectangle: + * @rectangle: + * @x: X-coord of point to test (in image coordinates) + * @y: Y-coord of point to test (in image coordinates) + * + * Returns: %TRUE if the passed point was within the rectangle + **/ +gboolean +gimp_tool_rectangle_point_in_rectangle (GimpToolRectangle *rectangle, + gdouble x, + gdouble y) +{ + gdouble x1, y1, x2, y2; + + g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE); + + gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2); + + return (x >= x1 && x <= x2 && + y >= y1 && y <= y2); +} + +/** + * gimp_tool_rectangle_frame_item: + * @rectangle: a #GimpToolRectangle interface + * @item: a #GimpItem attached to the image on which a + * rectangle is being shown. + * + * Convenience function to set the corners of the rectangle to + * match the bounds of the specified item. The rectangle interface + * must be active (i.e., showing a rectangle), and the item must be + * attached to the image on which the rectangle is active. + **/ +void +gimp_tool_rectangle_frame_item (GimpToolRectangle *rectangle, + GimpItem *item) +{ + GimpDisplayShell *shell; + gint offset_x; + gint offset_y; + gint width; + gint height; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + g_return_if_fail (GIMP_IS_ITEM (item)); + g_return_if_fail (gimp_item_is_attached (item)); + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + + g_return_if_fail (gimp_display_get_image (shell->display) == + gimp_item_get_image (item)); + + width = gimp_item_get_width (item); + height = gimp_item_get_height (item); + + gimp_item_get_offset (item, &offset_x, &offset_y); + + gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_CREATING); + + g_object_set (rectangle, + "x1", (gdouble) offset_x, + "y1", (gdouble) offset_y, + "x2", (gdouble) (offset_x + width), + "y2", (gdouble) (offset_y + height), + NULL); + + /* kludge to force handle sizes to update. This call may be harmful + * if this function is ever moved out of the text tool code. + */ + gimp_tool_rectangle_set_constraint (rectangle, GIMP_RECTANGLE_CONSTRAIN_NONE); +} + +void +gimp_tool_rectangle_auto_shrink (GimpToolRectangle *rectangle, + gboolean shrink_merged) +{ + GimpToolRectanglePrivate *private; + GimpDisplayShell *shell; + GimpImage *image; + GimpPickable *pickable; + gint offset_x = 0; + gint offset_y = 0; + gint x1, y1; + gint x2, y2; + gint shrunk_x; + gint shrunk_y; + gint shrunk_width; + gint shrunk_height; + + g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle)); + + private = rectangle->private; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle)); + image = gimp_display_get_image (shell->display); + + if (shrink_merged) + { + pickable = GIMP_PICKABLE (image); + + x1 = private->x1; + y1 = private->y1; + x2 = private->x2; + y2 = private->y2; + } + else + { + pickable = GIMP_PICKABLE (gimp_image_get_active_drawable (image)); + + if (! pickable) + return; + + gimp_item_get_offset (GIMP_ITEM (pickable), &offset_x, &offset_y); + + x1 = private->x1 - offset_x; + y1 = private->y1 - offset_y; + x2 = private->x2 - offset_x; + y2 = private->y2 - offset_y; + } + + switch (gimp_pickable_auto_shrink (pickable, + x1, y1, x2 - x1, y2 - y1, + &shrunk_x, + &shrunk_y, + &shrunk_width, + &shrunk_height)) + { + case GIMP_AUTO_SHRINK_SHRINK: + { + GimpRectangleFunction original_function = private->function; + + private->function = GIMP_TOOL_RECTANGLE_AUTO_SHRINK; + + private->x1 = offset_x + shrunk_x; + private->y1 = offset_y + shrunk_y; + private->x2 = offset_x + shrunk_x + shrunk_width; + private->y2 = offset_y + shrunk_y + shrunk_height; + + gimp_tool_rectangle_update_int_rect (rectangle); + + gimp_tool_rectangle_change_complete (rectangle); + + private->function = original_function; + + gimp_tool_rectangle_update_options (rectangle); + } + break; + + default: + break; + } +} diff --git a/app/display/gimptoolrectangle.h b/app/display/gimptoolrectangle.h new file mode 100644 index 0000000..361839a --- /dev/null +++ b/app/display/gimptoolrectangle.h @@ -0,0 +1,124 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolrectangle.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Based on GimpRectangleTool + * Copyright (C) 2007 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_RECTANGLE_H__ +#define __GIMP_TOOL_RECTANGLE_H__ + + +#include "gimptoolwidget.h" + + +typedef enum +{ + GIMP_TOOL_RECTANGLE_DEAD, + GIMP_TOOL_RECTANGLE_CREATING, + GIMP_TOOL_RECTANGLE_MOVING, + GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT, + GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT, + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT, + GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT, + GIMP_TOOL_RECTANGLE_RESIZING_LEFT, + GIMP_TOOL_RECTANGLE_RESIZING_RIGHT, + GIMP_TOOL_RECTANGLE_RESIZING_TOP, + GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM, + GIMP_TOOL_RECTANGLE_AUTO_SHRINK, + GIMP_TOOL_RECTANGLE_EXECUTING, + GIMP_N_TOOL_RECTANGLE_FUNCTIONS +} GimpRectangleFunction; + + +#define GIMP_TYPE_TOOL_RECTANGLE (gimp_tool_rectangle_get_type ()) +#define GIMP_TOOL_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_RECTANGLE, GimpToolRectangle)) +#define GIMP_TOOL_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_RECTANGLE, GimpToolRectangleClass)) +#define GIMP_IS_TOOL_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_RECTANGLE)) +#define GIMP_IS_TOOL_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_RECTANGLE)) +#define GIMP_TOOL_RECTANGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_RECTANGLE, GimpToolRectangleClass)) + + +typedef struct _GimpToolRectangle GimpToolRectangle; +typedef struct _GimpToolRectanglePrivate GimpToolRectanglePrivate; +typedef struct _GimpToolRectangleClass GimpToolRectangleClass; + +struct _GimpToolRectangle +{ + GimpToolWidget parent_instance; + + GimpToolRectanglePrivate *private; +}; + +struct _GimpToolRectangleClass +{ + GimpToolWidgetClass parent_class; + + /* signals */ + + gboolean (* change_complete) (GimpToolRectangle *rectangle); +}; + + +GType gimp_tool_rectangle_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_rectangle_new (GimpDisplayShell *shell); + +GimpRectangleFunction + gimp_tool_rectangle_get_function (GimpToolRectangle *rectangle); +void gimp_tool_rectangle_set_function (GimpToolRectangle *rectangle, + GimpRectangleFunction function); + +void gimp_tool_rectangle_set_constraint (GimpToolRectangle *rectangle, + GimpRectangleConstraint constraint); +GimpRectangleConstraint + gimp_tool_rectangle_get_constraint (GimpToolRectangle *rectangle); + +void gimp_tool_rectangle_get_public_rect (GimpToolRectangle *rectangle, + gdouble *pub_x1, + gdouble *pub_y1, + gdouble *pub_x2, + gdouble *pub_y2); + +void gimp_tool_rectangle_pending_size_set (GimpToolRectangle *rectangle, + GObject *object, + const gchar *width_property, + const gchar *height_property); + +void gimp_tool_rectangle_constraint_size_set + (GimpToolRectangle *rectangle, + GObject *object, + const gchar *width_property, + const gchar *height_property); + +gboolean gimp_tool_rectangle_rectangle_is_first + (GimpToolRectangle *rectangle); +gboolean gimp_tool_rectangle_rectangle_is_new (GimpToolRectangle *rectangle); +gboolean gimp_tool_rectangle_point_in_rectangle + (GimpToolRectangle *rectangle, + gdouble x, + gdouble y); + +void gimp_tool_rectangle_frame_item (GimpToolRectangle *rectangle, + GimpItem *item); +void gimp_tool_rectangle_auto_shrink (GimpToolRectangle *rectrectangle, + gboolean shrink_merged); + + +#endif /* __GIMP_TOOL_RECTANGLE_H__ */ diff --git a/app/display/gimptoolrotategrid.c b/app/display/gimptoolrotategrid.c new file mode 100644 index 0000000..c583490 --- /dev/null +++ b/app/display/gimptoolrotategrid.c @@ -0,0 +1,330 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolrotategrid.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "gimpdisplayshell.h" +#include "gimptoolrotategrid.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_ANGLE +}; + + +struct _GimpToolRotateGridPrivate +{ + gdouble angle; + + gboolean rotate_grab; + gdouble real_angle; + gdouble last_x; + gdouble last_y; +}; + + +/* local function prototypes */ + +static void gimp_tool_rotate_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_rotate_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint gimp_tool_rotate_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_rotate_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolRotateGrid, gimp_tool_rotate_grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID) + +#define parent_class gimp_tool_rotate_grid_parent_class + + +static void +gimp_tool_rotate_grid_class_init (GimpToolRotateGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->set_property = gimp_tool_rotate_grid_set_property; + object_class->get_property = gimp_tool_rotate_grid_get_property; + + widget_class->button_press = gimp_tool_rotate_grid_button_press; + widget_class->motion = gimp_tool_rotate_grid_motion; + + g_object_class_install_property (object_class, PROP_ANGLE, + g_param_spec_double ("angle", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_rotate_grid_init (GimpToolRotateGrid *grid) +{ + grid->private = gimp_tool_rotate_grid_get_instance_private (grid); +} + +static void +gimp_tool_rotate_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (object); + GimpToolRotateGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ANGLE: + private->angle = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_rotate_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (object); + GimpToolRotateGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ANGLE: + g_value_set_double (value, private->angle); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +gimp_tool_rotate_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (widget); + GimpToolRotateGridPrivate *private = grid->private; + GimpTransformHandle handle; + + handle = GIMP_TOOL_WIDGET_CLASS (parent_class)->button_press (widget, + coords, time, + state, + press_type); + + if (handle == GIMP_TRANSFORM_HANDLE_ROTATION) + { + private->rotate_grab = TRUE; + private->real_angle = private->angle; + private->last_x = coords->x; + private->last_y = coords->y; + } + else + { + private->rotate_grab = FALSE; + } + + return handle; +} + +void +gimp_tool_rotate_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (widget); + GimpToolRotateGridPrivate *private = grid->private; + gdouble angle1, angle2, angle; + gdouble pivot_x, pivot_y; + gdouble x1, y1, x2, y2; + gboolean constrain; + GimpMatrix3 transform; + + if (! private->rotate_grab) + { + gdouble old_pivot_x; + gdouble old_pivot_y; + + g_object_get (widget, + "pivot-x", &old_pivot_x, + "pivot-y", &old_pivot_y, + NULL); + + g_object_freeze_notify (G_OBJECT (widget)); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->motion (widget, + coords, time, state); + + g_object_get (widget, + "pivot-x", &pivot_x, + "pivot-y", &pivot_y, + NULL); + + if (old_pivot_x != pivot_x || + old_pivot_y != pivot_y) + { + gimp_matrix3_identity (&transform); + gimp_transform_matrix_rotate_center (&transform, + pivot_x, pivot_y, + private->angle); + + g_object_set (widget, + "transform", &transform, + NULL); + } + + g_object_thaw_notify (G_OBJECT (widget)); + + return; + } + + g_object_get (widget, + "pivot-x", &pivot_x, + "pivot-y", &pivot_y, + "constrain-rotate", &constrain, + NULL); + + x1 = coords->x - pivot_x; + x2 = private->last_x - pivot_x; + y1 = pivot_y - coords->y; + y2 = pivot_y - private->last_y; + + /* find the first angle */ + angle1 = atan2 (y1, x1); + + /* find the angle */ + angle2 = atan2 (y2, x2); + + angle = angle2 - angle1; + + if (angle > G_PI || angle < -G_PI) + angle = angle2 - ((angle1 < 0) ? 2.0 * G_PI + angle1 : angle1 - 2.0 * G_PI); + + /* increment the transform tool's angle */ + private->real_angle += angle; + + /* limit the angle to between -180 and 180 degrees */ + if (private->real_angle < - G_PI) + { + private->real_angle += 2.0 * G_PI; + } + else if (private->real_angle > G_PI) + { + private->real_angle -= 2.0 * G_PI; + } + + /* constrain the angle to 15-degree multiples if ctrl is held down */ +#define FIFTEEN_DEG (G_PI / 12.0) + + if (constrain) + { + angle = FIFTEEN_DEG * (gint) ((private->real_angle + + FIFTEEN_DEG / 2.0) / FIFTEEN_DEG); + } + else + { + angle = private->real_angle; + } + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_rotate_center (&transform, pivot_x, pivot_y, angle); + + g_object_set (widget, + "transform", &transform, + "angle", angle, + NULL); + + private->last_x = coords->x; + private->last_y = coords->y; +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_rotate_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble pivot_x, + gdouble pivot_y, + gdouble angle) +{ + GimpMatrix3 transform; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_rotate_center (&transform, pivot_x, pivot_y, angle); + + return g_object_new (GIMP_TYPE_TOOL_ROTATE_GRID, + "shell", shell, + "transform", &transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "pivot-x", pivot_x, + "pivot-y", pivot_y, + "angle", angle, + NULL); +} diff --git a/app/display/gimptoolrotategrid.h b/app/display/gimptoolrotategrid.h new file mode 100644 index 0000000..2254c8d --- /dev/null +++ b/app/display/gimptoolrotategrid.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolrotategrid.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_ROTATE_GRID_H__ +#define __GIMP_TOOL_ROTATE_GRID_H__ + + +#include "gimptooltransformgrid.h" + + +#define GIMP_TYPE_TOOL_ROTATE_GRID (gimp_tool_rotate_grid_get_type ()) +#define GIMP_TOOL_ROTATE_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGrid)) +#define GIMP_TOOL_ROTATE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGridClass)) +#define GIMP_IS_TOOL_ROTATE_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_ROTATE_GRID)) +#define GIMP_IS_TOOL_ROTATE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_ROTATE_GRID)) +#define GIMP_TOOL_ROTATE_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGridClass)) + + +typedef struct _GimpToolRotateGrid GimpToolRotateGrid; +typedef struct _GimpToolRotateGridPrivate GimpToolRotateGridPrivate; +typedef struct _GimpToolRotateGridClass GimpToolRotateGridClass; + +struct _GimpToolRotateGrid +{ + GimpToolTransformGrid parent_instance; + + GimpToolRotateGridPrivate *private; +}; + +struct _GimpToolRotateGridClass +{ + GimpToolTransformGridClass parent_class; +}; + + +GType gimp_tool_rotate_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_rotate_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble pivot_x, + gdouble pivot_y, + gdouble angle); + + +#endif /* __GIMP_TOOL_ROTATE_GRID_H__ */ diff --git a/app/display/gimptoolsheargrid.c b/app/display/gimptoolsheargrid.c new file mode 100644 index 0000000..878c549 --- /dev/null +++ b/app/display/gimptoolsheargrid.c @@ -0,0 +1,360 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolsheargrid.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "gimpdisplayshell.h" +#include "gimptoolsheargrid.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_SHEAR_X, + PROP_SHEAR_Y +}; + + +struct _GimpToolShearGridPrivate +{ + GimpOrientationType orientation; + gdouble shear_x; + gdouble shear_y; + + gdouble last_x; + gdouble last_y; +}; + + +/* local function prototypes */ + +static void gimp_tool_shear_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_shear_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint gimp_tool_shear_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_shear_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolShearGrid, gimp_tool_shear_grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID) + +#define parent_class gimp_tool_shear_grid_parent_class + + +static void +gimp_tool_shear_grid_class_init (GimpToolShearGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->set_property = gimp_tool_shear_grid_set_property; + object_class->get_property = gimp_tool_shear_grid_get_property; + + widget_class->button_press = gimp_tool_shear_grid_button_press; + widget_class->motion = gimp_tool_shear_grid_motion; + + g_object_class_install_property (object_class, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + NULL, NULL, + GIMP_TYPE_ORIENTATION_TYPE, + GIMP_ORIENTATION_UNKNOWN, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SHEAR_X, + g_param_spec_double ("shear-x", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SHEAR_Y, + g_param_spec_double ("shear-y", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_shear_grid_init (GimpToolShearGrid *grid) +{ + grid->private = gimp_tool_shear_grid_get_instance_private (grid); + + g_object_set (grid, + "inside-function", GIMP_TRANSFORM_FUNCTION_SHEAR, + "outside-function", GIMP_TRANSFORM_FUNCTION_SHEAR, + "use-corner-handles", FALSE, + "use-perspective-handles", FALSE, + "use-side-handles", FALSE, + "use-shear-handles", FALSE, + "use-center-handle", FALSE, + "use-pivot-handle", FALSE, + NULL); +} + +static void +gimp_tool_shear_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (object); + GimpToolShearGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ORIENTATION: + private->orientation = g_value_get_enum (value); + break; + case PROP_SHEAR_X: + private->shear_x = g_value_get_double (value); + break; + case PROP_SHEAR_Y: + private->shear_y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_shear_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (object); + GimpToolShearGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, private->orientation); + break; + case PROP_SHEAR_X: + g_value_set_double (value, private->shear_x); + break; + case PROP_SHEAR_Y: + g_value_set_double (value, private->shear_y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +gimp_tool_shear_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (widget); + GimpToolShearGridPrivate *private = grid->private; + + private->last_x = coords->x; + private->last_y = coords->y; + + return 1; +} + +void +gimp_tool_shear_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (widget); + GimpToolShearGridPrivate *private = grid->private; + gdouble diffx = coords->x - private->last_x; + gdouble diffy = coords->y - private->last_y; + gdouble amount = 0.0; + GimpMatrix3 transform; + GimpMatrix3 *t; + gdouble x1, y1; + gdouble x2, y2; + gdouble tx1, ty1; + gdouble tx2, ty2; + gdouble tx3, ty3; + gdouble tx4, ty4; + gdouble current_x; + gdouble current_y; + + g_object_get (widget, + "transform", &t, + "x1", &x1, + "y1", &y1, + "x2", &x2, + "y2", &y2, + NULL); + + gimp_matrix3_transform_point (t, x1, y1, &tx1, &ty1); + gimp_matrix3_transform_point (t, x2, y1, &tx2, &ty2); + gimp_matrix3_transform_point (t, x1, y2, &tx3, &ty3); + gimp_matrix3_transform_point (t, x2, y2, &tx4, &ty4); + + g_free (t); + + current_x = coords->x; + current_y = coords->y; + + diffx = current_x - private->last_x; + diffy = current_y - private->last_y; + + /* If we haven't yet decided on which way to control shearing + * decide using the maximum differential + */ + if (private->orientation == GIMP_ORIENTATION_UNKNOWN) + { +#define MIN_MOVE 5 + + if (ABS (diffx) > MIN_MOVE || ABS (diffy) > MIN_MOVE) + { + if (ABS (diffx) > ABS (diffy)) + { + private->orientation = GIMP_ORIENTATION_HORIZONTAL; + private->shear_x = 0.0; + } + else + { + private->orientation = GIMP_ORIENTATION_VERTICAL; + private->shear_y = 0.0; + } + } + /* set the current coords to the last ones */ + else + { + current_x = private->last_x; + current_y = private->last_y; + } + } + + /* if the direction is known, keep track of the magnitude */ + if (private->orientation == GIMP_ORIENTATION_HORIZONTAL) + { + if (current_y > (ty1 + ty3) / 2) + private->shear_x += diffx; + else + private->shear_x -= diffx; + + amount = private->shear_x; + } + else if (private->orientation == GIMP_ORIENTATION_VERTICAL) + { + if (current_x > (tx1 + tx2) / 2) + private->shear_y += diffy; + else + private->shear_y -= diffy; + + amount = private->shear_y; + } + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_shear (&transform, + x1, y1, x2 - x1, y2 - y1, + private->orientation, amount); + + g_object_set (widget, + "transform", &transform, + "orientation", private->orientation, + "shear-x", private->shear_x, + "shear_y", private->shear_y, + NULL); + + private->last_x = current_x; + private->last_y = current_y; +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_shear_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpOrientationType orientation, + gdouble shear_x, + gdouble shear_y) +{ + GimpMatrix3 transform; + gdouble amount; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + if (orientation == GIMP_ORIENTATION_HORIZONTAL) + amount = shear_x; + else + amount = shear_y; + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_shear (&transform, + x1, y1, x2 - x1, y2 - y1, + orientation, amount); + + return g_object_new (GIMP_TYPE_TOOL_SHEAR_GRID, + "shell", shell, + "transform", &transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "orientation", orientation, + "shear-x", shear_x, + "shear-y", shear_y, + NULL); +} diff --git a/app/display/gimptoolsheargrid.h b/app/display/gimptoolsheargrid.h new file mode 100644 index 0000000..dab96d5 --- /dev/null +++ b/app/display/gimptoolsheargrid.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolsheargrid.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_SHEAR_GRID_H__ +#define __GIMP_TOOL_SHEAR_GRID_H__ + + +#include "gimptooltransformgrid.h" + + +#define GIMP_TYPE_TOOL_SHEAR_GRID (gimp_tool_shear_grid_get_type ()) +#define GIMP_TOOL_SHEAR_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGrid)) +#define GIMP_TOOL_SHEAR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGridClass)) +#define GIMP_IS_TOOL_SHEAR_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_SHEAR_GRID)) +#define GIMP_IS_TOOL_SHEAR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_SHEAR_GRID)) +#define GIMP_TOOL_SHEAR_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGridClass)) + + +typedef struct _GimpToolShearGrid GimpToolShearGrid; +typedef struct _GimpToolShearGridPrivate GimpToolShearGridPrivate; +typedef struct _GimpToolShearGridClass GimpToolShearGridClass; + +struct _GimpToolShearGrid +{ + GimpToolTransformGrid parent_instance; + + GimpToolShearGridPrivate *private; +}; + +struct _GimpToolShearGridClass +{ + GimpToolTransformGridClass parent_class; +}; + + +GType gimp_tool_shear_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_shear_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpOrientationType orientation, + gdouble shear_x, + gdouble shear_y); + + +#endif /* __GIMP_TOOL_SHEAR_GRID_H__ */ diff --git a/app/display/gimptooltransform3dgrid.c b/app/display/gimptooltransform3dgrid.c new file mode 100644 index 0000000..06cd955 --- /dev/null +++ b/app/display/gimptooltransform3dgrid.c @@ -0,0 +1,1162 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptool3dtransformgrid.c + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "core/gimp-transform-3d-utils.h" +#include "core/gimp-utils.h" + +#include "gimpdisplayshell.h" +#include "gimpdisplayshell-transform.h" +#include "gimptooltransform3dgrid.h" + +#include "gimp-intl.h" + + +#define CONSTRAINT_MIN_DIST 8.0 +#define PIXELS_PER_REVOLUTION 1000 + + +enum +{ + PROP_0, + PROP_MODE, + PROP_UNIFIED, + PROP_CONSTRAIN_AXIS, + PROP_Z_AXIS, + PROP_LOCAL_FRAME, + PROP_CAMERA_X, + PROP_CAMERA_Y, + PROP_CAMERA_Z, + PROP_OFFSET_X, + PROP_OFFSET_Y, + PROP_OFFSET_Z, + PROP_ROTATION_ORDER, + PROP_ANGLE_X, + PROP_ANGLE_Y, + PROP_ANGLE_Z, + PROP_PIVOT_3D_X, + PROP_PIVOT_3D_Y, + PROP_PIVOT_3D_Z +}; + +typedef enum +{ + AXIS_NONE, + AXIS_X, + AXIS_Y +} Axis; + +struct _GimpToolTransform3DGridPrivate +{ + GimpTransform3DMode mode; + gboolean unified; + + gboolean constrain_axis; + gboolean z_axis; + gboolean local_frame; + + gdouble camera_x; + gdouble camera_y; + gdouble camera_z; + + gdouble offset_x; + gdouble offset_y; + gdouble offset_z; + + gint rotation_order; + gdouble angle_x; + gdouble angle_y; + gdouble angle_z; + + gdouble pivot_x; + gdouble pivot_y; + gdouble pivot_z; + + GimpTransformHandle handle; + + gdouble orig_x; + gdouble orig_y; + gdouble orig_offset_x; + gdouble orig_offset_y; + gdouble orig_offset_z; + GimpMatrix3 orig_transform; + + gdouble last_x; + gdouble last_y; + + Axis constrained_axis; +}; + + +/* local function prototypes */ + +static void gimp_tool_transform_3d_grid_constructed (GObject *object); +static void gimp_tool_transform_3d_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_transform_3d_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint gimp_tool_transform_3d_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_transform_3d_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static void gimp_tool_transform_3d_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_transform_3d_grid_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_transform_3d_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static void gimp_tool_transform_3d_grid_update_mode (GimpToolTransform3DGrid *grid); +static void gimp_tool_transform_3d_grid_reset_motion (GimpToolTransform3DGrid *grid); +static gboolean gimp_tool_transform_3d_grid_constrain (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y, + gdouble ox, + gdouble oy, + gdouble *tx, + gdouble *ty); + +static gboolean gimp_tool_transform_3d_grid_motion_vanishing_point (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y); +static gboolean gimp_tool_transform_3d_grid_motion_move (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y); +static gboolean gimp_tool_transform_3d_grid_motion_rotate (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolTransform3DGrid, gimp_tool_transform_3d_grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID) + +#define parent_class gimp_tool_transform_3d_grid_parent_class + + +static void +gimp_tool_transform_3d_grid_class_init (GimpToolTransform3DGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_transform_3d_grid_constructed; + object_class->set_property = gimp_tool_transform_3d_grid_set_property; + object_class->get_property = gimp_tool_transform_3d_grid_get_property; + + widget_class->button_press = gimp_tool_transform_3d_grid_button_press; + widget_class->motion = gimp_tool_transform_3d_grid_motion; + widget_class->hover = gimp_tool_transform_3d_grid_hover; + widget_class->hover_modifier = gimp_tool_transform_3d_grid_hover_modifier; + widget_class->get_cursor = gimp_tool_transform_3d_grid_get_cursor; + + g_object_class_install_property (object_class, PROP_MODE, + g_param_spec_enum ("mode", + NULL, NULL, + GIMP_TYPE_TRANSFORM_3D_MODE, + GIMP_TRANSFORM_3D_MODE_CAMERA, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_UNIFIED, + g_param_spec_boolean ("unified", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_AXIS, + g_param_spec_boolean ("constrain-axis", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Z_AXIS, + g_param_spec_boolean ("z-axis", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_LOCAL_FRAME, + g_param_spec_boolean ("local-frame", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CAMERA_X, + g_param_spec_double ("camera-x", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CAMERA_Y, + g_param_spec_double ("camera-y", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CAMERA_Z, + g_param_spec_double ("camera-z", + NULL, NULL, + -(1.0 / 0.0), + 1.0 / 0.0, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_OFFSET_X, + g_param_spec_double ("offset-x", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_OFFSET_Y, + g_param_spec_double ("offset-y", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_OFFSET_Z, + g_param_spec_double ("offset-z", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ROTATION_ORDER, + g_param_spec_int ("rotation-order", + NULL, NULL, + 0, 6, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ANGLE_X, + g_param_spec_double ("angle-x", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ANGLE_Y, + g_param_spec_double ("angle-y", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ANGLE_Z, + g_param_spec_double ("angle-z", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_3D_X, + g_param_spec_double ("pivot-3d-x", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_3D_Y, + g_param_spec_double ("pivot-3d-y", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_3D_Z, + g_param_spec_double ("pivot-3d-z", + NULL, NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_transform_3d_grid_init (GimpToolTransform3DGrid *grid) +{ + grid->priv = gimp_tool_transform_3d_grid_get_instance_private (grid); +} + +static void +gimp_tool_transform_3d_grid_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_object_set (object, + "clip-guides", TRUE, + "dynamic-handle-size", FALSE, + NULL); +} + +static void +gimp_tool_transform_3d_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (object); + GimpToolTransform3DGridPrivate *priv = grid->priv; + + switch (property_id) + { + case PROP_MODE: + priv->mode = g_value_get_enum (value); + gimp_tool_transform_3d_grid_update_mode (grid); + break; + + case PROP_UNIFIED: + priv->unified = g_value_get_boolean (value); + gimp_tool_transform_3d_grid_update_mode (grid); + break; + + case PROP_CONSTRAIN_AXIS: + priv->constrain_axis = g_value_get_boolean (value); + gimp_tool_transform_3d_grid_reset_motion (grid); + break; + case PROP_Z_AXIS: + priv->z_axis = g_value_get_boolean (value); + gimp_tool_transform_3d_grid_reset_motion (grid); + break; + case PROP_LOCAL_FRAME: + priv->local_frame = g_value_get_boolean (value); + gimp_tool_transform_3d_grid_reset_motion (grid); + break; + + case PROP_CAMERA_X: + priv->camera_x = g_value_get_double (value); + g_object_set (grid, + "pivot-x", priv->camera_x, + NULL); + break; + case PROP_CAMERA_Y: + priv->camera_y = g_value_get_double (value); + g_object_set (grid, + "pivot-y", priv->camera_y, + NULL); + break; + case PROP_CAMERA_Z: + priv->camera_z = g_value_get_double (value); + break; + + case PROP_OFFSET_X: + priv->offset_x = g_value_get_double (value); + break; + case PROP_OFFSET_Y: + priv->offset_y = g_value_get_double (value); + break; + case PROP_OFFSET_Z: + priv->offset_z = g_value_get_double (value); + break; + + case PROP_ROTATION_ORDER: + priv->rotation_order = g_value_get_int (value); + break; + case PROP_ANGLE_X: + priv->angle_x = g_value_get_double (value); + break; + case PROP_ANGLE_Y: + priv->angle_y = g_value_get_double (value); + break; + case PROP_ANGLE_Z: + priv->angle_z = g_value_get_double (value); + break; + + case PROP_PIVOT_3D_X: + priv->pivot_x = g_value_get_double (value); + break; + case PROP_PIVOT_3D_Y: + priv->pivot_y = g_value_get_double (value); + break; + case PROP_PIVOT_3D_Z: + priv->pivot_z = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_transform_3d_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (object); + GimpToolTransform3DGridPrivate *priv = grid->priv; + + switch (property_id) + { + case PROP_MODE: + g_value_set_enum (value, priv->mode); + break; + case PROP_UNIFIED: + g_value_set_boolean (value, priv->unified); + break; + + case PROP_CONSTRAIN_AXIS: + g_value_set_boolean (value, priv->constrain_axis); + break; + case PROP_Z_AXIS: + g_value_set_boolean (value, priv->z_axis); + break; + case PROP_LOCAL_FRAME: + g_value_set_boolean (value, priv->local_frame); + break; + + case PROP_CAMERA_X: + g_value_set_double (value, priv->camera_x); + break; + case PROP_CAMERA_Y: + g_value_set_double (value, priv->camera_y); + break; + case PROP_CAMERA_Z: + g_value_set_double (value, priv->camera_z); + break; + + case PROP_OFFSET_X: + g_value_set_double (value, priv->offset_x); + break; + case PROP_OFFSET_Y: + g_value_set_double (value, priv->offset_y); + break; + case PROP_OFFSET_Z: + g_value_set_double (value, priv->offset_z); + break; + + case PROP_ROTATION_ORDER: + g_value_set_int (value, priv->rotation_order); + break; + case PROP_ANGLE_X: + g_value_set_double (value, priv->angle_x); + break; + case PROP_ANGLE_Y: + g_value_set_double (value, priv->angle_y); + break; + case PROP_ANGLE_Z: + g_value_set_double (value, priv->angle_z); + break; + + case PROP_PIVOT_3D_X: + g_value_set_double (value, priv->pivot_x); + break; + case PROP_PIVOT_3D_Y: + g_value_set_double (value, priv->pivot_y); + break; + case PROP_PIVOT_3D_Z: + g_value_set_double (value, priv->pivot_z); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +gimp_tool_transform_3d_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget); + GimpToolTransform3DGridPrivate *priv = grid->priv; + + priv->handle = GIMP_TOOL_WIDGET_CLASS (parent_class)->button_press ( + widget, coords, time, state, press_type); + + priv->orig_x = coords->x; + priv->orig_y = coords->y; + priv->orig_offset_x = priv->offset_x; + priv->orig_offset_y = priv->offset_y; + priv->orig_offset_z = priv->offset_z; + priv->last_x = coords->x; + priv->last_y = coords->y; + + gimp_tool_transform_3d_grid_reset_motion (grid); + + return priv->handle; +} + +void +gimp_tool_transform_3d_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget); + GimpToolTransform3DGridPrivate *priv = grid->priv; + GimpMatrix3 transform; + gboolean update = TRUE; + + switch (priv->handle) + { + case GIMP_TRANSFORM_HANDLE_PIVOT: + update = gimp_tool_transform_3d_grid_motion_vanishing_point ( + grid, coords->x, coords->y); + break; + + case GIMP_TRANSFORM_HANDLE_CENTER: + update = gimp_tool_transform_3d_grid_motion_move ( + grid, coords->x, coords->y); + break; + + case GIMP_TRANSFORM_HANDLE_ROTATION: + update = gimp_tool_transform_3d_grid_motion_rotate ( + grid, coords->x, coords->y); + break; + + default: + g_return_if_reached (); + } + + if (update) + { + gimp_transform_3d_matrix (&transform, + + priv->camera_x, + priv->camera_y, + priv->camera_z, + + priv->offset_x, + priv->offset_y, + priv->offset_z, + + priv->rotation_order, + priv->angle_x, + priv->angle_y, + priv->angle_z, + + priv->pivot_x, + priv->pivot_y, + priv->pivot_z); + + g_object_set (widget, + "transform", &transform, + NULL); + } +} + +static void +gimp_tool_transform_3d_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GIMP_TOOL_WIDGET_CLASS (parent_class)->hover (widget, + coords, state, proximity); + + if (proximity && + gimp_tool_transform_grid_get_handle (GIMP_TOOL_TRANSFORM_GRID (widget)) == + GIMP_TRANSFORM_HANDLE_PIVOT) + { + gimp_tool_widget_set_status (widget, + _("Click-Drag to move the vanishing point")); + } +} + +static void +gimp_tool_transform_3d_grid_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolTransform3DGrid *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget); + GimpToolTransform3DGridPrivate *priv = grid->priv; + + GIMP_TOOL_WIDGET_CLASS (parent_class)->hover_modifier (widget, + key, press, state); + + priv->local_frame = (state & gimp_get_extend_selection_mask ()) != 0; +} + +static gboolean +gimp_tool_transform_3d_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + if (! GIMP_TOOL_WIDGET_CLASS (parent_class)->get_cursor (widget, + coords, + state, + cursor, + tool_cursor, + modifier)) + { + return FALSE; + } + + if (gimp_tool_transform_grid_get_handle (GIMP_TOOL_TRANSFORM_GRID (widget)) == + GIMP_TRANSFORM_HANDLE_PIVOT) + { + *tool_cursor = GIMP_TOOL_CURSOR_TRANSFORM_3D_CAMERA; + } + + return TRUE; +} + +static void +gimp_tool_transform_3d_grid_update_mode (GimpToolTransform3DGrid *grid) +{ + GimpToolTransform3DGridPrivate *priv = grid->priv; + + if (priv->unified) + { + g_object_set (grid, + "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE, + "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE, + "use-pivot-handle", TRUE, + NULL); + } + else + { + switch (priv->mode) + { + case GIMP_TRANSFORM_3D_MODE_CAMERA: + g_object_set (grid, + "inside-function", GIMP_TRANSFORM_FUNCTION_NONE, + "outside-function", GIMP_TRANSFORM_FUNCTION_NONE, + "use-pivot-handle", TRUE, + NULL); + break; + + case GIMP_TRANSFORM_3D_MODE_MOVE: + g_object_set (grid, + "inside-function", GIMP_TRANSFORM_FUNCTION_MOVE, + "outside-function", GIMP_TRANSFORM_FUNCTION_MOVE, + "use-pivot-handle", FALSE, + NULL); + break; + + case GIMP_TRANSFORM_3D_MODE_ROTATE: + g_object_set (grid, + "inside-function", GIMP_TRANSFORM_FUNCTION_ROTATE, + "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE, + "use-pivot-handle", FALSE, + NULL); + break; + } + } +} + +static void +gimp_tool_transform_3d_grid_reset_motion (GimpToolTransform3DGrid *grid) +{ + GimpToolTransform3DGridPrivate *priv = grid->priv; + GimpMatrix3 *transform; + + priv->constrained_axis = AXIS_NONE; + + g_object_get (grid, + "transform", &transform, + NULL); + + priv->orig_transform = *transform; + + g_free (transform); +} + +static gboolean +gimp_tool_transform_3d_grid_constrain (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y, + gdouble ox, + gdouble oy, + gdouble *tx, + gdouble *ty) +{ + GimpToolTransform3DGridPrivate *priv = grid->priv; + + if (! priv->constrain_axis) + return TRUE; + + if (priv->constrained_axis == AXIS_NONE) + { + GimpDisplayShell *shell; + gdouble x1, y1; + gdouble x2, y2; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (grid)); + + gimp_display_shell_transform_xy_f (shell, + priv->last_x, priv->last_y, + &x1, &y1); + gimp_display_shell_transform_xy_f (shell, + x, y, + &x2, &y2); + + if (hypot (x2 - x1, y2 - y1) < CONSTRAINT_MIN_DIST) + return FALSE; + + if (fabs (*tx - ox) >= fabs (*ty - oy)) + priv->constrained_axis = AXIS_X; + else + priv->constrained_axis = AXIS_Y; + } + + if (priv->constrained_axis == AXIS_X) + *ty = oy; + else + *tx = ox; + + return TRUE; +} + +static gboolean +gimp_tool_transform_3d_grid_motion_vanishing_point (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y) +{ + GimpToolTransform3DGridPrivate *priv = grid->priv; + GimpCoords c = {}; + gdouble pivot_x; + gdouble pivot_y; + + if (! gimp_tool_transform_3d_grid_constrain (grid, + x, y, + priv->last_x, priv->last_y, + &x, &y)) + { + return FALSE; + } + + c.x = x; + c.y = y; + + GIMP_TOOL_WIDGET_CLASS (parent_class)->motion (GIMP_TOOL_WIDGET (grid), + &c, 0, 0); + + g_object_get (grid, + "pivot-x", &pivot_x, + "pivot-y", &pivot_y, + NULL); + + g_object_set (grid, + "camera-x", pivot_x, + "camera-y", pivot_y, + NULL); + + priv->last_x = c.x; + priv->last_y = c.y; + + return TRUE; +} + +static gboolean +gimp_tool_transform_3d_grid_motion_move (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y) +{ + GimpToolTransform3DGridPrivate *priv = grid->priv; + GimpMatrix4 matrix; + + if (! priv->z_axis) + { + gdouble x1, y1, z1, w1; + gdouble x2, y2, z2, w2; + + if (! priv->local_frame) + { + gimp_matrix4_identity (&matrix); + } + else + { + GimpMatrix3 transform_inv = priv->orig_transform;; + + gimp_matrix3_invert (&transform_inv); + + gimp_transform_3d_matrix3_to_matrix4 (&transform_inv, &matrix, 2); + } + + w1 = gimp_matrix4_transform_point (&matrix, + priv->last_x, priv->last_y, 0.0, + &x1, &y1, &z1); + w2 = gimp_matrix4_transform_point (&matrix, + x, y, 0.0, + &x2, &y2, &z2); + + if (w1 <= 0.0) + return FALSE; + + if (! gimp_tool_transform_3d_grid_constrain (grid, + x, y, + x1, y1, + &x2, &y2)) + { + return FALSE; + } + + if (priv->local_frame) + { + gimp_matrix4_identity (&matrix); + + gimp_transform_3d_matrix4_rotate_euler (&matrix, + priv->rotation_order, + priv->angle_x, + priv->angle_y, + priv->angle_z, + 0.0, 0.0, 0.0); + + gimp_matrix4_transform_point (&matrix, + x1, y1, z1, + &x1, &y1, &z1); + gimp_matrix4_transform_point (&matrix, + x2, y2, z2, + &x2, &y2, &z2); + } + + if (w2 > 0.0) + { + g_object_set (grid, + "offset-x", priv->offset_x + (x2 - x1), + "offset-y", priv->offset_y + (y2 - y1), + "offset-z", priv->offset_z + (z2 - z1), + NULL); + + priv->last_x = x; + priv->last_y = y; + } + else + { + g_object_set (grid, + "offset-x", priv->orig_offset_x, + "offset-y", priv->orig_offset_y, + "offset-z", priv->orig_offset_z, + NULL); + + priv->last_x = priv->orig_x; + priv->last_y = priv->orig_y; + } + } + else + { + GimpVector3 axis; + gdouble amount; + + if (! priv->local_frame) + { + axis.x = 0.0; + axis.y = 0.0; + axis.z = 1.0; + } + else + { + gimp_matrix4_identity (&matrix); + + gimp_transform_3d_matrix4_rotate_euler (&matrix, + priv->rotation_order, + priv->angle_x, + priv->angle_y, + priv->angle_z, + 0.0, 0.0, 0.0); + + axis.x = matrix.coeff[0][2]; + axis.y = matrix.coeff[1][2]; + axis.z = matrix.coeff[2][2]; + + if (axis.x < 0.0) + gimp_vector3_neg (&axis); + } + + amount = x - priv->last_x; + + g_object_set (grid, + "offset-x", priv->offset_x + axis.x * amount, + "offset-y", priv->offset_y + axis.y * amount, + "offset-z", priv->offset_z + axis.z * amount, + NULL); + + priv->last_x = x; + priv->last_y = y; + } + + return TRUE; +} + +static gboolean +gimp_tool_transform_3d_grid_motion_rotate (GimpToolTransform3DGrid *grid, + gdouble x, + gdouble y) +{ + GimpToolTransform3DGridPrivate *priv = grid->priv; + GimpDisplayShell *shell; + GimpMatrix4 matrix; + GimpMatrix2 basis_inv; + GimpVector3 omega; + gdouble z_sign; + gboolean local_frame; + + shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (grid)); + + local_frame = priv->local_frame && (priv->constrain_axis || priv->z_axis); + + if (! local_frame) + { + gimp_matrix2_identity (&basis_inv); + z_sign = 1.0; + } + else + { + { + GimpVector3 o, n, c; + + gimp_matrix4_identity (&matrix); + + gimp_transform_3d_matrix4_rotate_euler (&matrix, + priv->rotation_order, + priv->angle_x, + priv->angle_y, + priv->angle_z, + priv->pivot_x, + priv->pivot_y, + priv->pivot_z); + + gimp_transform_3d_matrix4_translate (&matrix, + priv->offset_x, + priv->offset_y, + priv->offset_z); + + gimp_matrix4_transform_point (&matrix, + 0.0, 0.0, 0.0, + &o.x, &o.y, &o.z); + gimp_matrix4_transform_point (&matrix, + 0.0, 0.0, 1.0, + &n.x, &n.y, &n.z); + + c.x = priv->camera_x; + c.y = priv->camera_y; + c.z = priv->camera_z; + + gimp_vector3_sub (&n, &n, &o); + gimp_vector3_sub (&c, &c, &o); + + z_sign = gimp_vector3_inner_product (&c, &n) <= 0.0 ? +1.0 : -1.0; + } + + { + GimpVector2 o, u, v; + + gimp_matrix3_transform_point (&priv->orig_transform, + priv->pivot_x, priv->pivot_y, + &o.x, &o.y); + gimp_matrix3_transform_point (&priv->orig_transform, + priv->pivot_x + 1.0, priv->pivot_y, + &u.x, &u.y); + gimp_matrix3_transform_point (&priv->orig_transform, + priv->pivot_x, priv->pivot_y + 1.0, + &v.x, &v.y); + + gimp_vector2_sub (&u, &u, &o); + gimp_vector2_sub (&v, &v, &o); + + gimp_vector2_normalize (&u); + gimp_vector2_normalize (&v); + + basis_inv.coeff[0][0] = u.x; + basis_inv.coeff[1][0] = u.y; + basis_inv.coeff[0][1] = v.x; + basis_inv.coeff[1][1] = v.y; + + gimp_matrix2_invert (&basis_inv); + } + } + + if (! priv->z_axis) + { + GimpVector2 scale; + gdouble norm; + + gimp_matrix2_transform_point (&basis_inv, + -(y - priv->last_y), + x - priv->last_x, + &omega.x, &omega.y); + + omega.z = 0.0; + + if (! gimp_tool_transform_3d_grid_constrain (grid, + x, y, + 0.0, 0.0, + &omega.x, &omega.y)) + { + return FALSE; + } + + norm = gimp_vector3_length (&omega); + + if (norm > 0.0) + { + scale.x = shell->scale_x * omega.y / norm; + scale.y = shell->scale_y * omega.x / norm; + + gimp_vector3_mul (&omega, gimp_vector2_length (&scale)); + gimp_vector3_mul (&omega, 2.0 * G_PI / PIXELS_PER_REVOLUTION); + } + } + else + { + GimpVector2 o; + GimpVector2 v1 = {priv->last_x, priv->last_y}; + GimpVector2 v2 = {x, y}; + + g_warn_if_fail (priv->pivot_z == 0.0); + + gimp_matrix3_transform_point (&priv->orig_transform, + priv->pivot_x, priv->pivot_y, + &o.x, &o.y); + + gimp_vector2_sub (&v1, &v1, &o); + gimp_vector2_sub (&v2, &v2, &o); + + gimp_vector2_normalize (&v1); + gimp_vector2_normalize (&v2); + + omega.x = 0.0; + omega.y = 0.0; + omega.z = atan2 (gimp_vector2_cross_product (&v1, &v2).y, + gimp_vector2_inner_product (&v1, &v2)); + + omega.z *= z_sign; + } + + gimp_matrix4_identity (&matrix); + + if (local_frame) + gimp_transform_3d_matrix4_rotate (&matrix, &omega); + + gimp_transform_3d_matrix4_rotate_euler (&matrix, + priv->rotation_order, + priv->angle_x, + priv->angle_y, + priv->angle_z, + 0.0, 0.0, 0.0); + + if (! local_frame) + gimp_transform_3d_matrix4_rotate (&matrix, &omega); + + gimp_transform_3d_matrix4_rotate_euler_decompose (&matrix, + priv->rotation_order, + &priv->angle_x, + &priv->angle_y, + &priv->angle_z); + + priv->last_x = x; + priv->last_y = y; + + g_object_set (grid, + "angle-x", priv->angle_x, + "angle-y", priv->angle_y, + "angle-z", priv->angle_z, + NULL); + + return TRUE; +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_transform_3d_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble camera_x, + gdouble camera_y, + gdouble camera_z) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, + "shell", shell, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "camera-x", camera_x, + "camera-y", camera_y, + "camera-z", camera_z, + "pivot-3d-x", (x1 + x2) / 2.0, + "pivot-3d-y", (y1 + y2) / 2.0, + "pivot-3d-z", 0.0, + NULL); +} diff --git a/app/display/gimptooltransform3dgrid.h b/app/display/gimptooltransform3dgrid.h new file mode 100644 index 0000000..42ac3ea --- /dev/null +++ b/app/display/gimptooltransform3dgrid.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptool3dtransformgrid.h + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_TRANSFORM_3D_GRID_H__ +#define __GIMP_TOOL_TRANSFORM_3D_GRID_H__ + + +#include "gimptooltransformgrid.h" + + +#define GIMP_TYPE_TOOL_TRANSFORM_3D_GRID (gimp_tool_transform_3d_grid_get_type ()) +#define GIMP_TOOL_TRANSFORM_3D_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGrid)) +#define GIMP_TOOL_TRANSFORM_3D_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGridClass)) +#define GIMP_IS_TOOL_TRANSFORM_3D_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID)) +#define GIMP_IS_TOOL_TRANSFORM_3D_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID)) +#define GIMP_TOOL_TRANSFORM_3D_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGridClass)) + + +typedef struct _GimpToolTransform3DGrid GimpToolTransform3DGrid; +typedef struct _GimpToolTransform3DGridPrivate GimpToolTransform3DGridPrivate; +typedef struct _GimpToolTransform3DGridClass GimpToolTransform3DGridClass; + +struct _GimpToolTransform3DGrid +{ + GimpToolTransformGrid parent_instance; + + GimpToolTransform3DGridPrivate *priv; +}; + +struct _GimpToolTransform3DGridClass +{ + GimpToolTransformGridClass parent_class; +}; + + +GType gimp_tool_transform_3d_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_transform_3d_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble camera_x, + gdouble camera_y, + gdouble camera_z); + + +#endif /* __GIMP_TOOL_TRANSFORM_3D_GRID_H__ */ diff --git a/app/display/gimptooltransformgrid.c b/app/display/gimptooltransformgrid.c new file mode 100644 index 0000000..b186b91 --- /dev/null +++ b/app/display/gimptooltransformgrid.c @@ -0,0 +1,2494 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooltransformgrid.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * Based on GimpUnifiedTransformTool + * Copyright (C) 2011 Mikael Magnusson <mikachu@src.gnome.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpcanvashandle.h" +#include "gimpcanvastransformguides.h" +#include "gimpdisplayshell.h" +#include "gimptooltransformgrid.h" + +#include "gimp-intl.h" + + +#define MIN_HANDLE_SIZE 6 + + +enum +{ + PROP_0, + PROP_TRANSFORM, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_PIVOT_X, + PROP_PIVOT_Y, + PROP_GUIDE_TYPE, + PROP_N_GUIDES, + PROP_CLIP_GUIDES, + PROP_SHOW_GUIDES, + PROP_INSIDE_FUNCTION, + PROP_OUTSIDE_FUNCTION, + PROP_USE_CORNER_HANDLES, + PROP_USE_PERSPECTIVE_HANDLES, + PROP_USE_SIDE_HANDLES, + PROP_USE_SHEAR_HANDLES, + PROP_USE_CENTER_HANDLE, + PROP_USE_PIVOT_HANDLE, + PROP_DYNAMIC_HANDLE_SIZE, + PROP_CONSTRAIN_MOVE, + PROP_CONSTRAIN_SCALE, + PROP_CONSTRAIN_ROTATE, + PROP_CONSTRAIN_SHEAR, + PROP_CONSTRAIN_PERSPECTIVE, + PROP_FROMPIVOT_SCALE, + PROP_FROMPIVOT_SHEAR, + PROP_FROMPIVOT_PERSPECTIVE, + PROP_CORNERSNAP, + PROP_FIXEDPIVOT +}; + + +struct _GimpToolTransformGridPrivate +{ + GimpMatrix3 transform; + gdouble x1, y1; + gdouble x2, y2; + gdouble pivot_x; + gdouble pivot_y; + GimpGuidesType guide_type; + gint n_guides; + gboolean clip_guides; + gboolean show_guides; + GimpTransformFunction inside_function; + GimpTransformFunction outside_function; + gboolean use_corner_handles; + gboolean use_perspective_handles; + gboolean use_side_handles; + gboolean use_shear_handles; + gboolean use_center_handle; + gboolean use_pivot_handle; + gboolean dynamic_handle_size; + gboolean constrain_move; + gboolean constrain_scale; + gboolean constrain_rotate; + gboolean constrain_shear; + gboolean constrain_perspective; + gboolean frompivot_scale; + gboolean frompivot_shear; + gboolean frompivot_perspective; + gboolean cornersnap; + gboolean fixedpivot; + + gdouble curx; /* current x coord */ + gdouble cury; /* current y coord */ + + gdouble button_down; /* is the mouse button pressed */ + gdouble mousex; /* x coord where mouse was clicked */ + gdouble mousey; /* y coord where mouse was clicked */ + + gdouble cx, cy; /* center point (for moving) */ + + /* transformed handle coords */ + gdouble tx1, ty1; + gdouble tx2, ty2; + gdouble tx3, ty3; + gdouble tx4, ty4; + gdouble tcx, tcy; + gdouble tpx, tpy; + + /* previous transformed handle coords */ + gdouble prev_tx1, prev_ty1; + gdouble prev_tx2, prev_ty2; + gdouble prev_tx3, prev_ty3; + gdouble prev_tx4, prev_ty4; + gdouble prev_tcx, prev_tcy; + gdouble prev_tpx, prev_tpy; + + GimpTransformHandle handle; /* current tool activity */ + + GimpCanvasItem *guides; + GimpCanvasItem *handles[GIMP_N_TRANSFORM_HANDLES]; + GimpCanvasItem *center_items[2]; + GimpCanvasItem *pivot_items[2]; +}; + + +/* local function prototypes */ + +static void gimp_tool_transform_grid_constructed (GObject *object); +static void gimp_tool_transform_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_transform_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_transform_grid_changed (GimpToolWidget *widget); +static gint gimp_tool_transform_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_transform_grid_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_transform_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_transform_grid_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_transform_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_transform_grid_leave_notify (GimpToolWidget *widget); +static void gimp_tool_transform_grid_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static GimpTransformHandle + gimp_tool_transform_grid_get_handle_for_coords + (GimpToolTransformGrid *grid, + const GimpCoords *coords); +static void gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid); +static void gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid); +static void gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid); +static void gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid, + gint *handle_w, + gint *handle_h); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolTransformGrid, gimp_tool_transform_grid, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_transform_grid_parent_class + + +static void +gimp_tool_transform_grid_class_init (GimpToolTransformGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_transform_grid_constructed; + object_class->set_property = gimp_tool_transform_grid_set_property; + object_class->get_property = gimp_tool_transform_grid_get_property; + + widget_class->changed = gimp_tool_transform_grid_changed; + widget_class->button_press = gimp_tool_transform_grid_button_press; + widget_class->button_release = gimp_tool_transform_grid_button_release; + widget_class->motion = gimp_tool_transform_grid_motion; + widget_class->hit = gimp_tool_transform_grid_hit; + widget_class->hover = gimp_tool_transform_grid_hover; + widget_class->leave_notify = gimp_tool_transform_grid_leave_notify; + widget_class->hover_modifier = gimp_tool_transform_grid_hover_modifier; + widget_class->get_cursor = gimp_tool_transform_grid_get_cursor; + widget_class->update_on_scale = TRUE; + + g_object_class_install_property (object_class, PROP_TRANSFORM, + gimp_param_spec_matrix3 ("transform", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_X, + g_param_spec_double ("pivot-x", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_Y, + g_param_spec_double ("pivot-y", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_GUIDE_TYPE, + g_param_spec_enum ("guide-type", NULL, NULL, + GIMP_TYPE_GUIDES_TYPE, + GIMP_GUIDES_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_N_GUIDES, + g_param_spec_int ("n-guides", NULL, NULL, + 1, 128, 4, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CLIP_GUIDES, + g_param_spec_boolean ("clip-guides", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SHOW_GUIDES, + g_param_spec_boolean ("show-guides", NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_INSIDE_FUNCTION, + g_param_spec_enum ("inside-function", + NULL, NULL, + GIMP_TYPE_TRANSFORM_FUNCTION, + GIMP_TRANSFORM_FUNCTION_MOVE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_OUTSIDE_FUNCTION, + g_param_spec_enum ("outside-function", + NULL, NULL, + GIMP_TYPE_TRANSFORM_FUNCTION, + GIMP_TRANSFORM_FUNCTION_ROTATE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_CORNER_HANDLES, + g_param_spec_boolean ("use-corner-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_PERSPECTIVE_HANDLES, + g_param_spec_boolean ("use-perspective-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_SIDE_HANDLES, + g_param_spec_boolean ("use-side-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_SHEAR_HANDLES, + g_param_spec_boolean ("use-shear-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_CENTER_HANDLE, + g_param_spec_boolean ("use-center-handle", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_PIVOT_HANDLE, + g_param_spec_boolean ("use-pivot-handle", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DYNAMIC_HANDLE_SIZE, + g_param_spec_boolean ("dynamic-handle-size", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_MOVE, + g_param_spec_boolean ("constrain-move", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_SCALE, + g_param_spec_boolean ("constrain-scale", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_ROTATE, + g_param_spec_boolean ("constrain-rotate", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_SHEAR, + g_param_spec_boolean ("constrain-shear", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_PERSPECTIVE, + g_param_spec_boolean ("constrain-perspective", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FROMPIVOT_SCALE, + g_param_spec_boolean ("frompivot-scale", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FROMPIVOT_SHEAR, + g_param_spec_boolean ("frompivot-shear", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FROMPIVOT_PERSPECTIVE, + g_param_spec_boolean ("frompivot-perspective", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CORNERSNAP, + g_param_spec_boolean ("cornersnap", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FIXEDPIVOT, + g_param_spec_boolean ("fixedpivot", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_tool_transform_grid_init (GimpToolTransformGrid *grid) +{ + grid->private = gimp_tool_transform_grid_get_instance_private (grid); +} + +static void +gimp_tool_transform_grid_constructed (GObject *object) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolTransformGridPrivate *private = grid->private; + GimpCanvasGroup *stroke_group; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + private->guides = gimp_tool_widget_add_transform_guides (widget, + &private->transform, + private->x1, + private->y1, + private->x2, + private->y2, + private->guide_type, + private->n_guides, + private->clip_guides); + + for (i = 0; i < 4; i++) + { + /* draw the scale handles */ + private->handles[GIMP_TRANSFORM_HANDLE_NW + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + /* draw the perspective handles */ + private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_DIAMOND, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + /* draw the side handles */ + private->handles[GIMP_TRANSFORM_HANDLE_N + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + /* draw the shear handles */ + private->handles[GIMP_TRANSFORM_HANDLE_N_S + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_FILLED_DIAMOND, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + } + + /* draw the rotation center axis handle */ + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + private->handles[GIMP_TRANSFORM_HANDLE_PIVOT] = + GIMP_CANVAS_ITEM (stroke_group); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->pivot_items[0] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CIRCLE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + private->pivot_items[1] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CROSS, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + gimp_tool_widget_pop_group (widget); + + /* draw the center handle */ + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + private->handles[GIMP_TRANSFORM_HANDLE_CENTER] = + GIMP_CANVAS_ITEM (stroke_group); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->center_items[0] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + private->center_items[1] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CROSS, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + gimp_tool_widget_pop_group (widget); + + gimp_tool_transform_grid_changed (widget); +} + +static void +gimp_tool_transform_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object); + GimpToolTransformGridPrivate *private = grid->private; + gboolean box = FALSE; + + switch (property_id) + { + case PROP_TRANSFORM: + { + GimpMatrix3 *transform = g_value_get_boxed (value); + + if (transform) + private->transform = *transform; + else + gimp_matrix3_identity (&private->transform); + } + break; + + case PROP_X1: + private->x1 = g_value_get_double (value); + box = TRUE; + break; + case PROP_Y1: + private->y1 = g_value_get_double (value); + box = TRUE; + break; + case PROP_X2: + private->x2 = g_value_get_double (value); + box = TRUE; + break; + case PROP_Y2: + private->y2 = g_value_get_double (value); + box = TRUE; + break; + + case PROP_PIVOT_X: + private->pivot_x = g_value_get_double (value); + break; + case PROP_PIVOT_Y: + private->pivot_y = g_value_get_double (value); + break; + + case PROP_GUIDE_TYPE: + private->guide_type = g_value_get_enum (value); + break; + case PROP_N_GUIDES: + private->n_guides = g_value_get_int (value); + break; + case PROP_CLIP_GUIDES: + private->clip_guides = g_value_get_boolean (value); + break; + case PROP_SHOW_GUIDES: + private->show_guides = g_value_get_boolean (value); + break; + + case PROP_INSIDE_FUNCTION: + private->inside_function = g_value_get_enum (value); + break; + case PROP_OUTSIDE_FUNCTION: + private->outside_function = g_value_get_enum (value); + break; + + case PROP_USE_CORNER_HANDLES: + private->use_corner_handles = g_value_get_boolean (value); + break; + case PROP_USE_PERSPECTIVE_HANDLES: + private->use_perspective_handles = g_value_get_boolean (value); + break; + case PROP_USE_SIDE_HANDLES: + private->use_side_handles = g_value_get_boolean (value); + break; + case PROP_USE_SHEAR_HANDLES: + private->use_shear_handles = g_value_get_boolean (value); + break; + case PROP_USE_CENTER_HANDLE: + private->use_center_handle = g_value_get_boolean (value); + break; + case PROP_USE_PIVOT_HANDLE: + private->use_pivot_handle = g_value_get_boolean (value); + break; + + case PROP_DYNAMIC_HANDLE_SIZE: + private->dynamic_handle_size = g_value_get_boolean (value); + break; + + case PROP_CONSTRAIN_MOVE: + private->constrain_move = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_SCALE: + private->constrain_scale = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_ROTATE: + private->constrain_rotate = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_SHEAR: + private->constrain_shear = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_PERSPECTIVE: + private->constrain_perspective = g_value_get_boolean (value); + break; + + case PROP_FROMPIVOT_SCALE: + private->frompivot_scale = g_value_get_boolean (value); + break; + case PROP_FROMPIVOT_SHEAR: + private->frompivot_shear = g_value_get_boolean (value); + break; + case PROP_FROMPIVOT_PERSPECTIVE: + private->frompivot_perspective = g_value_get_boolean (value); + break; + + case PROP_CORNERSNAP: + private->cornersnap = g_value_get_boolean (value); + break; + case PROP_FIXEDPIVOT: + private->fixedpivot = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + if (box) + { + private->cx = (private->x1 + private->x2) / 2.0; + private->cy = (private->y1 + private->y2) / 2.0; + } +} + +static void +gimp_tool_transform_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object); + GimpToolTransformGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_TRANSFORM: + g_value_set_boxed (value, &private->transform); + break; + + case PROP_X1: + g_value_set_double (value, private->x1); + break; + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + case PROP_X2: + g_value_set_double (value, private->x2); + break; + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + case PROP_PIVOT_X: + g_value_set_double (value, private->pivot_x); + break; + case PROP_PIVOT_Y: + g_value_set_double (value, private->pivot_y); + break; + + case PROP_GUIDE_TYPE: + g_value_set_enum (value, private->guide_type); + break; + case PROP_N_GUIDES: + g_value_set_int (value, private->n_guides); + break; + case PROP_CLIP_GUIDES: + g_value_set_boolean (value, private->clip_guides); + break; + case PROP_SHOW_GUIDES: + g_value_set_boolean (value, private->show_guides); + break; + + case PROP_INSIDE_FUNCTION: + g_value_set_enum (value, private->inside_function); + break; + case PROP_OUTSIDE_FUNCTION: + g_value_set_enum (value, private->outside_function); + break; + + case PROP_USE_CORNER_HANDLES: + g_value_set_boolean (value, private->use_corner_handles); + break; + case PROP_USE_PERSPECTIVE_HANDLES: + g_value_set_boolean (value, private->use_perspective_handles); + break; + case PROP_USE_SIDE_HANDLES: + g_value_set_boolean (value, private->use_side_handles); + break; + case PROP_USE_SHEAR_HANDLES: + g_value_set_boolean (value, private->use_shear_handles); + break; + case PROP_USE_CENTER_HANDLE: + g_value_set_boolean (value, private->use_center_handle); + break; + case PROP_USE_PIVOT_HANDLE: + g_value_set_boolean (value, private->use_pivot_handle); + break; + + case PROP_DYNAMIC_HANDLE_SIZE: + g_value_set_boolean (value, private->dynamic_handle_size); + break; + + case PROP_CONSTRAIN_MOVE: + g_value_set_boolean (value, private->constrain_move); + break; + case PROP_CONSTRAIN_SCALE: + g_value_set_boolean (value, private->constrain_scale); + break; + case PROP_CONSTRAIN_ROTATE: + g_value_set_boolean (value, private->constrain_rotate); + break; + case PROP_CONSTRAIN_SHEAR: + g_value_set_boolean (value, private->constrain_shear); + break; + case PROP_CONSTRAIN_PERSPECTIVE: + g_value_set_boolean (value, private->constrain_perspective); + break; + + case PROP_FROMPIVOT_SCALE: + g_value_set_boolean (value, private->frompivot_scale); + break; + case PROP_FROMPIVOT_SHEAR: + g_value_set_boolean (value, private->frompivot_shear); + break; + case PROP_FROMPIVOT_PERSPECTIVE: + g_value_set_boolean (value, private->frompivot_perspective); + break; + + case PROP_CORNERSNAP: + g_value_set_boolean (value, private->cornersnap); + break; + case PROP_FIXEDPIVOT: + g_value_set_boolean (value, private->fixedpivot); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +transform_is_convex (GimpVector2 *pos) +{ + return gimp_transform_polygon_is_convex (pos[0].x, pos[0].y, + pos[1].x, pos[1].y, + pos[2].x, pos[2].y, + pos[3].x, pos[3].y); +} + +static gboolean +transform_grid_is_convex (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + + return gimp_transform_polygon_is_convex (private->tx1, private->ty1, + private->tx2, private->ty2, + private->tx3, private->ty3, + private->tx4, private->ty4); +} + +static inline gboolean +vectorisnull (GimpVector2 v) +{ + return ((v.x == 0.0) && (v.y == 0.0)); +} + +static inline gdouble +dotprod (GimpVector2 a, + GimpVector2 b) +{ + return a.x * b.x + a.y * b.y; +} + +static inline gdouble +norm (GimpVector2 a) +{ + return sqrt (dotprod (a, a)); +} + +static inline GimpVector2 +vectorsubtract (GimpVector2 a, + GimpVector2 b) +{ + GimpVector2 c; + + c.x = a.x - b.x; + c.y = a.y - b.y; + + return c; +} + +static inline GimpVector2 +vectoradd (GimpVector2 a, + GimpVector2 b) +{ + GimpVector2 c; + + c.x = a.x + b.x; + c.y = a.y + b.y; + + return c; +} + +static inline GimpVector2 +scalemult (GimpVector2 a, + gdouble b) +{ + GimpVector2 c; + + c.x = a.x * b; + c.y = a.y * b; + + return c; +} + +static inline GimpVector2 +vectorproject (GimpVector2 a, + GimpVector2 b) +{ + return scalemult (b, dotprod (a, b) / dotprod (b, b)); +} + +/* finds the clockwise angle between the vectors given, 0-2π */ +static inline gdouble +calcangle (GimpVector2 a, + GimpVector2 b) +{ + gdouble angle, angle2; + gdouble length; + + if (vectorisnull (a) || vectorisnull (b)) + return 0.0; + + length = norm (a) * norm (b); + + angle = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0)); + angle2 = b.y; + b.y = -b.x; + b.x = angle2; + angle2 = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0)); + + return ((angle2 > G_PI / 2.0) ? angle : 2.0 * G_PI - angle); +} + +static inline GimpVector2 +rotate2d (GimpVector2 p, + gdouble angle) +{ + GimpVector2 ret; + + ret.x = cos (angle) * p.x-sin (angle) * p.y; + ret.y = sin (angle) * p.x+cos (angle) * p.y; + + return ret; +} + +static inline GimpVector2 +lineintersect (GimpVector2 p1, GimpVector2 p2, + GimpVector2 q1, GimpVector2 q2) +{ + gdouble denom, u; + GimpVector2 p; + + denom = (q2.y - q1.y) * (p2.x - p1.x) - (q2.x - q1.x) * (p2.y - p1.y); + if (denom == 0.0) + { + p.x = (p1.x + p2.x + q1.x + q2.x) / 4; + p.y = (p1.y + p2.y + q1.y + q2.y) / 4; + } + else + { + u = (q2.x - q1.x) * (p1.y - q1.y) - (q2.y - q1.y) * (p1.x - q1.x); + u /= denom; + + p.x = p1.x + u * (p2.x - p1.x); + p.y = p1.y + u * (p2.y - p1.y); + } + + return p; +} + +static inline GimpVector2 +get_pivot_delta (GimpToolTransformGrid *grid, + GimpVector2 *oldpos, + GimpVector2 *newpos, + GimpVector2 pivot) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpMatrix3 transform_before; + GimpMatrix3 transform_after; + GimpVector2 delta; + + gimp_matrix3_identity (&transform_before); + gimp_matrix3_identity (&transform_after); + + gimp_transform_matrix_perspective (&transform_before, + private->x1, + private->y1, + private->x2 - private->x1, + private->y2 - private->y1, + oldpos[0].x, oldpos[0].y, + oldpos[1].x, oldpos[1].y, + oldpos[2].x, oldpos[2].y, + oldpos[3].x, oldpos[3].y); + gimp_transform_matrix_perspective (&transform_after, + private->x1, + private->y1, + private->x2 - private->x1, + private->y2 - private->y1, + newpos[0].x, newpos[0].y, + newpos[1].x, newpos[1].y, + newpos[2].x, newpos[2].y, + newpos[3].x, newpos[3].y); + gimp_matrix3_invert (&transform_before); + gimp_matrix3_mult (&transform_after, &transform_before); + gimp_matrix3_transform_point (&transform_before, + pivot.x, pivot.y, &delta.x, &delta.y); + + delta = vectorsubtract (delta, pivot); + + return delta; +} + +static gboolean +point_is_inside_polygon (gint n, + gdouble *x, + gdouble *y, + gdouble px, + gdouble py) +{ + gint i, j; + gboolean odd = FALSE; + + for (i = 0, j = n - 1; i < n; j = i++) + { + if ((y[i] < py && y[j] >= py) || + (y[j] < py && y[i] >= py)) + { + if (x[i] + (py - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < px) + odd = !odd; + } + } + + return odd; +} + +static gboolean +point_is_inside_polygon_pos (GimpVector2 *pos, + GimpVector2 point) +{ + return point_is_inside_polygon (4, + (gdouble[4]){ pos[0].x, pos[1].x, + pos[3].x, pos[2].x }, + (gdouble[4]){ pos[0].y, pos[1].y, + pos[3].y, pos[2].y }, + point.x, point.y); +} + +static void +get_handle_geometry (GimpToolTransformGrid *grid, + GimpVector2 *position, + gdouble *angle) +{ + GimpToolTransformGridPrivate *private = grid->private; + + GimpVector2 o[] = { { .x = private->tx1, .y = private->ty1 }, + { .x = private->tx2, .y = private->ty2 }, + { .x = private->tx3, .y = private->ty3 }, + { .x = private->tx4, .y = private->ty4 } }; + GimpVector2 right = { .x = 1.0, .y = 0.0 }; + GimpVector2 up = { .x = 0.0, .y = 1.0 }; + + if (position) + { + position[0] = o[0]; + position[1] = o[1]; + position[2] = o[2]; + position[3] = o[3]; + } + + angle[0] = calcangle (vectorsubtract (o[1], o[0]), right); + angle[1] = calcangle (vectorsubtract (o[3], o[2]), right); + angle[2] = calcangle (vectorsubtract (o[3], o[1]), up); + angle[3] = calcangle (vectorsubtract (o[2], o[0]), up); + + angle[4] = (angle[0] + angle[3]) / 2.0; + angle[5] = (angle[0] + angle[2]) / 2.0; + angle[6] = (angle[1] + angle[3]) / 2.0; + angle[7] = (angle[1] + angle[2]) / 2.0; + + angle[8] = (angle[0] + angle[1] + angle[2] + angle[3]) / 4.0; +} + +static void +gimp_tool_transform_grid_changed (GimpToolWidget *widget) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + gdouble angle[9]; + GimpVector2 o[4], t[4]; + gint handle_w; + gint handle_h; + gint d, i; + + gimp_tool_transform_grid_update_box (grid); + + gimp_canvas_transform_guides_set (private->guides, + &private->transform, + private->x1, + private->y1, + private->x2, + private->y2, + private->guide_type, + private->n_guides, + private->clip_guides); + gimp_canvas_item_set_visible (private->guides, private->show_guides); + + get_handle_geometry (grid, o, angle); + gimp_tool_transform_grid_calc_handles (grid, &handle_w, &handle_h); + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *h; + gdouble factor; + + /* the scale handles */ + factor = 1.0; + if (private->use_perspective_handles) + factor = 1.5; + + h = private->handles[GIMP_TRANSFORM_HANDLE_NW + i]; + gimp_canvas_item_set_visible (h, private->use_corner_handles); + + if (private->use_corner_handles) + { + gimp_canvas_handle_set_position (h, o[i].x, o[i].y); + gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor); + gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0); + } + + /* the perspective handles */ + factor = 1.0; + if (private->use_corner_handles) + factor = 0.8; + + h = private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i]; + gimp_canvas_item_set_visible (h, private->use_perspective_handles); + + if (private->use_perspective_handles) + { + gimp_canvas_handle_set_position (h, o[i].x, o[i].y); + gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor); + gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0); + } + } + + /* draw the side handles */ + t[0] = scalemult (vectoradd (o[0], o[1]), 0.5); + t[1] = scalemult (vectoradd (o[2], o[3]), 0.5); + t[2] = scalemult (vectoradd (o[1], o[3]), 0.5); + t[3] = scalemult (vectoradd (o[2], o[0]), 0.5); + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *h; + + h = private->handles[GIMP_TRANSFORM_HANDLE_N + i]; + gimp_canvas_item_set_visible (h, private->use_side_handles); + + if (private->use_side_handles) + { + gimp_canvas_handle_set_position (h, t[i].x, t[i].y); + gimp_canvas_handle_set_size (h, handle_w, handle_h); + gimp_canvas_handle_set_angles (h, angle[i], 0.0); + } + } + + /* draw the shear handles */ + t[0] = scalemult (vectoradd ( o[0] , scalemult (o[1], 3.0)), + 0.25); + t[1] = scalemult (vectoradd (scalemult (o[2], 3.0), o[3] ), + 0.25); + t[2] = scalemult (vectoradd ( o[1] , scalemult (o[3], 3.0)), + 0.25); + t[3] = scalemult (vectoradd (scalemult (o[0], 3.0), o[2] ), + 0.25); + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *h; + + h = private->handles[GIMP_TRANSFORM_HANDLE_N_S + i]; + gimp_canvas_item_set_visible (h, private->use_shear_handles); + + if (private->use_shear_handles) + { + gimp_canvas_handle_set_position (h, t[i].x, t[i].y); + gimp_canvas_handle_set_size (h, handle_w, handle_h); + gimp_canvas_handle_set_angles (h, angle[i], 0.0); + } + } + + d = MIN (handle_w, handle_h); + if (private->use_center_handle) + d *= 2; /* so you can grab it from under the center handle */ + + gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_PIVOT], + private->use_pivot_handle); + + if (private->use_pivot_handle) + { + gimp_canvas_handle_set_position (private->pivot_items[0], + private->tpx, private->tpy); + gimp_canvas_handle_set_size (private->pivot_items[0], d, d); + + gimp_canvas_handle_set_position (private->pivot_items[1], + private->tpx, private->tpy); + gimp_canvas_handle_set_size (private->pivot_items[1], d, d); + } + + d = MIN (handle_w, handle_h); + + gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_CENTER], + private->use_center_handle); + + if (private->use_center_handle) + { + gimp_canvas_handle_set_position (private->center_items[0], + private->tcx, private->tcy); + gimp_canvas_handle_set_size (private->center_items[0], d, d); + gimp_canvas_handle_set_angles (private->center_items[0], angle[8], 0.0); + + gimp_canvas_handle_set_position (private->center_items[1], + private->tcx, private->tcy); + gimp_canvas_handle_set_size (private->center_items[1], d, d); + gimp_canvas_handle_set_angles (private->center_items[1], angle[8], 0.0); + } + + gimp_tool_transform_grid_update_hilight (grid); +} + +gint +gimp_tool_transform_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + + private->button_down = TRUE; + private->mousex = coords->x; + private->mousey = coords->y; + + if (private->handle != GIMP_TRANSFORM_HANDLE_NONE) + { + if (private->handles[private->handle]) + { + GimpCanvasItem *handle; + gdouble x, y; + + switch (private->handle) + { + case GIMP_TRANSFORM_HANDLE_CENTER: + handle = private->center_items[0]; + break; + + case GIMP_TRANSFORM_HANDLE_PIVOT: + handle = private->pivot_items[0]; + break; + + default: + handle = private->handles[private->handle]; + break; + } + + gimp_canvas_handle_get_position (handle, &x, &y); + + gimp_tool_widget_set_snap_offsets (widget, + SIGNED_ROUND (x - coords->x), + SIGNED_ROUND (y - coords->y), + 0, 0); + } + else + { + gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0); + } + + private->prev_tx1 = private->tx1; + private->prev_ty1 = private->ty1; + private->prev_tx2 = private->tx2; + private->prev_ty2 = private->ty2; + private->prev_tx3 = private->tx3; + private->prev_ty3 = private->ty3; + private->prev_tx4 = private->tx4; + private->prev_ty4 = private->ty4; + private->prev_tpx = private->tpx; + private->prev_tpy = private->tpy; + private->prev_tcx = private->tcx; + private->prev_tcy = private->tcy; + + return private->handle; + } + + gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0); + + return 0; +} + +void +gimp_tool_transform_grid_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + + private->button_down = FALSE; +} + +void +gimp_tool_transform_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + gdouble *x[4], *y[4]; + gdouble *newpivot_x, *newpivot_y; + + GimpVector2 oldpos[5], newpos[4]; + GimpVector2 cur = { .x = coords->x, + .y = coords->y }; + GimpVector2 mouse = { .x = private->mousex, + .y = private->mousey }; + GimpVector2 d; + GimpVector2 pivot; + + gboolean fixedpivot = private->fixedpivot; + GimpTransformHandle handle = private->handle; + gint i; + + private->curx = coords->x; + private->cury = coords->y; + + x[0] = &private->tx1; + y[0] = &private->ty1; + x[1] = &private->tx2; + y[1] = &private->ty2; + x[2] = &private->tx3; + y[2] = &private->ty3; + x[3] = &private->tx4; + y[3] = &private->ty4; + + newpos[0].x = oldpos[0].x = private->prev_tx1; + newpos[0].y = oldpos[0].y = private->prev_ty1; + newpos[1].x = oldpos[1].x = private->prev_tx2; + newpos[1].y = oldpos[1].y = private->prev_ty2; + newpos[2].x = oldpos[2].x = private->prev_tx3; + newpos[2].y = oldpos[2].y = private->prev_ty3; + newpos[3].x = oldpos[3].x = private->prev_tx4; + newpos[3].y = oldpos[3].y = private->prev_ty4; + + /* put center point in this array too */ + oldpos[4].x = private->prev_tcx; + oldpos[4].y = private->prev_tcy; + + d = vectorsubtract (cur, mouse); + + newpivot_x = &private->tpx; + newpivot_y = &private->tpy; + + if (private->use_pivot_handle) + { + pivot.x = private->prev_tpx; + pivot.y = private->prev_tpy; + } + else + { + /* when the transform grid doesn't use a pivot handle, use the center + * point as the pivot instead. + */ + pivot.x = private->prev_tcx; + pivot.y = private->prev_tcy; + + fixedpivot = TRUE; + } + + /* move */ + if (handle == GIMP_TRANSFORM_HANDLE_CENTER) + { + if (private->constrain_move) + { + /* snap to 45 degree vectors from starting point */ + gdouble angle = 16.0 * calcangle ((GimpVector2) { 1.0, 0.0 }, + d) / (2.0 * G_PI); + gdouble dist = norm (d) / sqrt (2); + + if (angle < 1.0 || angle >= 15.0) + d.y = 0; + else if (angle < 3.0) + d.y = -(d.x = dist); + else if (angle < 5.0) + d.x = 0; + else if (angle < 7.0) + d.x = d.y = -dist; + else if (angle < 9.0) + d.y = 0; + else if (angle < 11.0) + d.x = -(d.y = dist); + else if (angle < 13.0) + d.x = 0; + else if (angle < 15.0) + d.x = d.y = dist; + } + + for (i = 0; i < 4; i++) + newpos[i] = vectoradd (oldpos[i], d); + } + + /* rotate */ + if (handle == GIMP_TRANSFORM_HANDLE_ROTATION) + { + gdouble angle = calcangle (vectorsubtract (cur, pivot), + vectorsubtract (mouse, pivot)); + + if (private->constrain_rotate) + { + /* round to 15 degree multiple */ + angle /= 2 * G_PI / 24.0; + angle = round (angle); + angle *= 2 * G_PI / 24.0; + } + + for (i = 0; i < 4; i++) + newpos[i] = vectoradd (pivot, + rotate2d (vectorsubtract (oldpos[i], pivot), + angle)); + + fixedpivot = TRUE; + } + + /* move rotation axis */ + if (handle == GIMP_TRANSFORM_HANDLE_PIVOT) + { + pivot = vectoradd (pivot, d); + + if (private->cornersnap) + { + /* snap to corner points and center */ + gint closest = 0; + gdouble closest_dist = G_MAXDOUBLE, dist; + + for (i = 0; i < 5; i++) + { + dist = norm (vectorsubtract (pivot, oldpos[i])); + if (dist < closest_dist) + { + closest_dist = dist; + closest = i; + } + } + + if (closest_dist * + gimp_tool_widget_get_shell (widget)->scale_x < 50) + { + pivot = oldpos[closest]; + } + } + + fixedpivot = TRUE; + } + + /* scaling via corner */ + if (handle == GIMP_TRANSFORM_HANDLE_NW || + handle == GIMP_TRANSFORM_HANDLE_NE || + handle == GIMP_TRANSFORM_HANDLE_SE || + handle == GIMP_TRANSFORM_HANDLE_SW) + { + /* Scaling through scale handles means translating one corner point, + * with all sides at constant angles. + */ + + gint this, left, right, opposite; + + /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */ + if (handle == GIMP_TRANSFORM_HANDLE_NW) + { + this = 0; left = 1; right = 2; opposite = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_NE) + { + this = 1; left = 3; right = 0; opposite = 2; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SW) + { + this = 2; left = 0; right = 3; opposite = 1; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SE) + { + this = 3; left = 2; right = 1; opposite = 0; + } + else + gimp_assert_not_reached (); + + /* when the keep aspect transformation constraint is enabled, + * the translation shall only be along the diagonal that runs + * trough this corner point. + */ + if (private->constrain_scale) + { + /* restrict to movement along the diagonal */ + GimpVector2 diag = vectorsubtract (oldpos[this], oldpos[opposite]); + + d = vectorproject (d, diag); + } + + /* Move the corner being interacted with */ + /* rp---------tp + * / /\ <- d, the interaction vector + * / / tp + * op----------/ + * + */ + newpos[this] = vectoradd (oldpos[this], d); + + /* Where the corner to the right and left would go, need these to form + * lines to intersect with the sides */ + /* rp----------/ + * /\ /\ + * / nr / nt + * op----------lp + * \ + * nl + */ + + newpos[right] = vectoradd (oldpos[right], d); + newpos[left] = vectoradd (oldpos[left], d); + + /* Now we just need to find the intersection of op-rp and nr-nt. + * rp----------/ + * / / + * / nr==========nt + * op----------/ + * + */ + newpos[right] = lineintersect (newpos[right], newpos[this], + oldpos[opposite], oldpos[right]); + newpos[left] = lineintersect (newpos[left], newpos[this], + oldpos[opposite], oldpos[left]); + /* /-----------/ + * / / + * rp============nt + * op----------/ + * + */ + + /* + * + * /--------------/ + * /--------------/ + * + */ + + if (private->frompivot_scale && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + /* transform the pivot point before the interaction and + * after, and move everything by this difference + */ + //TODO the handle doesn't actually end up where the mouse cursor is + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* scaling via sides */ + if (handle == GIMP_TRANSFORM_HANDLE_N || + handle == GIMP_TRANSFORM_HANDLE_E || + handle == GIMP_TRANSFORM_HANDLE_S || + handle == GIMP_TRANSFORM_HANDLE_W) + { + gint this_l, this_r, opp_l, opp_r; + GimpVector2 side_l, side_r, midline; + + /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */ + if (handle == GIMP_TRANSFORM_HANDLE_N) + { + this_l = 1; this_r = 0; + } + else if (handle == GIMP_TRANSFORM_HANDLE_E) + { + this_l = 3; this_r = 1; + } + else if (handle == GIMP_TRANSFORM_HANDLE_S) + { + this_l = 2; this_r = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_W) + { + this_l = 0; this_r = 2; + } + else + gimp_assert_not_reached (); + + opp_l = 3 - this_r; opp_r = 3 - this_l; + + side_l = vectorsubtract (oldpos[opp_l], oldpos[this_l]); + side_r = vectorsubtract (oldpos[opp_r], oldpos[this_r]); + midline = vectoradd (side_l, side_r); + + /* restrict to movement along the midline */ + d = vectorproject (d, midline); + + if (private->constrain_scale) + { + GimpVector2 before, after, effective_pivot = pivot; + gdouble distance; + + if (! private->frompivot_scale) + { + /* center of the opposite side is pivot */ + effective_pivot = scalemult (vectoradd (oldpos[opp_l], + oldpos[opp_r]), 0.5); + } + + /* get the difference between the distance from the pivot to + * where interaction started and the distance from the pivot + * to where cursor is now, and scale all corners distance + * from the pivot with this factor + */ + before = vectorsubtract (effective_pivot, mouse); + after = vectorsubtract (effective_pivot, cur); + after = vectorproject (after, before); + + distance = 0.5 * (after.x / before.x + after.y / before.y); + + for (i = 0; i < 4; i++) + newpos[i] = vectoradd (effective_pivot, + scalemult (vectorsubtract (oldpos[i], + effective_pivot), + distance)); + } + else + { + /* just move the side */ + newpos[this_l] = vectoradd (oldpos[this_l], d); + newpos[this_r] = vectoradd (oldpos[this_r], d); + } + + if (! private->constrain_scale && + private->frompivot_scale && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* shear */ + if (handle == GIMP_TRANSFORM_HANDLE_N_S || + handle == GIMP_TRANSFORM_HANDLE_E_S || + handle == GIMP_TRANSFORM_HANDLE_S_S || + handle == GIMP_TRANSFORM_HANDLE_W_S) + { + gint this_l, this_r; + + /* set up indices for this edge and the opposite edge */ + if (handle == GIMP_TRANSFORM_HANDLE_N_S) + { + this_l = 1; this_r = 0; + } + else if (handle == GIMP_TRANSFORM_HANDLE_W_S) + { + this_l = 0; this_r = 2; + } + else if (handle == GIMP_TRANSFORM_HANDLE_S_S) + { + this_l = 2; this_r = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_E_S) + { + this_l = 3; this_r = 1; + } + else + gimp_assert_not_reached (); + + if (private->constrain_shear) + { + /* restrict to movement along the side */ + GimpVector2 side = vectorsubtract (oldpos[this_r], oldpos[this_l]); + + d = vectorproject (d, side); + } + + newpos[this_l] = vectoradd (oldpos[this_l], d); + newpos[this_r] = vectoradd (oldpos[this_r], d); + + if (private->frompivot_shear && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* perspective transform */ + if (handle == GIMP_TRANSFORM_HANDLE_NW_P || + handle == GIMP_TRANSFORM_HANDLE_NE_P || + handle == GIMP_TRANSFORM_HANDLE_SE_P || + handle == GIMP_TRANSFORM_HANDLE_SW_P) + { + gint this, left, right, opposite; + + /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */ + if (handle == GIMP_TRANSFORM_HANDLE_NW_P) + { + this = 0; left = 1; right = 2; opposite = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_NE_P) + { + this = 1; left = 3; right = 0; opposite = 2; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SW_P) + { + this = 2; left = 0; right = 3; opposite = 1; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SE_P) + { + this = 3; left = 2; right = 1; opposite = 0; + } + else + gimp_assert_not_reached (); + + if (private->constrain_perspective) + { + /* when the constrain transformation constraint is enabled, + * the translation shall only be either along the side + * angles of the two sides that run to this corner point, or + * along the diagonal that runs trough this corner point. + */ + GimpVector2 proj[4]; + gdouble rej[4]; + + for (i = 0; i < 4; i++) + { + if (i == this) + continue; + + /* get the vectors along the sides and the diagonal */ + proj[i] = vectorsubtract (oldpos[this], oldpos[i]); + + /* project d on each candidate vector and see which has + * the shortest rejection + */ + proj[i] = vectorproject (d, proj[i]); + rej[i] = norm (vectorsubtract (d, proj[i])); + } + + if (rej[left] < rej[right] && rej[left] < rej[opposite]) + d = proj[left]; + else if (rej[right] < rej[opposite]) + d = proj[right]; + else + d = proj[opposite]; + } + + newpos[this] = vectoradd (oldpos[this], d); + + if (private->frompivot_perspective && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* this will have been set to TRUE if an operation used the pivot in + * addition to being a user option + */ + if (! fixedpivot && + transform_is_convex (newpos) && + transform_is_convex (oldpos) && + point_is_inside_polygon_pos (oldpos, pivot)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + pivot = vectoradd (pivot, delta); + } + + /* make sure the new coordinates are valid */ + for (i = 0; i < 4; i++) + { + if (! isfinite (newpos[i].x) || ! isfinite (newpos[i].y)) + return; + } + + if (! isfinite (pivot.x) || ! isfinite (pivot.y)) + return; + + for (i = 0; i < 4; i++) + { + *x[i] = newpos[i].x; + *y[i] = newpos[i].y; + } + + /* set unconditionally: if options get toggled during operation, we + * have to move pivot back + */ + *newpivot_x = pivot.x; + *newpivot_y = pivot.y; + + gimp_tool_transform_grid_update_matrix (grid); +} + +static const gchar * +get_friendly_operation_name (GimpTransformHandle handle) +{ + switch (handle) + { + case GIMP_TRANSFORM_HANDLE_NONE: + return ""; + case GIMP_TRANSFORM_HANDLE_NW_P: + case GIMP_TRANSFORM_HANDLE_NE_P: + case GIMP_TRANSFORM_HANDLE_SW_P: + case GIMP_TRANSFORM_HANDLE_SE_P: + return _("Click-Drag to change perspective"); + case GIMP_TRANSFORM_HANDLE_NW: + case GIMP_TRANSFORM_HANDLE_NE: + case GIMP_TRANSFORM_HANDLE_SW: + case GIMP_TRANSFORM_HANDLE_SE: + return _("Click-Drag to scale"); + case GIMP_TRANSFORM_HANDLE_N: + case GIMP_TRANSFORM_HANDLE_S: + case GIMP_TRANSFORM_HANDLE_E: + case GIMP_TRANSFORM_HANDLE_W: + return _("Click-Drag to scale"); + case GIMP_TRANSFORM_HANDLE_CENTER: + return _("Click-Drag to move"); + case GIMP_TRANSFORM_HANDLE_PIVOT: + return _("Click-Drag to move the pivot point"); + case GIMP_TRANSFORM_HANDLE_N_S: + case GIMP_TRANSFORM_HANDLE_S_S: + case GIMP_TRANSFORM_HANDLE_E_S: + case GIMP_TRANSFORM_HANDLE_W_S: + return _("Click-Drag to shear"); + case GIMP_TRANSFORM_HANDLE_ROTATION: + return _("Click-Drag to rotate"); + default: + gimp_assert_not_reached (); + } +} + +static GimpTransformHandle +gimp_tool_transform_get_area_handle (GimpToolTransformGrid *grid, + const GimpCoords *coords, + GimpTransformFunction function) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle handle = GIMP_TRANSFORM_HANDLE_NONE; + + switch (function) + { + case GIMP_TRANSFORM_FUNCTION_NONE: + break; + + case GIMP_TRANSFORM_FUNCTION_MOVE: + handle = GIMP_TRANSFORM_HANDLE_CENTER; + break; + + case GIMP_TRANSFORM_FUNCTION_ROTATE: + handle = GIMP_TRANSFORM_HANDLE_ROTATION; + break; + + case GIMP_TRANSFORM_FUNCTION_SCALE: + case GIMP_TRANSFORM_FUNCTION_PERSPECTIVE: + { + gdouble closest_dist; + gdouble dist; + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx1, + private->ty1); + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_NW_P; + else + handle = GIMP_TRANSFORM_HANDLE_NW; + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx2, + private->ty2); + if (dist < closest_dist) + { + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_NE_P; + else + handle = GIMP_TRANSFORM_HANDLE_NE; + } + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx3, + private->ty3); + if (dist < closest_dist) + { + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_SW_P; + else + handle = GIMP_TRANSFORM_HANDLE_SW; + } + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx4, + private->ty4); + if (dist < closest_dist) + { + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_SE_P; + else + handle = GIMP_TRANSFORM_HANDLE_SE; + } + } + break; + + case GIMP_TRANSFORM_FUNCTION_SHEAR: + { + gdouble handle_x; + gdouble handle_y; + gdouble closest_dist; + gdouble dist; + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_N], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_N_S; + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_W], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + if (dist < closest_dist) + { + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_W_S; + } + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_E], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + if (dist < closest_dist) + { + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_E_S; + } + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_S], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + if (dist < closest_dist) + { + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_S_S; + } + } + break; + } + + return handle; +} + +GimpHit +gimp_tool_transform_grid_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpTransformHandle handle; + + handle = gimp_tool_transform_grid_get_handle_for_coords (grid, coords); + + if (handle != GIMP_TRANSFORM_HANDLE_NONE) + return GIMP_HIT_DIRECT; + + return GIMP_HIT_INDIRECT; +} + +void +gimp_tool_transform_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle handle; + + handle = gimp_tool_transform_grid_get_handle_for_coords (grid, coords); + + if (handle == GIMP_TRANSFORM_HANDLE_NONE) + { + /* points passed in clockwise order */ + if (point_is_inside_polygon (4, + (gdouble[4]){ private->tx1, private->tx2, + private->tx4, private->tx3 }, + (gdouble[4]){ private->ty1, private->ty2, + private->ty4, private->ty3 }, + coords->x, coords->y)) + { + handle = gimp_tool_transform_get_area_handle (grid, coords, + private->inside_function); + } + else + { + handle = gimp_tool_transform_get_area_handle (grid, coords, + private->outside_function); + } + } + + if (handle != GIMP_TRANSFORM_HANDLE_NONE && proximity) + { + gimp_tool_widget_set_status (widget, + get_friendly_operation_name (handle)); + } + else + { + gimp_tool_widget_set_status (widget, NULL); + } + + private->handle = handle; + + gimp_tool_transform_grid_update_hilight (grid); +} + +void +gimp_tool_transform_grid_leave_notify (GimpToolWidget *widget) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + + private->handle = GIMP_TRANSFORM_HANDLE_NONE; + + gimp_tool_transform_grid_update_hilight (grid); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static void +gimp_tool_transform_grid_modifier (GimpToolWidget *widget, + GdkModifierType key) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + + if (key == gimp_get_constrain_behavior_mask ()) + { + g_object_set (widget, + "frompivot-scale", ! private->frompivot_scale, + "frompivot-shear", ! private->frompivot_shear, + "frompivot-perspective", ! private->frompivot_perspective, + NULL); + } + else if (key == gimp_get_extend_selection_mask ()) + { + g_object_set (widget, + "cornersnap", ! private->cornersnap, + "constrain-move", ! private->constrain_move, + "constrain-scale", ! private->constrain_scale, + "constrain-rotate", ! private->constrain_rotate, + "constrain-shear", ! private->constrain_shear, + "constrain-perspective", ! private->constrain_perspective, + NULL); + } +} + +static void +gimp_tool_transform_grid_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + GimpCoords coords = { 0.0, }; + + gimp_tool_transform_grid_modifier (widget, key); + + if (private->button_down) + { + /* send a non-motion to update the grid with the new constraints */ + coords.x = private->curx; + coords.y = private->cury; + gimp_tool_transform_grid_motion (widget, &coords, 0, state); + } +} + +static gboolean +gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + gdouble angle[9]; + gint i; + GimpCursorType map[8]; + GimpVector2 pos[4], this, that; + gboolean flip = FALSE; + gboolean side = FALSE; + gboolean set_cursor = TRUE; + + map[0] = GIMP_CURSOR_CORNER_TOP_LEFT; + map[1] = GIMP_CURSOR_CORNER_TOP; + map[2] = GIMP_CURSOR_CORNER_TOP_RIGHT; + map[3] = GIMP_CURSOR_CORNER_RIGHT; + map[4] = GIMP_CURSOR_CORNER_BOTTOM_RIGHT; + map[5] = GIMP_CURSOR_CORNER_BOTTOM; + map[6] = GIMP_CURSOR_CORNER_BOTTOM_LEFT; + map[7] = GIMP_CURSOR_CORNER_LEFT; + + get_handle_geometry (grid, pos, angle); + + for (i = 0; i < 8; i++) + angle[i] = round (angle[i] * 180.0 / G_PI / 45.0); + + switch (private->handle) + { + case GIMP_TRANSFORM_HANDLE_NW_P: + case GIMP_TRANSFORM_HANDLE_NW: + i = (gint) angle[4] + 0; + this = pos[0]; + that = pos[3]; + break; + + case GIMP_TRANSFORM_HANDLE_NE_P: + case GIMP_TRANSFORM_HANDLE_NE: + i = (gint) angle[5] + 2; + this = pos[1]; + that = pos[2]; + break; + + case GIMP_TRANSFORM_HANDLE_SW_P: + case GIMP_TRANSFORM_HANDLE_SW: + i = (gint) angle[6] + 6; + this = pos[2]; + that = pos[1]; + break; + + case GIMP_TRANSFORM_HANDLE_SE_P: + case GIMP_TRANSFORM_HANDLE_SE: + i = (gint) angle[7] + 4; + this = pos[3]; + that = pos[0]; + break; + + case GIMP_TRANSFORM_HANDLE_N: + case GIMP_TRANSFORM_HANDLE_N_S: + i = (gint) angle[0] + 1; + this = vectoradd (pos[0], pos[1]); + that = vectoradd (pos[2], pos[3]); + side = TRUE; + break; + + case GIMP_TRANSFORM_HANDLE_S: + case GIMP_TRANSFORM_HANDLE_S_S: + i = (gint) angle[1] + 5; + this = vectoradd (pos[2], pos[3]); + that = vectoradd (pos[0], pos[1]); + side = TRUE; + break; + + case GIMP_TRANSFORM_HANDLE_E: + case GIMP_TRANSFORM_HANDLE_E_S: + i = (gint) angle[2] + 3; + this = vectoradd (pos[1], pos[3]); + that = vectoradd (pos[0], pos[2]); + side = TRUE; + break; + + case GIMP_TRANSFORM_HANDLE_W: + case GIMP_TRANSFORM_HANDLE_W_S: + i = (gint) angle[3] + 7; + this = vectoradd (pos[0], pos[2]); + that = vectoradd (pos[1], pos[3]); + side = TRUE; + break; + + default: + set_cursor = FALSE; + break; + } + + if (set_cursor) + { + i %= 8; + + switch (map[i]) + { + case GIMP_CURSOR_CORNER_TOP_LEFT: + if (this.x + this.y > that.x + that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_TOP: + if (this.y > that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_TOP_RIGHT: + if (this.x - this.y < that.x - that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_RIGHT: + if (this.x < that.x) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_BOTTOM_RIGHT: + if (this.x + this.y < that.x + that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_BOTTOM: + if (this.y < that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_BOTTOM_LEFT: + if (this.x - this.y > that.x - that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_LEFT: + if (this.x > that.x) + flip = TRUE; + break; + default: + gimp_assert_not_reached (); + } + + if (flip) + *cursor = map[(i + 4) % 8]; + else + *cursor = map[i]; + + if (side) + *cursor += 8; + } + + /* parent class handles *cursor and *modifier for most handles */ + switch (private->handle) + { + case GIMP_TRANSFORM_HANDLE_NONE: + *tool_cursor = GIMP_TOOL_CURSOR_NONE; + break; + + case GIMP_TRANSFORM_HANDLE_NW_P: + case GIMP_TRANSFORM_HANDLE_NE_P: + case GIMP_TRANSFORM_HANDLE_SW_P: + case GIMP_TRANSFORM_HANDLE_SE_P: + *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE; + break; + + case GIMP_TRANSFORM_HANDLE_NW: + case GIMP_TRANSFORM_HANDLE_NE: + case GIMP_TRANSFORM_HANDLE_SW: + case GIMP_TRANSFORM_HANDLE_SE: + case GIMP_TRANSFORM_HANDLE_N: + case GIMP_TRANSFORM_HANDLE_S: + case GIMP_TRANSFORM_HANDLE_E: + case GIMP_TRANSFORM_HANDLE_W: + *tool_cursor = GIMP_TOOL_CURSOR_RESIZE; + break; + + case GIMP_TRANSFORM_HANDLE_CENTER: + *tool_cursor = GIMP_TOOL_CURSOR_MOVE; + break; + + case GIMP_TRANSFORM_HANDLE_PIVOT: + *tool_cursor = GIMP_TOOL_CURSOR_ROTATE; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case GIMP_TRANSFORM_HANDLE_N_S: + case GIMP_TRANSFORM_HANDLE_S_S: + case GIMP_TRANSFORM_HANDLE_E_S: + case GIMP_TRANSFORM_HANDLE_W_S: + *tool_cursor = GIMP_TOOL_CURSOR_SHEAR; + break; + + case GIMP_TRANSFORM_HANDLE_ROTATION: + *tool_cursor = GIMP_TOOL_CURSOR_ROTATE; + break; + + default: + g_return_val_if_reached (FALSE); + } + + return TRUE; +} + +static GimpTransformHandle +gimp_tool_transform_grid_get_handle_for_coords (GimpToolTransformGrid *grid, + const GimpCoords *coords) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle i; + + for (i = GIMP_TRANSFORM_HANDLE_NONE + 1; i < GIMP_N_TRANSFORM_HANDLES; i++) + { + if (private->handles[i] && + gimp_canvas_item_hit (private->handles[i], coords->x, coords->y)) + { + return i; + } + } + + return GIMP_TRANSFORM_HANDLE_NONE; +} + +static void +gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle handle; + + for (handle = GIMP_TRANSFORM_HANDLE_NONE; + handle < GIMP_N_TRANSFORM_HANDLES; + handle++) + { + if (private->handles[handle]) + { + gimp_canvas_item_set_highlight (private->handles[handle], + handle == private->handle); + } + } +} + +static void +gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + + gimp_matrix3_transform_point (&private->transform, + private->x1, private->y1, + &private->tx1, &private->ty1); + gimp_matrix3_transform_point (&private->transform, + private->x2, private->y1, + &private->tx2, &private->ty2); + gimp_matrix3_transform_point (&private->transform, + private->x1, private->y2, + &private->tx3, &private->ty3); + gimp_matrix3_transform_point (&private->transform, + private->x2, private->y2, + &private->tx4, &private->ty4); + + /* don't transform pivot */ + private->tpx = private->pivot_x; + private->tpy = private->pivot_y; + + if (transform_grid_is_convex (grid)) + { + gimp_matrix3_transform_point (&private->transform, + (private->x1 + private->x2) / 2.0, + (private->y1 + private->y2) / 2.0, + &private->tcx, &private->tcy); + } + else + { + private->tcx = (private->tx1 + + private->tx2 + + private->tx3 + + private->tx4) / 4.0; + private->tcy = (private->ty1 + + private->ty2 + + private->ty3 + + private->ty4) / 4.0; + } +} + +static void +gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + + gimp_matrix3_identity (&private->transform); + gimp_transform_matrix_perspective (&private->transform, + private->x1, + private->y1, + private->x2 - private->x1, + private->y2 - private->y1, + private->tx1, + private->ty1, + private->tx2, + private->ty2, + private->tx3, + private->ty3, + private->tx4, + private->ty4); + + private->pivot_x = private->tpx; + private->pivot_y = private->tpy; + + g_object_freeze_notify (G_OBJECT (grid)); + g_object_notify (G_OBJECT (grid), "transform"); + g_object_notify (G_OBJECT (grid), "pivot-x"); + g_object_notify (G_OBJECT (grid), "pivot-x"); + g_object_thaw_notify (G_OBJECT (grid)); +} + +static void +gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid, + gint *handle_w, + gint *handle_h) +{ + GimpToolTransformGridPrivate *private = grid->private; + gint dx1, dy1; + gint dx2, dy2; + gint dx3, dy3; + gint dx4, dy4; + gint x1, y1; + gint x2, y2; + + if (! private->dynamic_handle_size) + { + *handle_w = GIMP_CANVAS_HANDLE_SIZE_LARGE; + *handle_h = GIMP_CANVAS_HANDLE_SIZE_LARGE; + + return; + } + + gimp_canvas_item_transform_xy (private->guides, + private->tx1, private->ty1, + &dx1, &dy1); + gimp_canvas_item_transform_xy (private->guides, + private->tx2, private->ty2, + &dx2, &dy2); + gimp_canvas_item_transform_xy (private->guides, + private->tx3, private->ty3, + &dx3, &dy3); + gimp_canvas_item_transform_xy (private->guides, + private->tx4, private->ty4, + &dx4, &dy4); + + x1 = MIN4 (dx1, dx2, dx3, dx4); + y1 = MIN4 (dy1, dy2, dy3, dy4); + x2 = MAX4 (dx1, dx2, dx3, dx4); + y2 = MAX4 (dy1, dy2, dy3, dy4); + + *handle_w = CLAMP ((x2 - x1) / 3, + MIN_HANDLE_SIZE, GIMP_CANVAS_HANDLE_SIZE_LARGE); + *handle_h = CLAMP ((y2 - y1) / 3, + MIN_HANDLE_SIZE, GIMP_CANVAS_HANDLE_SIZE_LARGE); +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_transform_grid_new (GimpDisplayShell *shell, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_GRID, + "shell", shell, + "transform", transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); +} + + +/* protected functions */ + +GimpTransformHandle +gimp_tool_transform_grid_get_handle (GimpToolTransformGrid *grid) +{ + g_return_val_if_fail (GIMP_IS_TOOL_TRANSFORM_GRID (grid), + GIMP_TRANSFORM_HANDLE_NONE); + + return grid->private->handle; +} diff --git a/app/display/gimptooltransformgrid.h b/app/display/gimptooltransformgrid.h new file mode 100644 index 0000000..949beb2 --- /dev/null +++ b/app/display/gimptooltransformgrid.h @@ -0,0 +1,99 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooltransformgrid.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_TRANSFORM_GRID_H__ +#define __GIMP_TOOL_TRANSFORM_GRID_H__ + + +#include "gimpcanvashandle.h" +#include "gimptoolwidget.h" + + +#define GIMP_TOOL_TRANSFORM_GRID_MAX_HANDLE_SIZE \ + (1.5 * GIMP_CANVAS_HANDLE_SIZE_LARGE) + + +typedef enum +{ + GIMP_TRANSFORM_HANDLE_NONE, + GIMP_TRANSFORM_HANDLE_NW_P, /* north west perspective */ + GIMP_TRANSFORM_HANDLE_NE_P, /* north east perspective */ + GIMP_TRANSFORM_HANDLE_SW_P, /* south west perspective */ + GIMP_TRANSFORM_HANDLE_SE_P, /* south east perspective */ + GIMP_TRANSFORM_HANDLE_NW, /* north west */ + GIMP_TRANSFORM_HANDLE_NE, /* north east */ + GIMP_TRANSFORM_HANDLE_SW, /* south west */ + GIMP_TRANSFORM_HANDLE_SE, /* south east */ + GIMP_TRANSFORM_HANDLE_N, /* north */ + GIMP_TRANSFORM_HANDLE_S, /* south */ + GIMP_TRANSFORM_HANDLE_E, /* east */ + GIMP_TRANSFORM_HANDLE_W, /* west */ + GIMP_TRANSFORM_HANDLE_CENTER, /* center for moving */ + GIMP_TRANSFORM_HANDLE_PIVOT, /* pivot for rotation and scaling */ + GIMP_TRANSFORM_HANDLE_N_S, /* north shearing */ + GIMP_TRANSFORM_HANDLE_S_S, /* south shearing */ + GIMP_TRANSFORM_HANDLE_E_S, /* east shearing */ + GIMP_TRANSFORM_HANDLE_W_S, /* west shearing */ + GIMP_TRANSFORM_HANDLE_ROTATION, /* rotation */ + + GIMP_N_TRANSFORM_HANDLES /* keep this last so *handles[] is the right size */ +} GimpTransformHandle; + + +#define GIMP_TYPE_TOOL_TRANSFORM_GRID (gimp_tool_transform_grid_get_type ()) +#define GIMP_TOOL_TRANSFORM_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGrid)) +#define GIMP_TOOL_TRANSFORM_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGridClass)) +#define GIMP_IS_TOOL_TRANSFORM_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID)) +#define GIMP_IS_TOOL_TRANSFORM_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_TRANSFORM_GRID)) +#define GIMP_TOOL_TRANSFORM_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGridClass)) + + +typedef struct _GimpToolTransformGrid GimpToolTransformGrid; +typedef struct _GimpToolTransformGridPrivate GimpToolTransformGridPrivate; +typedef struct _GimpToolTransformGridClass GimpToolTransformGridClass; + +struct _GimpToolTransformGrid +{ + GimpToolWidget parent_instance; + + GimpToolTransformGridPrivate *private; +}; + +struct _GimpToolTransformGridClass +{ + GimpToolWidgetClass parent_class; +}; + + +GType gimp_tool_transform_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_transform_grid_new (GimpDisplayShell *shell, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + +/* protected functions */ + +GimpTransformHandle gimp_tool_transform_grid_get_handle (GimpToolTransformGrid *grid); + + +#endif /* __GIMP_TOOL_TRANSFORM_GRID_H__ */ diff --git a/app/display/gimptoolwidget.c b/app/display/gimptoolwidget.c new file mode 100644 index 0000000..516f366 --- /dev/null +++ b/app/display/gimptoolwidget.c @@ -0,0 +1,1117 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolwidget.c + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdarg.h> + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "display-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpcanvasarc.h" +#include "gimpcanvascorner.h" +#include "gimpcanvasgroup.h" +#include "gimpcanvashandle.h" +#include "gimpcanvaslimit.h" +#include "gimpcanvasline.h" +#include "gimpcanvaspath.h" +#include "gimpcanvaspolygon.h" +#include "gimpcanvasrectangle.h" +#include "gimpcanvasrectangleguides.h" +#include "gimpcanvastransformguides.h" +#include "gimpdisplayshell.h" +#include "gimptoolwidget.h" + + +enum +{ + PROP_0, + PROP_SHELL, + PROP_ITEM +}; + +enum +{ + CHANGED, + RESPONSE, + SNAP_OFFSETS, + STATUS, + STATUS_COORDS, + MESSAGE, + FOCUS_CHANGED, + LAST_SIGNAL +}; + +struct _GimpToolWidgetPrivate +{ + GimpDisplayShell *shell; + GimpCanvasItem *item; + GList *group_stack; + + gint snap_offset_x; + gint snap_offset_y; + gint snap_width; + gint snap_height; + + gboolean visible; + gboolean focus; +}; + + +/* local function prototypes */ + +static void gimp_tool_widget_finalize (GObject *object); +static void gimp_tool_widget_constructed (GObject *object); +static void gimp_tool_widget_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_widget_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_tool_widget_properties_changed (GObject *object, + guint n_pspecs, + GParamSpec **pspecs); + +static void gimp_tool_widget_real_leave_notify (GimpToolWidget *widget); +static gboolean gimp_tool_widget_real_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolWidget, gimp_tool_widget, GIMP_TYPE_OBJECT) + +#define parent_class gimp_tool_widget_parent_class + +static guint widget_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_tool_widget_class_init (GimpToolWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_tool_widget_finalize; + object_class->constructed = gimp_tool_widget_constructed; + object_class->set_property = gimp_tool_widget_set_property; + object_class->get_property = gimp_tool_widget_get_property; + object_class->dispatch_properties_changed = gimp_tool_widget_properties_changed; + + klass->leave_notify = gimp_tool_widget_real_leave_notify; + klass->key_press = gimp_tool_widget_real_key_press; + + widget_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + widget_signals[RESPONSE] = + g_signal_new ("response", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, response), + NULL, NULL, + gimp_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + widget_signals[SNAP_OFFSETS] = + g_signal_new ("snap-offsets", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, snap_offsets), + NULL, NULL, + gimp_marshal_VOID__INT_INT_INT_INT, + G_TYPE_NONE, 4, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT); + + widget_signals[STATUS] = + g_signal_new ("status", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, status), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + widget_signals[STATUS_COORDS] = + g_signal_new ("status-coords", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, status_coords), + NULL, NULL, + gimp_marshal_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING, + G_TYPE_NONE, 5, + G_TYPE_STRING, + G_TYPE_DOUBLE, + G_TYPE_STRING, + G_TYPE_DOUBLE, + G_TYPE_STRING); + + widget_signals[MESSAGE] = + g_signal_new ("message", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, message), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + widget_signals[FOCUS_CHANGED] = + g_signal_new ("focus-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpToolWidgetClass, focus_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_property (object_class, PROP_SHELL, + g_param_spec_object ("shell", + NULL, NULL, + GIMP_TYPE_DISPLAY_SHELL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_ITEM, + g_param_spec_object ("item", + NULL, NULL, + GIMP_TYPE_CANVAS_ITEM, + GIMP_PARAM_READABLE)); +} + +static void +gimp_tool_widget_init (GimpToolWidget *widget) +{ + widget->private = gimp_tool_widget_get_instance_private (widget); + + widget->private->visible = TRUE; +} + +static void +gimp_tool_widget_constructed (GObject *object) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolWidgetPrivate *private = widget->private; + GimpToolWidgetClass *klass = GIMP_TOOL_WIDGET_GET_CLASS (widget); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_DISPLAY_SHELL (private->shell)); + + private->item = gimp_canvas_group_new (private->shell); + + gimp_canvas_item_set_visible (private->item, private->visible); + + if (klass->changed) + { + if (klass->update_on_scale) + { + g_signal_connect_object (private->shell, "scaled", + G_CALLBACK (klass->changed), + widget, + G_CONNECT_SWAPPED); + } + + if (klass->update_on_scroll) + { + g_signal_connect_object (private->shell, "scrolled", + G_CALLBACK (klass->changed), + widget, + G_CONNECT_SWAPPED); + } + + if (klass->update_on_rotate) + { + g_signal_connect_object (private->shell, "rotated", + G_CALLBACK (klass->changed), + widget, + G_CONNECT_SWAPPED); + } + } +} + +static void +gimp_tool_widget_finalize (GObject *object) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolWidgetPrivate *private = widget->private; + + g_clear_object (&private->item); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_tool_widget_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolWidgetPrivate *private = widget->private; + + switch (property_id) + { + case PROP_SHELL: + private->shell = g_value_get_object (value); /* don't ref */ + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_widget_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolWidgetPrivate *private = widget->private; + + switch (property_id) + { + case PROP_SHELL: + g_value_set_object (value, private->shell); + break; + + case PROP_ITEM: + g_value_set_object (value, private->item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_widget_properties_changed (GObject *object, + guint n_pspecs, + GParamSpec **pspecs) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + + G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object, + n_pspecs, + pspecs); + + gimp_tool_widget_changed (widget); +} + +static void +gimp_tool_widget_real_leave_notify (GimpToolWidget *widget) +{ + gimp_tool_widget_set_status (widget, NULL); +} + +static gboolean +gimp_tool_widget_real_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + switch (kevent->keyval) + { + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CONFIRM); + return TRUE; + + case GDK_KEY_Escape: + gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CANCEL); + return TRUE; + + case GDK_KEY_BackSpace: + gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_RESET); + return TRUE; + + default: + break; + } + + return FALSE; +} + + +/* public functions */ + +GimpDisplayShell * +gimp_tool_widget_get_shell (GimpToolWidget *widget) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + return widget->private->shell; +} + +GimpCanvasItem * +gimp_tool_widget_get_item (GimpToolWidget *widget) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + return widget->private->item; +} + +void +gimp_tool_widget_set_visible (GimpToolWidget *widget, + gboolean visible) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + if (visible != widget->private->visible) + { + widget->private->visible = visible; + + if (widget->private->item) + gimp_canvas_item_set_visible (widget->private->item, visible); + + if (! visible) + gimp_tool_widget_set_status (widget, NULL); + } +} + +gboolean +gimp_tool_widget_get_visible (GimpToolWidget *widget) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE); + + return widget->private->visible; +} + +void +gimp_tool_widget_set_focus (GimpToolWidget *widget, + gboolean focus) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + if (focus != widget->private->focus) + { + widget->private->focus = focus; + + g_signal_emit (widget, widget_signals[FOCUS_CHANGED], 0); + } +} + +gboolean +gimp_tool_widget_get_focus (GimpToolWidget *widget) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE); + + return widget->private->focus; +} + +void +gimp_tool_widget_changed (GimpToolWidget *widget) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + g_signal_emit (widget, widget_signals[CHANGED], 0); +} + +void +gimp_tool_widget_response (GimpToolWidget *widget, + gint response_id) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + g_signal_emit (widget, widget_signals[RESPONSE], 0, + response_id); +} + +void +gimp_tool_widget_set_snap_offsets (GimpToolWidget *widget, + gint offset_x, + gint offset_y, + gint width, + gint height) +{ + GimpToolWidgetPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + private = widget->private; + + if (offset_x != private->snap_offset_x || + offset_y != private->snap_offset_y || + width != private->snap_width || + height != private->snap_height) + { + private->snap_offset_x = offset_x; + private->snap_offset_y = offset_y; + private->snap_width = width; + private->snap_height = height; + + g_signal_emit (widget, widget_signals[SNAP_OFFSETS], 0, + offset_x, offset_y, width, height); + } +} + +void +gimp_tool_widget_get_snap_offsets (GimpToolWidget *widget, + gint *offset_x, + gint *offset_y, + gint *width, + gint *height) +{ + GimpToolWidgetPrivate *private; + + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + private = widget->private; + + if (offset_x) *offset_x = private->snap_offset_x; + if (offset_y) *offset_y = private->snap_offset_y; + if (width) *width = private->snap_width; + if (height) *height = private->snap_height; +} + +void +gimp_tool_widget_set_status (GimpToolWidget *widget, + const gchar *status) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + g_signal_emit (widget, widget_signals[STATUS], 0, + status); +} + +void +gimp_tool_widget_set_status_coords (GimpToolWidget *widget, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + g_signal_emit (widget, widget_signals[STATUS_COORDS], 0, + title, x, separator, y, help); +} + +void +gimp_tool_widget_message (GimpToolWidget *widget, + const gchar *format, + ...) +{ + va_list args; + gchar *message; + + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (format != NULL); + + va_start (args, format); + + message = g_strdup_vprintf (format, args); + + va_end (args); + + gimp_tool_widget_message_literal (widget, message); + + g_free (message); +} + +void +gimp_tool_widget_message_literal (GimpToolWidget *widget, + const gchar *message) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (message != NULL); + + g_signal_emit (widget, widget_signals[MESSAGE], 0, + message); +} + +void +gimp_tool_widget_add_item (GimpToolWidget *widget, + GimpCanvasItem *item) +{ + GimpCanvasGroup *group; + + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + group = GIMP_CANVAS_GROUP (widget->private->item); + + if (widget->private->group_stack) + group = widget->private->group_stack->data; + + gimp_canvas_group_add_item (group, item); +} + +void +gimp_tool_widget_remove_item (GimpToolWidget *widget, + GimpCanvasItem *item) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (GIMP_IS_CANVAS_ITEM (item)); + + gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (widget->private->item), + item); +} + +GimpCanvasGroup * +gimp_tool_widget_add_group (GimpToolWidget *widget) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_group_new (widget->private->shell); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return GIMP_CANVAS_GROUP (item); +} + +GimpCanvasGroup * +gimp_tool_widget_add_stroke_group (GimpToolWidget *widget) +{ + GimpCanvasGroup *group; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + group = gimp_tool_widget_add_group (widget); + gimp_canvas_group_set_group_stroking (group, TRUE); + + return group; +} + +GimpCanvasGroup * +gimp_tool_widget_add_fill_group (GimpToolWidget *widget) +{ + GimpCanvasGroup *group; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + group = gimp_tool_widget_add_group (widget); + gimp_canvas_group_set_group_filling (group, TRUE); + + return group; +} + +void +gimp_tool_widget_push_group (GimpToolWidget *widget, + GimpCanvasGroup *group) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (GIMP_IS_CANVAS_GROUP (group)); + + widget->private->group_stack = g_list_prepend (widget->private->group_stack, + group); +} + +void +gimp_tool_widget_pop_group (GimpToolWidget *widget) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (widget->private->group_stack != NULL); + + widget->private->group_stack = g_list_remove (widget->private->group_stack, + widget->private->group_stack->data); +} + +/** + * gimp_tool_widget_add_line: + * @widget: the #GimpToolWidget + * @x1: start point X in image coordinates + * @y1: start point Y in image coordinates + * @x2: end point X in image coordinates + * @y2: end point Y in image coordinates + * + * This function adds a #GimpCanvasLine to @widget. + **/ +GimpCanvasItem * +gimp_tool_widget_add_line (GimpToolWidget *widget, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_line_new (widget->private->shell, + x1, y1, x2, y2); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_rectangle (GimpToolWidget *widget, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gboolean filled) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_rectangle_new (widget->private->shell, + x, y, width, height, filled); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_arc (GimpToolWidget *widget, + gdouble center_x, + gdouble center_y, + gdouble radius_x, + gdouble radius_y, + gdouble start_angle, + gdouble slice_angle, + gboolean filled) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_arc_new (widget->private->shell, + center_x, center_y, + radius_x, radius_y, + start_angle, slice_angle, + filled); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_limit (GimpToolWidget *widget, + GimpLimitType type, + gdouble x, + gdouble y, + gdouble radius, + gdouble aspect_ratio, + gdouble angle, + gboolean dashed) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_limit_new (widget->private->shell, + type, + x, y, + radius, + aspect_ratio, + angle, + dashed); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_polygon (GimpToolWidget *widget, + GimpMatrix3 *transform, + const GimpVector2 *points, + gint n_points, + gboolean filled) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + g_return_val_if_fail (points == NULL || n_points > 0, NULL); + + item = gimp_canvas_polygon_new (widget->private->shell, + points, n_points, + transform, filled); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_polygon_from_coords (GimpToolWidget *widget, + GimpMatrix3 *transform, + const GimpCoords *points, + gint n_points, + gboolean filled) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + g_return_val_if_fail (points == NULL || n_points > 0, NULL); + + item = gimp_canvas_polygon_new_from_coords (widget->private->shell, + points, n_points, + transform, filled); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_path (GimpToolWidget *widget, + const GimpBezierDesc *desc) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_path_new (widget->private->shell, + desc, 0, 0, FALSE, GIMP_PATH_STYLE_DEFAULT); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_handle (GimpToolWidget *widget, + GimpHandleType type, + gdouble x, + gdouble y, + gint width, + gint height, + GimpHandleAnchor anchor) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_handle_new (widget->private->shell, + type, anchor, x, y, width, height); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_corner (GimpToolWidget *widget, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpHandleAnchor anchor, + gint corner_width, + gint corner_height, + gboolean outside) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_corner_new (widget->private->shell, + x, y, width, height, + anchor, corner_width, corner_height, + outside); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_rectangle_guides (GimpToolWidget *widget, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpGuidesType type) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_rectangle_guides_new (widget->private->shell, + x, y, width, height, type, 4); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +GimpCanvasItem * +gimp_tool_widget_add_transform_guides (GimpToolWidget *widget, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType type, + gint n_guides, + gboolean clip) +{ + GimpCanvasItem *item; + + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), NULL); + + item = gimp_canvas_transform_guides_new (widget->private->shell, + transform, x1, y1, x2, y2, + type, n_guides, clip); + + gimp_tool_widget_add_item (widget, item); + g_object_unref (item); + + return item; +} + +gint +gimp_tool_widget_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), 0); + g_return_val_if_fail (coords != NULL, 0); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_press) + { + return GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_press (widget, + coords, time, + state, + press_type); + } + + return 0; +} + +void +gimp_tool_widget_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (coords != NULL); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_release) + { + GIMP_TOOL_WIDGET_GET_CLASS (widget)->button_release (widget, + coords, time, state, + release_type); + } +} + +void +gimp_tool_widget_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (coords != NULL); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion) + { + GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion (widget, + coords, time, state); + } +} + +GimpHit +gimp_tool_widget_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), GIMP_HIT_NONE); + g_return_val_if_fail (coords != NULL, GIMP_HIT_NONE); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->hit) + { + return GIMP_TOOL_WIDGET_GET_CLASS (widget)->hit (widget, + coords, state, + proximity); + } + + return GIMP_HIT_NONE; +} + +void +gimp_tool_widget_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + g_return_if_fail (coords != NULL); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover) + { + GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover (widget, + coords, state, proximity); + } +} + +void +gimp_tool_widget_leave_notify (GimpToolWidget *widget) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->leave_notify) + { + GIMP_TOOL_WIDGET_GET_CLASS (widget)->leave_notify (widget); + } +} + +gboolean +gimp_tool_widget_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE); + g_return_val_if_fail (kevent != NULL, FALSE); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_press) + { + return GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_press (widget, kevent); + } + + return FALSE; +} + +gboolean +gimp_tool_widget_key_release (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE); + g_return_val_if_fail (kevent != NULL, FALSE); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_release) + { + return GIMP_TOOL_WIDGET_GET_CLASS (widget)->key_release (widget, kevent); + } + + return FALSE; +} + +void +gimp_tool_widget_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion_modifier) + { + GIMP_TOOL_WIDGET_GET_CLASS (widget)->motion_modifier (widget, + key, press, state); + } +} + +void +gimp_tool_widget_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET (widget)); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover_modifier) + { + GIMP_TOOL_WIDGET_GET_CLASS (widget)->hover_modifier (widget, + key, press, state); + } +} + +gboolean +gimp_tool_widget_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) + +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET (widget), FALSE); + g_return_val_if_fail (coords != NULL, FALSE); + + if (widget->private->visible && + GIMP_TOOL_WIDGET_GET_CLASS (widget)->get_cursor) + { + GimpCursorType my_cursor; + GimpToolCursorType my_tool_cursor; + GimpCursorModifier my_modifier; + + if (cursor) my_cursor = *cursor; + if (tool_cursor) my_tool_cursor = *tool_cursor; + if (modifier) my_modifier = *modifier; + + if (GIMP_TOOL_WIDGET_GET_CLASS (widget)->get_cursor (widget, coords, + state, + &my_cursor, + &my_tool_cursor, + &my_modifier)) + { + if (cursor) *cursor = my_cursor; + if (tool_cursor) *tool_cursor = my_tool_cursor; + if (modifier) *modifier = my_modifier; + + return TRUE; + } + } + + return FALSE; +} diff --git a/app/display/gimptoolwidget.h b/app/display/gimptoolwidget.h new file mode 100644 index 0000000..742aa2a --- /dev/null +++ b/app/display/gimptoolwidget.h @@ -0,0 +1,318 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolwidget.h + * Copyright (C) 2017 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_WIDGET_H__ +#define __GIMP_TOOL_WIDGET_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TOOL_WIDGET_RESPONSE_CONFIRM -1 +#define GIMP_TOOL_WIDGET_RESPONSE_CANCEL -2 +#define GIMP_TOOL_WIDGET_RESPONSE_RESET -3 + + +#define GIMP_TYPE_TOOL_WIDGET (gimp_tool_widget_get_type ()) +#define GIMP_TOOL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_WIDGET, GimpToolWidget)) +#define GIMP_TOOL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_WIDGET, GimpToolWidgetClass)) +#define GIMP_IS_TOOL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_WIDGET)) +#define GIMP_IS_TOOL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_WIDGET)) +#define GIMP_TOOL_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_WIDGET, GimpToolWidgetClass)) + + +typedef struct _GimpToolWidgetPrivate GimpToolWidgetPrivate; +typedef struct _GimpToolWidgetClass GimpToolWidgetClass; + +struct _GimpToolWidget +{ + GimpObject parent_instance; + + GimpToolWidgetPrivate *private; +}; + +struct _GimpToolWidgetClass +{ + GimpObjectClass parent_class; + + /* signals */ + void (* changed) (GimpToolWidget *widget); + void (* response) (GimpToolWidget *widget, + gint response_id); + void (* snap_offsets) (GimpToolWidget *widget, + gint offset_x, + gint offset_y, + gint width, + gint height); + void (* status) (GimpToolWidget *widget, + const gchar *status); + void (* status_coords) (GimpToolWidget *widget, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help); + void (* message) (GimpToolWidget *widget, + const gchar *message); + void (* focus_changed) (GimpToolWidget *widget); + + /* virtual functions */ + gint (* button_press) (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); + void (* button_release) (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); + void (* motion) (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + + GimpHit (* hit) (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); + void (* hover) (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); + void (* leave_notify) (GimpToolWidget *widget); + + gboolean (* key_press) (GimpToolWidget *widget, + GdkEventKey *kevent); + gboolean (* key_release) (GimpToolWidget *widget, + GdkEventKey *kevent); + + void (* motion_modifier) (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); + void (* hover_modifier) (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); + + gboolean (* get_cursor) (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + + gboolean update_on_scale; + gboolean update_on_scroll; + gboolean update_on_rotate; +}; + + +GType gimp_tool_widget_get_type (void) G_GNUC_CONST; + +GimpDisplayShell * gimp_tool_widget_get_shell (GimpToolWidget *widget); +GimpCanvasItem * gimp_tool_widget_get_item (GimpToolWidget *widget); + +void gimp_tool_widget_set_visible (GimpToolWidget *widget, + gboolean visible); +gboolean gimp_tool_widget_get_visible (GimpToolWidget *widget); + +void gimp_tool_widget_set_focus (GimpToolWidget *widget, + gboolean focus); +gboolean gimp_tool_widget_get_focus (GimpToolWidget *widget); + +/* for subclasses, to notify the handling tool + */ +void gimp_tool_widget_changed (GimpToolWidget *widget); + +void gimp_tool_widget_response (GimpToolWidget *widget, + gint response_id); + +void gimp_tool_widget_set_snap_offsets (GimpToolWidget *widget, + gint offset_x, + gint offset_y, + gint width, + gint height); +void gimp_tool_widget_get_snap_offsets (GimpToolWidget *widget, + gint *offset_x, + gint *offset_y, + gint *width, + gint *height); + +void gimp_tool_widget_set_status (GimpToolWidget *widget, + const gchar *status); +void gimp_tool_widget_set_status_coords (GimpToolWidget *widget, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help); + +void gimp_tool_widget_message (GimpToolWidget *widget, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +void gimp_tool_widget_message_literal (GimpToolWidget *widget, + const gchar *message); + +/* for subclasses, to add and manage their items + */ +void gimp_tool_widget_add_item (GimpToolWidget *widget, + GimpCanvasItem *item); +void gimp_tool_widget_remove_item (GimpToolWidget *widget, + GimpCanvasItem *item); + +GimpCanvasGroup * gimp_tool_widget_add_group (GimpToolWidget *widget); +GimpCanvasGroup * gimp_tool_widget_add_stroke_group (GimpToolWidget *widget); +GimpCanvasGroup * gimp_tool_widget_add_fill_group (GimpToolWidget *widget); + +void gimp_tool_widget_push_group (GimpToolWidget *widget, + GimpCanvasGroup *group); +void gimp_tool_widget_pop_group (GimpToolWidget *widget); + +/* for subclasses, convenience functions to add specific items + */ +GimpCanvasItem * gimp_tool_widget_add_line (GimpToolWidget *widget, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); +GimpCanvasItem * gimp_tool_widget_add_rectangle (GimpToolWidget *widget, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gboolean filled); +GimpCanvasItem * gimp_tool_widget_add_arc (GimpToolWidget *widget, + gdouble center_x, + gdouble center_y, + gdouble radius_x, + gdouble radius_y, + gdouble start_angle, + gdouble slice_angle, + gboolean filled); +GimpCanvasItem * gimp_tool_widget_add_limit (GimpToolWidget *widget, + GimpLimitType type, + gdouble x, + gdouble y, + gdouble radius, + gdouble aspect_ratio, + gdouble angle, + gboolean dashed); +GimpCanvasItem * gimp_tool_widget_add_polygon (GimpToolWidget *widget, + GimpMatrix3 *transform, + const GimpVector2 *points, + gint n_points, + gboolean filled); +GimpCanvasItem * gimp_tool_widget_add_polygon_from_coords + (GimpToolWidget *widget, + GimpMatrix3 *transform, + const GimpCoords *points, + gint n_points, + gboolean filled); +GimpCanvasItem * gimp_tool_widget_add_path (GimpToolWidget *widget, + const GimpBezierDesc *desc); + +GimpCanvasItem * gimp_tool_widget_add_handle (GimpToolWidget *widget, + GimpHandleType type, + gdouble x, + gdouble y, + gint width, + gint height, + GimpHandleAnchor anchor); +GimpCanvasItem * gimp_tool_widget_add_corner (GimpToolWidget *widget, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpHandleAnchor anchor, + gint corner_width, + gint corner_height, + gboolean outside); + +GimpCanvasItem * gimp_tool_widget_add_rectangle_guides + (GimpToolWidget *widget, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + GimpGuidesType type); +GimpCanvasItem * gimp_tool_widget_add_transform_guides + (GimpToolWidget *widget, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType type, + gint n_guides, + gboolean clip); + +/* for tools, to be called from the respective GimpTool method + * implementations + */ +gint gimp_tool_widget_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +void gimp_tool_widget_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +void gimp_tool_widget_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + +GimpHit gimp_tool_widget_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +void gimp_tool_widget_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +void gimp_tool_widget_leave_notify (GimpToolWidget *widget); + +gboolean gimp_tool_widget_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +gboolean gimp_tool_widget_key_release (GimpToolWidget *widget, + GdkEventKey *kevent); + +void gimp_tool_widget_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +void gimp_tool_widget_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); + +gboolean gimp_tool_widget_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + + +#endif /* __GIMP_TOOL_WIDGET_H__ */ diff --git a/app/display/gimptoolwidgetgroup.c b/app/display/gimptoolwidgetgroup.c new file mode 100644 index 0000000..d62f4c9 --- /dev/null +++ b/app/display/gimptoolwidgetgroup.c @@ -0,0 +1,731 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolwidgetgroup.c + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "display-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimplist.h" + +#include "gimpcanvasgroup.h" +#include "gimpdisplayshell.h" +#include "gimptoolwidgetgroup.h" + + +struct _GimpToolWidgetGroupPrivate +{ + GimpContainer *children; + + GimpToolWidget *focus_widget; + GimpToolWidget *hover_widget; + + gboolean auto_raise; +}; + + +/* local function prototypes */ + +static void gimp_tool_widget_group_finalize (GObject *object); + +static void gimp_tool_widget_group_focus_changed (GimpToolWidget *widget); +static gint gimp_tool_widget_group_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_widget_group_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_widget_group_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static GimpHit gimp_tool_widget_group_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_widget_group_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static void gimp_tool_widget_group_leave_notify (GimpToolWidget *widget); +static gboolean gimp_tool_widget_group_key_press (GimpToolWidget *widget, + GdkEventKey *kevent); +static gboolean gimp_tool_widget_group_key_release (GimpToolWidget *widget, + GdkEventKey *kevent); +static void gimp_tool_widget_group_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static void gimp_tool_widget_group_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state); +static gboolean gimp_tool_widget_group_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier); + +static void gimp_tool_widget_group_children_add (GimpContainer *container, + GimpToolWidget *child, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_children_remove (GimpContainer *container, + GimpToolWidget *child, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_children_reorder (GimpContainer *container, + GimpToolWidget *child, + gint new_index, + GimpToolWidgetGroup *group); + +static void gimp_tool_widget_group_child_changed (GimpToolWidget *child, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_child_response (GimpToolWidget *child, + gint response_id, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_child_snap_offsets (GimpToolWidget *child, + gint offset_x, + gint offset_y, + gint width, + gint height, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_child_status (GimpToolWidget *child, + const gchar *status, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_child_status_coords (GimpToolWidget *child, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_child_message (GimpToolWidget *child, + const gchar *message, + GimpToolWidgetGroup *group); +static void gimp_tool_widget_group_child_focus_changed (GimpToolWidget *child, + GimpToolWidgetGroup *group); + +static GimpToolWidget * gimp_tool_widget_group_get_hover_widget (GimpToolWidgetGroup *group, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity, + GimpHit *hit); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolWidgetGroup, gimp_tool_widget_group, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_widget_group_parent_class + + +/* priv functions */ + + +static void +gimp_tool_widget_group_class_init (GimpToolWidgetGroupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->finalize = gimp_tool_widget_group_finalize; + + widget_class->focus_changed = gimp_tool_widget_group_focus_changed; + widget_class->button_press = gimp_tool_widget_group_button_press; + widget_class->button_release = gimp_tool_widget_group_button_release; + widget_class->motion = gimp_tool_widget_group_motion; + widget_class->hit = gimp_tool_widget_group_hit; + widget_class->hover = gimp_tool_widget_group_hover; + widget_class->leave_notify = gimp_tool_widget_group_leave_notify; + widget_class->key_press = gimp_tool_widget_group_key_press; + widget_class->key_release = gimp_tool_widget_group_key_release; + widget_class->motion_modifier = gimp_tool_widget_group_motion_modifier; + widget_class->hover_modifier = gimp_tool_widget_group_hover_modifier; + widget_class->get_cursor = gimp_tool_widget_group_get_cursor; +} + +static void +gimp_tool_widget_group_init (GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv; + + priv = group->priv = gimp_tool_widget_group_get_instance_private (group); + + priv->children = g_object_new (GIMP_TYPE_LIST, + "children-type", GIMP_TYPE_TOOL_WIDGET, + "append", TRUE, + NULL); + + g_signal_connect (priv->children, "add", + G_CALLBACK (gimp_tool_widget_group_children_add), + group); + g_signal_connect (priv->children, "remove", + G_CALLBACK (gimp_tool_widget_group_children_remove), + group); + g_signal_connect (priv->children, "reorder", + G_CALLBACK (gimp_tool_widget_group_children_reorder), + group); + + gimp_container_add_handler (priv->children, "changed", + G_CALLBACK (gimp_tool_widget_group_child_changed), + group); + gimp_container_add_handler (priv->children, "response", + G_CALLBACK (gimp_tool_widget_group_child_response), + group); + gimp_container_add_handler (priv->children, "snap-offsets", + G_CALLBACK (gimp_tool_widget_group_child_snap_offsets), + group); + gimp_container_add_handler (priv->children, "status", + G_CALLBACK (gimp_tool_widget_group_child_status), + group); + gimp_container_add_handler (priv->children, "status-coords", + G_CALLBACK (gimp_tool_widget_group_child_status_coords), + group); + gimp_container_add_handler (priv->children, "message", + G_CALLBACK (gimp_tool_widget_group_child_message), + group); + gimp_container_add_handler (priv->children, "focus-changed", + G_CALLBACK (gimp_tool_widget_group_child_focus_changed), + group); +} + +static void +gimp_tool_widget_group_finalize (GObject *object) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (object); + GimpToolWidgetGroupPrivate *priv = group->priv; + + g_clear_object (&priv->children); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint +gimp_tool_widget_group_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + gimp_tool_widget_group_hover (widget, coords, state, TRUE); + + if (priv->focus_widget != priv->hover_widget) + { + if (priv->hover_widget) + gimp_tool_widget_set_focus (priv->hover_widget, TRUE); + else if (priv->focus_widget) + gimp_tool_widget_set_focus (priv->focus_widget, FALSE); + } + + if (priv->hover_widget) + { + if (priv->auto_raise) + { + gimp_container_reorder (priv->children, + GIMP_OBJECT (priv->hover_widget), -1); + } + + return gimp_tool_widget_button_press (priv->hover_widget, + coords, time, state, press_type); + } + + return FALSE; +} + +static void +gimp_tool_widget_group_focus_changed (GimpToolWidget *widget) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->focus_widget) + { + GimpToolWidget *focus_widget = priv->focus_widget; + + gimp_tool_widget_set_focus (priv->focus_widget, + gimp_tool_widget_get_focus (widget)); + + priv->focus_widget = focus_widget; + } +} + +static void +gimp_tool_widget_group_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->hover_widget) + { + gimp_tool_widget_button_release (priv->hover_widget, + coords, time, state, release_type); + } +} + +static void +gimp_tool_widget_group_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->hover_widget) + gimp_tool_widget_motion (priv->hover_widget, coords, time, state); +} + +static GimpHit +gimp_tool_widget_group_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpHit hit; + + gimp_tool_widget_group_get_hover_widget (group, + coords, state, proximity, + &hit); + + return hit; +} + +static void +gimp_tool_widget_group_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *hover_widget; + + hover_widget = + gimp_tool_widget_group_get_hover_widget (group, + coords, state, proximity, + NULL); + + if (priv->hover_widget && priv->hover_widget != hover_widget) + gimp_tool_widget_leave_notify (priv->hover_widget); + + priv->hover_widget = hover_widget; + + if (priv->hover_widget) + gimp_tool_widget_hover (priv->hover_widget, coords, state, proximity); +} + +static void +gimp_tool_widget_group_leave_notify (GimpToolWidget *widget) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->hover_widget) + { + gimp_tool_widget_leave_notify (priv->hover_widget); + + priv->hover_widget = NULL; + } + + GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget); +} + +static gboolean +gimp_tool_widget_group_key_press (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->focus_widget) + return gimp_tool_widget_key_press (priv->focus_widget, kevent); + + return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent); +} + +static gboolean +gimp_tool_widget_group_key_release (GimpToolWidget *widget, + GdkEventKey *kevent) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->focus_widget) + return gimp_tool_widget_key_release (priv->focus_widget, kevent); + + return FALSE; +} + +static void +gimp_tool_widget_group_motion_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->hover_widget) + gimp_tool_widget_motion_modifier (priv->hover_widget, key, press, state); +} + +static void +gimp_tool_widget_group_hover_modifier (GimpToolWidget *widget, + GdkModifierType key, + gboolean press, + GdkModifierType state) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + GList *iter; + + for (iter = g_queue_peek_head_link (GIMP_LIST (priv->children)->queue); + iter; + iter = g_list_next (iter)) + { + gimp_tool_widget_hover_modifier (iter->data, key, press, state); + } +} + +static gboolean +gimp_tool_widget_group_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *modifier) +{ + GimpToolWidgetGroup *group = GIMP_TOOL_WIDGET_GROUP (widget); + GimpToolWidgetGroupPrivate *priv = group->priv; + + if (priv->hover_widget) + { + return gimp_tool_widget_get_cursor (priv->hover_widget, + coords, state, + cursor, tool_cursor, modifier); + } + + return FALSE; +} + +static void +gimp_tool_widget_group_children_add (GimpContainer *container, + GimpToolWidget *child, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + GimpCanvasGroup *canvas_group; + + canvas_group = GIMP_CANVAS_GROUP (gimp_tool_widget_get_item (widget)); + + gimp_canvas_group_add_item (canvas_group, + gimp_tool_widget_get_item (child)); + + if (gimp_tool_widget_get_focus (child) && priv->focus_widget) + { + gimp_tool_widget_set_focus (priv->focus_widget, FALSE); + + priv->focus_widget = NULL; + } + + if (! priv->focus_widget) + { + priv->focus_widget = child; + + gimp_tool_widget_set_focus (child, gimp_tool_widget_get_focus (widget)); + } + + gimp_tool_widget_changed (widget); +} + +static void +gimp_tool_widget_group_children_remove (GimpContainer *container, + GimpToolWidget *child, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + GimpCanvasGroup *canvas_group; + + canvas_group = GIMP_CANVAS_GROUP (gimp_tool_widget_get_item (widget)); + + if (priv->focus_widget == child) + { + gimp_tool_widget_set_focus (child, FALSE); + + priv->focus_widget = NULL; + } + + if (priv->hover_widget == child) + { + gimp_tool_widget_leave_notify (child); + + priv->hover_widget = NULL; + } + + if (! priv->focus_widget) + { + priv->focus_widget = + GIMP_TOOL_WIDGET (gimp_container_get_last_child (container)); + + if (priv->focus_widget) + gimp_tool_widget_set_focus (priv->focus_widget, TRUE); + } + + gimp_canvas_group_remove_item (canvas_group, + gimp_tool_widget_get_item (child)); + + gimp_tool_widget_changed (widget); +} + +static void +gimp_tool_widget_group_children_reorder (GimpContainer *container, + GimpToolWidget *child, + gint new_index, + GimpToolWidgetGroup *group) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + GimpCanvasGroup *canvas_group; + GList *iter; + + canvas_group = GIMP_CANVAS_GROUP (gimp_tool_widget_get_item (widget)); + + for (iter = g_queue_peek_head_link (GIMP_LIST (container)->queue); + iter; + iter = g_list_next (iter)) + { + GimpCanvasItem *item = gimp_tool_widget_get_item (iter->data); + + gimp_canvas_group_remove_item (canvas_group, item); + gimp_canvas_group_add_item (canvas_group, item); + } + + gimp_tool_widget_changed (widget); +} + +static void +gimp_tool_widget_group_child_changed (GimpToolWidget *child, + GimpToolWidgetGroup *group) +{ + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + gimp_tool_widget_changed (widget); +} + +static void +gimp_tool_widget_group_child_response (GimpToolWidget *child, + gint response_id, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + if (priv->focus_widget == child) + gimp_tool_widget_response (widget, response_id); +} + +static void +gimp_tool_widget_group_child_snap_offsets (GimpToolWidget *child, + gint offset_x, + gint offset_y, + gint width, + gint height, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + if (priv->hover_widget == child) + { + gimp_tool_widget_set_snap_offsets (widget, + offset_x, offset_y, width, height); + } +} + +static void +gimp_tool_widget_group_child_status (GimpToolWidget *child, + const gchar *status, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + if (priv->hover_widget == child) + gimp_tool_widget_set_status (widget, status); +} + +static void +gimp_tool_widget_group_child_status_coords (GimpToolWidget *child, + const gchar *title, + gdouble x, + const gchar *separator, + gdouble y, + const gchar *help, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + if (priv->hover_widget == child) + gimp_tool_widget_set_status_coords (widget, title, x, separator, y, help); +} + +static void +gimp_tool_widget_group_child_message (GimpToolWidget *child, + const gchar *message, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + if (priv->focus_widget == child) + gimp_tool_widget_message_literal (widget, message); +} + +static void +gimp_tool_widget_group_child_focus_changed (GimpToolWidget *child, + GimpToolWidgetGroup *group) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (group); + + if (gimp_tool_widget_get_focus (child)) + { + if (priv->focus_widget && priv->focus_widget != child) + gimp_tool_widget_set_focus (priv->focus_widget, FALSE); + + priv->focus_widget = child; + + gimp_tool_widget_set_focus (widget, TRUE); + } + else + { + if (priv->focus_widget == child) + priv->focus_widget = NULL; + } +} + +static GimpToolWidget * +gimp_tool_widget_group_get_hover_widget (GimpToolWidgetGroup *group, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity, + GimpHit *hit) +{ + GimpToolWidgetGroupPrivate *priv = group->priv; + GimpToolWidget *indirect_child = NULL; + gboolean indirect = FALSE; + GList *iter; + + for (iter = g_queue_peek_tail_link (GIMP_LIST (priv->children)->queue); + iter; + iter = g_list_previous (iter)) + { + GimpToolWidget *child = iter->data; + + switch (gimp_tool_widget_hit (child, coords, state, proximity)) + { + case GIMP_HIT_DIRECT: + if (hit) *hit = GIMP_HIT_DIRECT; + + return child; + + case GIMP_HIT_INDIRECT: + if (! indirect || child == priv->focus_widget) + indirect_child = child; + else if (indirect_child != priv->focus_widget) + indirect_child = NULL; + + indirect = TRUE; + + break; + + case GIMP_HIT_NONE: + break; + } + } + + if (hit) *hit = indirect_child ? GIMP_HIT_INDIRECT : GIMP_HIT_NONE; + + return indirect_child; +} + + +/* public functions */ + + +GimpToolWidget * +gimp_tool_widget_group_new (GimpDisplayShell *shell) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_WIDGET_GROUP, + "shell", shell, + NULL); +} + +GimpContainer * +gimp_tool_widget_group_get_children (GimpToolWidgetGroup *group) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group), NULL); + + return group->priv->children; +} + +GimpToolWidget * +gimp_tool_widget_group_get_focus_widget (GimpToolWidgetGroup *group) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group), NULL); + + return group->priv->focus_widget; +} + +void +gimp_tool_widget_group_set_auto_raise (GimpToolWidgetGroup *group, + gboolean auto_raise) +{ + g_return_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group)); + + group->priv->auto_raise = auto_raise; +} + +gboolean +gimp_tool_widget_group_get_auto_raise (GimpToolWidgetGroup *group) +{ + g_return_val_if_fail (GIMP_IS_TOOL_WIDGET_GROUP (group), FALSE); + + return group->priv->auto_raise; +} + diff --git a/app/display/gimptoolwidgetgroup.h b/app/display/gimptoolwidgetgroup.h new file mode 100644 index 0000000..0a8331f --- /dev/null +++ b/app/display/gimptoolwidgetgroup.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolwidgetgroup.h + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_TOOL_WIDGET_GROUP_H__ +#define __GIMP_TOOL_WIDGET_GROUP_H__ + + +#include "gimptoolwidget.h" + + +#define GIMP_TYPE_TOOL_WIDGET_GROUP (gimp_tool_widget_group_get_type ()) +#define GIMP_TOOL_WIDGET_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_WIDGET_GROUP, GimpToolWidgetGroup)) +#define GIMP_TOOL_WIDGET_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_WIDGET_GROUP, GimpToolWidgetGroupClass)) +#define GIMP_IS_TOOL_WIDGET_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_WIDGET_GROUP)) +#define GIMP_IS_TOOL_WIDGET_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_WIDGET_GROUP)) +#define GIMP_TOOL_WIDGET_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_WIDGET_GROUP, GimpToolWidgetGroupClass)) + + +typedef struct _GimpToolWidgetGroupPrivate GimpToolWidgetGroupPrivate; +typedef struct _GimpToolWidgetGroupClass GimpToolWidgetGroupClass; + +struct _GimpToolWidgetGroup +{ + GimpToolWidget parent_instance; + + GimpToolWidgetGroupPrivate *priv; +}; + +struct _GimpToolWidgetGroupClass +{ + GimpToolWidgetClass parent_class; +}; + + +GType gimp_tool_widget_group_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_widget_group_new (GimpDisplayShell *shell); + +GimpContainer * gimp_tool_widget_group_get_children (GimpToolWidgetGroup *group); + +GimpToolWidget * gimp_tool_widget_group_get_focus_widget (GimpToolWidgetGroup *group); + +void gimp_tool_widget_group_set_auto_raise (GimpToolWidgetGroup *group, + gboolean auto_raise); +gboolean gimp_tool_widget_group_get_auto_raise (GimpToolWidgetGroup *group); + + +#endif /* __GIMP_TOOL_WIDGET_GROUP_H__ */ |