diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
commit | 3c57dd931145d43f2b0aef96c4d178135956bf91 (patch) | |
tree | 3de698981e9f0cc2c4f9569b19a5f3595e741f6b /libgimpwidgets | |
parent | Initial commit. (diff) | |
download | gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip |
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libgimpwidgets')
148 files changed, 57268 insertions, 0 deletions
diff --git a/libgimpwidgets/Makefile.am b/libgimpwidgets/Makefile.am new file mode 100644 index 0000000..9b4dbf5 --- /dev/null +++ b/libgimpwidgets/Makefile.am @@ -0,0 +1,418 @@ +## Process this file with automake to produce Makefile.in + +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la + +if PLATFORM_WIN32 +no_undefined = -no-undefined +libgdi32 = -lgdi32 +libmscms = -lmscms +else +libm = -lm +endif + +if PLATFORM_OSX +xobjective_c = "-xobjective-c" +xobjective_cxx = "-xobjective-c++" +xnone = "-xnone" +framework_cocoa = -framework Cocoa +endif + +if OS_WIN32 +gimpwidgets_def = gimpwidgets.def +libgimpwidgets_export_symbols = -export-symbols $(srcdir)/gimpwidgets.def + +install-libtool-import-lib: + $(INSTALL) .libs/libgimpwidgets-$(GIMP_API_VERSION).dll.a $(DESTDIR)$(libdir) + $(INSTALL) $(srcdir)/gimpwidgets.def $(DESTDIR)$(libdir) + +uninstall-libtool-import-lib: + -rm $(DESTDIR)$(libdir)/libgimpwidgets-$(GIMP_API_VERSION).dll.a + -rm $(DESTDIR)$(libdir)/gimpwidgets.def +else +install-libtool-import-lib: +uninstall-libtool-import-lib: +endif + +if MS_LIB_AVAILABLE +noinst_DATA = gimpwidgets-$(GIMP_API_VERSION).lib + +install-ms-lib: + $(INSTALL) gimpwidgets-$(GIMP_API_VERSION).lib $(DESTDIR)$(libdir) + +uninstall-ms-lib: + -rm $(DESTDIR)$(libdir)/gimpwidgets-$(GIMP_API_VERSION).lib + +gimpwidgets-@GIMP_API_VERSION@.lib: gimpwidgets.def + lib -name:libgimpwidgets-$(GIMP_API_VERSION)-@LT_CURRENT_MINUS_AGE@.dll -def:gimpwidgets.def -out:$@ + +else +install-ms-lib: +uninstall-ms-lib: +endif + +libgimpwidgetsincludedir = $(includedir)/gimp-$(GIMP_API_VERSION)/libgimpwidgets + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"LibGimpWidgets\" \ + -DGIMP_WIDGETS_COMPILATION \ + -I$(top_srcdir) \ + $(GEGL_CFLAGS) \ + $(GTK_CFLAGS) \ + $(LCMS_CFLAGS) \ + -I$(includedir) + +AM_CFLAGS = \ + $(xobjective_c) + +AM_CXXFLAGS = \ + $(xobjective_cxx) + +AM_LDFLAGS = \ + $(xnone) + +lib_LTLIBRARIES = libgimpwidgets-@GIMP_API_VERSION@.la + +libgimpwidgets_sources = \ + gimpbrowser.c \ + gimpbrowser.h \ + gimpbusybox.c \ + gimpbusybox.h \ + gimpbutton.c \ + gimpbutton.h \ + gimpcairo-utils.c \ + gimpcairo-utils.h \ + gimpcellrenderercolor.c \ + gimpcellrenderercolor.h \ + gimpcellrenderertoggle.c \ + gimpcellrenderertoggle.h \ + gimpchainbutton.c \ + gimpchainbutton.h \ + gimpcolorarea.c \ + gimpcolorarea.h \ + gimpcolorbutton.c \ + gimpcolorbutton.h \ + gimpcolordisplay.c \ + gimpcolordisplay.h \ + gimpcolordisplaystack.c \ + gimpcolordisplaystack.h \ + gimpcolorhexentry.c \ + gimpcolorhexentry.h \ + gimpcolornotebook.c \ + gimpcolornotebook.h \ + gimpcolorprofilechooserdialog.c \ + gimpcolorprofilechooserdialog.h \ + gimpcolorprofilecombobox.c \ + gimpcolorprofilecombobox.h \ + gimpcolorprofilestore-private.h \ + gimpcolorprofilestore.c \ + gimpcolorprofilestore.h \ + gimpcolorprofileview.c \ + gimpcolorprofileview.h \ + gimpcolorscale.c \ + gimpcolorscale.h \ + gimpcolorscales.c \ + gimpcolorscales.h \ + gimpcolorselect.c \ + gimpcolorselect.h \ + gimpcolorselection.c \ + gimpcolorselection.h \ + gimpcolorselector.c \ + gimpcolorselector.h \ + gimpcontroller.c \ + gimpcontroller.h \ + gimpdialog.c \ + gimpdialog.h \ + gimpeevl.c \ + gimpeevl.h \ + gimpenumcombobox.c \ + gimpenumcombobox.h \ + gimpenumlabel.c \ + gimpenumlabel.h \ + gimpenumstore.c \ + gimpenumstore.h \ + gimpenumwidgets.c \ + gimpenumwidgets.h \ + gimpfileentry.c \ + gimpfileentry.h \ + gimpframe.c \ + gimpframe.h \ + gimphelpui.c \ + gimphelpui.h \ + gimphintbox.c \ + gimphintbox.h \ + gimpicons.c \ + gimpicons.h \ + gimpintcombobox.c \ + gimpintcombobox.h \ + gimpintstore.c \ + gimpintstore.h \ + gimpmemsizeentry.c \ + gimpmemsizeentry.h \ + gimpnumberpairentry.c \ + gimpnumberpairentry.h \ + gimpoffsetarea.c \ + gimpoffsetarea.h \ + gimpoldwidgets.c \ + gimpoldwidgets.h \ + gimppageselector.c \ + gimppageselector.h \ + gimppatheditor.c \ + gimppatheditor.h \ + gimppickbutton.c \ + gimppickbutton.h \ + gimppixmap.c \ + gimppixmap.h \ + gimppreview.c \ + gimppreview.h \ + gimppreviewarea.c \ + gimppreviewarea.h \ + gimppropwidgets.c \ + gimppropwidgets.h \ + gimpquerybox.c \ + gimpquerybox.h \ + gimpruler.c \ + gimpruler.h \ + gimpscaleentry.c \ + gimpscaleentry.h \ + gimpscrolledpreview.c \ + gimpscrolledpreview.h \ + gimpsizeentry.c \ + gimpsizeentry.h \ + gimpspinbutton.c \ + gimpspinbutton.h \ + gimpstringcombobox.c \ + gimpstringcombobox.h \ + gimpunitcombobox.c \ + gimpunitcombobox.h \ + gimpunitmenu.c \ + gimpunitmenu.h \ + gimpunitstore.c \ + gimpunitstore.h \ + gimpwidgets-error.c \ + gimpwidgets-error.h \ + gimpwidgets-private.c \ + gimpwidgets-private.h \ + gimpwidgets.c \ + gimpwidgets.h \ + gimpwidgetsenums.h \ + gimpwidgetstypes.h \ + gimpwidgetsutils.c \ + gimpwidgetsutils.h \ + gimpzoommodel.c \ + gimpzoommodel.h \ + gimp3migration.c \ + gimp3migration.h + +libgimpwidgets_built_sources = \ + gimpwidgetsenums.c \ + gimpwidgetsmarshal.c \ + gimpwidgetsmarshal.h + +libgimpwidgets_extra_sources = gimpwidgetsmarshal.list + +libgimpwidgets_@GIMP_API_VERSION@_la_SOURCES = \ + $(libgimpwidgets_built_sources) \ + $(libgimpwidgets_sources) + +libgimpwidgetsinclude_HEADERS = \ + gimpbrowser.h \ + gimpbusybox.h \ + gimpbutton.h \ + gimpcairo-utils.h \ + gimpcellrenderercolor.h \ + gimpcellrenderertoggle.h \ + gimpchainbutton.h \ + gimpcolorarea.h \ + gimpcolorbutton.h \ + gimpcolordisplay.h \ + gimpcolordisplaystack.h \ + gimpcolorhexentry.h \ + gimpcolornotebook.h \ + gimpcolorprofilechooserdialog.h \ + gimpcolorprofilecombobox.h \ + gimpcolorprofilestore.h \ + gimpcolorprofileview.h \ + gimpcolorscale.h \ + gimpcolorscales.h \ + gimpcolorselect.h \ + gimpcolorselection.h \ + gimpcolorselector.h \ + gimpcontroller.h \ + gimpdialog.h \ + gimpenumcombobox.h \ + gimpenumlabel.h \ + gimpenumstore.h \ + gimpenumwidgets.h \ + gimpfileentry.h \ + gimpframe.h \ + gimphelpui.h \ + gimphintbox.h \ + gimpicons.h \ + gimpintcombobox.h \ + gimpintstore.h \ + gimpmemsizeentry.h \ + gimpnumberpairentry.h \ + gimpoffsetarea.h \ + gimpoldwidgets.h \ + gimppageselector.h \ + gimppatheditor.h \ + gimppickbutton.h \ + gimppixmap.h \ + gimppreview.h \ + gimppreviewarea.h \ + gimppropwidgets.h \ + gimpquerybox.h \ + gimpruler.h \ + gimpscaleentry.h \ + gimpscrolledpreview.h \ + gimpsizeentry.h \ + gimpspinbutton.h \ + gimpstringcombobox.h \ + gimpunitcombobox.h \ + gimpunitmenu.h \ + gimpunitstore.h \ + gimpwidgets-error.h \ + gimpwidgets.h \ + gimpwidgetsenums.h \ + gimpwidgetstypes.h \ + gimpwidgetsutils.h \ + gimpzoommodel.h \ + gimp3migration.h + +libgimpwidgets_@GIMP_API_VERSION@_la_LDFLAGS = \ + -version-info $(LT_VERSION_INFO) \ + $(no_undefined) \ + $(libgimpwidgets_export_symbols) \ + $(framework_cocoa) \ + $(xnone) + +EXTRA_libgimpwidgets_@GIMP_API_VERSION@_la_DEPENDENCIES = $(gimpwidgets_def) + +libgimpwidgets_@GIMP_API_VERSION@_la_LIBADD = \ + $(libgimpbase) \ + $(libgimpcolor) \ + $(libgimpconfig) \ + $(GEGL_LIBS) \ + $(GTK_LIBS) \ + $(LCMS_LIBS) \ + $(libm) \ + $(libgdi32) \ + $(libmscms) + +BUILT_SOURCES = \ + $(libgimpwidgets_built_sources) + +EXTRA_DIST = \ + gimpwidgets.def \ + $(libgimpwidgets_extra_sources) + +# +# platform-dependent source files +# + + +if PLATFORM_OSX_QUARTZ +libgimpwidgets_sources += gimppickbutton-quartz.c gimppickbutton-quartz.h +AM_CPPFLAGS += "-xobjective-c" +else + +if PLATFORM_WIN32 +libgimpwidgets_sources += gimppickbutton-win32.c gimppickbutton-win32.h +else +libgimpwidgets_sources += \ + gimppickbutton-default.c \ + gimppickbutton-default.h \ + gimppickbutton-kwin.c \ + gimppickbutton-kwin.h \ + gimppickbutton-xdg.c \ + gimppickbutton-xdg.h +endif + +endif + + +# +# rules to generate built sources +# +# setup autogeneration dependencies +gen_sources = xgen-wec xgen-wmh xgen-wmc +CLEANFILES = $(gen_sources) + +xgen-wec: $(srcdir)/gimpwidgetsenums.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 \"gimpwidgetsenums.h\"\n#include \"libgimp/libgimp-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_domain (type, GETTEXT_PACKAGE \"-libgimp\");\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)/gimpwidgetsenums.c: xgen-wec + $(AM_V_GEN) if ! cmp -s $< $@; then \ + cp $< $@; \ + else \ + touch $@ 2> /dev/null \ + || true; \ + fi + +gimpwidgetsmarshal.h: $(srcdir)/gimpwidgetsmarshal.list + $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --header >> xgen-wmh \ + && (cmp -s xgen-wmh $(@F) || cp xgen-wmh $(@F)) \ + && rm -f xgen-wmh xgen-wmh~ + +gimpwidgetsmarshal.c: gimpwidgetsmarshal.h + $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --header --body >> xgen-wmc \ + && cp xgen-wmc $(@F) \ + && rm -f xgen-wmc xgen-wmc~ + + +# +# test programs, not installed +# + +EXTRA_PROGRAMS = \ + test-preview-area \ + test-eevl + + +test_preview_area_SOURCES = test-preview-area.c + +test_preview_area_LDADD = \ + $(GTK_LIBS) \ + $(libgimpbase) \ + $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la + + +test_eevl_SOURCES = \ + test-eevl.c + +test_eevl_LDADD = \ + $(GLIB_LIBS) \ + $(libgimpcolor) \ + $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la + + +# +# test programs, not to be built by default and never installed +# + +TESTS = test-eevl$(EXEEXT) + + + + +CLEANFILES += $(EXTRA_PROGRAMS) + + +install-data-local: install-ms-lib install-libtool-import-lib + +uninstall-local: uninstall-ms-lib uninstall-libtool-import-lib diff --git a/libgimpwidgets/Makefile.in b/libgimpwidgets/Makefile.in new file mode 100644 index 0000000..8e1e0d3 --- /dev/null +++ b/libgimpwidgets/Makefile.in @@ -0,0 +1,1988 @@ +# Makefile.in generated by automake 1.16.3 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@ + +# +# platform-dependent source files +# +@PLATFORM_OSX_QUARTZ_TRUE@am__append_1 = gimppickbutton-quartz.c gimppickbutton-quartz.h +@PLATFORM_OSX_QUARTZ_TRUE@am__append_2 = "-xobjective-c" +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_TRUE@am__append_3 = gimppickbutton-win32.c gimppickbutton-win32.h +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@am__append_4 = \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-default.c \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-default.h \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-kwin.c \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-kwin.h \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-xdg.c \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-xdg.h + +EXTRA_PROGRAMS = test-preview-area$(EXEEXT) test-eevl$(EXEEXT) +subdir = libgimpwidgets +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(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 $(libgimpwidgetsinclude_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" \ + "$(DESTDIR)$(libgimpwidgetsincludedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libgimpwidgets_@GIMP_API_VERSION@_la_DEPENDENCIES = $(libgimpbase) \ + $(libgimpcolor) $(libgimpconfig) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am__libgimpwidgets_@GIMP_API_VERSION@_la_SOURCES_DIST = \ + gimpwidgetsenums.c gimpwidgetsmarshal.c gimpwidgetsmarshal.h \ + gimpbrowser.c gimpbrowser.h gimpbusybox.c gimpbusybox.h \ + gimpbutton.c gimpbutton.h gimpcairo-utils.c gimpcairo-utils.h \ + gimpcellrenderercolor.c gimpcellrenderercolor.h \ + gimpcellrenderertoggle.c gimpcellrenderertoggle.h \ + gimpchainbutton.c gimpchainbutton.h gimpcolorarea.c \ + gimpcolorarea.h gimpcolorbutton.c gimpcolorbutton.h \ + gimpcolordisplay.c gimpcolordisplay.h gimpcolordisplaystack.c \ + gimpcolordisplaystack.h gimpcolorhexentry.c \ + gimpcolorhexentry.h gimpcolornotebook.c gimpcolornotebook.h \ + gimpcolorprofilechooserdialog.c \ + gimpcolorprofilechooserdialog.h gimpcolorprofilecombobox.c \ + gimpcolorprofilecombobox.h gimpcolorprofilestore-private.h \ + gimpcolorprofilestore.c gimpcolorprofilestore.h \ + gimpcolorprofileview.c gimpcolorprofileview.h gimpcolorscale.c \ + gimpcolorscale.h gimpcolorscales.c gimpcolorscales.h \ + gimpcolorselect.c gimpcolorselect.h gimpcolorselection.c \ + gimpcolorselection.h gimpcolorselector.c gimpcolorselector.h \ + gimpcontroller.c gimpcontroller.h gimpdialog.c gimpdialog.h \ + gimpeevl.c gimpeevl.h gimpenumcombobox.c gimpenumcombobox.h \ + gimpenumlabel.c gimpenumlabel.h gimpenumstore.c \ + gimpenumstore.h gimpenumwidgets.c gimpenumwidgets.h \ + gimpfileentry.c gimpfileentry.h gimpframe.c gimpframe.h \ + gimphelpui.c gimphelpui.h gimphintbox.c gimphintbox.h \ + gimpicons.c gimpicons.h gimpintcombobox.c gimpintcombobox.h \ + gimpintstore.c gimpintstore.h gimpmemsizeentry.c \ + gimpmemsizeentry.h gimpnumberpairentry.c gimpnumberpairentry.h \ + gimpoffsetarea.c gimpoffsetarea.h gimpoldwidgets.c \ + gimpoldwidgets.h gimppageselector.c gimppageselector.h \ + gimppatheditor.c gimppatheditor.h gimppickbutton.c \ + gimppickbutton.h gimppixmap.c gimppixmap.h gimppreview.c \ + gimppreview.h gimppreviewarea.c gimppreviewarea.h \ + gimppropwidgets.c gimppropwidgets.h gimpquerybox.c \ + gimpquerybox.h gimpruler.c gimpruler.h gimpscaleentry.c \ + gimpscaleentry.h gimpscrolledpreview.c gimpscrolledpreview.h \ + gimpsizeentry.c gimpsizeentry.h gimpspinbutton.c \ + gimpspinbutton.h gimpstringcombobox.c gimpstringcombobox.h \ + gimpunitcombobox.c gimpunitcombobox.h gimpunitmenu.c \ + gimpunitmenu.h gimpunitstore.c gimpunitstore.h \ + gimpwidgets-error.c gimpwidgets-error.h gimpwidgets-private.c \ + gimpwidgets-private.h gimpwidgets.c gimpwidgets.h \ + gimpwidgetsenums.h gimpwidgetstypes.h gimpwidgetsutils.c \ + gimpwidgetsutils.h gimpzoommodel.c gimpzoommodel.h \ + gimp3migration.c gimp3migration.h gimppickbutton-quartz.c \ + gimppickbutton-quartz.h gimppickbutton-win32.c \ + gimppickbutton-win32.h gimppickbutton-default.c \ + gimppickbutton-default.h gimppickbutton-kwin.c \ + gimppickbutton-kwin.h gimppickbutton-xdg.c \ + gimppickbutton-xdg.h +am__objects_1 = gimpwidgetsenums.lo gimpwidgetsmarshal.lo +@PLATFORM_OSX_QUARTZ_TRUE@am__objects_2 = gimppickbutton-quartz.lo +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_TRUE@am__objects_3 = gimppickbutton-win32.lo +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@am__objects_4 = gimppickbutton-default.lo \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-kwin.lo \ +@PLATFORM_OSX_QUARTZ_FALSE@@PLATFORM_WIN32_FALSE@ gimppickbutton-xdg.lo +am__objects_5 = gimpbrowser.lo gimpbusybox.lo gimpbutton.lo \ + gimpcairo-utils.lo gimpcellrenderercolor.lo \ + gimpcellrenderertoggle.lo gimpchainbutton.lo gimpcolorarea.lo \ + gimpcolorbutton.lo gimpcolordisplay.lo \ + gimpcolordisplaystack.lo gimpcolorhexentry.lo \ + gimpcolornotebook.lo gimpcolorprofilechooserdialog.lo \ + gimpcolorprofilecombobox.lo gimpcolorprofilestore.lo \ + gimpcolorprofileview.lo gimpcolorscale.lo gimpcolorscales.lo \ + gimpcolorselect.lo gimpcolorselection.lo gimpcolorselector.lo \ + gimpcontroller.lo gimpdialog.lo gimpeevl.lo \ + gimpenumcombobox.lo gimpenumlabel.lo gimpenumstore.lo \ + gimpenumwidgets.lo gimpfileentry.lo gimpframe.lo gimphelpui.lo \ + gimphintbox.lo gimpicons.lo gimpintcombobox.lo gimpintstore.lo \ + gimpmemsizeentry.lo gimpnumberpairentry.lo gimpoffsetarea.lo \ + gimpoldwidgets.lo gimppageselector.lo gimppatheditor.lo \ + gimppickbutton.lo gimppixmap.lo gimppreview.lo \ + gimppreviewarea.lo gimppropwidgets.lo gimpquerybox.lo \ + gimpruler.lo gimpscaleentry.lo gimpscrolledpreview.lo \ + gimpsizeentry.lo gimpspinbutton.lo gimpstringcombobox.lo \ + gimpunitcombobox.lo gimpunitmenu.lo gimpunitstore.lo \ + gimpwidgets-error.lo gimpwidgets-private.lo gimpwidgets.lo \ + gimpwidgetsutils.lo gimpzoommodel.lo gimp3migration.lo \ + $(am__objects_2) $(am__objects_3) $(am__objects_4) +am_libgimpwidgets_@GIMP_API_VERSION@_la_OBJECTS = $(am__objects_1) \ + $(am__objects_5) +libgimpwidgets_@GIMP_API_VERSION@_la_OBJECTS = \ + $(am_libgimpwidgets_@GIMP_API_VERSION@_la_OBJECTS) +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 = +libgimpwidgets_@GIMP_API_VERSION@_la_LINK = $(LIBTOOL) $(AM_V_lt) \ + --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libgimpwidgets_@GIMP_API_VERSION@_la_LDFLAGS) $(LDFLAGS) -o \ + $@ +am_test_eevl_OBJECTS = test-eevl.$(OBJEXT) +test_eevl_OBJECTS = $(am_test_eevl_OBJECTS) +test_eevl_DEPENDENCIES = $(am__DEPENDENCIES_1) $(libgimpcolor) \ + $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +am_test_preview_area_OBJECTS = test-preview-area.$(OBJEXT) +test_preview_area_OBJECTS = $(am_test_preview_area_OBJECTS) +test_preview_area_DEPENDENCIES = $(am__DEPENDENCIES_1) $(libgimpbase) \ + $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +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)/gimp3migration.Plo \ + ./$(DEPDIR)/gimpbrowser.Plo ./$(DEPDIR)/gimpbusybox.Plo \ + ./$(DEPDIR)/gimpbutton.Plo ./$(DEPDIR)/gimpcairo-utils.Plo \ + ./$(DEPDIR)/gimpcellrenderercolor.Plo \ + ./$(DEPDIR)/gimpcellrenderertoggle.Plo \ + ./$(DEPDIR)/gimpchainbutton.Plo ./$(DEPDIR)/gimpcolorarea.Plo \ + ./$(DEPDIR)/gimpcolorbutton.Plo \ + ./$(DEPDIR)/gimpcolordisplay.Plo \ + ./$(DEPDIR)/gimpcolordisplaystack.Plo \ + ./$(DEPDIR)/gimpcolorhexentry.Plo \ + ./$(DEPDIR)/gimpcolornotebook.Plo \ + ./$(DEPDIR)/gimpcolorprofilechooserdialog.Plo \ + ./$(DEPDIR)/gimpcolorprofilecombobox.Plo \ + ./$(DEPDIR)/gimpcolorprofilestore.Plo \ + ./$(DEPDIR)/gimpcolorprofileview.Plo \ + ./$(DEPDIR)/gimpcolorscale.Plo ./$(DEPDIR)/gimpcolorscales.Plo \ + ./$(DEPDIR)/gimpcolorselect.Plo \ + ./$(DEPDIR)/gimpcolorselection.Plo \ + ./$(DEPDIR)/gimpcolorselector.Plo \ + ./$(DEPDIR)/gimpcontroller.Plo ./$(DEPDIR)/gimpdialog.Plo \ + ./$(DEPDIR)/gimpeevl.Plo ./$(DEPDIR)/gimpenumcombobox.Plo \ + ./$(DEPDIR)/gimpenumlabel.Plo ./$(DEPDIR)/gimpenumstore.Plo \ + ./$(DEPDIR)/gimpenumwidgets.Plo ./$(DEPDIR)/gimpfileentry.Plo \ + ./$(DEPDIR)/gimpframe.Plo ./$(DEPDIR)/gimphelpui.Plo \ + ./$(DEPDIR)/gimphintbox.Plo ./$(DEPDIR)/gimpicons.Plo \ + ./$(DEPDIR)/gimpintcombobox.Plo ./$(DEPDIR)/gimpintstore.Plo \ + ./$(DEPDIR)/gimpmemsizeentry.Plo \ + ./$(DEPDIR)/gimpnumberpairentry.Plo \ + ./$(DEPDIR)/gimpoffsetarea.Plo ./$(DEPDIR)/gimpoldwidgets.Plo \ + ./$(DEPDIR)/gimppageselector.Plo \ + ./$(DEPDIR)/gimppatheditor.Plo \ + ./$(DEPDIR)/gimppickbutton-default.Plo \ + ./$(DEPDIR)/gimppickbutton-kwin.Plo \ + ./$(DEPDIR)/gimppickbutton-quartz.Plo \ + ./$(DEPDIR)/gimppickbutton-win32.Plo \ + ./$(DEPDIR)/gimppickbutton-xdg.Plo \ + ./$(DEPDIR)/gimppickbutton.Plo ./$(DEPDIR)/gimppixmap.Plo \ + ./$(DEPDIR)/gimppreview.Plo ./$(DEPDIR)/gimppreviewarea.Plo \ + ./$(DEPDIR)/gimppropwidgets.Plo ./$(DEPDIR)/gimpquerybox.Plo \ + ./$(DEPDIR)/gimpruler.Plo ./$(DEPDIR)/gimpscaleentry.Plo \ + ./$(DEPDIR)/gimpscrolledpreview.Plo \ + ./$(DEPDIR)/gimpsizeentry.Plo ./$(DEPDIR)/gimpspinbutton.Plo \ + ./$(DEPDIR)/gimpstringcombobox.Plo \ + ./$(DEPDIR)/gimpunitcombobox.Plo ./$(DEPDIR)/gimpunitmenu.Plo \ + ./$(DEPDIR)/gimpunitstore.Plo \ + ./$(DEPDIR)/gimpwidgets-error.Plo \ + ./$(DEPDIR)/gimpwidgets-private.Plo \ + ./$(DEPDIR)/gimpwidgets.Plo ./$(DEPDIR)/gimpwidgetsenums.Plo \ + ./$(DEPDIR)/gimpwidgetsmarshal.Plo \ + ./$(DEPDIR)/gimpwidgetsutils.Plo ./$(DEPDIR)/gimpzoommodel.Plo \ + ./$(DEPDIR)/test-eevl.Po ./$(DEPDIR)/test-preview-area.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +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 = $(libgimpwidgets_@GIMP_API_VERSION@_la_SOURCES) \ + $(test_eevl_SOURCES) $(test_preview_area_SOURCES) +DIST_SOURCES = \ + $(am__libgimpwidgets_@GIMP_API_VERSION@_la_SOURCES_DIST) \ + $(test_eevl_SOURCES) $(test_preview_area_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +DATA = $(noinst_DATA) +HEADERS = $(libgimpwidgetsinclude_HEADERS) +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__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +am__recheck_rx = ^[ ]*:recheck:[ ]* +am__global_test_result_rx = ^[ ]*:global-test-result:[ ]* +am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]* +# A command that, given a newline-separated list of test names on the +# standard input, print the name of the tests that are to be re-run +# upon "make recheck". +am__list_recheck_tests = $(AWK) '{ \ + recheck = 1; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + { \ + if ((getline line2 < ($$0 ".log")) < 0) \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \ + { \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \ + { \ + break; \ + } \ + }; \ + if (recheck) \ + print $$0; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# A command that, given a newline-separated list of test names on the +# standard input, create the global log from their .trs and .log files. +am__create_global_log = $(AWK) ' \ +function fatal(msg) \ +{ \ + print "fatal: making $@: " msg | "cat >&2"; \ + exit 1; \ +} \ +function rst_section(header) \ +{ \ + print header; \ + len = length(header); \ + for (i = 1; i <= len; i = i + 1) \ + printf "="; \ + printf "\n\n"; \ +} \ +{ \ + copy_in_global_log = 1; \ + global_test_result = "RUN"; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".trs"); \ + if (line ~ /$(am__global_test_result_rx)/) \ + { \ + sub("$(am__global_test_result_rx)", "", line); \ + sub("[ ]*$$", "", line); \ + global_test_result = line; \ + } \ + else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \ + copy_in_global_log = 0; \ + }; \ + if (copy_in_global_log) \ + { \ + rst_section(global_test_result ": " $$0); \ + while ((rc = (getline line < ($$0 ".log"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".log"); \ + print line; \ + }; \ + printf "\n"; \ + }; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# Restructured Text title. +am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; } +# Solaris 10 'make', and several other traditional 'make' implementations, +# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it +# by disabling -e (using the XSI extension "set +e") if it's set. +am__sh_e_setup = case $$- in *e*) set +e;; esac +# Default flags passed to test drivers. +am__common_driver_flags = \ + --color-tests "$$am__color_tests" \ + --enable-hard-errors "$$am__enable_hard_errors" \ + --expect-failure "$$am__expect_failure" +# To be inserted before the command running the test. Creates the +# directory for the log if needed. Stores in $dir the directory +# containing $f, in $tst the test, in $log the log. Executes the +# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and +# passes TESTS_ENVIRONMENT. Set up options for the wrapper that +# will run the test scripts (or their associated LOG_COMPILER, if +# thy have one). +am__check_pre = \ +$(am__sh_e_setup); \ +$(am__vpath_adj_setup) $(am__vpath_adj) \ +$(am__tty_colors); \ +srcdir=$(srcdir); export srcdir; \ +case "$@" in \ + */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \ + *) am__odir=.;; \ +esac; \ +test "x$$am__odir" = x"." || test -d "$$am__odir" \ + || $(MKDIR_P) "$$am__odir" || exit $$?; \ +if test -f "./$$f"; then dir=./; \ +elif test -f "$$f"; then dir=; \ +else dir="$(srcdir)/"; fi; \ +tst=$$dir$$f; log='$@'; \ +if test -n '$(DISABLE_HARD_ERRORS)'; then \ + am__enable_hard_errors=no; \ +else \ + am__enable_hard_errors=yes; \ +fi; \ +case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \ + am__expect_failure=yes;; \ + *) \ + am__expect_failure=no;; \ +esac; \ +$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT) +# A shell command to get the names of the tests scripts with any registered +# extension removed (i.e., equivalently, the names of the test logs, with +# the '.log' extension removed). The result is saved in the shell variable +# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly, +# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)", +# since that might cause problem with VPATH rewrites for suffix-less tests. +# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'. +am__set_TESTS_bases = \ + bases='$(TEST_LOGS)'; \ + bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ + bases=`echo $$bases` +AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)' +RECHECK_LOGS = $(TEST_LOGS) +AM_RECURSIVE_TARGETS = check recheck +TEST_SUITE_LOG = test-suite.log +TEST_EXTENSIONS = @EXEEXT@ .test +LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver +LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS) +am__set_b = \ + case '$@' in \ + */*) \ + case '$*' in \ + */*) b='$*';; \ + *) b=`echo '$@' | sed 's/\.log$$//'`; \ + esac;; \ + *) \ + b='$*';; \ + esac +am__test_logs1 = $(TESTS:=.log) +am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log) +TEST_LOGS = $(am__test_logs2:.test.log=.log) +TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver +TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \ + $(TEST_LOG_FLAGS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \ + $(top_srcdir)/test-driver +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_JPEGXL = @FILE_JPEGXL@ +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_RELEASE = @GIMP_RELEASE@ +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@ +JXL_CFLAGS = @JXL_CFLAGS@ +JXL_LIBS = @JXL_LIBS@ +JXL_THREADS_CFLAGS = @JXL_THREADS_CFLAGS@ +JXL_THREADS_LIBS = @JXL_THREADS_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@ +LIBJXL_REQUIRED_VERSION = @LIBJXL_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@ +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +@PLATFORM_WIN32_TRUE@no_undefined = -no-undefined +@PLATFORM_WIN32_TRUE@libgdi32 = -lgdi32 +@PLATFORM_WIN32_TRUE@libmscms = -lmscms +@PLATFORM_WIN32_FALSE@libm = -lm +@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c" +@PLATFORM_OSX_TRUE@xobjective_cxx = "-xobjective-c++" +@PLATFORM_OSX_TRUE@xnone = "-xnone" +@PLATFORM_OSX_TRUE@framework_cocoa = -framework Cocoa +@OS_WIN32_TRUE@gimpwidgets_def = gimpwidgets.def +@OS_WIN32_TRUE@libgimpwidgets_export_symbols = -export-symbols $(srcdir)/gimpwidgets.def +@MS_LIB_AVAILABLE_TRUE@noinst_DATA = gimpwidgets-$(GIMP_API_VERSION).lib +libgimpwidgetsincludedir = $(includedir)/gimp-$(GIMP_API_VERSION)/libgimpwidgets +AM_CPPFLAGS = -DG_LOG_DOMAIN=\"LibGimpWidgets\" \ + -DGIMP_WIDGETS_COMPILATION -I$(top_srcdir) $(GEGL_CFLAGS) \ + $(GTK_CFLAGS) $(LCMS_CFLAGS) -I$(includedir) $(am__append_2) +AM_CFLAGS = \ + $(xobjective_c) + +AM_CXXFLAGS = \ + $(xobjective_cxx) + +AM_LDFLAGS = \ + $(xnone) + +lib_LTLIBRARIES = libgimpwidgets-@GIMP_API_VERSION@.la +libgimpwidgets_sources = gimpbrowser.c gimpbrowser.h gimpbusybox.c \ + gimpbusybox.h gimpbutton.c gimpbutton.h gimpcairo-utils.c \ + gimpcairo-utils.h gimpcellrenderercolor.c \ + gimpcellrenderercolor.h gimpcellrenderertoggle.c \ + gimpcellrenderertoggle.h gimpchainbutton.c gimpchainbutton.h \ + gimpcolorarea.c gimpcolorarea.h gimpcolorbutton.c \ + gimpcolorbutton.h gimpcolordisplay.c gimpcolordisplay.h \ + gimpcolordisplaystack.c gimpcolordisplaystack.h \ + gimpcolorhexentry.c gimpcolorhexentry.h gimpcolornotebook.c \ + gimpcolornotebook.h gimpcolorprofilechooserdialog.c \ + gimpcolorprofilechooserdialog.h gimpcolorprofilecombobox.c \ + gimpcolorprofilecombobox.h gimpcolorprofilestore-private.h \ + gimpcolorprofilestore.c gimpcolorprofilestore.h \ + gimpcolorprofileview.c gimpcolorprofileview.h gimpcolorscale.c \ + gimpcolorscale.h gimpcolorscales.c gimpcolorscales.h \ + gimpcolorselect.c gimpcolorselect.h gimpcolorselection.c \ + gimpcolorselection.h gimpcolorselector.c gimpcolorselector.h \ + gimpcontroller.c gimpcontroller.h gimpdialog.c gimpdialog.h \ + gimpeevl.c gimpeevl.h gimpenumcombobox.c gimpenumcombobox.h \ + gimpenumlabel.c gimpenumlabel.h gimpenumstore.c \ + gimpenumstore.h gimpenumwidgets.c gimpenumwidgets.h \ + gimpfileentry.c gimpfileentry.h gimpframe.c gimpframe.h \ + gimphelpui.c gimphelpui.h gimphintbox.c gimphintbox.h \ + gimpicons.c gimpicons.h gimpintcombobox.c gimpintcombobox.h \ + gimpintstore.c gimpintstore.h gimpmemsizeentry.c \ + gimpmemsizeentry.h gimpnumberpairentry.c gimpnumberpairentry.h \ + gimpoffsetarea.c gimpoffsetarea.h gimpoldwidgets.c \ + gimpoldwidgets.h gimppageselector.c gimppageselector.h \ + gimppatheditor.c gimppatheditor.h gimppickbutton.c \ + gimppickbutton.h gimppixmap.c gimppixmap.h gimppreview.c \ + gimppreview.h gimppreviewarea.c gimppreviewarea.h \ + gimppropwidgets.c gimppropwidgets.h gimpquerybox.c \ + gimpquerybox.h gimpruler.c gimpruler.h gimpscaleentry.c \ + gimpscaleentry.h gimpscrolledpreview.c gimpscrolledpreview.h \ + gimpsizeentry.c gimpsizeentry.h gimpspinbutton.c \ + gimpspinbutton.h gimpstringcombobox.c gimpstringcombobox.h \ + gimpunitcombobox.c gimpunitcombobox.h gimpunitmenu.c \ + gimpunitmenu.h gimpunitstore.c gimpunitstore.h \ + gimpwidgets-error.c gimpwidgets-error.h gimpwidgets-private.c \ + gimpwidgets-private.h gimpwidgets.c gimpwidgets.h \ + gimpwidgetsenums.h gimpwidgetstypes.h gimpwidgetsutils.c \ + gimpwidgetsutils.h gimpzoommodel.c gimpzoommodel.h \ + gimp3migration.c gimp3migration.h $(am__append_1) \ + $(am__append_3) $(am__append_4) +libgimpwidgets_built_sources = \ + gimpwidgetsenums.c \ + gimpwidgetsmarshal.c \ + gimpwidgetsmarshal.h + +libgimpwidgets_extra_sources = gimpwidgetsmarshal.list +libgimpwidgets_@GIMP_API_VERSION@_la_SOURCES = \ + $(libgimpwidgets_built_sources) \ + $(libgimpwidgets_sources) + +libgimpwidgetsinclude_HEADERS = \ + gimpbrowser.h \ + gimpbusybox.h \ + gimpbutton.h \ + gimpcairo-utils.h \ + gimpcellrenderercolor.h \ + gimpcellrenderertoggle.h \ + gimpchainbutton.h \ + gimpcolorarea.h \ + gimpcolorbutton.h \ + gimpcolordisplay.h \ + gimpcolordisplaystack.h \ + gimpcolorhexentry.h \ + gimpcolornotebook.h \ + gimpcolorprofilechooserdialog.h \ + gimpcolorprofilecombobox.h \ + gimpcolorprofilestore.h \ + gimpcolorprofileview.h \ + gimpcolorscale.h \ + gimpcolorscales.h \ + gimpcolorselect.h \ + gimpcolorselection.h \ + gimpcolorselector.h \ + gimpcontroller.h \ + gimpdialog.h \ + gimpenumcombobox.h \ + gimpenumlabel.h \ + gimpenumstore.h \ + gimpenumwidgets.h \ + gimpfileentry.h \ + gimpframe.h \ + gimphelpui.h \ + gimphintbox.h \ + gimpicons.h \ + gimpintcombobox.h \ + gimpintstore.h \ + gimpmemsizeentry.h \ + gimpnumberpairentry.h \ + gimpoffsetarea.h \ + gimpoldwidgets.h \ + gimppageselector.h \ + gimppatheditor.h \ + gimppickbutton.h \ + gimppixmap.h \ + gimppreview.h \ + gimppreviewarea.h \ + gimppropwidgets.h \ + gimpquerybox.h \ + gimpruler.h \ + gimpscaleentry.h \ + gimpscrolledpreview.h \ + gimpsizeentry.h \ + gimpspinbutton.h \ + gimpstringcombobox.h \ + gimpunitcombobox.h \ + gimpunitmenu.h \ + gimpunitstore.h \ + gimpwidgets-error.h \ + gimpwidgets.h \ + gimpwidgetsenums.h \ + gimpwidgetstypes.h \ + gimpwidgetsutils.h \ + gimpzoommodel.h \ + gimp3migration.h + +libgimpwidgets_@GIMP_API_VERSION@_la_LDFLAGS = \ + -version-info $(LT_VERSION_INFO) \ + $(no_undefined) \ + $(libgimpwidgets_export_symbols) \ + $(framework_cocoa) \ + $(xnone) + +EXTRA_libgimpwidgets_@GIMP_API_VERSION@_la_DEPENDENCIES = $(gimpwidgets_def) +libgimpwidgets_@GIMP_API_VERSION@_la_LIBADD = \ + $(libgimpbase) \ + $(libgimpcolor) \ + $(libgimpconfig) \ + $(GEGL_LIBS) \ + $(GTK_LIBS) \ + $(LCMS_LIBS) \ + $(libm) \ + $(libgdi32) \ + $(libmscms) + +BUILT_SOURCES = \ + $(libgimpwidgets_built_sources) + +EXTRA_DIST = \ + gimpwidgets.def \ + $(libgimpwidgets_extra_sources) + + +# +# rules to generate built sources +# +# setup autogeneration dependencies +gen_sources = xgen-wec xgen-wmh xgen-wmc +CLEANFILES = $(gen_sources) $(EXTRA_PROGRAMS) +test_preview_area_SOURCES = test-preview-area.c +test_preview_area_LDADD = \ + $(GTK_LIBS) \ + $(libgimpbase) \ + $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la + +test_eevl_SOURCES = \ + test-eevl.c + +test_eevl_LDADD = \ + $(GLIB_LIBS) \ + $(libgimpcolor) \ + $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la + + +# +# test programs, not to be built by default and never installed +# +TESTS = test-eevl$(EXEEXT) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs +$(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 libgimpwidgets/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu libgimpwidgets/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): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libgimpwidgets-@GIMP_API_VERSION@.la: $(libgimpwidgets_@GIMP_API_VERSION@_la_OBJECTS) $(libgimpwidgets_@GIMP_API_VERSION@_la_DEPENDENCIES) $(EXTRA_libgimpwidgets_@GIMP_API_VERSION@_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgimpwidgets_@GIMP_API_VERSION@_la_LINK) -rpath $(libdir) $(libgimpwidgets_@GIMP_API_VERSION@_la_OBJECTS) $(libgimpwidgets_@GIMP_API_VERSION@_la_LIBADD) $(LIBS) + +test-eevl$(EXEEXT): $(test_eevl_OBJECTS) $(test_eevl_DEPENDENCIES) $(EXTRA_test_eevl_DEPENDENCIES) + @rm -f test-eevl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_eevl_OBJECTS) $(test_eevl_LDADD) $(LIBS) + +test-preview-area$(EXEEXT): $(test_preview_area_OBJECTS) $(test_preview_area_DEPENDENCIES) $(EXTRA_test_preview_area_DEPENDENCIES) + @rm -f test-preview-area$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_preview_area_OBJECTS) $(test_preview_area_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp3migration.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrowser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbusybox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbutton.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcairo-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcellrenderercolor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcellrenderertoggle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchainbutton.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorarea.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorbutton.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolordisplay.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolordisplaystack.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorhexentry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolornotebook.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorprofilechooserdialog.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorprofilecombobox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorprofilestore.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorprofileview.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorscale.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorscales.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorselect.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorselection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorselector.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontroller.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdialog.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpeevl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpenumcombobox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpenumlabel.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpenumstore.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpenumwidgets.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfileentry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpframe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphelpui.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphintbox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpicons.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpintcombobox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpintstore.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmemsizeentry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpnumberpairentry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoffsetarea.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoldwidgets.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppageselector.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppatheditor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickbutton-default.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickbutton-kwin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickbutton-quartz.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickbutton-win32.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickbutton-xdg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickbutton.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppixmap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppreview.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppreviewarea.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppropwidgets.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpquerybox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpruler.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscaleentry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscrolledpreview.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsizeentry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpspinbutton.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstringcombobox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpunitcombobox.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpunitmenu.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpunitstore.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgets-error.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgets-private.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgets.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgetsenums.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgetsmarshal.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgetsutils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpzoommodel.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-eevl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-preview-area.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 +install-libgimpwidgetsincludeHEADERS: $(libgimpwidgetsinclude_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libgimpwidgetsinclude_HEADERS)'; test -n "$(libgimpwidgetsincludedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libgimpwidgetsincludedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libgimpwidgetsincludedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libgimpwidgetsincludedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libgimpwidgetsincludedir)" || exit $$?; \ + done + +uninstall-libgimpwidgetsincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libgimpwidgetsinclude_HEADERS)'; test -n "$(libgimpwidgetsincludedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libgimpwidgetsincludedir)'; $(am__uninstall_files_from_dir) + +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 + +# Recover from deleted '.trs' file; this should ensure that +# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create +# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells +# to avoid problems with "make -n". +.log.trs: + rm -f $< $@ + $(MAKE) $(AM_MAKEFLAGS) $< + +# Leading 'am--fnord' is there to ensure the list of targets does not +# expand to empty, as could happen e.g. with make check TESTS=''. +am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck) +am--force-recheck: + @: + +$(TEST_SUITE_LOG): $(TEST_LOGS) + @$(am__set_TESTS_bases); \ + am__f_ok () { test -f "$$1" && test -r "$$1"; }; \ + redo_bases=`for i in $$bases; do \ + am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \ + done`; \ + if test -n "$$redo_bases"; then \ + redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \ + redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \ + if $(am__make_dryrun); then :; else \ + rm -f $$redo_logs && rm -f $$redo_results || exit 1; \ + fi; \ + fi; \ + if test -n "$$am__remaking_logs"; then \ + echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \ + "recursion detected" >&2; \ + elif test -n "$$redo_logs"; then \ + am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \ + fi; \ + if $(am__make_dryrun); then :; else \ + st=0; \ + errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \ + for i in $$redo_bases; do \ + test -f $$i.trs && test -r $$i.trs \ + || { echo "$$errmsg $$i.trs" >&2; st=1; }; \ + test -f $$i.log && test -r $$i.log \ + || { echo "$$errmsg $$i.log" >&2; st=1; }; \ + done; \ + test $$st -eq 0 || exit 1; \ + fi + @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \ + ws='[ ]'; \ + results=`for b in $$bases; do echo $$b.trs; done`; \ + test -n "$$results" || results=/dev/null; \ + all=` grep "^$$ws*:test-result:" $$results | wc -l`; \ + pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \ + fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \ + skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \ + xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \ + xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \ + error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \ + if test `expr $$fail + $$xpass + $$error` -eq 0; then \ + success=true; \ + else \ + success=false; \ + fi; \ + br='==================='; br=$$br$$br$$br$$br; \ + result_count () \ + { \ + if test x"$$1" = x"--maybe-color"; then \ + maybe_colorize=yes; \ + elif test x"$$1" = x"--no-color"; then \ + maybe_colorize=no; \ + else \ + echo "$@: invalid 'result_count' usage" >&2; exit 4; \ + fi; \ + shift; \ + desc=$$1 count=$$2; \ + if test $$maybe_colorize = yes && test $$count -gt 0; then \ + color_start=$$3 color_end=$$std; \ + else \ + color_start= color_end=; \ + fi; \ + echo "$${color_start}# $$desc $$count$${color_end}"; \ + }; \ + create_testsuite_report () \ + { \ + result_count $$1 "TOTAL:" $$all "$$brg"; \ + result_count $$1 "PASS: " $$pass "$$grn"; \ + result_count $$1 "SKIP: " $$skip "$$blu"; \ + result_count $$1 "XFAIL:" $$xfail "$$lgn"; \ + result_count $$1 "FAIL: " $$fail "$$red"; \ + result_count $$1 "XPASS:" $$xpass "$$red"; \ + result_count $$1 "ERROR:" $$error "$$mgn"; \ + }; \ + { \ + echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \ + $(am__rst_title); \ + create_testsuite_report --no-color; \ + echo; \ + echo ".. contents:: :depth: 2"; \ + echo; \ + for b in $$bases; do echo $$b; done \ + | $(am__create_global_log); \ + } >$(TEST_SUITE_LOG).tmp || exit 1; \ + mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \ + if $$success; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ + fi; \ + echo "$${col}$$br$${std}"; \ + echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \ + echo "$${col}$$br$${std}"; \ + create_testsuite_report --maybe-color; \ + echo "$$col$$br$$std"; \ + if $$success; then :; else \ + echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \ + if test -n "$(PACKAGE_BUGREPORT)"; then \ + echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \ + fi; \ + echo "$$col$$br$$std"; \ + fi; \ + $$success || exit 1 + +check-TESTS: + @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list + @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + trs_list=`for i in $$bases; do echo $$i.trs; done`; \ + log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \ + exit $$?; +recheck: all + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + bases=`for i in $$bases; do echo $$i; done \ + | $(am__list_recheck_tests)` || exit 1; \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + log_list=`echo $$log_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \ + am__force_recheck=am--force-recheck \ + TEST_LOGS="$$log_list"; \ + exit $$? +test-eevl.log: test-eevl$(EXEEXT) + @p='test-eevl$(EXEEXT)'; \ + b='test-eevl'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +.test.log: + @p='$<'; \ + $(am__set_b); \ + $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +@am__EXEEXT_TRUE@.test$(EXEEXT).log: +@am__EXEEXT_TRUE@ @p='$<'; \ +@am__EXEEXT_TRUE@ $(am__set_b); \ +@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ +@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \ +@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ +@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT) + +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 + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libgimpwidgetsincludedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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: + -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS) + -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs) + -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + +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." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/gimp3migration.Plo + -rm -f ./$(DEPDIR)/gimpbrowser.Plo + -rm -f ./$(DEPDIR)/gimpbusybox.Plo + -rm -f ./$(DEPDIR)/gimpbutton.Plo + -rm -f ./$(DEPDIR)/gimpcairo-utils.Plo + -rm -f ./$(DEPDIR)/gimpcellrenderercolor.Plo + -rm -f ./$(DEPDIR)/gimpcellrenderertoggle.Plo + -rm -f ./$(DEPDIR)/gimpchainbutton.Plo + -rm -f ./$(DEPDIR)/gimpcolorarea.Plo + -rm -f ./$(DEPDIR)/gimpcolorbutton.Plo + -rm -f ./$(DEPDIR)/gimpcolordisplay.Plo + -rm -f ./$(DEPDIR)/gimpcolordisplaystack.Plo + -rm -f ./$(DEPDIR)/gimpcolorhexentry.Plo + -rm -f ./$(DEPDIR)/gimpcolornotebook.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofilechooserdialog.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofilecombobox.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofilestore.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofileview.Plo + -rm -f ./$(DEPDIR)/gimpcolorscale.Plo + -rm -f ./$(DEPDIR)/gimpcolorscales.Plo + -rm -f ./$(DEPDIR)/gimpcolorselect.Plo + -rm -f ./$(DEPDIR)/gimpcolorselection.Plo + -rm -f ./$(DEPDIR)/gimpcolorselector.Plo + -rm -f ./$(DEPDIR)/gimpcontroller.Plo + -rm -f ./$(DEPDIR)/gimpdialog.Plo + -rm -f ./$(DEPDIR)/gimpeevl.Plo + -rm -f ./$(DEPDIR)/gimpenumcombobox.Plo + -rm -f ./$(DEPDIR)/gimpenumlabel.Plo + -rm -f ./$(DEPDIR)/gimpenumstore.Plo + -rm -f ./$(DEPDIR)/gimpenumwidgets.Plo + -rm -f ./$(DEPDIR)/gimpfileentry.Plo + -rm -f ./$(DEPDIR)/gimpframe.Plo + -rm -f ./$(DEPDIR)/gimphelpui.Plo + -rm -f ./$(DEPDIR)/gimphintbox.Plo + -rm -f ./$(DEPDIR)/gimpicons.Plo + -rm -f ./$(DEPDIR)/gimpintcombobox.Plo + -rm -f ./$(DEPDIR)/gimpintstore.Plo + -rm -f ./$(DEPDIR)/gimpmemsizeentry.Plo + -rm -f ./$(DEPDIR)/gimpnumberpairentry.Plo + -rm -f ./$(DEPDIR)/gimpoffsetarea.Plo + -rm -f ./$(DEPDIR)/gimpoldwidgets.Plo + -rm -f ./$(DEPDIR)/gimppageselector.Plo + -rm -f ./$(DEPDIR)/gimppatheditor.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-default.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-kwin.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-quartz.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-win32.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-xdg.Plo + -rm -f ./$(DEPDIR)/gimppickbutton.Plo + -rm -f ./$(DEPDIR)/gimppixmap.Plo + -rm -f ./$(DEPDIR)/gimppreview.Plo + -rm -f ./$(DEPDIR)/gimppreviewarea.Plo + -rm -f ./$(DEPDIR)/gimppropwidgets.Plo + -rm -f ./$(DEPDIR)/gimpquerybox.Plo + -rm -f ./$(DEPDIR)/gimpruler.Plo + -rm -f ./$(DEPDIR)/gimpscaleentry.Plo + -rm -f ./$(DEPDIR)/gimpscrolledpreview.Plo + -rm -f ./$(DEPDIR)/gimpsizeentry.Plo + -rm -f ./$(DEPDIR)/gimpspinbutton.Plo + -rm -f ./$(DEPDIR)/gimpstringcombobox.Plo + -rm -f ./$(DEPDIR)/gimpunitcombobox.Plo + -rm -f ./$(DEPDIR)/gimpunitmenu.Plo + -rm -f ./$(DEPDIR)/gimpunitstore.Plo + -rm -f ./$(DEPDIR)/gimpwidgets-error.Plo + -rm -f ./$(DEPDIR)/gimpwidgets-private.Plo + -rm -f ./$(DEPDIR)/gimpwidgets.Plo + -rm -f ./$(DEPDIR)/gimpwidgetsenums.Plo + -rm -f ./$(DEPDIR)/gimpwidgetsmarshal.Plo + -rm -f ./$(DEPDIR)/gimpwidgetsutils.Plo + -rm -f ./$(DEPDIR)/gimpzoommodel.Plo + -rm -f ./$(DEPDIR)/test-eevl.Po + -rm -f ./$(DEPDIR)/test-preview-area.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-data-local \ + install-libgimpwidgetsincludeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +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)/gimp3migration.Plo + -rm -f ./$(DEPDIR)/gimpbrowser.Plo + -rm -f ./$(DEPDIR)/gimpbusybox.Plo + -rm -f ./$(DEPDIR)/gimpbutton.Plo + -rm -f ./$(DEPDIR)/gimpcairo-utils.Plo + -rm -f ./$(DEPDIR)/gimpcellrenderercolor.Plo + -rm -f ./$(DEPDIR)/gimpcellrenderertoggle.Plo + -rm -f ./$(DEPDIR)/gimpchainbutton.Plo + -rm -f ./$(DEPDIR)/gimpcolorarea.Plo + -rm -f ./$(DEPDIR)/gimpcolorbutton.Plo + -rm -f ./$(DEPDIR)/gimpcolordisplay.Plo + -rm -f ./$(DEPDIR)/gimpcolordisplaystack.Plo + -rm -f ./$(DEPDIR)/gimpcolorhexentry.Plo + -rm -f ./$(DEPDIR)/gimpcolornotebook.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofilechooserdialog.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofilecombobox.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofilestore.Plo + -rm -f ./$(DEPDIR)/gimpcolorprofileview.Plo + -rm -f ./$(DEPDIR)/gimpcolorscale.Plo + -rm -f ./$(DEPDIR)/gimpcolorscales.Plo + -rm -f ./$(DEPDIR)/gimpcolorselect.Plo + -rm -f ./$(DEPDIR)/gimpcolorselection.Plo + -rm -f ./$(DEPDIR)/gimpcolorselector.Plo + -rm -f ./$(DEPDIR)/gimpcontroller.Plo + -rm -f ./$(DEPDIR)/gimpdialog.Plo + -rm -f ./$(DEPDIR)/gimpeevl.Plo + -rm -f ./$(DEPDIR)/gimpenumcombobox.Plo + -rm -f ./$(DEPDIR)/gimpenumlabel.Plo + -rm -f ./$(DEPDIR)/gimpenumstore.Plo + -rm -f ./$(DEPDIR)/gimpenumwidgets.Plo + -rm -f ./$(DEPDIR)/gimpfileentry.Plo + -rm -f ./$(DEPDIR)/gimpframe.Plo + -rm -f ./$(DEPDIR)/gimphelpui.Plo + -rm -f ./$(DEPDIR)/gimphintbox.Plo + -rm -f ./$(DEPDIR)/gimpicons.Plo + -rm -f ./$(DEPDIR)/gimpintcombobox.Plo + -rm -f ./$(DEPDIR)/gimpintstore.Plo + -rm -f ./$(DEPDIR)/gimpmemsizeentry.Plo + -rm -f ./$(DEPDIR)/gimpnumberpairentry.Plo + -rm -f ./$(DEPDIR)/gimpoffsetarea.Plo + -rm -f ./$(DEPDIR)/gimpoldwidgets.Plo + -rm -f ./$(DEPDIR)/gimppageselector.Plo + -rm -f ./$(DEPDIR)/gimppatheditor.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-default.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-kwin.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-quartz.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-win32.Plo + -rm -f ./$(DEPDIR)/gimppickbutton-xdg.Plo + -rm -f ./$(DEPDIR)/gimppickbutton.Plo + -rm -f ./$(DEPDIR)/gimppixmap.Plo + -rm -f ./$(DEPDIR)/gimppreview.Plo + -rm -f ./$(DEPDIR)/gimppreviewarea.Plo + -rm -f ./$(DEPDIR)/gimppropwidgets.Plo + -rm -f ./$(DEPDIR)/gimpquerybox.Plo + -rm -f ./$(DEPDIR)/gimpruler.Plo + -rm -f ./$(DEPDIR)/gimpscaleentry.Plo + -rm -f ./$(DEPDIR)/gimpscrolledpreview.Plo + -rm -f ./$(DEPDIR)/gimpsizeentry.Plo + -rm -f ./$(DEPDIR)/gimpspinbutton.Plo + -rm -f ./$(DEPDIR)/gimpstringcombobox.Plo + -rm -f ./$(DEPDIR)/gimpunitcombobox.Plo + -rm -f ./$(DEPDIR)/gimpunitmenu.Plo + -rm -f ./$(DEPDIR)/gimpunitstore.Plo + -rm -f ./$(DEPDIR)/gimpwidgets-error.Plo + -rm -f ./$(DEPDIR)/gimpwidgets-private.Plo + -rm -f ./$(DEPDIR)/gimpwidgets.Plo + -rm -f ./$(DEPDIR)/gimpwidgetsenums.Plo + -rm -f ./$(DEPDIR)/gimpwidgetsmarshal.Plo + -rm -f ./$(DEPDIR)/gimpwidgetsutils.Plo + -rm -f ./$(DEPDIR)/gimpzoommodel.Plo + -rm -f ./$(DEPDIR)/test-eevl.Po + -rm -f ./$(DEPDIR)/test-preview-area.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: uninstall-libLTLIBRARIES \ + uninstall-libgimpwidgetsincludeHEADERS uninstall-local + +.MAKE: all check check-am install install-am install-exec \ + install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-generic clean-libLTLIBRARIES \ + clean-libtool 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-data-local install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES \ + install-libgimpwidgetsincludeHEADERS 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 \ + recheck tags tags-am uninstall uninstall-am \ + uninstall-libLTLIBRARIES \ + uninstall-libgimpwidgetsincludeHEADERS uninstall-local + +.PRECIOUS: Makefile + + +@OS_WIN32_TRUE@install-libtool-import-lib: +@OS_WIN32_TRUE@ $(INSTALL) .libs/libgimpwidgets-$(GIMP_API_VERSION).dll.a $(DESTDIR)$(libdir) +@OS_WIN32_TRUE@ $(INSTALL) $(srcdir)/gimpwidgets.def $(DESTDIR)$(libdir) + +@OS_WIN32_TRUE@uninstall-libtool-import-lib: +@OS_WIN32_TRUE@ -rm $(DESTDIR)$(libdir)/libgimpwidgets-$(GIMP_API_VERSION).dll.a +@OS_WIN32_TRUE@ -rm $(DESTDIR)$(libdir)/gimpwidgets.def +@OS_WIN32_FALSE@install-libtool-import-lib: +@OS_WIN32_FALSE@uninstall-libtool-import-lib: + +@MS_LIB_AVAILABLE_TRUE@install-ms-lib: +@MS_LIB_AVAILABLE_TRUE@ $(INSTALL) gimpwidgets-$(GIMP_API_VERSION).lib $(DESTDIR)$(libdir) + +@MS_LIB_AVAILABLE_TRUE@uninstall-ms-lib: +@MS_LIB_AVAILABLE_TRUE@ -rm $(DESTDIR)$(libdir)/gimpwidgets-$(GIMP_API_VERSION).lib + +@MS_LIB_AVAILABLE_TRUE@gimpwidgets-@GIMP_API_VERSION@.lib: gimpwidgets.def +@MS_LIB_AVAILABLE_TRUE@ lib -name:libgimpwidgets-$(GIMP_API_VERSION)-@LT_CURRENT_MINUS_AGE@.dll -def:gimpwidgets.def -out:$@ + +@MS_LIB_AVAILABLE_FALSE@install-ms-lib: +@MS_LIB_AVAILABLE_FALSE@uninstall-ms-lib: + +xgen-wec: $(srcdir)/gimpwidgetsenums.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 \"gimpwidgetsenums.h\"\n#include \"libgimp/libgimp-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_domain (type, GETTEXT_PACKAGE \"-libgimp\");\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)/gimpwidgetsenums.c: xgen-wec + $(AM_V_GEN) if ! cmp -s $< $@; then \ + cp $< $@; \ + else \ + touch $@ 2> /dev/null \ + || true; \ + fi + +gimpwidgetsmarshal.h: $(srcdir)/gimpwidgetsmarshal.list + $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --header >> xgen-wmh \ + && (cmp -s xgen-wmh $(@F) || cp xgen-wmh $(@F)) \ + && rm -f xgen-wmh xgen-wmh~ + +gimpwidgetsmarshal.c: gimpwidgetsmarshal.h + $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --header --body >> xgen-wmc \ + && cp xgen-wmc $(@F) \ + && rm -f xgen-wmc xgen-wmc~ + +install-data-local: install-ms-lib install-libtool-import-lib + +uninstall-local: uninstall-ms-lib uninstall-libtool-import-lib + +# 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/libgimpwidgets/gimp3migration.c b/libgimpwidgets/gimp3migration.c new file mode 100644 index 0000000..640590e --- /dev/null +++ b/libgimpwidgets/gimp3migration.c @@ -0,0 +1,262 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimp3migration.c + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimp3migration.h" + + +GtkWidget * +gtk_box_new (GtkOrientation orientation, + gint spacing) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return gtk_hbox_new (FALSE, spacing); + else + return gtk_vbox_new (FALSE, spacing); +} + +GtkWidget * +gtk_button_box_new (GtkOrientation orientation) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return gtk_hbutton_box_new (); + else + return gtk_vbutton_box_new (); +} + +GtkWidget * +gtk_paned_new (GtkOrientation orientation) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return gtk_hpaned_new (); + else + return gtk_vpaned_new (); +} + +GtkWidget * +gtk_scale_new (GtkOrientation orientation, + GtkAdjustment *adjustment) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return gtk_hscale_new (adjustment); + else + return gtk_vscale_new (adjustment); +} + +GtkWidget * +gtk_scrollbar_new (GtkOrientation orientation, + GtkAdjustment *adjustment) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return gtk_hscrollbar_new (adjustment); + else + return gtk_vscrollbar_new (adjustment); +} + +GtkWidget * +gtk_separator_new (GtkOrientation orientation) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return gtk_hseparator_new (); + else + return gtk_vseparator_new (); +} + +#if ! GTK_CHECK_VERSION (3, 3, 0) + +gboolean +gdk_event_triggers_context_menu (const GdkEvent *event) +{ + g_return_val_if_fail (event != NULL, FALSE); + + if (event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (bevent->button == 3 && + ! (bevent->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK))) + return TRUE; + +#ifdef GDK_WINDOWING_QUARTZ + if (bevent->button == 1 && + ! (bevent->state & (GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)) && + (bevent->state & GDK_CONTROL_MASK)) + return TRUE; +#endif + } + + return FALSE; +} + +GdkModifierType +gdk_keymap_get_modifier_mask (GdkKeymap *keymap, + GdkModifierIntent intent) +{ + g_return_val_if_fail (GDK_IS_KEYMAP (keymap), 0); + +#ifdef GDK_WINDOWING_QUARTZ + switch (intent) + { + case GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR: + return GDK_MOD2_MASK; + + case GDK_MODIFIER_INTENT_CONTEXT_MENU: + return GDK_CONTROL_MASK; + + case GDK_MODIFIER_INTENT_EXTEND_SELECTION: + return GDK_SHIFT_MASK; + + case GDK_MODIFIER_INTENT_MODIFY_SELECTION: + return GDK_MOD2_MASK; + + case GDK_MODIFIER_INTENT_NO_TEXT_INPUT: + return GDK_MOD2_MASK | GDK_CONTROL_MASK; + + default: + g_return_val_if_reached (0); + } +#else + switch (intent) + { + case GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR: + return GDK_CONTROL_MASK; + + case GDK_MODIFIER_INTENT_CONTEXT_MENU: + return 0; + + case GDK_MODIFIER_INTENT_EXTEND_SELECTION: + return GDK_SHIFT_MASK; + + case GDK_MODIFIER_INTENT_MODIFY_SELECTION: + return GDK_CONTROL_MASK; + + case GDK_MODIFIER_INTENT_NO_TEXT_INPUT: + return GDK_MOD1_MASK | GDK_CONTROL_MASK; + + default: + g_return_val_if_reached (0); + } +#endif +} + +GdkModifierType +gtk_widget_get_modifier_mask (GtkWidget *widget, + GdkModifierIntent intent) +{ + GdkDisplay *display; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + + display = gtk_widget_get_display (widget); + + return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + intent); +} + +#endif /* GTK+ 3.3 */ + +gboolean +gdk_cairo_get_clip_rectangle (cairo_t *cr, + GdkRectangle *rect) +{ + double x1, y1, x2, y2; + gboolean clip_exists; + + cairo_clip_extents (cr, &x1, &y1, &x2, &y2); + + clip_exists = x1 < x2 && y1 < y2; + + if (rect) + { + x1 = floor (x1); + y1 = floor (y1); + x2 = ceil (x2); + y2 = ceil (y2); + + rect->x = CLAMP (x1, G_MININT, G_MAXINT); + rect->y = CLAMP (y1, G_MININT, G_MAXINT); + rect->width = CLAMP (x2 - x1, G_MININT, G_MAXINT); + rect->height = CLAMP (y2 - y1, G_MININT, G_MAXINT); + } + + return clip_exists; +} + +void +gdk_screen_get_monitor_workarea (GdkScreen *screen, + gint monitor_num, + GdkRectangle *dest) +{ + gdk_screen_get_monitor_geometry (screen, monitor_num, dest); +} + +void +gtk_label_set_xalign (GtkLabel *label, + gfloat xalign) +{ + g_return_if_fail (GTK_IS_LABEL (label)); + + xalign = CLAMP (xalign, 0.0, 1.0); + + g_object_set (label, "xalign", xalign, NULL); +} + +gfloat +gtk_label_get_xalign (GtkLabel *label) +{ + gfloat xalign; + + g_return_val_if_fail (GTK_IS_LABEL (label), 0.5); + + g_object_get (label, "xalign", &xalign, NULL); + + return xalign; +} + +void +gtk_label_set_yalign (GtkLabel *label, + gfloat yalign) +{ + g_return_if_fail (GTK_IS_LABEL (label)); + + yalign = CLAMP (yalign, 0.0, 1.0); + + g_object_set (label, "yalign", yalign, NULL); +} + +gfloat +gtk_label_get_yalign (GtkLabel *label) +{ + gfloat yalign; + + g_return_val_if_fail (GTK_IS_LABEL (label), 0.5); + + g_object_get (label, "yalign", &yalign, NULL); + + return yalign; +} diff --git a/libgimpwidgets/gimp3migration.h b/libgimpwidgets/gimp3migration.h new file mode 100644 index 0000000..53d4ddc --- /dev/null +++ b/libgimpwidgets/gimp3migration.h @@ -0,0 +1,77 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimp3migration.h + * Copyright (C) 2011 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_3_MIGRATION_H__ +#define __GIMP_3_MIGRATION_H__ + + +/* This file is evil. Its purpose is to keep GIMP's gtk3-port branch + * manageable, and contains functions that are only in GTK+ 3.x but + * are *not* in GTK+ 2.x. Please just ignore the uglyness and move + * along. This file will be removed in GIMP 3. + */ + +GtkWidget * gtk_box_new (GtkOrientation orientation, + gint spacing); +GtkWidget * gtk_button_box_new (GtkOrientation orientation); +GtkWidget * gtk_paned_new (GtkOrientation orientation); +GtkWidget * gtk_scale_new (GtkOrientation orientation, + GtkAdjustment *adjustment); +GtkWidget * gtk_scrollbar_new (GtkOrientation orientation, + GtkAdjustment *adjustment); +GtkWidget * gtk_separator_new (GtkOrientation orientation); + + +typedef enum +{ + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR, + GDK_MODIFIER_INTENT_CONTEXT_MENU, + GDK_MODIFIER_INTENT_EXTEND_SELECTION, + GDK_MODIFIER_INTENT_MODIFY_SELECTION, + GDK_MODIFIER_INTENT_NO_TEXT_INPUT +} GdkModifierIntent; + +gboolean gdk_event_triggers_context_menu (const GdkEvent *event); +GdkModifierType gdk_keymap_get_modifier_mask (GdkKeymap *keymap, + GdkModifierIntent intent); +GdkModifierType gtk_widget_get_modifier_mask (GtkWidget *widget, + GdkModifierIntent intent); + +gboolean gdk_cairo_get_clip_rectangle (cairo_t *cr, + GdkRectangle *rect); +void gdk_screen_get_monitor_workarea (GdkScreen *screen, + gint monitor_num, + GdkRectangle *dest); + +void gtk_label_set_xalign (GtkLabel *label, + gfloat xalign); +gfloat gtk_label_get_xalign (GtkLabel *label); + +void gtk_label_set_yalign (GtkLabel *label, + gfloat yalign); +gfloat gtk_label_get_yalign (GtkLabel *label); + + +#endif /* __GIMP_3_MIGRATION_H__ */ diff --git a/libgimpwidgets/gimpbrowser.c b/libgimpwidgets/gimpbrowser.c new file mode 100644 index 0000000..c3321f3 --- /dev/null +++ b/libgimpwidgets/gimpbrowser.c @@ -0,0 +1,396 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpbrowser.c + * Copyright (C) 2005 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpwidgets.h" +#include "gimpwidgetsmarshal.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpbrowser + * @title: GimpBrowser + * @short_description: A base class for a documentation browser. + * + * A base class for a documentation browser. + **/ + + +enum +{ + SEARCH, + LAST_SIGNAL +}; + + +static void gimp_browser_dispose (GObject *object); + +static void gimp_browser_combo_changed (GtkComboBox *combo, + GimpBrowser *browser); +static void gimp_browser_entry_changed (GtkEntry *entry, + GimpBrowser *browser); +static void gimp_browser_entry_icon_press (GtkEntry *entry, + GtkEntryIconPosition icon_pos, + GdkEvent *event, + GimpBrowser *browser); +static gboolean gimp_browser_search_timeout (gpointer data); + + +G_DEFINE_TYPE (GimpBrowser, gimp_browser, GTK_TYPE_HPANED) + +#define parent_class gimp_browser_parent_class + +static guint browser_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_browser_class_init (GimpBrowserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + browser_signals[SEARCH] = + g_signal_new ("search", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpBrowserClass, search), + NULL, NULL, + _gimp_widgets_marshal_VOID__STRING_INT, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_INT); + + object_class->dispose = gimp_browser_dispose; + + klass->search = NULL; +} + +static void +gimp_browser_init (GimpBrowser *browser) +{ + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *scrolled_window; + GtkWidget *viewport; + + browser->search_type = -1; + + browser->left_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_paned_pack1 (GTK_PANED (browser), browser->left_vbox, FALSE, TRUE); + gtk_widget_show (browser->left_vbox); + + /* search entry */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (browser->left_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("_Search:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + browser->search_entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (hbox), browser->search_entry, TRUE, TRUE, 0); + gtk_widget_show (browser->search_entry); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), browser->search_entry); + + g_signal_connect (browser->search_entry, "changed", + G_CALLBACK (gimp_browser_entry_changed), + browser); + + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (browser->search_entry), + GTK_ENTRY_ICON_SECONDARY, "edit-clear"); + gtk_entry_set_icon_activatable (GTK_ENTRY (browser->search_entry), + GTK_ENTRY_ICON_SECONDARY, TRUE); + gtk_entry_set_icon_sensitive (GTK_ENTRY (browser->search_entry), + GTK_ENTRY_ICON_SECONDARY, FALSE); + + g_signal_connect (browser->search_entry, "icon-press", + G_CALLBACK (gimp_browser_entry_icon_press), + browser); + + /* count label */ + + browser->count_label = gtk_label_new (_("No matches")); + gtk_label_set_xalign (GTK_LABEL (browser->count_label), 0.0); + gimp_label_set_attributes (GTK_LABEL (browser->count_label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_end (GTK_BOX (browser->left_vbox), browser->count_label, + FALSE, FALSE, 0); + gtk_widget_show (browser->count_label); + + /* scrolled window */ + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_paned_pack2 (GTK_PANED (browser), scrolled_window, TRUE, TRUE); + gtk_widget_show (scrolled_window); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (scrolled_window), viewport); + gtk_widget_show (viewport); + + browser->right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_set_border_width (GTK_CONTAINER (browser->right_vbox), 12); + gtk_container_add (GTK_CONTAINER (viewport), browser->right_vbox); + gtk_widget_show (browser->right_vbox); + + gtk_widget_grab_focus (browser->search_entry); +} + +static void +gimp_browser_dispose (GObject *object) +{ + GimpBrowser *browser = GIMP_BROWSER (object); + + if (browser->search_timeout_id) + { + g_source_remove (browser->search_timeout_id); + browser->search_timeout_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +/* public functions */ + + +/** + * gimp_browser_new: + * + * Create a new #GimpBrowser widget. + * + * Return Value: a newly created #GimpBrowser. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_browser_new (void) +{ + return g_object_new (GIMP_TYPE_BROWSER, NULL); +} + +/** + * gimp_browser_add_search_types: + * @browser: a #GimpBrowser widget + * @first_type_label: the label of the first search type + * @first_type_id: an integer that identifies the first search type + * @...: a %NULL-terminated list of more labels and ids. + * + * Populates the #GtkComboBox with search types. + * + * Since: 2.4 + **/ +void +gimp_browser_add_search_types (GimpBrowser *browser, + const gchar *first_type_label, + gint first_type_id, + ...) +{ + g_return_if_fail (GIMP_IS_BROWSER (browser)); + g_return_if_fail (first_type_label != NULL); + + if (! browser->search_type_combo) + { + GtkWidget *combo; + va_list args; + + va_start (args, first_type_id); + combo = gimp_int_combo_box_new_valist (first_type_label, + first_type_id, + args); + va_end (args); + + gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (combo), FALSE); + + browser->search_type_combo = combo; + browser->search_type = first_type_id; + + gtk_box_pack_end (GTK_BOX (gtk_widget_get_parent (browser->search_entry)), + combo, FALSE, FALSE, 0); + gtk_widget_show (combo); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + browser->search_type, + G_CALLBACK (gimp_int_combo_box_get_active), + &browser->search_type); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_browser_combo_changed), + browser); + } + else + { + gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (browser->search_type_combo), + first_type_label, first_type_id, + NULL); + } +} + +/** + * gimp_browser_set_widget: + * @browser: a #GimpBrowser widget + * @widget: a #GtkWidget + * + * Sets the widget to appear on the right side of the @browser. + * + * Since: 2.4 + **/ +void +gimp_browser_set_widget (GimpBrowser *browser, + GtkWidget *widget) +{ + g_return_if_fail (GIMP_IS_BROWSER (browser)); + g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget)); + + if (widget == browser->right_widget) + return; + + if (browser->right_widget) + gtk_container_remove (GTK_CONTAINER (browser->right_vbox), + browser->right_widget); + + browser->right_widget = widget; + + if (widget) + { + gtk_box_pack_start (GTK_BOX (browser->right_vbox), widget, + FALSE, FALSE, 0); + gtk_widget_show (widget); + } +} + +/** + * gimp_browser_show_message: + * @browser: a #GimpBrowser widget + * @message: text message + * + * Displays @message in the right side of the @browser. Unless the right + * side already contains a #GtkLabel, the widget previously added with + * gimp_browser_set_widget() is removed and replaced by a #GtkLabel. + * + * Since: 2.4 + **/ +void +gimp_browser_show_message (GimpBrowser *browser, + const gchar *message) +{ + g_return_if_fail (GIMP_IS_BROWSER (browser)); + g_return_if_fail (message != NULL); + + if (GTK_IS_LABEL (browser->right_widget)) + { + gtk_label_set_text (GTK_LABEL (browser->right_widget), message); + } + else + { + GtkWidget *label = gtk_label_new (message); + + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gimp_browser_set_widget (browser, label); + } + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + + +/* private functions */ + +static void +gimp_browser_queue_search (GimpBrowser *browser) +{ + if (browser->search_timeout_id) + g_source_remove (browser->search_timeout_id); + + browser->search_timeout_id = + g_timeout_add (100, gimp_browser_search_timeout, browser); +} + +static void +gimp_browser_combo_changed (GtkComboBox *combo, + GimpBrowser *browser) +{ + gimp_browser_queue_search (browser); +} + +static void +gimp_browser_entry_changed (GtkEntry *entry, + GimpBrowser *browser) +{ + gimp_browser_queue_search (browser); + + gtk_entry_set_icon_sensitive (entry, + GTK_ENTRY_ICON_SECONDARY, + gtk_entry_get_text_length (entry) > 0); +} + +static void +gimp_browser_entry_icon_press (GtkEntry *entry, + GtkEntryIconPosition icon_pos, + GdkEvent *event, + GimpBrowser *browser) +{ + GdkEventButton *bevent = (GdkEventButton *) event; + + if (icon_pos == GTK_ENTRY_ICON_SECONDARY && bevent->button == 1) + { + gtk_entry_set_text (entry, ""); + } +} + +static gboolean +gimp_browser_search_timeout (gpointer data) +{ + GimpBrowser *browser = GIMP_BROWSER (data); + const gchar *search_string; + + GDK_THREADS_ENTER(); + + search_string = gtk_entry_get_text (GTK_ENTRY (browser->search_entry)); + + if (! search_string) + search_string = ""; + + g_signal_emit (browser, browser_signals[SEARCH], 0, + search_string, browser->search_type); + + browser->search_timeout_id = 0; + + GDK_THREADS_LEAVE(); + + return FALSE; +} diff --git a/libgimpwidgets/gimpbrowser.h b/libgimpwidgets/gimpbrowser.h new file mode 100644 index 0000000..c72e2b0 --- /dev/null +++ b/libgimpwidgets/gimpbrowser.h @@ -0,0 +1,95 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpbrowser.h + * Copyright (C) 2005 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_BROWSER_H__ +#define __GIMP_BROWSER_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_BROWSER (gimp_browser_get_type ()) +#define GIMP_BROWSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BROWSER, GimpBrowser)) +#define GIMP_BROWSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BROWSER, GimpBrowserClass)) +#define GIMP_IS_BROWSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BROWSER)) +#define GIMP_IS_BROWSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BROWSER)) +#define GIMP_BROWSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BROWSER, GimpBrowserClass)) + + +typedef struct _GimpBrowserClass GimpBrowserClass; + +struct _GimpBrowser +{ + GtkHPaned parent_instance; + + GtkWidget *left_vbox; + + GtkWidget *search_entry; + guint search_timeout_id; + + GtkWidget *search_type_combo; + gint search_type; + + GtkWidget *count_label; + + GtkWidget *right_vbox; + GtkWidget *right_widget; +}; + +struct _GimpBrowserClass +{ + GtkHPanedClass parent_class; + + void (* search) (GimpBrowser *browser, + const gchar *search_string, + gint search_type); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_browser_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_browser_new (void); + +void gimp_browser_add_search_types (GimpBrowser *browser, + const gchar *first_type_label, + gint first_type_id, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_browser_set_widget (GimpBrowser *browser, + GtkWidget *widget); +void gimp_browser_show_message (GimpBrowser *browser, + const gchar *message); + + +G_END_DECLS + +#endif /* __GIMP_BROWSER_H__ */ diff --git a/libgimpwidgets/gimpbusybox.c b/libgimpwidgets/gimpbusybox.c new file mode 100644 index 0000000..f47a520 --- /dev/null +++ b/libgimpwidgets/gimpbusybox.c @@ -0,0 +1,236 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpbusybox.c + * Copyright (C) 2018 Ell + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimp3migration.h" +#include "gimpbusybox.h" +#include "gimpwidgetsutils.h" + + +/** + * SECTION: gimpbusybox + * @title: GimpBusyBox + * @short_description: A widget indicating an ongoing operation + * + * #GimpBusyBox displays a styled message, providing indication of + * an ongoing operation. + **/ + + +enum +{ + PROP_0, + PROP_MESSAGE +}; + + +struct _GimpBusyBoxPrivate +{ + GtkLabel *label; +}; + + +/* local function prototypes */ + +static void gimp_busy_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_busy_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpBusyBox, gimp_busy_box, GTK_TYPE_ALIGNMENT) + +#define parent_class gimp_busy_box_parent_class + + +/* private functions */ + + +static void +gimp_busy_box_class_init (GimpBusyBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gimp_busy_box_set_property; + object_class->get_property = gimp_busy_box_get_property; + + /** + * GimpBusyBox:message: + * + * Specifies the displayed message. + * + * Since: 2.10.4 + **/ + g_object_class_install_property (object_class, PROP_MESSAGE, + g_param_spec_string ("message", + "Message", + "The message to display", + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_busy_box_init (GimpBusyBox *box) +{ + GtkWidget *hbox; + GtkWidget *spinner; + GtkWidget *label; + + box->priv = gimp_busy_box_get_instance_private (box); + + gtk_alignment_set (GTK_ALIGNMENT (box), 0.5, 0.5, 0.0, 0.0); + + /* the main hbox */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); + gtk_container_add (GTK_CONTAINER (box), hbox); + gtk_widget_show (hbox); + + /* the spinner */ + spinner = gtk_spinner_new (); + gtk_widget_set_size_request (spinner, 16, 16); + gtk_spinner_start (GTK_SPINNER (spinner)); + gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_widget_show (spinner); + + /* the label */ + label = gtk_label_new (NULL); + box->priv->label = GTK_LABEL (label); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); +} + +static void +gimp_busy_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpBusyBox *box = GIMP_BUSY_BOX (object); + + switch (property_id) + { + case PROP_MESSAGE: + gtk_label_set_text (box->priv->label, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_busy_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpBusyBox *box = GIMP_BUSY_BOX (object); + + switch (property_id) + { + case PROP_MESSAGE: + g_value_set_string (value, gtk_label_get_text (box->priv->label)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + + +/** + * gimp_busy_box_new: + * @message: (allow-none): the displayed message, or %NULL + * + * Creates a new #GimpBusyBox widget. + * + * Returns: A pointer to the new #GimpBusyBox widget. + * + * Since: 2.10.4 + **/ +GtkWidget * +gimp_busy_box_new (const gchar *message) +{ + if (message == NULL) + message = ""; + + return g_object_new (GIMP_TYPE_BUSY_BOX, + "message", message, + NULL); +} + +/** + * gimp_busy_box_set_message: + * @box: a #GimpBusyBox + * @message: the displayed message + * + * Sets the displayed message og @box to @message. + * + * Since: 2.10.4 + **/ +void +gimp_busy_box_set_message (GimpBusyBox *box, + const gchar *message) +{ + g_return_if_fail (GIMP_IS_BUSY_BOX (box)); + g_return_if_fail (message != NULL); + + g_object_set (box, + "message", message, + NULL); +} + +/** + * gimp_busy_box_get_message: + * @box: a #GimpBusyBox + * + * Returns the displayed message of @box. + * + * Returns: The displayed message. + * + * Since: 2.10.4 + **/ +const gchar * +gimp_busy_box_get_message (GimpBusyBox *box) +{ + g_return_val_if_fail (GIMP_IS_BUSY_BOX (box), NULL); + + return gtk_label_get_text (box->priv->label); +} diff --git a/libgimpwidgets/gimpbusybox.h b/libgimpwidgets/gimpbusybox.h new file mode 100644 index 0000000..f2779ad --- /dev/null +++ b/libgimpwidgets/gimpbusybox.h @@ -0,0 +1,76 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpbusybox.h + * Copyright (C) 2018 Ell + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_BUSY_BOX_H__ +#define __GIMP_BUSY_BOX_H__ + +G_BEGIN_DECLS + +#define GIMP_TYPE_BUSY_BOX (gimp_busy_box_get_type ()) +#define GIMP_BUSY_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUSY_BOX, GimpBusyBox)) +#define GIMP_BUSY_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUSY_BOX, GimpBusyBoxClass)) +#define GIMP_IS_BUSY_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUSY_BOX)) +#define GIMP_IS_BUSY_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUSY_BOX)) +#define GIMP_BUSY_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUSY_BOX, GimpBusyBoxClass)) + + +typedef struct _GimpBusyBoxPrivate GimpBusyBoxPrivate; +typedef struct _GimpBusyBoxClass GimpBusyBoxClass; + +struct _GimpBusyBox +{ + GtkAlignment parent_instance; + + GimpBusyBoxPrivate *priv; +}; + +struct _GimpBusyBoxClass +{ + GtkAlignmentClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); + void (* _gimp_reserved5) (void); + void (* _gimp_reserved6) (void); + void (* _gimp_reserved7) (void); + void (* _gimp_reserved8) (void); +}; + + +GType gimp_busy_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_busy_box_new (const gchar *message); + +void gimp_busy_box_set_message (GimpBusyBox *box, + const gchar *message); +const gchar * gimp_busy_box_get_message (GimpBusyBox *box); + + +G_END_DECLS + +#endif /* __GIMP_BUSY_BOX_H__ */ diff --git a/libgimpwidgets/gimpbutton.c b/libgimpwidgets/gimpbutton.c new file mode 100644 index 0000000..67039ac --- /dev/null +++ b/libgimpwidgets/gimpbutton.c @@ -0,0 +1,165 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpbutton.c + * Copyright (C) 2000-2008 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpbutton.h" +#include "gimp3migration.h" + + +/** + * SECTION: gimpbutton + * @title: GimpButton + * @short_description: A #GtkButton with a little extra functionality. + * + * #GimpButton adds an extra signal to the #GtkButton widget that + * allows the callback to distinguish a normal click from a click that + * was performed with modifier keys pressed. + **/ + + +enum +{ + EXTENDED_CLICKED, + LAST_SIGNAL +}; + + +static gboolean gimp_button_button_press (GtkWidget *widget, + GdkEventButton *event); +static void gimp_button_clicked (GtkButton *button); + + +G_DEFINE_TYPE (GimpButton, gimp_button, GTK_TYPE_BUTTON) + +#define parent_class gimp_button_parent_class + +static guint button_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_button_class_init (GimpButtonClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + + /** + * GimpButton::extended-clicked: + * @gimpbutton: the object that received the signal. + * @arg1: the state of modifier keys when the button was clicked + * + * This signal is emitted when the button is clicked with a modifier + * key pressed. + **/ + button_signals[EXTENDED_CLICKED] = + g_signal_new ("extended-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpButtonClass, extended_clicked), + NULL, NULL, + g_cclosure_marshal_VOID__FLAGS, + G_TYPE_NONE, 1, + GDK_TYPE_MODIFIER_TYPE); + + widget_class->button_press_event = gimp_button_button_press; + + button_class->clicked = gimp_button_clicked; +} + +static void +gimp_button_init (GimpButton *button) +{ + button->press_state = 0; +} + +/** + * gimp_button_new: + * + * Creates a new #GimpButton widget. + * + * Returns: A pointer to the new #GimpButton widget. + **/ +GtkWidget * +gimp_button_new (void) +{ + return g_object_new (GIMP_TYPE_BUTTON, NULL); +} + +/** + * gimp_button_extended_clicked: + * @button: a #GimpButton. + * @state: a state as found in #GdkEventButton->state, e.g. #GDK_SHIFT_MASK. + * + * Emits the button's "extended_clicked" signal. + **/ +void +gimp_button_extended_clicked (GimpButton *button, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_BUTTON (button)); + + g_signal_emit (button, button_signals[EXTENDED_CLICKED], 0, state); +} + +static gboolean +gimp_button_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpButton *button = GIMP_BUTTON (widget); + + if (bevent->button == 1) + { + button->press_state = bevent->state; + } + else + { + button->press_state = 0; + } + + return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent); +} + +static void +gimp_button_clicked (GtkButton *button) +{ + if (GIMP_BUTTON (button)->press_state & + (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | + gtk_widget_get_modifier_mask (GTK_WIDGET (button), + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR) | + gtk_widget_get_modifier_mask (GTK_WIDGET (button), + GDK_MODIFIER_INTENT_EXTEND_SELECTION) | + gtk_widget_get_modifier_mask (GTK_WIDGET (button), + GDK_MODIFIER_INTENT_MODIFY_SELECTION))) + { + g_signal_stop_emission_by_name (button, "clicked"); + + gimp_button_extended_clicked (GIMP_BUTTON (button), + GIMP_BUTTON (button)->press_state); + } + else if (GTK_BUTTON_CLASS (parent_class)->clicked) + { + GTK_BUTTON_CLASS (parent_class)->clicked (button); + } +} diff --git a/libgimpwidgets/gimpbutton.h b/libgimpwidgets/gimpbutton.h new file mode 100644 index 0000000..d1ebc36 --- /dev/null +++ b/libgimpwidgets/gimpbutton.h @@ -0,0 +1,77 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpbutton.h + * Copyright (C) 2001 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_BUTTON_H__ +#define __GIMP_BUTTON_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_BUTTON (gimp_button_get_type ()) +#define GIMP_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUTTON, GimpButton)) +#define GIMP_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUTTON, GimpButtonClass)) +#define GIMP_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUTTON)) +#define GIMP_IS_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUTTON)) +#define GIMP_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUTTON, GimpButtonClass)) + + +typedef struct _GimpButtonClass GimpButtonClass; + +struct _GimpButton +{ + GtkButton parent_instance; + + /*< private >*/ + GdkModifierType press_state; +}; + +struct _GimpButtonClass +{ + GtkButtonClass parent_class; + + void (* extended_clicked) (GimpButton *button, + GdkModifierType modifier_state); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_button_new (void); + +void gimp_button_extended_clicked (GimpButton *button, + GdkModifierType state); + + +G_END_DECLS + +#endif /* __GIMP_BUTTON_H__ */ diff --git a/libgimpwidgets/gimpcairo-utils.c b/libgimpwidgets/gimpcairo-utils.c new file mode 100644 index 0000000..a956f28 --- /dev/null +++ b/libgimpwidgets/gimpcairo-utils.c @@ -0,0 +1,202 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcairo-utils.c + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "gimpcairo-utils.h" + + +/** + * SECTION: gimpcairo-utils + * @title: GimpCairo-utils + * @short_description: Utility functions for cairo + * + * Utility functions that make cairo easier to use with common + * GIMP data types. + **/ + + +/** + * gimp_cairo_set_focus_line_pattern: + * @cr: Cairo context + * @widget: widget to draw the focus indicator on + * + * Sets color and dash pattern for stroking a focus line on the given + * @cr. The line pattern is taken from @widget. + * + * Return value: %TRUE if the widget style has a focus line pattern, + * %FALSE otherwise + * + * Since: 2.6 + **/ +gboolean +gimp_cairo_set_focus_line_pattern (cairo_t *cr, + GtkWidget *widget) +{ + gint8 *dash_list; + gboolean retval = FALSE; + + g_return_val_if_fail (cr != NULL, FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + gtk_widget_style_get (widget, + "focus-line-pattern", (gchar *) &dash_list, + NULL); + + if (dash_list[0]) + { + /* Taken straight from gtk_default_draw_focus() + */ + gint n_dashes = strlen ((const gchar *) dash_list); + gdouble *dashes = g_new (gdouble, n_dashes); + gdouble total_length = 0; + gint i; + + for (i = 0; i < n_dashes; i++) + { + dashes[i] = dash_list[i]; + total_length += dash_list[i]; + } + + cairo_set_dash (cr, dashes, n_dashes, 0.5); + + g_free (dashes); + + retval = TRUE; + } + + g_free (dash_list); + + return retval; +} + +/** + * gimp_cairo_surface_create_from_pixbuf: + * @pixbuf: a #GdkPixbuf + * + * Create a Cairo image surface from a GdkPixbuf. + * + * You should avoid calling this function as there are probably more + * efficient ways of achieving the result you are looking for. + * + * Returns: a #cairo_surface_t. + * + * Since: 2.6 + **/ +cairo_surface_t * +gimp_cairo_surface_create_from_pixbuf (GdkPixbuf *pixbuf) +{ + cairo_surface_t *surface; + cairo_format_t format; + guchar *dest; + const guchar *src; + gint width; + gint height; + gint src_stride; + gint dest_stride; + gint y; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + switch (gdk_pixbuf_get_n_channels (pixbuf)) + { + case 3: + format = CAIRO_FORMAT_RGB24; + break; + case 4: + format = CAIRO_FORMAT_ARGB32; + break; + default: + g_assert_not_reached (); + break; + } + + surface = cairo_image_surface_create (format, width, height); + + cairo_surface_flush (surface); + + src = gdk_pixbuf_get_pixels (pixbuf); + src_stride = gdk_pixbuf_get_rowstride (pixbuf); + + dest = cairo_image_surface_get_data (surface); + dest_stride = cairo_image_surface_get_stride (surface); + + switch (format) + { + case CAIRO_FORMAT_RGB24: + for (y = 0; y < height; y++) + { + const guchar *s = src; + guchar *d = dest; + gint w = width; + + while (w--) + { + GIMP_CAIRO_RGB24_SET_PIXEL (d, s[0], s[1], s[2]); + + s += 3; + d += 4; + } + + src += src_stride; + dest += dest_stride; + } + break; + + case CAIRO_FORMAT_ARGB32: + for (y = 0; y < height; y++) + { + const guchar *s = src; + guchar *d = dest; + gint w = width; + + while (w--) + { + GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[1], s[2], s[3]); + + s += 4; + d += 4; + } + + src += src_stride; + dest += dest_stride; + } + break; + + default: + break; + } + + cairo_surface_mark_dirty (surface); + + return surface; +} diff --git a/libgimpwidgets/gimpcairo-utils.h b/libgimpwidgets/gimpcairo-utils.h new file mode 100644 index 0000000..85a5ee4 --- /dev/null +++ b/libgimpwidgets/gimpcairo-utils.h @@ -0,0 +1,36 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcairo-utils.h + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_CAIRO_UTILS_H__ +#define __GIMP_CAIRO_UTILS_H__ + + +gboolean gimp_cairo_set_focus_line_pattern (cairo_t *cr, + GtkWidget *widget); + +cairo_surface_t * gimp_cairo_surface_create_from_pixbuf (GdkPixbuf *pixbuf); + + +#endif /* __GIMP_CAIRO_UTILS_H__ */ diff --git a/libgimpwidgets/gimpcellrenderercolor.c b/libgimpwidgets/gimpcellrenderercolor.c new file mode 100644 index 0000000..cfda1e7 --- /dev/null +++ b/libgimpwidgets/gimpcellrenderercolor.c @@ -0,0 +1,332 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderercolor.c + * Copyright (C) 2004,2007 Sven Neuman <sven1@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "gimpwidgetstypes.h" + +#include "gimpcairo-utils.h" +#include "gimpcellrenderercolor.h" + + +/** + * SECTION: gimpcellrenderercolor + * @title: GimpCellRendererColor + * @short_description: A #GtkCellRenderer to display a #GimpRGB color. + * + * A #GtkCellRenderer to display a #GimpRGB color. + **/ + + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_MENU + + +enum +{ + PROP_0, + PROP_COLOR, + PROP_OPAQUE, + PROP_SIZE +}; + + +static void gimp_cell_renderer_color_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_color_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_color_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *rectangle, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void gimp_cell_renderer_color_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); + + + +G_DEFINE_TYPE (GimpCellRendererColor, gimp_cell_renderer_color, + GTK_TYPE_CELL_RENDERER) + +#define parent_class gimp_cell_renderer_color_parent_class + + +static void +gimp_cell_renderer_color_class_init (GimpCellRendererColorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + object_class->get_property = gimp_cell_renderer_color_get_property; + object_class->set_property = gimp_cell_renderer_color_set_property; + + cell_class->get_size = gimp_cell_renderer_color_get_size; + cell_class->render = gimp_cell_renderer_color_render; + + g_object_class_install_property (object_class, PROP_COLOR, + g_param_spec_boxed ("color", + "Color", + "The displayed color", + GIMP_TYPE_RGB, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_OPAQUE, + g_param_spec_boolean ("opaque", + "Opaque", + "Whether to show transparency", + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SIZE, + g_param_spec_int ("icon-size", + "Icon Size", + "The cell's size", + 0, G_MAXINT, + DEFAULT_ICON_SIZE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_cell_renderer_color_init (GimpCellRendererColor *cell) +{ + gimp_rgba_set (&cell->color, 0.0, 0.0, 0.0, 1.0); +} + +static void +gimp_cell_renderer_color_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererColor *cell = GIMP_CELL_RENDERER_COLOR (object); + + switch (param_id) + { + case PROP_COLOR: + g_value_set_boxed (value, &cell->color); + break; + case PROP_OPAQUE: + g_value_set_boolean (value, cell->opaque); + break; + case PROP_SIZE: + g_value_set_int (value, cell->size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_color_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererColor *cell = GIMP_CELL_RENDERER_COLOR (object); + GimpRGB *color; + + switch (param_id) + { + case PROP_COLOR: + color = g_value_get_boxed (value); + cell->color = *color; + break; + case PROP_OPAQUE: + cell->opaque = g_value_get_boolean (value); + break; + case PROP_SIZE: + cell->size = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_color_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + GimpCellRendererColor *color = GIMP_CELL_RENDERER_COLOR (cell); + gint calc_width; + gint calc_height; + gfloat xalign; + gfloat yalign; + gint xpad; + gint ypad; + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget), + color->size, &calc_width, &calc_height); + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + if (cell_area && calc_width > 0 && calc_height > 0) + { + if (x_offset) + { + *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + 1.0 - xalign : xalign) * + (cell_area->width - calc_width)); + *x_offset = MAX (*x_offset, 0) + xpad; + } + if (y_offset) + { + *y_offset = (yalign * (cell_area->height - calc_height)); + *y_offset = MAX (*y_offset, 0) + ypad; + } + } + else + { + if (x_offset) + *x_offset = 0; + if (y_offset) + *y_offset = 0; + } + + if (width) + *width = calc_width + 2 * xpad; + if (height) + *height = calc_height + 2 * ypad; +} + +static void +gimp_cell_renderer_color_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + GimpCellRendererColor *color = GIMP_CELL_RENDERER_COLOR (cell); + GdkRectangle rect; + gint xpad; + gint ypad; + + gimp_cell_renderer_color_get_size (cell, widget, cell_area, + &rect.x, + &rect.y, + &rect.width, + &rect.height); + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + rect.x += cell_area->x + xpad; + rect.y += cell_area->y + ypad; + rect.width -= 2 * xpad; + rect.height -= 2 * ypad; + + if (rect.width > 2 && rect.height > 2) + { + cairo_t *cr = gdk_cairo_create (window); + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state; + + cairo_rectangle (cr, + rect.x + 1, rect.y + 1, + rect.width - 2, rect.height - 2); + + gimp_cairo_set_source_rgb (cr, &color->color); + cairo_fill (cr); + + if (! color->opaque && color->color.a < 1.0) + { + cairo_pattern_t *pattern; + + cairo_move_to (cr, rect.x + 1, rect.y + rect.height - 1); + cairo_line_to (cr, rect.x + rect.width - 1, rect.y + rect.height - 1); + cairo_line_to (cr, rect.x + rect.width - 1, rect.y + 1); + cairo_close_path (cr); + + pattern = gimp_cairo_checkerboard_create (cr, + GIMP_CHECK_SIZE_SM, + NULL, NULL); + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + + cairo_fill_preserve (cr); + + gimp_cairo_set_source_rgba (cr, &color->color); + cairo_fill (cr); + } + + /* draw border */ + cairo_rectangle (cr, + rect.x + 0.5, rect.y + 0.5, + rect.width - 1, rect.height - 1); + + if (! gtk_cell_renderer_get_sensitive (cell) || + ! gtk_widget_is_sensitive (widget)) + { + state = GTK_STATE_INSENSITIVE; + } + else + { + state = (flags & GTK_CELL_RENDERER_SELECTED ? + GTK_STATE_SELECTED : GTK_STATE_NORMAL); + } + + cairo_set_line_width (cr, 1); + gdk_cairo_set_source_color (cr, &style->fg[state]); + cairo_stroke_preserve (cr); + + cairo_destroy (cr); + } +} + +/** + * gimp_cell_renderer_color_new: + * + * Creates a #GtkCellRenderer that displays a color. + * + * Return value: a new #GimpCellRendererColor + * + * Since: 2.2 + **/ +GtkCellRenderer * +gimp_cell_renderer_color_new (void) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_COLOR, NULL); +} diff --git a/libgimpwidgets/gimpcellrenderercolor.h b/libgimpwidgets/gimpcellrenderercolor.h new file mode 100644 index 0000000..291df40 --- /dev/null +++ b/libgimpwidgets/gimpcellrenderercolor.h @@ -0,0 +1,71 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderercolor.h + * Copyright (C) 2004 Sven Neuman <sven1@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_CELL_RENDERER_COLOR_H__ +#define __GIMP_CELL_RENDERER_COLOR_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_CELL_RENDERER_COLOR (gimp_cell_renderer_color_get_type ()) +#define GIMP_CELL_RENDERER_COLOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_COLOR, GimpCellRendererColor)) +#define GIMP_CELL_RENDERER_COLOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_COLOR, GimpCellRendererColorClass)) +#define GIMP_IS_CELL_RENDERER_COLOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_COLOR)) +#define GIMP_IS_CELL_RENDERER_COLOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_COLOR)) +#define GIMP_CELL_RENDERER_COLOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_COLOR, GimpCellRendererColorClass)) + + +typedef struct _GimpCellRendererColorClass GimpCellRendererColorClass; + +struct _GimpCellRendererColor +{ + GtkCellRenderer parent_instance; + + GimpRGB color; + gboolean opaque; + GtkIconSize size; + gint border; +}; + +struct _GimpCellRendererColorClass +{ + GtkCellRendererClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_cell_renderer_color_get_type (void) G_GNUC_CONST; + +GtkCellRenderer * gimp_cell_renderer_color_new (void); + + +G_END_DECLS + +#endif /* __GIMP_CELL_RENDERER_COLOR_H__ */ diff --git a/libgimpwidgets/gimpcellrenderertoggle.c b/libgimpwidgets/gimpcellrenderertoggle.c new file mode 100644 index 0000000..84dff32 --- /dev/null +++ b/libgimpwidgets/gimpcellrenderertoggle.c @@ -0,0 +1,576 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderertoggle.c + * Copyright (C) 2003-2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpwidgetsmarshal.h" +#include "gimpcellrenderertoggle.h" + + +/** + * SECTION: gimpcellrenderertoggle + * @title: GimpCellRendererToggle + * @short_description: A #GtkCellRendererToggle that displays icons instead + * of a checkbox. + * + * A #GtkCellRendererToggle that displays icons instead of a checkbox. + **/ + + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_BUTTON + + +enum +{ + CLICKED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_ICON_NAME, + PROP_STOCK_ID, + PROP_STOCK_SIZE, + PROP_OVERRIDE_BACKGROUND +}; + + +typedef struct _GimpCellRendererTogglePrivate GimpCellRendererTogglePrivate; + +struct _GimpCellRendererTogglePrivate +{ + gchar *icon_name; + gboolean override_background; +}; + +#define GET_PRIVATE(obj) \ + ((GimpCellRendererTogglePrivate *) gimp_cell_renderer_toggle_get_instance_private ((GimpCellRendererToggle *) (obj))) + + +static void gimp_cell_renderer_toggle_finalize (GObject *object); +static void gimp_cell_renderer_toggle_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_toggle_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_toggle_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *rectangle, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void gimp_cell_renderer_toggle_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); +static gboolean gimp_cell_renderer_toggle_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); +static void gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle, + GtkWidget *widget); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCellRendererToggle, gimp_cell_renderer_toggle, + GTK_TYPE_CELL_RENDERER_TOGGLE) + +#define parent_class gimp_cell_renderer_toggle_parent_class + +static guint toggle_cell_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_cell_renderer_toggle_class_init (GimpCellRendererToggleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + toggle_cell_signals[CLICKED] = + g_signal_new ("clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererToggleClass, clicked), + NULL, NULL, + _gimp_widgets_marshal_VOID__STRING_FLAGS, + G_TYPE_NONE, 2, + G_TYPE_STRING, + GDK_TYPE_MODIFIER_TYPE); + + object_class->finalize = gimp_cell_renderer_toggle_finalize; + object_class->get_property = gimp_cell_renderer_toggle_get_property; + object_class->set_property = gimp_cell_renderer_toggle_set_property; + + cell_class->get_size = gimp_cell_renderer_toggle_get_size; + cell_class->render = gimp_cell_renderer_toggle_render; + cell_class->activate = gimp_cell_renderer_toggle_activate; + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "The icon to display", + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_STOCK_ID, + g_param_spec_string ("stock-id", + "Stock ID", + "The icon to display, deprecated", + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_STOCK_SIZE, + g_param_spec_int ("stock-size", + "Stock Size", + "The icon size to use", + 0, G_MAXINT, + DEFAULT_ICON_SIZE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_OVERRIDE_BACKGROUND, + g_param_spec_boolean ("override-background", + "Override Background", + "Draw the background if the row is selected", + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_cell_renderer_toggle_init (GimpCellRendererToggle *toggle) +{ +} + +static void +gimp_cell_renderer_toggle_finalize (GObject *object) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + GimpCellRendererTogglePrivate *priv = GET_PRIVATE (object); + + g_clear_pointer (&priv->icon_name, g_free); + g_clear_pointer (&toggle->stock_id, g_free); + + g_clear_object (&toggle->pixbuf); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_cell_renderer_toggle_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + GimpCellRendererTogglePrivate *priv = GET_PRIVATE (object); + + switch (param_id) + { + case PROP_ICON_NAME: + g_value_set_string (value, priv->icon_name); + break; + + case PROP_STOCK_ID: + g_value_set_string (value, toggle->stock_id); + break; + + case PROP_STOCK_SIZE: + g_value_set_int (value, toggle->stock_size); + break; + + case PROP_OVERRIDE_BACKGROUND: + g_value_set_boolean (value, priv->override_background); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_toggle_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + GimpCellRendererTogglePrivate *priv = GET_PRIVATE (object); + + switch (param_id) + { + case PROP_ICON_NAME: + if (priv->icon_name) + g_free (priv->icon_name); + priv->icon_name = g_value_dup_string (value); + break; + + case PROP_STOCK_ID: + if (toggle->stock_id) + g_free (toggle->stock_id); + toggle->stock_id = g_value_dup_string (value); + break; + + case PROP_STOCK_SIZE: + toggle->stock_size = g_value_get_int (value); + break; + + case PROP_OVERRIDE_BACKGROUND: + priv->override_background = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } + + g_clear_object (&toggle->pixbuf); +} + +static void +gimp_cell_renderer_toggle_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (cell); + GimpCellRendererTogglePrivate *priv = GET_PRIVATE (cell); + GtkStyle *style = gtk_widget_get_style (widget); + gint calc_width; + gint calc_height; + gint pixbuf_width; + gint pixbuf_height; + gfloat xalign; + gfloat yalign; + gint xpad; + gint ypad; + + if (! priv->icon_name && ! toggle->stock_id) + { + GTK_CELL_RENDERER_CLASS (parent_class)->get_size (cell, + widget, + cell_area, + x_offset, y_offset, + width, height); + return; + } + + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + if (! toggle->pixbuf) + gimp_cell_renderer_toggle_create_pixbuf (toggle, widget); + + pixbuf_width = gdk_pixbuf_get_width (toggle->pixbuf); + pixbuf_height = gdk_pixbuf_get_height (toggle->pixbuf); + + calc_width = (pixbuf_width + + (gint) xpad * 2 + style->xthickness * 2); + calc_height = (pixbuf_height + + (gint) ypad * 2 + style->ythickness * 2); + + if (width) + *width = calc_width; + + if (height) + *height = calc_height; + + if (cell_area) + { + if (x_offset) + { + *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + (1.0 - xalign) : xalign) * + (cell_area->width - calc_width)); + *x_offset = MAX (*x_offset, 0); + } + + if (y_offset) + { + *y_offset = yalign * (cell_area->height - calc_height); + *y_offset = MAX (*y_offset, 0); + } + } +} + +static void +gimp_cell_renderer_toggle_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (cell); + GimpCellRendererTogglePrivate *priv = GET_PRIVATE (cell); + GtkStyle *style = gtk_widget_get_style (widget); + GdkRectangle toggle_rect; + GdkRectangle draw_rect; + GtkStateType state; + gboolean active; + gint xpad; + gint ypad; + + if (! priv->icon_name && ! toggle->stock_id) + { + GTK_CELL_RENDERER_CLASS (parent_class)->render (cell, window, widget, + background_area, + cell_area, expose_area, + flags); + return; + } + + if ((flags & GTK_CELL_RENDERER_SELECTED) && + priv->override_background) + { + gboolean background_set; + + g_object_get (cell, + "cell-background-set", &background_set, + NULL); + + if (background_set) + { + cairo_t *cr = gdk_cairo_create (window); + GdkColor *color; + + g_object_get (cell, + "cell-background-gdk", &color, + NULL); + + gdk_cairo_rectangle (cr, background_area); + gdk_cairo_set_source_color (cr, color); + cairo_fill (cr); + + gdk_color_free (color); + cairo_destroy (cr); + } + } + + gimp_cell_renderer_toggle_get_size (cell, widget, cell_area, + &toggle_rect.x, + &toggle_rect.y, + &toggle_rect.width, + &toggle_rect.height); + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + toggle_rect.x += cell_area->x + xpad; + toggle_rect.y += cell_area->y + ypad; + toggle_rect.width -= xpad * 2; + toggle_rect.height -= ypad * 2; + + if (toggle_rect.width <= 0 || toggle_rect.height <= 0) + return; + + active = + gtk_cell_renderer_toggle_get_active (GTK_CELL_RENDERER_TOGGLE (cell)); + + if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED) + { + if (gtk_widget_has_focus (widget)) + state = GTK_STATE_SELECTED; + else + state = GTK_STATE_ACTIVE; + } + else + { + if (gtk_cell_renderer_toggle_get_activatable (GTK_CELL_RENDERER_TOGGLE (cell))) + state = GTK_STATE_NORMAL; + else + state = GTK_STATE_INSENSITIVE; + } + + if (gdk_rectangle_intersect (expose_area, cell_area, &draw_rect) && + (flags & GTK_CELL_RENDERER_PRELIT)) + gtk_paint_shadow (style, + window, + state, + active ? GTK_SHADOW_IN : GTK_SHADOW_OUT, + &draw_rect, + widget, NULL, + toggle_rect.x, toggle_rect.y, + toggle_rect.width, toggle_rect.height); + + if (active) + { + toggle_rect.x += style->xthickness; + toggle_rect.y += style->ythickness; + toggle_rect.width -= style->xthickness * 2; + toggle_rect.height -= style->ythickness * 2; + + if (gdk_rectangle_intersect (&draw_rect, &toggle_rect, &draw_rect)) + { + cairo_t *cr = gdk_cairo_create (window); + gboolean inconsistent; + + gdk_cairo_rectangle (cr, &draw_rect); + cairo_clip (cr); + + gdk_cairo_set_source_pixbuf (cr, toggle->pixbuf, + toggle_rect.x, toggle_rect.y); + cairo_paint (cr); + + g_object_get (toggle, + "inconsistent", &inconsistent, + NULL); + + if (inconsistent) + { + gdk_cairo_set_source_color (cr, &style->fg[state]); + cairo_set_line_width (cr, 1.5); + cairo_move_to (cr, + toggle_rect.x + toggle_rect.width - 1, + toggle_rect.y + 1); + cairo_line_to (cr, + toggle_rect.x + 1, + toggle_rect.y + toggle_rect.height - 1); + cairo_stroke (cr); + } + + cairo_destroy (cr); + } + } +} + +static gboolean +gimp_cell_renderer_toggle_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererToggle *toggle = GTK_CELL_RENDERER_TOGGLE (cell); + + if (gtk_cell_renderer_toggle_get_activatable (toggle)) + { + GdkModifierType state = 0; + + if (event && ((GdkEventAny *) event)->type == GDK_BUTTON_PRESS) + state = ((GdkEventButton *) event)->state; + + gimp_cell_renderer_toggle_clicked (GIMP_CELL_RENDERER_TOGGLE (cell), + path, state); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle, + GtkWidget *widget) +{ + GimpCellRendererTogglePrivate *priv = GET_PRIVATE (toggle); + + g_clear_object (&toggle->pixbuf); + + if (priv->icon_name) + { + gint width, height; + + if (! gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget), + toggle->stock_size, + &width, &height)) + { + width = 20; + height = 20; + } + + toggle->pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + priv->icon_name, + MIN (width, height), 0, NULL); + } + else + { + toggle->pixbuf = gtk_widget_render_icon (widget, + toggle->stock_id, + toggle->stock_size, NULL); + } +} + + +/** + * gimp_cell_renderer_toggle_new: + * @icon_name: the icon name of the icon to use for the active state + * + * Creates a custom version of the #GtkCellRendererToggle. Instead of + * showing the standard toggle button, it shows a named icon if the + * cell is active and no icon otherwise. This cell renderer is for + * example used in the Layers treeview to indicate and control the + * layer's visibility by showing %GIMP_STOCK_VISIBLE. + * + * Return value: a new #GimpCellRendererToggle + * + * Since: 2.2 + **/ +GtkCellRenderer * +gimp_cell_renderer_toggle_new (const gchar *icon_name) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_TOGGLE, + "icon-name", icon_name, + NULL); +} + +/** + * gimp_cell_renderer_toggle_clicked: + * @cell: a #GimpCellRendererToggle + * @path: the path to the clicked row + * @state: the modifier state + * + * Emits the "clicked" signal from a #GimpCellRendererToggle. + * + * Since: 2.2 + **/ +void +gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_CELL_RENDERER_TOGGLE (cell)); + g_return_if_fail (path != NULL); + + g_signal_emit (cell, toggle_cell_signals[CLICKED], 0, path, state); +} diff --git a/libgimpwidgets/gimpcellrenderertoggle.h b/libgimpwidgets/gimpcellrenderertoggle.h new file mode 100644 index 0000000..368a6a1 --- /dev/null +++ b/libgimpwidgets/gimpcellrenderertoggle.h @@ -0,0 +1,78 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderertoggle.h + * Copyright (C) 2003-2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_CELL_RENDERER_TOGGLE_H__ +#define __GIMP_CELL_RENDERER_TOGGLE_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_CELL_RENDERER_TOGGLE (gimp_cell_renderer_toggle_get_type ()) +#define GIMP_CELL_RENDERER_TOGGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggle)) +#define GIMP_CELL_RENDERER_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggleClass)) +#define GIMP_IS_CELL_RENDERER_TOGGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE)) +#define GIMP_IS_CELL_RENDERER_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_TOGGLE)) +#define GIMP_CELL_RENDERER_TOGGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggleClass)) + + +typedef struct _GimpCellRendererToggleClass GimpCellRendererToggleClass; + +struct _GimpCellRendererToggle +{ + GtkCellRendererToggle parent_instance; + + gchar *stock_id; + GtkIconSize stock_size; + GdkPixbuf *pixbuf; +}; + +struct _GimpCellRendererToggleClass +{ + GtkCellRendererToggleClass parent_class; + + void (* clicked) (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_cell_renderer_toggle_get_type (void) G_GNUC_CONST; + +GtkCellRenderer * gimp_cell_renderer_toggle_new (const gchar *icon_name); + +void gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state); + + +G_END_DECLS + +#endif /* __GIMP_CELL_RENDERER_TOGGLE_H__ */ diff --git a/libgimpwidgets/gimpchainbutton.c b/libgimpwidgets/gimpchainbutton.c new file mode 100644 index 0000000..83ee9f7 --- /dev/null +++ b/libgimpwidgets/gimpchainbutton.c @@ -0,0 +1,554 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpchainbutton.c + * Copyright (C) 1999-2000 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpchainbutton.h" +#include "gimpicons.h" + + +/** + * SECTION: gimpchainbutton + * @title: GimpChainButton + * @short_description: Widget to visually connect two entry widgets. + * @see_also: You may want to use the convenience function + * gimp_coordinates_new() to set up two GimpSizeEntries + * (see #GimpSizeEntry) linked with a #GimpChainButton. + * + * This widget provides a button showing either a linked or a broken + * chain that can be used to link two entries, spinbuttons, colors or + * other GUI elements and show that they may be locked. Use it for + * example to connect X and Y ratios to provide the possibility of a + * constrained aspect ratio. + * + * The #GimpChainButton only gives visual feedback, it does not really + * connect widgets. You have to take care of locking the values + * yourself by checking the state of the #GimpChainButton whenever a + * value changes in one of the connected widgets and adjusting the + * other value if necessary. + **/ + + +enum +{ + PROP_0, + PROP_POSITION, + PROP_ICON_SIZE, + PROP_ACTIVE +}; + +enum +{ + TOGGLED, + LAST_SIGNAL +}; + +static void gimp_chain_button_constructed (GObject *object); +static void gimp_chain_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_chain_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_chain_button_clicked_callback (GtkWidget *widget, + GimpChainButton *button); +static void gimp_chain_button_update_image (GimpChainButton *button); + +static GtkWidget * gimp_chain_line_new (GimpChainPosition position, + gint which); + + +G_DEFINE_TYPE (GimpChainButton, gimp_chain_button, GTK_TYPE_TABLE) + +#define parent_class gimp_chain_button_parent_class + +static guint gimp_chain_button_signals[LAST_SIGNAL] = { 0 }; + +static const gchar * const gimp_chain_icon_names[] = +{ + GIMP_ICON_CHAIN_HORIZONTAL, + GIMP_ICON_CHAIN_HORIZONTAL_BROKEN, + GIMP_ICON_CHAIN_VERTICAL, + GIMP_ICON_CHAIN_VERTICAL_BROKEN +}; + + +static void +gimp_chain_button_class_init (GimpChainButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_chain_button_constructed; + object_class->set_property = gimp_chain_button_set_property; + object_class->get_property = gimp_chain_button_get_property; + + gimp_chain_button_signals[TOGGLED] = + g_signal_new ("toggled", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpChainButtonClass, toggled), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->toggled = NULL; + + /** + * GimpChainButton:position: + * + * The position in which the chain button will be used. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_POSITION, + g_param_spec_enum ("position", + "Position", + "The chain's position", + GIMP_TYPE_CHAIN_POSITION, + GIMP_CHAIN_TOP, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); + + /** + * GimpChainButton:icon-size: + * + * The chain button icon size. + * + * Since: 2.10.10 + */ + g_object_class_install_property (object_class, PROP_ICON_SIZE, + g_param_spec_enum ("icon-size", + "Icon Size", + "The chain's icon size", + GTK_TYPE_ICON_SIZE, + GTK_ICON_SIZE_BUTTON, + G_PARAM_CONSTRUCT | + GIMP_PARAM_READWRITE)); + + /** + * GimpChainButton:active: + * + * The toggled state of the chain button. + * + * Since: 2.10.10 + */ + g_object_class_install_property (object_class, PROP_ACTIVE, + g_param_spec_boolean ("active", + "Active", + "The chain's toggled state", + FALSE, + G_PARAM_CONSTRUCT | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_chain_button_init (GimpChainButton *button) +{ + button->position = GIMP_CHAIN_TOP; + button->active = FALSE; + button->image = gtk_image_new (); + button->button = gtk_button_new (); + + gtk_button_set_relief (GTK_BUTTON (button->button), GTK_RELIEF_NONE); + gtk_container_add (GTK_CONTAINER (button->button), button->image); + gtk_widget_show (button->image); + + g_signal_connect (button->button, "clicked", + G_CALLBACK (gimp_chain_button_clicked_callback), + button); +} + +static void +gimp_chain_button_constructed (GObject *object) +{ + GimpChainButton *button = GIMP_CHAIN_BUTTON (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + button->line1 = gimp_chain_line_new (button->position, 1); + button->line2 = gimp_chain_line_new (button->position, -1); + + gimp_chain_button_update_image (button); + + if (button->position & GIMP_CHAIN_LEFT) /* are we a vertical chainbutton? */ + { + gtk_table_resize (GTK_TABLE (button), 3, 1); + gtk_table_attach (GTK_TABLE (button), button->button, 0, 1, 1, 2, + GTK_SHRINK, GTK_SHRINK, 0, 0); + gtk_table_attach_defaults (GTK_TABLE (button), + button->line1, 0, 1, 0, 1); + gtk_table_attach_defaults (GTK_TABLE (button), + button->line2, 0, 1, 2, 3); + } + else + { + gtk_table_resize (GTK_TABLE (button), 1, 3); + gtk_table_attach (GTK_TABLE (button), button->button, 1, 2, 0, 1, + GTK_SHRINK, GTK_SHRINK, 0, 0); + gtk_table_attach_defaults (GTK_TABLE (button), + button->line1, 0, 1, 0, 1); + gtk_table_attach_defaults (GTK_TABLE (button), + button->line2, 2, 3, 0, 1); + } + + gtk_widget_show (button->button); + gtk_widget_show (button->line1); + gtk_widget_show (button->line2); +} + +static void +gimp_chain_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpChainButton *button = GIMP_CHAIN_BUTTON (object); + + switch (property_id) + { + case PROP_POSITION: + button->position = g_value_get_enum (value); + break; + + case PROP_ICON_SIZE: + g_object_set_property (G_OBJECT (button->image), "icon-size", value); + break; + + case PROP_ACTIVE: + gimp_chain_button_set_active (button, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_chain_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpChainButton *button = GIMP_CHAIN_BUTTON (object); + + switch (property_id) + { + case PROP_POSITION: + g_value_set_enum (value, button->position); + break; + + case PROP_ICON_SIZE: + g_object_get_property (G_OBJECT (button->image), "icon-size", value); + break; + + case PROP_ACTIVE: + g_value_set_boolean (value, gimp_chain_button_get_active (button)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_chain_button_new: + * @position: The position you are going to use for the button + * with respect to the widgets you want to chain. + * + * Creates a new #GimpChainButton widget. + * + * This returns a button showing either a broken or a linked chain and + * small clamps attached to both sides that visually group the two widgets + * you want to connect. This widget looks best when attached + * to a table taking up two columns (or rows respectively) next + * to the widgets that it is supposed to connect. It may work + * for more than two widgets, but the look is optimized for two. + * + * Returns: Pointer to the new #GimpChainButton, which is inactive + * by default. Use gimp_chain_button_set_active() to + * change its state. + */ +GtkWidget * +gimp_chain_button_new (GimpChainPosition position) +{ + return g_object_new (GIMP_TYPE_CHAIN_BUTTON, + "position", position, + NULL); +} + +/** + * gimp_chain_button_set_icon_size: + * @button: Pointer to a #GimpChainButton. + * @size: The new icon size. + * + * Sets the icon size of the #GimpChainButton. + * + * Since: 2.10.10 + */ +void +gimp_chain_button_set_icon_size (GimpChainButton *button, + GtkIconSize size) +{ + g_return_if_fail (GIMP_IS_CHAIN_BUTTON (button)); + + g_object_set (button, + "icon-size", size, + NULL); +} + +/** + * gimp_chain_button_get_icon_size: + * @button: Pointer to a #GimpChainButton. + * + * Gets the icon size of the #GimpChainButton. + * + * Returns: The icon size. + * + * Since: 2.10.10 + */ +GtkIconSize +gimp_chain_button_get_icon_size (GimpChainButton *button) +{ + GtkIconSize size; + + g_return_val_if_fail (GIMP_IS_CHAIN_BUTTON (button), GTK_ICON_SIZE_BUTTON); + + g_object_get (button, + "icon-size", &size, + NULL); + + return size; +} + +/** + * gimp_chain_button_set_active: + * @button: Pointer to a #GimpChainButton. + * @active: The new state. + * + * Sets the state of the #GimpChainButton to be either locked (%TRUE) or + * unlocked (%FALSE) and changes the showed pixmap to reflect the new state. + */ +void +gimp_chain_button_set_active (GimpChainButton *button, + gboolean active) +{ + g_return_if_fail (GIMP_IS_CHAIN_BUTTON (button)); + + if (button->active != active) + { + button->active = active ? TRUE : FALSE; + + gimp_chain_button_update_image (button); + + g_signal_emit (button, gimp_chain_button_signals[TOGGLED], 0); + + g_object_notify (G_OBJECT (button), "active"); + } +} + +/** + * gimp_chain_button_get_active + * @button: Pointer to a #GimpChainButton. + * + * Checks the state of the #GimpChainButton. + * + * Returns: %TRUE if the #GimpChainButton is active (locked). + */ +gboolean +gimp_chain_button_get_active (GimpChainButton *button) +{ + g_return_val_if_fail (GIMP_IS_CHAIN_BUTTON (button), FALSE); + + return button->active; +} + +static void +gimp_chain_button_clicked_callback (GtkWidget *widget, + GimpChainButton *button) +{ + gimp_chain_button_set_active (button, ! button->active); +} + +static void +gimp_chain_button_update_image (GimpChainButton *button) +{ + guint i; + + i = ((button->position & GIMP_CHAIN_LEFT) << 1) + (button->active ? 0 : 1); + + gtk_image_set_from_icon_name (GTK_IMAGE (button->image), + gimp_chain_icon_names[i], + gimp_chain_button_get_icon_size (button)); +} + + +/* GimpChainLine is a simple no-window widget for drawing the lines. + * + * Originally this used to be a GtkDrawingArea but this turned out to + * be a bad idea. We don't need an extra window to draw on and we also + * don't need any input events. + */ + +static GType gimp_chain_line_get_type (void) G_GNUC_CONST; +static gboolean gimp_chain_line_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +struct _GimpChainLine +{ + GtkWidget parent_instance; + GimpChainPosition position; + gint which; +}; + +typedef struct _GimpChainLine GimpChainLine; +typedef GtkWidgetClass GimpChainLineClass; + +G_DEFINE_TYPE (GimpChainLine, gimp_chain_line, GTK_TYPE_WIDGET) + +static void +gimp_chain_line_class_init (GimpChainLineClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->expose_event = gimp_chain_line_expose_event; +} + +static void +gimp_chain_line_init (GimpChainLine *line) +{ + gtk_widget_set_has_window (GTK_WIDGET (line), FALSE); +} + +static GtkWidget * +gimp_chain_line_new (GimpChainPosition position, + gint which) +{ + GimpChainLine *line = g_object_new (gimp_chain_line_get_type (), NULL); + + line->position = position; + line->which = which; + + return GTK_WIDGET (line); +} + +static gboolean +gimp_chain_line_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkStyle *style = gtk_widget_get_style (widget); + GimpChainLine *line = ((GimpChainLine *) widget); + GtkAllocation allocation; + GdkPoint points[3]; + GimpChainPosition position; + cairo_t *cr; + + gtk_widget_get_allocation (widget, &allocation); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, event->region); + cairo_translate (cr, allocation.x, allocation.y); + cairo_clip (cr); + +#define SHORT_LINE 4 + points[0].x = allocation.width / 2; + points[0].y = allocation.height / 2; + + position = line->position; + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + { + switch (position) + { + case GIMP_CHAIN_TOP: + case GIMP_CHAIN_BOTTOM: + break; + + case GIMP_CHAIN_LEFT: + position = GIMP_CHAIN_RIGHT; + break; + + case GIMP_CHAIN_RIGHT: + position = GIMP_CHAIN_LEFT; + break; + } + } + + switch (position) + { + case GIMP_CHAIN_LEFT: + points[0].x += SHORT_LINE; + points[1].x = points[0].x - SHORT_LINE; + points[1].y = points[0].y; + points[2].x = points[1].x; + points[2].y = (line->which == 1 ? allocation.height - 1 : 0); + break; + + case GIMP_CHAIN_RIGHT: + points[0].x -= SHORT_LINE; + points[1].x = points[0].x + SHORT_LINE; + points[1].y = points[0].y; + points[2].x = points[1].x; + points[2].y = (line->which == 1 ? allocation.height - 1 : 0); + break; + + case GIMP_CHAIN_TOP: + points[0].y += SHORT_LINE; + points[1].x = points[0].x; + points[1].y = points[0].y - SHORT_LINE; + points[2].x = (line->which == 1 ? allocation.width - 1 : 0); + points[2].y = points[1].y; + break; + + case GIMP_CHAIN_BOTTOM: + points[0].y -= SHORT_LINE; + points[1].x = points[0].x; + points[1].y = points[0].y + SHORT_LINE; + points[2].x = (line->which == 1 ? allocation.width - 1 : 0); + points[2].y = points[1].y; + break; + + default: + return FALSE; + } + + cairo_move_to (cr, points[0].x, points[0].y); + cairo_line_to (cr, points[1].x, points[1].y); + cairo_line_to (cr, points[2].x, points[2].y); + + cairo_set_line_width (cr, 2.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); + + cairo_stroke (cr); + + cairo_destroy (cr); + + return TRUE; +} diff --git a/libgimpwidgets/gimpchainbutton.h b/libgimpwidgets/gimpchainbutton.h new file mode 100644 index 0000000..b89f7e9 --- /dev/null +++ b/libgimpwidgets/gimpchainbutton.h @@ -0,0 +1,92 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpchainbutton.h + * Copyright (C) 1999-2000 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* + * This implements a widget derived from GtkTable that visualizes + * it's state with two different pixmaps showing a closed and a + * broken chain. It's intended to be used with the GimpSizeEntry + * widget. The usage is quite similar to the one the GtkToggleButton + * provides. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_CHAIN_BUTTON_H__ +#define __GIMP_CHAIN_BUTTON_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_CHAIN_BUTTON (gimp_chain_button_get_type ()) +#define GIMP_CHAIN_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CHAIN_BUTTON, GimpChainButton)) +#define GIMP_CHAIN_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CHAIN_BUTTON, GimpChainButtonClass)) +#define GIMP_IS_CHAIN_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CHAIN_BUTTON)) +#define GIMP_IS_CHAIN_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CHAIN_BUTTON)) +#define GIMP_CHAIN_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CHAIN_BUTTON, GimpChainButtonClass)) + + +typedef struct _GimpChainButtonClass GimpChainButtonClass; + +struct _GimpChainButton +{ + GtkTable parent_instance; + + GimpChainPosition position; + gboolean active; + + GtkWidget *button; + GtkWidget *line1; + GtkWidget *line2; + GtkWidget *image; +}; + +struct _GimpChainButtonClass +{ + GtkTableClass parent_class; + + void (* toggled) (GimpChainButton *button); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_chain_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_chain_button_new (GimpChainPosition position); + +void gimp_chain_button_set_icon_size (GimpChainButton *button, + GtkIconSize size); +GtkIconSize gimp_chain_button_get_icon_size (GimpChainButton *button); + +void gimp_chain_button_set_active (GimpChainButton *button, + gboolean active); +gboolean gimp_chain_button_get_active (GimpChainButton *button); + + +G_END_DECLS + +#endif /* __GIMP_CHAIN_BUTTON_H__ */ diff --git a/libgimpwidgets/gimpcolorarea.c b/libgimpwidgets/gimpcolorarea.c new file mode 100644 index 0000000..75f69c2 --- /dev/null +++ b/libgimpwidgets/gimpcolorarea.c @@ -0,0 +1,956 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorarea.c + * Copyright (C) 2001-2002 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "gimpwidgetstypes.h" + +#include "gimpcairo-utils.h" +#include "gimpcolorarea.h" +#include "gimpwidgetsutils.h" + + +/** + * SECTION: gimpcolorarea + * @title: GimpColorArea + * @short_description: Displays a #GimpRGB color, optionally with + * alpha-channel. + * + * Displays a #GimpRGB color, optionally with alpha-channel. + **/ + + +#define RGBA_EPSILON 1e-6 +#define DRAG_PREVIEW_SIZE 32 +#define DRAG_ICON_OFFSET -8 + + +enum +{ + COLOR_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_COLOR, + PROP_TYPE, + PROP_DRAG_MASK, + PROP_DRAW_BORDER +}; + + +typedef struct _GimpColorAreaPrivate GimpColorAreaPrivate; + +struct _GimpColorAreaPrivate +{ + GimpColorConfig *config; + GimpColorTransform *transform; +}; + +#define GET_PRIVATE(obj) \ + ((GimpColorAreaPrivate *) gimp_color_area_get_instance_private ((GimpColorArea *) (obj))) + + +static void gimp_color_area_dispose (GObject *object); +static void gimp_color_area_finalize (GObject *object); +static void gimp_color_area_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_color_area_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_color_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_color_area_state_changed (GtkWidget *widget, + GtkStateType previous_state); +static gboolean gimp_color_area_expose (GtkWidget *widget, + GdkEventExpose *event); +static void gimp_color_area_render_buf (GtkWidget *widget, + gboolean insensitive, + GimpColorAreaType type, + guchar *buf, + guint width, + guint height, + guint rowstride, + GimpRGB *color); +static void gimp_color_area_render (GimpColorArea *area); + +static void gimp_color_area_drag_begin (GtkWidget *widget, + GdkDragContext *context); +static void gimp_color_area_drag_end (GtkWidget *widget, + GdkDragContext *context); +static void gimp_color_area_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time); +static void gimp_color_area_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time); + +static void gimp_color_area_create_transform (GimpColorArea *area); +static void gimp_color_area_destroy_transform (GimpColorArea *area); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpColorArea, gimp_color_area, + GTK_TYPE_DRAWING_AREA) + +#define parent_class gimp_color_area_parent_class + +static guint gimp_color_area_signals[LAST_SIGNAL] = { 0 }; + +static const GtkTargetEntry target = { "application/x-color", 0 }; + + +static void +gimp_color_area_class_init (GimpColorAreaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpRGB color; + + gimp_color_area_signals[COLOR_CHANGED] = + g_signal_new ("color-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorAreaClass, color_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_color_area_dispose; + object_class->finalize = gimp_color_area_finalize; + object_class->get_property = gimp_color_area_get_property; + object_class->set_property = gimp_color_area_set_property; + + widget_class->size_allocate = gimp_color_area_size_allocate; + widget_class->state_changed = gimp_color_area_state_changed; + widget_class->expose_event = gimp_color_area_expose; + + widget_class->drag_begin = gimp_color_area_drag_begin; + widget_class->drag_end = gimp_color_area_drag_end; + widget_class->drag_data_received = gimp_color_area_drag_data_received; + widget_class->drag_data_get = gimp_color_area_drag_data_get; + + klass->color_changed = NULL; + + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); + + /** + * GimpColorArea:color: + * + * The color displayed in the color area. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_COLOR, + gimp_param_spec_rgb ("color", + "Color", + "The displayed color", + TRUE, &color, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorArea:type: + * + * The type of the color area. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", + "Type", + "The type of the color area", + GIMP_TYPE_COLOR_AREA_TYPE, + GIMP_COLOR_AREA_FLAT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorArea:drag-type: + * + * The modifier mask that should trigger drags. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_DRAG_MASK, + g_param_spec_flags ("drag-mask", + "Drag Mask", + "The modifier mask that triggers dragging the color", + GDK_TYPE_MODIFIER_TYPE, + 0, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + /** + * GimpColorArea:draw-border: + * + * Whether to draw a thin border in the foreground color around the area. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_DRAW_BORDER, + g_param_spec_boolean ("draw-border", + "Draw Border", + "Whether to draw a thin border in the foreground color around the area", + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_color_area_init (GimpColorArea *area) +{ + area->buf = NULL; + area->width = 0; + area->height = 0; + area->rowstride = 0; + area->draw_border = FALSE; + + gtk_drag_dest_set (GTK_WIDGET (area), + GTK_DEST_DEFAULT_HIGHLIGHT | + GTK_DEST_DEFAULT_MOTION | + GTK_DEST_DEFAULT_DROP, + &target, 1, + GDK_ACTION_COPY); + + gimp_widget_track_monitor (GTK_WIDGET (area), + G_CALLBACK (gimp_color_area_destroy_transform), + NULL); +} + +static void +gimp_color_area_dispose (GObject *object) +{ + GimpColorArea *area = GIMP_COLOR_AREA (object); + + gimp_color_area_set_color_config (area, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_area_finalize (GObject *object) +{ + GimpColorArea *area = GIMP_COLOR_AREA (object); + + g_clear_pointer (&area->buf, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_area_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorArea *area = GIMP_COLOR_AREA (object); + + switch (property_id) + { + case PROP_COLOR: + g_value_set_boxed (value, &area->color); + break; + + case PROP_TYPE: + g_value_set_enum (value, area->type); + break; + + case PROP_DRAW_BORDER: + g_value_set_boolean (value, area->draw_border); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_area_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorArea *area = GIMP_COLOR_AREA (object); + GdkModifierType drag_mask; + + switch (property_id) + { + case PROP_COLOR: + gimp_color_area_set_color (area, g_value_get_boxed (value)); + break; + + case PROP_TYPE: + gimp_color_area_set_type (area, g_value_get_enum (value)); + break; + + case PROP_DRAG_MASK: + drag_mask = g_value_get_flags (value) & (GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK | + GDK_BUTTON3_MASK); + if (drag_mask) + gtk_drag_source_set (GTK_WIDGET (area), + drag_mask, + &target, 1, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + break; + + case PROP_DRAW_BORDER: + gimp_color_area_set_draw_border (area, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpColorArea *area = GIMP_COLOR_AREA (widget); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (allocation->width != area->width || + allocation->height != area->height) + { + area->width = allocation->width; + area->height = allocation->height; + + area->rowstride = area->width * 4 + 4; + + g_free (area->buf); + area->buf = g_new (guchar, area->rowstride * area->height); + + area->needs_render = TRUE; + } +} + +static void +gimp_color_area_state_changed (GtkWidget *widget, + GtkStateType previous_state) +{ + if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE || + previous_state == GTK_STATE_INSENSITIVE) + { + GIMP_COLOR_AREA (widget)->needs_render = TRUE; + } + + if (GTK_WIDGET_CLASS (parent_class)->state_changed) + GTK_WIDGET_CLASS (parent_class)->state_changed (widget, previous_state); +} + +static gboolean +gimp_color_area_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpColorArea *area = GIMP_COLOR_AREA (widget); + GimpColorAreaPrivate *priv = GET_PRIVATE (widget); + GtkStyle *style = gtk_widget_get_style (widget); + cairo_t *cr; + cairo_surface_t *buffer; + + if (! area->buf || ! gtk_widget_is_drawable (widget)) + return FALSE; + + if (area->needs_render) + gimp_color_area_render (area); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + if (! priv->transform) + gimp_color_area_create_transform (area); + + if (priv->transform) + { + const Babl *format = babl_format ("cairo-RGB24"); + guchar *buf = g_new (guchar, area->rowstride * area->height); + guchar *src = area->buf; + guchar *dest = buf; + gint i; + + for (i = 0; i < area->height; i++) + { + gimp_color_transform_process_pixels (priv->transform, + format, src, + format, dest, + area->width); + + src += area->rowstride; + dest += area->rowstride; + } + + buffer = cairo_image_surface_create_for_data (buf, + CAIRO_FORMAT_RGB24, + area->width, + area->height, + area->rowstride); + cairo_surface_set_user_data (buffer, NULL, + buf, (cairo_destroy_func_t) g_free); + } + else + { + buffer = cairo_image_surface_create_for_data (area->buf, + CAIRO_FORMAT_RGB24, + area->width, + area->height, + area->rowstride); + } + + cairo_set_source_surface (cr, buffer, 0.0, 0.0); + cairo_surface_destroy (buffer); + cairo_paint (cr); + + if (priv->config && + (area->color.r < 0.0 || area->color.r > 1.0 || + area->color.g < 0.0 || area->color.g > 1.0 || + area->color.b < 0.0 || area->color.b > 1.0)) + { + gint side = MIN (area->width, area->height) * 2 / 3; + + cairo_move_to (cr, area->width, 0); + cairo_line_to (cr, area->width - side, 0); + cairo_line_to (cr, area->width, side); + cairo_line_to (cr, area->width, 0); + + gimp_cairo_set_source_rgb (cr, &priv->config->out_of_gamut_color); + cairo_fill (cr); + } + + if (area->draw_border) + { + cairo_set_line_width (cr, 1.0); + gdk_cairo_set_source_color (cr, + &style->fg[gtk_widget_get_state (widget)]); + + cairo_rectangle (cr, 0.5, 0.5, area->width - 1, area->height - 1); + cairo_stroke (cr); + } + + cairo_destroy (cr); + + return FALSE; +} + +/** + * gimp_color_area_new: + * @color: A pointer to a #GimpRGB struct. + * @type: The type of color area to create. + * @drag_mask: The event_mask that should trigger drags. + * + * Creates a new #GimpColorArea widget. + * + * This returns a preview area showing the color. It handles color + * DND. If the color changes, the "color_changed" signal is emitted. + * + * Returns: Pointer to the new #GimpColorArea widget. + **/ +GtkWidget * +gimp_color_area_new (const GimpRGB *color, + GimpColorAreaType type, + GdkModifierType drag_mask) +{ + return g_object_new (GIMP_TYPE_COLOR_AREA, + "color", color, + "type", type, + "drag-mask", drag_mask, + NULL); +} + +/** + * gimp_color_area_set_color: + * @area: Pointer to a #GimpColorArea. + * @color: Pointer to a #GimpRGB struct that defines the new color. + * + * Sets @area to a different @color. + **/ +void +gimp_color_area_set_color (GimpColorArea *area, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_AREA (area)); + g_return_if_fail (color != NULL); + + if (gimp_rgba_distance (&area->color, color) < RGBA_EPSILON) + return; + + area->color = *color; + + area->needs_render = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (area)); + + g_object_notify (G_OBJECT (area), "color"); + + g_signal_emit (area, gimp_color_area_signals[COLOR_CHANGED], 0); +} + +/** + * gimp_color_area_get_color: + * @area: Pointer to a #GimpColorArea. + * @color: Pointer to a #GimpRGB struct that is used to return the color. + * + * Retrieves the current color of the @area. + **/ +void +gimp_color_area_get_color (GimpColorArea *area, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_AREA (area)); + g_return_if_fail (color != NULL); + + *color = area->color; +} + +/** + * gimp_color_area_has_alpha: + * @area: Pointer to a #GimpColorArea. + * + * Checks whether the @area shows transparency information. This is determined + * via the @area's #GimpColorAreaType. + * + * Returns: %TRUE if @area shows transparency information, %FALSE otherwise. + **/ +gboolean +gimp_color_area_has_alpha (GimpColorArea *area) +{ + g_return_val_if_fail (GIMP_IS_COLOR_AREA (area), FALSE); + + return area->type != GIMP_COLOR_AREA_FLAT; +} + +/** + * gimp_color_area_set_type: + * @area: Pointer to a #GimpColorArea. + * @type: A #GimpColorAreaType. + * + * Changes the type of @area. The #GimpColorAreaType determines + * whether the widget shows transparency information and chooses the + * size of the checkerboard used to do that. + **/ +void +gimp_color_area_set_type (GimpColorArea *area, + GimpColorAreaType type) +{ + g_return_if_fail (GIMP_IS_COLOR_AREA (area)); + + if (area->type != type) + { + area->type = type; + + area->needs_render = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (area)); + + g_object_notify (G_OBJECT (area), "type"); + } +} + +/** + * gimp_color_area_set_draw_border: + * @area: Pointer to a #GimpColorArea. + * @draw_border: whether to draw a border or not + * + * The @area can draw a thin border in the foreground color around + * itself. This function toggles this behaviour on and off. The + * default is not draw a border. + **/ +void +gimp_color_area_set_draw_border (GimpColorArea *area, + gboolean draw_border) +{ + g_return_if_fail (GIMP_IS_COLOR_AREA (area)); + + draw_border = draw_border ? TRUE : FALSE; + + if (area->draw_border != draw_border) + { + area->draw_border = draw_border; + + gtk_widget_queue_draw (GTK_WIDGET (area)); + + g_object_notify (G_OBJECT (area), "draw-border"); + } +} + +/** + * gimp_color_area_set_color_config: + * @area: a #GimpColorArea widget. + * @config: a #GimpColorConfig object. + * + * Sets the color management configuration to use with this color area. + * + * Since: 2.10 + */ +void +gimp_color_area_set_color_config (GimpColorArea *area, + GimpColorConfig *config) +{ + GimpColorAreaPrivate *priv; + + g_return_if_fail (GIMP_IS_COLOR_AREA (area)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + priv = GET_PRIVATE (area); + + if (config != priv->config) + { + if (priv->config) + { + g_signal_handlers_disconnect_by_func (priv->config, + gimp_color_area_destroy_transform, + area); + + gimp_color_area_destroy_transform (area); + } + + g_set_object (&priv->config, config); + + if (priv->config) + { + g_signal_connect_swapped (priv->config, "notify", + G_CALLBACK (gimp_color_area_destroy_transform), + area); + } + } +} + + +/* private functions */ + +static void +gimp_color_area_render_buf (GtkWidget *widget, + gboolean insensitive, + GimpColorAreaType type, + guchar *buf, + guint width, + guint height, + guint rowstride, + GimpRGB *color) +{ + GtkStyle *style = gtk_widget_get_style (widget); + guint x, y; + guint check_size = 0; + guchar light[3]; + guchar dark[3]; + guchar opaque[3]; + guchar insens[3]; + guchar *p; + gdouble frac; + + switch (type) + { + case GIMP_COLOR_AREA_FLAT: + check_size = 0; + break; + + case GIMP_COLOR_AREA_SMALL_CHECKS: + check_size = GIMP_CHECK_SIZE_SM; + break; + + case GIMP_COLOR_AREA_LARGE_CHECKS: + check_size = GIMP_CHECK_SIZE; + break; + } + + gimp_rgb_get_uchar (color, opaque, opaque + 1, opaque + 2); + + insens[0] = style->bg[GTK_STATE_INSENSITIVE].red >> 8; + insens[1] = style->bg[GTK_STATE_INSENSITIVE].green >> 8; + insens[2] = style->bg[GTK_STATE_INSENSITIVE].blue >> 8; + + if (insensitive || check_size == 0 || color->a == 1.0) + { + for (y = 0; y < height; y++) + { + p = buf + y * rowstride; + + for (x = 0; x < width; x++) + { + if (insensitive && ((x + y) % 2)) + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + insens[0], + insens[1], + insens[2]); + } + else + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + opaque[0], + opaque[1], + opaque[2]); + } + + p += 4; + } + } + + return; + } + + light[0] = (GIMP_CHECK_LIGHT + + (color->r - GIMP_CHECK_LIGHT) * color->a) * 255.999; + light[1] = (GIMP_CHECK_LIGHT + + (color->g - GIMP_CHECK_LIGHT) * color->a) * 255.999; + light[2] = (GIMP_CHECK_LIGHT + + (color->b - GIMP_CHECK_LIGHT) * color->a) * 255.999; + + dark[0] = (GIMP_CHECK_DARK + + (color->r - GIMP_CHECK_DARK) * color->a) * 255.999; + dark[1] = (GIMP_CHECK_DARK + + (color->g - GIMP_CHECK_DARK) * color->a) * 255.999; + dark[2] = (GIMP_CHECK_DARK + + (color->b - GIMP_CHECK_DARK) * color->a) * 255.999; + + for (y = 0; y < height; y++) + { + p = buf + y * rowstride; + + for (x = 0; x < width; x++) + { + if ((width - x) * height > y * width) + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + opaque[0], + opaque[1], + opaque[2]); + p += 4; + + continue; + } + + frac = y - (gdouble) ((width - x) * height) / (gdouble) width; + + if (((x / check_size) ^ (y / check_size)) & 1) + { + if ((gint) frac) + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + light[0], + light[1], + light[2]); + } + else + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + ((gdouble) light[0] * frac + + (gdouble) opaque[0] * (1.0 - frac)), + ((gdouble) light[1] * frac + + (gdouble) opaque[1] * (1.0 - frac)), + ((gdouble) light[2] * frac + + (gdouble) opaque[2] * (1.0 - frac))); + } + } + else + { + if ((gint) frac) + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + dark[0], + dark[1], + dark[2]); + } + else + { + GIMP_CAIRO_RGB24_SET_PIXEL (p, + ((gdouble) dark[0] * frac + + (gdouble) opaque[0] * (1.0 - frac)), + ((gdouble) dark[1] * frac + + (gdouble) opaque[1] * (1.0 - frac)), + ((gdouble) dark[2] * frac + + (gdouble) opaque[2] * (1.0 - frac))); + } + } + + p += 4; + } + } +} + +static void +gimp_color_area_render (GimpColorArea *area) +{ + if (! area->buf) + return; + + gimp_color_area_render_buf (GTK_WIDGET (area), + ! gtk_widget_is_sensitive (GTK_WIDGET (area)), + area->type, + area->buf, + area->width, area->height, area->rowstride, + &area->color); + + area->needs_render = FALSE; +} + +static void +gimp_color_area_drag_begin (GtkWidget *widget, + GdkDragContext *context) +{ + GimpRGB color; + GtkWidget *window; + GtkWidget *frame; + GtkWidget *color_area; + + window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND); + gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (widget)); + + gtk_widget_realize (window); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (window), frame); + + gimp_color_area_get_color (GIMP_COLOR_AREA (widget), &color); + + color_area = gimp_color_area_new (&color, + GIMP_COLOR_AREA (widget)->type, + 0); + + gtk_widget_set_size_request (color_area, + DRAG_PREVIEW_SIZE, DRAG_PREVIEW_SIZE); + gtk_container_add (GTK_CONTAINER (frame), color_area); + gtk_widget_show (color_area); + gtk_widget_show (frame); + + g_object_set_data_full (G_OBJECT (widget), + "gimp-color-area-drag-window", + window, + (GDestroyNotify) gtk_widget_destroy); + + gtk_drag_set_icon_widget (context, window, + DRAG_ICON_OFFSET, DRAG_ICON_OFFSET); +} + +static void +gimp_color_area_drag_end (GtkWidget *widget, + GdkDragContext *context) +{ + g_object_set_data (G_OBJECT (widget), + "gimp-color-area-drag-window", NULL); +} + +static void +gimp_color_area_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GimpColorArea *area = GIMP_COLOR_AREA (widget); + const guint16 *vals; + GimpRGB color; + + if (gtk_selection_data_get_length (selection_data) != 8 || + gtk_selection_data_get_format (selection_data) != 16) + { + g_warning ("%s: received invalid color data", G_STRFUNC); + return; + } + + vals = (const guint16 *) gtk_selection_data_get_data (selection_data); + + gimp_rgba_set (&color, + (gdouble) vals[0] / 0xffff, + (gdouble) vals[1] / 0xffff, + (gdouble) vals[2] / 0xffff, + (gdouble) vals[3] / 0xffff); + + gimp_color_area_set_color (area, &color); +} + +static void +gimp_color_area_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GimpColorArea *area = GIMP_COLOR_AREA (widget); + guint16 vals[4]; + + vals[0] = area->color.r * 0xffff; + vals[1] = area->color.g * 0xffff; + vals[2] = area->color.b * 0xffff; + + if (area->type == GIMP_COLOR_AREA_FLAT) + vals[3] = 0xffff; + else + vals[3] = area->color.a * 0xffff; + + gtk_selection_data_set (selection_data, + gdk_atom_intern ("application/x-color", FALSE), + 16, (guchar *) vals, 8); +} + +static void +gimp_color_area_create_transform (GimpColorArea *area) +{ + GimpColorAreaPrivate *priv = GET_PRIVATE (area); + + if (priv->config) + { + static GimpColorProfile *profile = NULL; + + const Babl *format = babl_format ("cairo-RGB24"); + + if (G_UNLIKELY (! profile)) + profile = gimp_color_profile_new_rgb_srgb (); + + priv->transform = gimp_widget_get_color_transform (GTK_WIDGET (area), + priv->config, + profile, + format, + format); + } +} + +static void +gimp_color_area_destroy_transform (GimpColorArea *area) +{ + GimpColorAreaPrivate *priv = GET_PRIVATE (area); + + g_clear_object (&priv->transform); + + gtk_widget_queue_draw (GTK_WIDGET (area)); +} diff --git a/libgimpwidgets/gimpcolorarea.h b/libgimpwidgets/gimpcolorarea.h new file mode 100644 index 0000000..f5d19fb --- /dev/null +++ b/libgimpwidgets/gimpcolorarea.h @@ -0,0 +1,100 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorarea.h + * Copyright (C) 2001-2002 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* This provides a color preview area. The preview + * can handle transparency by showing the checkerboard and + * handles drag'n'drop. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_AREA_H__ +#define __GIMP_COLOR_AREA_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_AREA (gimp_color_area_get_type ()) +#define GIMP_COLOR_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_AREA, GimpColorArea)) +#define GIMP_COLOR_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_AREA, GimpColorAreaClass)) +#define GIMP_IS_COLOR_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_AREA)) +#define GIMP_IS_COLOR_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_AREA)) +#define GIMP_COLOR_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_AREA, GimpColorAreaClass)) + + +typedef struct _GimpColorAreaClass GimpColorAreaClass; + +struct _GimpColorArea +{ + GtkDrawingArea parent_instance; + + /*< private >*/ + guchar *buf; + guint width; + guint height; + guint rowstride; + + GimpColorAreaType type; + GimpRGB color; + guint draw_border : 1; + guint needs_render : 1; +}; + +struct _GimpColorAreaClass +{ + GtkDrawingAreaClass parent_class; + + void (* color_changed) (GimpColorArea *area); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_area_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_area_new (const GimpRGB *color, + GimpColorAreaType type, + GdkModifierType drag_mask); + +void gimp_color_area_set_color (GimpColorArea *area, + const GimpRGB *color); +void gimp_color_area_get_color (GimpColorArea *area, + GimpRGB *color); + +gboolean gimp_color_area_has_alpha (GimpColorArea *area); +void gimp_color_area_set_type (GimpColorArea *area, + GimpColorAreaType type); +void gimp_color_area_set_draw_border (GimpColorArea *area, + gboolean draw_border); + +void gimp_color_area_set_color_config (GimpColorArea *area, + GimpColorConfig *config); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_AREA_H__ */ diff --git a/libgimpwidgets/gimpcolorbutton.c b/libgimpwidgets/gimpcolorbutton.c new file mode 100644 index 0000000..6ec562e --- /dev/null +++ b/libgimpwidgets/gimpcolorbutton.c @@ -0,0 +1,1014 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorbutton.c + * Copyright (C) 1999-2001 Sven Neumann + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorarea.h" +#include "gimpcolorbutton.h" +#include "gimpcolornotebook.h" +#include "gimpcolorselection.h" +#include "gimpdialog.h" +#include "gimphelpui.h" +#include "gimpicons.h" +#include "gimpwidgets-private.h" +#include "gimp3migration.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorbutton + * @title: GimpColorButton + * @short_description: Widget for selecting a color from a simple button. + * @see_also: #libgimpcolor-gimpcolorspace + * + * This widget provides a simple button with a preview showing the + * color. + * + * On click a color selection dialog is opened. Additionally the + * button supports Drag and Drop and has a right-click menu that + * allows one to choose the color from the current FG or BG color. If + * the user changes the color, the "color-changed" signal is emitted. + **/ + + +#define COLOR_BUTTON_KEY "gimp-color-button" +#define RESPONSE_RESET 1 + +#define TODOUBLE(i) (i / 65535.0) +#define TOUINT16(d) ((guint16) (d * 65535 + 0.5)) + + +#define GIMP_COLOR_BUTTON_COLOR_FG "color-button-use-foreground" +#define GIMP_COLOR_BUTTON_COLOR_BG "color-button-use-background" +#define GIMP_COLOR_BUTTON_COLOR_BLACK "color-button-use-black" +#define GIMP_COLOR_BUTTON_COLOR_WHITE "color-button-use-white" + + +enum +{ + COLOR_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_TITLE, + PROP_COLOR, + PROP_TYPE, + PROP_UPDATE, + PROP_AREA_WIDTH, + PROP_AREA_HEIGHT, + PROP_COLOR_CONFIG +}; + + +typedef struct _GimpColorButtonPrivate GimpColorButtonPrivate; + +struct _GimpColorButtonPrivate +{ + GtkWidget *selection; + + GimpColorConfig *config; +}; + +#define GET_PRIVATE(obj) (gimp_color_button_get_instance_private (GIMP_COLOR_BUTTON (obj))) + + +static void gimp_color_button_constructed (GObject *object); +static void gimp_color_button_finalize (GObject *object); +static void gimp_color_button_dispose (GObject *object); +static void gimp_color_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_color_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static gboolean gimp_color_button_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static void gimp_color_button_state_changed (GtkWidget *widget, + GtkStateType prev_state); +static void gimp_color_button_clicked (GtkButton *button); +static GType gimp_color_button_get_action_type (GimpColorButton *button); + +static void gimp_color_button_dialog_response (GtkWidget *dialog, + gint response_id, + GimpColorButton *button); +static void gimp_color_button_use_color (GtkAction *action, + GimpColorButton *button); +static void gimp_color_button_area_changed (GtkWidget *color_area, + GimpColorButton *button); +static void gimp_color_button_selection_changed (GtkWidget *selection, + GimpColorButton *button); +static void gimp_color_button_help_func (const gchar *help_id, + gpointer help_data); + + +static const GtkActionEntry actions[] = +{ + { "color-button-popup", NULL, + "Color Button Menu", NULL, NULL, + NULL + }, + + { GIMP_COLOR_BUTTON_COLOR_FG, NULL, + N_("_Foreground Color"), NULL, NULL, + G_CALLBACK (gimp_color_button_use_color) + }, + { GIMP_COLOR_BUTTON_COLOR_BG, NULL, + N_("_Background Color"), NULL, NULL, + G_CALLBACK (gimp_color_button_use_color) + }, + { GIMP_COLOR_BUTTON_COLOR_BLACK, NULL, + N_("Blac_k"), NULL, NULL, + G_CALLBACK (gimp_color_button_use_color) + }, + { GIMP_COLOR_BUTTON_COLOR_WHITE, NULL, + N_("_White"), NULL, NULL, + G_CALLBACK (gimp_color_button_use_color) + } +}; + + +G_DEFINE_TYPE_WITH_CODE (GimpColorButton, gimp_color_button, GIMP_TYPE_BUTTON, + G_ADD_PRIVATE (GimpColorButton)) + +#define parent_class gimp_color_button_parent_class + +static guint gimp_color_button_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_color_button_class_init (GimpColorButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + GimpRGB color; + + parent_class = g_type_class_peek_parent (klass); + + gimp_color_button_signals[COLOR_CHANGED] = + g_signal_new ("color-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorButtonClass, color_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->constructed = gimp_color_button_constructed; + object_class->finalize = gimp_color_button_finalize; + object_class->dispose = gimp_color_button_dispose; + object_class->get_property = gimp_color_button_get_property; + object_class->set_property = gimp_color_button_set_property; + + widget_class->button_press_event = gimp_color_button_button_press; + widget_class->state_changed = gimp_color_button_state_changed; + + button_class->clicked = gimp_color_button_clicked; + + klass->color_changed = NULL; + klass->get_action_type = gimp_color_button_get_action_type; + + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); + + /** + * GimpColorButton:title: + * + * The title to be used for the color selection dialog. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_TITLE, + g_param_spec_string ("title", + "Title", + "The title to be used for the color selection dialog", + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorButton:color: + * + * The color displayed in the button's color area. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_COLOR, + gimp_param_spec_rgb ("color", + "Color", + "The color displayed in the button's color area", + TRUE, &color, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorButton:type: + * + * The type of the button's color area. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("type", + "Type", + "The type of the button's color area", + GIMP_TYPE_COLOR_AREA_TYPE, + GIMP_COLOR_AREA_FLAT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorButton:continuous-update: + * + * The update policy of the color button. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_UPDATE, + g_param_spec_boolean ("continuous-update", + "Contiguous Update", + "The update policy of the color button", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorButton:area-width: + * + * The minimum width of the button's #GimpColorArea. + * + * Since: 2.8 + */ + g_object_class_install_property (object_class, PROP_AREA_WIDTH, + g_param_spec_int ("area-width", + "Area Width", + "The minimum width of the button's GimpColorArea", + 1, G_MAXINT, 16, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorButton:area-height: + * + * The minimum height of the button's #GimpColorArea. + * + * Since: 2.8 + */ + g_object_class_install_property (object_class, PROP_AREA_HEIGHT, + g_param_spec_int ("area-height", + "Area Height", + "The minimum height of the button's GimpColorArea", + 1, G_MAXINT, 16, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + /** + * GimpColorButton:color-config: + * + * The #GimpColorConfig object used for the button's #GimpColorArea + * and #GimpColorSelection. + * + * Since: 2.10 + */ + g_object_class_install_property (object_class, PROP_COLOR_CONFIG, + g_param_spec_object ("color-config", + "Color Config", + "The color config object used", + GIMP_TYPE_COLOR_CONFIG, + G_PARAM_READWRITE)); +} + +static void +gimp_color_button_init (GimpColorButton *button) +{ + button->color_area = g_object_new (GIMP_TYPE_COLOR_AREA, + "drag-mask", GDK_BUTTON1_MASK, + NULL); + + g_signal_connect (button->color_area, "color-changed", + G_CALLBACK (gimp_color_button_area_changed), + button); + + gtk_container_add (GTK_CONTAINER (button), button->color_area); + gtk_widget_show (button->color_area); +} + +static void +gimp_color_button_constructed (GObject *object) +{ + GimpColorButton *button = GIMP_COLOR_BUTTON (object); + GimpColorButtonClass *klass = GIMP_COLOR_BUTTON_GET_CLASS (object); + GtkUIManager *ui_manager; + GtkActionGroup *group; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + /* right-click opens a popup */ + button->popup_menu = ui_manager = gtk_ui_manager_new (); + + group = gtk_action_group_new ("color-button"); + + for (i = 0; i < G_N_ELEMENTS (actions); i++) + { + const gchar *label = gettext (actions[i].label); + const gchar *tooltip = gettext (actions[i].tooltip); + GtkAction *action; + + action = g_object_new (klass->get_action_type (button), + "name", actions[i].name, + "label", label, + "tooltip", tooltip, + "icon-name", actions[i].stock_id, + NULL); + + if (actions[i].callback) + g_signal_connect (action, "activate", + actions[i].callback, + button); + + gtk_action_group_add_action_with_accel (group, action, + actions[i].accelerator); + + g_object_unref (action); + } + + gtk_ui_manager_insert_action_group (ui_manager, group, -1); + g_object_unref (group); + + gtk_ui_manager_add_ui_from_string + (ui_manager, + "<ui>\n" + " <popup action=\"color-button-popup\">\n" + " <menuitem action=\"" GIMP_COLOR_BUTTON_COLOR_FG "\" />\n" + " <menuitem action=\"" GIMP_COLOR_BUTTON_COLOR_BG "\" />\n" + " <separator />\n" + " <menuitem action=\"" GIMP_COLOR_BUTTON_COLOR_BLACK "\" />\n" + " <menuitem action=\"" GIMP_COLOR_BUTTON_COLOR_WHITE "\" />\n" + " </popup>\n" + "</ui>\n", + -1, NULL); +} + +static void +gimp_color_button_finalize (GObject *object) +{ + GimpColorButton *button = GIMP_COLOR_BUTTON (object); + + g_clear_pointer (&button->title, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_button_dispose (GObject *object) +{ + GimpColorButton *button = GIMP_COLOR_BUTTON (object); + GimpColorButtonPrivate *priv = GET_PRIVATE (button); + + g_clear_pointer (&button->dialog, gtk_widget_destroy); + priv->selection = NULL; + + g_clear_pointer (&button->color_area, gtk_widget_destroy); + + g_clear_object (&button->popup_menu); + + gimp_color_button_set_color_config (button, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorButton *button = GIMP_COLOR_BUTTON (object); + GimpColorButtonPrivate *priv = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_TITLE: + g_value_set_string (value, button->title); + break; + + case PROP_COLOR: + g_object_get_property (G_OBJECT (button->color_area), "color", value); + break; + + case PROP_TYPE: + g_object_get_property (G_OBJECT (button->color_area), "type", value); + break; + + case PROP_UPDATE: + g_value_set_boolean (value, button->continuous_update); + break; + + case PROP_COLOR_CONFIG: + g_value_set_object (value, priv->config); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorButton *button = GIMP_COLOR_BUTTON (object); + gint other; + + switch (property_id) + { + case PROP_TITLE: + gimp_color_button_set_title (button, g_value_get_string (value)); + break; + + case PROP_COLOR: + g_object_set_property (G_OBJECT (button->color_area), "color", value); + break; + + case PROP_TYPE: + g_object_set_property (G_OBJECT (button->color_area), "type", value); + break; + + case PROP_UPDATE: + gimp_color_button_set_update (button, g_value_get_boolean (value)); + break; + + case PROP_AREA_WIDTH: + gtk_widget_get_size_request (button->color_area, NULL, &other); + gtk_widget_set_size_request (button->color_area, + g_value_get_int (value), other); + break; + + case PROP_AREA_HEIGHT: + gtk_widget_get_size_request (button->color_area, &other, NULL); + gtk_widget_set_size_request (button->color_area, + other, g_value_get_int (value)); + break; + + case PROP_COLOR_CONFIG: + gimp_color_button_set_color_config (button, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_color_button_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpColorButton *button = GIMP_COLOR_BUTTON (widget); + + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + GtkWidget *menu = gtk_ui_manager_get_widget (button->popup_menu, + "/color-button-popup"); + + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget)); + + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, + bevent->button, bevent->time); + } + + if (GTK_WIDGET_CLASS (parent_class)->button_press_event) + return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent); + + return FALSE; +} + +static void +gimp_color_button_state_changed (GtkWidget *widget, + GtkStateType prev_state) +{ + g_return_if_fail (GIMP_IS_COLOR_BUTTON (widget)); + + if (! gtk_widget_is_sensitive (widget) && GIMP_COLOR_BUTTON (widget)->dialog) + gtk_widget_hide (GIMP_COLOR_BUTTON (widget)->dialog); + + if (GTK_WIDGET_CLASS (parent_class)->state_changed) + GTK_WIDGET_CLASS (parent_class)->state_changed (widget, prev_state); +} + +static void +gimp_color_button_clicked (GtkButton *button) +{ + GimpColorButton *color_button = GIMP_COLOR_BUTTON (button); + GimpColorButtonPrivate *priv = GET_PRIVATE (button); + GimpRGB color; + + if (! color_button->dialog) + { + GtkWidget *dialog; + + dialog = color_button->dialog = + gimp_dialog_new (color_button->title, "gimp-color-selection", + gtk_widget_get_toplevel (GTK_WIDGET (button)), 0, + gimp_color_button_help_func, NULL, + + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + g_object_set_data (G_OBJECT (dialog), COLOR_BUTTON_KEY, button); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_color_button_dialog_response), + color_button); + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &color_button->dialog); + + priv->selection = gimp_color_selection_new (); + gtk_container_set_border_width (GTK_CONTAINER (priv->selection), 6); + gimp_color_selection_set_show_alpha (GIMP_COLOR_SELECTION (priv->selection), + gimp_color_button_has_alpha (color_button)); + gimp_color_selection_set_config (GIMP_COLOR_SELECTION (priv->selection), + priv->config); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + priv->selection, TRUE, TRUE, 0); + gtk_widget_show (priv->selection); + + g_signal_connect (priv->selection, "color-changed", + G_CALLBACK (gimp_color_button_selection_changed), + button); + } + + gimp_color_button_get_color (color_button, &color); + + g_signal_handlers_block_by_func (priv->selection, + gimp_color_button_selection_changed, + button); + + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (priv->selection), &color); + gimp_color_selection_set_old_color (GIMP_COLOR_SELECTION (priv->selection), + &color); + + g_signal_handlers_unblock_by_func (priv->selection, + gimp_color_button_selection_changed, + button); + + gtk_window_present (GTK_WINDOW (color_button->dialog)); +} + +static GType +gimp_color_button_get_action_type (GimpColorButton *button) +{ + return GTK_TYPE_ACTION; +} + + +/* public functions */ + +/** + * gimp_color_button_new: + * @title: String that will be used as title for the color_selector. + * @width: Width of the colorpreview in pixels. + * @height: Height of the colorpreview in pixels. + * @color: A pointer to a #GimpRGB color. + * @type: The type of transparency to be displayed. + * + * Creates a new #GimpColorButton widget. + * + * This returns a button with a preview showing the color. + * When the button is clicked a GtkColorSelectionDialog is opened. + * If the user changes the color the new color is written into the + * array that was used to pass the initial color and the "color-changed" + * signal is emitted. + * + * Returns: Pointer to the new #GimpColorButton widget. + **/ +GtkWidget * +gimp_color_button_new (const gchar *title, + gint width, + gint height, + const GimpRGB *color, + GimpColorAreaType type) +{ + g_return_val_if_fail (color != NULL, NULL); + g_return_val_if_fail (width > 0, NULL); + g_return_val_if_fail (height > 0, NULL); + + return g_object_new (GIMP_TYPE_COLOR_BUTTON, + "title", title, + "type", type, + "color", color, + "area-width", width, + "area-height", height, + NULL); +} + +/** + * gimp_color_button_set_title: + * @button: a #GimpColorButton. + * @title: the new title. + * + * Sets the @button dialog's title. + * + * Since: 2.10 + **/ +void +gimp_color_button_set_title (GimpColorButton *button, + const gchar *title) +{ + g_return_if_fail (GIMP_IS_COLOR_BUTTON (button)); + g_return_if_fail (title != NULL); + + g_free (button->title); + button->title = g_strdup (title); + + if (button->dialog) + gtk_window_set_title (GTK_WINDOW (button->dialog), title); + + g_object_notify (G_OBJECT (button), "title"); +} + +/** + * gimp_color_button_get_title: + * @button: a #GimpColorButton. + * + * Returns: The @button dialog's title. + * + * Since: 2.10 + **/ +const gchar * +gimp_color_button_get_title (GimpColorButton *button) +{ + g_return_val_if_fail (GIMP_IS_COLOR_BUTTON (button), NULL); + + return button->title; +} + +/** + * gimp_color_button_set_color: + * @button: Pointer to a #GimpColorButton. + * @color: Pointer to the new #GimpRGB color. + * + * Sets the @button to the given @color. + **/ +void +gimp_color_button_set_color (GimpColorButton *button, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_BUTTON (button)); + g_return_if_fail (color != NULL); + + gimp_color_area_set_color (GIMP_COLOR_AREA (button->color_area), color); + + g_object_notify (G_OBJECT (button), "color"); +} + +/** + * gimp_color_button_get_color: + * @button: Pointer to a #GimpColorButton. + * @color: Pointer to a #GimpRGB struct used to return the color. + * + * Retrieves the currently set color from the @button. + **/ +void +gimp_color_button_get_color (GimpColorButton *button, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_BUTTON (button)); + g_return_if_fail (color != NULL); + + gimp_color_area_get_color (GIMP_COLOR_AREA (button->color_area), color); +} + +/** + * gimp_color_button_has_alpha: + * @button: Pointer to a #GimpColorButton. + * + * Checks whether the @buttons shows transparency information. + * + * Returns: %TRUE if the @button shows transparency information, %FALSE + * otherwise. + **/ +gboolean +gimp_color_button_has_alpha (GimpColorButton *button) +{ + g_return_val_if_fail (GIMP_IS_COLOR_BUTTON (button), FALSE); + + return gimp_color_area_has_alpha (GIMP_COLOR_AREA (button->color_area)); +} + +/** + * gimp_color_button_set_type: + * @button: Pointer to a #GimpColorButton. + * @type: the new #GimpColorAreaType + * + * Sets the @button to the given @type. See also gimp_color_area_set_type(). + **/ +void +gimp_color_button_set_type (GimpColorButton *button, + GimpColorAreaType type) +{ + g_return_if_fail (GIMP_IS_COLOR_BUTTON (button)); + + gimp_color_area_set_type (GIMP_COLOR_AREA (button->color_area), type); + + g_object_notify (G_OBJECT (button), "type"); +} + +/** + * gimp_color_button_get_update: + * @button: A #GimpColorButton widget. + * + * Returns the color button's @continuous_update property. + * + * Return value: the @continuous_update property. + **/ +gboolean +gimp_color_button_get_update (GimpColorButton *button) +{ + g_return_val_if_fail (GIMP_IS_COLOR_BUTTON (button), FALSE); + + return button->continuous_update; +} + +/** + * gimp_color_button_set_update: + * @button: A #GimpColorButton widget. + * @continuous: The new setting of the @continuous_update property. + * + * When set to #TRUE, the @button will emit the "color-changed" + * continuously while the color is changed in the color selection + * dialog. + **/ +void +gimp_color_button_set_update (GimpColorButton *button, + gboolean continuous) +{ + GimpColorButtonPrivate *priv; + + g_return_if_fail (GIMP_IS_COLOR_BUTTON (button)); + + priv = GET_PRIVATE (button); + + if (continuous != button->continuous_update) + { + button->continuous_update = continuous ? TRUE : FALSE; + + if (priv->selection) + { + GimpRGB color; + + if (button->continuous_update) + { + gimp_color_selection_get_color (GIMP_COLOR_SELECTION (priv->selection), + &color); + gimp_color_button_set_color (button, &color); + } + else + { + gimp_color_selection_get_old_color (GIMP_COLOR_SELECTION (priv->selection), + &color); + gimp_color_button_set_color (button, &color); + } + } + + g_object_notify (G_OBJECT (button), "continuous-update"); + } +} + +/** + * gimp_color_button_set_color_config: + * @button: a #GimpColorButton widget. + * @config: a #GimpColorConfig object. + * + * Sets the color management configuration to use with this color button's + * #GimpColorArea. + * + * Since: 2.10 + */ +void +gimp_color_button_set_color_config (GimpColorButton *button, + GimpColorConfig *config) +{ + GimpColorButtonPrivate *priv; + + g_return_if_fail (GIMP_IS_COLOR_BUTTON (button)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + priv = GET_PRIVATE (button); + + if (g_set_object (&priv->config, config)) + { + if (button->color_area) + gimp_color_area_set_color_config (GIMP_COLOR_AREA (button->color_area), + priv->config); + + if (priv->selection) + gimp_color_selection_set_config (GIMP_COLOR_SELECTION (priv->selection), + priv->config); + } +} + +/** + * gimp_color_button_get_ui_manager: + * @button: a #GimpColorButton. + * + * Returns: The @button's #GtkUIManager. + * + * Since: 2.10 + **/ +GtkUIManager * +gimp_color_button_get_ui_manager (GimpColorButton *button) +{ + g_return_val_if_fail (GIMP_IS_COLOR_BUTTON (button), NULL); + + return button->popup_menu; +} + + +/* private functions */ + +static void +gimp_color_button_dialog_response (GtkWidget *dialog, + gint response_id, + GimpColorButton *button) +{ + GimpColorButtonPrivate *priv = GET_PRIVATE (button); + GimpRGB color; + + switch (response_id) + { + case RESPONSE_RESET: + gimp_color_selection_reset (GIMP_COLOR_SELECTION (priv->selection)); + break; + + case GTK_RESPONSE_OK: + if (! button->continuous_update) + { + gimp_color_selection_get_color (GIMP_COLOR_SELECTION (priv->selection), + &color); + gimp_color_button_set_color (button, &color); + } + + gtk_widget_hide (dialog); + break; + + default: + if (button->continuous_update) + { + gimp_color_selection_get_old_color (GIMP_COLOR_SELECTION (priv->selection), + &color); + gimp_color_button_set_color (button, &color); + } + + gtk_widget_hide (dialog); + break; + } +} + +static void +gimp_color_button_use_color (GtkAction *action, + GimpColorButton *button) +{ + const gchar *name; + GimpRGB color; + + name = gtk_action_get_name (action); + gimp_color_button_get_color (button, &color); + + if (! strcmp (name, GIMP_COLOR_BUTTON_COLOR_FG)) + { + if (_gimp_get_foreground_func) + _gimp_get_foreground_func (&color); + else + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); + } + else if (! strcmp (name, GIMP_COLOR_BUTTON_COLOR_BG)) + { + if (_gimp_get_background_func) + _gimp_get_background_func (&color); + else + gimp_rgba_set (&color, 1.0, 1.0, 1.0, 1.0); + } + else if (! strcmp (name, GIMP_COLOR_BUTTON_COLOR_BLACK)) + { + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); + } + else if (! strcmp (name, GIMP_COLOR_BUTTON_COLOR_WHITE)) + { + gimp_rgba_set (&color, 1.0, 1.0, 1.0, 1.0); + } + + gimp_color_button_set_color (button, &color); +} + +static void +gimp_color_button_area_changed (GtkWidget *color_area, + GimpColorButton *button) +{ + GimpColorButtonPrivate *priv = GET_PRIVATE (button); + + if (priv->selection) + { + GimpRGB color; + + gimp_color_button_get_color (button, &color); + + g_signal_handlers_block_by_func (priv->selection, + gimp_color_button_selection_changed, + button); + + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (priv->selection), + &color); + + g_signal_handlers_unblock_by_func (priv->selection, + gimp_color_button_selection_changed, + button); + } + + g_signal_emit (button, gimp_color_button_signals[COLOR_CHANGED], 0); +} + +static void +gimp_color_button_selection_changed (GtkWidget *selection, + GimpColorButton *button) +{ + if (button->continuous_update) + { + GimpRGB color; + + gimp_color_selection_get_color (GIMP_COLOR_SELECTION (selection), &color); + + g_signal_handlers_block_by_func (button->color_area, + gimp_color_button_area_changed, + button); + + gimp_color_area_set_color (GIMP_COLOR_AREA (button->color_area), &color); + + g_signal_handlers_unblock_by_func (button->color_area, + gimp_color_button_area_changed, + button); + + g_signal_emit (button, gimp_color_button_signals[COLOR_CHANGED], 0); + } +} + +static void +gimp_color_button_help_func (const gchar *help_id, + gpointer help_data) +{ + GimpColorButton *button; + GimpColorButtonPrivate *priv = GET_PRIVATE (help_data); + GimpColorNotebook *notebook; + + button = g_object_get_data (G_OBJECT (help_data), COLOR_BUTTON_KEY); + priv = GET_PRIVATE (button); + + notebook = GIMP_COLOR_NOTEBOOK (GIMP_COLOR_SELECTION (priv->selection)->notebook); + + help_id = GIMP_COLOR_SELECTOR_GET_CLASS (notebook->cur_page)->help_id; + + gimp_standard_help_func (help_id, NULL); +} diff --git a/libgimpwidgets/gimpcolorbutton.h b/libgimpwidgets/gimpcolorbutton.h new file mode 100644 index 0000000..fe7771d --- /dev/null +++ b/libgimpwidgets/gimpcolorbutton.h @@ -0,0 +1,114 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorbutton.h + * Copyright (C) 1999-2001 Sven Neumann + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* This provides a button with a color preview. The preview + * can handle transparency by showing the checkerboard. + * On click, a color selector is opened, which is already + * fully functional wired to the preview button. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_BUTTON_H__ +#define __GIMP_COLOR_BUTTON_H__ + +#include <libgimpwidgets/gimpbutton.h> + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_BUTTON (gimp_color_button_get_type ()) +#define GIMP_COLOR_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_BUTTON, GimpColorButton)) +#define GIMP_COLOR_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_BUTTON, GimpColorButtonClass)) +#define GIMP_IS_COLOR_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_BUTTON)) +#define GIMP_IS_COLOR_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_BUTTON)) +#define GIMP_COLOR_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_BUTTON, GimpColorButtonClass)) + + +typedef struct _GimpColorButtonClass GimpColorButtonClass; + +struct _GimpColorButton +{ + GimpButton parent_instance; + + gchar *title; + gboolean continuous_update; + + GtkWidget *color_area; + GtkWidget *dialog; + + /*< private >*/ + gpointer popup_menu; +}; + +struct _GimpColorButtonClass +{ + GimpButtonClass parent_class; + + /* signals */ + void (* color_changed) (GimpColorButton *button); + + /* virtual functions */ + GType (* get_action_type) (GimpColorButton *button); + + /* Padding for future expansion */ + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_button_new (const gchar *title, + gint width, + gint height, + const GimpRGB *color, + GimpColorAreaType type); + +void gimp_color_button_set_title (GimpColorButton *button, + const gchar *title); +const gchar * gimp_color_button_get_title (GimpColorButton *button); + +void gimp_color_button_set_color (GimpColorButton *button, + const GimpRGB *color); +void gimp_color_button_get_color (GimpColorButton *button, + GimpRGB *color); + +gboolean gimp_color_button_has_alpha (GimpColorButton *button); +void gimp_color_button_set_type (GimpColorButton *button, + GimpColorAreaType type); + +gboolean gimp_color_button_get_update (GimpColorButton *button); +void gimp_color_button_set_update (GimpColorButton *button, + gboolean continuous); + +void gimp_color_button_set_color_config (GimpColorButton *button, + GimpColorConfig *config); + +GtkUIManager * gimp_color_button_get_ui_manager (GimpColorButton *button); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_BUTTON_H__ */ diff --git a/libgimpwidgets/gimpcolordisplay.c b/libgimpwidgets/gimpcolordisplay.c new file mode 100644 index 0000000..d064e9b --- /dev/null +++ b/libgimpwidgets/gimpcolordisplay.c @@ -0,0 +1,563 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolordisplay.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolordisplay.h" +#include "gimpicons.h" + + +/** + * SECTION: gimpcolordisplay + * @title: GimpColorDisplay + * @short_description: Pluggable GIMP display color correction modules. + * @see_also: #GModule, #GTypeModule, #GimpModule + * + * Functions and definitions for creating pluggable GIMP + * display color correction modules. + **/ + + +enum +{ + PROP_0, + PROP_ENABLED, + PROP_COLOR_CONFIG, + PROP_COLOR_MANAGED +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + + +typedef struct +{ + GimpColorConfig *config; + GimpColorManaged *managed; +} GimpColorDisplayPrivate; + +#define GIMP_COLOR_DISPLAY_GET_PRIVATE(obj) ((GimpColorDisplayPrivate *) gimp_color_display_get_instance_private ((GimpColorDisplay *) (obj))) + + +static void gimp_color_display_constructed (GObject *object); +static void gimp_color_display_dispose (GObject *object); +static void gimp_color_display_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_color_display_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_color_display_set_color_config (GimpColorDisplay *display, + GimpColorConfig *config); +static void gimp_color_display_set_color_managed (GimpColorDisplay *display, + GimpColorManaged *managed); + + +G_DEFINE_TYPE_WITH_CODE (GimpColorDisplay, gimp_color_display, G_TYPE_OBJECT, + G_ADD_PRIVATE (GimpColorDisplay) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL)) + +#define parent_class gimp_color_display_parent_class + +static guint display_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_color_display_class_init (GimpColorDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_color_display_constructed; + object_class->dispose = gimp_color_display_dispose; + object_class->set_property = gimp_color_display_set_property; + object_class->get_property = gimp_color_display_get_property; + + g_object_class_install_property (object_class, PROP_ENABLED, + g_param_spec_boolean ("enabled", + "Enabled", + "Whether this display filter is enabled", + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_COLOR_CONFIG, + g_param_spec_object ("color-config", + "Color Config", + "The color config used for this filter", + GIMP_TYPE_COLOR_CONFIG, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_COLOR_MANAGED, + g_param_spec_object ("color-managed", + "Color Managed", + "The color managed pixel source that is filtered", + GIMP_TYPE_COLOR_MANAGED, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + display_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorDisplayClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->name = "Unnamed"; + klass->help_id = NULL; + klass->icon_name = GIMP_ICON_DISPLAY_FILTER; + + klass->clone = NULL; + klass->convert_buffer = NULL; + klass->convert_surface = NULL; + klass->convert = NULL; + klass->load_state = NULL; + klass->save_state = NULL; + klass->configure = NULL; + klass->configure_reset = NULL; + klass->changed = NULL; +} + +static void +gimp_color_display_init (GimpColorDisplay *display) +{ + display->enabled = FALSE; +} + +static void +gimp_color_display_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + /* emit an initial "changed" signal after all construct properties are set */ + gimp_color_display_changed (GIMP_COLOR_DISPLAY (object)); +} + +static void +gimp_color_display_dispose (GObject *object) +{ + GimpColorDisplayPrivate *private = GIMP_COLOR_DISPLAY_GET_PRIVATE (object); + + if (private->config) + { + g_signal_handlers_disconnect_by_func (private->config, + gimp_color_display_changed, + object); + g_object_unref (private->config); + private->config = NULL; + } + + if (private->managed) + { + g_signal_handlers_disconnect_by_func (private->managed, + gimp_color_display_changed, + object); + g_object_unref (private->managed); + private->managed = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_display_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorDisplay *display = GIMP_COLOR_DISPLAY (object); + + switch (property_id) + { + case PROP_ENABLED: + display->enabled = g_value_get_boolean (value); + break; + + case PROP_COLOR_CONFIG: + gimp_color_display_set_color_config (display, + g_value_get_object (value)); + break; + + case PROP_COLOR_MANAGED: + gimp_color_display_set_color_managed (display, + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_display_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorDisplay *display = GIMP_COLOR_DISPLAY (object); + + switch (property_id) + { + case PROP_ENABLED: + g_value_set_boolean (value, display->enabled); + break; + + case PROP_COLOR_CONFIG: + g_value_set_object (value, + GIMP_COLOR_DISPLAY_GET_PRIVATE (display)->config); + break; + + case PROP_COLOR_MANAGED: + g_value_set_object (value, + GIMP_COLOR_DISPLAY_GET_PRIVATE (display)->managed); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_display_set_color_config (GimpColorDisplay *display, + GimpColorConfig *config) +{ + GimpColorDisplayPrivate *private = GIMP_COLOR_DISPLAY_GET_PRIVATE (display); + + g_return_if_fail (private->config == NULL); + + if (config) + { + private->config = g_object_ref (config); + + g_signal_connect_swapped (private->config, "notify", + G_CALLBACK (gimp_color_display_changed), + display); + } +} + +static void +gimp_color_display_set_color_managed (GimpColorDisplay *display, + GimpColorManaged *managed) +{ + GimpColorDisplayPrivate *private = GIMP_COLOR_DISPLAY_GET_PRIVATE (display); + + g_return_if_fail (private->managed == NULL); + + if (managed) + { + private->managed = g_object_ref (managed); + + g_signal_connect_swapped (private->managed, "profile-changed", + G_CALLBACK (gimp_color_display_changed), + display); + } +} + +/** + * gimp_color_display_new: + * @display_type: the GType of the GimpColorDisplay to instantiate. + * + * This function is deprecated. Please use g_object_new() directly. + * + * Return value: a new %GimpColorDisplay object. + **/ +GimpColorDisplay * +gimp_color_display_new (GType display_type) +{ + g_return_val_if_fail (g_type_is_a (display_type, GIMP_TYPE_COLOR_DISPLAY), + NULL); + + return g_object_new (display_type, NULL); +} + +GimpColorDisplay * +gimp_color_display_clone (GimpColorDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY (display), NULL); + + /* implementing the clone method is deprecated + */ + if (GIMP_COLOR_DISPLAY_GET_CLASS (display)->clone) + { + GimpColorDisplay *clone; + + clone = GIMP_COLOR_DISPLAY_GET_CLASS (display)->clone (display); + + if (clone) + { + GimpColorDisplayPrivate *private; + + private = GIMP_COLOR_DISPLAY_GET_PRIVATE (display); + + g_object_set (clone, + "enabled", display->enabled, + "color-managed", private->managed, + NULL); + } + + return clone; + } + + return GIMP_COLOR_DISPLAY (gimp_config_duplicate (GIMP_CONFIG (display))); +} + +/** + * gimp_color_display_convert_buffer: + * @display: a #GimpColorDisplay + * @buffer: a #GeglBuffer + * @area: area in @buffer to convert + * + * Converts all pixels in @area of @buffer. + * + * Since: 2.10 + **/ +void +gimp_color_display_convert_buffer (GimpColorDisplay *display, + GeglBuffer *buffer, + GeglRectangle *area) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + g_return_if_fail (GEGL_IS_BUFFER (buffer)); + + if (display->enabled && + GIMP_COLOR_DISPLAY_GET_CLASS (display)->convert_buffer) + { + GIMP_COLOR_DISPLAY_GET_CLASS (display)->convert_buffer (display, buffer, + area); + } +} + +/** + * gimp_color_display_convert_surface: + * @display: a #GimpColorDisplay + * @surface: a #cairo_image_surface_t of type ARGB32 + * + * Converts all pixels in @surface. + * + * Since: 2.8 + * + * Deprecated: GIMP 2.8: Use gimp_color_display_convert_buffer() instead. + **/ +void +gimp_color_display_convert_surface (GimpColorDisplay *display, + cairo_surface_t *surface) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + g_return_if_fail (surface != NULL); + g_return_if_fail (cairo_surface_get_type (surface) == + CAIRO_SURFACE_TYPE_IMAGE); + + if (display->enabled && + GIMP_COLOR_DISPLAY_GET_CLASS (display)->convert_surface) + { + cairo_surface_flush (surface); + GIMP_COLOR_DISPLAY_GET_CLASS (display)->convert_surface (display, surface); + cairo_surface_mark_dirty (surface); + } +} + +/** + * gimp_color_display_convert: + * @display: a #GimpColorDisplay + * @buf: the pixel buffer to convert + * @width: the width of the buffer + * @height: the height of the buffer + * @bpp: the number of bytes per pixel + * @bpl: the buffer's rowstride + * + * Converts all pixels in @buf. + * + * Deprecated: GIMP 2.8: Use gimp_color_display_convert_buffer() instead. + **/ +void +gimp_color_display_convert (GimpColorDisplay *display, + guchar *buf, + gint width, + gint height, + gint bpp, + gint bpl) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + + /* implementing the convert method is deprecated + */ + if (display->enabled && GIMP_COLOR_DISPLAY_GET_CLASS (display)->convert) + GIMP_COLOR_DISPLAY_GET_CLASS (display)->convert (display, buf, + width, height, + bpp, bpl); +} + +void +gimp_color_display_load_state (GimpColorDisplay *display, + GimpParasite *state) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + g_return_if_fail (state != NULL); + + /* implementing the load_state method is deprecated + */ + if (GIMP_COLOR_DISPLAY_GET_CLASS (display)->load_state) + { + GIMP_COLOR_DISPLAY_GET_CLASS (display)->load_state (display, state); + } + else + { + gimp_config_deserialize_string (GIMP_CONFIG (display), + gimp_parasite_data (state), + gimp_parasite_data_size (state), + NULL, NULL); + } +} + +GimpParasite * +gimp_color_display_save_state (GimpColorDisplay *display) +{ + GimpParasite *parasite; + gchar *str; + + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY (display), NULL); + + /* implementing the save_state method is deprecated + */ + if (GIMP_COLOR_DISPLAY_GET_CLASS (display)->save_state) + { + return GIMP_COLOR_DISPLAY_GET_CLASS (display)->save_state (display); + } + + str = gimp_config_serialize_to_string (GIMP_CONFIG (display), NULL); + + parasite = gimp_parasite_new ("Display/Proof", + GIMP_PARASITE_PERSISTENT, + strlen (str) + 1, str); + g_free (str); + + return parasite; +} + +GtkWidget * +gimp_color_display_configure (GimpColorDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY (display), NULL); + + if (GIMP_COLOR_DISPLAY_GET_CLASS (display)->configure) + return GIMP_COLOR_DISPLAY_GET_CLASS (display)->configure (display); + + return NULL; +} + +void +gimp_color_display_configure_reset (GimpColorDisplay *display) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + + /* implementing the configure_reset method is deprecated + */ + if (GIMP_COLOR_DISPLAY_GET_CLASS (display)->configure_reset) + { + GIMP_COLOR_DISPLAY_GET_CLASS (display)->configure_reset (display); + } + else + { + gimp_config_reset (GIMP_CONFIG (display)); + } +} + +void +gimp_color_display_changed (GimpColorDisplay *display) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + + g_signal_emit (display, display_signals[CHANGED], 0); +} + +void +gimp_color_display_set_enabled (GimpColorDisplay *display, + gboolean enabled) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + + if (enabled != display->enabled) + { + g_object_set (display, + "enabled", enabled, + NULL); + } +} + +gboolean +gimp_color_display_get_enabled (GimpColorDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY (display), FALSE); + + return display->enabled; +} + +/** + * gimp_color_display_get_config: + * @display: + * + * Return value: a pointer to the #GimpColorConfig object or %NULL. + * + * Since: 2.4 + **/ +GimpColorConfig * +gimp_color_display_get_config (GimpColorDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY (display), NULL); + + return GIMP_COLOR_DISPLAY_GET_PRIVATE (display)->config; +} + +/** + * gimp_color_display_get_managed: + * @display: + * + * Return value: a pointer to the #GimpColorManaged object or %NULL. + * + * Since: 2.4 + **/ +GimpColorManaged * +gimp_color_display_get_managed (GimpColorDisplay *display) +{ + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY (display), NULL); + + return GIMP_COLOR_DISPLAY_GET_PRIVATE (display)->managed; +} diff --git a/libgimpwidgets/gimpcolordisplay.h b/libgimpwidgets/gimpcolordisplay.h new file mode 100644 index 0000000..59c6b0d --- /dev/null +++ b/libgimpwidgets/gimpcolordisplay.h @@ -0,0 +1,142 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolordisplay.c + * Copyright (C) 1999 Manish Singh <yosh@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_DISPLAY_H__ +#define __GIMP_COLOR_DISPLAY_H__ + +G_BEGIN_DECLS + +/* For information look at the html documentation */ + + +#define GIMP_TYPE_COLOR_DISPLAY (gimp_color_display_get_type ()) +#define GIMP_COLOR_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_DISPLAY, GimpColorDisplay)) +#define GIMP_COLOR_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_DISPLAY, GimpColorDisplayClass)) +#define GIMP_IS_COLOR_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_DISPLAY)) +#define GIMP_IS_COLOR_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_DISPLAY)) +#define GIMP_COLOR_DISPLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_DISPLAY, GimpColorDisplayClass)) + + +typedef struct _GimpColorDisplayClass GimpColorDisplayClass; + +struct _GimpColorDisplay +{ + GObject parent_instance; + + gboolean enabled; +}; + +struct _GimpColorDisplayClass +{ + GObjectClass parent_class; + + const gchar *name; + const gchar *help_id; + + /* virtual functions */ + + /* implementing the GimpColorDisplay::clone method is deprecated */ + GimpColorDisplay * (* clone) (GimpColorDisplay *display); + + /* implementing the GimpColorDisplay::convert method is deprecated */ + void (* convert) (GimpColorDisplay *display, + guchar *buf, + gint width, + gint height, + gint bpp, + gint bpl); + + /* implementing the GimpColorDisplay::load_state method is deprecated */ + void (* load_state) (GimpColorDisplay *display, + GimpParasite *state); + + /* implementing the GimpColorDisplay::save_state method is deprecated */ + GimpParasite * (* save_state) (GimpColorDisplay *display); + + GtkWidget * (* configure) (GimpColorDisplay *display); + + /* implementing the GimpColorDisplay::configure_reset method is deprecated */ + void (* configure_reset) (GimpColorDisplay *display); + + /* signals */ + void (* changed) (GimpColorDisplay *display); + +#ifdef GIMP_DISABLE_DEPRECATED + gpointer deprecated_stock_id; +#else + const gchar *stock_id; +#endif + + /* implementing the GimpColorDisplay::convert_surface method is deprecated */ + void (* convert_surface) (GimpColorDisplay *display, + cairo_surface_t *surface); + + void (* convert_buffer) (GimpColorDisplay *display, + GeglBuffer *buffer, + GeglRectangle *area); + + /* icon name */ + const gchar *icon_name; +}; + + +GType gimp_color_display_get_type (void) G_GNUC_CONST; + +GIMP_DEPRECATED_FOR(g_object_new) +GimpColorDisplay * gimp_color_display_new (GType display_type); +GimpColorDisplay * gimp_color_display_clone (GimpColorDisplay *display); + +void gimp_color_display_convert_buffer (GimpColorDisplay *display, + GeglBuffer *buffer, + GeglRectangle *area); +GIMP_DEPRECATED_FOR(gimp_color_display_convert_buffer) +void gimp_color_display_convert_surface (GimpColorDisplay *display, + cairo_surface_t *surface); +GIMP_DEPRECATED_FOR(gimp_color_display_convert_buffer) +void gimp_color_display_convert (GimpColorDisplay *display, + guchar *buf, + gint width, + gint height, + gint bpp, + gint bpl); +void gimp_color_display_load_state (GimpColorDisplay *display, + GimpParasite *state); +GimpParasite * gimp_color_display_save_state (GimpColorDisplay *display); +GtkWidget * gimp_color_display_configure (GimpColorDisplay *display); +void gimp_color_display_configure_reset (GimpColorDisplay *display); + +void gimp_color_display_changed (GimpColorDisplay *display); + +void gimp_color_display_set_enabled (GimpColorDisplay *display, + gboolean enabled); +gboolean gimp_color_display_get_enabled (GimpColorDisplay *display); + +GimpColorConfig * gimp_color_display_get_config (GimpColorDisplay *display); +GimpColorManaged * gimp_color_display_get_managed (GimpColorDisplay *display); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_DISPLAY_H__ */ diff --git a/libgimpwidgets/gimpcolordisplaystack.c b/libgimpwidgets/gimpcolordisplaystack.c new file mode 100644 index 0000000..0020270 --- /dev/null +++ b/libgimpwidgets/gimpcolordisplaystack.c @@ -0,0 +1,412 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolordisplaystack.c + * Copyright (C) 2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpcolordisplay.h" +#include "gimpcolordisplaystack.h" +#include "gimpwidgetsmarshal.h" + + +/** + * SECTION: gimpcolordisplaystack + * @title: GimpColorDisplayStack + * @short_description: A stack of color correction modules. + * @see_also: #GimpColorDisplay + * + * A stack of color correction modules. + **/ + + +enum +{ + CHANGED, + ADDED, + REMOVED, + REORDERED, + LAST_SIGNAL +}; + + +static void gimp_color_display_stack_dispose (GObject *object); + +static void gimp_color_display_stack_display_changed (GimpColorDisplay *display, + GimpColorDisplayStack *stack); +static void gimp_color_display_stack_display_enabled (GimpColorDisplay *display, + GParamSpec *pspec, + GimpColorDisplayStack *stack); +static void gimp_color_display_stack_disconnect (GimpColorDisplayStack *stack, + GimpColorDisplay *display); + + +G_DEFINE_TYPE (GimpColorDisplayStack, gimp_color_display_stack, G_TYPE_OBJECT) + +#define parent_class gimp_color_display_stack_parent_class + +static guint stack_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_color_display_stack_class_init (GimpColorDisplayStackClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + stack_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorDisplayStackClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + stack_signals[ADDED] = + g_signal_new ("added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorDisplayStackClass, added), + NULL, NULL, + _gimp_widgets_marshal_VOID__OBJECT_INT, + G_TYPE_NONE, 2, + GIMP_TYPE_COLOR_DISPLAY, + G_TYPE_INT); + + stack_signals[REMOVED] = + g_signal_new ("removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorDisplayStackClass, removed), + NULL, NULL, + _gimp_widgets_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_COLOR_DISPLAY); + + stack_signals[REORDERED] = + g_signal_new ("reordered", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorDisplayStackClass, reordered), + NULL, NULL, + _gimp_widgets_marshal_VOID__OBJECT_INT, + G_TYPE_NONE, 2, + GIMP_TYPE_COLOR_DISPLAY, + G_TYPE_INT); + + object_class->dispose = gimp_color_display_stack_dispose; + + klass->changed = NULL; + klass->added = NULL; + klass->removed = NULL; + klass->reordered = NULL; +} + +static void +gimp_color_display_stack_init (GimpColorDisplayStack *stack) +{ + stack->filters = NULL; +} + +static void +gimp_color_display_stack_dispose (GObject *object) +{ + GimpColorDisplayStack *stack = GIMP_COLOR_DISPLAY_STACK (object); + + if (stack->filters) + { + GList *list; + + for (list = stack->filters; list; list = g_list_next (list)) + { + GimpColorDisplay *display = list->data; + + gimp_color_display_stack_disconnect (stack, display); + g_object_unref (display); + } + + g_list_free (stack->filters); + stack->filters = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +GimpColorDisplayStack * +gimp_color_display_stack_new (void) +{ + return g_object_new (GIMP_TYPE_COLOR_DISPLAY_STACK, NULL); +} + +GimpColorDisplayStack * +gimp_color_display_stack_clone (GimpColorDisplayStack *stack) +{ + GimpColorDisplayStack *clone; + GList *list; + + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack), NULL); + + clone = g_object_new (GIMP_TYPE_COLOR_DISPLAY_STACK, NULL); + + for (list = stack->filters; list; list = g_list_next (list)) + { + GimpColorDisplay *display; + + display = gimp_color_display_clone (list->data); + + gimp_color_display_stack_add (clone, display); + g_object_unref (display); + } + + return clone; +} + +void +gimp_color_display_stack_changed (GimpColorDisplayStack *stack) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + + g_signal_emit (stack, stack_signals[CHANGED], 0); +} + +void +gimp_color_display_stack_add (GimpColorDisplayStack *stack, + GimpColorDisplay *display) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + g_return_if_fail (g_list_find (stack->filters, display) == NULL); + + stack->filters = g_list_append (stack->filters, g_object_ref (display)); + + g_signal_connect (display, "changed", + G_CALLBACK (gimp_color_display_stack_display_changed), + G_OBJECT (stack)); + g_signal_connect (display, "notify::enabled", + G_CALLBACK (gimp_color_display_stack_display_enabled), + G_OBJECT (stack)); + + g_signal_emit (stack, stack_signals[ADDED], 0, + display, g_list_length (stack->filters) - 1); + + gimp_color_display_stack_changed (stack); +} + +void +gimp_color_display_stack_remove (GimpColorDisplayStack *stack, + GimpColorDisplay *display) +{ + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + g_return_if_fail (g_list_find (stack->filters, display) != NULL); + + gimp_color_display_stack_disconnect (stack, display); + + stack->filters = g_list_remove (stack->filters, display); + + g_signal_emit (stack, stack_signals[REMOVED], 0, display); + + gimp_color_display_stack_changed (stack); + + g_object_unref (display); +} + +void +gimp_color_display_stack_reorder_up (GimpColorDisplayStack *stack, + GimpColorDisplay *display) +{ + GList *list; + + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + + list = g_list_find (stack->filters, display); + + g_return_if_fail (list != NULL); + + if (list->prev) + { + list->data = list->prev->data; + list->prev->data = display; + + g_signal_emit (stack, stack_signals[REORDERED], 0, + display, g_list_position (stack->filters, list->prev)); + + gimp_color_display_stack_changed (stack); + } +} + +void +gimp_color_display_stack_reorder_down (GimpColorDisplayStack *stack, + GimpColorDisplay *display) +{ + GList *list; + + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + g_return_if_fail (GIMP_IS_COLOR_DISPLAY (display)); + + list = g_list_find (stack->filters, display); + + g_return_if_fail (list != NULL); + + if (list->next) + { + list->data = list->next->data; + list->next->data = display; + + g_signal_emit (stack, stack_signals[REORDERED], 0, + display, g_list_position (stack->filters, list->next)); + + gimp_color_display_stack_changed (stack); + } +} + +/** + * gimp_color_display_stack_convert_buffer: + * @stack: a #GimpColorDisplayStack + * @buffer: a #GeglBuffer + * @area: area of @buffer to convert + * + * Runs all the stack's filters on all pixels in @area of @buffer. + * + * Since: 2.10 + **/ +void +gimp_color_display_stack_convert_buffer (GimpColorDisplayStack *stack, + GeglBuffer *buffer, + GeglRectangle *area) +{ + GList *list; + + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + g_return_if_fail (GEGL_IS_BUFFER (buffer)); + + for (list = stack->filters; list; list = g_list_next (list)) + { + GimpColorDisplay *display = list->data; + + gimp_color_display_convert_buffer (display, buffer, area); + } +} + +/** + * gimp_color_display_stack_convert_surface: + * @stack: a #GimpColorDisplayStack + * @surface: a #cairo_image_surface_t of type ARGB32 + * + * Runs all the stack's filters on all pixels in @surface. + * + * Since: 2.8 + * + * Deprecated: GIMP 2.10: Use gimp_color_display_stack_convert_buffer() instead. + **/ +void +gimp_color_display_stack_convert_surface (GimpColorDisplayStack *stack, + cairo_surface_t *surface) +{ + GList *list; + + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + g_return_if_fail (surface != NULL); + g_return_if_fail (cairo_surface_get_type (surface) == + CAIRO_SURFACE_TYPE_IMAGE); + + for (list = stack->filters; list; list = g_list_next (list)) + { + GimpColorDisplay *display = list->data; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gimp_color_display_convert_surface (display, surface); + G_GNUC_END_IGNORE_DEPRECATIONS + } +} + +/** + * gimp_color_display_stack_convert: + * @stack: a #GimpColorDisplayStack + * @buf: the pixel buffer to convert + * @width: the width of the buffer + * @height: the height of the buffer + * @bpp: the number of bytes per pixel + * @bpl: the buffer's rowstride + * + * Converts all pixels in @buf. + * + * Deprecated: GIMP 2.8: Use gimp_color_display_stack_convert_buffer() instead. + **/ +void +gimp_color_display_stack_convert (GimpColorDisplayStack *stack, + guchar *buf, + gint width, + gint height, + gint bpp, + gint bpl) +{ + GList *list; + + g_return_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack)); + + for (list = stack->filters; list; list = g_list_next (list)) + { + GimpColorDisplay *display = list->data; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gimp_color_display_convert (display, buf, width, height, bpp, bpl); + G_GNUC_END_IGNORE_DEPRECATIONS + } +} + + +/* private functions */ + +static void +gimp_color_display_stack_display_changed (GimpColorDisplay *display, + GimpColorDisplayStack *stack) +{ + if (display->enabled) + gimp_color_display_stack_changed (stack); +} + +static void +gimp_color_display_stack_display_enabled (GimpColorDisplay *display, + GParamSpec *pspec, + GimpColorDisplayStack *stack) +{ + gimp_color_display_stack_changed (stack); +} + +static void +gimp_color_display_stack_disconnect (GimpColorDisplayStack *stack, + GimpColorDisplay *display) +{ + g_signal_handlers_disconnect_by_func (display, + gimp_color_display_stack_display_changed, + stack); + g_signal_handlers_disconnect_by_func (display, + gimp_color_display_stack_display_enabled, + stack); +} diff --git a/libgimpwidgets/gimpcolordisplaystack.h b/libgimpwidgets/gimpcolordisplaystack.h new file mode 100644 index 0000000..4009501 --- /dev/null +++ b/libgimpwidgets/gimpcolordisplaystack.h @@ -0,0 +1,104 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolordisplaystack.h + * Copyright (C) 2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_DISPLAY_STACK_H__ +#define __GIMP_COLOR_DISPLAY_STACK_H__ + +G_BEGIN_DECLS + +/* For information look at the html documentation */ + + +#define GIMP_TYPE_COLOR_DISPLAY_STACK (gimp_color_display_stack_get_type ()) +#define GIMP_COLOR_DISPLAY_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_DISPLAY_STACK, GimpColorDisplayStack)) +#define GIMP_COLOR_DISPLAY_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_DISPLAY_STACK, GimpColorDisplayStackClass)) +#define GIMP_IS_COLOR_DISPLAY_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_DISPLAY_STACK)) +#define GIMP_IS_COLOR_DISPLAY_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_DISPLAY_STACK)) +#define GIMP_COLOR_DISPLAY_STACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_DISPLAY_STACK, GimpColorDisplayStackClass)) + + +typedef struct _GimpColorDisplayStackClass GimpColorDisplayStackClass; + +struct _GimpColorDisplayStack +{ + GObject parent_instance; + + GList *filters; +}; + +struct _GimpColorDisplayStackClass +{ + GObjectClass parent_class; + + void (* changed) (GimpColorDisplayStack *stack); + + void (* added) (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + gint position); + void (* removed) (GimpColorDisplayStack *stack, + GimpColorDisplay *display); + void (* reordered) (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + gint position); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_display_stack_get_type (void) G_GNUC_CONST; +GimpColorDisplayStack * gimp_color_display_stack_new (void); +GimpColorDisplayStack * gimp_color_display_stack_clone (GimpColorDisplayStack *stack); + +void gimp_color_display_stack_changed (GimpColorDisplayStack *stack); + +void gimp_color_display_stack_add (GimpColorDisplayStack *stack, + GimpColorDisplay *display); +void gimp_color_display_stack_remove (GimpColorDisplayStack *stack, + GimpColorDisplay *display); +void gimp_color_display_stack_reorder_up (GimpColorDisplayStack *stack, + GimpColorDisplay *display); +void gimp_color_display_stack_reorder_down (GimpColorDisplayStack *stack, + GimpColorDisplay *display); +void gimp_color_display_stack_convert_buffer (GimpColorDisplayStack *stack, + GeglBuffer *buffer, + GeglRectangle *area); +GIMP_DEPRECATED_FOR(gimp_color_display_stack_convert_buffer) +void gimp_color_display_stack_convert_surface (GimpColorDisplayStack *stack, + cairo_surface_t *surface); +GIMP_DEPRECATED_FOR(gimp_color_display_stack_convert_buffer) +void gimp_color_display_stack_convert (GimpColorDisplayStack *stack, + guchar *buf, + gint width, + gint height, + gint bpp, + gint bpl); + +G_END_DECLS + +#endif /* __GIMP_COLOR_DISPLAY_STACK_H__ */ diff --git a/libgimpwidgets/gimpcolorhexentry.c b/libgimpwidgets/gimpcolorhexentry.c new file mode 100644 index 0000000..8c7474f --- /dev/null +++ b/libgimpwidgets/gimpcolorhexentry.c @@ -0,0 +1,324 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorhexentry.c + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcellrenderercolor.h" +#include "gimpcolorhexentry.h" +#include "gimphelpui.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorhexentry + * @title: GimpColorHexEntry + * @short_description: Widget for entering a color's hex triplet. + * + * Widget for entering a color's hex triplet. + **/ + + +enum +{ + COLOR_CHANGED, + LAST_SIGNAL +}; + +enum +{ + COLUMN_NAME, + COLUMN_COLOR, + NUM_COLUMNS +}; + + +static void gimp_color_hex_entry_constructed (GObject *object); + +static gboolean gimp_color_hex_entry_events (GtkWidget *widget, + GdkEvent *event); + +static gboolean gimp_color_hex_entry_matched (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter, + GimpColorHexEntry *entry); + + +G_DEFINE_TYPE (GimpColorHexEntry, gimp_color_hex_entry, GTK_TYPE_ENTRY) + +#define parent_class gimp_color_hex_entry_parent_class + +static guint entry_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_color_hex_entry_class_init (GimpColorHexEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + entry_signals[COLOR_CHANGED] = + g_signal_new ("color-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorHexEntryClass, color_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->constructed = gimp_color_hex_entry_constructed; + + klass->color_changed = NULL; +} + +static void +gimp_color_hex_entry_init (GimpColorHexEntry *entry) +{ + GtkEntryCompletion *completion; + GtkCellRenderer *cell; + GtkListStore *store; + GimpRGB *colors; + const gchar **names; + gint num_colors; + gint i; + + /* GtkEntry's minimum size is way too large, set a reasonable one + * for our use case + */ + gtk_entry_set_width_chars (GTK_ENTRY (entry), 8); + + gimp_help_set_help_data (GTK_WIDGET (entry), + _("Hexadecimal color notation as used in HTML and " + "CSS. This entry also accepts CSS color names."), + NULL); + + gimp_rgba_set (&entry->color, 0.0, 0.0, 0.0, 1.0); + + store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, GIMP_TYPE_RGB); + + num_colors = gimp_rgb_list_names (&names, &colors); + + for (i = 0; i < num_colors; i++) + { + GtkTreeIter iter; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, names[i], + COLUMN_COLOR, colors + i, + -1); + } + + g_free (colors); + g_free (names); + + completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, + "model", store, + NULL); + g_object_unref (store); + + cell = gimp_cell_renderer_color_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), cell, + "color", COLUMN_COLOR, + NULL); + + gtk_entry_completion_set_text_column (completion, COLUMN_NAME); + + gtk_entry_set_completion (GTK_ENTRY (entry), completion); + g_object_unref (completion); + + g_signal_connect (entry, "focus-out-event", + G_CALLBACK (gimp_color_hex_entry_events), + NULL); + g_signal_connect (entry, "key-press-event", + G_CALLBACK (gimp_color_hex_entry_events), + NULL); + + g_signal_connect (completion, "match-selected", + G_CALLBACK (gimp_color_hex_entry_matched), + entry); +} + +static void +gimp_color_hex_entry_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_entry_set_text (GTK_ENTRY (object), "000000"); +} + +/** + * gimp_color_hex_entry_new: + * + * Return value: a new #GimpColorHexEntry widget + * + * Since: 2.2 + **/ +GtkWidget * +gimp_color_hex_entry_new (void) +{ + return g_object_new (GIMP_TYPE_COLOR_HEX_ENTRY, NULL); +} + +/** + * gimp_color_hex_entry_set_color: + * @entry: a #GimpColorHexEntry widget + * @color: pointer to a #GimpRGB + * + * Sets the color displayed by a #GimpColorHexEntry. If the new color + * is different to the previously set color, the "color-changed" + * signal is emitted. + * + * Since: 2.2 + **/ +void +gimp_color_hex_entry_set_color (GimpColorHexEntry *entry, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_HEX_ENTRY (entry)); + g_return_if_fail (color != NULL); + + if (gimp_rgb_distance (&entry->color, color) > 0.0) + { + gchar buffer[8]; + guchar r, g, b; + + gimp_rgb_set (&entry->color, color->r, color->g, color->b); + gimp_rgb_clamp (&entry->color); + + gimp_rgb_get_uchar (&entry->color, &r, &g, &b); + g_snprintf (buffer, sizeof (buffer), "%.2x%.2x%.2x", r, g, b); + + gtk_entry_set_text (GTK_ENTRY (entry), buffer); + + /* move cursor to the end */ + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + + g_signal_emit (entry, entry_signals[COLOR_CHANGED], 0); + } +} + +/** + * gimp_color_hex_entry_get_color: + * @entry: a #GimpColorHexEntry widget + * @color: pointer to a #GimpRGB + * + * Retrieves the color value displayed by a #GimpColorHexEntry. + * + * Since: 2.2 + **/ +void +gimp_color_hex_entry_get_color (GimpColorHexEntry *entry, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_HEX_ENTRY (entry)); + g_return_if_fail (color != NULL); + + *color = entry->color; +} + +static gboolean +gimp_color_hex_entry_events (GtkWidget *widget, + GdkEvent *event) +{ + GimpColorHexEntry *entry = GIMP_COLOR_HEX_ENTRY (widget); + + switch (event->type) + { + case GDK_KEY_PRESS: + { + GdkEventKey *kevent = (GdkEventKey *) event; + + if (kevent->keyval != GDK_KEY_Return && + kevent->keyval != GDK_KEY_KP_Enter && + kevent->keyval != GDK_KEY_ISO_Enter) + break; + /* else fall through */ + } + + case GDK_FOCUS_CHANGE: + { + const gchar *text; + gchar buffer[8]; + guchar r, g, b; + + text = gtk_entry_get_text (GTK_ENTRY (widget)); + + gimp_rgb_get_uchar (&entry->color, &r, &g, &b); + g_snprintf (buffer, sizeof (buffer), "%.2x%.2x%.2x", r, g, b); + + if (g_ascii_strcasecmp (buffer, text) != 0) + { + GimpRGB color; + gsize len = strlen (text); + + if (len > 0 && + (gimp_rgb_parse_hex (&color, text, len) || + gimp_rgb_parse_name (&color, text, -1))) + { + gimp_color_hex_entry_set_color (entry, &color); + } + else + { + gtk_entry_set_text (GTK_ENTRY (entry), buffer); + } + } + } + break; + + default: + /* do nothing */ + break; + } + + return FALSE; +} + +static gboolean +gimp_color_hex_entry_matched (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter, + GimpColorHexEntry *entry) +{ + gchar *name; + GimpRGB color; + + gtk_tree_model_get (model, iter, + COLUMN_NAME, &name, + -1); + + if (gimp_rgb_parse_name (&color, name, -1)) + gimp_color_hex_entry_set_color (entry, &color); + + g_free (name); + + return TRUE; +} diff --git a/libgimpwidgets/gimpcolorhexentry.h b/libgimpwidgets/gimpcolorhexentry.h new file mode 100644 index 0000000..cdf2119 --- /dev/null +++ b/libgimpwidgets/gimpcolorhexentry.h @@ -0,0 +1,75 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorhexentry.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_HEX_ENTRY_H__ +#define __GIMP_COLOR_HEX_ENTRY_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_HEX_ENTRY (gimp_color_hex_entry_get_type ()) +#define GIMP_COLOR_HEX_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_HEX_ENTRY, GimpColorHexEntry)) +#define GIMP_COLOR_HEX_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_HEX_ENTRY, GimpColorHexEntryClass)) +#define GIMP_IS_COLOR_HEX_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_HEX_ENTRY)) +#define GIMP_IS_COLOR_HEX_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_HEX_ENTRY)) +#define GIMP_COLOR_HEX_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_HEX_AREA, GimpColorHexEntryClass)) + + +typedef struct _GimpColorHexEntryClass GimpColorHexEntryClass; + +struct _GimpColorHexEntry +{ + GtkEntry parent_instance; + + GimpRGB color; +}; + +struct _GimpColorHexEntryClass +{ + GtkEntryClass parent_class; + + void (* color_changed) (GimpColorHexEntry *entry); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_hex_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_hex_entry_new (void); + +void gimp_color_hex_entry_set_color (GimpColorHexEntry *entry, + const GimpRGB *color); +void gimp_color_hex_entry_get_color (GimpColorHexEntry *entry, + GimpRGB *color); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_HEX_ENTRY_H__ */ diff --git a/libgimpwidgets/gimpcolornotebook.c b/libgimpwidgets/gimpcolornotebook.c new file mode 100644 index 0000000..ec405ce --- /dev/null +++ b/libgimpwidgets/gimpcolornotebook.c @@ -0,0 +1,547 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolornotebook.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on color_notebook module + * Copyright (C) 1998 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolornotebook.h" +#include "gimpcolorscales.h" +#include "gimphelpui.h" +#include "gimpwidgetsmarshal.h" +#include "gimp3migration.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolornotebook + * @title: GimpColorNotebook + * @short_description: A #GimpColorSelector implementation. + * + * The #GimpColorNotebook widget is an implementation of a + * #GimpColorSelector. It serves as a container for + * #GimpColorSelectors. + **/ + + +#define DEFAULT_TAB_BORDER 0 +#define DEFAULT_TAB_ICON_SIZE GTK_ICON_SIZE_BUTTON + + +static void gimp_color_notebook_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_color_notebook_togg_visible (GimpColorSelector *selector, + gboolean visible); +static void gimp_color_notebook_togg_sensitive (GimpColorSelector *selector, + gboolean sensitive); +static void gimp_color_notebook_set_show_alpha (GimpColorSelector *selector, + gboolean show_alpha); +static void gimp_color_notebook_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); +static void gimp_color_notebook_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel); +static void gimp_color_notebook_set_model_visible + (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean gboolean); +static void gimp_color_notebook_set_config (GimpColorSelector *selector, + GimpColorConfig *config); + + +static void gimp_color_notebook_switch_page (GtkNotebook *gtk_notebook, + gpointer page, + guint page_num, + GimpColorNotebook *notebook); + +static void gimp_color_notebook_color_changed (GimpColorSelector *page, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorNotebook *notebook); +static void gimp_color_notebook_channel_changed (GimpColorSelector *page, + GimpColorSelectorChannel channel, + GimpColorNotebook *notebook); +static void gimp_color_notebook_model_visible_changed + (GimpColorSelector *page, + GimpColorSelectorModel model, + gboolean visible, + GimpColorNotebook *notebook); + +static GtkWidget * gimp_color_notebook_add_page (GimpColorNotebook *notebook, + GType page_type); +static void gimp_color_notebook_remove_selector (GtkContainer *container, + GtkWidget *widget, + GimpColorNotebook *notebook); + + +G_DEFINE_TYPE (GimpColorNotebook, gimp_color_notebook, + GIMP_TYPE_COLOR_SELECTOR) + +#define parent_class gimp_color_notebook_parent_class + + +static void +gimp_color_notebook_class_init (GimpColorNotebookClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpColorSelectorClass *selector_class = GIMP_COLOR_SELECTOR_CLASS (klass); + + widget_class->style_set = gimp_color_notebook_style_set; + + selector_class->name = "Notebook"; + selector_class->help_id = "gimp-colorselector-notebook"; + selector_class->set_toggles_visible = gimp_color_notebook_togg_visible; + selector_class->set_toggles_sensitive = gimp_color_notebook_togg_sensitive; + selector_class->set_show_alpha = gimp_color_notebook_set_show_alpha; + selector_class->set_color = gimp_color_notebook_set_color; + selector_class->set_channel = gimp_color_notebook_set_channel; + selector_class->set_model_visible = gimp_color_notebook_set_model_visible; + selector_class->set_config = gimp_color_notebook_set_config; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("tab-border", + NULL, + "Width of the border around the tab contents", + 0, G_MAXINT, + DEFAULT_TAB_BORDER, + G_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("tab-icon-size", + NULL, + "Size for icons displayed in the tab", + GTK_TYPE_ICON_SIZE, + DEFAULT_TAB_ICON_SIZE, + G_PARAM_READABLE)); +} + +static void +gimp_color_notebook_init (GimpColorNotebook *notebook) +{ + GType *selector_types; + guint n_selector_types; + guint i; + + notebook->notebook = gtk_notebook_new (); + gtk_notebook_popup_enable (GTK_NOTEBOOK (notebook->notebook)); + gtk_box_pack_start (GTK_BOX (notebook), notebook->notebook, TRUE, TRUE, 0); + gtk_widget_show (notebook->notebook); + + g_signal_connect (notebook->notebook, "switch-page", + G_CALLBACK (gimp_color_notebook_switch_page), + notebook); + g_signal_connect (notebook->notebook, "remove", + G_CALLBACK (gimp_color_notebook_remove_selector), + notebook); + + selector_types = g_type_children (GIMP_TYPE_COLOR_SELECTOR, + &n_selector_types); + + if (n_selector_types == 2) + { + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook->notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook->notebook), FALSE); + } + + for (i = 0; i < n_selector_types; i++) + { + /* skip ourselves */ + if (g_type_is_a (selector_types[i], GIMP_TYPE_COLOR_NOTEBOOK)) + continue; + + /* skip the "Scales" color selector */ + if (g_type_is_a (selector_types[i], GIMP_TYPE_COLOR_SCALES)) + continue; + + gimp_color_notebook_add_page (notebook, selector_types[i]); + } + + g_free (selector_types); +} + +static void +gimp_color_notebook_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (widget); + GList *list; + gint tab_border; + GtkIconSize icon_size; + + if (GTK_WIDGET_CLASS (parent_class)->style_set) + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "tab-border", &tab_border, + "tab-icon_size", &icon_size, + NULL); + + g_object_set (notebook->notebook, + "tab-border", tab_border, + NULL); + + for (list = notebook->selectors; list; list = g_list_next (list)) + { + GimpColorSelectorClass *selector_class; + GtkWidget *image; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (list->data); + + image = gtk_image_new_from_icon_name (selector_class->icon_name, + icon_size); + gimp_help_set_help_data (image, gettext (selector_class->name), NULL); + + gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook->notebook), + GTK_WIDGET (list->data), + image); + } +} + +static void +gimp_color_notebook_togg_visible (GimpColorSelector *selector, + gboolean visible) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + GList *list; + + for (list = notebook->selectors; list; list = g_list_next (list)) + { + GimpColorSelector *child = list->data; + + gimp_color_selector_set_toggles_visible (child, visible); + } +} + +static void +gimp_color_notebook_togg_sensitive (GimpColorSelector *selector, + gboolean sensitive) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + GList *list; + + for (list = notebook->selectors; list; list = g_list_next (list)) + { + GimpColorSelector *child = list->data; + + gimp_color_selector_set_toggles_sensitive (child, sensitive); + } +} + +static void +gimp_color_notebook_set_show_alpha (GimpColorSelector *selector, + gboolean show_alpha) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + GList *list; + + for (list = notebook->selectors; list; list = g_list_next (list)) + { + GimpColorSelector *child = list->data; + + gimp_color_selector_set_show_alpha (child, show_alpha); + } +} + +static void +gimp_color_notebook_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + + g_signal_handlers_block_by_func (notebook->cur_page, + gimp_color_notebook_color_changed, + notebook); + + gimp_color_selector_set_color (notebook->cur_page, rgb, hsv); + + g_signal_handlers_unblock_by_func (notebook->cur_page, + gimp_color_notebook_color_changed, + notebook); +} + +static void +gimp_color_notebook_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + + g_signal_handlers_block_by_func (notebook->cur_page, + gimp_color_notebook_channel_changed, + notebook); + + gimp_color_selector_set_channel (notebook->cur_page, channel); + + g_signal_handlers_unblock_by_func (notebook->cur_page, + gimp_color_notebook_channel_changed, + notebook); +} + +static void +gimp_color_notebook_set_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + + g_signal_handlers_block_by_func (notebook->cur_page, + gimp_color_notebook_model_visible_changed, + notebook); + + gimp_color_selector_set_model_visible (notebook->cur_page, model, visible); + + g_signal_handlers_unblock_by_func (notebook->cur_page, + gimp_color_notebook_model_visible_changed, + notebook); +} + +static void +gimp_color_notebook_set_config (GimpColorSelector *selector, + GimpColorConfig *config) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selector); + GList *list; + + for (list = notebook->selectors; list; list = g_list_next (list)) + { + GimpColorSelector *child = list->data; + + gimp_color_selector_set_config (child, config); + } +} + +static void +gimp_color_notebook_switch_page (GtkNotebook *gtk_notebook, + gpointer page, + guint page_num, + GimpColorNotebook *notebook) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (notebook); + GtkWidget *page_widget; + GimpColorSelectorModel model; + + page_widget = gtk_notebook_get_nth_page (gtk_notebook, page_num); + + notebook->cur_page = GIMP_COLOR_SELECTOR (page_widget); + + g_signal_handlers_block_by_func (notebook->cur_page, + gimp_color_notebook_color_changed, + notebook); + g_signal_handlers_block_by_func (notebook->cur_page, + gimp_color_notebook_channel_changed, + notebook); + g_signal_handlers_block_by_func (notebook->cur_page, + gimp_color_notebook_model_visible_changed, + notebook); + + gimp_color_selector_set_color (notebook->cur_page, + &selector->rgb, + &selector->hsv); + gimp_color_selector_set_channel (notebook->cur_page, + gimp_color_selector_get_channel (selector)); + + for (model = GIMP_COLOR_SELECTOR_MODEL_RGB; + model <= GIMP_COLOR_SELECTOR_MODEL_HSV; + model++) + { + gboolean visible = gimp_color_selector_get_model_visible (selector, model); + + gimp_color_selector_set_model_visible (notebook->cur_page, model, + visible); + } + + g_signal_handlers_unblock_by_func (notebook->cur_page, + gimp_color_notebook_color_changed, + notebook); + g_signal_handlers_unblock_by_func (notebook->cur_page, + gimp_color_notebook_channel_changed, + notebook); + g_signal_handlers_unblock_by_func (notebook->cur_page, + gimp_color_notebook_model_visible_changed, + notebook); +} + +static void +gimp_color_notebook_color_changed (GimpColorSelector *page, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorNotebook *notebook) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (notebook); + + selector->rgb = *rgb; + selector->hsv = *hsv; + + gimp_color_selector_color_changed (selector); +} + +static void +gimp_color_notebook_channel_changed (GimpColorSelector *page, + GimpColorSelectorChannel channel, + GimpColorNotebook *notebook) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (notebook); + + gimp_color_selector_set_channel (selector, channel); +} + +static void +gimp_color_notebook_model_visible_changed (GimpColorSelector *page, + GimpColorSelectorModel model, + gboolean visible, + GimpColorNotebook *notebook) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (notebook); + + gimp_color_selector_set_model_visible (selector, model, visible); +} + +static GtkWidget * +gimp_color_notebook_add_page (GimpColorNotebook *notebook, + GType page_type) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (notebook); + GimpColorSelectorClass *selector_class; + GtkWidget *page; + GtkWidget *menu_widget; + GtkWidget *image; + GtkWidget *label; + gboolean show_alpha; + + page = gimp_color_selector_new (page_type, + &selector->rgb, + &selector->hsv, + gimp_color_selector_get_channel (selector)); + + if (! page) + return NULL; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (page); + + show_alpha = gimp_color_selector_get_show_alpha (GIMP_COLOR_SELECTOR (notebook)); + gimp_color_selector_set_show_alpha (GIMP_COLOR_SELECTOR (page), show_alpha); + + menu_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + + image = gtk_image_new_from_icon_name (selector_class->icon_name, + GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (menu_widget), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + label = gtk_label_new (gettext (selector_class->name)); + gtk_box_pack_start (GTK_BOX (menu_widget), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + image = gtk_image_new_from_icon_name (selector_class->icon_name, + DEFAULT_TAB_ICON_SIZE); + gimp_help_set_help_data (image, gettext (selector_class->name), NULL); + + gtk_notebook_append_page_menu (GTK_NOTEBOOK (notebook->notebook), + page, image, menu_widget); + + if (! notebook->cur_page) + notebook->cur_page = GIMP_COLOR_SELECTOR (page); + + notebook->selectors = g_list_append (notebook->selectors, page); + + gtk_widget_show (page); + + g_signal_connect (page, "color-changed", + G_CALLBACK (gimp_color_notebook_color_changed), + notebook); + g_signal_connect (page, "channel-changed", + G_CALLBACK (gimp_color_notebook_channel_changed), + notebook); + g_signal_connect (page, "model-visible-changed", + G_CALLBACK (gimp_color_notebook_model_visible_changed), + notebook); + + return page; +} + +static void +gimp_color_notebook_remove_selector (GtkContainer *container, + GtkWidget *widget, + GimpColorNotebook *notebook) +{ + notebook->selectors = g_list_remove (notebook->selectors, widget); + + if (! notebook->selectors) + notebook->cur_page = NULL; +} + + +/** + * gimp_color_notebook_set_has_page: + * @notebook: A #GimpColorNotebook widget. + * @page_type: The #GType of the notebook page to add or remove. + * @has_page: Whether the page should be added or removed. + * + * This function adds and removed pages to / from a #GimpColorNotebook. + * The @page_type passed must be a #GimpColorSelector subtype. + * + * Return value: The new page widget, if @has_page was #TRUE, or #NULL + * if @has_page was #FALSE. + **/ +GtkWidget * +gimp_color_notebook_set_has_page (GimpColorNotebook *notebook, + GType page_type, + gboolean has_page) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_COLOR_NOTEBOOK (notebook), NULL); + g_return_val_if_fail (g_type_is_a (page_type, GIMP_TYPE_COLOR_SELECTOR), + NULL); + g_return_val_if_fail (! g_type_is_a (page_type, GIMP_TYPE_COLOR_NOTEBOOK), + NULL); + + for (list = notebook->selectors; list; list = g_list_next (list)) + { + GimpColorSelector *page = list->data; + + if (G_TYPE_FROM_INSTANCE (page) == page_type) + { + if (has_page) + return GTK_WIDGET (page); + + gtk_container_remove (GTK_CONTAINER (notebook->notebook), + GTK_WIDGET (page)); + + return NULL; + } + } + + if (! has_page) + return NULL; + + return gimp_color_notebook_add_page (notebook, page_type); +} diff --git a/libgimpwidgets/gimpcolornotebook.h b/libgimpwidgets/gimpcolornotebook.h new file mode 100644 index 0000000..37d17e3 --- /dev/null +++ b/libgimpwidgets/gimpcolornotebook.h @@ -0,0 +1,78 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolornotebook.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on color_notebook module + * Copyright (C) 1998 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_NOTEBOOK_H__ +#define __GIMP_COLOR_NOTEBOOK_H__ + +#include <libgimpwidgets/gimpcolorselector.h> + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_NOTEBOOK (gimp_color_notebook_get_type ()) +#define GIMP_COLOR_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_NOTEBOOK, GimpColorNotebook)) +#define GIMP_COLOR_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_NOTEBOOK, GimpColorNotebookClass)) +#define GIMP_IS_COLOR_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_NOTEBOOK)) +#define GIMP_IS_COLOR_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_NOTEBOOK)) +#define GIMP_COLOR_NOTEBOOK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_NOTEBOOK, GimpColorNotebookClass)) + + +typedef struct _GimpColorNotebookClass GimpColorNotebookClass; + +struct _GimpColorNotebook +{ + GimpColorSelector parent_instance; + + GtkWidget *notebook; + + GList *selectors; + GimpColorSelector *cur_page; +}; + +struct _GimpColorNotebookClass +{ + GimpColorSelectorClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_notebook_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_notebook_set_has_page (GimpColorNotebook *notebook, + GType page_type, + gboolean has_page); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_NOTEBOOK_H__ */ diff --git a/libgimpwidgets/gimpcolorprofilechooserdialog.c b/libgimpwidgets/gimpcolorprofilechooserdialog.c new file mode 100644 index 0000000..7849e8f --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilechooserdialog.c @@ -0,0 +1,355 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpColorProfileChooserDialog + * Copyright (C) 2006-2014 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#ifdef PLATFORM_OSX +#include <AppKit/AppKit.h> +#endif + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorprofilechooserdialog.h" +#include "gimpcolorprofileview.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorprofilechooserdialog + * @title: GimpColorProfileChooserDialog + * @short_description: A file chooser for selecting color profiles. + * + * A #GtkFileChooser subclass for selecting color profiles. + **/ + + +struct _GimpColorProfileChooserDialogPrivate +{ + GimpColorProfileView *profile_view; +}; + + +static void gimp_color_profile_chooser_dialog_constructed (GObject *object); + +static gboolean gimp_color_profile_chooser_dialog_delete_event (GtkWidget *widget, + GdkEventAny *event); + +static void gimp_color_profile_chooser_dialog_add_shortcut (GimpColorProfileChooserDialog *dialog); +static void gimp_color_profile_chooser_dialog_update_preview (GimpColorProfileChooserDialog *dialog); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpColorProfileChooserDialog, + gimp_color_profile_chooser_dialog, + GTK_TYPE_FILE_CHOOSER_DIALOG) + +#define parent_class gimp_color_profile_chooser_dialog_parent_class + + +static void +gimp_color_profile_chooser_dialog_class_init (GimpColorProfileChooserDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_color_profile_chooser_dialog_constructed; + + widget_class->delete_event = gimp_color_profile_chooser_dialog_delete_event; +} + +static void +gimp_color_profile_chooser_dialog_init (GimpColorProfileChooserDialog *dialog) +{ + dialog->priv = + gimp_color_profile_chooser_dialog_get_instance_private (dialog); +} + +static void +gimp_color_profile_chooser_dialog_constructed (GObject *object) +{ + GimpColorProfileChooserDialog *dialog; + GtkFileFilter *filter; + GtkWidget *scrolled_window; + GtkWidget *profile_view; + + dialog = GIMP_COLOR_PROFILE_CHOOSER_DIALOG (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_window_set_role (GTK_WINDOW (dialog), "gimp-profile-chooser-dialog"); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("All files (*.*)")); + gtk_file_filter_add_pattern (filter, "*"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("ICC color profile (*.icc, *.icm)")); + gtk_file_filter_add_pattern (filter, "*.[Ii][Cc][Cc]"); + gtk_file_filter_add_pattern (filter, "*.[Ii][Cc][Mm]"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + + /* the preview widget */ + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (scrolled_window, 300, -1); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + profile_view = gimp_color_profile_view_new (); + gtk_container_add (GTK_CONTAINER (scrolled_window), profile_view); + gtk_widget_show (profile_view); + + dialog->priv->profile_view = GIMP_COLOR_PROFILE_VIEW (profile_view); + + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), + scrolled_window); + + g_signal_connect (dialog, "update-preview", + G_CALLBACK (gimp_color_profile_chooser_dialog_update_preview), + NULL); +} + +static gboolean +gimp_color_profile_chooser_dialog_delete_event (GtkWidget *widget, + GdkEventAny *event) +{ + return TRUE; +} + +GtkWidget * +gimp_color_profile_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + GtkFileChooserAction action) +{ + GtkWidget *dialog; + + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL); + + dialog = g_object_new (GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG, + "title", title, + "action", action, + NULL); + + if (parent) + gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); + + if (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == + GTK_FILE_CHOOSER_ACTION_SAVE) + { + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_ACCEPT, + NULL); + + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + } + else + { + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_ACCEPT, + NULL); + } + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + + gimp_color_profile_chooser_dialog_add_shortcut (GIMP_COLOR_PROFILE_CHOOSER_DIALOG (dialog)); + + return dialog; +} + +/* Add shortcuts for default ICC profile locations */ +static gboolean +add_shortcut (GimpColorProfileChooserDialog *dialog, + const gchar *folder) +{ + return (g_file_test (folder, G_FILE_TEST_IS_DIR) && + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), + folder, NULL)); +} + +static void +gimp_color_profile_chooser_dialog_add_shortcut (GimpColorProfileChooserDialog *dialog) +{ + gboolean save = (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) == + GTK_FILE_CHOOSER_ACTION_SAVE); + +#ifdef G_OS_WIN32 + { + const gchar *prefix = g_getenv ("SystemRoot"); + gchar *folder; + + if (! prefix) + prefix = "c:\\windows"; + + folder = g_strconcat (prefix, "\\system32\\spool\\drivers\\color", NULL); + + add_shortcut (dialog, folder); + + g_free (folder); + } +#elif defined(PLATFORM_OSX) + { + NSAutoreleasePool *pool; + NSArray *path; + NSString *library_dir; + gchar *folder; + gboolean folder_set = FALSE; + + pool = [[NSAutoreleasePool alloc] init]; + + if (save) + { + path = NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, + NSUserDomainMask, YES); + library_dir = [path objectAtIndex:0]; + + folder = g_build_filename ([library_dir UTF8String], + "ColorSync", "Profiles", NULL); + + folder_set = add_shortcut (dialog, folder); + g_free (folder); + } + + if (! folder_set) + { + path = NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, + NSSystemDomainMask, YES); + library_dir = [path objectAtIndex:0]; + + folder = g_build_filename ([library_dir UTF8String], + "ColorSync", "Profiles", NULL); + + add_shortcut (dialog, folder); + g_free (folder); + } + + [pool drain]; + } +#else + { + gboolean folder_set = FALSE; + + if (save) + { + gchar *folder = g_build_filename (g_get_user_data_dir (), + "color", "icc", NULL); + + folder_set = add_shortcut (dialog, folder); + + if (! folder_set) + { + g_free (folder); + + /* Some software, like GNOME color, will save profiles in + * $XDG_DATA_HOME/icc/ + */ + folder = g_build_filename (g_get_user_data_dir (), + "icc", NULL); + + folder_set = add_shortcut (dialog, folder); + } + + if (! folder_set) + { + g_free (folder); + folder = g_build_filename (g_get_home_dir (), + ".color", "icc", NULL); + + folder_set = add_shortcut (dialog, folder); + } + + g_free (folder); + } + + if (! folder_set) + add_shortcut (dialog, COLOR_PROFILE_DIRECTORY); + } +#endif +} + +static void +gimp_color_profile_chooser_dialog_update_preview (GimpColorProfileChooserDialog *dialog) +{ + GimpColorProfile *profile; + GFile *file; + GError *error = NULL; + + file = gtk_file_chooser_get_preview_file (GTK_FILE_CHOOSER (dialog)); + + if (! file) + { + gimp_color_profile_view_set_profile (dialog->priv->profile_view, NULL); + return; + } + + switch (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL)) + { + case G_FILE_TYPE_REGULAR: + profile = gimp_color_profile_new_from_file (file, &error); + + if (! profile) + { + gimp_color_profile_view_set_error (dialog->priv->profile_view, + error->message); + g_clear_error (&error); + } + else + { + gimp_color_profile_view_set_profile (dialog->priv->profile_view, + profile); + g_object_unref (profile); + } + break; + + case G_FILE_TYPE_DIRECTORY: + gimp_color_profile_view_set_error (dialog->priv->profile_view, + _("Folder")); + break; + + default: + gimp_color_profile_view_set_error (dialog->priv->profile_view, + _("Not a regular file.")); + break; + } + + g_object_unref (file); +} diff --git a/libgimpwidgets/gimpcolorprofilechooserdialog.h b/libgimpwidgets/gimpcolorprofilechooserdialog.h new file mode 100644 index 0000000..ca67154 --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilechooserdialog.h @@ -0,0 +1,67 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpColorProfileChooserDialog + * Copyright (C) 2006-2014 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_COLOR_PROFILE_CHOOSER_DIALOG_H__ +#define __GIMP_COLOR_PROFILE_CHOOSER_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG (gimp_color_profile_chooser_dialog_get_type ()) +#define GIMP_COLOR_PROFILE_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG, GimpColorProfileChooserDialog)) +#define GIMP_COLOR_PROFILE_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG, GimpColorProfileChooserDialogClass)) +#define GIMP_IS_COLOR_PROFILE_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG)) +#define GIMP_IS_COLOR_PROFILE_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG)) +#define GIMP_COLOR_PROFILE_CHOOSER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PROFILE_CHOOSER_DIALOG, GimpColorProfileChooserDialogClass)) + + +typedef struct _GimpColorProfileChooserDialogClass GimpColorProfileChooserDialogClass; +typedef struct _GimpColorProfileChooserDialogPrivate GimpColorProfileChooserDialogPrivate; + +struct _GimpColorProfileChooserDialog +{ + GtkFileChooserDialog parent_instance; + + GimpColorProfileChooserDialogPrivate *priv; +}; + +struct _GimpColorProfileChooserDialogClass +{ + GtkFileChooserDialogClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_profile_chooser_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_profile_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + GtkFileChooserAction action); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_PROFILE_CHOOSER_DIALOG_H__ */ diff --git a/libgimpwidgets/gimpcolorprofilecombobox.c b/libgimpwidgets/gimpcolorprofilecombobox.c new file mode 100644 index 0000000..3481365 --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilecombobox.c @@ -0,0 +1,628 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorprofilecombobox.c + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorprofilechooserdialog.h" +#include "gimpcolorprofilecombobox.h" +#include "gimpcolorprofilestore.h" +#include "gimpcolorprofilestore-private.h" + + +/** + * SECTION: gimpcolorprofilecombobox + * @title: GimpColorProfileComboBox + * @short_description: A combo box for selecting color profiles. + * + * A combo box for selecting color profiles. + **/ + + +enum +{ + PROP_0, + PROP_DIALOG, + PROP_MODEL +}; + + +typedef struct +{ + GtkTreePath *last_path; +} GimpColorProfileComboBoxPrivate; + +#define GIMP_COLOR_PROFILE_COMBO_BOX_GET_PRIVATE(obj) \ + ((GimpColorProfileComboBoxPrivate *) gimp_color_profile_combo_box_get_instance_private ((GimpColorProfileComboBox *) (obj))) + + +static void gimp_color_profile_combo_box_finalize (GObject *object); +static void gimp_color_profile_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_color_profile_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_color_profile_combo_box_changed (GtkComboBox *combo); + +static gboolean gimp_color_profile_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); + +static void gimp_color_profile_combo_dialog_response (GimpColorProfileChooserDialog *dialog, + gint response, + GimpColorProfileComboBox *combo); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpColorProfileComboBox, + gimp_color_profile_combo_box, GTK_TYPE_COMBO_BOX) + +#define parent_class gimp_color_profile_combo_box_parent_class + + +static void +gimp_color_profile_combo_box_class_init (GimpColorProfileComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (klass); + + object_class->set_property = gimp_color_profile_combo_box_set_property; + object_class->get_property = gimp_color_profile_combo_box_get_property; + object_class->finalize = gimp_color_profile_combo_box_finalize; + + combo_class->changed = gimp_color_profile_combo_box_changed; + + /** + * GimpColorProfileComboBox:dialog: + * + * #GtkDialog to present when the user selects the + * "Select color profile from disk..." item. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_DIALOG, + g_param_spec_object ("dialog", + "Dialog", + "The dialog to present when selecting profiles from disk", + GTK_TYPE_DIALOG, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); + /** + * GimpColorProfileComboBox:model: + * + * Overrides the "model" property of the #GtkComboBox class. + * #GimpColorProfileComboBox requires the model to be a + * #GimpColorProfileStore. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_MODEL, + g_param_spec_object ("model", + "Model", + "The profile store used for this combo box", + GIMP_TYPE_COLOR_PROFILE_STORE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_color_profile_combo_box_init (GimpColorProfileComboBox *combo_box) +{ + GtkCellRenderer *cell = gtk_cell_renderer_text_new (); + + g_object_set (cell, + "width-chars", 42, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell, + "text", GIMP_COLOR_PROFILE_STORE_LABEL, + NULL); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), + gimp_color_profile_row_separator_func, + NULL, NULL); +} + +static void +gimp_color_profile_combo_box_finalize (GObject *object) +{ + GimpColorProfileComboBox *combo; + GimpColorProfileComboBoxPrivate *priv; + + combo = GIMP_COLOR_PROFILE_COMBO_BOX (object); + + if (combo->dialog) + { + if (GIMP_IS_COLOR_PROFILE_CHOOSER_DIALOG (combo->dialog)) + gtk_widget_destroy (combo->dialog); + + g_object_unref (combo->dialog); + combo->dialog = NULL; + } + + priv = GIMP_COLOR_PROFILE_COMBO_BOX_GET_PRIVATE (combo); + + g_clear_pointer (&priv->last_path, gtk_tree_path_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_profile_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorProfileComboBox *combo_box = GIMP_COLOR_PROFILE_COMBO_BOX (object); + + switch (property_id) + { + case PROP_DIALOG: + g_return_if_fail (combo_box->dialog == NULL); + combo_box->dialog = g_value_dup_object (value); + + if (GIMP_IS_COLOR_PROFILE_CHOOSER_DIALOG (combo_box->dialog)) + g_signal_connect (combo_box->dialog, "response", + G_CALLBACK (gimp_color_profile_combo_dialog_response), + combo_box); + break; + + case PROP_MODEL: + gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_profile_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorProfileComboBox *combo_box = GIMP_COLOR_PROFILE_COMBO_BOX (object); + + switch (property_id) + { + case PROP_DIALOG: + g_value_set_object (value, combo_box->dialog); + break; + + case PROP_MODEL: + g_value_set_object (value, + gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_profile_combo_box_changed (GtkComboBox *combo) +{ + GimpColorProfileComboBoxPrivate *priv; + + GtkTreeModel *model = gtk_combo_box_get_model (combo); + GtkTreeIter iter; + gint type; + + if (! gtk_combo_box_get_active_iter (combo, &iter)) + return; + + gtk_tree_model_get (model, &iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + -1); + + priv = GIMP_COLOR_PROFILE_COMBO_BOX_GET_PRIVATE (combo); + + switch (type) + { + case GIMP_COLOR_PROFILE_STORE_ITEM_DIALOG: + { + GtkWidget *dialog = GIMP_COLOR_PROFILE_COMBO_BOX (combo)->dialog; + GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (combo)); + + if (GTK_IS_WINDOW (parent)) + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (parent)); + + gtk_window_present (GTK_WINDOW (dialog)); + + if (priv->last_path && + gtk_tree_model_get_iter (model, &iter, priv->last_path)) + { + gtk_combo_box_set_active_iter (combo, &iter); + } + } + break; + + case GIMP_COLOR_PROFILE_STORE_ITEM_FILE: + if (priv->last_path) + gtk_tree_path_free (priv->last_path); + + priv->last_path = gtk_tree_model_get_path (model, &iter); + + _gimp_color_profile_store_history_reorder (GIMP_COLOR_PROFILE_STORE (model), + &iter); + break; + + default: + break; + } +} + + +/** + * gimp_color_profile_combo_box_new: + * @dialog: a #GtkDialog to present when the user selects the + * "Select color profile from disk..." item + * @history: filename of the profilerc (or %NULL for no history) + * + * Create a combo-box widget for selecting color profiles. The combo-box + * is populated from the file specified as @history. This filename is + * typically created using the following code snippet: + * <informalexample><programlisting> + * gchar *history = gimp_personal_rc_file ("profilerc"); + * </programlisting></informalexample> + * + * The recommended @dialog type to use is a #GimpColorProfileChooserDialog. + * If a #GimpColorProfileChooserDialog is passed, #GimpColorProfileComboBox + * will take complete control over the dialog, which means connecting + * a GtkDialog::response() callback by itself, and take care of destroying + * the dialog when the combo box is destroyed. + * + * If another type of @dialog is passed, this has to be implemented + * separately. + * + * See also gimp_color_profile_combo_box_new_with_model(). + * + * Return value: a new #GimpColorProfileComboBox. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_color_profile_combo_box_new (GtkWidget *dialog, + const gchar *history) +{ + GtkWidget *combo; + GtkListStore *store; + + g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); + + store = gimp_color_profile_store_new (history); + combo = gimp_color_profile_combo_box_new_with_model (dialog, + GTK_TREE_MODEL (store)); + g_object_unref (store); + + return combo; +} + +/** + * gimp_color_profile_combo_box_new_with_model: + * @dialog: a #GtkDialog to present when the user selects the + * "Select color profile from disk..." item + * @model: a #GimpColorProfileStore object + * + * This constructor is useful when you want to create several + * combo-boxes for profile selection that all share the same + * #GimpColorProfileStore. This is for example done in the + * GIMP Preferences dialog. + * + * See also gimp_color_profile_combo_box_new(). + * + * Return value: a new #GimpColorProfileComboBox. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_color_profile_combo_box_new_with_model (GtkWidget *dialog, + GtkTreeModel *model) +{ + g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); + g_return_val_if_fail (GIMP_IS_COLOR_PROFILE_STORE (model), NULL); + + return g_object_new (GIMP_TYPE_COLOR_PROFILE_COMBO_BOX, + "dialog", dialog, + "model", model, + NULL); +} + +/** + * gimp_color_profile_combo_box_add: + * @combo: a #GimpColorProfileComboBox + * @filename: filename of the profile to add (or %NULL) + * @label: label to use for the profile + * (may only be %NULL if @filename is %NULL) + * + * This function delegates to the underlying + * #GimpColorProfileStore. Please refer to the documentation of + * gimp_color_profile_store_add_file() for details. + * + * Deprecated: use gimp_color_profile_combo_box_add_file() instead. + * + * Since: 2.4 + **/ +void +gimp_color_profile_combo_box_add (GimpColorProfileComboBox *combo, + const gchar *filename, + const gchar *label) +{ + GFile *file = NULL; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_COMBO_BOX (combo)); + g_return_if_fail (label != NULL || filename == NULL); + + if (filename) + file = g_file_new_for_path (filename); + + gimp_color_profile_combo_box_add_file (combo, file, label); + + if (file) + g_object_unref (file); +} + +/** + * gimp_color_profile_combo_box_add_file: + * @combo: a #GimpColorProfileComboBox + * @file: file of the profile to add (or %NULL) + * @label: label to use for the profile + * (may only be %NULL if @file is %NULL) + * + * This function delegates to the underlying + * #GimpColorProfileStore. Please refer to the documentation of + * gimp_color_profile_store_add_file() for details. + * + * Since: 2.10 + **/ +void +gimp_color_profile_combo_box_add_file (GimpColorProfileComboBox *combo, + GFile *file, + const gchar *label) +{ + GtkTreeModel *model; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_COMBO_BOX (combo)); + g_return_if_fail (label != NULL || file == NULL); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + gimp_color_profile_store_add_file (GIMP_COLOR_PROFILE_STORE (model), + file, label); +} + +/** + * gimp_color_profile_combo_box_set_active: + * @combo: a #GimpColorProfileComboBox + * @filename: filename of the profile to select + * @label: label to use when adding a new entry (can be %NULL) + * + * Selects a color profile from the @combo and makes it the active + * item. If the profile is not listed in the @combo, then it is added + * with the given @label (or @filename in case that @label is %NULL). + * + * Deprecated: use gimp_color_profile_combo_box_set_active_file() instead. + * + * Since: 2.4 + **/ +void +gimp_color_profile_combo_box_set_active (GimpColorProfileComboBox *combo, + const gchar *filename, + const gchar *label) +{ + GFile *file = NULL; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_COMBO_BOX (combo)); + + if (filename) + file = g_file_new_for_path (filename); + + gimp_color_profile_combo_box_set_active_file (combo, file, label); + + if (file) + g_object_unref (file); +} + +/** + * gimp_color_profile_combo_box_set_active_file: + * @combo: a #GimpColorProfileComboBox + * @file: file of the profile to select + * @label: label to use when adding a new entry (can be %NULL) + * + * Selects a color profile from the @combo and makes it the active + * item. If the profile is not listed in the @combo, then it is added + * with the given @label (or @file in case that @label is %NULL). + * + * Since: 2.10 + **/ +void +gimp_color_profile_combo_box_set_active_file (GimpColorProfileComboBox *combo, + GFile *file, + const gchar *label) +{ + GimpColorProfile *profile = NULL; + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_COMBO_BOX (combo)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + if (file && ! (label && *label)) + { + GError *error = NULL; + + profile = gimp_color_profile_new_from_file (file, &error); + + if (! profile) + { + g_message ("%s", error->message); + g_clear_error (&error); + } + else + { + label = gimp_color_profile_get_label (profile); + } + } + + if (_gimp_color_profile_store_history_add (GIMP_COLOR_PROFILE_STORE (model), + file, label, &iter)) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + } + + if (profile) + g_object_unref (profile); +} + +/** + * gimp_color_profile_combo_box_get_active: + * @combo: a #GimpColorProfileComboBox + * + * Return value: The filename of the currently selected color profile, + * This is a newly allocated string and should be released + * using g_free() when it is not any longer needed. + * + * Deprecated: use gimp_color_profile_combo_box_get_active_file() instead. + * + * Since: 2.4 + **/ +gchar * +gimp_color_profile_combo_box_get_active (GimpColorProfileComboBox *combo) +{ + GFile *file; + gchar *path = NULL; + + g_return_val_if_fail (GIMP_IS_COLOR_PROFILE_COMBO_BOX (combo), NULL); + + file = gimp_color_profile_combo_box_get_active_file (combo); + + if (file) + { + path = g_file_get_path (file); + g_object_unref (file); + } + + return path; +} + +/** + * gimp_color_profile_combo_box_get_active_file: + * @combo: a #GimpColorProfileComboBox + * + * Return value: The file of the currently selected color profile, + * release using g_object_unref() when it is not any + * longer needed. + * + * Since: 2.10 + **/ +GFile * +gimp_color_profile_combo_box_get_active_file (GimpColorProfileComboBox *combo) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_COLOR_PROFILE_COMBO_BOX (combo), NULL); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) + { + GFile *file; + gint type; + + gtk_tree_model_get (model, &iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + GIMP_COLOR_PROFILE_STORE_FILE, &file, + -1); + + if (type == GIMP_COLOR_PROFILE_STORE_ITEM_FILE) + return file; + + if (file) + g_object_unref (file); + } + + return NULL; +} + +static gboolean +gimp_color_profile_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gint type; + + gtk_tree_model_get (model, iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + -1); + + switch (type) + { + case GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_TOP: + case GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_BOTTOM: + return TRUE; + + default: + return FALSE; + } +} + +static void +gimp_color_profile_combo_dialog_response (GimpColorProfileChooserDialog *dialog, + gint response, + GimpColorProfileComboBox *combo) +{ + if (response == GTK_RESPONSE_ACCEPT) + { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + + if (file) + { + gimp_color_profile_combo_box_set_active_file (combo, file, NULL); + + g_object_unref (file); + } + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} diff --git a/libgimpwidgets/gimpcolorprofilecombobox.h b/libgimpwidgets/gimpcolorprofilecombobox.h new file mode 100644 index 0000000..e52b48b --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilecombobox.h @@ -0,0 +1,90 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorprofilecombobox.h + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_PROFILE_COMBO_BOX_H__ +#define __GIMP_COLOR_PROFILE_COMBO_BOX_H__ + +G_BEGIN_DECLS + +#define GIMP_TYPE_COLOR_PROFILE_COMBO_BOX (gimp_color_profile_combo_box_get_type ()) +#define GIMP_COLOR_PROFILE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PROFILE_COMBO_BOX, GimpColorProfileComboBox)) +#define GIMP_COLOR_PROFILE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PROFILE_COMBO_BOX, GimpColorProfileComboBoxClass)) +#define GIMP_IS_COLOR_PROFILE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PROFILE_COMBO_BOX)) +#define GIMP_IS_COLOR_PROFILE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PROFILE_COMBO_BOX)) +#define GIMP_COLOR_PROFILE_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PROFILE_COMBO_BOX, GimpColorProfileComboBoxClass)) + + +typedef struct _GimpColorProfileComboBoxClass GimpColorProfileComboBoxClass; + +struct _GimpColorProfileComboBox +{ + GtkComboBox parent_instance; + + GtkWidget *dialog; +}; + +struct _GimpColorProfileComboBoxClass +{ + GtkComboBoxClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_profile_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_profile_combo_box_new (GtkWidget *dialog, + const gchar *history); +GtkWidget * gimp_color_profile_combo_box_new_with_model (GtkWidget *dialog, + GtkTreeModel *model); + +GIMP_DEPRECATED_FOR (gimp_color_profile_combo_box_add_file) +void gimp_color_profile_combo_box_add (GimpColorProfileComboBox *combo, + const gchar *filename, + const gchar *label); +void gimp_color_profile_combo_box_add_file (GimpColorProfileComboBox *combo, + GFile *file, + const gchar *label); + +GIMP_DEPRECATED_FOR (gimp_color_profile_combo_box_set_active_file) +void gimp_color_profile_combo_box_set_active (GimpColorProfileComboBox *combo, + const gchar *filename, + const gchar *label); +void gimp_color_profile_combo_box_set_active_file (GimpColorProfileComboBox *combo, + GFile *file, + const gchar *label); + +GIMP_DEPRECATED_FOR (gimp_color_profile_combo_box_get_active_file) +gchar * gimp_color_profile_combo_box_get_active (GimpColorProfileComboBox *combo); +GFile * gimp_color_profile_combo_box_get_active_file (GimpColorProfileComboBox *combo); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_PROFILE_COMBO_BOX_H__ */ diff --git a/libgimpwidgets/gimpcolorprofilestore-private.h b/libgimpwidgets/gimpcolorprofilestore-private.h new file mode 100644 index 0000000..98de84d --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilestore-private.h @@ -0,0 +1,52 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpprofilestore-private.h + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_COLOR_PROFILE_STORE_PRIVATE_H__ +#define __GIMP_COLOR_PROFILE_STORE_PRIVATE_H__ + + +typedef enum +{ + GIMP_COLOR_PROFILE_STORE_ITEM_FILE, + GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_TOP, + GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_BOTTOM, + GIMP_COLOR_PROFILE_STORE_ITEM_DIALOG +} GimpColorProfileStoreItemType; + +typedef enum +{ + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, + GIMP_COLOR_PROFILE_STORE_LABEL, + GIMP_COLOR_PROFILE_STORE_FILE, + GIMP_COLOR_PROFILE_STORE_INDEX +} GimpColorProfileStoreColumns; + + +G_GNUC_INTERNAL gboolean _gimp_color_profile_store_history_add (GimpColorProfileStore *store, + GFile *file, + const gchar *label, + GtkTreeIter *iter); + +G_GNUC_INTERNAL void _gimp_color_profile_store_history_reorder (GimpColorProfileStore *store, + GtkTreeIter *iter); + + +#endif /* __GIMP_COLOR_PROFILE_STORE_PRIVATE_H__ */ diff --git a/libgimpwidgets/gimpcolorprofilestore.c b/libgimpwidgets/gimpcolorprofilestore.c new file mode 100644 index 0000000..324e4c6 --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilestore.c @@ -0,0 +1,794 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpprofilestore.c + * Copyright (C) 2004-2008 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorprofilestore.h" +#include "gimpcolorprofilestore-private.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorprofilestore + * @title: GimpColorProfileStore + * @short_description: A #GtkListStore subclass that keep color profiles. + * + * A #GtkListStore subclass that keep color profiles. + **/ + + +#define HISTORY_SIZE 8 + +enum +{ + PROP_0, + PROP_HISTORY +}; + + +static void gimp_color_profile_store_constructed (GObject *object); +static void gimp_color_profile_store_dispose (GObject *object); +static void gimp_color_profile_store_finalize (GObject *object); +static void gimp_color_profile_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_color_profile_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_color_profile_store_history_insert (GimpColorProfileStore *store, + GtkTreeIter *iter, + GFile *file, + const gchar *label, + gint index); +static void gimp_color_profile_store_get_separator (GimpColorProfileStore *store, + GtkTreeIter *iter, + gboolean top); +static gboolean gimp_color_profile_store_save (GimpColorProfileStore *store, + const gchar *filename, + GError **error); +static gboolean gimp_color_profile_store_load (GimpColorProfileStore *store, + const gchar *filename, + GError **error); + + +G_DEFINE_TYPE (GimpColorProfileStore, + gimp_color_profile_store, GTK_TYPE_LIST_STORE) + +#define parent_class gimp_color_profile_store_parent_class + + +static void +gimp_color_profile_store_class_init (GimpColorProfileStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_color_profile_store_constructed; + object_class->dispose = gimp_color_profile_store_dispose; + object_class->finalize = gimp_color_profile_store_finalize; + object_class->set_property = gimp_color_profile_store_set_property; + object_class->get_property = gimp_color_profile_store_get_property; + + /** + * GimpColorProfileStore:history: + * + * Filename of the color history used to populate the profile store. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_HISTORY, + g_param_spec_string ("history", + "History", + "Filename of the color history used to populate the profile store", + NULL, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_color_profile_store_init (GimpColorProfileStore *store) +{ + GType types[] = + { + G_TYPE_INT, /* GIMP_COLOR_PROFILE_STORE_ITEM_TYPE */ + G_TYPE_STRING, /* GIMP_COLOR_PROFILE_STORE_LABEL */ + G_TYPE_FILE, /* GIMP_COLOR_PROFILE_STORE_FILE */ + G_TYPE_INT /* GIMP_COLOR_PROFILE_STORE_INDEX */ + }; + + gtk_list_store_set_column_types (GTK_LIST_STORE (store), + G_N_ELEMENTS (types), types); +} + +static void +gimp_color_profile_store_constructed (GObject *object) +{ + GimpColorProfileStore *store = GIMP_COLOR_PROFILE_STORE (object); + GtkTreeIter iter; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, + GIMP_COLOR_PROFILE_STORE_ITEM_DIALOG, + GIMP_COLOR_PROFILE_STORE_LABEL, + _("Select color profile from disk..."), + -1); + + if (store->history) + { + gimp_color_profile_store_load (store, store->history, NULL); + } +} + +static void +gimp_color_profile_store_dispose (GObject *object) +{ + GimpColorProfileStore *store = GIMP_COLOR_PROFILE_STORE (object); + + if (store->history) + { + gimp_color_profile_store_save (store, store->history, NULL); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_profile_store_finalize (GObject *object) +{ + GimpColorProfileStore *store = GIMP_COLOR_PROFILE_STORE (object); + + g_clear_pointer (&store->history, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_profile_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorProfileStore *store = GIMP_COLOR_PROFILE_STORE (object); + + switch (property_id) + { + case PROP_HISTORY: + g_return_if_fail (store->history == NULL); + store->history = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_profile_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorProfileStore *store = GIMP_COLOR_PROFILE_STORE (object); + + switch (property_id) + { + case PROP_HISTORY: + g_value_set_string (value, store->history); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/** + * gimp_color_profile_store_new: + * @history: filename of the profilerc (or %NULL for no history) + * + * Creates a new #GimpColorProfileStore object and populates it with + * last used profiles read from the file @history. The updated history + * is written back to disk when the store is disposed. + * + * The filename passed as @history is typically created using the + * following code snippet: + * <informalexample><programlisting> + * gchar *history = gimp_personal_rc_file ("profilerc"); + * </programlisting></informalexample> + * + * Return value: a new #GimpColorProfileStore + * + * Since: 2.4 + **/ +GtkListStore * +gimp_color_profile_store_new (const gchar *history) +{ + return g_object_new (GIMP_TYPE_COLOR_PROFILE_STORE, + "history", history, + NULL); +} + +/** + * gimp_color_profile_store_add: + * @store: a #GimpColorProfileStore + * @filename: filename of the profile to add (or %NULL) + * @label: label to use for the profile + * (may only be %NULL if @filename is %NULL) + * + * Adds a color profile item to the #GimpColorProfileStore. Items + * added with this function will be kept at the top, separated from + * the history of last used color profiles. + * + * This function is often used to add a selectable item for the %NULL + * filename. If you pass %NULL for both @filename and @label, the + * @label will be set to the string "None" for you (and translated for + * the user). + * + * Deprecated: use gimp_color_profile_store_add_file() instead. + * + * Since: 2.4 + **/ +void +gimp_color_profile_store_add (GimpColorProfileStore *store, + const gchar *filename, + const gchar *label) +{ + GFile *file = NULL; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_STORE (store)); + g_return_if_fail (label != NULL || filename == NULL); + + if (filename) + file = g_file_new_for_path (filename); + + gimp_color_profile_store_add_file (store, file, label); + + g_object_unref (file); +} + +/** + * gimp_color_profile_store_add_file: + * @store: a #GimpColorProfileStore + * @file: file of the profile to add (or %NULL) + * @label: label to use for the profile + * (may only be %NULL if @filename is %NULL) + * + * Adds a color profile item to the #GimpColorProfileStore. Items + * added with this function will be kept at the top, separated from + * the history of last used color profiles. + * + * This function is often used to add a selectable item for the %NULL + * file. If you pass %NULL for both @file and @label, the @label will + * be set to the string "None" for you (and translated for the user). + * + * Since: 2.10 + **/ +void +gimp_color_profile_store_add_file (GimpColorProfileStore *store, + GFile *file, + const gchar *label) +{ + GtkTreeIter separator; + GtkTreeIter iter; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_STORE (store)); + g_return_if_fail (label != NULL || file == NULL); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (! file && ! label) + label = C_("profile", "None"); + + gimp_color_profile_store_get_separator (store, &separator, TRUE); + + gtk_list_store_insert_before (GTK_LIST_STORE (store), &iter, &separator); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, + GIMP_COLOR_PROFILE_STORE_ITEM_FILE, + GIMP_COLOR_PROFILE_STORE_FILE, file, + GIMP_COLOR_PROFILE_STORE_LABEL, label, + GIMP_COLOR_PROFILE_STORE_INDEX, -1, + -1); +} + +/** + * _gimp_color_profile_store_history_add: + * @store: a #GimpColorProfileStore + * @file: file of the profile to add (or %NULL) + * @label: label to use for the profile (or %NULL) + * @iter: a #GtkTreeIter + * + * Return value: %TRUE if the iter is valid and pointing to the item + * + * Since: 2.4 + **/ +gboolean +_gimp_color_profile_store_history_add (GimpColorProfileStore *store, + GFile *file, + const gchar *label, + GtkTreeIter *iter) +{ + GtkTreeModel *model; + gboolean iter_valid; + gint max = -1; + + g_return_val_if_fail (GIMP_IS_COLOR_PROFILE_STORE (store), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + model = GTK_TREE_MODEL (store); + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + gint type; + gint index; + GFile *this; + + gtk_tree_model_get (model, iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + GIMP_COLOR_PROFILE_STORE_INDEX, &index, + -1); + + if (type != GIMP_COLOR_PROFILE_STORE_ITEM_FILE) + continue; + + if (index > max) + max = index; + + /* check if we found a filename match */ + gtk_tree_model_get (model, iter, + GIMP_COLOR_PROFILE_STORE_FILE, &this, + -1); + + if ((this && file && g_file_equal (this, file)) || + (! this && ! file)) + { + /* update the label */ + if (label && *label) + gtk_list_store_set (GTK_LIST_STORE (store), iter, + GIMP_COLOR_PROFILE_STORE_LABEL, label, + -1); + + if (this) + g_object_unref (this); + + return TRUE; + } + + if (this) + g_object_unref (this); + } + + if (! file) + return FALSE; + + if (label && *label) + { + iter_valid = gimp_color_profile_store_history_insert (store, iter, + file, label, + ++max); + } + else + { + const gchar *utf8 = gimp_file_get_utf8_name (file); + gchar *basename = g_path_get_basename (utf8); + + iter_valid = gimp_color_profile_store_history_insert (store, iter, + file, basename, + ++max); + g_free (basename); + } + + return iter_valid; +} + +/** + * _gimp_color_profile_store_history_reorder + * @store: a #GimpColorProfileStore + * @iter: a #GtkTreeIter + * + * Moves the entry pointed to by @iter to the front of the MRU list. + * + * Since: 2.4 + **/ +void +_gimp_color_profile_store_history_reorder (GimpColorProfileStore *store, + GtkTreeIter *iter) +{ + GtkTreeModel *model; + gint index; + gboolean iter_valid; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_STORE (store)); + g_return_if_fail (iter != NULL); + + model = GTK_TREE_MODEL (store); + + gtk_tree_model_get (model, iter, + GIMP_COLOR_PROFILE_STORE_INDEX, &index, + -1); + + if (index == 0) + return; /* already at the top */ + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + gint type; + gint this_index; + + gtk_tree_model_get (model, iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + GIMP_COLOR_PROFILE_STORE_INDEX, &this_index, + -1); + + if (type == GIMP_COLOR_PROFILE_STORE_ITEM_FILE && this_index > -1) + { + if (this_index < index) + { + this_index++; + } + else if (this_index == index) + { + this_index = 0; + } + + gtk_list_store_set (GTK_LIST_STORE (store), iter, + GIMP_COLOR_PROFILE_STORE_INDEX, this_index, + -1); + } + } +} + +static gboolean +gimp_color_profile_store_history_insert (GimpColorProfileStore *store, + GtkTreeIter *iter, + GFile *file, + const gchar *label, + gint index) +{ + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter sibling; + gboolean iter_valid; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (label != NULL, FALSE); + g_return_val_if_fail (index > -1, FALSE); + + gimp_color_profile_store_get_separator (store, iter, FALSE); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &sibling); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &sibling)) + { + gint type; + gint this_index; + + gtk_tree_model_get (model, &sibling, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + GIMP_COLOR_PROFILE_STORE_INDEX, &this_index, + -1); + + if (type == GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_BOTTOM) + { + gtk_list_store_insert_before (GTK_LIST_STORE (store), + iter, &sibling); + break; + } + + if (type == GIMP_COLOR_PROFILE_STORE_ITEM_FILE && this_index > -1) + { + gchar *this_label; + + gtk_tree_model_get (model, &sibling, + GIMP_COLOR_PROFILE_STORE_LABEL, &this_label, + -1); + + if (this_label && g_utf8_collate (label, this_label) < 0) + { + gtk_list_store_insert_before (GTK_LIST_STORE (store), + iter, &sibling); + g_free (this_label); + break; + } + + g_free (this_label); + } + } + + if (iter_valid) + gtk_list_store_set (GTK_LIST_STORE (store), iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, + GIMP_COLOR_PROFILE_STORE_ITEM_FILE, + GIMP_COLOR_PROFILE_STORE_FILE, file, + GIMP_COLOR_PROFILE_STORE_LABEL, label, + GIMP_COLOR_PROFILE_STORE_INDEX, index, + -1); + + return iter_valid; +} + +static void +gimp_color_profile_store_create_separator (GimpColorProfileStore *store, + GtkTreeIter *iter, + gboolean top) +{ + if (top) + { + gtk_list_store_prepend (GTK_LIST_STORE (store), iter); + } + else + { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter sibling; + gboolean iter_valid; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &sibling); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &sibling)) + { + gint type; + + gtk_tree_model_get (model, &sibling, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + -1); + + if (type == GIMP_COLOR_PROFILE_STORE_ITEM_DIALOG) + break; + } + + if (iter_valid) + gtk_list_store_insert_before (GTK_LIST_STORE (store), iter, &sibling); + } + + gtk_list_store_set (GTK_LIST_STORE (store), iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, + top ? + GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_TOP : + GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_BOTTOM, + GIMP_COLOR_PROFILE_STORE_INDEX, -1, + -1); +} + +static void +gimp_color_profile_store_get_separator (GimpColorProfileStore *store, + GtkTreeIter *iter, + gboolean top) +{ + GtkTreeModel *model = GTK_TREE_MODEL (store); + gboolean iter_valid; + gint needle; + + needle = (top ? + GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_TOP : + GIMP_COLOR_PROFILE_STORE_ITEM_SEPARATOR_BOTTOM); + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + gint type; + + gtk_tree_model_get (model, iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + -1); + + if (type == needle) + return; + } + + gimp_color_profile_store_create_separator (store, iter, top); +} + +static GTokenType +gimp_color_profile_store_load_profile (GimpColorProfileStore *store, + GScanner *scanner, + gint index) +{ + GtkTreeIter iter; + gchar *label = NULL; + gchar *path = NULL; + + if (gimp_scanner_parse_string (scanner, &label) && + gimp_scanner_parse_string (scanner, &path)) + { + GFile *file = NULL; + + if (g_str_has_prefix (path, "file://")) + { + file = g_file_new_for_uri (path); + } + else + { + file = gimp_file_new_for_config_path (path, NULL); + } + + if (file) + { + if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_REGULAR) + { + gimp_color_profile_store_history_insert (store, &iter, + file, label, index); + } + + g_object_unref (file); + } + + g_free (label); + g_free (path); + + return G_TOKEN_RIGHT_PAREN; + } + + g_free (label); + g_free (path); + + return G_TOKEN_STRING; +} + +static gboolean +gimp_color_profile_store_load (GimpColorProfileStore *store, + const gchar *filename, + GError **error) +{ + GScanner *scanner; + GTokenType token; + gint i = 0; + + scanner = gimp_scanner_new_file (filename, error); + if (! scanner) + return FALSE; + + g_scanner_scope_add_symbol (scanner, 0, "color-profile", NULL); + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_SYMBOL; + break; + + case G_TOKEN_SYMBOL: + token = gimp_color_profile_store_load_profile (store, scanner, i++); + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: /* do nothing */ + break; + } + } + + if (token != G_TOKEN_LEFT_PAREN) + { + g_scanner_get_next_token (scanner); + g_scanner_unexp_token (scanner, token, NULL, NULL, NULL, + _("fatal parse error"), TRUE); + } + + gimp_scanner_destroy (scanner); + + return TRUE; +} + +static gboolean +gimp_color_profile_store_save (GimpColorProfileStore *store, + const gchar *filename, + GError **error) +{ + GimpConfigWriter *writer; + GtkTreeModel *model; + GtkTreeIter iter; + gchar *labels[HISTORY_SIZE] = { NULL, }; + GFile *files[HISTORY_SIZE] = { NULL, }; + gboolean iter_valid; + gint i; + + writer = gimp_config_writer_new_file (filename, + TRUE, + "GIMP color profile history", + error); + if (! writer) + return FALSE; + + model = GTK_TREE_MODEL (store); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gint type; + gint index; + + gtk_tree_model_get (model, &iter, + GIMP_COLOR_PROFILE_STORE_ITEM_TYPE, &type, + GIMP_COLOR_PROFILE_STORE_INDEX, &index, + -1); + + if (type == GIMP_COLOR_PROFILE_STORE_ITEM_FILE && + index >= 0 && + index < HISTORY_SIZE) + { + if (labels[index] || files[index]) + g_warning ("%s: double index %d", G_STRFUNC, index); + + gtk_tree_model_get (model, &iter, + GIMP_COLOR_PROFILE_STORE_LABEL, + &labels[index], + GIMP_COLOR_PROFILE_STORE_FILE, + &files[index], + -1); + } + } + + + for (i = 0; i < HISTORY_SIZE; i++) + { + if (files[i] && labels[i]) + { + gchar *path = gimp_file_get_config_path (files[i], NULL); + + if (path) + { + gimp_config_writer_open (writer, "color-profile"); + gimp_config_writer_string (writer, labels[i]); + gimp_config_writer_string (writer, path); + gimp_config_writer_close (writer); + + g_free (path); + } + } + + if (files[i]) + g_object_unref (files[i]); + + g_free (labels[i]); + } + + return gimp_config_writer_finish (writer, + "end of color profile history", error); +} diff --git a/libgimpwidgets/gimpcolorprofilestore.h b/libgimpwidgets/gimpcolorprofilestore.h new file mode 100644 index 0000000..b1f8108 --- /dev/null +++ b/libgimpwidgets/gimpcolorprofilestore.h @@ -0,0 +1,76 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpprofilestore.h + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_PROFILE_STORE_H__ +#define __GIMP_COLOR_PROFILE_STORE_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_PROFILE_STORE (gimp_color_profile_store_get_type ()) +#define GIMP_COLOR_PROFILE_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PROFILE_STORE, GimpColorProfileStore)) +#define GIMP_COLOR_PROFILE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PROFILE_STORE, GimpColorProfileStoreClass)) +#define GIMP_IS_COLOR_PROFILE_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PROFILE_STORE)) +#define GIMP_IS_COLOR_PROFILE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PROFILE_STORE)) +#define GIMP_COLOR_PROFILE_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PROFILE_STORE, GimpColorProfileStoreClass)) + + +typedef struct _GimpColorProfileStoreClass GimpColorProfileStoreClass; + +struct _GimpColorProfileStore +{ + GtkListStore parent_instance; + + gchar *history; +}; + +struct _GimpColorProfileStoreClass +{ + GtkListStoreClass parent_class; + + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_profile_store_get_type (void) G_GNUC_CONST; + +GtkListStore * gimp_color_profile_store_new (const gchar *history); + +GIMP_DEPRECATED_FOR(gimp_color_profile_store_add_file) +void gimp_color_profile_store_add (GimpColorProfileStore *store, + const gchar *filename, + const gchar *label); + +void gimp_color_profile_store_add_file (GimpColorProfileStore *store, + GFile *file, + const gchar *label); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_PROFILE_STORE_H__ */ diff --git a/libgimpwidgets/gimpcolorprofileview.c b/libgimpwidgets/gimpcolorprofileview.c new file mode 100644 index 0000000..558392a --- /dev/null +++ b/libgimpwidgets/gimpcolorprofileview.c @@ -0,0 +1,209 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpColorProfileView + * Copyright (C) 2014 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 "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorprofileview.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorprofileview + * @title: GimpColorProfileView + * @short_description: A widget for viewing color profile properties + * + * A widget for viewing the properties of a #GimpColorProfile. + **/ + + +struct _GimpColorProfileViewPrivate +{ + GimpColorProfile *profile; +}; + + +static void gimp_color_profile_view_constructed (GObject *object); +static void gimp_color_profile_view_finalize (GObject *object); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpColorProfileView, gimp_color_profile_view, + GTK_TYPE_TEXT_VIEW) + +#define parent_class gimp_color_profile_view_parent_class + + +static void +gimp_color_profile_view_class_init (GimpColorProfileViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_color_profile_view_constructed; + object_class->finalize = gimp_color_profile_view_finalize; +} + +static void +gimp_color_profile_view_init (GimpColorProfileView *view) +{ + view->priv = gimp_color_profile_view_get_instance_private (view); +} + +static void +gimp_color_profile_view_constructed (GObject *object) +{ + GtkTextBuffer *buffer; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (object)); + + gtk_text_buffer_create_tag (buffer, "text", + NULL); + gtk_text_buffer_create_tag (buffer, "title", + "weight", PANGO_WEIGHT_BOLD, + "scale", PANGO_SCALE_LARGE, + NULL); + gtk_text_buffer_create_tag (buffer, "header", + "weight", PANGO_WEIGHT_BOLD, + NULL); + gtk_text_buffer_create_tag (buffer, "error", + "style", PANGO_STYLE_OBLIQUE, + NULL); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (object), FALSE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (object), GTK_WRAP_WORD); + + gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (object), 6); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (object), 6); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (object), 6); +} + +static void +gimp_color_profile_view_finalize (GObject *object) +{ + GimpColorProfileView *view = GIMP_COLOR_PROFILE_VIEW (object); + + g_clear_object (&view->priv->profile); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GtkWidget * +gimp_color_profile_view_new (void) +{ + return g_object_new (GIMP_TYPE_COLOR_PROFILE_VIEW, NULL); +} + +void +gimp_color_profile_view_set_profile (GimpColorProfileView *view, + GimpColorProfile *profile) +{ + GtkTextBuffer *buffer; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_VIEW (view)); + g_return_if_fail (profile == NULL || GIMP_IS_COLOR_PROFILE (profile)); + + if (profile == view->priv->profile) + return; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + gtk_text_buffer_set_text (buffer, "", 0); + + if (g_set_object (&view->priv->profile, profile) && profile) + { + GtkTextIter iter; + const gchar *text; + + gtk_text_buffer_get_start_iter (buffer, &iter); + + text = gimp_color_profile_get_label (profile); + if (text && strlen (text)) + { + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + text, -1, + "title", NULL); + gtk_text_buffer_insert (buffer, &iter, "\n", 1); + } + + text = gimp_color_profile_get_model (profile); + if (text && strlen (text)) + { + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + text, -1, + "text", NULL); + gtk_text_buffer_insert (buffer, &iter, "\n", 1); + } + + text = gimp_color_profile_get_manufacturer (profile); + if (text && strlen (text)) + { + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + _("Manufacturer: "), -1, + "header", NULL); + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + text, -1, + "text", NULL); + gtk_text_buffer_insert (buffer, &iter, "\n", 1); + } + + text = gimp_color_profile_get_copyright (profile); + if (text && strlen (text)) + { + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + _("Copyright: "), -1, + "header", NULL); + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + text, -1, + "text", NULL); + gtk_text_buffer_insert (buffer, &iter, "\n", 1); + } + } +} + +void +gimp_color_profile_view_set_error (GimpColorProfileView *view, + const gchar *message) +{ + GtkTextBuffer *buffer; + GtkTextIter iter; + + g_return_if_fail (GIMP_IS_COLOR_PROFILE_VIEW (view)); + g_return_if_fail (message != NULL); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + gtk_text_buffer_set_text (buffer, "", 0); + + gtk_text_buffer_get_start_iter (buffer, &iter); + + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + message, -1, + "error", NULL); +} diff --git a/libgimpwidgets/gimpcolorprofileview.h b/libgimpwidgets/gimpcolorprofileview.h new file mode 100644 index 0000000..4843a69 --- /dev/null +++ b/libgimpwidgets/gimpcolorprofileview.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpColorProfileView + * Copyright (C) 2014 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_COLOR_PROFILE_VIEW_H__ +#define __GIMP_COLOR_PROFILE_VIEW_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_PROFILE_VIEW (gimp_color_profile_view_get_type ()) +#define GIMP_COLOR_PROFILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PROFILE_VIEW, GimpColorProfileView)) +#define GIMP_COLOR_PROFILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PROFILE_VIEW, GimpColorProfileViewClass)) +#define GIMP_IS_COLOR_PROFILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PROFILE_VIEW)) +#define GIMP_IS_COLOR_PROFILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PROFILE_VIEW)) +#define GIMP_COLOR_PROFILE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PROFILE_VIEW, GimpColorProfileViewClass)) + + +typedef struct _GimpColorProfileViewClass GimpColorProfileViewClass; +typedef struct _GimpColorProfileViewPrivate GimpColorProfileViewPrivate; + +struct _GimpColorProfileView +{ + GtkTextView parent_instance; + + GimpColorProfileViewPrivate *priv; +}; + +struct _GimpColorProfileViewClass +{ + GtkTextViewClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_profile_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_profile_view_new (void); + +void gimp_color_profile_view_set_profile (GimpColorProfileView *view, + GimpColorProfile *profile); +void gimp_color_profile_view_set_error (GimpColorProfileView *view, + const gchar *message); + +G_END_DECLS + +#endif /* __GIMP_COLOR_PROFILE_VIEW_H__ */ diff --git a/libgimpwidgets/gimpcolorscale.c b/libgimpwidgets/gimpcolorscale.c new file mode 100644 index 0000000..f87ab08 --- /dev/null +++ b/libgimpwidgets/gimpcolorscale.c @@ -0,0 +1,1133 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorscale.c + * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcairo-utils.h" +#include "gimpcolorscale.h" +#include "gimpwidgetsutils.h" + + +/** + * SECTION: gimpcolorscale + * @title: GimpColorScale + * @short_description: Fancy colored sliders. + * + * Fancy colored sliders. + **/ + + +enum +{ + PROP_0, + PROP_CHANNEL +}; + + +typedef struct _GimpLCH GimpLCH; + +struct _GimpLCH +{ + gdouble l, c, h, a; +}; + + +typedef struct _GimpColorScalePrivate GimpColorScalePrivate; + +struct _GimpColorScalePrivate +{ + GimpColorConfig *config; + GimpColorTransform *transform; + guchar oog_color[3]; +}; + +#define GET_PRIVATE(obj) \ + ((GimpColorScalePrivate *) gimp_color_scale_get_instance_private ((GimpColorScale *) (obj))) + + +static void gimp_color_scale_dispose (GObject *object); +static void gimp_color_scale_finalize (GObject *object); +static void gimp_color_scale_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_color_scale_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_color_scale_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_color_scale_state_changed (GtkWidget *widget, + GtkStateType previous_state); +static gboolean gimp_color_scale_button_press (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_color_scale_button_release (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_color_scale_scroll (GtkWidget *widget, + GdkEventScroll *event); +static gboolean gimp_color_scale_expose (GtkWidget *widget, + GdkEventExpose *event); + +static void gimp_color_scale_render (GimpColorScale *scale); +static void gimp_color_scale_render_alpha (GimpColorScale *scale); +static void gimp_color_scale_render_stipple (GimpColorScale *scale); + +static void gimp_color_scale_create_transform (GimpColorScale *scale); +static void gimp_color_scale_destroy_transform (GimpColorScale *scale); +static void gimp_color_scale_notify_config (GimpColorConfig *config, + const GParamSpec *pspec, + GimpColorScale *scale); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpColorScale, gimp_color_scale, GTK_TYPE_SCALE) + +#define parent_class gimp_color_scale_parent_class + +static const Babl *fish_rgb_to_lch = NULL; +static const Babl *fish_lch_to_rgb = NULL; + + +static void +gimp_color_scale_class_init (GimpColorScaleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_color_scale_dispose; + object_class->finalize = gimp_color_scale_finalize; + object_class->get_property = gimp_color_scale_get_property; + object_class->set_property = gimp_color_scale_set_property; + + widget_class->size_allocate = gimp_color_scale_size_allocate; + widget_class->state_changed = gimp_color_scale_state_changed; + widget_class->button_press_event = gimp_color_scale_button_press; + widget_class->button_release_event = gimp_color_scale_button_release; + widget_class->scroll_event = gimp_color_scale_scroll; + widget_class->expose_event = gimp_color_scale_expose; + + /** + * GimpColorScale:channel: + * + * The channel which is edited by the color scale. + * + * Since: 2.8 + */ + g_object_class_install_property (object_class, PROP_CHANNEL, + g_param_spec_enum ("channel", + "Channel", + "The channel which is edited by the color scale", + GIMP_TYPE_COLOR_SELECTOR_CHANNEL, + GIMP_COLOR_SELECTOR_VALUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + fish_rgb_to_lch = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE LCH(ab) double")); + fish_lch_to_rgb = babl_fish (babl_format ("CIE LCH(ab) double"), + babl_format ("R'G'B' double")); +} + +static void +gimp_color_scale_dispose (GObject *object) +{ + GimpColorScale *scale = GIMP_COLOR_SCALE (object); + + gimp_color_scale_set_color_config (scale, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_scale_init (GimpColorScale *scale) +{ + GtkRange *range = GTK_RANGE (scale); + + gtk_range_set_slider_size_fixed (range, TRUE); + gtk_range_set_flippable (GTK_RANGE (scale), TRUE); + + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + + scale->channel = GIMP_COLOR_SELECTOR_VALUE; + scale->needs_render = TRUE; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (range), + GTK_ORIENTATION_HORIZONTAL); + + gimp_rgba_set (&scale->rgb, 0.0, 0.0, 0.0, 1.0); + gimp_rgb_to_hsv (&scale->rgb, &scale->hsv); + + gimp_widget_track_monitor (GTK_WIDGET (scale), + G_CALLBACK (gimp_color_scale_destroy_transform), + NULL); +} + +static void +gimp_color_scale_finalize (GObject *object) +{ + GimpColorScale *scale = GIMP_COLOR_SCALE (object); + + g_clear_pointer (&scale->buf, g_free); + scale->width = 0; + scale->height = 0; + scale->rowstride = 0; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_scale_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorScale *scale = GIMP_COLOR_SCALE (object); + + switch (property_id) + { + case PROP_CHANNEL: + g_value_set_enum (value, scale->channel); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_scale_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorScale *scale = GIMP_COLOR_SCALE (object); + + switch (property_id) + { + case PROP_CHANNEL: + gimp_color_scale_set_channel (scale, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_scale_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpColorScale *scale = GIMP_COLOR_SCALE (widget); + GtkRange *range = GTK_RANGE (widget); + GdkRectangle range_rect; + gint focus = 0; + gint trough_border; + gint scale_width; + gint scale_height; + + gtk_widget_style_get (widget, + "trough-border", &trough_border, + NULL); + + if (gtk_widget_get_can_focus (widget)) + { + gint focus_padding = 0; + + gtk_widget_style_get (widget, + "focus-line-width", &focus, + "focus-padding", &focus_padding, + NULL); + focus += focus_padding; + } + + gtk_range_set_min_slider_size (range, + (MIN (allocation->width, + allocation->height) - 2 * focus) / 2); + + if (GTK_WIDGET_CLASS (parent_class)->size_allocate) + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + gtk_range_get_range_rect (range, &range_rect); + + scale_width = range_rect.width - 2 * (focus + trough_border); + scale_height = range_rect.height - 2 * (focus + trough_border); + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + scale_width -= gtk_range_get_min_slider_size (range) - 1; + scale_height -= 2; + break; + + case GTK_ORIENTATION_VERTICAL: + scale_width -= 2; + scale_height -= gtk_range_get_min_slider_size (range) - 1; + break; + } + + if (scale_width != scale->width || scale_height != scale->height) + { + scale->width = scale_width; + scale->height = scale_height; + + scale->rowstride = scale->width * 4; + + g_free (scale->buf); + scale->buf = g_new (guchar, scale->rowstride * scale->height); + + scale->needs_render = TRUE; + } +} + +static void +gimp_color_scale_state_changed (GtkWidget *widget, + GtkStateType previous_state) +{ + if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE || + previous_state == GTK_STATE_INSENSITIVE) + { + GIMP_COLOR_SCALE (widget)->needs_render = TRUE; + } + + if (GTK_WIDGET_CLASS (parent_class)->state_changed) + GTK_WIDGET_CLASS (parent_class)->state_changed (widget, previous_state); +} + +static gboolean +gimp_color_scale_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + if (event->button == 1) + { + GdkEventButton *my_event; + gboolean retval; + + my_event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event); + my_event->button = 2; + + retval = GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, + my_event); + + gdk_event_free ((GdkEvent *) my_event); + + return retval; + } + + return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); +} + +static gboolean +gimp_color_scale_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + if (event->button == 1) + { + GdkEventButton *my_event; + gboolean retval; + + my_event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event); + my_event->button = 2; + + retval = GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, + my_event); + + gdk_event_free ((GdkEvent *) my_event); + + return retval; + } + + return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event); +} + +static gboolean +gimp_color_scale_scroll (GtkWidget *widget, + GdkEventScroll *event) +{ + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == + GTK_ORIENTATION_HORIZONTAL) + { + GdkEventScroll *my_event; + gboolean retval; + + my_event = (GdkEventScroll *) gdk_event_copy ((GdkEvent *) event); + + switch (my_event->direction) + { + case GDK_SCROLL_UP: + my_event->direction = GDK_SCROLL_RIGHT; + break; + + case GDK_SCROLL_DOWN: + my_event->direction = GDK_SCROLL_LEFT; + break; + + default: + break; + } + + retval = GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, my_event); + + gdk_event_free ((GdkEvent *) my_event); + + return retval; + } + + return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event); +} + +static gboolean +gimp_color_scale_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpColorScale *scale = GIMP_COLOR_SCALE (widget); + GimpColorScalePrivate *priv = GET_PRIVATE (widget); + GtkRange *range = GTK_RANGE (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GdkWindow *window = gtk_widget_get_window (widget); + gboolean sensitive = gtk_widget_is_sensitive (widget); + GtkAllocation allocation; + GdkRectangle range_rect; + GdkRectangle area = { 0, }; + cairo_surface_t *buffer; + gint focus = 0; + gint trough_border; + gint slider_start; + gint slider_size; + gint x, y; + gint w, h; + cairo_t *cr; + + if (! scale->buf || ! gtk_widget_is_drawable (widget)) + return FALSE; + + gtk_widget_get_allocation (widget, &allocation); + + cr = gdk_cairo_create (window); + gdk_cairo_region (cr, event->region); + cairo_translate (cr, allocation.x, allocation.y); + cairo_clip (cr); + + gtk_widget_style_get (widget, + "trough-border", &trough_border, + NULL); + + if (gtk_widget_get_can_focus (widget)) + { + gint focus_padding = 0; + + gtk_widget_style_get (widget, + "focus-line-width", &focus, + "focus-padding", &focus_padding, + NULL); + focus += focus_padding; + } + + gtk_range_get_range_rect (range, &range_rect); + gtk_range_get_slider_range (range, &slider_start, NULL); + + x = range_rect.x + focus; + y = range_rect.y + focus; + w = range_rect.width - 2 * focus; + h = range_rect.height - 2 * focus; + + slider_size = gtk_range_get_min_slider_size (range) / 2; + + if (scale->needs_render) + { + gimp_color_scale_render (scale); + + if (! sensitive) + gimp_color_scale_render_stipple (scale); + + scale->needs_render = FALSE; + } + + gtk_paint_box (style, window, + sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE, + GTK_SHADOW_IN, + &event->area, widget, "trough", + x + allocation.x, + y + allocation.y, + w, h); + + if (! priv->transform) + gimp_color_scale_create_transform (scale); + + if (priv->transform) + { + const Babl *format = babl_format ("cairo-RGB24"); + guchar *buf = g_new (guchar, scale->rowstride * scale->height); + guchar *src = scale->buf; + guchar *dest = buf; + gint i; + + for (i = 0; i < scale->height; i++) + { + gimp_color_transform_process_pixels (priv->transform, + format, src, + format, dest, + scale->width); + + src += scale->rowstride; + dest += scale->rowstride; + } + + buffer = cairo_image_surface_create_for_data (buf, + CAIRO_FORMAT_RGB24, + scale->width, + scale->height, + scale->rowstride); + cairo_surface_set_user_data (buffer, NULL, + buf, (cairo_destroy_func_t) g_free); + } + else + { + buffer = cairo_image_surface_create_for_data (scale->buf, + CAIRO_FORMAT_RGB24, + scale->width, + scale->height, + scale->rowstride); + } + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + cairo_set_source_surface (cr, buffer, + x + trough_border + slider_size, + y + trough_border + 1); + break; + + case GTK_ORIENTATION_VERTICAL: + cairo_set_source_surface (cr, buffer, + x + trough_border + 1, + y + trough_border + slider_size); + break; + } + + cairo_surface_destroy (buffer); + cairo_paint (cr); + + if (gtk_widget_has_focus (widget)) + gtk_paint_focus (style, window, gtk_widget_get_state (widget), + &event->area, widget, "trough", + range_rect.x + allocation.x, + range_rect.y + allocation.y, + range_rect.width, + range_rect.height); + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + area.x = slider_start; + area.y = y + trough_border; + area.width = 2 * slider_size + 1; + area.height = h - 2 * trough_border; + break; + + case GTK_ORIENTATION_VERTICAL: + area.x = x + trough_border; + area.y = slider_start; + area.width = w - 2 * trough_border; + area.height = 2 * slider_size + 1; + break; + } + + if (gtk_widget_is_sensitive (widget)) + gdk_cairo_set_source_color (cr, &style->black); + else + gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_INSENSITIVE]); + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + cairo_move_to (cr, area.x, area.y); + cairo_line_to (cr, area.x + area.width, area.y); + cairo_line_to (cr, + area.x + area.width / 2 + 0.5, + area.y + area.width / 2); + break; + + case GTK_ORIENTATION_VERTICAL: + cairo_move_to (cr, area.x, area.y); + cairo_line_to (cr, area.x, area.y + area.height); + cairo_line_to (cr, + area.x + area.height / 2, + area.y + area.height / 2 + 0.5); + break; + } + + cairo_close_path (cr); + cairo_fill (cr); + + if (gtk_widget_is_sensitive (widget)) + gdk_cairo_set_source_color (cr, &style->white); + else + gdk_cairo_set_source_color (cr, &style->light[GTK_STATE_INSENSITIVE]); + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + cairo_move_to (cr, area.x, area.y + area.height); + cairo_line_to (cr, area.x + area.width, area.y + area.height); + cairo_line_to (cr, + area.x + area.width / 2 + 0.5, + area.y + area.height - area.width / 2); + break; + + case GTK_ORIENTATION_VERTICAL: + cairo_move_to (cr, area.x + area.width, area.y); + cairo_line_to (cr, area.x + area.width, area.y + area.height); + cairo_line_to (cr, + area.x + area.width - area.height / 2, + area.y + area.height / 2 + 0.5); + break; + } + + cairo_close_path (cr); + cairo_fill (cr); + + cairo_destroy (cr); + + return FALSE; +} + +/** + * gimp_color_scale_new: + * @orientation: the scale's orientation (horizontal or vertical) + * @channel: the scale's color channel + * + * Creates a new #GimpColorScale widget. + * + * Return value: a new #GimpColorScale widget + **/ +GtkWidget * +gimp_color_scale_new (GtkOrientation orientation, + GimpColorSelectorChannel channel) +{ + GimpColorScale *scale = g_object_new (GIMP_TYPE_COLOR_SCALE, + "orientation", orientation, + "channel", channel, + NULL); + + gtk_range_set_flippable (GTK_RANGE (scale), + orientation == GTK_ORIENTATION_HORIZONTAL); + + return GTK_WIDGET (scale); +} + +/** + * gimp_color_scale_set_channel: + * @scale: a #GimpColorScale widget + * @channel: the new color channel + * + * Changes the color channel displayed by the @scale. + **/ +void +gimp_color_scale_set_channel (GimpColorScale *scale, + GimpColorSelectorChannel channel) +{ + g_return_if_fail (GIMP_IS_COLOR_SCALE (scale)); + + if (channel != scale->channel) + { + scale->channel = channel; + + scale->needs_render = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (scale)); + + g_object_notify (G_OBJECT (scale), "channel"); + } +} + +/** + * gimp_color_scale_set_color: + * @scale: a #GimpColorScale widget + * @rgb: the new color as #GimpRGB + * @hsv: the new color as #GimpHSV + * + * Changes the color value of the @scale. + **/ +void +gimp_color_scale_set_color (GimpColorScale *scale, + const GimpRGB *rgb, + const GimpHSV *hsv) +{ + g_return_if_fail (GIMP_IS_COLOR_SCALE (scale)); + g_return_if_fail (rgb != NULL); + g_return_if_fail (hsv != NULL); + + scale->rgb = *rgb; + scale->hsv = *hsv; + + scale->needs_render = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (scale)); +} + +/** + * gimp_color_scale_set_color_config: + * @scale: a #GimpColorScale widget. + * @config: a #GimpColorConfig object. + * + * Sets the color management configuration to use with this color scale. + * + * Since: 2.10 + */ +void +gimp_color_scale_set_color_config (GimpColorScale *scale, + GimpColorConfig *config) +{ + GimpColorScalePrivate *priv; + + g_return_if_fail (GIMP_IS_COLOR_SCALE (scale)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + priv = GET_PRIVATE (scale); + + if (config != priv->config) + { + if (priv->config) + { + g_signal_handlers_disconnect_by_func (priv->config, + gimp_color_scale_notify_config, + scale); + + gimp_color_scale_destroy_transform (scale); + } + + g_set_object (&priv->config, config); + + if (priv->config) + { + g_signal_connect (priv->config, "notify", + G_CALLBACK (gimp_color_scale_notify_config), + scale); + + gimp_color_scale_notify_config (priv->config, NULL, scale); + } + } +} + + +/* as in gtkrange.c */ +static gboolean +should_invert (GtkRange *range) +{ + gboolean inverted = gtk_range_get_inverted (range); + gboolean flippable = gtk_range_get_flippable (range); + + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == + GTK_ORIENTATION_HORIZONTAL) + { + return + (inverted && !flippable) || + (inverted && flippable && + gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) || + (!inverted && flippable && + gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL); + } + else + { + return inverted; + } +} + +static void +gimp_color_scale_render (GimpColorScale *scale) +{ + GimpColorScalePrivate *priv = GET_PRIVATE (scale); + GtkRange *range = GTK_RANGE (scale); + GimpRGB rgb; + GimpHSV hsv; + GimpLCH lch; + gint multiplier = 1; + guint x, y; + gdouble *channel_value = NULL; /* shut up compiler */ + gboolean from_hsv = FALSE; + gboolean from_lch = FALSE; + gboolean invert; + guchar *buf; + guchar *d; + + if ((buf = scale->buf) == NULL) + return; + + if (scale->channel == GIMP_COLOR_SELECTOR_ALPHA) + { + gimp_color_scale_render_alpha (scale); + return; + } + + rgb = scale->rgb; + hsv = scale->hsv; + babl_process (fish_rgb_to_lch, &rgb, &lch, 1); + + switch (scale->channel) + { + case GIMP_COLOR_SELECTOR_HUE: channel_value = &hsv.h; break; + case GIMP_COLOR_SELECTOR_SATURATION: channel_value = &hsv.s; break; + case GIMP_COLOR_SELECTOR_VALUE: channel_value = &hsv.v; break; + + case GIMP_COLOR_SELECTOR_RED: channel_value = &rgb.r; break; + case GIMP_COLOR_SELECTOR_GREEN: channel_value = &rgb.g; break; + case GIMP_COLOR_SELECTOR_BLUE: channel_value = &rgb.b; break; + case GIMP_COLOR_SELECTOR_ALPHA: channel_value = &rgb.a; break; + + case GIMP_COLOR_SELECTOR_LCH_LIGHTNESS: channel_value = &lch.l; break; + case GIMP_COLOR_SELECTOR_LCH_CHROMA: channel_value = &lch.c; break; + case GIMP_COLOR_SELECTOR_LCH_HUE: channel_value = &lch.h; break; + } + + switch (scale->channel) + { + case GIMP_COLOR_SELECTOR_HUE: + case GIMP_COLOR_SELECTOR_SATURATION: + case GIMP_COLOR_SELECTOR_VALUE: + from_hsv = TRUE; + break; + + case GIMP_COLOR_SELECTOR_LCH_LIGHTNESS: + multiplier = 100; + from_lch = TRUE; + break; + case GIMP_COLOR_SELECTOR_LCH_CHROMA: + multiplier = 200; + from_lch = TRUE; + break; + case GIMP_COLOR_SELECTOR_LCH_HUE: + multiplier = 360; + from_lch = TRUE; + break; + + default: + break; + } + + invert = should_invert (range); + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + for (x = 0, d = buf; x < scale->width; x++, d += 4) + { + gdouble value = (gdouble) x * multiplier / (gdouble) (scale->width - 1); + guchar r, g, b; + + if (invert) + value = multiplier - value; + + *channel_value = value; + + if (from_hsv) + gimp_hsv_to_rgb (&hsv, &rgb); + else if (from_lch) + babl_process (fish_lch_to_rgb, &lch, &rgb, 1); + + if (rgb.r < 0.0 || rgb.r > 1.0 || + rgb.g < 0.0 || rgb.g > 1.0 || + rgb.b < 0.0 || rgb.b > 1.0) + { + r = priv->oog_color[0]; + g = priv->oog_color[1]; + b = priv->oog_color[2]; + } + else + { + gimp_rgb_get_uchar (&rgb, &r, &g, &b); + } + + GIMP_CAIRO_RGB24_SET_PIXEL (d, r, g, b); + } + + d = buf + scale->rowstride; + for (y = 1; y < scale->height; y++) + { + memcpy (d, buf, scale->rowstride); + d += scale->rowstride; + } + break; + + case GTK_ORIENTATION_VERTICAL: + for (y = 0; y < scale->height; y++) + { + gdouble value = (gdouble) y * multiplier / (gdouble) (scale->height - 1); + guchar r, g, b; + + if (invert) + value = multiplier - value; + + *channel_value = value; + + if (from_hsv) + gimp_hsv_to_rgb (&hsv, &rgb); + else if (from_lch) + babl_process (fish_lch_to_rgb, &lch, &rgb, 1); + + if (rgb.r < 0.0 || rgb.r > 1.0 || + rgb.g < 0.0 || rgb.g > 1.0 || + rgb.b < 0.0 || rgb.b > 1.0) + { + r = priv->oog_color[0]; + g = priv->oog_color[1]; + b = priv->oog_color[2]; + } + else + { + gimp_rgb_get_uchar (&rgb, &r, &g, &b); + } + + for (x = 0, d = buf; x < scale->width; x++, d += 4) + { + GIMP_CAIRO_RGB24_SET_PIXEL (d, r, g, b); + } + + buf += scale->rowstride; + } + break; + } +} + +static void +gimp_color_scale_render_alpha (GimpColorScale *scale) +{ + GtkRange *range = GTK_RANGE (scale); + GimpRGB rgb; + gboolean invert; + gdouble a; + guint x, y; + guchar *buf; + guchar *d, *l; + + invert = should_invert (range); + + buf = scale->buf; + rgb = scale->rgb; + + switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range))) + { + case GTK_ORIENTATION_HORIZONTAL: + { + guchar *light; + guchar *dark; + + light = buf; + /* this won't work correctly for very thin scales */ + dark = (scale->height > GIMP_CHECK_SIZE_SM ? + buf + GIMP_CHECK_SIZE_SM * scale->rowstride : light); + + for (x = 0, d = light, l = dark; x < scale->width; x++) + { + if ((x % GIMP_CHECK_SIZE_SM) == 0) + { + guchar *t; + + t = d; + d = l; + l = t; + } + + a = (gdouble) x / (gdouble) (scale->width - 1); + + if (invert) + a = 1.0 - a; + + GIMP_CAIRO_RGB24_SET_PIXEL (l, + (GIMP_CHECK_LIGHT + + (rgb.r - GIMP_CHECK_LIGHT) * a) * 255.999, + (GIMP_CHECK_LIGHT + + (rgb.g - GIMP_CHECK_LIGHT) * a) * 255.999, + (GIMP_CHECK_LIGHT + + (rgb.b - GIMP_CHECK_LIGHT) * a) * 255.999); + l += 4; + + GIMP_CAIRO_RGB24_SET_PIXEL (d, + (GIMP_CHECK_DARK + + (rgb.r - GIMP_CHECK_DARK) * a) * 255.999, + (GIMP_CHECK_DARK + + (rgb.g - GIMP_CHECK_DARK) * a) * 255.999, + (GIMP_CHECK_DARK + + (rgb.b - GIMP_CHECK_DARK) * a) * 255.999); + d += 4; + } + + for (y = 0, d = buf; y < scale->height; y++, d += scale->rowstride) + { + if (y == 0 || y == GIMP_CHECK_SIZE_SM) + continue; + + if ((y / GIMP_CHECK_SIZE_SM) & 1) + memcpy (d, dark, scale->rowstride); + else + memcpy (d, light, scale->rowstride); + } + } + break; + + case GTK_ORIENTATION_VERTICAL: + { + guchar light[4] = {0xff, 0xff, 0xff, 0xff}; + guchar dark[4] = {0xff, 0xff, 0xff, 0xff}; + + for (y = 0, d = buf; y < scale->height; y++, d += scale->rowstride) + { + a = (gdouble) y / (gdouble) (scale->height - 1); + + if (invert) + a = 1.0 - a; + + GIMP_CAIRO_RGB24_SET_PIXEL (light, + (GIMP_CHECK_LIGHT + + (rgb.r - GIMP_CHECK_LIGHT) * a) * 255.999, + (GIMP_CHECK_LIGHT + + (rgb.g - GIMP_CHECK_LIGHT) * a) * 255.999, + (GIMP_CHECK_LIGHT + + (rgb.b - GIMP_CHECK_LIGHT) * a) * 255.999); + + GIMP_CAIRO_RGB24_SET_PIXEL (dark, + (GIMP_CHECK_DARK + + (rgb.r - GIMP_CHECK_DARK) * a) * 255.999, + (GIMP_CHECK_DARK + + (rgb.g - GIMP_CHECK_DARK) * a) * 255.999, + (GIMP_CHECK_DARK + + (rgb.b - GIMP_CHECK_DARK) * a) * 255.999); + + for (x = 0, l = d; x < scale->width; x++, l += 4) + { + if (((x / GIMP_CHECK_SIZE_SM) ^ (y / GIMP_CHECK_SIZE_SM)) & 1) + { + l[0] = light[0]; + l[1] = light[1]; + l[2] = light[2]; + l[3] = light[3]; + } + else + { + l[0] = dark[0]; + l[1] = dark[1]; + l[2] = dark[2]; + l[3] = dark[3]; + } + } + } + } + break; + } +} + +/* + * This could be integrated into the render functions which might be + * slightly faster. But we trade speed for keeping the code simple. + */ +static void +gimp_color_scale_render_stipple (GimpColorScale *scale) +{ + GtkWidget *widget = GTK_WIDGET (scale); + GtkStyle *style = gtk_widget_get_style (widget); + guchar *buf; + guchar insensitive[4] = {0xff, 0xff, 0xff, 0xff}; + guint x, y; + + if ((buf = scale->buf) == NULL) + return; + + GIMP_CAIRO_RGB24_SET_PIXEL (insensitive, + style->bg[GTK_STATE_INSENSITIVE].red >> 8, + style->bg[GTK_STATE_INSENSITIVE].green >> 8, + style->bg[GTK_STATE_INSENSITIVE].blue >> 8); + + for (y = 0; y < scale->height; y++, buf += scale->rowstride) + { + guchar *d = buf + 4 * (y % 2); + + for (x = 0; x < scale->width - (y % 2); x += 2, d += 8) + { + d[0] = insensitive[0]; + d[1] = insensitive[1]; + d[2] = insensitive[2]; + d[3] = insensitive[3]; + } + } +} + +static void +gimp_color_scale_create_transform (GimpColorScale *scale) +{ + GimpColorScalePrivate *priv = GET_PRIVATE (scale); + + if (priv->config) + { + static GimpColorProfile *profile = NULL; + + const Babl *format = babl_format ("cairo-RGB24"); + + if (G_UNLIKELY (! profile)) + profile = gimp_color_profile_new_rgb_srgb (); + + priv->transform = gimp_widget_get_color_transform (GTK_WIDGET (scale), + priv->config, + profile, + format, + format); + } +} + +static void +gimp_color_scale_destroy_transform (GimpColorScale *scale) +{ + GimpColorScalePrivate *priv = GET_PRIVATE (scale); + + if (priv->transform) + { + g_object_unref (priv->transform); + priv->transform = NULL; + } + + gtk_widget_queue_draw (GTK_WIDGET (scale)); +} + +static void +gimp_color_scale_notify_config (GimpColorConfig *config, + const GParamSpec *pspec, + GimpColorScale *scale) +{ + GimpColorScalePrivate *priv = GET_PRIVATE (scale); + + gimp_color_scale_destroy_transform (scale); + + gimp_rgb_get_uchar (&config->out_of_gamut_color, + priv->oog_color, + priv->oog_color + 1, + priv->oog_color + 2); + scale->needs_render = TRUE; +} diff --git a/libgimpwidgets/gimpcolorscale.h b/libgimpwidgets/gimpcolorscale.h new file mode 100644 index 0000000..d6aa662 --- /dev/null +++ b/libgimpwidgets/gimpcolorscale.h @@ -0,0 +1,88 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorscale.h + * Copyright (C) 2002 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_SCALE_H__ +#define __GIMP_COLOR_SCALE_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_SCALE (gimp_color_scale_get_type ()) +#define GIMP_COLOR_SCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_SCALE, GimpColorScale)) +#define GIMP_COLOR_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_SCALE, GimpColorScaleClass)) +#define GIMP_IS_COLOR_SCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_SCALE)) +#define GIMP_IS_COLOR_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_SCALE)) +#define GIMP_COLOR_SCALE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_SCALE, GimpColorScaleClass)) + + +typedef struct _GimpColorScaleClass GimpColorScaleClass; + +struct _GimpColorScale +{ + GtkScale parent_instance; + + /*< private >*/ + GimpColorSelectorChannel channel; + GimpRGB rgb; + GimpHSV hsv; + + guchar *buf; + guint width; + guint height; + guint rowstride; + + gboolean needs_render; +}; + +struct _GimpColorScaleClass +{ + GtkScaleClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_scale_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_color_scale_new (GtkOrientation orientation, + GimpColorSelectorChannel channel); + +void gimp_color_scale_set_channel (GimpColorScale *scale, + GimpColorSelectorChannel channel); +void gimp_color_scale_set_color (GimpColorScale *scale, + const GimpRGB *rgb, + const GimpHSV *hsv); + +void gimp_color_scale_set_color_config (GimpColorScale *scale, + GimpColorConfig *config); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_SCALE_H__ */ diff --git a/libgimpwidgets/gimpcolorscales.c b/libgimpwidgets/gimpcolorscales.c new file mode 100644 index 0000000..0c86013 --- /dev/null +++ b/libgimpwidgets/gimpcolorscales.c @@ -0,0 +1,925 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorscales.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on color_notebook module + * Copyright (C) 1998 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "libgimpmath/gimpmath.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorscale.h" +#include "gimpcolorscales.h" +#include "gimpwidgets.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorscales + * @title: GimpColorScales + * @short_description: A #GimpColorSelector implementation. + * + * The #GimpColorScales widget is an implementation of a + * #GimpColorSelector. It shows a group of #GimpColorScale widgets + * that allow to adjust the HSV, LCH, and RGB color channels. + **/ + + +enum +{ + PROP_0, + PROP_SHOW_RGB_U8, + PROP_SHOW_HSV +}; + +enum +{ + GIMP_COLOR_SELECTOR_RED_U8 = GIMP_COLOR_SELECTOR_LCH_HUE + 1, + GIMP_COLOR_SELECTOR_GREEN_U8, + GIMP_COLOR_SELECTOR_BLUE_U8, + GIMP_COLOR_SELECTOR_ALPHA_U8 +}; + + +typedef struct _GimpLCH GimpLCH; + +struct _GimpLCH +{ + gdouble l, c, h, a; +}; + + +typedef struct _ColorScale ColorScale; + +struct _ColorScale +{ + GimpColorSelectorChannel channel; + + gdouble default_value; + gdouble scale_min_value; + gdouble scale_max_value; + gdouble scale_inc; + gdouble spin_min_value; + gdouble spin_max_value; +}; + + +#define GIMP_COLOR_SCALES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_SCALES, GimpColorScalesClass)) +#define GIMP_IS_COLOR_SCALES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_SCALES)) +#define GIMP_COLOR_SCALES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_SCALES, GimpColorScalesClass)) + + +typedef struct _GimpColorScalesClass GimpColorScalesClass; + +struct _GimpColorScales +{ + GimpColorSelector parent_instance; + + gboolean show_rgb_u8; + GBinding *show_rgb_u8_binding; + GBinding *show_hsv_binding; + + GtkWidget *lch_group; + GtkWidget *hsv_group; + GtkWidget *rgb_percent_group; + GtkWidget *rgb_u8_group; + GtkWidget *alpha_percent_group; + GtkWidget *alpha_u8_group; + + GtkWidget *dummy_u8_toggle; + GtkWidget *toggles[14]; + GtkAdjustment *adjustments[14]; + GtkWidget *scales[14]; +}; + +struct _GimpColorScalesClass +{ + GimpColorSelectorClass parent_class; +}; + + +static void gimp_color_scales_dispose (GObject *object); +static void gimp_color_scales_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_color_scales_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_color_scales_togg_sensitive (GimpColorSelector *selector, + gboolean sensitive); +static void gimp_color_scales_togg_visible (GimpColorSelector *selector, + gboolean visible); + +static void gimp_color_scales_set_show_alpha (GimpColorSelector *selector, + gboolean show_alpha); +static void gimp_color_scales_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); +static void gimp_color_scales_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel); +static void gimp_color_scales_set_model_visible + (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible); +static void gimp_color_scales_set_config (GimpColorSelector *selector, + GimpColorConfig *config); + +static void gimp_color_scales_update_visible (GimpColorScales *scales); +static void gimp_color_scales_update_scales (GimpColorScales *scales, + gint skip); +static void gimp_color_scales_toggle_changed (GtkWidget *widget, + GimpColorScales *scales); +static void gimp_color_scales_scale_changed (GtkAdjustment *adjustment, + GimpColorScales *scales); +static void gimp_color_scales_toggle_lch_hsv (GtkToggleButton *toggle, + GimpColorScales *scales); + + +G_DEFINE_TYPE (GimpColorScales, gimp_color_scales, GIMP_TYPE_COLOR_SELECTOR) + +#define parent_class gimp_color_scales_parent_class + +static const Babl *fish_rgb_to_lch = NULL; +static const Babl *fish_lch_to_rgb = NULL; + +static const ColorScale scale_defs[] = +{ + { GIMP_COLOR_SELECTOR_HUE, 0, 0, 360, 30, 0, 360 }, + { GIMP_COLOR_SELECTOR_SATURATION, 0, 0, 100, 10, 0, 500 }, + { GIMP_COLOR_SELECTOR_VALUE, 0, 0, 100, 10, 0, 500 }, + + { GIMP_COLOR_SELECTOR_RED, 0, 0, 100, 10, -500, 500 }, + { GIMP_COLOR_SELECTOR_GREEN, 0, 0, 100, 10, -500, 500 }, + { GIMP_COLOR_SELECTOR_BLUE, 0, 0, 100, 10, -500, 500 }, + { GIMP_COLOR_SELECTOR_ALPHA, 0, 0, 100, 10, 0, 100 }, + + { GIMP_COLOR_SELECTOR_LCH_LIGHTNESS, 0, 0, 100, 10, 0, 300 }, + { GIMP_COLOR_SELECTOR_LCH_CHROMA, 0, 0, 200, 10, 0, 300 }, + { GIMP_COLOR_SELECTOR_LCH_HUE, 0, 0, 360, 30, 0, 360 }, + + { GIMP_COLOR_SELECTOR_RED_U8, 0, 0, 255, 16, -1275, 1275 }, + { GIMP_COLOR_SELECTOR_GREEN_U8, 0, 0, 255, 16, -1275, 1275 }, + { GIMP_COLOR_SELECTOR_BLUE_U8, 0, 0, 255, 16, -1275, 1275 }, + { GIMP_COLOR_SELECTOR_ALPHA_U8, 0, 0, 255, 16, 0, 255 } +}; + + +static void +gimp_color_scales_class_init (GimpColorScalesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpColorSelectorClass *selector_class = GIMP_COLOR_SELECTOR_CLASS (klass); + + object_class->dispose = gimp_color_scales_dispose; + object_class->get_property = gimp_color_scales_get_property; + object_class->set_property = gimp_color_scales_set_property; + + selector_class->name = _("Scales"); + selector_class->help_id = "gimp-colorselector-scales"; + selector_class->icon_name = GIMP_ICON_DIALOG_TOOL_OPTIONS; + selector_class->set_toggles_visible = gimp_color_scales_togg_visible; + selector_class->set_toggles_sensitive = gimp_color_scales_togg_sensitive; + selector_class->set_show_alpha = gimp_color_scales_set_show_alpha; + selector_class->set_color = gimp_color_scales_set_color; + selector_class->set_channel = gimp_color_scales_set_channel; + selector_class->set_model_visible = gimp_color_scales_set_model_visible; + selector_class->set_config = gimp_color_scales_set_config; + + g_object_class_install_property (object_class, PROP_SHOW_RGB_U8, + g_param_spec_boolean ("show-rgb-u8", + "Show RGB 0..255", + "Show RGB 0..255 scales", + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_SHOW_HSV, + g_param_spec_boolean ("show-hsv", + "Show HSV", + "Show HSV instead of LCH", + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + fish_rgb_to_lch = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE LCH(ab) alpha double")); + fish_lch_to_rgb = babl_fish (babl_format ("CIE LCH(ab) alpha double"), + babl_format ("R'G'B'A double")); +} + +static GtkWidget * +create_group (GimpColorScales *scales, + GSList **radio_group, + GtkSizeGroup *size_group0, + GtkSizeGroup *size_group1, + GtkSizeGroup *size_group2, + GimpColorSelectorChannel first_channel, + GimpColorSelectorChannel last_channel) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + GtkWidget *table; + GEnumClass *enum_class; + gint row; + gint i; + + table = gtk_table_new (last_channel - first_channel + 1, 4, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 1); + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 0); + + enum_class = g_type_class_ref (GIMP_TYPE_COLOR_SELECTOR_CHANNEL); + + for (i = first_channel, row = 0; i <= last_channel; i++, row++) + { + GimpEnumDesc *enum_desc; + gint enum_value = i; + gboolean is_u8 = FALSE; + + if (enum_value >= GIMP_COLOR_SELECTOR_RED_U8 && + enum_value <= GIMP_COLOR_SELECTOR_ALPHA_U8) + { + enum_value -= 7; + is_u8 = TRUE; + } + + enum_desc = gimp_enum_get_desc (enum_class, enum_value); + + if (i == GIMP_COLOR_SELECTOR_ALPHA || + i == GIMP_COLOR_SELECTOR_ALPHA_U8) + { + /* just to allocate the space via the size group */ + scales->toggles[i] = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + } + else + { + scales->toggles[i] = gtk_radio_button_new (*radio_group); + *radio_group = + gtk_radio_button_get_group (GTK_RADIO_BUTTON (scales->toggles[i])); + + if (enum_value == gimp_color_selector_get_channel (selector)) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scales->toggles[i]), + TRUE); + + if (is_u8) + { + /* bind the RGB U8 toggles to the RGB percent toggles */ + g_object_bind_property (scales->toggles[i - 7], "active", + scales->toggles[i], "active", + G_BINDING_SYNC_CREATE | + G_BINDING_BIDIRECTIONAL); + } + else + { + g_signal_connect (scales->toggles[i], "toggled", + G_CALLBACK (gimp_color_scales_toggle_changed), + scales); + } + } + + gtk_table_attach (GTK_TABLE (table), scales->toggles[i], + 0, 1, row, row + 1, + GTK_SHRINK, GTK_EXPAND, 0, 0); + + if (gimp_color_selector_get_toggles_visible (selector)) + gtk_widget_show (scales->toggles[i]); + + gimp_help_set_help_data (scales->toggles[i], + gettext (enum_desc->value_help), NULL); + + gtk_size_group_add_widget (size_group0, scales->toggles[i]); + + scales->adjustments[i] = (GtkAdjustment *) + gimp_color_scale_entry_new (GTK_TABLE (table), 1, row, + gettext (enum_desc->value_desc), + -1, -1, + scale_defs[i].default_value, + scale_defs[i].scale_min_value, + scale_defs[i].scale_max_value, + 1.0, + scale_defs[i].scale_inc, + 1, + gettext (enum_desc->value_help), + NULL); + + gtk_adjustment_configure (scales->adjustments[i], + scale_defs[i].default_value, + scale_defs[i].spin_min_value, + scale_defs[i].spin_max_value, + 1.0, + scale_defs[i].scale_inc, + 0); + + scales->scales[i] = GIMP_SCALE_ENTRY_SCALE (scales->adjustments[i]); + g_object_add_weak_pointer (G_OBJECT (scales->scales[i]), + (gpointer) &scales->scales[i]); + + gimp_color_scale_set_channel (GIMP_COLOR_SCALE (scales->scales[i]), + enum_value); + gtk_size_group_add_widget (size_group1, scales->scales[i]); + + gtk_size_group_add_widget (size_group2, + GIMP_SCALE_ENTRY_SPINBUTTON (scales->adjustments[i])); + + g_signal_connect (scales->adjustments[i], "value-changed", + G_CALLBACK (gimp_color_scales_scale_changed), + scales); + } + + g_type_class_unref (enum_class); + + return table; +} + +static void +gimp_color_scales_init (GimpColorScales *scales) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + GtkSizeGroup *size_group0; + GtkSizeGroup *size_group1; + GtkSizeGroup *size_group2; + GtkWidget *hbox; + GtkWidget *radio1; + GtkWidget *radio2; + GtkWidget *table; + GSList *main_group; + GSList *u8_group; + GSList *radio_group; + + gtk_box_set_spacing (GTK_BOX (scales), 5); + + scales->show_rgb_u8_binding = NULL; + scales->show_hsv_binding = NULL; + + /* don't need the toggles for our own operation */ + selector->toggles_visible = FALSE; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_start (GTK_BOX (scales), hbox, 0, 0, FALSE); + gtk_widget_show (hbox); + + main_group = NULL; + u8_group = NULL; + + scales->dummy_u8_toggle = gtk_radio_button_new (NULL); + g_object_ref_sink (scales->dummy_u8_toggle); + u8_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (scales->dummy_u8_toggle)); + + size_group0 = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + size_group1 = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + size_group2 = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + scales->rgb_percent_group = + table = create_group (scales, &main_group, + size_group0, size_group1, size_group2, + GIMP_COLOR_SELECTOR_RED, + GIMP_COLOR_SELECTOR_BLUE); + gtk_box_pack_start (GTK_BOX (scales), table, FALSE, FALSE, 0); + + scales->rgb_u8_group = + table = create_group (scales, &u8_group, + size_group0, size_group1, size_group2, + GIMP_COLOR_SELECTOR_RED_U8, + GIMP_COLOR_SELECTOR_BLUE_U8); + gtk_box_pack_start (GTK_BOX (scales), table, FALSE, FALSE, 0); + + scales->lch_group = + table = create_group (scales, &main_group, + size_group0, size_group1, size_group2, + GIMP_COLOR_SELECTOR_LCH_LIGHTNESS, + GIMP_COLOR_SELECTOR_LCH_HUE); + gtk_box_pack_start (GTK_BOX (scales), table, FALSE, FALSE, 0); + + scales->hsv_group = + table = create_group (scales, &main_group, + size_group0, size_group1, size_group2, + GIMP_COLOR_SELECTOR_HUE, + GIMP_COLOR_SELECTOR_VALUE); + gtk_box_pack_start (GTK_BOX (scales), table, FALSE, FALSE, 0); + + scales->alpha_percent_group = + table = create_group (scales, &main_group, + size_group0, size_group1, size_group2, + GIMP_COLOR_SELECTOR_ALPHA, + GIMP_COLOR_SELECTOR_ALPHA); + gtk_box_pack_start (GTK_BOX (scales), table, FALSE, FALSE, 0); + + scales->alpha_u8_group = + table = create_group (scales, &u8_group, + size_group0, size_group1, size_group2, + GIMP_COLOR_SELECTOR_ALPHA_U8, + GIMP_COLOR_SELECTOR_ALPHA_U8); + gtk_box_pack_start (GTK_BOX (scales), table, FALSE, FALSE, 0); + + g_object_unref (size_group0); + g_object_unref (size_group1); + g_object_unref (size_group2); + + gimp_color_scales_update_visible (scales); + + radio_group = NULL; + + radio1 = gtk_radio_button_new_with_label (NULL, _("0..100")); + radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio1)); + radio2 = gtk_radio_button_new_with_label (radio_group, _("0..255")); + + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (radio1), FALSE); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (radio2), FALSE); + + gtk_box_pack_start (GTK_BOX (hbox), radio1, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), radio2, FALSE, FALSE, 0); + + gtk_widget_show (radio1); + gtk_widget_show (radio2); + + if (scales->show_rgb_u8) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio2), TRUE); + + g_object_bind_property (G_OBJECT (radio2), "active", + G_OBJECT (scales), "show-rgb-u8", + G_BINDING_SYNC_CREATE | + G_BINDING_BIDIRECTIONAL); + + radio_group = NULL; + + radio1 = gtk_radio_button_new_with_label (NULL, _("LCh")); + radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio1)); + radio2 = gtk_radio_button_new_with_label (radio_group, _("HSV")); + + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (radio1), FALSE); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (radio2), FALSE); + + gtk_box_pack_end (GTK_BOX (hbox), radio2, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (hbox), radio1, FALSE, FALSE, 0); + + gtk_widget_show (radio1); + gtk_widget_show (radio2); + + if (gimp_color_selector_get_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_HSV)) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio2), TRUE); + + g_object_bind_property (G_OBJECT (radio2), "active", + G_OBJECT (scales), "show-hsv", + G_BINDING_SYNC_CREATE | + G_BINDING_BIDIRECTIONAL); + + g_signal_connect (radio1, "toggled", + G_CALLBACK (gimp_color_scales_toggle_lch_hsv), + scales); +} + +static void +gimp_color_scales_dispose (GObject *object) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (object); + + g_clear_object (&scales->dummy_u8_toggle); + + g_clear_pointer (&scales->show_rgb_u8_binding, g_binding_unbind); + g_clear_pointer (&scales->show_hsv_binding, g_binding_unbind); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_scales_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (object); + gboolean hsv; + + switch (property_id) + { + case PROP_SHOW_RGB_U8: + g_value_set_boolean (value, scales->show_rgb_u8); + break; + case PROP_SHOW_HSV: + hsv = gimp_color_selector_get_model_visible (GIMP_COLOR_SELECTOR (object), + GIMP_COLOR_SELECTOR_MODEL_HSV); + g_value_set_boolean (value, hsv); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_scales_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (object); + gboolean show_hsv; + + switch (property_id) + { + case PROP_SHOW_RGB_U8: + gimp_color_scales_set_show_rgb_u8 (scales, g_value_get_boolean (value)); + break; + case PROP_SHOW_HSV: + show_hsv = g_value_get_boolean (value); + + gimp_color_selector_set_model_visible (GIMP_COLOR_SELECTOR (object), + GIMP_COLOR_SELECTOR_MODEL_LCH, + ! show_hsv); + gimp_color_selector_set_model_visible (GIMP_COLOR_SELECTOR (object), + GIMP_COLOR_SELECTOR_MODEL_HSV, + show_hsv); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_scales_togg_sensitive (GimpColorSelector *selector, + gboolean sensitive) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (selector); + gint i; + + for (i = 0; i < G_N_ELEMENTS (scale_defs); i++) + if (scales->toggles[i]) + gtk_widget_set_sensitive (scales->toggles[i], sensitive); +} + +static void +gimp_color_scales_togg_visible (GimpColorSelector *selector, + gboolean visible) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (selector); + gint i; + + for (i = 0; i < G_N_ELEMENTS (scale_defs); i++) + if (scales->toggles[i]) + gtk_widget_set_visible (scales->toggles[i], visible); +} + +static void +gimp_color_scales_set_show_alpha (GimpColorSelector *selector, + gboolean show_alpha) +{ + gimp_color_scales_update_visible (GIMP_COLOR_SCALES (selector)); +} + +static void +gimp_color_scales_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (selector); + + gimp_color_scales_update_scales (scales, -1); +} + +static void +gimp_color_scales_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (selector); + + if (GTK_IS_RADIO_BUTTON (scales->toggles[channel])) + { + g_signal_handlers_block_by_func (scales->toggles[channel], + gimp_color_scales_toggle_changed, + scales); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scales->toggles[channel]), + TRUE); + + g_signal_handlers_unblock_by_func (scales->toggles[channel], + gimp_color_scales_toggle_changed, + scales); + } +} + +static void +gimp_color_scales_set_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible) +{ + gimp_color_scales_update_visible (GIMP_COLOR_SCALES (selector)); +} + +static void +gimp_color_scales_set_config (GimpColorSelector *selector, + GimpColorConfig *config) +{ + GimpColorScales *scales = GIMP_COLOR_SCALES (selector); + gint i; + + g_clear_pointer (&scales->show_rgb_u8_binding, g_binding_unbind); + g_clear_pointer (&scales->show_hsv_binding, g_binding_unbind); + + if (config) + { + scales->show_rgb_u8_binding = g_object_bind_property (config, "show-rgb-u8", + scales, "show-rgb-u8", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + scales->show_hsv_binding = g_object_bind_property (config, "show-hsv", + scales, "show-hsv", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + } + + for (i = 0; i < G_N_ELEMENTS (scale_defs); i++) + { + if (scales->scales[i]) + gimp_color_scale_set_color_config (GIMP_COLOR_SCALE (scales->scales[i]), + config); + } +} + + +/* public functions */ + +void +gimp_color_scales_set_show_rgb_u8 (GimpColorScales *scales, + gboolean show_rgb_u8) +{ + g_return_if_fail (GIMP_IS_COLOR_SCALES (scales)); + + show_rgb_u8 = show_rgb_u8 ? TRUE : FALSE; + + if (show_rgb_u8 != scales->show_rgb_u8) + { + scales->show_rgb_u8 = show_rgb_u8; + + g_object_notify (G_OBJECT (scales), "show-rgb-u8"); + + gimp_color_scales_update_visible (scales); + } +} + +gboolean +gimp_color_scales_get_show_rgb_u8 (GimpColorScales *scales) +{ + g_return_val_if_fail (GIMP_IS_COLOR_SCALES (scales), FALSE); + + return scales->show_rgb_u8; +} + + +/* private functions */ + +static void +gimp_color_scales_update_visible (GimpColorScales *scales) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + gboolean show_alpha; + gboolean rgb_visible; + gboolean lch_visible; + gboolean hsv_visible; + + show_alpha = gimp_color_selector_get_show_alpha (selector); + rgb_visible = gimp_color_selector_get_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_RGB); + lch_visible = gimp_color_selector_get_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_LCH); + hsv_visible = gimp_color_selector_get_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_HSV); + + gtk_widget_set_visible (scales->rgb_percent_group, + rgb_visible && ! scales->show_rgb_u8); + gtk_widget_set_visible (scales->rgb_u8_group, + rgb_visible && scales->show_rgb_u8); + + gtk_widget_set_visible (scales->lch_group, lch_visible); + gtk_widget_set_visible (scales->hsv_group, hsv_visible); + + gtk_widget_set_visible (scales->alpha_percent_group, + show_alpha && ! scales->show_rgb_u8); + gtk_widget_set_visible (scales->alpha_u8_group, + show_alpha && scales->show_rgb_u8); +} + +static void +gimp_color_scales_update_scales (GimpColorScales *scales, + gint skip) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + GimpLCH lch; + gdouble values[G_N_ELEMENTS (scale_defs)]; + gint i; + + babl_process (fish_rgb_to_lch, &selector->rgb, &lch, 1); + + values[GIMP_COLOR_SELECTOR_HUE] = selector->hsv.h * 360.0; + values[GIMP_COLOR_SELECTOR_SATURATION] = selector->hsv.s * 100.0; + values[GIMP_COLOR_SELECTOR_VALUE] = selector->hsv.v * 100.0; + + values[GIMP_COLOR_SELECTOR_RED] = selector->rgb.r * 100.0; + values[GIMP_COLOR_SELECTOR_GREEN] = selector->rgb.g * 100.0; + values[GIMP_COLOR_SELECTOR_BLUE] = selector->rgb.b * 100.0; + values[GIMP_COLOR_SELECTOR_ALPHA] = selector->rgb.a * 100.0; + + values[GIMP_COLOR_SELECTOR_LCH_LIGHTNESS] = lch.l; + values[GIMP_COLOR_SELECTOR_LCH_CHROMA] = lch.c; + values[GIMP_COLOR_SELECTOR_LCH_HUE] = lch.h; + + values[GIMP_COLOR_SELECTOR_RED_U8] = selector->rgb.r * 255.0; + values[GIMP_COLOR_SELECTOR_GREEN_U8] = selector->rgb.g * 255.0; + values[GIMP_COLOR_SELECTOR_BLUE_U8] = selector->rgb.b * 255.0; + values[GIMP_COLOR_SELECTOR_ALPHA_U8] = selector->rgb.a * 255.0; + + for (i = 0; i < G_N_ELEMENTS (scale_defs); i++) + { + if (i != skip) + { + g_signal_handlers_block_by_func (scales->adjustments[i], + gimp_color_scales_scale_changed, + scales); + + gtk_adjustment_set_value (scales->adjustments[i], values[i]); + + g_signal_handlers_unblock_by_func (scales->adjustments[i], + gimp_color_scales_scale_changed, + scales); + } + + gimp_color_scale_set_color (GIMP_COLOR_SCALE (scales->scales[i]), + &selector->rgb, &selector->hsv); + } +} + +static void +gimp_color_scales_toggle_changed (GtkWidget *widget, + GimpColorScales *scales) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + gint i; + + for (i = 0; i < G_N_ELEMENTS (scale_defs); i++) + { + if (widget == scales->toggles[i]) + { + gimp_color_selector_set_channel (selector, i); + + if (i < GIMP_COLOR_SELECTOR_RED || + i > GIMP_COLOR_SELECTOR_BLUE) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scales->dummy_u8_toggle), + TRUE); + } + + break; + } + } + } +} + +static void +gimp_color_scales_scale_changed (GtkAdjustment *adjustment, + GimpColorScales *scales) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + gdouble value = gtk_adjustment_get_value (adjustment); + GimpLCH lch; + gint i; + + for (i = 0; i < G_N_ELEMENTS (scale_defs); i++) + if (scales->adjustments[i] == adjustment) + break; + + switch (i) + { + case GIMP_COLOR_SELECTOR_HUE: + selector->hsv.h = value / 360.0; + break; + + case GIMP_COLOR_SELECTOR_SATURATION: + selector->hsv.s = value / 100.0; + break; + + case GIMP_COLOR_SELECTOR_VALUE: + selector->hsv.v = value / 100.0; + break; + + case GIMP_COLOR_SELECTOR_RED: + selector->rgb.r = value / 100.0; + break; + + case GIMP_COLOR_SELECTOR_GREEN: + selector->rgb.g = value / 100.0; + break; + + case GIMP_COLOR_SELECTOR_BLUE: + selector->rgb.b = value / 100.0; + break; + + case GIMP_COLOR_SELECTOR_ALPHA: + selector->hsv.a = selector->rgb.a = value / 100.0; + break; + + case GIMP_COLOR_SELECTOR_LCH_LIGHTNESS: + babl_process (fish_rgb_to_lch, &selector->rgb, &lch, 1); + lch.l = value; + break; + + case GIMP_COLOR_SELECTOR_LCH_CHROMA: + babl_process (fish_rgb_to_lch, &selector->rgb, &lch, 1); + lch.c = value; + break; + + case GIMP_COLOR_SELECTOR_LCH_HUE: + babl_process (fish_rgb_to_lch, &selector->rgb, &lch, 1); + lch.h = value; + break; + + case GIMP_COLOR_SELECTOR_RED_U8: + selector->rgb.r = value / 255.0; + break; + + case GIMP_COLOR_SELECTOR_GREEN_U8: + selector->rgb.g = value / 255.0; + break; + + case GIMP_COLOR_SELECTOR_BLUE_U8: + selector->rgb.b = value / 255.0; + break; + + case GIMP_COLOR_SELECTOR_ALPHA_U8: + selector->hsv.a = selector->rgb.a = value / 255.0; + break; + } + + if ((i >= GIMP_COLOR_SELECTOR_HUE) && + (i <= GIMP_COLOR_SELECTOR_VALUE)) + { + gimp_hsv_to_rgb (&selector->hsv, &selector->rgb); + } + else if ((i >= GIMP_COLOR_SELECTOR_LCH_LIGHTNESS) && + (i <= GIMP_COLOR_SELECTOR_LCH_HUE)) + { + babl_process (fish_lch_to_rgb, &lch, &selector->rgb, 1); + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + } + else if ((i >= GIMP_COLOR_SELECTOR_RED) && + (i <= GIMP_COLOR_SELECTOR_BLUE)) + { + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + } + else if ((i >= GIMP_COLOR_SELECTOR_RED_U8) && + (i <= GIMP_COLOR_SELECTOR_BLUE_U8)) + { + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + } + + gimp_color_scales_update_scales (scales, i); + + gimp_color_selector_color_changed (selector); +} + +static void +gimp_color_scales_toggle_lch_hsv (GtkToggleButton *toggle, + GimpColorScales *scales) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (scales); + gboolean show_hsv = ! gtk_toggle_button_get_active (toggle); + + gimp_color_selector_set_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_LCH, + ! show_hsv); + gimp_color_selector_set_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_HSV, + show_hsv); + g_object_set (scales, "show-hsv", show_hsv, NULL); +} diff --git a/libgimpwidgets/gimpcolorscales.h b/libgimpwidgets/gimpcolorscales.h new file mode 100644 index 0000000..9bfc90c --- /dev/null +++ b/libgimpwidgets/gimpcolorscales.h @@ -0,0 +1,49 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorscales.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on color_notebook module + * Copyright (C) 1998 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_SCALES_H__ +#define __GIMP_COLOR_SCALES_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_SCALES (gimp_color_scales_get_type ()) +#define GIMP_COLOR_SCALES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_SCALES, GimpColorScales)) +#define GIMP_IS_COLOR_SCALES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_SCALES)) + + +GType gimp_color_scales_get_type (void) G_GNUC_CONST; + +void gimp_color_scales_set_show_rgb_u8 (GimpColorScales *scales, + gboolean show_rgb_u8); +gboolean gimp_color_scales_get_show_rgb_u8 (GimpColorScales *scales); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_SCALES_H__ */ diff --git a/libgimpwidgets/gimpcolorselect.c b/libgimpwidgets/gimpcolorselect.c new file mode 100644 index 0000000..373917d --- /dev/null +++ b/libgimpwidgets/gimpcolorselect.c @@ -0,0 +1,2006 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselect.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on color_notebook module + * Copyright (C) 1998 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorselector.h" +#include "gimpcolorselect.h" +#include "gimphelpui.h" +#include "gimpicons.h" +#include "gimpwidgetsutils.h" +#include "gimp3migration.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorselect + * @title: GimpColorSelect + * @short_description: A #GimpColorSelector implementation. + * + * The #GimpColorSelect widget is an implementation of a + * #GimpColorSelector. It shows a square area that supports + * interactively changing two color channels and a smaller area to + * change the third channel. You can select which channel should be + * the third by calling gimp_color_selector_set_channel(). The widget + * will then change the other two channels accordingly. + **/ + + +#define COLOR_AREA_EVENT_MASK (GDK_EXPOSURE_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK | \ + GDK_BUTTON_MOTION_MASK | \ + GDK_ENTER_NOTIFY_MASK) + + +typedef enum +{ + COLOR_SELECT_HUE = 0, + COLOR_SELECT_SATURATION, + COLOR_SELECT_VALUE, + + COLOR_SELECT_RED, + COLOR_SELECT_GREEN, + COLOR_SELECT_BLUE, + COLOR_SELECT_ALPHA, + + COLOR_SELECT_LCH_LIGHTNESS, + COLOR_SELECT_LCH_CHROMA, + COLOR_SELECT_LCH_HUE, + + COLOR_SELECT_HUE_SATURATION, + COLOR_SELECT_HUE_VALUE, + COLOR_SELECT_SATURATION_VALUE, + + COLOR_SELECT_RED_GREEN, + COLOR_SELECT_RED_BLUE, + COLOR_SELECT_GREEN_BLUE, + + COLOR_SELECT_LCH_HUE_CHROMA, + COLOR_SELECT_LCH_HUE_LIGHTNESS, + COLOR_SELECT_LCH_CHROMA_LIGHTNESS +} ColorSelectFillType; + +typedef enum +{ + UPDATE_VALUES = 1 << 0, + UPDATE_POS = 1 << 1, + UPDATE_XY_COLOR = 1 << 2, + UPDATE_Z_COLOR = 1 << 3, + UPDATE_CALLER = 1 << 6 +} ColorSelectUpdateType; + +typedef enum +{ + DRAG_NONE, + DRAG_XY, + DRAG_Z +} ColorSelectDragMode; + + +typedef struct _GimpLCH GimpLCH; + +struct _GimpLCH +{ + gdouble l, c, h, a; +}; + + +#define GIMP_COLOR_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_SELECT, GimpColorSelectClass)) +#define GIMP_IS_COLOR_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_SELECT)) +#define GIMP_COLOR_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_SELECT, GimpColorSelectClass)) + + +typedef struct _GimpColorSelectClass GimpColorSelectClass; + +struct _GimpColorSelect +{ + GimpColorSelector parent_instance; + + GtkWidget *toggle_box[3]; + + GtkWidget *xy_color; + ColorSelectFillType xy_color_fill; + guchar *xy_buf; + gint xy_width; + gint xy_height; + gint xy_rowstride; + gboolean xy_needs_render; + + GtkWidget *z_color; + ColorSelectFillType z_color_fill; + guchar *z_buf; + gint z_width; + gint z_height; + gint z_rowstride; + gboolean z_needs_render; + + gdouble pos[3]; + + ColorSelectDragMode drag_mode; + + GimpColorConfig *config; + GimpColorTransform *transform; + guchar oog_color[3]; +}; + +struct _GimpColorSelectClass +{ + GimpColorSelectorClass parent_class; +}; + + +typedef struct _ColorSelectFill ColorSelectFill; + +typedef void (* ColorSelectRenderFunc) (ColorSelectFill *color_select_fill); + +struct _ColorSelectFill +{ + guchar *buffer; + gint y; + gint width; + gint height; + GimpRGB rgb; + GimpHSV hsv; + GimpLCH lch; + guchar oog_color[3]; + + ColorSelectRenderFunc render_line; +}; + + +static void gimp_color_select_finalize (GObject *object); + +static void gimp_color_select_togg_visible (GimpColorSelector *selector, + gboolean visible); +static void gimp_color_select_togg_sensitive (GimpColorSelector *selector, + gboolean sensitive); +static void gimp_color_select_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); +static void gimp_color_select_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel); +static void gimp_color_select_set_model_visible + (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible); +static void gimp_color_select_set_config (GimpColorSelector *selector, + GimpColorConfig *config); + +static void gimp_color_select_channel_toggled (GtkWidget *widget, + GimpColorSelect *select); + +static void gimp_color_select_update (GimpColorSelect *select, + ColorSelectUpdateType type); +static void gimp_color_select_update_values (GimpColorSelect *select); +static void gimp_color_select_update_pos (GimpColorSelect *select); + +#if 0 +static void gimp_color_select_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); +#endif + +static void gimp_color_select_xy_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpColorSelect *select); +static gboolean gimp_color_select_xy_expose (GtkWidget *widget, + GdkEventExpose *eevent, + GimpColorSelect *select); +static gboolean gimp_color_select_xy_events (GtkWidget *widget, + GdkEvent *event, + GimpColorSelect *select); +static void gimp_color_select_z_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpColorSelect *select); +static gboolean gimp_color_select_z_expose (GtkWidget *widget, + GdkEventExpose *eevent, + GimpColorSelect *select); +static gboolean gimp_color_select_z_events (GtkWidget *widget, + GdkEvent *event, + GimpColorSelect *select); + +static void gimp_color_select_render (GtkWidget *widget, + guchar *buf, + gint width, + gint height, + gint rowstride, + ColorSelectFillType fill_type, + const GimpHSV *hsv, + const GimpRGB *rgb, + const guchar *oog_color); + +static void color_select_render_red (ColorSelectFill *csf); +static void color_select_render_green (ColorSelectFill *csf); +static void color_select_render_blue (ColorSelectFill *csf); + +static void color_select_render_hue (ColorSelectFill *csf); +static void color_select_render_saturation (ColorSelectFill *csf); +static void color_select_render_value (ColorSelectFill *csf); + +static void color_select_render_lch_lightness (ColorSelectFill *csf); +static void color_select_render_lch_chroma (ColorSelectFill *csf); +static void color_select_render_lch_hue (ColorSelectFill *csf); + +static void color_select_render_red_green (ColorSelectFill *csf); +static void color_select_render_red_blue (ColorSelectFill *csf); +static void color_select_render_green_blue (ColorSelectFill *csf); + +static void color_select_render_hue_saturation (ColorSelectFill *csf); +static void color_select_render_hue_value (ColorSelectFill *csf); +static void color_select_render_saturation_value (ColorSelectFill *csf); + +static void color_select_render_lch_chroma_lightness (ColorSelectFill *csf); +static void color_select_render_lch_hue_lightness (ColorSelectFill *csf); +static void color_select_render_lch_hue_chroma (ColorSelectFill *csf); + +static void gimp_color_select_create_transform (GimpColorSelect *select); +static void gimp_color_select_destroy_transform (GimpColorSelect *select); +static void gimp_color_select_notify_config (GimpColorConfig *config, + const GParamSpec *pspec, + GimpColorSelect *select); + + +G_DEFINE_TYPE (GimpColorSelect, gimp_color_select, GIMP_TYPE_COLOR_SELECTOR) + +#define parent_class gimp_color_select_parent_class + +static const ColorSelectRenderFunc render_funcs[] = +{ + color_select_render_hue, + color_select_render_saturation, + color_select_render_value, + + color_select_render_red, + color_select_render_green, + color_select_render_blue, + NULL, /* alpha */ + + color_select_render_lch_lightness, + color_select_render_lch_chroma, + color_select_render_lch_hue, + + color_select_render_hue_saturation, + color_select_render_hue_value, + color_select_render_saturation_value, + + color_select_render_red_green, + color_select_render_red_blue, + color_select_render_green_blue, + + color_select_render_lch_hue_chroma, + color_select_render_lch_hue_lightness, + color_select_render_lch_chroma_lightness +}; + +static const Babl *fish_rgb_to_lch = NULL; +static const Babl *fish_lch_to_rgb = NULL; +static const Babl *fish_lch_to_rgb_u8 = NULL; + + +static void +gimp_color_select_class_init (GimpColorSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpColorSelectorClass *selector_class = GIMP_COLOR_SELECTOR_CLASS (klass); + + object_class->finalize = gimp_color_select_finalize; + + selector_class->name = "GIMP"; + selector_class->help_id = "gimp-colorselector-gimp"; + selector_class->icon_name = GIMP_ICON_WILBER; + selector_class->set_toggles_visible = gimp_color_select_togg_visible; + selector_class->set_toggles_sensitive = gimp_color_select_togg_sensitive; + selector_class->set_color = gimp_color_select_set_color; + selector_class->set_channel = gimp_color_select_set_channel; + selector_class->set_model_visible = gimp_color_select_set_model_visible; + selector_class->set_config = gimp_color_select_set_config; + + fish_rgb_to_lch = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE LCH(ab) double")); + fish_lch_to_rgb = babl_fish (babl_format ("CIE LCH(ab) double"), + babl_format ("R'G'B' double")); + fish_lch_to_rgb_u8 = babl_fish (babl_format ("CIE LCH(ab) double"), + babl_format ("R'G'B' u8")); +} + +static void +gimp_color_select_init (GimpColorSelect *select) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (select); + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *vbox; + GEnumClass *model_class; + GEnumClass *channel_class; + GimpEnumDesc *enum_desc; + GimpColorSelectorModel model; + GSList *group = NULL; + + /* Default values. */ + select->z_color_fill = COLOR_SELECT_HUE; + select->xy_color_fill = COLOR_SELECT_SATURATION_VALUE; + select->drag_mode = DRAG_NONE; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_start (GTK_BOX (select), hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + /* The x/y component preview */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + select->xy_color = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (select->xy_color), FALSE); + g_object_add_weak_pointer (G_OBJECT (select->xy_color), + (gpointer) &select->xy_color); + gtk_widget_set_size_request (select->xy_color, + GIMP_COLOR_SELECTOR_SIZE, + GIMP_COLOR_SELECTOR_SIZE); + gtk_widget_set_events (select->xy_color, COLOR_AREA_EVENT_MASK); + gtk_container_add (GTK_CONTAINER (frame), select->xy_color); + gtk_widget_show (select->xy_color); + + g_signal_connect (select->xy_color, "size-allocate", + G_CALLBACK (gimp_color_select_xy_size_allocate), + select); + g_signal_connect_after (select->xy_color, "expose-event", + G_CALLBACK (gimp_color_select_xy_expose), + select); + g_signal_connect (select->xy_color, "event", + G_CALLBACK (gimp_color_select_xy_events), + select); + +#if 0 + gimp_dnd_color_dest_add (select->xy_color, gimp_color_select_drop_color, + select); +#endif + + /* The z component preview */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + select->z_color = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (select->z_color), FALSE); + g_object_add_weak_pointer (G_OBJECT (select->z_color), + (gpointer) &select->z_color); + gtk_widget_set_size_request (select->z_color, + GIMP_COLOR_SELECTOR_BAR_SIZE, -1); + gtk_widget_set_events (select->z_color, COLOR_AREA_EVENT_MASK); + gtk_container_add (GTK_CONTAINER (frame), select->z_color); + gtk_widget_show (select->z_color); + + g_signal_connect (select->z_color, "size-allocate", + G_CALLBACK (gimp_color_select_z_size_allocate), + select); + g_signal_connect_after (select->z_color, "expose-event", + G_CALLBACK (gimp_color_select_z_expose), + select); + g_signal_connect (select->z_color, "event", + G_CALLBACK (gimp_color_select_z_events), + select); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + model_class = g_type_class_ref (GIMP_TYPE_COLOR_SELECTOR_MODEL); + channel_class = g_type_class_ref (GIMP_TYPE_COLOR_SELECTOR_CHANNEL); + + for (model = GIMP_COLOR_SELECTOR_MODEL_RGB; + model <= GIMP_COLOR_SELECTOR_MODEL_HSV; + model++) + { + enum_desc = gimp_enum_get_desc (model_class, model); + + select->toggle_box[model] = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (vbox), select->toggle_box[model], + FALSE, FALSE, 0); + + if (gimp_color_selector_get_model_visible (selector, model)) + gtk_widget_show (select->toggle_box[model]); + + /* channel toggles */ + { + GimpColorSelectorChannel channel = GIMP_COLOR_SELECTOR_RED; + GimpColorSelectorChannel end_channel; + + switch (model) + { + case GIMP_COLOR_SELECTOR_MODEL_RGB: + channel = GIMP_COLOR_SELECTOR_RED; + break; + case GIMP_COLOR_SELECTOR_MODEL_LCH: + channel = GIMP_COLOR_SELECTOR_LCH_LIGHTNESS; + break; + case GIMP_COLOR_SELECTOR_MODEL_HSV: + channel = GIMP_COLOR_SELECTOR_HUE; + break; + default: + /* Should not happen. */ + g_return_if_reached (); + break; + } + + end_channel = channel + 3; + + for (; channel < end_channel; channel++) + { + GtkWidget *button; + + enum_desc = gimp_enum_get_desc (channel_class, channel); + + button = gtk_radio_button_new_with_mnemonic (group, + gettext (enum_desc->value_desc)); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); + gtk_box_pack_start (GTK_BOX (select->toggle_box[model]), button, + TRUE, TRUE, 0); + gtk_widget_show (button); + + g_object_set_data (G_OBJECT (button), "channel", + GINT_TO_POINTER (channel)); + + if (channel == gimp_color_selector_get_channel (selector)) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + gimp_help_set_help_data (button, gettext (enum_desc->value_help), + NULL); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_color_select_channel_toggled), + select); + } + } + } + + g_type_class_unref (model_class); + g_type_class_unref (channel_class); +} + +static void +gimp_color_select_finalize (GObject *object) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (object); + + g_clear_pointer (&select->xy_buf, g_free); + select->xy_width = 0; + select->xy_height = 0; + select->xy_rowstride = 0; + + g_clear_pointer (&select->z_buf, g_free); + select->z_width = 0; + select->z_height = 0; + select->z_rowstride = 0; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_select_togg_visible (GimpColorSelector *selector, + gboolean visible) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (selector); + gint i; + + for (i = 0; i < 3; i++) + { + gtk_widget_set_visible (select->toggle_box[i], visible); + } +} + +static void +gimp_color_select_togg_sensitive (GimpColorSelector *selector, + gboolean sensitive) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (selector); + gint i; + + for (i = 0; i < 3; i++) + { + gtk_widget_set_sensitive (select->toggle_box[i], sensitive); + } +} + +static void +gimp_color_select_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (selector); + + gimp_color_select_update (select, + UPDATE_POS | UPDATE_XY_COLOR | UPDATE_Z_COLOR); +} + +static void +gimp_color_select_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (selector); + + switch ((ColorSelectFillType) channel) + { + case COLOR_SELECT_HUE: + select->z_color_fill = COLOR_SELECT_HUE; + select->xy_color_fill = COLOR_SELECT_SATURATION_VALUE; + break; + + case COLOR_SELECT_SATURATION: + select->z_color_fill = COLOR_SELECT_SATURATION; + select->xy_color_fill = COLOR_SELECT_HUE_VALUE; + break; + + case COLOR_SELECT_VALUE: + select->z_color_fill = COLOR_SELECT_VALUE; + select->xy_color_fill = COLOR_SELECT_HUE_SATURATION; + break; + + case COLOR_SELECT_RED: + select->z_color_fill = COLOR_SELECT_RED; + select->xy_color_fill = COLOR_SELECT_GREEN_BLUE; + break; + + case COLOR_SELECT_GREEN: + select->z_color_fill = COLOR_SELECT_GREEN; + select->xy_color_fill = COLOR_SELECT_RED_BLUE; + break; + + case COLOR_SELECT_BLUE: + select->z_color_fill = COLOR_SELECT_BLUE; + select->xy_color_fill = COLOR_SELECT_RED_GREEN; + break; + + case COLOR_SELECT_LCH_LIGHTNESS: + select->z_color_fill = COLOR_SELECT_LCH_LIGHTNESS; + select->xy_color_fill = COLOR_SELECT_LCH_HUE_CHROMA; + break; + + case COLOR_SELECT_LCH_CHROMA: + select->z_color_fill = COLOR_SELECT_LCH_CHROMA; + select->xy_color_fill = COLOR_SELECT_LCH_HUE_LIGHTNESS; + break; + + case COLOR_SELECT_LCH_HUE: + select->z_color_fill = COLOR_SELECT_LCH_HUE; + select->xy_color_fill = COLOR_SELECT_LCH_CHROMA_LIGHTNESS; + break; + + default: + break; + } + + gimp_color_select_update (select, + UPDATE_POS | UPDATE_Z_COLOR | UPDATE_XY_COLOR); +} + +static void +gimp_color_select_set_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (selector); + + gtk_widget_set_visible (select->toggle_box[model], visible); +} + +static void +gimp_color_select_set_config (GimpColorSelector *selector, + GimpColorConfig *config) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (selector); + + if (config != select->config) + { + if (select->config) + { + g_signal_handlers_disconnect_by_func (select->config, + gimp_color_select_notify_config, + select); + + gimp_color_select_destroy_transform (select); + } + + g_set_object (&select->config, config); + + if (select->config) + { + g_signal_connect (select->config, "notify", + G_CALLBACK (gimp_color_select_notify_config), + select); + + gimp_color_select_notify_config (select->config, NULL, select); + } + } +} + +static void +gimp_color_select_channel_toggled (GtkWidget *widget, + GimpColorSelect *select) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (select); + GimpColorSelectorChannel channel; + + channel = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "channel")); + + gimp_color_selector_set_channel (selector, channel); + } +} + +static void +gimp_color_select_update (GimpColorSelect *select, + ColorSelectUpdateType update) +{ + if (update & UPDATE_POS) + gimp_color_select_update_pos (select); + + if (update & UPDATE_VALUES) + gimp_color_select_update_values (select); + + if (update & UPDATE_XY_COLOR) + { + select->xy_needs_render = TRUE; + gtk_widget_queue_draw (select->xy_color); + } + + if (update & UPDATE_Z_COLOR) + { + select->z_needs_render = TRUE; + gtk_widget_queue_draw (select->z_color); + } + + if (update & UPDATE_CALLER) + gimp_color_selector_color_changed (GIMP_COLOR_SELECTOR (select)); +} + +static void +gimp_color_select_update_values (GimpColorSelect *select) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (select); + GimpLCH lch; + + switch (select->z_color_fill) + { + case COLOR_SELECT_RED: + selector->rgb.g = select->pos[0]; + selector->rgb.b = select->pos[1]; + selector->rgb.r = select->pos[2]; + break; + case COLOR_SELECT_GREEN: + selector->rgb.r = select->pos[0]; + selector->rgb.b = select->pos[1]; + selector->rgb.g = select->pos[2]; + break; + case COLOR_SELECT_BLUE: + selector->rgb.r = select->pos[0]; + selector->rgb.g = select->pos[1]; + selector->rgb.b = select->pos[2]; + break; + + case COLOR_SELECT_HUE: + selector->hsv.s = select->pos[0]; + selector->hsv.v = select->pos[1]; + selector->hsv.h = select->pos[2]; + break; + case COLOR_SELECT_SATURATION: + selector->hsv.h = select->pos[0]; + selector->hsv.v = select->pos[1]; + selector->hsv.s = select->pos[2]; + break; + case COLOR_SELECT_VALUE: + selector->hsv.h = select->pos[0]; + selector->hsv.s = select->pos[1]; + selector->hsv.v = select->pos[2]; + break; + + case COLOR_SELECT_LCH_LIGHTNESS: + lch.h = select->pos[0] * 360; + lch.c = select->pos[1] * 200; + lch.l = select->pos[2] * 100; + break; + case COLOR_SELECT_LCH_CHROMA: + lch.h = select->pos[0] * 360; + lch.l = select->pos[1] * 100; + lch.c = select->pos[2] * 200; + break; + case COLOR_SELECT_LCH_HUE: + lch.c = select->pos[0] * 200; + lch.l = select->pos[1] * 100; + lch.h = select->pos[2] * 360; + break; + + default: + break; + } + + switch (select->z_color_fill) + { + case COLOR_SELECT_RED: + case COLOR_SELECT_GREEN: + case COLOR_SELECT_BLUE: + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + break; + + case COLOR_SELECT_HUE: + case COLOR_SELECT_SATURATION: + case COLOR_SELECT_VALUE: + gimp_hsv_to_rgb (&selector->hsv, &selector->rgb); + break; + + case COLOR_SELECT_LCH_LIGHTNESS: + case COLOR_SELECT_LCH_CHROMA: + case COLOR_SELECT_LCH_HUE: + babl_process (fish_lch_to_rgb, &lch, &selector->rgb, 1); + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + break; + + default: + break; + } +} + +static void +gimp_color_select_update_pos (GimpColorSelect *select) +{ + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (select); + GimpLCH lch; + + babl_process (fish_rgb_to_lch, &selector->rgb, &lch, 1); + + switch (select->z_color_fill) + { + case COLOR_SELECT_RED: + select->pos[0] = CLAMP (selector->rgb.g, 0.0, 1.0); + select->pos[1] = CLAMP (selector->rgb.b, 0.0, 1.0); + select->pos[2] = CLAMP (selector->rgb.r, 0.0, 1.0); + break; + case COLOR_SELECT_GREEN: + select->pos[0] = CLAMP (selector->rgb.r, 0.0, 1.0); + select->pos[1] = CLAMP (selector->rgb.b, 0.0, 1.0); + select->pos[2] = CLAMP (selector->rgb.g, 0.0, 1.0); + break; + case COLOR_SELECT_BLUE: + select->pos[0] = CLAMP (selector->rgb.r, 0.0, 1.0); + select->pos[1] = CLAMP (selector->rgb.g, 0.0, 1.0); + select->pos[2] = CLAMP (selector->rgb.b, 0.0, 1.0); + break; + + case COLOR_SELECT_HUE: + select->pos[0] = CLAMP (selector->hsv.s, 0.0, 1.0); + select->pos[1] = CLAMP (selector->hsv.v, 0.0, 1.0); + select->pos[2] = CLAMP (selector->hsv.h, 0.0, 1.0); + break; + case COLOR_SELECT_SATURATION: + select->pos[0] = CLAMP (selector->hsv.h, 0.0, 1.0); + select->pos[1] = CLAMP (selector->hsv.v, 0.0, 1.0); + select->pos[2] = CLAMP (selector->hsv.s, 0.0, 1.0); + break; + case COLOR_SELECT_VALUE: + select->pos[0] = CLAMP (selector->hsv.h, 0.0, 1.0); + select->pos[1] = CLAMP (selector->hsv.s, 0.0, 1.0); + select->pos[2] = CLAMP (selector->hsv.v, 0.0, 1.0); + break; + + case COLOR_SELECT_LCH_LIGHTNESS: + select->pos[0] = CLAMP (lch.h / 360, 0.0, 1.0); + select->pos[1] = CLAMP (lch.c / 200, 0.0, 1.0); + select->pos[2] = CLAMP (lch.l / 100, 0.0, 1.0); + break; + case COLOR_SELECT_LCH_CHROMA: + select->pos[0] = CLAMP (lch.h / 360, 0.0, 1.0); + select->pos[1] = CLAMP (lch.l / 100, 0.0, 1.0); + select->pos[2] = CLAMP (lch.c / 200, 0.0, 1.0); + break; + case COLOR_SELECT_LCH_HUE: + select->pos[0] = CLAMP (lch.c / 200, 0.0, 1.0); + select->pos[1] = CLAMP (lch.l / 100, 0.0, 1.0); + select->pos[2] = CLAMP (lch.h / 360, 0.0, 1.0); + break; + + default: + break; + } +} + +#if 0 +static void +gimp_color_select_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpColorSelect *select = GIMP_COLOR_SELECT (data); + + select->rgb = *color; + + gimp_color_select_update_hsv_values (select); + gimp_color_select_update_lch_values (select); + + gimp_color_select_update (select, + UPDATE_POS | UPDATE_XY_COLOR | UPDATE_Z_COLOR | + UPDATE_CALLER); +} +#endif + +static void +gimp_color_select_xy_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpColorSelect *select) +{ + if (allocation->width != select->xy_width || + allocation->height != select->xy_height) + { + select->xy_width = allocation->width; + select->xy_height = allocation->height; + + select->xy_rowstride = (select->xy_width * 3 + 3) & ~3; + + g_free (select->xy_buf); + select->xy_buf = g_new (guchar, select->xy_rowstride * select->xy_height); + + select->xy_needs_render = TRUE; + } + + gimp_color_select_update (select, UPDATE_XY_COLOR); +} + +static gboolean +gimp_color_select_xy_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpColorSelect *select) +{ + GtkAllocation allocation; + cairo_t *cr; + GdkPixbuf *pixbuf; + gint x, y; + + if (! select->xy_buf) + return FALSE; + + if (select->xy_needs_render) + { + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (select); + + gimp_color_select_render (select->xy_color, + select->xy_buf, + select->xy_width, + select->xy_height, + select->xy_rowstride, + select->xy_color_fill, + &selector->hsv, + &selector->rgb, + select->oog_color); + select->xy_needs_render = FALSE; + } + + if (! select->transform) + gimp_color_select_create_transform (select); + + if (select->transform) + { + const Babl *format = babl_format ("R'G'B' u8"); + guchar *buf = g_new (guchar, + select->xy_rowstride * select->xy_height); + guchar *src = select->xy_buf; + guchar *dest = buf; + gint i; + + for (i = 0; i < select->xy_height; i++) + { + gimp_color_transform_process_pixels (select->transform, + format, src, + format, dest, + select->xy_width); + + src += select->xy_rowstride; + dest += select->xy_rowstride; + } + + pixbuf = gdk_pixbuf_new_from_data (buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + select->xy_width, + select->xy_height, + select->xy_rowstride, + (GdkPixbufDestroyNotify) g_free, NULL); + } + else + { + pixbuf = gdk_pixbuf_new_from_data (select->xy_buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + select->xy_width, + select->xy_height, + select->xy_rowstride, + NULL, NULL); + } + + gtk_widget_get_allocation (select->xy_color, &allocation); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_translate (cr, allocation.x, allocation.y); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + g_object_unref (pixbuf); + cairo_paint (cr); + + x = (allocation.width - 1) * select->pos[0]; + y = (allocation.height - 1) - (allocation.height - 1) * select->pos[1]; + + cairo_move_to (cr, 0, y + 0.5); + cairo_line_to (cr, allocation.width, y + 0.5); + + cairo_move_to (cr, x + 0.5, 0); + cairo_line_to (cr, x + 0.5, allocation.height); + + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke (cr); + + cairo_destroy (cr); + + return TRUE; +} + +static gboolean +gimp_color_select_xy_events (GtkWidget *widget, + GdkEvent *event, + GimpColorSelect *select) +{ + GtkAllocation allocation; + gdouble x, y; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (select->drag_mode != DRAG_NONE || bevent->button != 1) + return FALSE; + + x = bevent->x; + y = bevent->y; + + gtk_grab_add (widget); + select->drag_mode = DRAG_XY; + } + break; + + case GDK_BUTTON_RELEASE: + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (select->drag_mode != DRAG_XY || bevent->button != 1) + return FALSE; + + x = bevent->x; + y = bevent->y; + + gtk_grab_remove (widget); + select->drag_mode = DRAG_NONE; + } + break; + + case GDK_MOTION_NOTIFY: + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + + if (select->drag_mode != DRAG_XY) + return FALSE; + + x = mevent->x; + y = mevent->y; + } + break; + + default: + return FALSE; + } + + gtk_widget_get_allocation (select->xy_color, &allocation); + + if (allocation.width > 1 && allocation.height > 1) + { + select->pos[0] = x / (allocation.width - 1); + select->pos[1] = 1.0 - y / (allocation.height - 1); + } + + select->pos[0] = CLAMP (select->pos[0], 0.0, 1.0); + select->pos[1] = CLAMP (select->pos[1], 0.0, 1.0); + + gtk_widget_queue_draw (select->xy_color); + + gimp_color_select_update (select, UPDATE_VALUES | UPDATE_CALLER); + + /* Ask for more motion events in case the event was a hint */ + gdk_event_request_motions ((GdkEventMotion *) event); + + return TRUE; +} + +static void +gimp_color_select_z_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpColorSelect *select) +{ + if (allocation->width != select->z_width || + allocation->height != select->z_height) + { + select->z_width = allocation->width; + select->z_height = allocation->height; + + select->z_rowstride = (select->z_width * 3 + 3) & ~3; + + g_free (select->z_buf); + select->z_buf = g_new (guchar, select->z_rowstride * select->z_height); + + select->z_needs_render = TRUE; + } + + gimp_color_select_update (select, UPDATE_Z_COLOR); +} + +static gboolean +gimp_color_select_z_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpColorSelect *select) +{ + GtkAllocation allocation; + cairo_t *cr; + GdkPixbuf *pixbuf; + gint y; + + if (! select->z_buf) + return FALSE; + + if (select->z_needs_render) + { + GimpColorSelector *selector = GIMP_COLOR_SELECTOR (select); + + gimp_color_select_render (select->z_color, + select->z_buf, + select->z_width, + select->z_height, + select->z_rowstride, + select->z_color_fill, + &selector->hsv, + &selector->rgb, + select->oog_color); + select->z_needs_render = FALSE; + } + + gtk_widget_get_allocation (widget, &allocation); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + if (! select->transform) + gimp_color_select_create_transform (select); + + if (select->transform) + { + const Babl *format = babl_format ("R'G'B' u8"); + guchar *buf = g_new (guchar, + select->z_rowstride * select->z_height); + guchar *src = select->z_buf; + guchar *dest = buf; + gint i; + + for (i = 0; i < select->z_height; i++) + { + gimp_color_transform_process_pixels (select->transform, + format, src, + format, dest, + select->z_width); + + src += select->z_rowstride; + dest += select->z_rowstride; + } + + pixbuf = gdk_pixbuf_new_from_data (buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + select->z_width, + select->z_height, + select->z_rowstride, + (GdkPixbufDestroyNotify) g_free, NULL); + } + else + { + pixbuf = gdk_pixbuf_new_from_data (select->z_buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + select->z_width, + select->z_height, + select->z_rowstride, + NULL, NULL); + } + + cairo_translate (cr, allocation.x, allocation.y); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + g_object_unref (pixbuf); + cairo_paint (cr); + + y = (allocation.height - 1) - (allocation.height - 1) * select->pos[2]; + + cairo_move_to (cr, 0, y + 0.5); + cairo_line_to (cr, allocation.width, y + 0.5); + + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke (cr); + + cairo_destroy (cr); + + return TRUE; +} + +static gboolean +gimp_color_select_z_events (GtkWidget *widget, + GdkEvent *event, + GimpColorSelect *select) +{ + GtkAllocation allocation; + gdouble z; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (select->drag_mode != DRAG_NONE || bevent->button != 1) + return FALSE; + + z = bevent->y; + + gtk_grab_add (widget); + select->drag_mode = DRAG_Z; + } + break; + + case GDK_BUTTON_RELEASE: + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (select->drag_mode != DRAG_Z || bevent->button != 1) + return FALSE; + + z = bevent->y; + + gtk_grab_remove (widget); + select->drag_mode = DRAG_NONE; + } + break; + + case GDK_MOTION_NOTIFY: + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + + if (select->drag_mode != DRAG_Z) + return FALSE; + + z = mevent->y; + } + break; + + default: + return FALSE; + } + + gtk_widget_get_allocation (select->z_color, &allocation); + + if (allocation.height > 1) + select->pos[2] = 1.0 - z / (allocation.height - 1); + + select->pos[2] = CLAMP (select->pos[2], 0.0, 1.0); + + gtk_widget_queue_draw (select->z_color); + + gimp_color_select_update (select, + UPDATE_VALUES | UPDATE_XY_COLOR | UPDATE_CALLER); + + /* Ask for more motion events in case the event was a hint */ + gdk_event_request_motions ((GdkEventMotion *) event); + + return TRUE; +} + +static void +gimp_color_select_render (GtkWidget *preview, + guchar *buf, + gint width, + gint height, + gint rowstride, + ColorSelectFillType fill_type, + const GimpHSV *hsv, + const GimpRGB *rgb, + const guchar *oog_color) +{ + ColorSelectFill csf; + + csf.width = width; + csf.height = height; + csf.hsv = *hsv; + csf.rgb = *rgb; + csf.render_line = render_funcs[fill_type]; + + csf.oog_color[0] = oog_color[0]; + csf.oog_color[1] = oog_color[1]; + csf.oog_color[2] = oog_color[2]; + + babl_process (fish_rgb_to_lch, rgb, &csf.lch, 1); + + for (csf.y = 0; csf.y < csf.height; csf.y++) + { + csf.buffer = buf; + + csf.render_line (&csf); + + buf += rowstride; + } +} + +static void +color_select_render_red (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gint i, r; + + r = (csf->height - csf->y + 1) * 255 / csf->height; + r = CLAMP (r, 0, 255); + + for (i = 0; i < csf->width; i++) + { + *p++ = r; + *p++ = 0; + *p++ = 0; + } +} + +static void +color_select_render_green (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gint i, g; + + g = (csf->height - csf->y + 1) * 255 / csf->height; + g = CLAMP (g, 0, 255); + + for (i = 0; i < csf->width; i++) + { + *p++ = 0; + *p++ = g; + *p++ = 0; + } +} + +static void +color_select_render_blue (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gint i, b; + + b = (csf->height - csf->y + 1) * 255 / csf->height; + b = CLAMP (b, 0, 255); + + for (i = 0; i < csf->width; i++) + { + *p++ = 0; + *p++ = 0; + *p++ = b; + } +} + +static void +color_select_render_hue (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat h, f; + gint r, g, b; + gint i; + + h = csf->y * 360.0 / csf->height; + h = CLAMP (360 - h, 0, 360); + + h /= 60; + f = (h - (int) h) * 255; + + r = g = b = 0; + + switch ((int) h) + { + case 0: + r = 255; + g = f; + b = 0; + break; + case 1: + r = 255 - f; + g = 255; + b = 0; + break; + case 2: + r = 0; + g = 255; + b = f; + break; + case 3: + r = 0; + g = 255 - f; + b = 255; + break; + case 4: + r = f; + g = 0; + b = 255; + break; + case 5: + r = 255; + g = 0; + b = 255 - f; + break; + } + + for (i = 0; i < csf->width; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + } +} + +static void +color_select_render_saturation (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gint s; + gint i; + + s = csf->y * 255 / csf->height; + s = CLAMP (s, 0, 255); + + s = 255 - s; + + for (i = 0; i < csf->width; i++) + { + *p++ = s; + *p++ = s; + *p++ = s; + } +} + +static void +color_select_render_value (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gint v; + gint i; + + v = csf->y * 255 / csf->height; + v = CLAMP (v, 0, 255); + + v = 255 - v; + + for (i = 0; i < csf->width; i++) + { + *p++ = v; + *p++ = v; + *p++ = v; + } +} + +static void +color_select_render_lch_lightness (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + GimpLCH lch = { 0.0, 0.0, 0.0, 1.0 }; + guchar rgb[3]; + gint i; + + lch.l = (csf->height - 1 - csf->y) * 100.0 / csf->height; + babl_process (fish_lch_to_rgb_u8, &lch, &rgb, 1); + + for (i = 0; i < csf->width; i++) + { + *p++ = rgb[0]; + *p++ = rgb[1]; + *p++ = rgb[2]; + } +} + +static void +color_select_render_lch_chroma (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + GimpLCH lch = { 80.0, 0.0, 0.0, 1.0 }; + guchar rgb[3]; + gint i; + + lch.c = (csf->height - 1 - csf->y) * 200.0 / csf->height ; + babl_process (fish_lch_to_rgb_u8, &lch, &rgb, 1); + + for (i = 0; i < csf->width; i++) + { + *p++ = rgb[0]; + *p++ = rgb[1]; + *p++ = rgb[2]; + } +} + +static void +color_select_render_lch_hue (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + GimpLCH lch = { 80.0, 200.0, 0.0, 1.0 }; + guchar rgb[3]; + gint i; + + lch.h = (csf->height - 1 - csf->y) * 360.0 / csf->height; + babl_process (fish_lch_to_rgb_u8, &lch, &rgb, 1); + + for (i = 0; i < csf->width; i++) + { + *p++ = rgb[0]; + *p++ = rgb[1]; + *p++ = rgb[2]; + } +} + +static void +color_select_render_red_green (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat r = 0; + gfloat g = 0; + gfloat b = 0; + gfloat dr = 0; + gint i; + + b = csf->rgb.b * 255.0; + + if (b < 0.0 || b > 255.0) + { + r = csf->oog_color[0]; + g = csf->oog_color[1]; + b = csf->oog_color[2]; + } + else + { + g = (csf->height - csf->y + 1) * 255.0 / csf->height; + + dr = 255.0 / csf->width; + } + + for (i = 0; i < csf->width; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + + r += dr; + } +} + +static void +color_select_render_red_blue (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat r = 0; + gfloat g = 0; + gfloat b = 0; + gfloat dr = 0; + gint i; + + g = csf->rgb.g * 255.0; + + if (g < 0.0 || g > 255.0) + { + r = csf->oog_color[0]; + g = csf->oog_color[1]; + b = csf->oog_color[2]; + } + else + { + b = (csf->height - csf->y + 1) * 255.0 / csf->height; + + dr = 255.0 / csf->width; + } + + for (i = 0; i < csf->width; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + + r += dr; + } +} + +static void +color_select_render_green_blue (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat r = 0; + gfloat g = 0; + gfloat b = 0; + gfloat dg = 0; + gint i; + + r = csf->rgb.r * 255.0; + + if (r < 0.0 || r > 255.0) + { + r = csf->oog_color[0]; + g = csf->oog_color[1]; + b = csf->oog_color[2]; + } + else + { + b = (csf->height - csf->y + 1) * 255.0 / csf->height; + + dg = 255.0 / csf->width; + } + + for (i = 0; i < csf->width; i++) + { + *p++ = r; + *p++ = g; + *p++ = b; + + g += dg; + } +} + +static void +color_select_render_hue_saturation (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat h, dh, s, v; + gint f; + gint i; + + v = csf->hsv.v; + + s = (gfloat) csf->y / csf->height; + s = CLAMP (s, 0.0, 1.0); + s = 1.0 - s; + + h = 0; + dh = 360.0 / csf->width; + + for (i = 0; i < csf->width; i++) + { + gfloat r, g, b; + + f = ((h / 60) - (int) (h / 60)) * 255; + + switch ((int) (h / 60)) + { + default: + case 0: + r = v * 255; + g = v * (255 - (s * (255 - f))); + b = v * 255 * (1 - s); + break; + case 1: + r = v * (255 - s * f); + g = v * 255; + b = v * 255 * (1 - s); + break; + case 2: + r = v * 255 * (1 - s); + g = v *255; + b = v * (255 - (s * (255 - f))); + break; + case 3: + r = v * 255 * (1 - s); + g = v * (255 - s * f); + b = v * 255; + break; + case 4: + r = v * (255 - (s * (255 - f))); + g = v * (255 * (1 - s)); + b = v * 255; + break; + case 5: + r = v * 255; + g = v * 255 * (1 - s); + b = v * (255 - s * f); + break; + } + + if (r < 0.0 || r > 255.0 || + g < 0.0 || g > 255.0 || + b < 0.0 || b > 255.0) + { + *p++ = csf->oog_color[0]; + *p++ = csf->oog_color[1]; + *p++ = csf->oog_color[2]; + } + else + { + *p++ = r; + *p++ = g; + *p++ = b; + } + + h += dh; + } +} + +static void +color_select_render_hue_value (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat h, dh, s, v; + gint f; + gint i; + + s = csf->hsv.s; + + v = (gfloat) csf->y / csf->height; + v = CLAMP (v, 0.0, 1.0); + v = 1.0 - v; + + h = 0; + dh = 360.0 / csf->width; + + for (i = 0; i < csf->width; i++) + { + gfloat r, g, b; + + f = ((h / 60) - (int) (h / 60)) * 255; + + switch ((int) (h / 60)) + { + default: + case 0: + r = v * 255; + g = v * (255 - (s * (255 - f))); + b = v * 255 * (1 - s); + break; + case 1: + r = v * (255 - s * f); + g = v * 255; + b = v * 255 * (1 - s); + break; + case 2: + r = v * 255 * (1 - s); + g = v *255; + b = v * (255 - (s * (255 - f))); + break; + case 3: + r = v * 255 * (1 - s); + g = v * (255 - s * f); + b = v * 255; + break; + case 4: + r = v * (255 - (s * (255 - f))); + g = v * (255 * (1 - s)); + b = v * 255; + break; + case 5: + r = v * 255; + g = v * 255 * (1 - s); + b = v * (255 - s * f); + break; + } + + if (r < 0.0 || r > 255.0 || + g < 0.0 || g > 255.0 || + b < 0.0 || b > 255.0) + { + *p++ = csf->oog_color[0]; + *p++ = csf->oog_color[1]; + *p++ = csf->oog_color[2]; + } + else + { + *p++ = r; + *p++ = g; + *p++ = b; + } + + h += dh; + } +} + +static void +color_select_render_saturation_value (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + gfloat h, s, ds, v; + gint f; + gint i; + + h = (gfloat) csf->hsv.h * 360.0; + if (h >= 360) + h -= 360; + h /= 60; + f = (h - (gint) h) * 255; + + v = (gfloat) csf->y / csf->height; + v = CLAMP (v, 0.0, 1.0); + v = 1.0 - v; + + s = 0; + ds = 1.0 / csf->width; + + switch ((gint) h) + { + case 0: + for (i = 0; i < csf->width; i++) + { + *p++ = v * 255; + *p++ = v * (255 - (s * (255 - f))); + *p++ = v * 255 * (1 - s); + + s += ds; + } + break; + case 1: + for (i = 0; i < csf->width; i++) + { + *p++ = v * (255 - s * f); + *p++ = v * 255; + *p++ = v * 255 * (1 - s); + + s += ds; + } + break; + case 2: + for (i = 0; i < csf->width; i++) + { + *p++ = v * 255 * (1 - s); + *p++ = v *255; + *p++ = v * (255 - (s * (255 - f))); + + s += ds; + } + break; + case 3: + for (i = 0; i < csf->width; i++) + { + *p++ = v * 255 * (1 - s); + *p++ = v * (255 - s * f); + *p++ = v * 255; + + s += ds; + } + break; + case 4: + for (i = 0; i < csf->width; i++) + { + *p++ = v * (255 - (s * (255 - f))); + *p++ = v * (255 * (1 - s)); + *p++ = v * 255; + + s += ds; + } + break; + case 5: + for (i = 0; i < csf->width; i++) + { + *p++ = v * 255; + *p++ = v * 255 * (1 - s); + *p++ = v * (255 - s * f); + + s += ds; + } + break; + } +} + +static void +color_select_render_lch_chroma_lightness (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + GimpLCH lch; + gint i; + + lch.l = (csf->height - 1 - csf->y) * 100.0 / csf->height; + lch.h = csf->lch.h; + + for (i = 0; i < csf->width; i++) + { + GimpRGB rgb; + + lch.c = i * 200.0 / csf->width; + + babl_process (fish_lch_to_rgb, &lch, &rgb, 1); + + if (rgb.r < 0.0 || rgb.r > 1.0 || + rgb.g < 0.0 || rgb.g > 1.0 || + rgb.b < 0.0 || rgb.b > 1.0) + { + p[0] = csf->oog_color[0]; + p[1] = csf->oog_color[1]; + p[2] = csf->oog_color[2]; + } + else + { + gimp_rgb_get_uchar (&rgb, p, p + 1, p + 2); + } + + p += 3; + } +} + +static void +color_select_render_lch_hue_lightness (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + GimpLCH lch; + gint i; + + lch.l = (csf->height - 1 - csf->y) * 100.0 / csf->height; + lch.c = csf->lch.c; + + for (i = 0; i < csf->width; i++) + { + GimpRGB rgb; + + lch.h = i * 360.0 / csf->width; + + babl_process (fish_lch_to_rgb, &lch, &rgb, 1); + + if (rgb.r < 0.0 || rgb.r > 1.0 || + rgb.g < 0.0 || rgb.g > 1.0 || + rgb.b < 0.0 || rgb.b > 1.0) + { + p[0] = csf->oog_color[0]; + p[1] = csf->oog_color[1]; + p[2] = csf->oog_color[2]; + } + else + { + gimp_rgb_get_uchar (&rgb, p, p + 1, p + 2); + } + + p += 3; + } +} + +static void +color_select_render_lch_hue_chroma (ColorSelectFill *csf) +{ + guchar *p = csf->buffer; + GimpLCH lch; + gint i; + + lch.l = csf->lch.l; + lch.c = (csf->height - 1 - csf->y) * 200.0 / csf->height; + + for (i = 0; i < csf->width; i++) + { + GimpRGB rgb; + + lch.h = i * 360.0 / csf->width; + + babl_process (fish_lch_to_rgb, &lch, &rgb, 1); + + if (rgb.r < 0.0 || rgb.r > 1.0 || + rgb.g < 0.0 || rgb.g > 1.0 || + rgb.b < 0.0 || rgb.b > 1.0) + { + p[0] = csf->oog_color[0]; + p[1] = csf->oog_color[1]; + p[2] = csf->oog_color[2]; + } + else + { + gimp_rgb_get_uchar (&rgb, p, p + 1, p + 2); + } + + p += 3; + } +} + +static void +gimp_color_select_create_transform (GimpColorSelect *select) +{ + if (select->config) + { + static GimpColorProfile *profile = NULL; + + const Babl *format = babl_format ("cairo-RGB24"); + + if (G_UNLIKELY (! profile)) + profile = gimp_color_profile_new_rgb_srgb (); + + select->transform = gimp_widget_get_color_transform (GTK_WIDGET (select), + select->config, + profile, + format, + format); + } +} + +static void +gimp_color_select_destroy_transform (GimpColorSelect *select) +{ + if (select->transform) + { + g_object_unref (select->transform); + select->transform = NULL; + } + + gtk_widget_queue_draw (select->xy_color); + gtk_widget_queue_draw (select->z_color); +} + +static void +gimp_color_select_notify_config (GimpColorConfig *config, + const GParamSpec *pspec, + GimpColorSelect *select) +{ + gimp_color_select_destroy_transform (select); + + gimp_rgb_get_uchar (&config->out_of_gamut_color, + select->oog_color, + select->oog_color + 1, + select->oog_color + 2); + select->xy_needs_render = TRUE; + select->z_needs_render = TRUE; +} diff --git a/libgimpwidgets/gimpcolorselect.h b/libgimpwidgets/gimpcolorselect.h new file mode 100644 index 0000000..a73d37c --- /dev/null +++ b/libgimpwidgets/gimpcolorselect.h @@ -0,0 +1,41 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselect.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on color_notebook module + * Copyright (C) 1998 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_COLOR_SELECT_H__ +#define __GIMP_COLOR_SELECT_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_COLOR_SELECT (gimp_color_select_get_type ()) +#define GIMP_COLOR_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_SELECT, GimpColorSelect)) +#define GIMP_IS_COLOR_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_SELECT)) + + +GType gimp_color_select_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_COLOR_SELECT_H__ */ diff --git a/libgimpwidgets/gimpcolorselection.c b/libgimpwidgets/gimpcolorselection.c new file mode 100644 index 0000000..a7a9838 --- /dev/null +++ b/libgimpwidgets/gimpcolorselection.c @@ -0,0 +1,697 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselection.c + * Copyright (C) 2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorarea.h" +#include "gimpcolornotebook.h" +#include "gimpcolorscales.h" +#include "gimpcolorselect.h" +#include "gimpcolorselection.h" +#include "gimphelpui.h" +#include "gimpicons.h" +#include "gimpwidgets.h" +#include "gimpwidgets-private.h" + +#include "gimpwidgetsmarshal.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpcolorselection + * @title: GimpColorSelection + * @short_description: Widget for doing a color selection. + * + * Widget for doing a color selection. + **/ + + +#define COLOR_AREA_SIZE 20 + + +typedef enum +{ + UPDATE_NOTEBOOK = 1 << 0, + UPDATE_SCALES = 1 << 1, + UPDATE_ENTRY = 1 << 2, + UPDATE_COLOR = 1 << 3 +} UpdateType; + +#define UPDATE_ALL (UPDATE_NOTEBOOK | \ + UPDATE_SCALES | \ + UPDATE_ENTRY | \ + UPDATE_COLOR) + +enum +{ + COLOR_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CONFIG +}; + + +static void gimp_color_selection_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_color_selection_switch_page (GtkWidget *widget, + gpointer page, + guint page_num, + GimpColorSelection *selection); +static void gimp_color_selection_notebook_changed (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorSelection *selection); +static void gimp_color_selection_scales_changed (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorSelection *selection); +static void gimp_color_selection_color_picked (GtkWidget *widget, + const GimpRGB *rgb, + GimpColorSelection *selection); +static void gimp_color_selection_entry_changed (GimpColorHexEntry *entry, + GimpColorSelection *selection); +static void gimp_color_selection_channel_changed (GimpColorSelector *selector, + GimpColorSelectorChannel channel, + GimpColorSelection *selection); +static void gimp_color_selection_new_color_changed (GtkWidget *widget, + GimpColorSelection *selection); + +static void gimp_color_selection_update (GimpColorSelection *selection, + UpdateType update); + + +G_DEFINE_TYPE (GimpColorSelection, gimp_color_selection, GTK_TYPE_BOX) + +#define parent_class gimp_color_selection_parent_class + +static guint selection_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_color_selection_class_init (GimpColorSelectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gimp_color_selection_set_property; + + klass->color_changed = NULL; + + g_object_class_install_property (object_class, PROP_CONFIG, + g_param_spec_object ("config", + "Config", + "The color config used by this color selection", + GIMP_TYPE_COLOR_CONFIG, + G_PARAM_WRITABLE)); + + selection_signals[COLOR_CHANGED] = + g_signal_new ("color-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorSelectionClass, color_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + +} + +static void +gimp_color_selection_init (GimpColorSelection *selection) +{ + GtkWidget *main_hbox; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *button; + GtkSizeGroup *new_group; + GtkSizeGroup *old_group; + + selection->show_alpha = TRUE; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (selection), + GTK_ORIENTATION_VERTICAL); + + gimp_rgba_set (&selection->rgb, 0.0, 0.0, 0.0, 1.0); + gimp_rgb_to_hsv (&selection->rgb, &selection->hsv); + + selection->channel = GIMP_COLOR_SELECTOR_RED; + + main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (selection), main_hbox, TRUE, TRUE, 0); + gtk_widget_show (main_hbox); + + /* The left vbox with the notebook */ + selection->left_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (main_hbox), selection->left_vbox, + TRUE, TRUE, 0); + gtk_widget_show (selection->left_vbox); + + if (_gimp_ensure_modules_func) + { + g_type_class_ref (GIMP_TYPE_COLOR_SELECT); + _gimp_ensure_modules_func (); + } + + selection->notebook = gimp_color_selector_new (GIMP_TYPE_COLOR_NOTEBOOK, + &selection->rgb, + &selection->hsv, + selection->channel); + + if (_gimp_ensure_modules_func) + g_type_class_unref (g_type_class_peek (GIMP_TYPE_COLOR_SELECT)); + + gimp_color_selector_set_toggles_visible + (GIMP_COLOR_SELECTOR (selection->notebook), FALSE); + gtk_box_pack_start (GTK_BOX (selection->left_vbox), selection->notebook, + TRUE, TRUE, 0); + gtk_widget_show (selection->notebook); + + g_signal_connect (selection->notebook, "color-changed", + G_CALLBACK (gimp_color_selection_notebook_changed), + selection); + g_signal_connect (GIMP_COLOR_NOTEBOOK (selection->notebook)->notebook, + "switch-page", + G_CALLBACK (gimp_color_selection_switch_page), + selection); + + /* The hbox for the color_areas */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_end (GTK_BOX (selection->left_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* The labels */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + label = gtk_label_new (_("Current:")); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + new_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + gtk_size_group_add_widget (new_group, label); + g_object_unref (new_group); + + label = gtk_label_new (_("Old:")); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + old_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + gtk_size_group_add_widget (old_group, label); + g_object_unref (old_group); + + /* The color areas */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + selection->new_color = gimp_color_area_new (&selection->rgb, + selection->show_alpha ? + GIMP_COLOR_AREA_SMALL_CHECKS : + GIMP_COLOR_AREA_FLAT, + GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK); + gtk_size_group_add_widget (new_group, selection->new_color); + gtk_box_pack_start (GTK_BOX (vbox), selection->new_color, FALSE, FALSE, 0); + gtk_widget_show (selection->new_color); + + g_signal_connect (selection->new_color, "color-changed", + G_CALLBACK (gimp_color_selection_new_color_changed), + selection); + + selection->old_color = gimp_color_area_new (&selection->rgb, + selection->show_alpha ? + GIMP_COLOR_AREA_SMALL_CHECKS : + GIMP_COLOR_AREA_FLAT, + GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK); + gtk_drag_dest_unset (selection->old_color); + gtk_size_group_add_widget (old_group, selection->old_color); + gtk_box_pack_start (GTK_BOX (vbox), selection->old_color, FALSE, FALSE, 0); + gtk_widget_show (selection->old_color); + + /* The right vbox with color scales */ + selection->right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (main_hbox), selection->right_vbox, + TRUE, TRUE, 0); + gtk_widget_show (selection->right_vbox); + + selection->scales = gimp_color_selector_new (GIMP_TYPE_COLOR_SCALES, + &selection->rgb, + &selection->hsv, + selection->channel); + gimp_color_selector_set_toggles_visible + (GIMP_COLOR_SELECTOR (selection->scales), TRUE); + gimp_color_selector_set_show_alpha (GIMP_COLOR_SELECTOR (selection->scales), + selection->show_alpha); + gtk_box_pack_start (GTK_BOX (selection->right_vbox), selection->scales, + TRUE, TRUE, 0); + gtk_widget_show (selection->scales); + + g_signal_connect (selection->scales, "channel-changed", + G_CALLBACK (gimp_color_selection_channel_changed), + selection); + g_signal_connect (selection->scales, "color-changed", + G_CALLBACK (gimp_color_selection_scales_changed), + selection); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (selection->right_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* The color picker */ + button = gimp_pick_button_new (); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "color-picked", + G_CALLBACK (gimp_color_selection_color_picked), + selection); + + /* The hex triplet entry */ + entry = gimp_color_hex_entry_new (); + gtk_box_pack_end (GTK_BOX (hbox), entry, TRUE, TRUE, 0); + gtk_widget_show (entry); + + label = gtk_label_new_with_mnemonic (_("HTML _notation:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + g_object_set_data (G_OBJECT (selection), "color-hex-entry", entry); + + g_signal_connect (entry, "color-changed", + G_CALLBACK (gimp_color_selection_entry_changed), + selection); +} + +static void +gimp_color_selection_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorSelection *selection = GIMP_COLOR_SELECTION (object); + + switch (property_id) + { + case PROP_CONFIG: + gimp_color_selection_set_config (selection, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/** + * gimp_color_selection_new: + * + * Creates a new #GimpColorSelection widget. + * + * Return value: The new #GimpColorSelection widget. + **/ +GtkWidget * +gimp_color_selection_new (void) +{ + return g_object_new (GIMP_TYPE_COLOR_SELECTION, NULL); +} + +/** + * gimp_color_selection_set_show_alpha: + * @selection: A #GimpColorSelection widget. + * @show_alpha: The new @show_alpha setting. + * + * Sets the @show_alpha property of the @selection widget. + **/ +void +gimp_color_selection_set_show_alpha (GimpColorSelection *selection, + gboolean show_alpha) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + + if (show_alpha != selection->show_alpha) + { + selection->show_alpha = show_alpha ? TRUE : FALSE; + + gimp_color_selector_set_show_alpha + (GIMP_COLOR_SELECTOR (selection->notebook), selection->show_alpha); + gimp_color_selector_set_show_alpha + (GIMP_COLOR_SELECTOR (selection->scales), selection->show_alpha); + + gimp_color_area_set_type (GIMP_COLOR_AREA (selection->new_color), + selection->show_alpha ? + GIMP_COLOR_AREA_SMALL_CHECKS : + GIMP_COLOR_AREA_FLAT); + gimp_color_area_set_type (GIMP_COLOR_AREA (selection->old_color), + selection->show_alpha ? + GIMP_COLOR_AREA_SMALL_CHECKS : + GIMP_COLOR_AREA_FLAT); + } +} + +/** + * gimp_color_selection_get_show_alpha: + * @selection: A #GimpColorSelection widget. + * + * Returns the @selection's @show_alpha property. + * + * Return value: #TRUE if the #GimpColorSelection has alpha controls. + **/ +gboolean +gimp_color_selection_get_show_alpha (GimpColorSelection *selection) +{ + g_return_val_if_fail (GIMP_IS_COLOR_SELECTION (selection), FALSE); + + return selection->show_alpha; +} + +/** + * gimp_color_selection_set_color: + * @selection: A #GimpColorSelection widget. + * @color: The @color to set as current color. + * + * Sets the #GimpColorSelection's current color to the new @color. + **/ +void +gimp_color_selection_set_color (GimpColorSelection *selection, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + g_return_if_fail (color != NULL); + + selection->rgb = *color; + gimp_rgb_to_hsv (&selection->rgb, &selection->hsv); + + gimp_color_selection_update (selection, UPDATE_ALL); + + gimp_color_selection_color_changed (selection); +} + +/** + * gimp_color_selection_get_color: + * @selection: A #GimpColorSelection widget. + * @color: Return location for the @selection's current @color. + * + * This function returns the #GimpColorSelection's current color. + **/ +void +gimp_color_selection_get_color (GimpColorSelection *selection, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + g_return_if_fail (color != NULL); + + *color = selection->rgb; +} + +/** + * gimp_color_selection_set_old_color: + * @selection: A #GimpColorSelection widget. + * @color: The @color to set as old color. + * + * Sets the #GimpColorSelection's old color. + **/ +void +gimp_color_selection_set_old_color (GimpColorSelection *selection, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + g_return_if_fail (color != NULL); + + gimp_color_area_set_color (GIMP_COLOR_AREA (selection->old_color), color); +} + +/** + * gimp_color_selection_get_old_color: + * @selection: A #GimpColorSelection widget. + * @color: Return location for the @selection's old @color. + * + * This function returns the #GimpColorSelection's old color. + **/ +void +gimp_color_selection_get_old_color (GimpColorSelection *selection, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + g_return_if_fail (color != NULL); + + gimp_color_area_get_color (GIMP_COLOR_AREA (selection->old_color), color); +} + +/** + * gimp_color_selection_reset: + * @selection: A #GimpColorSelection widget. + * + * Sets the #GimpColorSelection's current color to its old color. + **/ +void +gimp_color_selection_reset (GimpColorSelection *selection) +{ + GimpRGB color; + + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + + gimp_color_area_get_color (GIMP_COLOR_AREA (selection->old_color), &color); + gimp_color_selection_set_color (selection, &color); +} + +/** + * gimp_color_selection_color_changed: + * @selection: A #GimpColorSelection widget. + * + * Emits the "color-changed" signal. + **/ +void +gimp_color_selection_color_changed (GimpColorSelection *selection) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + + g_signal_emit (selection, selection_signals[COLOR_CHANGED], 0); +} + +/** + * gimp_color_selection_set_config: + * @selection: A #GimpColorSelection widget. + * @config: A #GimpColorConfig object. + * + * Sets the color management configuration to use with this color selection. + * + * Since: 2.4 + */ +void +gimp_color_selection_set_config (GimpColorSelection *selection, + GimpColorConfig *config) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTION (selection)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + gimp_color_selector_set_config (GIMP_COLOR_SELECTOR (selection->notebook), + config); + gimp_color_selector_set_config (GIMP_COLOR_SELECTOR (selection->scales), + config); + gimp_color_area_set_color_config (GIMP_COLOR_AREA (selection->old_color), + config); + gimp_color_area_set_color_config (GIMP_COLOR_AREA (selection->new_color), + config); +} + +/* private functions */ + +static void +gimp_color_selection_switch_page (GtkWidget *widget, + gpointer page, + guint page_num, + GimpColorSelection *selection) +{ + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (selection->notebook); + gboolean sensitive; + + sensitive = + (GIMP_COLOR_SELECTOR_GET_CLASS (notebook->cur_page)->set_channel != NULL); + + gimp_color_selector_set_toggles_sensitive + (GIMP_COLOR_SELECTOR (selection->scales), sensitive); +} + +static void +gimp_color_selection_notebook_changed (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorSelection *selection) +{ + selection->hsv = *hsv; + selection->rgb = *rgb; + + gimp_color_selection_update (selection, + UPDATE_SCALES | UPDATE_ENTRY | UPDATE_COLOR); + gimp_color_selection_color_changed (selection); +} + +static void +gimp_color_selection_scales_changed (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorSelection *selection) +{ + selection->rgb = *rgb; + selection->hsv = *hsv; + + gimp_color_selection_update (selection, + UPDATE_ENTRY | UPDATE_NOTEBOOK | UPDATE_COLOR); + gimp_color_selection_color_changed (selection); +} + +static void +gimp_color_selection_color_picked (GtkWidget *widget, + const GimpRGB *rgb, + GimpColorSelection *selection) +{ + gimp_color_selection_set_color (selection, rgb); +} + +static void +gimp_color_selection_entry_changed (GimpColorHexEntry *entry, + GimpColorSelection *selection) +{ + gimp_color_hex_entry_get_color (entry, &selection->rgb); + + gimp_rgb_to_hsv (&selection->rgb, &selection->hsv); + + gimp_color_selection_update (selection, + UPDATE_NOTEBOOK | UPDATE_SCALES | UPDATE_COLOR); + gimp_color_selection_color_changed (selection); +} + +static void +gimp_color_selection_channel_changed (GimpColorSelector *selector, + GimpColorSelectorChannel channel, + GimpColorSelection *selection) +{ + selection->channel = channel; + + gimp_color_selector_set_channel (GIMP_COLOR_SELECTOR (selection->notebook), + selection->channel); +} + +static void +gimp_color_selection_new_color_changed (GtkWidget *widget, + GimpColorSelection *selection) +{ + gimp_color_area_get_color (GIMP_COLOR_AREA (widget), &selection->rgb); + gimp_rgb_to_hsv (&selection->rgb, &selection->hsv); + + gimp_color_selection_update (selection, + UPDATE_NOTEBOOK | UPDATE_SCALES | UPDATE_ENTRY); + gimp_color_selection_color_changed (selection); +} + +static void +gimp_color_selection_update (GimpColorSelection *selection, + UpdateType update) +{ + if (update & UPDATE_NOTEBOOK) + { + g_signal_handlers_block_by_func (selection->notebook, + gimp_color_selection_notebook_changed, + selection); + + gimp_color_selector_set_color (GIMP_COLOR_SELECTOR (selection->notebook), + &selection->rgb, + &selection->hsv); + + g_signal_handlers_unblock_by_func (selection->notebook, + gimp_color_selection_notebook_changed, + selection); + } + + if (update & UPDATE_SCALES) + { + g_signal_handlers_block_by_func (selection->scales, + gimp_color_selection_scales_changed, + selection); + + gimp_color_selector_set_color (GIMP_COLOR_SELECTOR (selection->scales), + &selection->rgb, + &selection->hsv); + + g_signal_handlers_unblock_by_func (selection->scales, + gimp_color_selection_scales_changed, + selection); + } + + if (update & UPDATE_ENTRY) + { + GimpColorHexEntry *entry; + + entry = g_object_get_data (G_OBJECT (selection), "color-hex-entry"); + + g_signal_handlers_block_by_func (entry, + gimp_color_selection_entry_changed, + selection); + + gimp_color_hex_entry_set_color (entry, &selection->rgb); + + g_signal_handlers_unblock_by_func (entry, + gimp_color_selection_entry_changed, + selection); + } + + if (update & UPDATE_COLOR) + { + g_signal_handlers_block_by_func (selection->new_color, + gimp_color_selection_new_color_changed, + selection); + + gimp_color_area_set_color (GIMP_COLOR_AREA (selection->new_color), + &selection->rgb); + + g_signal_handlers_unblock_by_func (selection->new_color, + gimp_color_selection_new_color_changed, + selection); + } +} diff --git a/libgimpwidgets/gimpcolorselection.h b/libgimpwidgets/gimpcolorselection.h new file mode 100644 index 0000000..16bf915 --- /dev/null +++ b/libgimpwidgets/gimpcolorselection.h @@ -0,0 +1,106 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselection.h + * Copyright (C) 2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_SELECTION_H__ +#define __GIMP_COLOR_SELECTION_H__ + +G_BEGIN_DECLS + +/* For information look at the html documentation */ + + +#define GIMP_TYPE_COLOR_SELECTION (gimp_color_selection_get_type ()) +#define GIMP_COLOR_SELECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_SELECTION, GimpColorSelection)) +#define GIMP_COLOR_SELECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_SELECTION, GimpColorSelectionClass)) +#define GIMP_IS_COLOR_SELECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_SELECTION)) +#define GIMP_IS_COLOR_SELECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_SELECTION)) +#define GIMP_COLOR_SELECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_SELECTION, GimpColorSelectionClass)) + + +typedef struct _GimpColorSelectionClass GimpColorSelectionClass; + +struct _GimpColorSelection +{ + GtkBox parent_instance; + + gboolean show_alpha; + + GimpHSV hsv; + GimpRGB rgb; + GimpColorSelectorChannel channel; + + GtkWidget *left_vbox; + GtkWidget *right_vbox; + + GtkWidget *notebook; + GtkWidget *scales; + + GtkWidget *new_color; + GtkWidget *old_color; +}; + +struct _GimpColorSelectionClass +{ + GtkBoxClass parent_class; + + void (* color_changed) (GimpColorSelection *selection); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_color_selection_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_selection_new (void); + +void gimp_color_selection_set_show_alpha (GimpColorSelection *selection, + gboolean show_alpha); +gboolean gimp_color_selection_get_show_alpha (GimpColorSelection *selection); + +void gimp_color_selection_set_color (GimpColorSelection *selection, + const GimpRGB *color); +void gimp_color_selection_get_color (GimpColorSelection *selection, + GimpRGB *color); + +void gimp_color_selection_set_old_color (GimpColorSelection *selection, + const GimpRGB *color); +void gimp_color_selection_get_old_color (GimpColorSelection *selection, + GimpRGB *color); + +void gimp_color_selection_reset (GimpColorSelection *selection); + +void gimp_color_selection_color_changed (GimpColorSelection *selection); + +void gimp_color_selection_set_config (GimpColorSelection *selection, + GimpColorConfig *config); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_SELECTION_H__ */ diff --git a/libgimpwidgets/gimpcolorselector.c b/libgimpwidgets/gimpcolorselector.c new file mode 100644 index 0000000..72023ac --- /dev/null +++ b/libgimpwidgets/gimpcolorselector.c @@ -0,0 +1,638 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselector.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on: + * Colour selector module + * Copyright (C) 1999 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcolorselector.h" +#include "gimpicons.h" +#include "gimpwidgetsmarshal.h" + + +/** + * SECTION: gimpcolorselector + * @title: GimpColorSelector + * @short_description: Pluggable GIMP color selector modules. + * @see_also: #GModule, #GTypeModule, #GimpModule + * + * Functions and definitions for creating pluggable GIMP color + * selector modules. + **/ + + +enum +{ + COLOR_CHANGED, + CHANNEL_CHANGED, + MODEL_VISIBLE_CHANGED, + LAST_SIGNAL +}; + + +typedef struct _GimpColorSelectorPrivate GimpColorSelectorPrivate; + +struct _GimpColorSelectorPrivate +{ + gboolean model_visible[3]; +}; + +#define GET_PRIVATE(obj) \ + ((GimpColorSelectorPrivate *) gimp_color_selector_get_instance_private ((GimpColorSelector *) (obj))) + + +static void gimp_color_selector_dispose (GObject *object); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpColorSelector, gimp_color_selector, + GTK_TYPE_BOX) + +#define parent_class gimp_color_selector_parent_class + +static guint selector_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_color_selector_class_init (GimpColorSelectorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_color_selector_dispose; + + selector_signals[COLOR_CHANGED] = + g_signal_new ("color-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorSelectorClass, color_changed), + NULL, NULL, + _gimp_widgets_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, 2, + G_TYPE_POINTER, + G_TYPE_POINTER); + + selector_signals[CHANNEL_CHANGED] = + g_signal_new ("channel-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorSelectorClass, channel_changed), + NULL, NULL, + _gimp_widgets_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GIMP_TYPE_COLOR_SELECTOR_CHANNEL); + + selector_signals[MODEL_VISIBLE_CHANGED] = + g_signal_new ("model-visible-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorSelectorClass, model_visible_changed), + NULL, NULL, + _gimp_widgets_marshal_VOID__ENUM_BOOLEAN, + G_TYPE_NONE, 2, + GIMP_TYPE_COLOR_SELECTOR_MODEL, + G_TYPE_BOOLEAN); + + klass->name = "Unnamed"; + klass->help_id = NULL; + klass->icon_name = GIMP_ICON_PALETTE; + + klass->set_toggles_visible = NULL; + klass->set_toggles_sensitive = NULL; + klass->set_show_alpha = NULL; + klass->set_color = NULL; + klass->set_channel = NULL; + klass->set_model_visible = NULL; + klass->color_changed = NULL; + klass->channel_changed = NULL; + klass->model_visible_changed = NULL; + klass->set_config = NULL; +} + +static void +gimp_color_selector_init (GimpColorSelector *selector) +{ + GimpColorSelectorPrivate *priv = GET_PRIVATE (selector); + + selector->toggles_visible = TRUE; + selector->toggles_sensitive = TRUE; + selector->show_alpha = TRUE; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (selector), + GTK_ORIENTATION_VERTICAL); + + gimp_rgba_set (&selector->rgb, 0.0, 0.0, 0.0, 1.0); + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + + selector->channel = GIMP_COLOR_SELECTOR_RED; + + priv->model_visible[GIMP_COLOR_SELECTOR_MODEL_RGB] = TRUE; + priv->model_visible[GIMP_COLOR_SELECTOR_MODEL_LCH] = TRUE; + priv->model_visible[GIMP_COLOR_SELECTOR_MODEL_HSV] = FALSE; +} + +static void +gimp_color_selector_dispose (GObject *object) +{ + gimp_color_selector_set_config (GIMP_COLOR_SELECTOR (object), NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +/* public functions */ + +/** + * gimp_color_selector_new: + * @selector_type: The #GType of the selector to create. + * @rgb: The initial color to be edited. + * @hsv: The same color in HSV. + * @channel: The selector's initial channel. + * + * Creates a new #GimpColorSelector widget of type @selector_type. + * + * Note that this is mostly internal API to be used by other widgets. + * + * Please use gimp_color_selection_new() for the "GIMP-typical" color + * selection widget. Also see gimp_color_button_new(). + * + * Retunn value: the new #GimpColorSelector widget. + **/ +GtkWidget * +gimp_color_selector_new (GType selector_type, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorSelectorChannel channel) +{ + GimpColorSelector *selector; + + g_return_val_if_fail (g_type_is_a (selector_type, GIMP_TYPE_COLOR_SELECTOR), + NULL); + g_return_val_if_fail (rgb != NULL, NULL); + g_return_val_if_fail (hsv != NULL, NULL); + + selector = g_object_new (selector_type, NULL); + + gimp_color_selector_set_color (selector, rgb, hsv); + gimp_color_selector_set_channel (selector, channel); + + return GTK_WIDGET (selector); +} + +/** + * gimp_color_selector_set_toggles_visible: + * @selector: A #GimpColorSelector widget. + * @visible: The new @visible setting. + * + * Sets the @visible property of the @selector's toggles. + * + * This function has no effect if this @selector instance has no + * toggles to switch channels. + **/ +void +gimp_color_selector_set_toggles_visible (GimpColorSelector *selector, + gboolean visible) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + if (selector->toggles_visible != visible) + { + GimpColorSelectorClass *selector_class; + + selector->toggles_visible = visible ? TRUE : FALSE; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_toggles_visible) + selector_class->set_toggles_visible (selector, visible); + } +} + +/** + * gimp_color_selector_get_toggles_visible: + * @selector: A #GimpColorSelector widget. + * + * Returns the @visible property of the @selector's toggles. + * + * Return value: #TRUE if the #GimpColorSelector's toggles are visible. + * + * Since: 2.10 + **/ +gboolean +gimp_color_selector_get_toggles_visible (GimpColorSelector *selector) +{ + g_return_val_if_fail (GIMP_IS_COLOR_SELECTOR (selector), FALSE); + + return selector->toggles_visible; +} + +/** + * gimp_color_selector_set_toggles_sensitive: + * @selector: A #GimpColorSelector widget. + * @sensitive: The new @sensitive setting. + * + * Sets the @sensitive property of the @selector's toggles. + * + * This function has no effect if this @selector instance has no + * toggles to switch channels. + **/ +void +gimp_color_selector_set_toggles_sensitive (GimpColorSelector *selector, + gboolean sensitive) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + if (selector->toggles_sensitive != sensitive) + { + GimpColorSelectorClass *selector_class; + + selector->toggles_sensitive = sensitive ? TRUE : FALSE; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_toggles_sensitive) + selector_class->set_toggles_sensitive (selector, sensitive); + } +} + +/** + * gimp_color_selector_get_toggles_sensitive: + * @selector: A #GimpColorSelector widget. + * + * Returns the @sensitive property of the @selector's toggles. + * + * Return value: #TRUE if the #GimpColorSelector's toggles are sensitive. + * + * Since: 2.10 + **/ +gboolean +gimp_color_selector_get_toggles_sensitive (GimpColorSelector *selector) +{ + g_return_val_if_fail (GIMP_IS_COLOR_SELECTOR (selector), FALSE); + + return selector->toggles_sensitive; +} + +/** + * gimp_color_selector_set_show_alpha: + * @selector: A #GimpColorSelector widget. + * @show_alpha: The new @show_alpha setting. + * + * Sets the @show_alpha property of the @selector widget. + **/ +void +gimp_color_selector_set_show_alpha (GimpColorSelector *selector, + gboolean show_alpha) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + if (show_alpha != selector->show_alpha) + { + GimpColorSelectorClass *selector_class; + + selector->show_alpha = show_alpha ? TRUE : FALSE; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_show_alpha) + selector_class->set_show_alpha (selector, show_alpha); + } +} + +/** + * gimp_color_selector_get_show_alpha: + * @selector: A #GimpColorSelector widget. + * + * Returns the @selector's @show_alpha property. + * + * Return value: #TRUE if the #GimpColorSelector has alpha controls. + * + * Since: 2.10 + **/ +gboolean +gimp_color_selector_get_show_alpha (GimpColorSelector *selector) +{ + g_return_val_if_fail (GIMP_IS_COLOR_SELECTOR (selector), FALSE); + + return selector->show_alpha; +} + +/** + * gimp_color_selector_set_color: + * @selector: A #GimpColorSelector widget. + * @rgb: The new color. + * @hsv: The same color in HSV. + * + * Sets the color shown in the @selector widget. + **/ +void +gimp_color_selector_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv) +{ + GimpColorSelectorClass *selector_class; + + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + g_return_if_fail (rgb != NULL); + g_return_if_fail (hsv != NULL); + + selector->rgb = *rgb; + selector->hsv = *hsv; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_color) + selector_class->set_color (selector, rgb, hsv); + + gimp_color_selector_color_changed (selector); +} + +/** + * gimp_color_selector_get_color: + * @selector: A #GimpColorSelector widget. + * @rgb: Return location for the color. + * @hsv: Return location for the same same color in HSV. + * + * Retrieves the color shown in the @selector widget. + * + * Since: 2.10 + **/ +void +gimp_color_selector_get_color (GimpColorSelector *selector, + GimpRGB *rgb, + GimpHSV *hsv) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + g_return_if_fail (rgb != NULL); + g_return_if_fail (hsv != NULL); + + *rgb = selector->rgb; + *hsv = selector->hsv; +} + +/** + * gimp_color_selector_set_channel: + * @selector: A #GimpColorSelector widget. + * @channel: The new @channel setting. + * + * Sets the @channel property of the @selector widget. + * + * Changes between displayed channels if this @selector instance has + * the ability to show different channels. + * This will also update the color model if needed. + **/ +void +gimp_color_selector_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + if (channel != selector->channel) + { + GimpColorSelectorClass *selector_class; + GimpColorSelectorModel model = -1; + + selector->channel = channel; + + switch (channel) + { + case GIMP_COLOR_SELECTOR_RED: + case GIMP_COLOR_SELECTOR_GREEN: + case GIMP_COLOR_SELECTOR_BLUE: + model = GIMP_COLOR_SELECTOR_MODEL_RGB; + break; + + case GIMP_COLOR_SELECTOR_HUE: + case GIMP_COLOR_SELECTOR_SATURATION: + case GIMP_COLOR_SELECTOR_VALUE: + model = GIMP_COLOR_SELECTOR_MODEL_HSV; + break; + + case GIMP_COLOR_SELECTOR_LCH_LIGHTNESS: + case GIMP_COLOR_SELECTOR_LCH_CHROMA: + case GIMP_COLOR_SELECTOR_LCH_HUE: + model = GIMP_COLOR_SELECTOR_MODEL_LCH; + break; + + case GIMP_COLOR_SELECTOR_ALPHA: + /* Alpha channel does not change the color model. */ + break; + + default: + /* Should not happen. */ + g_return_if_reached (); + break; + } + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_channel) + selector_class->set_channel (selector, channel); + + gimp_color_selector_channel_changed (selector); + + if (model != -1) + { + /* make visibility of LCH and HSV mutuallky exclusive */ + if (model == GIMP_COLOR_SELECTOR_MODEL_HSV) + { + gimp_color_selector_set_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_LCH, + FALSE); + } + else if (model == GIMP_COLOR_SELECTOR_MODEL_LCH) + { + gimp_color_selector_set_model_visible (selector, + GIMP_COLOR_SELECTOR_MODEL_HSV, + FALSE); + } + + gimp_color_selector_set_model_visible (selector, model, TRUE); + } + } +} + +/** + * gimp_color_selector_get_channel: + * @selector: A #GimpColorSelector widget. + * + * Returns the @selector's current channel. + * + * Return value: The #GimpColorSelectorChannel currently shown by the + * @selector. + * + * Since: 2.10 + **/ +GimpColorSelectorChannel +gimp_color_selector_get_channel (GimpColorSelector *selector) +{ + g_return_val_if_fail (GIMP_IS_COLOR_SELECTOR (selector), + GIMP_COLOR_SELECTOR_RED); + + return selector->channel; +} + +/** + * gimp_color_selector_set_model_visible: + * @selector: A #GimpColorSelector widget. + * @model: The affected #GimpColorSelectorModel. + * @visible: The new visible setting. + * + * Sets the @model visible/invisible on the @selector widget. + * + * Toggles visibility of displayed models if this @selector instance + * has the ability to show different color models. + * + * Since: 2.10 + **/ +void +gimp_color_selector_set_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible) +{ + GimpColorSelectorPrivate *priv; + + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + priv = GET_PRIVATE (selector); + + visible = visible ? TRUE : FALSE; + + if (visible != priv->model_visible[model]) + { + GimpColorSelectorClass *selector_class; + + priv->model_visible[model] = visible; + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_model_visible) + selector_class->set_model_visible (selector, model, visible); + + gimp_color_selector_model_visible_changed (selector, model); + } +} + +/** + * gimp_color_selector_get_model_visible: + * @selector: A #GimpColorSelector widget. + * @model: The #GimpColorSelectorModel. + * + * Return value: whether @model is visible in @selector. + * + * Since: 2.10 + **/ +gboolean +gimp_color_selector_get_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model) +{ + GimpColorSelectorPrivate *priv; + + g_return_val_if_fail (GIMP_IS_COLOR_SELECTOR (selector), FALSE); + + priv = GET_PRIVATE (selector); + + return priv->model_visible[model]; +} + +/** + * gimp_color_selector_color_changed: + * @selector: A #GimpColorSelector widget. + * + * Emits the "color-changed" signal. + **/ +void +gimp_color_selector_color_changed (GimpColorSelector *selector) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + g_signal_emit (selector, selector_signals[COLOR_CHANGED], 0, + &selector->rgb, &selector->hsv); +} + +/** + * gimp_color_selector_channel_changed: + * @selector: A #GimpColorSelector widget. + * + * Emits the "channel-changed" signal. + **/ +void +gimp_color_selector_channel_changed (GimpColorSelector *selector) +{ + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + g_signal_emit (selector, selector_signals[CHANNEL_CHANGED], 0, + selector->channel); +} + +/** + * gimp_color_selector_model_visible_changed: + * @selector: A #GimpColorSelector widget. + * + * Emits the "model-visible-changed" signal. + * + * Since: 2.10 + **/ +void +gimp_color_selector_model_visible_changed (GimpColorSelector *selector, + GimpColorSelectorModel model) +{ + GimpColorSelectorPrivate *priv; + + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + + priv = GET_PRIVATE (selector); + + g_signal_emit (selector, selector_signals[MODEL_VISIBLE_CHANGED], 0, + model, priv->model_visible[model]); +} + +/** + * gimp_color_selector_set_config: + * @selector: a #GimpColorSelector widget. + * @config: a #GimpColorConfig object. + * + * Sets the color management configuration to use with this color selector. + * + * Since: 2.4 + */ +void +gimp_color_selector_set_config (GimpColorSelector *selector, + GimpColorConfig *config) +{ + GimpColorSelectorClass *selector_class; + + g_return_if_fail (GIMP_IS_COLOR_SELECTOR (selector)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + if (selector_class->set_config) + selector_class->set_config (selector, config); +} diff --git a/libgimpwidgets/gimpcolorselector.h b/libgimpwidgets/gimpcolorselector.h new file mode 100644 index 0000000..d6c6660 --- /dev/null +++ b/libgimpwidgets/gimpcolorselector.h @@ -0,0 +1,177 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselector.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on: + * Colour selector module + * Copyright (C) 1999 Austin Donnelly <austin@greenend.org.uk> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_COLOR_SELECTOR_H__ +#define __GIMP_COLOR_SELECTOR_H__ + +G_BEGIN_DECLS + +/* For information look at the html documentation */ + + +/** + * GIMP_COLOR_SELECTOR_SIZE: + * + * The suggested size for a color area in a #GimpColorSelector + * implementation. + **/ +#define GIMP_COLOR_SELECTOR_SIZE 150 + +/** + * GIMP_COLOR_SELECTOR_BAR_SIZE: + * + * The suggested width for a color bar in a #GimpColorSelector + * implementation. + **/ +#define GIMP_COLOR_SELECTOR_BAR_SIZE 15 + + +#define GIMP_TYPE_COLOR_SELECTOR (gimp_color_selector_get_type ()) +#define GIMP_COLOR_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_SELECTOR, GimpColorSelector)) +#define GIMP_COLOR_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_SELECTOR, GimpColorSelectorClass)) +#define GIMP_IS_COLOR_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_SELECTOR)) +#define GIMP_IS_COLOR_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_SELECTOR)) +#define GIMP_COLOR_SELECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_SELECTOR, GimpColorSelectorClass)) + + +typedef struct _GimpColorSelectorClass GimpColorSelectorClass; + +struct _GimpColorSelector +{ + GtkBox parent_instance; + + gboolean toggles_visible; + gboolean toggles_sensitive; + gboolean show_alpha; + + GimpRGB rgb; + GimpHSV hsv; + + GimpColorSelectorChannel channel; +}; + +struct _GimpColorSelectorClass +{ + GtkBoxClass parent_class; + + const gchar *name; + const gchar *help_id; +#ifdef GIMP_DISABLE_DEPRECATED + gpointer deprecated_stock_id; +#else + const gchar *stock_id; +#endif + + /* virtual functions */ + void (* set_toggles_visible) (GimpColorSelector *selector, + gboolean visible); + void (* set_toggles_sensitive) (GimpColorSelector *selector, + gboolean sensitive); + void (* set_show_alpha) (GimpColorSelector *selector, + gboolean show_alpha); + void (* set_color) (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); + void (* set_channel) (GimpColorSelector *selector, + GimpColorSelectorChannel channel); + + /* signals */ + void (* color_changed) (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); + void (* channel_changed) (GimpColorSelector *selector, + GimpColorSelectorChannel channel); + + /* another virtual function */ + void (* set_config) (GimpColorSelector *selector, + GimpColorConfig *config); + + /* icon name */ + const gchar *icon_name; + + /* another virtual function */ + void (* set_model_visible) (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible); + + /* another signal */ + void (* model_visible_changed) (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible); +}; + + +GType gimp_color_selector_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_color_selector_new (GType selector_type, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorSelectorChannel channel); + +void gimp_color_selector_set_toggles_visible (GimpColorSelector *selector, + gboolean visible); +gboolean gimp_color_selector_get_toggles_visible (GimpColorSelector *selector); + +void gimp_color_selector_set_toggles_sensitive (GimpColorSelector *selector, + gboolean sensitive); +gboolean gimp_color_selector_get_toggles_sensitive (GimpColorSelector *selector); + +void gimp_color_selector_set_show_alpha (GimpColorSelector *selector, + gboolean show_alpha); +gboolean gimp_color_selector_get_show_alpha (GimpColorSelector *selector); + +void gimp_color_selector_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); +void gimp_color_selector_get_color (GimpColorSelector *selector, + GimpRGB *rgb, + GimpHSV *hsv); + +void gimp_color_selector_set_channel (GimpColorSelector *selector, + GimpColorSelectorChannel channel); +GimpColorSelectorChannel + gimp_color_selector_get_channel (GimpColorSelector *selector); + +void gimp_color_selector_set_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model, + gboolean visible); +gboolean gimp_color_selector_get_model_visible (GimpColorSelector *selector, + GimpColorSelectorModel model); + +void gimp_color_selector_color_changed (GimpColorSelector *selector); +void gimp_color_selector_channel_changed (GimpColorSelector *selector); +void gimp_color_selector_model_visible_changed (GimpColorSelector *selector, + GimpColorSelectorModel model); + +void gimp_color_selector_set_config (GimpColorSelector *selector, + GimpColorConfig *config); + + +G_END_DECLS + +#endif /* __GIMP_COLOR_SELECTOR_H__ */ diff --git a/libgimpwidgets/gimpcontroller.c b/libgimpwidgets/gimpcontroller.c new file mode 100644 index 0000000..72199df --- /dev/null +++ b/libgimpwidgets/gimpcontroller.c @@ -0,0 +1,263 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontroller.c + * Copyright (C) 2004 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimpwidgetsmarshal.h" + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "gimpcontroller.h" +#include "gimpicons.h" + + +/** + * SECTION: gimpcontroller + * @title: GimpController + * @short_description: Pluggable GIMP input controller modules. + * + * An abstract interface for implementing arbitrary input controllers. + **/ + + +enum +{ + PROP_0, + PROP_NAME, + PROP_STATE +}; + +enum +{ + EVENT, + LAST_SIGNAL +}; + + +static void gimp_controller_finalize (GObject *object); +static void gimp_controller_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_controller_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE_WITH_CODE (GimpController, gimp_controller, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL)) + +#define parent_class gimp_controller_parent_class + +static guint controller_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_controller_class_init (GimpControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_controller_finalize; + object_class->set_property = gimp_controller_set_property; + object_class->get_property = gimp_controller_get_property; + + klass->name = "Unnamed"; + klass->help_domain = NULL; + klass->help_id = NULL; + klass->icon_name = GIMP_ICON_CONTROLLER; + + klass->get_n_events = NULL; + klass->get_event_name = NULL; + klass->event = NULL; + + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string ("name", + "Name", + "The controller's name", + "Unnamed Controller", + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_STATE, + g_param_spec_string ("state", + "State", + "The controller's state, as human-readable string", + "Unknown", + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + controller_signals[EVENT] = + g_signal_new ("event", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpControllerClass, event), + g_signal_accumulator_true_handled, NULL, + _gimp_widgets_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, 1, + G_TYPE_POINTER); +} + +static void +gimp_controller_init (GimpController *controller) +{ +} + +static void +gimp_controller_finalize (GObject *object) +{ + GimpController *controller = GIMP_CONTROLLER (object); + + g_clear_pointer (&controller->name, g_free); + g_clear_pointer (&controller->state, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_controller_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpController *controller = GIMP_CONTROLLER (object); + + switch (property_id) + { + case PROP_NAME: + if (controller->name) + g_free (controller->name); + controller->name = g_value_dup_string (value); + break; + case PROP_STATE: + if (controller->state) + g_free (controller->state); + controller->state = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_controller_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpController *controller = GIMP_CONTROLLER (object); + + switch (property_id) + { + case PROP_NAME: + g_value_set_string (value, controller->name); + break; + case PROP_STATE: + g_value_set_string (value, controller->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GimpController * +gimp_controller_new (GType controller_type) +{ + GimpController *controller; + + g_return_val_if_fail (g_type_is_a (controller_type, GIMP_TYPE_CONTROLLER), + NULL); + + controller = g_object_new (controller_type, NULL); + + return controller; +} + +gint +gimp_controller_get_n_events (GimpController *controller) +{ + g_return_val_if_fail (GIMP_IS_CONTROLLER (controller), 0); + + if (GIMP_CONTROLLER_GET_CLASS (controller)->get_n_events) + return GIMP_CONTROLLER_GET_CLASS (controller)->get_n_events (controller); + + return 0; +} + +const gchar * +gimp_controller_get_event_name (GimpController *controller, + gint event_id) +{ + const gchar *name = NULL; + + g_return_val_if_fail (GIMP_IS_CONTROLLER (controller), NULL); + + if (GIMP_CONTROLLER_GET_CLASS (controller)->get_event_name) + name = GIMP_CONTROLLER_GET_CLASS (controller)->get_event_name (controller, + event_id); + + if (! name) + name = "<invalid event id>"; + + return name; +} + +const gchar * +gimp_controller_get_event_blurb (GimpController *controller, + gint event_id) +{ + const gchar *blurb = NULL; + + g_return_val_if_fail (GIMP_IS_CONTROLLER (controller), NULL); + + if (GIMP_CONTROLLER_GET_CLASS (controller)->get_event_blurb) + blurb = GIMP_CONTROLLER_GET_CLASS (controller)->get_event_blurb (controller, + event_id); + + if (! blurb) + blurb = "<invalid event id>"; + + return blurb; +} + +gboolean +gimp_controller_event (GimpController *controller, + const GimpControllerEvent *event) +{ + gboolean retval = FALSE; + + g_return_val_if_fail (GIMP_IS_CONTROLLER (controller), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + g_signal_emit (controller, controller_signals[EVENT], 0, + event, &retval); + + return retval; +} diff --git a/libgimpwidgets/gimpcontroller.h b/libgimpwidgets/gimpcontroller.h new file mode 100644 index 0000000..b258084 --- /dev/null +++ b/libgimpwidgets/gimpcontroller.h @@ -0,0 +1,183 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontroller.h + * Copyright (C) 2004 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#error GimpController is unstable API under construction +#endif + +#ifndef __GIMP_CONTROLLER_H__ +#define __GIMP_CONTROLLER_H__ + +G_BEGIN_DECLS + +/* For information look at the html documentation */ + + +/** + * GimpControllerEventType: + * @GIMP_CONTROLLER_EVENT_TRIGGER: the event is a simple trigger + * @GIMP_CONTROLLER_EVENT_VALUE: the event carries a double value + * + * Event types for #GimpController. + **/ +typedef enum +{ + GIMP_CONTROLLER_EVENT_TRIGGER, + GIMP_CONTROLLER_EVENT_VALUE +} GimpControllerEventType; + + +typedef struct _GimpControllerEventAny GimpControllerEventAny; +typedef struct _GimpControllerEventTrigger GimpControllerEventTrigger; +typedef struct _GimpControllerEventValue GimpControllerEventValue; +typedef union _GimpControllerEvent GimpControllerEvent; + +/** + * GimpControllerEventAny: + * @type: The event's #GimpControllerEventType + * @source: The event's source #GimpController + * @event_id: The event's ID + * + * Generic controller event. Every event has these three members at the + * beginning of its struct + **/ +struct _GimpControllerEventAny +{ + GimpControllerEventType type; + GimpController *source; + gint event_id; +}; + +/** + * GimpControllerEventTrigger: + * @type: The event's #GimpControllerEventType + * @source: The event's source #GimpController + * @event_id: The event's ID + * + * Trigger controller event. + **/ +struct _GimpControllerEventTrigger +{ + GimpControllerEventType type; + GimpController *source; + gint event_id; +}; + +/** + * GimpControllerEventValue: + * @type: The event's #GimpControllerEventType + * @source: The event's source #GimpController + * @event_id: The event's ID + * @value: The event's value + * + * Value controller event. + **/ +struct _GimpControllerEventValue +{ + GimpControllerEventType type; + GimpController *source; + gint event_id; + GValue value; +}; + +/** + * GimpControllerEvent: + * @type: The event type + * @any: GimpControllerEventAny + * @trigger: GimpControllerEventTrigger + * @value: GimpControllerEventValue + * + * A union to hjold all event event types + **/ +union _GimpControllerEvent +{ + GimpControllerEventType type; + GimpControllerEventAny any; + GimpControllerEventTrigger trigger; + GimpControllerEventValue value; +}; + + +#define GIMP_TYPE_CONTROLLER (gimp_controller_get_type ()) +#define GIMP_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER, GimpController)) +#define GIMP_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER, GimpControllerClass)) +#define GIMP_IS_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER)) +#define GIMP_IS_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER)) +#define GIMP_CONTROLLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER, GimpControllerClass)) + + +typedef struct _GimpControllerClass GimpControllerClass; + +struct _GimpController +{ + GObject parent_instance; + + gchar *name; + gchar *state; +}; + +struct _GimpControllerClass +{ + GObjectClass parent_class; + + const gchar *name; + const gchar *help_domain; + const gchar *help_id; + + /* virtual functions */ + gint (* get_n_events) (GimpController *controller); + const gchar * (* get_event_name) (GimpController *controller, + gint event_id); + const gchar * (* get_event_blurb) (GimpController *controller, + gint event_id); + + /* signals */ + gboolean (* event) (GimpController *controller, + const GimpControllerEvent *event); + + const gchar *icon_name; + + /* Padding for future expansion */ + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_controller_get_type (void) G_GNUC_CONST; +GimpController * gimp_controller_new (GType controller_type); + +gint gimp_controller_get_n_events (GimpController *controller); +const gchar * gimp_controller_get_event_name (GimpController *controller, + gint event_id); +const gchar * gimp_controller_get_event_blurb (GimpController *controller, + gint event_id); + + +/* protected */ + +gboolean gimp_controller_event (GimpController *controller, + const GimpControllerEvent *event); + + +G_END_DECLS + +#endif /* __GIMP_CONTROLLER_H__ */ diff --git a/libgimpwidgets/gimpdialog.c b/libgimpwidgets/gimpdialog.c new file mode 100644 index 0000000..be92c40 --- /dev/null +++ b/libgimpwidgets/gimpdialog.c @@ -0,0 +1,689 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdialog.c + * Copyright (C) 2000-2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpdialog.h" +#include "gimphelpui.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpdialog + * @title: GimpDialog + * @short_description: Constructors for #GtkDialog's and action_areas as + * well as other dialog-related stuff. + * + * Constructors for #GtkDialog's and action_areas as well as other + * dialog-related stuff. + **/ + + +enum +{ + PROP_0, + PROP_HELP_FUNC, + PROP_HELP_ID, + PROP_PARENT +}; + + +typedef struct _GimpDialogPrivate GimpDialogPrivate; + +struct _GimpDialogPrivate +{ + GimpHelpFunc help_func; + gchar *help_id; + GtkWidget *help_button; +}; + +#define GET_PRIVATE(dialog) ((GimpDialogPrivate *) gimp_dialog_get_instance_private ((GimpDialog *) (dialog))) + + +static void gimp_dialog_constructed (GObject *object); +static void gimp_dialog_dispose (GObject *object); +static void gimp_dialog_finalize (GObject *object); +static void gimp_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_dialog_hide (GtkWidget *widget); +static gboolean gimp_dialog_delete_event (GtkWidget *widget, + GdkEventAny *event); + +static void gimp_dialog_close (GtkDialog *dialog); + +static void gimp_dialog_help (GObject *dialog); +static void gimp_dialog_response (GtkDialog *dialog, + gint response_id); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDialog, gimp_dialog, GTK_TYPE_DIALOG) + +#define parent_class gimp_dialog_parent_class + +static gboolean show_help_button = TRUE; + + +static void +gimp_dialog_class_init (GimpDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + object_class->constructed = gimp_dialog_constructed; + object_class->dispose = gimp_dialog_dispose; + object_class->finalize = gimp_dialog_finalize; + object_class->set_property = gimp_dialog_set_property; + object_class->get_property = gimp_dialog_get_property; + + widget_class->hide = gimp_dialog_hide; + widget_class->delete_event = gimp_dialog_delete_event; + + dialog_class->close = gimp_dialog_close; + + /** + * GimpDialog:help-func: + * + * Since: 2.2 + **/ + g_object_class_install_property (object_class, PROP_HELP_FUNC, + g_param_spec_pointer ("help-func", + "Help Func", + "The help function to call when F1 is hit", + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * GimpDialog:help-id: + * + * Since: 2.2 + **/ + g_object_class_install_property (object_class, PROP_HELP_ID, + g_param_spec_string ("help-id", + "Help ID", + "The help ID to pass to help-func", + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + /** + * GimpDialog:parent: + * + * Since: 2.8 + **/ + g_object_class_install_property (object_class, PROP_PARENT, + g_param_spec_object ("parent", + "Parent", + "The dialog's parent widget", + GTK_TYPE_WIDGET, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_dialog_init (GimpDialog *dialog) +{ + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_dialog_response), + NULL); +} + +static void +gimp_dialog_constructed (GObject *object) +{ + GimpDialogPrivate *private = GET_PRIVATE (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + if (private->help_func) + gimp_help_connect (GTK_WIDGET (object), + private->help_func, private->help_id, + object); + + if (show_help_button && private->help_func && private->help_id) + { + GtkDialog *dialog = GTK_DIALOG (object); + GtkWidget *action_area = gtk_dialog_get_action_area (dialog); + + private->help_button = gtk_button_new_with_mnemonic (_("_Help")); + + gtk_box_pack_end (GTK_BOX (action_area), private->help_button, + FALSE, TRUE, 0); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), + private->help_button, TRUE); + gtk_widget_show (private->help_button); + + g_signal_connect_object (private->help_button, "clicked", + G_CALLBACK (gimp_dialog_help), + dialog, G_CONNECT_SWAPPED); + } +} + +static void +gimp_dialog_dispose (GObject *object) +{ + GdkDisplay *display = NULL; + + if (g_main_depth () == 0) + { + display = gtk_widget_get_display (GTK_WIDGET (object)); + g_object_ref (display); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); + + if (display) + { + gdk_display_flush (display); + g_object_unref (display); + } +} + +static void +gimp_dialog_finalize (GObject *object) +{ + GimpDialogPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->help_id, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDialogPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_HELP_FUNC: + private->help_func = g_value_get_pointer (value); + break; + + case PROP_HELP_ID: + g_free (private->help_id); + private->help_id = g_value_dup_string (value); + gimp_help_set_help_data (GTK_WIDGET (object), NULL, private->help_id); + break; + + case PROP_PARENT: + { + GtkWidget *parent = g_value_get_object (value); + + if (parent) + { + if (GTK_IS_WINDOW (parent)) + { + gtk_window_set_transient_for (GTK_WINDOW (object), + GTK_WINDOW (parent)); + } + else + { + gtk_window_set_screen (GTK_WINDOW (object), + gtk_widget_get_screen (parent)); + gtk_window_set_position (GTK_WINDOW (object), + GTK_WIN_POS_MOUSE); + } + } + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDialogPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_HELP_FUNC: + g_value_set_pointer (value, private->help_func); + break; + + case PROP_HELP_ID: + g_value_set_string (value, private->help_id); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dialog_hide (GtkWidget *widget) +{ + /* set focus to NULL so focus_out callbacks are invoked synchronously */ + gtk_window_set_focus (GTK_WINDOW (widget), NULL); + + GTK_WIDGET_CLASS (parent_class)->hide (widget); +} + +static gboolean +gimp_dialog_delete_event (GtkWidget *widget, + GdkEventAny *event) +{ + return TRUE; +} + +static void +gimp_dialog_close (GtkDialog *dialog) +{ + /* Synthesize delete_event to close dialog. */ + + GtkWidget *widget = GTK_WIDGET (dialog); + + if (gtk_widget_get_window (widget)) + { + GdkEvent *event = gdk_event_new (GDK_DELETE); + + event->any.window = g_object_ref (gtk_widget_get_window (widget)); + event->any.send_event = TRUE; + + gtk_main_do_event (event); + gdk_event_free (event); + } +} + +static void +gimp_dialog_help (GObject *dialog) +{ + GimpDialogPrivate *private = GET_PRIVATE (dialog); + + if (private->help_func) + private->help_func (private->help_id, dialog); +} + +static void +gimp_dialog_response (GtkDialog *dialog, + gint response_id) +{ + GtkWidget *action_area; + GList *children; + GList *list; + + action_area = gtk_dialog_get_action_area (dialog); + + children = gtk_container_get_children (GTK_CONTAINER (action_area)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *widget = list->data; + + if (gtk_dialog_get_response_for_widget (dialog, widget) == response_id) + { + if (! GTK_IS_BUTTON (widget) || + gtk_button_get_focus_on_click (GTK_BUTTON (widget))) + { + gtk_widget_grab_focus (widget); + } + + break; + } + } + + g_list_free (children); +} + + +/** + * gimp_dialog_new: + * @title: The dialog's title which will be set with + * gtk_window_set_title(). + * @role: The dialog's @role which will be set with + * gtk_window_set_role(). + * @parent: The @parent widget of this dialog. + * @flags: The @flags (see the #GtkDialog documentation). + * @help_func: The function which will be called if the user presses "F1". + * @help_id: The help_id which will be passed to @help_func. + * @...: A %NULL-terminated @va_list destribing the + * action_area buttons. + * + * Creates a new @GimpDialog widget. + * + * This function simply packs the action_area arguments passed in "..." + * into a @va_list variable and passes everything to gimp_dialog_new_valist(). + * + * For a description of the format of the @va_list describing the + * action_area buttons see gtk_dialog_new_with_buttons(). + * + * Returns: A #GimpDialog. + **/ +GtkWidget * +gimp_dialog_new (const gchar *title, + const gchar *role, + GtkWidget *parent, + GtkDialogFlags flags, + GimpHelpFunc help_func, + const gchar *help_id, + ...) +{ + GtkWidget *dialog; + va_list args; + + g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL); + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (role != NULL, NULL); + + va_start (args, help_id); + + dialog = gimp_dialog_new_valist (title, role, + parent, flags, + help_func, help_id, + args); + + va_end (args); + + return dialog; +} + +/** + * gimp_dialog_new_valist: + * @title: The dialog's title which will be set with + * gtk_window_set_title(). + * @role: The dialog's @role which will be set with + * gtk_window_set_role(). + * @parent: The @parent widget of this dialog or %NULL. + * @flags: The @flags (see the #GtkDialog documentation). + * @help_func: The function which will be called if the user presses "F1". + * @help_id: The help_id which will be passed to @help_func. + * @args: A @va_list destribing the action_area buttons. + * + * Creates a new @GimpDialog widget. If a GtkWindow is specified as + * @parent then the dialog will be made transient for this window. + * + * For a description of the format of the @va_list describing the + * action_area buttons see gtk_dialog_new_with_buttons(). + * + * Returns: A #GimpDialog. + **/ +GtkWidget * +gimp_dialog_new_valist (const gchar *title, + const gchar *role, + GtkWidget *parent, + GtkDialogFlags flags, + GimpHelpFunc help_func, + const gchar *help_id, + va_list args) +{ + GtkWidget *dialog; + + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (role != NULL, NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL); + + dialog = g_object_new (GIMP_TYPE_DIALOG, + "title", title, + "role", role, + "modal", (flags & GTK_DIALOG_MODAL), + "help-func", help_func, + "help-id", help_id, + "parent", parent, + NULL); + + if (parent) + { + if (flags & GTK_DIALOG_DESTROY_WITH_PARENT) + g_signal_connect_object (parent, "destroy", + G_CALLBACK (gimp_dialog_close), + dialog, G_CONNECT_SWAPPED); + } + + gimp_dialog_add_buttons_valist (GIMP_DIALOG (dialog), args); + + return dialog; +} + +/** + * gimp_dialog_add_button: + * @dialog: The @dialog to add a button to. + * @button_text: text of button, or stock ID. + * @response_id: response ID for the button. + * + * This function is essentially the same as gtk_dialog_add_button() + * except it ensures there is only one help button and automatically + * sets the RESPONSE_OK widget as the default response. + * + * Return value: the button widget that was added. + **/ +GtkWidget * +gimp_dialog_add_button (GimpDialog *dialog, + const gchar *button_text, + gint response_id) +{ + GtkWidget *button; + + /* hide the automatically added help button if another one is added */ + if (response_id == GTK_RESPONSE_HELP) + { + GimpDialogPrivate *private = GET_PRIVATE (dialog); + + if (private->help_button) + gtk_widget_hide (private->help_button); + } + + button = gtk_dialog_add_button (GTK_DIALOG (dialog), button_text, + response_id); + + if (response_id == GTK_RESPONSE_OK) + { + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + } + + return button; +} + +/** + * gimp_dialog_add_buttons: + * @dialog: The @dialog to add buttons to. + * @...: button_text-response_id pairs. + * + * This function is essentially the same as gtk_dialog_add_buttons() + * except it calls gimp_dialog_add_button() instead of gtk_dialog_add_button() + **/ +void +gimp_dialog_add_buttons (GimpDialog *dialog, + ...) +{ + va_list args; + + va_start (args, dialog); + + gimp_dialog_add_buttons_valist (dialog, args); + + va_end (args); +} + +/** + * gimp_dialog_add_buttons_valist: + * @dialog: The @dialog to add buttons to. + * @args: The buttons as va_list. + * + * This function is essentially the same as gimp_dialog_add_buttons() + * except it takes a va_list instead of '...' + **/ +void +gimp_dialog_add_buttons_valist (GimpDialog *dialog, + va_list args) +{ + const gchar *button_text; + gint response_id; + + g_return_if_fail (GIMP_IS_DIALOG (dialog)); + + while ((button_text = va_arg (args, const gchar *))) + { + response_id = va_arg (args, gint); + + gimp_dialog_add_button (dialog, button_text, response_id); + } +} + + +typedef struct +{ + GtkDialog *dialog; + gint response_id; + GMainLoop *loop; + gboolean destroyed; +} RunInfo; + +static void +run_shutdown_loop (RunInfo *ri) +{ + if (g_main_loop_is_running (ri->loop)) + g_main_loop_quit (ri->loop); +} + +static void +run_unmap_handler (GtkDialog *dialog, + RunInfo *ri) +{ + run_shutdown_loop (ri); +} + +static void +run_response_handler (GtkDialog *dialog, + gint response_id, + RunInfo *ri) +{ + ri->response_id = response_id; + + run_shutdown_loop (ri); +} + +static gint +run_delete_handler (GtkDialog *dialog, + GdkEventAny *event, + RunInfo *ri) +{ + run_shutdown_loop (ri); + + return TRUE; /* Do not destroy */ +} + +static void +run_destroy_handler (GtkDialog *dialog, + RunInfo *ri) +{ + /* shutdown_loop will be called by run_unmap_handler */ + + ri->destroyed = TRUE; +} + +/** + * gimp_dialog_run: + * @dialog: a #GimpDialog + * + * This function does exactly the same as gtk_dialog_run() except it + * does not make the dialog modal while the #GMainLoop is running. + * + * Return value: response ID + **/ +gint +gimp_dialog_run (GimpDialog *dialog) +{ + RunInfo ri = { NULL, GTK_RESPONSE_NONE, NULL }; + gulong response_handler; + gulong unmap_handler; + gulong destroy_handler; + gulong delete_handler; + + g_return_val_if_fail (GIMP_IS_DIALOG (dialog), -1); + + g_object_ref (dialog); + + gtk_window_present (GTK_WINDOW (dialog)); + + response_handler = g_signal_connect (dialog, "response", + G_CALLBACK (run_response_handler), + &ri); + unmap_handler = g_signal_connect (dialog, "unmap", + G_CALLBACK (run_unmap_handler), + &ri); + delete_handler = g_signal_connect (dialog, "delete-event", + G_CALLBACK (run_delete_handler), + &ri); + destroy_handler = g_signal_connect (dialog, "destroy", + G_CALLBACK (run_destroy_handler), + &ri); + + ri.loop = g_main_loop_new (NULL, FALSE); + + GDK_THREADS_LEAVE (); + g_main_loop_run (ri.loop); + GDK_THREADS_ENTER (); + + g_main_loop_unref (ri.loop); + + ri.loop = NULL; + ri.destroyed = FALSE; + + if (!ri.destroyed) + { + g_signal_handler_disconnect (dialog, response_handler); + g_signal_handler_disconnect (dialog, unmap_handler); + g_signal_handler_disconnect (dialog, delete_handler); + g_signal_handler_disconnect (dialog, destroy_handler); + } + + g_object_unref (dialog); + + return ri.response_id; +} + +/** + * gimp_dialogs_show_help_button: + * @show: whether a help button should be added when creating a GimpDialog + * + * This function is for internal use only. + * + * Since: 2.2 + **/ +void +gimp_dialogs_show_help_button (gboolean show) +{ + show_help_button = show ? TRUE : FALSE; +} diff --git a/libgimpwidgets/gimpdialog.h b/libgimpwidgets/gimpdialog.h new file mode 100644 index 0000000..ce321e5 --- /dev/null +++ b/libgimpwidgets/gimpdialog.h @@ -0,0 +1,95 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdialog.h + * Copyright (C) 2000-2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_DIALOG_H__ +#define __GIMP_DIALOG_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_DIALOG (gimp_dialog_get_type ()) +#define GIMP_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DIALOG, GimpDialog)) +#define GIMP_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DIALOG, GimpDialogClass)) +#define GIMP_IS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DIALOG)) +#define GIMP_IS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DIALOG)) +#define GIMP_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DIALOG, GimpDialogClass)) + + +typedef struct _GimpDialogClass GimpDialogClass; + +struct _GimpDialog +{ + GtkDialog parent_instance; +}; + +struct _GimpDialogClass +{ + GtkDialogClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dialog_new (const gchar *title, + const gchar *role, + GtkWidget *parent, + GtkDialogFlags flags, + GimpHelpFunc help_func, + const gchar *help_id, + ...) G_GNUC_NULL_TERMINATED; + +GtkWidget * gimp_dialog_new_valist (const gchar *title, + const gchar *role, + GtkWidget *parent, + GtkDialogFlags flags, + GimpHelpFunc help_func, + const gchar *help_id, + va_list args); + +GtkWidget * gimp_dialog_add_button (GimpDialog *dialog, + const gchar *button_text, + gint response_id); +void gimp_dialog_add_buttons (GimpDialog *dialog, + ...) G_GNUC_NULL_TERMINATED; +void gimp_dialog_add_buttons_valist (GimpDialog *dialog, + va_list args); + +gint gimp_dialog_run (GimpDialog *dialog); + +/* for internal use only! */ +void gimp_dialogs_show_help_button (gboolean show); + + +G_END_DECLS + +#endif /* __GIMP_DIALOG_H__ */ diff --git a/libgimpwidgets/gimpeevl.c b/libgimpwidgets/gimpeevl.c new file mode 100644 index 0000000..55bf323 --- /dev/null +++ b/libgimpwidgets/gimpeevl.c @@ -0,0 +1,666 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpeevl.c + * Copyright (C) 2008 Fredrik Alstromer <roe@excu.se> + * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* Introducing eevl eva, the evaluator. A straightforward recursive + * descent parser, no fuss, no new dependencies. The lexer is hand + * coded, tedious, not extremely fast but works. It evaluates the + * expression as it goes along, and does not create a parse tree or + * anything, and will not optimize anything. It uses doubles for + * precision, with the given use case, that's enough to combat any + * rounding errors (as opposed to optimizing the evaluation). + * + * It relies on external unit resolving through a callback and does + * elementary dimensionality constraint check (e.g. "2 mm + 3 px * 4 + * in" is an error, as L + L^2 is a mismatch). It uses setjmp/longjmp + * for try/catch like pattern on error, it uses g_strtod() for numeric + * conversions and it's non-destructive in terms of the parameters, and + * it's reentrant. + * + * EBNF: + * + * expression ::= term { ('+' | '-') term }* | + * <empty string> ; + * + * term ::= ratio { ( '*' | '/' ) ratio }* ; + * + * ratio ::= signed factor { ':' signed factor }* ; + * + * signed factor ::= ( '+' | '-' )? factor ; + * + * factor ::= quantity ( '^' signed factor )? ; + * + * quantity ::= number unit? | '(' expression ')' ; + * + * number ::= ? what g_strtod() consumes ? ; + * + * unit ::= simple unit ( '^' signed factor )? ; + * + * simple unit ::= ? what not g_strtod() consumes and not whitespace ? ; + * + * The code should match the EBNF rather closely (except for the + * non-terminal unit factor, which is inlined into factor) for + * maintainability reasons. + * + * It will allow 1++1 and 1+-1 (resulting in 2 and 0, respectively), + * but I figured one might want that, and I don't think it's going to + * throw anyone off. + */ + +#include "config.h" + +#include <setjmp.h> +#include <string.h> + +#include <glib-object.h> + +#include "libgimpmath/gimpmath.h" + +#include "gimpeevl.h" +#include "gimpwidgets-error.h" + +#include "libgimp/libgimp-intl.h" + + +typedef enum +{ + GIMP_EEVL_TOKEN_NUM = 30000, + GIMP_EEVL_TOKEN_IDENTIFIER = 30001, + + GIMP_EEVL_TOKEN_ANY = 40000, + + GIMP_EEVL_TOKEN_END = 50000 +} GimpEevlTokenType; + + +typedef struct +{ + GimpEevlTokenType type; + + union + { + gdouble fl; + + struct + { + const gchar *c; + gint size; + }; + + } value; + +} GimpEevlToken; + +typedef struct +{ + const gchar *string; + GimpEevlOptions options; + + GimpEevlToken current_token; + const gchar *start_of_current_token; + + jmp_buf catcher; + const gchar *error_message; + +} GimpEevl; + + +static void gimp_eevl_init (GimpEevl *eva, + const gchar *string, + const GimpEevlOptions *options); +static GimpEevlQuantity gimp_eevl_complete (GimpEevl *eva); +static GimpEevlQuantity gimp_eevl_expression (GimpEevl *eva); +static GimpEevlQuantity gimp_eevl_term (GimpEevl *eva); +static GimpEevlQuantity gimp_eevl_ratio (GimpEevl *eva); +static GimpEevlQuantity gimp_eevl_signed_factor (GimpEevl *eva); +static GimpEevlQuantity gimp_eevl_factor (GimpEevl *eva); +static GimpEevlQuantity gimp_eevl_quantity (GimpEevl *eva); +static gboolean gimp_eevl_accept (GimpEevl *eva, + GimpEevlTokenType token_type, + GimpEevlToken *consumed_token); +static void gimp_eevl_lex (GimpEevl *eva); +static void gimp_eevl_lex_accept_count (GimpEevl *eva, + gint count, + GimpEevlTokenType token_type); +static void gimp_eevl_lex_accept_to (GimpEevl *eva, + gchar *to, + GimpEevlTokenType token_type); +static void gimp_eevl_move_past_whitespace (GimpEevl *eva); +static gboolean gimp_eevl_unit_identifier_start (gunichar c); +static gboolean gimp_eevl_unit_identifier_continue (gunichar c); +static gint gimp_eevl_unit_identifier_size (const gchar *s, + gint start); +static void gimp_eevl_expect (GimpEevl *eva, + GimpEevlTokenType token_type, + GimpEevlToken *value); +static void gimp_eevl_error (GimpEevl *eva, + gchar *msg); + + +/** + * gimp_eevl_evaluate: + * @string: The NULL-terminated string to be evaluated. + * @options: Evaluations options. + * @result: Result of evaluation. + * @error_pos: Will point to the position within the string, + * before which the parse / evaluation error + * occurred. Will be set to null of no error occurred. + * @error_message: Will point to a static string with a semi-descriptive + * error message if parsing / evaluation failed. + * + * Evaluates the given arithmetic expression, along with an optional dimension + * analysis, and basic unit conversions. + * + * All units conversions factors are relative to some implicit + * base-unit (which in GIMP is inches). This is also the unit of the + * returned value. + * + * Returns: A #GimpEevlQuantity with a value given in the base unit along with + * the order of the dimension (i.e. if the base unit is inches, a dimension + * order of two means in^2). + **/ +gboolean +gimp_eevl_evaluate (const gchar *string, + const GimpEevlOptions *options, + GimpEevlQuantity *result, + const gchar **error_pos, + GError **error) +{ + GimpEevl eva; + + g_return_val_if_fail (g_utf8_validate (string, -1, NULL), FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (options->unit_resolver_proc != NULL, FALSE); + g_return_val_if_fail (result != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + gimp_eevl_init (&eva, + string, + options); + + if (!setjmp (eva.catcher)) /* try... */ + { + *result = gimp_eevl_complete (&eva); + + return TRUE; + } + else /* catch.. */ + { + if (error_pos) + *error_pos = eva.start_of_current_token; + + g_set_error_literal (error, + GIMP_WIDGETS_ERROR, + GIMP_WIDGETS_PARSE_ERROR, + eva.error_message); + + return FALSE; + } +} + +static void +gimp_eevl_init (GimpEevl *eva, + const gchar *string, + const GimpEevlOptions *options) +{ + eva->string = string; + eva->options = *options; + + eva->current_token.type = GIMP_EEVL_TOKEN_END; + + eva->error_message = NULL; + + /* Preload symbol... */ + gimp_eevl_lex (eva); +} + +static GimpEevlQuantity +gimp_eevl_complete (GimpEevl *eva) +{ + GimpEevlQuantity result = {0, 0}; + GimpEevlQuantity default_unit_factor; + gdouble default_unit_offset; + + /* Empty expression evaluates to 0 */ + if (gimp_eevl_accept (eva, GIMP_EEVL_TOKEN_END, NULL)) + return result; + + result = gimp_eevl_expression (eva); + + /* There should be nothing left to parse by now */ + gimp_eevl_expect (eva, GIMP_EEVL_TOKEN_END, 0); + + eva->options.unit_resolver_proc (NULL, + &default_unit_factor, + &default_unit_offset, + eva->options.data); + + /* Entire expression is dimensionless, apply default unit if + * applicable + */ + if (result.dimension == 0 && default_unit_factor.dimension != 0) + { + result.value /= default_unit_factor.value; + result.value += default_unit_offset; + result.dimension = default_unit_factor.dimension; + } + return result; +} + +static GimpEevlQuantity +gimp_eevl_expression (GimpEevl *eva) +{ + gboolean subtract; + GimpEevlQuantity evaluated_terms; + + evaluated_terms = gimp_eevl_term (eva); + + /* continue evaluating terms, chained with + or -. */ + for (subtract = FALSE; + gimp_eevl_accept (eva, '+', NULL) || + (subtract = gimp_eevl_accept (eva, '-', NULL)); + subtract = FALSE) + { + GimpEevlQuantity new_term = gimp_eevl_term (eva); + + /* If dimensions mismatch, attempt default unit assignment */ + if (new_term.dimension != evaluated_terms.dimension) + { + GimpEevlQuantity default_unit_factor; + gdouble default_unit_offset; + + eva->options.unit_resolver_proc (NULL, + &default_unit_factor, + &default_unit_offset, + eva->options.data); + + if (new_term.dimension == 0 && + evaluated_terms.dimension == default_unit_factor.dimension) + { + new_term.value /= default_unit_factor.value; + new_term.value += default_unit_offset; + new_term.dimension = default_unit_factor.dimension; + } + else if (evaluated_terms.dimension == 0 && + new_term.dimension == default_unit_factor.dimension) + { + evaluated_terms.value /= default_unit_factor.value; + evaluated_terms.value += default_unit_offset; + evaluated_terms.dimension = default_unit_factor.dimension; + } + else + { + gimp_eevl_error (eva, "Dimension mismatch during addition"); + } + } + + evaluated_terms.value += (subtract ? -new_term.value : new_term.value); + } + + return evaluated_terms; +} + +static GimpEevlQuantity +gimp_eevl_term (GimpEevl *eva) +{ + gboolean division; + GimpEevlQuantity evaluated_ratios; + + evaluated_ratios = gimp_eevl_ratio (eva); + + for (division = FALSE; + gimp_eevl_accept (eva, '*', NULL) || + (division = gimp_eevl_accept (eva, '/', NULL)); + division = FALSE) + { + GimpEevlQuantity new_ratio = gimp_eevl_ratio (eva); + + if (division) + { + evaluated_ratios.value /= new_ratio.value; + evaluated_ratios.dimension -= new_ratio.dimension; + } + else + { + evaluated_ratios.value *= new_ratio.value; + evaluated_ratios.dimension += new_ratio.dimension; + } + } + + return evaluated_ratios; +} + +static GimpEevlQuantity +gimp_eevl_ratio (GimpEevl *eva) +{ + GimpEevlQuantity evaluated_signed_factors; + + if (! eva->options.ratio_expressions) + return gimp_eevl_signed_factor (eva); + + evaluated_signed_factors = gimp_eevl_signed_factor (eva); + + while (gimp_eevl_accept (eva, ':', NULL)) + { + GimpEevlQuantity new_signed_factor = gimp_eevl_signed_factor (eva); + + if (eva->options.ratio_invert) + { + GimpEevlQuantity temp; + + temp = evaluated_signed_factors; + evaluated_signed_factors = new_signed_factor; + new_signed_factor = temp; + } + + evaluated_signed_factors.value *= eva->options.ratio_quantity.value / + new_signed_factor.value; + evaluated_signed_factors.dimension += eva->options.ratio_quantity.dimension - + new_signed_factor.dimension; + } + + return evaluated_signed_factors; +} + +static GimpEevlQuantity +gimp_eevl_signed_factor (GimpEevl *eva) +{ + GimpEevlQuantity result; + gboolean negate = FALSE; + + if (! gimp_eevl_accept (eva, '+', NULL)) + negate = gimp_eevl_accept (eva, '-', NULL); + + result = gimp_eevl_factor (eva); + + if (negate) result.value = -result.value; + + return result; +} + +static GimpEevlQuantity +gimp_eevl_factor (GimpEevl *eva) +{ + GimpEevlQuantity evaluated_factor; + + evaluated_factor = gimp_eevl_quantity (eva); + + if (gimp_eevl_accept (eva, '^', NULL)) + { + GimpEevlQuantity evaluated_exponent; + + evaluated_exponent = gimp_eevl_signed_factor (eva); + + if (evaluated_exponent.dimension != 0) + gimp_eevl_error (eva, "Exponent is not a dimensionless quantity"); + + evaluated_factor.value = pow (evaluated_factor.value, + evaluated_exponent.value); + evaluated_factor.dimension *= evaluated_exponent.value; + } + + return evaluated_factor; +} + +static GimpEevlQuantity +gimp_eevl_quantity (GimpEevl *eva) +{ + GimpEevlQuantity evaluated_quantity = { 0, 0 }; + GimpEevlToken consumed_token; + + if (gimp_eevl_accept (eva, + GIMP_EEVL_TOKEN_NUM, + &consumed_token)) + { + evaluated_quantity.value = consumed_token.value.fl; + } + else if (gimp_eevl_accept (eva, '(', NULL)) + { + evaluated_quantity = gimp_eevl_expression (eva); + gimp_eevl_expect (eva, ')', 0); + } + else + { + gimp_eevl_error (eva, "Expected number or '('"); + } + + if (eva->current_token.type == GIMP_EEVL_TOKEN_IDENTIFIER) + { + gchar *identifier; + GimpEevlQuantity factor; + gdouble offset; + + gimp_eevl_accept (eva, + GIMP_EEVL_TOKEN_ANY, + &consumed_token); + + identifier = g_newa (gchar, consumed_token.value.size + 1); + + strncpy (identifier, consumed_token.value.c, consumed_token.value.size); + identifier[consumed_token.value.size] = '\0'; + + if (eva->options.unit_resolver_proc (identifier, + &factor, + &offset, + eva->options.data)) + { + if (gimp_eevl_accept (eva, '^', NULL)) + { + GimpEevlQuantity evaluated_exponent; + + evaluated_exponent = gimp_eevl_signed_factor (eva); + + if (evaluated_exponent.dimension != 0) + { + gimp_eevl_error (eva, + "Exponent is not a dimensionless quantity"); + } + + if (offset != 0.0) + { + gimp_eevl_error (eva, + "Invalid unit exponent"); + } + + factor.value = pow (factor.value, evaluated_exponent.value); + factor.dimension *= evaluated_exponent.value; + } + + evaluated_quantity.value /= factor.value; + evaluated_quantity.value += offset; + evaluated_quantity.dimension += factor.dimension; + } + else + { + gimp_eevl_error (eva, "Unit was not resolved"); + } + } + + return evaluated_quantity; +} + +static gboolean +gimp_eevl_accept (GimpEevl *eva, + GimpEevlTokenType token_type, + GimpEevlToken *consumed_token) +{ + gboolean existed = FALSE; + + if (token_type == eva->current_token.type || + token_type == GIMP_EEVL_TOKEN_ANY) + { + existed = TRUE; + + if (consumed_token) + *consumed_token = eva->current_token; + + /* Parse next token */ + gimp_eevl_lex (eva); + } + + return existed; +} + +static void +gimp_eevl_lex (GimpEevl *eva) +{ + const gchar *s; + + gimp_eevl_move_past_whitespace (eva); + s = eva->string; + eva->start_of_current_token = s; + + if (! s || s[0] == '\0') + { + /* We're all done */ + eva->current_token.type = GIMP_EEVL_TOKEN_END; + } + else if (s[0] == '+' || s[0] == '-') + { + /* Snatch these before the g_strtod() does, otherwise they might + * be used in a numeric conversion. + */ + gimp_eevl_lex_accept_count (eva, 1, s[0]); + } + else + { + /* Attempt to parse a numeric value */ + gchar *endptr = NULL; + gdouble value = g_strtod (s, &endptr); + + if (endptr && endptr != s) + { + /* A numeric could be parsed, use it */ + eva->current_token.value.fl = value; + + gimp_eevl_lex_accept_to (eva, endptr, GIMP_EEVL_TOKEN_NUM); + } + else if (gimp_eevl_unit_identifier_start (s[0])) + { + /* Unit identifier */ + eva->current_token.value.c = s; + eva->current_token.value.size = gimp_eevl_unit_identifier_size (s, 0); + + gimp_eevl_lex_accept_count (eva, + eva->current_token.value.size, + GIMP_EEVL_TOKEN_IDENTIFIER); + } + else + { + /* Everything else is a single character token */ + gimp_eevl_lex_accept_count (eva, 1, s[0]); + } + } +} + +static void +gimp_eevl_lex_accept_count (GimpEevl *eva, + gint count, + GimpEevlTokenType token_type) +{ + eva->current_token.type = token_type; + eva->string += count; +} + +static void +gimp_eevl_lex_accept_to (GimpEevl *eva, + gchar *to, + GimpEevlTokenType token_type) +{ + eva->current_token.type = token_type; + eva->string = to; +} + +static void +gimp_eevl_move_past_whitespace (GimpEevl *eva) +{ + if (! eva->string) + return; + + while (g_ascii_isspace (*eva->string)) + eva->string++; +} + +static gboolean +gimp_eevl_unit_identifier_start (gunichar c) +{ + return (g_unichar_isalpha (c) || + c == (gunichar) '%' || + c == (gunichar) '\''); +} + +static gboolean +gimp_eevl_unit_identifier_continue (gunichar c) +{ + return (gimp_eevl_unit_identifier_start (c) || + g_unichar_isdigit (c)); +} + +/** + * gimp_eevl_unit_identifier_size: + * @s: + * @start: + * + * Returns: Size of identifier in bytes (not including NULL + * terminator). + **/ +static gint +gimp_eevl_unit_identifier_size (const gchar *string, + gint start_offset) +{ + const gchar *start = g_utf8_offset_to_pointer (string, start_offset); + const gchar *s = start; + gunichar c = g_utf8_get_char (s); + gint length = 0; + + if (gimp_eevl_unit_identifier_start (c)) + { + s = g_utf8_next_char (s); + c = g_utf8_get_char (s); + length++; + + while (gimp_eevl_unit_identifier_continue (c)) + { + s = g_utf8_next_char (s); + c = g_utf8_get_char (s); + length++; + } + } + + return g_utf8_offset_to_pointer (start, length) - start; +} + +static void +gimp_eevl_expect (GimpEevl *eva, + GimpEevlTokenType token_type, + GimpEevlToken *value) +{ + if (! gimp_eevl_accept (eva, token_type, value)) + gimp_eevl_error (eva, "Unexpected token"); +} + +static void +gimp_eevl_error (GimpEevl *eva, + gchar *msg) +{ + eva->error_message = msg; + longjmp (eva->catcher, 1); +} diff --git a/libgimpwidgets/gimpeevl.h b/libgimpwidgets/gimpeevl.h new file mode 100644 index 0000000..bbcb69b --- /dev/null +++ b/libgimpwidgets/gimpeevl.h @@ -0,0 +1,99 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpeevl.h + * Copyright (C) 2008-2009 Fredrik Alstromer <roe@excu.se> + * Copyright (C) 2008-2009 Martin Nordholts <martinn@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_EEVL_H__ +#define __GIMP_EEVL_H__ + +G_BEGIN_DECLS + + +/** + * GimpEevlQuantity: + * @value: In reference units. + * @dimension: in has a dimension of 1, in^2 has a dimension of 2 etc + */ +typedef struct +{ + gdouble value; + gint dimension; +} GimpEevlQuantity; + + +/** + * GimpEevlUnitResolverProc: + * @identifier: Identifier of unit to resolve or %NULL if default unit + * should be resolved. + * @factor: Units per reference unit. For example, in GIMP the + * reference unit is inches so resolving "mm" should + * return 25.4 since there are 25.4 millimeters per inch. + * @offset: Offset to apply after scaling the value according to @factor. + * @data: Data given to gimp_eevl_evaluate(). + * + * Returns: If the unit was successfully resolved or not. + * + */ +typedef gboolean (* GimpEevlUnitResolverProc) (const gchar *identifier, + GimpEevlQuantity *factor, + gdouble *offset, + gpointer data); + + +/** + * GimpEevlOptions: + * @unit_resolver_proc: Unit resolver callback. + * @data: Data passed to unit resolver. + * @ratio_expressions: Allow ratio expressions + * @ratio_invert: Invert ratios + * @ratio_quantity: Quantity to multiply ratios by + */ +typedef struct +{ + GimpEevlUnitResolverProc unit_resolver_proc; + gpointer data; + + gboolean ratio_expressions; + gboolean ratio_invert; + GimpEevlQuantity ratio_quantity; +} GimpEevlOptions; + +#define GIMP_EEVL_OPTIONS_INIT \ + ((const GimpEevlOptions) \ + { \ + .unit_resolver_proc = NULL, \ + .data = NULL, \ + \ + .ratio_expressions = FALSE, \ + .ratio_invert = FALSE, \ + .ratio_quantity = {0.0, 0} \ + }) + + +gboolean gimp_eevl_evaluate (const gchar *string, + const GimpEevlOptions *options, + GimpEevlQuantity *result, + const gchar **error_pos, + GError **error); + + +G_END_DECLS + +#endif /* __GIMP_EEVL_H__ */ diff --git a/libgimpwidgets/gimpenumcombobox.c b/libgimpwidgets/gimpenumcombobox.c new file mode 100644 index 0000000..0715109 --- /dev/null +++ b/libgimpwidgets/gimpenumcombobox.c @@ -0,0 +1,229 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumcombobox.c + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpenumcombobox.h" +#include "gimpenumstore.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpenumcombobox + * @title: GimpEnumComboBox + * @short_description: A #GimpIntComboBox subclass for selecting an enum value. + * + * A #GtkComboBox subclass for selecting an enum value. + **/ + + +enum +{ + PROP_0, + PROP_MODEL +}; + + +static void gimp_enum_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_enum_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpEnumComboBox, gimp_enum_combo_box, + GIMP_TYPE_INT_COMBO_BOX) + +#define parent_class gimp_enum_combo_box_parent_class + + +static void +gimp_enum_combo_box_class_init (GimpEnumComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gimp_enum_combo_box_set_property; + object_class->get_property = gimp_enum_combo_box_get_property; + + /* override the "model" property of GtkComboBox */ + g_object_class_install_property (object_class, + PROP_MODEL, + g_param_spec_object ("model", + "Model", + "The enum store used by this combo box", + GIMP_TYPE_ENUM_STORE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_enum_combo_box_init (GimpEnumComboBox *combo_box) +{ +} + +static void +gimp_enum_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (object); + + switch (prop_id) + { + case PROP_MODEL: + gtk_combo_box_set_model (combo_box, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_enum_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (object); + + switch (prop_id) + { + case PROP_MODEL: + g_value_set_object (value, gtk_combo_box_get_model (combo_box)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/** + * gimp_enum_combo_box_new: + * @enum_type: the #GType of an enum. + * + * Creates a #GtkComboBox readily filled with all enum values from a + * given @enum_type. The enum needs to be registered to the type + * system. It should also have %GimpEnumDesc descriptions registered + * that contain translatable value names. This is the case for the + * enums used in the GIMP PDB functions. + * + * This is just a convenience function. If you need more control over + * the enum values that appear in the combo_box, you can create your + * own #GimpEnumStore and use gimp_enum_combo_box_new_with_model(). + * + * Return value: a new #GimpEnumComboBox. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_combo_box_new (GType enum_type) +{ + GtkListStore *store; + GtkWidget *combo_box; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + store = gimp_enum_store_new (enum_type); + + combo_box = g_object_new (GIMP_TYPE_ENUM_COMBO_BOX, + "model", store, + NULL); + + g_object_unref (store); + + return combo_box; +} + +/** + * gimp_enum_combo_box_new_with_model + * @enum_store: a #GimpEnumStore to use as the model + * + * Creates a #GtkComboBox for the given @enum_store. + * + * Return value: a new #GimpEnumComboBox. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_combo_box_new_with_model (GimpEnumStore *enum_store) +{ + g_return_val_if_fail (GIMP_IS_ENUM_STORE (enum_store), NULL); + + return g_object_new (GIMP_TYPE_ENUM_COMBO_BOX, + "model", enum_store, + NULL); +} + +/** + * gimp_enum_combo_box_set_stock_prefix: + * @combo_box: a #GimpEnumComboBox + * @stock_prefix: a prefix to create icon stock ID from enum values + * + * Attempts to create stock icons for all items in the @combo_box. See + * gimp_enum_store_set_stock_prefix() to find out what to use as + * @stock_prefix. + * + * Since: 2.4 + * + * Deprecated: GIMP 2.10 + **/ +void +gimp_enum_combo_box_set_stock_prefix (GimpEnumComboBox *combo_box, + const gchar *stock_prefix) +{ + gimp_enum_combo_box_set_icon_prefix (combo_box, stock_prefix); +} + +/** + * gimp_enum_combo_box_set_icon_prefix: + * @combo_box: a #GimpEnumComboBox + * @icon_prefix: a prefix to create icon names from enum values + * + * Attempts to create icons for all items in the @combo_box. See + * gimp_enum_store_set_icon_prefix() to find out what to use as + * @icon_prefix. + * + * Since: 2.10 + **/ +void +gimp_enum_combo_box_set_icon_prefix (GimpEnumComboBox *combo_box, + const gchar *icon_prefix) +{ + GtkTreeModel *model; + + g_return_if_fail (GIMP_IS_ENUM_COMBO_BOX (combo_box)); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + + gimp_enum_store_set_icon_prefix (GIMP_ENUM_STORE (model), icon_prefix); +} diff --git a/libgimpwidgets/gimpenumcombobox.h b/libgimpwidgets/gimpenumcombobox.h new file mode 100644 index 0000000..6d8a7c1 --- /dev/null +++ b/libgimpwidgets/gimpenumcombobox.h @@ -0,0 +1,74 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumcombobox.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_ENUM_COMBO_BOX_H__ +#define __GIMP_ENUM_COMBO_BOX_H__ + +#include <libgimpwidgets/gimpintcombobox.h> + +G_BEGIN_DECLS + +#define GIMP_TYPE_ENUM_COMBO_BOX (gimp_enum_combo_box_get_type ()) +#define GIMP_ENUM_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ENUM_COMBO_BOX, GimpEnumComboBox)) +#define GIMP_ENUM_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ENUM_COMBO_BOX, GimpEnumComboBoxClass)) +#define GIMP_IS_ENUM_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ENUM_COMBO_BOX)) +#define GIMP_IS_ENUM_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ENUM_COMBO_BOX)) +#define GIMP_ENUM_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ENUM_COMBO_BOX, GimpEnumComboBoxClass)) + + +typedef struct _GimpEnumComboBoxClass GimpEnumComboBoxClass; + +struct _GimpEnumComboBox +{ + GimpIntComboBox parent_instance; +}; + +struct _GimpEnumComboBoxClass +{ + GimpIntComboBoxClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_enum_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_enum_combo_box_new (GType enum_type); +GtkWidget * gimp_enum_combo_box_new_with_model (GimpEnumStore *enum_store); + +GIMP_DEPRECATED_FOR(gimp_enum_combo_box_set_icon_prefix) +void gimp_enum_combo_box_set_stock_prefix (GimpEnumComboBox *combo_box, + const gchar *stock_prefix); + +void gimp_enum_combo_box_set_icon_prefix (GimpEnumComboBox *combo_box, + const gchar *icon_prefix); + +G_END_DECLS + +#endif /* __GIMP_ENUM_COMBO_BOX_H__ */ diff --git a/libgimpwidgets/gimpenumlabel.c b/libgimpwidgets/gimpenumlabel.c new file mode 100644 index 0000000..ad114ad --- /dev/null +++ b/libgimpwidgets/gimpenumlabel.c @@ -0,0 +1,218 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumlabel.c + * Copyright (C) 2005 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpenumlabel.h" + + +/** + * SECTION: gimpenumlabel + * @title: GimpEnumLabel + * @short_description: A #GtkLabel subclass that displays an enum value. + * + * A #GtkLabel subclass that displays an enum value. + **/ + + +enum +{ + PROP_0, + PROP_ENUM_TYPE, + PROP_ENUM_VALUE +}; + + +static void gimp_enum_label_finalize (GObject *object); +static void gimp_enum_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_enum_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpEnumLabel, gimp_enum_label, GTK_TYPE_LABEL) + +#define parent_class gimp_enum_label_parent_class + + +static void +gimp_enum_label_class_init (GimpEnumLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_enum_label_finalize; + object_class->get_property = gimp_enum_label_get_property; + object_class->set_property = gimp_enum_label_set_property; + + /** + * GimpEnumLabel:enum-type: + * + * The #GType of the enum. + * + * Since: 2.8 + **/ + g_object_class_install_property (object_class, PROP_ENUM_TYPE, + g_param_spec_gtype ("enum-type", + "Enum Type", + "The type of the displayed enum", + G_TYPE_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * GimpEnumLabel:enum-value: + * + * The value to display. + * + * Since: 2.8 + **/ + g_object_class_install_property (object_class, PROP_ENUM_VALUE, + g_param_spec_int ("enum-value", + "Enum Value", + "The enum value to display", + G_MININT, G_MAXINT, 0, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_enum_label_init (GimpEnumLabel *enum_label) +{ +} + +static void +gimp_enum_label_finalize (GObject *object) +{ + GimpEnumLabel *enum_label = GIMP_ENUM_LABEL (object); + + g_clear_pointer (&enum_label->enum_class, g_type_class_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_enum_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpEnumLabel *label = GIMP_ENUM_LABEL (object); + + switch (property_id) + { + case PROP_ENUM_TYPE: + if (label->enum_class) + g_value_set_gtype (value, G_TYPE_FROM_CLASS (label->enum_class)); + else + g_value_set_gtype (value, G_TYPE_NONE); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_enum_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpEnumLabel *label = GIMP_ENUM_LABEL (object); + + switch (property_id) + { + case PROP_ENUM_TYPE: + label->enum_class = g_type_class_ref (g_value_get_gtype (value)); + break; + + case PROP_ENUM_VALUE: + gimp_enum_label_set_value (label, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_enum_label_new: + * @enum_type: the #GType of an enum + * @value: an enum value + * + * Return value: a new #GimpEnumLabel. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_label_new (GType enum_type, + gint value) +{ + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + return g_object_new (GIMP_TYPE_ENUM_LABEL, + "enum-type", enum_type, + "enum-value", value, + NULL); +} + +/** + * gimp_enum_label_set_value + * @label: a #GimpEnumLabel + * @value: an enum value + * + * Since: 2.4 + **/ +void +gimp_enum_label_set_value (GimpEnumLabel *label, + gint value) +{ + const gchar *nick; + const gchar *desc; + + g_return_if_fail (GIMP_IS_ENUM_LABEL (label)); + + if (! gimp_enum_get_value (G_TYPE_FROM_CLASS (label->enum_class), value, + NULL, &nick, &desc, NULL)) + { + g_warning ("%s: %d is not valid for enum of type '%s'", + G_STRLOC, value, + g_type_name (G_TYPE_FROM_CLASS (label->enum_class))); + return; + } + + if (! desc) + desc = nick; + + gtk_label_set_text (GTK_LABEL (label), desc); +} diff --git a/libgimpwidgets/gimpenumlabel.h b/libgimpwidgets/gimpenumlabel.h new file mode 100644 index 0000000..8737eb3 --- /dev/null +++ b/libgimpwidgets/gimpenumlabel.h @@ -0,0 +1,66 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumlabel.h + * Copyright (C) 2005 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_ENUM__LABEL_H__ +#define __GIMP_ENUM__LABEL_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_ENUM_LABEL (gimp_enum_label_get_type ()) +#define GIMP_ENUM_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ENUM_LABEL, GimpEnumLabel)) +#define GIMP_ENUM_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ENUM_LABEL, GimpEnumLabelClass)) +#define GIMP_IS_ENUM_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ENUM_LABEL)) +#define GIMP_IS_ENUM_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ENUM_LABEL)) +#define GIMP_ENUM_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ENUM_LABEL, GimpEnumLabelClass)) + + +typedef struct _GimpEnumLabelClass GimpEnumLabelClass; + +struct _GimpEnumLabel +{ + GtkLabel parent_instance; + + /*< private >*/ + GEnumClass *enum_class; +}; + +struct _GimpEnumLabelClass +{ + GtkLabelClass parent_class; +}; + + +GType gimp_enum_label_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_enum_label_new (GType enum_type, + gint value); +void gimp_enum_label_set_value (GimpEnumLabel *label, + gint value); + + +G_END_DECLS + +#endif /* __GIMP_ENUM_LABEL_H__ */ diff --git a/libgimpwidgets/gimpenumstore.c b/libgimpwidgets/gimpenumstore.c new file mode 100644 index 0000000..c062119 --- /dev/null +++ b/libgimpwidgets/gimpenumstore.c @@ -0,0 +1,397 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumstore.c + * Copyright (C) 2004-2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpenumstore.h" + + +/** + * SECTION: gimpenumstore + * @title: GimpEnumStore + * @short_description: A #GimpIntStore subclass that keeps enum values. + * + * A #GimpIntStore subclass that keeps enum values. + **/ + + +enum +{ + PROP_0, + PROP_ENUM_TYPE +}; + + +static void gimp_enum_store_finalize (GObject *object); +static void gimp_enum_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_enum_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_enum_store_add_value (GtkListStore *store, + GEnumValue *value); + + +G_DEFINE_TYPE (GimpEnumStore, gimp_enum_store, GIMP_TYPE_INT_STORE) + +#define parent_class gimp_enum_store_parent_class + + +static void +gimp_enum_store_class_init (GimpEnumStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_enum_store_finalize; + object_class->set_property = gimp_enum_store_set_property; + object_class->get_property = gimp_enum_store_get_property; + + /** + * GimpEnumStore:enum-type: + * + * Sets the #GType of the enum to be used in the store. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_ENUM_TYPE, + g_param_spec_gtype ("enum-type", + "Enum Type", + "The type of the enum", + G_TYPE_ENUM, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_enum_store_init (GimpEnumStore *store) +{ +} + +static void +gimp_enum_store_finalize (GObject *object) +{ + GimpEnumStore *store = GIMP_ENUM_STORE (object); + + if (store->enum_class) + g_type_class_unref (store->enum_class); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_enum_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpEnumStore *store = GIMP_ENUM_STORE (object); + + switch (property_id) + { + case PROP_ENUM_TYPE: + g_return_if_fail (store->enum_class == NULL); + store->enum_class = g_type_class_ref (g_value_get_gtype (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_enum_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpEnumStore *store = GIMP_ENUM_STORE (object); + + switch (property_id) + { + case PROP_ENUM_TYPE: + g_value_set_gtype (value, (store->enum_class ? + G_TYPE_FROM_CLASS (store->enum_class) : + G_TYPE_NONE)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_enum_store_add_value (GtkListStore *store, + GEnumValue *value) +{ + GtkTreeIter iter = { 0, }; + const gchar *desc; + const gchar *abbrev; + gchar *stripped; + + desc = gimp_enum_value_get_desc (GIMP_ENUM_STORE (store)->enum_class, value); + abbrev = gimp_enum_value_get_abbrev (GIMP_ENUM_STORE (store)->enum_class, value); + + /* no mnemonics in combo boxes */ + stripped = gimp_strip_uline (desc); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_VALUE, value->value, + GIMP_INT_STORE_LABEL, stripped, + GIMP_INT_STORE_ABBREV, abbrev, + -1); + + g_free (stripped); +} + + +/** + * gimp_enum_store_new: + * @enum_type: the #GType of an enum. + * + * Creates a new #GimpEnumStore, derived from #GtkListStore and fills + * it with enum values. The enum needs to be registered to the type + * system and should have translatable value names. + * + * Return value: a new #GimpEnumStore. + * + * Since: 2.4 + **/ +GtkListStore * +gimp_enum_store_new (GType enum_type) +{ + GtkListStore *store; + GEnumClass *enum_class; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + enum_class = g_type_class_ref (enum_type); + + store = gimp_enum_store_new_with_range (enum_type, + enum_class->minimum, + enum_class->maximum); + + g_type_class_unref (enum_class); + + return store; +} + +/** + * gimp_enum_store_new_with_range: + * @enum_type: the #GType of an enum. + * @minimum: the minimum value to include + * @maximum: the maximum value to include + * + * Creates a new #GimpEnumStore like gimp_enum_store_new() but allows + * to limit the enum values to a certain range. Values smaller than + * @minimum or larger than @maximum are not added to the store. + * + * Return value: a new #GimpEnumStore. + * + * Since: 2.4 + **/ +GtkListStore * +gimp_enum_store_new_with_range (GType enum_type, + gint minimum, + gint maximum) +{ + GtkListStore *store; + GEnumValue *value; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + store = g_object_new (GIMP_TYPE_ENUM_STORE, + "enum-type", enum_type, + NULL); + + for (value = GIMP_ENUM_STORE (store)->enum_class->values; + value->value_name; + value++) + { + if (value->value < minimum || value->value > maximum) + continue; + + gimp_enum_store_add_value (store, value); + } + + return store; +} + +/** + * gimp_enum_store_new_with_values + * @enum_type: the #GType of an enum. + * @n_values: the number of enum values to include + * @...: a list of enum values (exactly @n_values) + * + * Creates a new #GimpEnumStore like gimp_enum_store_new() but allows + * to explicitly list the enum values that should be added to the + * store. + * + * Return value: a new #GimpEnumStore. + * + * Since: 2.4 + **/ +GtkListStore * +gimp_enum_store_new_with_values (GType enum_type, + gint n_values, + ...) +{ + GtkListStore *store; + va_list args; + + va_start (args, n_values); + + store = gimp_enum_store_new_with_values_valist (enum_type, n_values, args); + + va_end (args); + + return store; +} + +/** + * gimp_enum_store_new_with_values_valist: + * @enum_type: the #GType of an enum. + * @n_values: the number of enum values to include + * @args: a va_list of enum values (exactly @n_values) + * + * See gimp_enum_store_new_with_values(). + * + * Return value: a new #GimpEnumStore. + * + * Since: 2.4 + **/ +GtkListStore * +gimp_enum_store_new_with_values_valist (GType enum_type, + gint n_values, + va_list args) +{ + GtkListStore *store; + GEnumValue *value; + gint i; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + g_return_val_if_fail (n_values > 1, NULL); + + store = g_object_new (GIMP_TYPE_ENUM_STORE, + "enum-type", enum_type, + NULL); + + for (i = 0; i < n_values; i++) + { + value = g_enum_get_value (GIMP_ENUM_STORE (store)->enum_class, + va_arg (args, gint)); + + if (value) + gimp_enum_store_add_value (store, value); + } + + return store; +} + +/** + * gimp_enum_store_set_stock_prefix: + * @store: a #GimpEnumStore + * @stock_prefix: a prefix to create icon stock ID from enum values + * + * Creates a stock ID for each enum value in the @store by appending + * the value's nick to the given @stock_prefix, separated by a hyphen. + * + * See also: gimp_enum_combo_box_set_stock_prefix(). + * + * Since: 2.4 + * + * Deprecated: GIMP 2.10 + **/ +void +gimp_enum_store_set_stock_prefix (GimpEnumStore *store, + const gchar *stock_prefix) +{ + gimp_enum_store_set_icon_prefix (store, stock_prefix); +} + +/** + * gimp_enum_store_set_icon_prefix: + * @store: a #GimpEnumStore + * @icon_prefix: a prefix to create icon names from enum values + * + * Creates an icon name for each enum value in the @store by appending + * the value's nick to the given @icon_prefix, separated by a hyphen. + * + * See also: gimp_enum_combo_box_set_icon_prefix(). + * + * Since: 2.10 + **/ +void +gimp_enum_store_set_icon_prefix (GimpEnumStore *store, + const gchar *icon_prefix) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_if_fail (GIMP_IS_ENUM_STORE (store)); + + model = GTK_TREE_MODEL (store); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *icon_name = NULL; + + if (icon_prefix) + { + GEnumValue *enum_value; + gint value; + + gtk_tree_model_get (model, &iter, + GIMP_INT_STORE_VALUE, &value, + -1); + + enum_value = g_enum_get_value (store->enum_class, value); + + if (enum_value) + { + icon_name = g_strconcat (icon_prefix, "-", + enum_value->value_nick, + NULL); + } + } + + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + GIMP_INT_STORE_ICON_NAME, icon_name, + -1); + + if (icon_name) + g_free (icon_name); + } +} diff --git a/libgimpwidgets/gimpenumstore.h b/libgimpwidgets/gimpenumstore.h new file mode 100644 index 0000000..738eab0 --- /dev/null +++ b/libgimpwidgets/gimpenumstore.h @@ -0,0 +1,85 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumstore.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_ENUM_STORE_H__ +#define __GIMP_ENUM_STORE_H__ + +#include <libgimpwidgets/gimpintstore.h> + + +G_BEGIN_DECLS + +#define GIMP_TYPE_ENUM_STORE (gimp_enum_store_get_type ()) +#define GIMP_ENUM_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ENUM_STORE, GimpEnumStore)) +#define GIMP_ENUM_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ENUM_STORE, GimpEnumStoreClass)) +#define GIMP_IS_ENUM_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ENUM_STORE)) +#define GIMP_IS_ENUM_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ENUM_STORE)) +#define GIMP_ENUM_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ENUM_STORE, GimpEnumStoreClass)) + + +typedef struct _GimpEnumStoreClass GimpEnumStoreClass; + +struct _GimpEnumStore +{ + GimpIntStore parent_instance; + + GEnumClass *enum_class; +}; + +struct _GimpEnumStoreClass +{ + GimpIntStoreClass parent_class; + + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_enum_store_get_type (void) G_GNUC_CONST; + +GtkListStore * gimp_enum_store_new (GType enum_type); +GtkListStore * gimp_enum_store_new_with_range (GType enum_type, + gint minimum, + gint maximum); +GtkListStore * gimp_enum_store_new_with_values (GType enum_type, + gint n_values, + ...); +GtkListStore * gimp_enum_store_new_with_values_valist (GType enum_type, + gint n_values, + va_list args); + +GIMP_DEPRECATED_FOR(gimp_enum_store_set_icon_prefix) +void gimp_enum_store_set_stock_prefix (GimpEnumStore *store, + const gchar *stock_prefix); + +void gimp_enum_store_set_icon_prefix (GimpEnumStore *store, + const gchar *icon_prefix); + + +G_END_DECLS + +#endif /* __GIMP_ENUM_STORE_H__ */ diff --git a/libgimpwidgets/gimpenumwidgets.c b/libgimpwidgets/gimpenumwidgets.c new file mode 100644 index 0000000..1f30d1e --- /dev/null +++ b/libgimpwidgets/gimpenumwidgets.c @@ -0,0 +1,536 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumwidgets.c + * Copyright (C) 2002-2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpenumwidgets.h" +#include "gimpframe.h" +#include "gimphelpui.h" +#include "gimp3migration.h" + + +/** + * SECTION: gimpenumwidgets + * @title: GimpEnumWidgets + * @short_description: A set of utility functions to create widgets + * based on enums. + * + * A set of utility functions to create widgets based on enums. + **/ + + +/** + * gimp_enum_radio_box_new: + * @enum_type: the #GType of an enum. + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Creates a new group of #GtkRadioButtons representing the enum + * values. A group of radiobuttons is a good way to represent enums + * with up to three or four values. Often it is better to use a + * #GimpEnumComboBox instead. + * + * Return value: a new #GtkVBox holding a group of #GtkRadioButtons. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_radio_box_new (GType enum_type, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + GEnumClass *enum_class; + GtkWidget *vbox; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + enum_class = g_type_class_ref (enum_type); + + vbox = gimp_enum_radio_box_new_with_range (enum_type, + enum_class->minimum, + enum_class->maximum, + callback, callback_data, + first_button); + + g_type_class_unref (enum_class); + + return vbox; +} + +/** + * gimp_enum_radio_box_new_with_range: + * @minimum: the minimum enum value + * @maximum: the maximum enum value + * @enum_type: the #GType of an enum. + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Just like gimp_enum_radio_box_new(), this function creates a group + * of radio buttons, but additionally it supports limiting the range + * of available enum values. + * + * Return value: a new #GtkVBox holding a group of #GtkRadioButtons. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_radio_box_new_with_range (GType enum_type, + gint minimum, + gint maximum, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + GtkWidget *vbox; + GtkWidget *button; + GEnumClass *enum_class; + GEnumValue *value; + GSList *group = NULL; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + enum_class = g_type_class_ref (enum_type); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); + g_object_weak_ref (G_OBJECT (vbox), + (GWeakNotify) g_type_class_unref, enum_class); + + if (first_button) + *first_button = NULL; + + for (value = enum_class->values; value->value_name; value++) + { + const gchar *desc; + + if (value->value < minimum || value->value > maximum) + continue; + + desc = gimp_enum_value_get_desc (enum_class, value); + + button = gtk_radio_button_new_with_mnemonic (group, desc); + + if (first_button && *first_button == NULL) + *first_button = button; + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_object_set_data (G_OBJECT (button), "gimp-item-data", + GINT_TO_POINTER (value->value)); + + if (callback) + g_signal_connect (button, "toggled", + callback, + callback_data); + } + + return vbox; +} + +/** + * gimp_enum_radio_frame_new: + * @enum_type: the #GType of an enum. + * @label_widget: a widget to use as label for the frame that will + * hold the radio box. + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Calls gimp_enum_radio_box_new() and puts the resulting vbox into a + * #GtkFrame. + * + * Return value: a new #GtkFrame holding a group of #GtkRadioButtons. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_radio_frame_new (GType enum_type, + GtkWidget *label_widget, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + GtkWidget *frame; + GtkWidget *radio_box; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + g_return_val_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget), + NULL); + + frame = gimp_frame_new (NULL); + + if (label_widget) + { + gtk_frame_set_label_widget (GTK_FRAME (frame), label_widget); + gtk_widget_show (label_widget); + } + + radio_box = gimp_enum_radio_box_new (enum_type, + callback, callback_data, + first_button); + gtk_container_add (GTK_CONTAINER (frame), radio_box); + gtk_widget_show (radio_box); + + return frame; +} + +/** + * gimp_enum_radio_frame_new_with_range: + * @enum_type: the #GType of an enum. + * @minimum: the minimum enum value + * @maximum: the maximum enum value + * @label_widget: a widget to put into the frame that will hold the radio box. + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Calls gimp_enum_radio_box_new_with_range() and puts the resulting + * vbox into a #GtkFrame. + * + * Return value: a new #GtkFrame holding a group of #GtkRadioButtons. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_enum_radio_frame_new_with_range (GType enum_type, + gint minimum, + gint maximum, + GtkWidget *label_widget, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + GtkWidget *frame; + GtkWidget *radio_box; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + g_return_val_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget), + NULL); + + frame = gimp_frame_new (NULL); + + if (label_widget) + { + gtk_frame_set_label_widget (GTK_FRAME (frame), label_widget); + gtk_widget_show (label_widget); + } + + radio_box = gimp_enum_radio_box_new_with_range (enum_type, + minimum, + maximum, + callback, callback_data, + first_button); + gtk_container_add (GTK_CONTAINER (frame), radio_box); + gtk_widget_show (radio_box); + + return frame; +} + + +/** + * gimp_enum_stock_box_new: + * @enum_type: the #GType of an enum. + * @stock_prefix: the prefix of the group of stock ids to use. + * @icon_size: the icon size for the stock icons + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Creates a horizontal box of radio buttons with stock icons. The + * stock_id for each icon is created by appending the enum_value's + * nick to the given @stock_prefix. + * + * Return value: a new #GtkHBox holding a group of #GtkRadioButtons. + * + * Since: 2.4 + * + * Deprecated: GIMP 2.10 + **/ +GtkWidget * +gimp_enum_stock_box_new (GType enum_type, + const gchar *stock_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + return gimp_enum_icon_box_new (enum_type, stock_prefix, icon_size, + callback, callback_data, + first_button); +} + +/** + * gimp_enum_stock_box_new_with_range: + * @enum_type: the #GType of an enum. + * @minimum: the minumim enum value + * @maximum: the maximum enum value + * @stock_prefix: the prefix of the group of stock ids to use. + * @icon_size: the icon size for the stock icons + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Just like gimp_enum_stock_box_new(), this function creates a group + * of radio buttons, but additionally it supports limiting the range + * of available enum values. + * + * Return value: a new #GtkHBox holding a group of #GtkRadioButtons. + * + * Since: 2.4 + * + * Deprecated: GIMP 2.10 + **/ +GtkWidget * +gimp_enum_stock_box_new_with_range (GType enum_type, + gint minimum, + gint maximum, + const gchar *stock_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + return gimp_enum_icon_box_new_with_range (enum_type, minimum, maximum, + stock_prefix, icon_size, + callback, callback_data, + first_button); +} + +/** + * gimp_enum_stock_box_set_child_padding: + * @stock_box: a stock box widget + * @xpad: horizontal padding + * @ypad: vertical padding + * + * Sets the padding of all buttons in a box created by + * gimp_enum_stock_box_new(). + * + * Since: 2.4 + * + * Deprecated: GIMP 2.10 + **/ +void +gimp_enum_stock_box_set_child_padding (GtkWidget *stock_box, + gint xpad, + gint ypad) +{ + gimp_enum_icon_box_set_child_padding (stock_box, xpad, ypad); +} + +/** + * gimp_enum_icon_box_new: + * @enum_type: the #GType of an enum. + * @icon_prefix: the prefix of the group of icon names to use. + * @icon_size: the icon size for the icons + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Creates a horizontal box of radio buttons with named icons. The + * icon name for each icon is created by appending the enum_value's + * nick to the given @icon_prefix. + * + * Return value: a new #GtkHBox holding a group of #GtkRadioButtons. + * + * Since: 2.10 + **/ +GtkWidget * +gimp_enum_icon_box_new (GType enum_type, + const gchar *icon_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + GEnumClass *enum_class; + GtkWidget *box; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + + enum_class = g_type_class_ref (enum_type); + + box = gimp_enum_icon_box_new_with_range (enum_type, + enum_class->minimum, + enum_class->maximum, + icon_prefix, icon_size, + callback, callback_data, + first_button); + + g_type_class_unref (enum_class); + + return box; +} + +/** + * gimp_enum_icon_box_new_with_range: + * @enum_type: the #GType of an enum. + * @minimum: the minumim enum value + * @maximum: the maximum enum value + * @icon_prefix: the prefix of the group of icon names to use. + * @icon_size: the icon size for the icons + * @callback: a callback to connect to the "toggled" signal of each + * #GtkRadioButton that is created. + * @callback_data: data to pass to the @callback. + * @first_button: returns the first button in the created group. + * + * Just like gimp_enum_icon_box_new(), this function creates a group + * of radio buttons, but additionally it supports limiting the range + * of available enum values. + * + * Return value: a new #GtkHBox holding a group of #GtkRadioButtons. + * + * Since: 2.10 + **/ +GtkWidget * +gimp_enum_icon_box_new_with_range (GType enum_type, + gint minimum, + gint maximum, + const gchar *icon_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button) +{ + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *image; + GEnumClass *enum_class; + GEnumValue *value; + gchar *icon_name; + GSList *group = NULL; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + g_return_val_if_fail (icon_prefix != NULL, NULL); + + enum_class = g_type_class_ref (enum_type); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + g_object_weak_ref (G_OBJECT (hbox), + (GWeakNotify) g_type_class_unref, enum_class); + + if (first_button) + *first_button = NULL; + + for (value = enum_class->values; value->value_name; value++) + { + if (value->value < minimum || value->value > maximum) + continue; + + button = gtk_radio_button_new (group); + + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); + + if (first_button && *first_button == NULL) + *first_button = button; + + icon_name = g_strconcat (icon_prefix, "-", value->value_nick, NULL); + + image = gtk_image_new_from_icon_name (icon_name, icon_size); + + g_free (icon_name); + + if (image) + { + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + } + + gimp_help_set_help_data (button, + gimp_enum_value_get_desc (enum_class, value), + NULL); + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_object_set_data (G_OBJECT (button), "gimp-item-data", + GINT_TO_POINTER (value->value)); + + if (callback) + g_signal_connect (button, "toggled", + callback, + callback_data); + } + + return hbox; +} + +/** + * gimp_enum_icon_box_set_child_padding: + * @icon_box: an icon box widget + * @xpad: horizontal padding + * @ypad: vertical padding + * + * Sets the padding of all buttons in a box created by + * gimp_enum_icon_box_new(). + * + * Since: 2.10 + **/ +void +gimp_enum_icon_box_set_child_padding (GtkWidget *icon_box, + gint xpad, + gint ypad) +{ + GList *children; + GList *list; + + g_return_if_fail (GTK_IS_CONTAINER (icon_box)); + + children = gtk_container_get_children (GTK_CONTAINER (icon_box)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (list->data)); + + if (GTK_IS_MISC (child)) + { + GtkMisc *misc = GTK_MISC (child); + gint misc_xpad; + gint misc_ypad; + + gtk_misc_get_padding (misc, &misc_xpad, &misc_ypad); + + gtk_misc_set_padding (misc, + xpad < 0 ? misc_xpad : xpad, + ypad < 0 ? misc_ypad : ypad); + } + } + + g_list_free (children); +} diff --git a/libgimpwidgets/gimpenumwidgets.h b/libgimpwidgets/gimpenumwidgets.h new file mode 100644 index 0000000..847b94a --- /dev/null +++ b/libgimpwidgets/gimpenumwidgets.h @@ -0,0 +1,97 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpenumwidgets.h + * Copyright (C) 2002-2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_ENUM_WIDGETS_H__ +#define __GIMP_ENUM_WIDGETS_H__ + +G_BEGIN_DECLS + + +GtkWidget * gimp_enum_radio_box_new (GType enum_type, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); +GtkWidget * gimp_enum_radio_box_new_with_range (GType enum_type, + gint minimum, + gint maximum, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); + +GtkWidget * gimp_enum_radio_frame_new (GType enum_type, + GtkWidget *label_widget, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); +GtkWidget * gimp_enum_radio_frame_new_with_range (GType enum_type, + gint minimum, + gint maximum, + GtkWidget *label_widget, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); + +GIMP_DEPRECATED_FOR(gimp_enum_icon_box_new) +GtkWidget * gimp_enum_stock_box_new (GType enum_type, + const gchar *stock_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); +GIMP_DEPRECATED_FOR(gimp_enum_icon_box_new_with_range) +GtkWidget * gimp_enum_stock_box_new_with_range (GType enum_type, + gint minimum, + gint maximum, + const gchar *stock_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); +GIMP_DEPRECATED_FOR(gimp_enum_icon_box_set_child_padding) +void gimp_enum_stock_box_set_child_padding (GtkWidget *stock_box, + gint xpad, + gint ypad); + +GtkWidget * gimp_enum_icon_box_new (GType enum_type, + const gchar *icon_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); +GtkWidget * gimp_enum_icon_box_new_with_range (GType enum_type, + gint minimum, + gint maximum, + const gchar *icon_prefix, + GtkIconSize icon_size, + GCallback callback, + gpointer callback_data, + GtkWidget **first_button); +void gimp_enum_icon_box_set_child_padding (GtkWidget *icon_box, + gint xpad, + gint ypad); + +G_END_DECLS + +#endif /* __GIMP_ENUM_WIDGETS_H__ */ diff --git a/libgimpwidgets/gimpfileentry.c b/libgimpwidgets/gimpfileentry.c new file mode 100644 index 0000000..b3cc59c --- /dev/null +++ b/libgimpwidgets/gimpfileentry.c @@ -0,0 +1,499 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpfileentry.c + * Copyright (C) 1999-2004 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpfileentry.h" + +#include "gimphelpui.h" +#include "gimpicons.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpfileentry + * @title: GimpFileEntry + * @short_description: Widget for entering a filename. + * @see_also: #GimpPathEditor + * + * This widget is used to enter filenames or directories. + * + * There is a #GtkEntry for entering the filename manually and a "..." + * button which will pop up a #GtkFileChooserDialog. + * + * You can restrict the #GimpFileEntry to directories. In this + * case the filename listbox of the #GtkFileChooser dialog will be + * set to directory mode. + * + * If you specify @check_valid as #TRUE in gimp_file_entry_new() the + * entered filename will be checked for validity and a pixmap will be + * shown which indicates if the file exists or not. + * + * Whenever the user changes the filename, the "filename_changed" + * signal will be emitted. + **/ + + +enum +{ + FILENAME_CHANGED, + LAST_SIGNAL +}; + + +static void gimp_file_entry_dispose (GObject *object); + +static void gimp_file_entry_entry_changed (GtkWidget *widget, + GtkWidget *button); +static void gimp_file_entry_entry_activate (GtkWidget *widget, + GimpFileEntry *entry); +static gint gimp_file_entry_entry_focus_out (GtkWidget *widget, + GdkEvent *event, + GimpFileEntry *entry); +static void gimp_file_entry_file_manager_clicked (GtkWidget *widget, + GimpFileEntry *entry); +static void gimp_file_entry_browse_clicked (GtkWidget *widget, + GimpFileEntry *entry); +static void gimp_file_entry_check_filename (GimpFileEntry *entry); + + +G_DEFINE_TYPE (GimpFileEntry, gimp_file_entry, GTK_TYPE_BOX) + +#define parent_class gimp_file_entry_parent_class + +static guint gimp_file_entry_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_file_entry_class_init (GimpFileEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + /** + * GimpFileEntry::filename-changed: + * + * This signal is emitted whenever the user changes the filename. + **/ + gimp_file_entry_signals[FILENAME_CHANGED] = + g_signal_new ("filename-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpFileEntryClass, filename_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_file_entry_dispose; + + klass->filename_changed = NULL; +} + +static void +gimp_file_entry_init (GimpFileEntry *entry) +{ + GtkWidget *image; + GtkWidget *button; + + entry->title = NULL; + entry->file_dialog = NULL; + entry->check_valid = FALSE; + entry->file_exists = NULL; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (entry), + GTK_ORIENTATION_HORIZONTAL); + + gtk_box_set_spacing (GTK_BOX (entry), 4); + gtk_box_set_homogeneous (GTK_BOX (entry), FALSE); + + button = gtk_button_new (); + gtk_box_pack_end (GTK_BOX (entry), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + gtk_widget_set_sensitive (button, FALSE); + + image = gtk_image_new_from_icon_name (GIMP_ICON_FILE_MANAGER, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_file_entry_file_manager_clicked), + entry); + + gimp_help_set_help_data (button, + _("Show file location in the file manager"), + NULL); + + entry->browse_button = gtk_button_new (); + gtk_box_pack_end (GTK_BOX (entry), entry->browse_button, FALSE, FALSE, 0); + gtk_widget_show (entry->browse_button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_DOCUMENT_OPEN, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (entry->browse_button), image); + gtk_widget_show (image); + + g_signal_connect (entry->browse_button, "clicked", + G_CALLBACK (gimp_file_entry_browse_clicked), + entry); + + entry->entry = gtk_entry_new (); + gtk_box_pack_end (GTK_BOX (entry), entry->entry, TRUE, TRUE, 0); + gtk_widget_show (entry->entry); + + g_signal_connect (entry->entry, "changed", + G_CALLBACK (gimp_file_entry_entry_changed), + button); + g_signal_connect (entry->entry, "activate", + G_CALLBACK (gimp_file_entry_entry_activate), + entry); + g_signal_connect (entry->entry, "focus-out-event", + G_CALLBACK (gimp_file_entry_entry_focus_out), + entry); +} + +static void +gimp_file_entry_dispose (GObject *object) +{ + GimpFileEntry *entry = GIMP_FILE_ENTRY (object); + + g_clear_pointer (&entry->file_dialog, gtk_widget_destroy); + + g_clear_pointer (&entry->title, g_free); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * gimp_file_entry_new: + * @title: The title of the #GimpFileEntry dialog. + * @filename: The initial filename. + * @dir_only: %TRUE if the file entry should accept directories only. + * @check_valid: %TRUE if the widget should check if the entered file + * really exists. + * + * You should use #GtkFileChooserButton instead. + * + * Returns: A pointer to the new #GimpFileEntry widget. + **/ +GtkWidget * +gimp_file_entry_new (const gchar *title, + const gchar *filename, + gboolean dir_only, + gboolean check_valid) +{ + GimpFileEntry *entry; + + entry = g_object_new (GIMP_TYPE_FILE_ENTRY, NULL); + + entry->title = g_strdup (title); + entry->dir_only = dir_only; + entry->check_valid = check_valid; + + gimp_help_set_help_data (entry->browse_button, + entry->dir_only ? + _("Open a file selector to browse your folders") : + _("Open a file selector to browse your files"), + NULL); + + if (check_valid) + { + entry->file_exists = gtk_image_new_from_icon_name ("gtk-no", + GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start (GTK_BOX (entry), entry->file_exists, FALSE, FALSE, 0); + gtk_widget_show (entry->file_exists); + + gimp_help_set_help_data (entry->file_exists, + entry->dir_only ? + _("Indicates whether or not the folder exists") : + _("Indicates whether or not the file exists"), + NULL); + } + + gimp_file_entry_set_filename (entry, filename); + + return GTK_WIDGET (entry); +} + +/** + * gimp_file_entry_get_filename: + * @entry: The file entry you want to know the filename from. + * + * Note that you have to g_free() the returned string. + * + * Returns: The file or directory the user has entered. + **/ +gchar * +gimp_file_entry_get_filename (GimpFileEntry *entry) +{ + gchar *utf8; + gchar *filename; + + g_return_val_if_fail (GIMP_IS_FILE_ENTRY (entry), NULL); + + utf8 = gtk_editable_get_chars (GTK_EDITABLE (entry->entry), 0, -1); + + filename = g_filename_from_utf8 (utf8, -1, NULL, NULL, NULL); + + g_free (utf8); + + return filename; +} + +/** + * gimp_file_entry_set_filename: + * @entry: The file entry you want to set the filename for. + * @filename: The new filename. + * + * If you specified @check_valid as %TRUE in gimp_file_entry_new() + * the #GimpFileEntry will immediately check the validity of the file + * name. + **/ +void +gimp_file_entry_set_filename (GimpFileEntry *entry, + const gchar *filename) +{ + gchar *utf8; + + g_return_if_fail (GIMP_IS_FILE_ENTRY (entry)); + + if (filename) + utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); + else + utf8 = g_strdup (""); + + gtk_entry_set_text (GTK_ENTRY (entry->entry), utf8); + g_free (utf8); + + /* update everything + */ + gimp_file_entry_entry_activate (entry->entry, entry); +} + +static void +gimp_file_entry_entry_changed (GtkWidget *widget, + GtkWidget *button) +{ + const gchar *text = gtk_entry_get_text (GTK_ENTRY (widget)); + + if (text && strlen (text)) + gtk_widget_set_sensitive (button, TRUE); + else + gtk_widget_set_sensitive (button, FALSE); +} + +static void +gimp_file_entry_entry_activate (GtkWidget *widget, + GimpFileEntry *entry) +{ + gchar *utf8; + gchar *filename; + gint len; + + /* filenames still need more sanity checking + * (erase double G_DIR_SEPARATORS, ...) + */ + utf8 = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1); + utf8 = g_strstrip (utf8); + + while (((len = strlen (utf8)) > 1) && + (utf8[len - 1] == G_DIR_SEPARATOR)) + utf8[len - 1] = '\0'; + + filename = g_filename_from_utf8 (utf8, -1, NULL, NULL, NULL); + + g_signal_handlers_block_by_func (entry->entry, + gimp_file_entry_entry_activate, + entry); + gtk_entry_set_text (GTK_ENTRY (entry->entry), utf8); + g_signal_handlers_unblock_by_func (entry->entry, + gimp_file_entry_entry_activate, + entry); + + if (entry->file_dialog) + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (entry->file_dialog), + filename); + + g_free (filename); + g_free (utf8); + + gimp_file_entry_check_filename (entry); + + gtk_editable_set_position (GTK_EDITABLE (entry->entry), -1); + + g_signal_emit (entry, gimp_file_entry_signals[FILENAME_CHANGED], 0); +} + +static gboolean +gimp_file_entry_entry_focus_out (GtkWidget *widget, + GdkEvent *event, + GimpFileEntry *entry) +{ + gimp_file_entry_entry_activate (widget, entry); + + return FALSE; +} + +/* local callback of gimp_file_entry_browse_clicked() */ +static void +gimp_file_entry_chooser_response (GtkWidget *dialog, + gint response_id, + GimpFileEntry *entry) +{ + if (response_id == GTK_RESPONSE_OK) + { + gchar *filename; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + gimp_file_entry_set_filename (entry, filename); + g_free (filename); + } + + gtk_widget_hide (dialog); +} + +static void +gimp_file_entry_file_manager_clicked (GtkWidget *widget, + GimpFileEntry *entry) +{ + gchar *utf8; + GFile *file; + GError *error = NULL; + + utf8 = gtk_editable_get_chars (GTK_EDITABLE (entry->entry), 0, -1); + file = g_file_parse_name (utf8); + g_free (utf8); + + if (! gimp_file_show_in_file_manager (file, &error)) + { + g_message (_("Can't show file in file manager: %s"), + error->message); + g_clear_error (&error); + } + + g_object_unref (file); +} + +static void +gimp_file_entry_browse_clicked (GtkWidget *widget, + GimpFileEntry *entry) +{ + GtkFileChooser *chooser; + gchar *utf8; + gchar *filename; + + utf8 = gtk_editable_get_chars (GTK_EDITABLE (entry->entry), 0, -1); + filename = g_filename_from_utf8 (utf8, -1, NULL, NULL, NULL); + g_free (utf8); + + if (! entry->file_dialog) + { + const gchar *title = entry->title; + + if (! title) + { + if (entry->dir_only) + title = _("Select Folder"); + else + title = _("Select File"); + } + + entry->file_dialog = + gtk_file_chooser_dialog_new (title, NULL, + entry->dir_only ? + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (entry->file_dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + chooser = GTK_FILE_CHOOSER (entry->file_dialog); + + gtk_window_set_position (GTK_WINDOW (chooser), GTK_WIN_POS_MOUSE); + gtk_window_set_role (GTK_WINDOW (chooser), + "gimp-file-entry-file-dialog"); + + g_signal_connect (chooser, "response", + G_CALLBACK (gimp_file_entry_chooser_response), + entry); + g_signal_connect (chooser, "delete-event", + G_CALLBACK (gtk_true), + NULL); + + g_signal_connect_swapped (entry, "unmap", + G_CALLBACK (gtk_widget_hide), + chooser); + } + else + { + chooser = GTK_FILE_CHOOSER (entry->file_dialog); + } + + gtk_file_chooser_set_filename (chooser, filename); + + g_free (filename); + + gtk_window_set_screen (GTK_WINDOW (chooser), gtk_widget_get_screen (widget)); + gtk_window_present (GTK_WINDOW (chooser)); +} + +static void +gimp_file_entry_check_filename (GimpFileEntry *entry) +{ + gchar *utf8; + gchar *filename; + gboolean exists; + + if (! entry->check_valid || ! entry->file_exists) + return; + + utf8 = gtk_editable_get_chars (GTK_EDITABLE (entry->entry), 0, -1); + filename = g_filename_from_utf8 (utf8, -1, NULL, NULL, NULL); + g_free (utf8); + + if (entry->dir_only) + exists = g_file_test (filename, G_FILE_TEST_IS_DIR); + else + exists = g_file_test (filename, G_FILE_TEST_IS_REGULAR); + + g_free (filename); + + gtk_image_set_from_icon_name (GTK_IMAGE (entry->file_exists), + exists ? "gtk-yes" : "gtk-no", + GTK_ICON_SIZE_BUTTON); +} diff --git a/libgimpwidgets/gimpfileentry.h b/libgimpwidgets/gimpfileentry.h new file mode 100644 index 0000000..c408960 --- /dev/null +++ b/libgimpwidgets/gimpfileentry.h @@ -0,0 +1,91 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpfileentry.h + * Copyright (C) 1999-2004 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef GIMP_DISABLE_DEPRECATED + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_FILE_ENTRY_H__ +#define __GIMP_FILE_ENTRY_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_FILE_ENTRY (gimp_file_entry_get_type ()) +#define GIMP_FILE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILE_ENTRY, GimpFileEntry)) +#define GIMP_FILE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILE_ENTRY, GimpFileEntryClass)) +#define GIMP_IS_FILE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_FILE_ENTRY)) +#define GIMP_IS_FILE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILE_ENTRY)) +#define GIMP_FILE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILE_ENTRY, GimpFileEntryClass)) + + +typedef struct _GimpFileEntryClass GimpFileEntryClass; + +struct _GimpFileEntry +{ + GtkBox parent_instance; + + GtkWidget *file_exists; + GtkWidget *entry; + GtkWidget *browse_button; + + GtkWidget *file_dialog; + + gchar *title; + gboolean dir_only; + gboolean check_valid; +}; + +struct _GimpFileEntryClass +{ + GtkBoxClass parent_class; + + void (* filename_changed) (GimpFileEntry *entry); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_file_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_file_entry_new (const gchar *title, + const gchar *filename, + gboolean dir_only, + gboolean check_valid); + +gchar * gimp_file_entry_get_filename (GimpFileEntry *entry); +void gimp_file_entry_set_filename (GimpFileEntry *entry, + const gchar *filename); + + +G_END_DECLS + +#endif /* __GIMP_FILE_ENTRY_H__ */ + +#endif /* GIMP_DISABLE_DEPRECATED */ diff --git a/libgimpwidgets/gimpframe.c b/libgimpwidgets/gimpframe.c new file mode 100644 index 0000000..97fb487 --- /dev/null +++ b/libgimpwidgets/gimpframe.c @@ -0,0 +1,372 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpframe.c + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimp3migration.h" +#include "gimpframe.h" + + +/** + * SECTION: gimpframe + * @title: GimpFrame + * @short_description: A widget providing a HIG-compliant subclass + * of #GtkFrame. + * + * A widget providing a HIG-compliant subclass of #GtkFrame. + **/ + + +#define DEFAULT_LABEL_SPACING 6 +#define DEFAULT_LABEL_BOLD TRUE + +#define GIMP_FRAME_INDENT_KEY "gimp-frame-indent" +#define GIMP_FRAME_IN_EXPANDER_KEY "gimp-frame-in-expander" + + +static void gimp_frame_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_frame_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_frame_style_set (GtkWidget *widget, + GtkStyle *previous); +static gboolean gimp_frame_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static void gimp_frame_child_allocate (GtkFrame *frame, + GtkAllocation *allocation); +static void gimp_frame_label_widget_notify (GtkFrame *frame); +static gint gimp_frame_get_indent (GtkWidget *widget); +static gint gimp_frame_get_label_spacing (GtkFrame *frame); + + +G_DEFINE_TYPE (GimpFrame, gimp_frame, GTK_TYPE_FRAME) + +#define parent_class gimp_frame_parent_class + + +static void +gimp_frame_class_init (GimpFrameClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->size_request = gimp_frame_size_request; + widget_class->size_allocate = gimp_frame_size_allocate; + widget_class->style_set = gimp_frame_style_set; + widget_class->expose_event = gimp_frame_expose_event; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("label-bold", + "Label Bold", + "Whether the frame's label should be bold", + DEFAULT_LABEL_BOLD, + G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("label-spacing", + "Label Spacing", + "The spacing between the label and the frame content", + 0, + G_MAXINT, + DEFAULT_LABEL_SPACING, + G_PARAM_READABLE)); +} + + +static void +gimp_frame_init (GimpFrame *frame) +{ + g_signal_connect (frame, "notify::label-widget", + G_CALLBACK (gimp_frame_label_widget_notify), + NULL); +} + +static void +gimp_frame_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkFrame *frame = GTK_FRAME (widget); + GtkWidget *label_widget = gtk_frame_get_label_widget (frame); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkRequisition child_requisition; + gint border_width; + + if (label_widget && gtk_widget_get_visible (label_widget)) + { + gtk_widget_size_request (label_widget, requisition); + } + else + { + requisition->width = 0; + requisition->height = 0; + } + + requisition->height += gimp_frame_get_label_spacing (frame); + + if (child && gtk_widget_get_visible (child)) + { + gint indent = gimp_frame_get_indent (widget); + + gtk_widget_size_request (child, &child_requisition); + + requisition->width = MAX (requisition->width, + child_requisition.width + indent); + requisition->height += child_requisition.height; + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + requisition->width += 2 * border_width; + requisition->height += 2 * border_width; +} + +static void +gimp_frame_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkFrame *frame = GTK_FRAME (widget); + GtkWidget *label_widget = gtk_frame_get_label_widget (frame); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkAllocation child_allocation; + + /* must not chain up here */ + gtk_widget_set_allocation (widget, allocation); + + gimp_frame_child_allocate (frame, &child_allocation); + + if (child && gtk_widget_get_visible (child)) + gtk_widget_size_allocate (child, &child_allocation); + + if (label_widget && gtk_widget_get_visible (label_widget)) + { + GtkAllocation label_allocation; + GtkRequisition label_requisition; + gint border_width; + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + gtk_widget_get_child_requisition (label_widget, &label_requisition); + + label_allocation.x = allocation->x + border_width; + label_allocation.y = allocation->y + border_width; + label_allocation.width = MAX (label_requisition.width, + allocation->width - 2 * border_width); + label_allocation.height = label_requisition.height; + + gtk_widget_size_allocate (label_widget, &label_allocation); + } +} + +static void +gimp_frame_child_allocate (GtkFrame *frame, + GtkAllocation *child_allocation) +{ + GtkWidget *widget = GTK_WIDGET (frame); + GtkWidget *label_widget = gtk_frame_get_label_widget (frame); + GtkAllocation allocation; + gint border_width; + gint spacing = 0; + gint indent = gimp_frame_get_indent (widget); + + gtk_widget_get_allocation (widget, &allocation); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (frame)); + + if (label_widget && gtk_widget_get_visible (label_widget)) + { + GtkRequisition child_requisition; + + gtk_widget_get_child_requisition (label_widget, &child_requisition); + spacing += child_requisition.height; + } + + spacing += gimp_frame_get_label_spacing (frame); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + child_allocation->x = border_width + indent; + else + child_allocation->x = border_width; + + child_allocation->y = border_width + spacing; + child_allocation->width = MAX (1, + allocation.width - 2 * border_width - indent); + child_allocation->height = MAX (1, + allocation.height - + child_allocation->y - border_width); + + child_allocation->x += allocation.x; + child_allocation->y += allocation.y; +} + +static void +gimp_frame_style_set (GtkWidget *widget, + GtkStyle *previous) +{ + /* font changes invalidate the indentation */ + g_object_set_data (G_OBJECT (widget), GIMP_FRAME_INDENT_KEY, NULL); + + /* for "label_bold" */ + gimp_frame_label_widget_notify (GTK_FRAME (widget)); +} + +static gboolean +gimp_frame_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + if (gtk_widget_is_drawable (widget)) + { + GtkWidgetClass *widget_class = g_type_class_peek_parent (parent_class); + + return widget_class->expose_event (widget, event); + } + + return FALSE; +} + +static void +gimp_frame_label_widget_notify (GtkFrame *frame) +{ + GtkWidget *label_widget = gtk_frame_get_label_widget (frame); + + if (label_widget) + { + GtkLabel *label = NULL; + + if (GTK_IS_LABEL (label_widget)) + { + gfloat xalign, yalign; + + label = GTK_LABEL (label_widget); + + gtk_frame_get_label_align (frame, &xalign, &yalign); + gtk_label_set_xalign (GTK_LABEL (label), xalign); + gtk_label_set_yalign (GTK_LABEL (label), yalign); + } + else if (GTK_IS_BIN (label_widget)) + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (label_widget)); + + if (GTK_IS_LABEL (child)) + label = GTK_LABEL (child); + } + + if (label) + { + PangoAttrList *attrs = pango_attr_list_new (); + PangoAttribute *attr; + gboolean bold; + + gtk_widget_style_get (GTK_WIDGET (frame), "label_bold", &bold, NULL); + + attr = pango_attr_weight_new (bold ? + PANGO_WEIGHT_BOLD : + PANGO_WEIGHT_NORMAL); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attrs, attr); + + gtk_label_set_attributes (label, attrs); + + pango_attr_list_unref (attrs); + } + } +} + +static gint +gimp_frame_get_indent (GtkWidget *widget) +{ + gint width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + GIMP_FRAME_INDENT_KEY)); + + if (! width) + { + PangoLayout *layout; + + /* the HIG suggests to use four spaces so do just that */ + layout = gtk_widget_create_pango_layout (widget, " "); + pango_layout_get_pixel_size (layout, &width, NULL); + g_object_unref (layout); + + g_object_set_data (G_OBJECT (widget), + GIMP_FRAME_INDENT_KEY, GINT_TO_POINTER (width)); + } + + return width; +} + +static gint +gimp_frame_get_label_spacing (GtkFrame *frame) +{ + GtkWidget *label_widget = gtk_frame_get_label_widget (frame); + gint spacing = 0; + + if ((label_widget && gtk_widget_get_visible (label_widget)) || + (g_object_get_data (G_OBJECT (frame), GIMP_FRAME_IN_EXPANDER_KEY))) + { + gtk_widget_style_get (GTK_WIDGET (frame), + "label_spacing", &spacing, + NULL); + } + + return spacing; +} + +/** + * gimp_frame_new: + * @label: text to set as the frame's title label (or %NULL for no title) + * + * Creates a #GimpFrame widget. A #GimpFrame is a HIG-compliant + * variant of #GtkFrame. It doesn't render a frame at all but + * otherwise behaves like a frame. The frame's title is rendered in + * bold and the frame content is indented four spaces as suggested by + * the GNOME HIG (see https://developer.gnome.org/hig/stable/). + * + * Return value: a new #GimpFrame widget + * + * Since: 2.2 + **/ +GtkWidget * +gimp_frame_new (const gchar *label) +{ + GtkWidget *frame; + gboolean expander = FALSE; + + /* somewhat hackish, should perhaps be an object property of GimpFrame */ + if (label && strcmp (label, "<expander>") == 0) + { + expander = TRUE; + label = NULL; + } + + frame = g_object_new (GIMP_TYPE_FRAME, + "label", label, + NULL); + + if (expander) + g_object_set_data (G_OBJECT (frame), + GIMP_FRAME_IN_EXPANDER_KEY, (gpointer) TRUE); + + return frame; +} diff --git a/libgimpwidgets/gimpframe.h b/libgimpwidgets/gimpframe.h new file mode 100644 index 0000000..48d84ae --- /dev/null +++ b/libgimpwidgets/gimpframe.h @@ -0,0 +1,67 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpframe.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_FRAME_H__ +#define __GIMP_FRAME_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_FRAME (gimp_frame_get_type ()) +#define GIMP_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FRAME, GimpFrame)) +#define GIMP_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FRAME, GimpFrameClass)) +#define GIMP_IS_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FRAME)) +#define GIMP_IS_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FRAME)) +#define GIMP_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FRAME, GimpFrameClass)) + + +typedef struct _GimpFrameClass GimpFrameClass; + +struct _GimpFrame +{ + GtkFrame parent_instance; +}; + +struct _GimpFrameClass +{ + GtkFrameClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_frame_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_frame_new (const gchar *label); + + +G_END_DECLS + +#endif /* __GIMP_FRAME_H__ */ diff --git a/libgimpwidgets/gimphelpui.c b/libgimpwidgets/gimphelpui.c new file mode 100644 index 0000000..8c57f9a --- /dev/null +++ b/libgimpwidgets/gimphelpui.c @@ -0,0 +1,563 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphelpui.c + * Copyright (C) 2000-2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "gimpwidgets.h" +#include "gimpwidgets-private.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimphelpui + * @title: GimpHelpUI + * @short_description: Functions for setting tooltip and help identifier + * used by the GIMP help system. + * + * Functions for setting tooltip and help identifier used by the GIMP + * help system. + **/ + + +typedef enum +{ + GIMP_WIDGET_HELP_TOOLTIP = GTK_WIDGET_HELP_TOOLTIP, + GIMP_WIDGET_HELP_WHATS_THIS = GTK_WIDGET_HELP_WHATS_THIS, + GIMP_WIDGET_HELP_TYPE_HELP = 0xff +} GimpWidgetHelpType; + + +/* local variables */ + +static gboolean tooltips_enabled = TRUE; +static gboolean tooltips_enable_called = FALSE; + + +/* local function prototypes */ + +static const gchar * gimp_help_get_help_data (GtkWidget *widget, + GtkWidget **help_widget, + gpointer *ret_data); +static gboolean gimp_help_callback (GtkWidget *widget, + GimpWidgetHelpType help_type, + GimpHelpFunc help_func); + +static void gimp_help_menu_item_set_tooltip (GtkWidget *widget, + const gchar *tooltip, + const gchar *help_id); +static gboolean gimp_help_menu_item_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip); +static gboolean gimp_context_help_idle_start (gpointer widget); +static gboolean gimp_context_help_button_press (GtkWidget *widget, + GdkEventButton *bevent, + gpointer data); +static gboolean gimp_context_help_key_press (GtkWidget *widget, + GdkEventKey *kevent, + gpointer data); +static gboolean gimp_context_help_idle_show_help (gpointer data); + + +/* public functions */ + +/** + * gimp_help_enable_tooltips: + * + * Enable tooltips to be shown in the GIMP user interface. + * + * As a plug-in author, you don't need to care about this as this + * function is called for you from gimp_ui_init(). This ensures that + * the user setting from the GIMP preferences dialog is respected in + * all plug-in dialogs. + **/ +void +gimp_help_enable_tooltips (void) +{ + if (! tooltips_enable_called) + { + tooltips_enable_called = TRUE; + tooltips_enabled = TRUE; + } +} + +/** + * gimp_help_disable_tooltips: + * + * Disable tooltips to be shown in the GIMP user interface. + * + * As a plug-in author, you don't need to care about this as this + * function is called for you from gimp_ui_init(). This ensures that + * the user setting from the GIMP preferences dialog is respected in + * all plug-in dialogs. + **/ +void +gimp_help_disable_tooltips (void) +{ + if (! tooltips_enable_called) + { + tooltips_enable_called = TRUE; + tooltips_enabled = FALSE; + } +} + +/** + * gimp_standard_help_func: + * @help_id: A unique help identifier. + * @help_data: The @help_data passed to gimp_help_connect(). + * + * This is the standard GIMP help function which does nothing but calling + * gimp_help(). It is the right function to use in almost all cases. + **/ +void +gimp_standard_help_func (const gchar *help_id, + gpointer help_data) +{ + if (! _gimp_standard_help_func) + { + g_warning ("%s: you must call gimp_widgets_init() before using " + "the help system", G_STRFUNC); + return; + } + + (* _gimp_standard_help_func) (help_id, help_data); +} + +/** + * gimp_help_connect: + * @widget: The widget you want to connect the help accelerator for. Will + * be a #GtkWindow in most cases. + * @help_func: The function which will be called if the user presses "F1". + * @help_id: The @help_id which will be passed to @help_func. + * @help_data: The @help_data pointer which will be passed to @help_func. + * + * Note that this function is automatically called by all libgimp dialog + * constructors. You only have to call it for windows/dialogs you created + * "manually". + **/ +void +gimp_help_connect (GtkWidget *widget, + GimpHelpFunc help_func, + const gchar *help_id, + gpointer help_data) +{ + static gboolean initialized = FALSE; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (help_func != NULL); + + /* set up the help signals + */ + if (! initialized) + { + GtkBindingSet *binding_set; + + binding_set = + gtk_binding_set_by_class (g_type_class_peek (GTK_TYPE_WIDGET)); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_F1, 0, + "show-help", 1, + GTK_TYPE_WIDGET_HELP_TYPE, + GIMP_WIDGET_HELP_TYPE_HELP); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_F1, 0, + "show-help", 1, + GTK_TYPE_WIDGET_HELP_TYPE, + GIMP_WIDGET_HELP_TYPE_HELP); + + initialized = TRUE; + } + + gimp_help_set_help_data (widget, NULL, help_id); + + g_object_set_data (G_OBJECT (widget), "gimp-help-data", help_data); + + g_signal_connect (widget, "show-help", + G_CALLBACK (gimp_help_callback), + help_func); + + gtk_widget_add_events (widget, GDK_BUTTON_PRESS_MASK); +} + +/** + * gimp_help_set_help_data: + * @widget: The #GtkWidget you want to set a @tooltip and/or @help_id for. + * @tooltip: The text for this widget's tooltip (or %NULL). + * @help_id: The @help_id for the #GtkTipsQuery tooltips inspector. + * + * The reason why we don't use gtk_widget_set_tooltip_text() is that + * elements in the GIMP user interface should, if possible, also have + * a @help_id set for context-sensitive help. + * + * This function can be called with #NULL for @tooltip. Use this feature + * if you want to set a help link for a widget which shouldn't have + * a visible tooltip. + **/ +void +gimp_help_set_help_data (GtkWidget *widget, + const gchar *tooltip, + const gchar *help_id) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (tooltips_enabled) + { + gtk_widget_set_tooltip_text (widget, tooltip); + + if (GTK_IS_MENU_ITEM (widget)) + gimp_help_menu_item_set_tooltip (widget, tooltip, help_id); + } + + g_object_set_qdata (G_OBJECT (widget), GIMP_HELP_ID, (gpointer) help_id); +} + +/** + * gimp_help_set_help_data_with_markup: + * @widget: The #GtkWidget you want to set a @tooltip and/or @help_id for. + * @tooltip: The markup for this widget's tooltip (or %NULL). + * @help_id: The @help_id for the #GtkTipsQuery tooltips inspector. + * + * Just like gimp_help_set_help_data(), but supports to pass text + * which is marked up with <link linkend="PangoMarkupFormat">Pango + * text markup language</link>. + * + * Since: 2.6 + **/ +void +gimp_help_set_help_data_with_markup (GtkWidget *widget, + const gchar *tooltip, + const gchar *help_id) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (tooltips_enabled) + { + gtk_widget_set_tooltip_markup (widget, tooltip); + + if (GTK_IS_MENU_ITEM (widget)) + gimp_help_menu_item_set_tooltip (widget, tooltip, help_id); + } + + g_object_set_qdata (G_OBJECT (widget), GIMP_HELP_ID, (gpointer) help_id); +} + +/** + * gimp_context_help: + * @widget: Any #GtkWidget on the screen. + * + * This function invokes the context help inspector. + * + * The mouse cursor will turn turn into a question mark and the user can + * click on any widget of the application which started the inspector. + * + * If the widget the user clicked on has a @help_id string attached + * (see gimp_help_set_help_data()), the corresponding help page will + * be displayed. Otherwise the help system will ascend the widget hierarchy + * until it finds an attached @help_id string (which should be the + * case at least for every window/dialog). + **/ +void +gimp_context_help (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_help_callback (widget, GIMP_WIDGET_HELP_WHATS_THIS, NULL); +} + +/** + * gimp_help_id_quark: + * + * This function returns the #GQuark which should be used as key when + * attaching help IDs to widgets and objects. + * + * Return value: The #GQuark. + * + * Since: 2.2 + **/ +GQuark +gimp_help_id_quark (void) +{ + static GQuark quark = 0; + + if (! quark) + quark = g_quark_from_static_string ("gimp-help-id"); + + return quark; +} + + +/* private functions */ + +static const gchar * +gimp_help_get_help_data (GtkWidget *widget, + GtkWidget **help_widget, + gpointer *ret_data) +{ + const gchar *help_id = NULL; + gpointer help_data = NULL; + + for (; widget; widget = gtk_widget_get_parent (widget)) + { + help_id = g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID); + help_data = g_object_get_data (G_OBJECT (widget), "gimp-help-data"); + + if (help_id) + { + if (help_widget) + *help_widget = widget; + + if (ret_data) + *ret_data = help_data; + + return help_id; + } + } + + if (help_widget) + *help_widget = NULL; + + if (ret_data) + *ret_data = NULL; + + return NULL; +} + +static gboolean +gimp_help_callback (GtkWidget *widget, + GimpWidgetHelpType help_type, + GimpHelpFunc help_func) +{ + switch (help_type) + { + case GIMP_WIDGET_HELP_TYPE_HELP: + if (help_func) + { + help_func (g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID), + g_object_get_data (G_OBJECT (widget), "gimp-help-data")); + } + return TRUE; + + case GIMP_WIDGET_HELP_WHATS_THIS: + g_idle_add (gimp_context_help_idle_start, widget); + return TRUE; + + default: + break; + } + + return FALSE; +} + +static void +gimp_help_menu_item_set_tooltip (GtkWidget *widget, + const gchar *tooltip, + const gchar *help_id) +{ + g_return_if_fail (GTK_IS_MENU_ITEM (widget)); + + if (tooltip && help_id) + { + g_object_set (widget, "has-tooltip", TRUE, NULL); + + g_signal_connect (widget, "query-tooltip", + G_CALLBACK (gimp_help_menu_item_query_tooltip), + NULL); + } + else if (! tooltip) + { + g_object_set (widget, "has-tooltip", FALSE, NULL); + + g_signal_handlers_disconnect_by_func (widget, + gimp_help_menu_item_query_tooltip, + NULL); + } +} + +static gboolean +gimp_help_menu_item_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip) +{ + GtkWidget *vbox; + GtkWidget *label; + gchar *text; + gboolean use_markup = TRUE; + + text = gtk_widget_get_tooltip_markup (widget); + + if (! text) + { + text = gtk_widget_get_tooltip_text (widget); + use_markup = FALSE; + } + + if (! text) + return FALSE; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + + label = gtk_label_new (text); + gtk_label_set_use_markup (GTK_LABEL (label), use_markup); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + g_free (text); + + label = gtk_label_new (_("Press F1 for more help")); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_tooltip_set_custom (tooltip, vbox); + + return TRUE; +} + + +/* Do all the actual context help calls in idle functions and check for + * some widget holding a grab before starting the query because strange + * things happen if (1) the help browser pops up while the query has + * grabbed the pointer or (2) the query grabs the pointer while some + * other part of GIMP has grabbed it (e.g. a tool, eek) + */ + +static gboolean +gimp_context_help_idle_start (gpointer widget) +{ + if (! gtk_grab_get_current ()) + { + GtkWidget *invisible; + GdkCursor *cursor; + GdkGrabStatus status; + + invisible = gtk_invisible_new_for_screen (gtk_widget_get_screen (widget)); + gtk_widget_show (invisible); + + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (invisible), + GDK_QUESTION_ARROW); + + status = gdk_pointer_grab (gtk_widget_get_window (invisible), TRUE, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK, + NULL, cursor, + GDK_CURRENT_TIME); + + gdk_cursor_unref (cursor); + + if (status != GDK_GRAB_SUCCESS) + { + gtk_widget_destroy (invisible); + return FALSE; + } + + if (gdk_keyboard_grab (gtk_widget_get_window (invisible), TRUE, + GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) + { + gdk_display_pointer_ungrab (gtk_widget_get_display (invisible), + GDK_CURRENT_TIME); + gtk_widget_destroy (invisible); + return FALSE; + } + + gtk_grab_add (invisible); + + g_signal_connect (invisible, "button-press-event", + G_CALLBACK (gimp_context_help_button_press), + NULL); + g_signal_connect (invisible, "key-press-event", + G_CALLBACK (gimp_context_help_key_press), + NULL); + } + + return FALSE; +} + +static gboolean +gimp_context_help_button_press (GtkWidget *widget, + GdkEventButton *bevent, + gpointer data) +{ + GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) bevent); + + if (event_widget && bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS) + { + GdkDisplay *display = gtk_widget_get_display (widget); + + gtk_grab_remove (widget); + gdk_display_keyboard_ungrab (display, bevent->time); + gdk_display_pointer_ungrab (display, bevent->time); + gtk_widget_destroy (widget); + + if (event_widget != widget) + g_idle_add (gimp_context_help_idle_show_help, event_widget); + } + + return TRUE; +} + +static gboolean +gimp_context_help_key_press (GtkWidget *widget, + GdkEventKey *kevent, + gpointer data) +{ + if (kevent->keyval == GDK_KEY_Escape) + { + GdkDisplay *display = gtk_widget_get_display (widget); + + gtk_grab_remove (widget); + gdk_display_keyboard_ungrab (display, kevent->time); + gdk_display_pointer_ungrab (display, kevent->time); + gtk_widget_destroy (widget); + } + + return TRUE; +} + +static gboolean +gimp_context_help_idle_show_help (gpointer data) +{ + GtkWidget *help_widget; + const gchar *help_id = NULL; + gpointer help_data = NULL; + + help_id = gimp_help_get_help_data (GTK_WIDGET (data), &help_widget, + &help_data); + + if (help_id) + gimp_standard_help_func (help_id, help_data); + + return FALSE; +} diff --git a/libgimpwidgets/gimphelpui.h b/libgimpwidgets/gimphelpui.h new file mode 100644 index 0000000..0445d64 --- /dev/null +++ b/libgimpwidgets/gimphelpui.h @@ -0,0 +1,76 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphelpui.h + * Copyright (C) 2000-2003 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_HELP_UI_H__ +#define __GIMP_HELP_UI_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +void gimp_help_enable_tooltips (void); +void gimp_help_disable_tooltips (void); + +/* the standard gimp help function + */ +void gimp_standard_help_func (const gchar *help_id, + gpointer help_data); + +/* connect the help callback of a window */ +void gimp_help_connect (GtkWidget *widget, + GimpHelpFunc help_func, + const gchar *help_id, + gpointer help_data); + +/* set help data for non-window widgets */ +void gimp_help_set_help_data (GtkWidget *widget, + const gchar *tooltip, + const gchar *help_id); + +/* set help data with markup for non-window widgets */ +void gimp_help_set_help_data_with_markup (GtkWidget *widget, + const gchar *tooltip, + const gchar *help_id); + +/* activate the context help inspector */ +void gimp_context_help (GtkWidget *widget); + + +/** + * GIMP_HELP_ID: + * + * The #GQuark used to attach GIMP help IDs to widgets. + * + * Since: 2.2 + **/ +#define GIMP_HELP_ID (gimp_help_id_quark ()) + +GQuark gimp_help_id_quark (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_HELP_UI_H__ */ diff --git a/libgimpwidgets/gimphintbox.c b/libgimpwidgets/gimphintbox.c new file mode 100644 index 0000000..f1e2291 --- /dev/null +++ b/libgimpwidgets/gimphintbox.c @@ -0,0 +1,246 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimphintbox.c + * Copyright (C) 2006 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "gimpwidgets.h" + + +/** + * SECTION: gimphintbox + * @title: GimpHintBox + * @short_description: Displays a wilber icon and a text. + * + * Displays a wilber icon and a text. + **/ + + +enum +{ + PROP_0, + PROP_ICON_NAME, + PROP_STOCK_ID, + PROP_HINT +}; + +struct _GimpHintBoxPrivate +{ + gchar *icon_name; + gchar *stock_id; + gchar *hint; +}; + + +static void gimp_hint_box_constructed (GObject *object); +static void gimp_hint_box_finalize (GObject *object); +static void gimp_hint_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_hint_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpHintBox, gimp_hint_box, GTK_TYPE_BOX) + +#define parent_class gimp_hint_box_parent_class + + +static void +gimp_hint_box_class_init (GimpHintBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_hint_box_constructed; + object_class->finalize = gimp_hint_box_finalize; + object_class->set_property = gimp_hint_box_set_property; + object_class->get_property = gimp_hint_box_get_property; + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "The icon to show next to the hint", + GIMP_ICON_DIALOG_INFORMATION, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_STOCK_ID, + g_param_spec_string ("stock-id", + "Stock ID", + "Deprecated: use icon-name instead", + GIMP_ICON_DIALOG_INFORMATION, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HINT, + g_param_spec_string ("hint", + "Hint", + "The hint to display", + NULL, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_hint_box_init (GimpHintBox *box) +{ + box->priv = gimp_hint_box_get_instance_private (box); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_HORIZONTAL); +} + +static void +gimp_hint_box_constructed (GObject *object) +{ + GimpHintBox *box = GIMP_HINT_BOX (object); + GtkWidget *image = NULL; + GtkWidget *label; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_box_set_spacing (GTK_BOX (box), 12); + + if (box->priv->icon_name) + { + image = gtk_image_new_from_icon_name (box->priv->icon_name, + GTK_ICON_SIZE_DIALOG); + } + else if (box->priv->stock_id) + { + image = gtk_image_new_from_stock (box->priv->stock_id, + GTK_ICON_SIZE_DIALOG); + } + + if (image) + { + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + gtk_widget_show (image); + } + + label = g_object_new (GTK_TYPE_LABEL, + "label", box->priv->hint, + "wrap", TRUE, + "justify", GTK_JUSTIFY_LEFT, + "xalign", 0.0, + "yalign", 0.5, + NULL); + + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); +} + +static void +gimp_hint_box_finalize (GObject *object) +{ + GimpHintBox *box = GIMP_HINT_BOX (object); + + g_clear_pointer (&box->priv->icon_name, g_free); + g_clear_pointer (&box->priv->stock_id, g_free); + g_clear_pointer (&box->priv->hint, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_hint_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpHintBox *box = GIMP_HINT_BOX (object); + + switch (property_id) + { + case PROP_ICON_NAME: + box->priv->icon_name = g_value_dup_string (value); + break; + + case PROP_STOCK_ID: + box->priv->stock_id = g_value_dup_string (value); + break; + + case PROP_HINT: + box->priv->hint = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_hint_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpHintBox *box = GIMP_HINT_BOX (object); + + switch (property_id) + { + case PROP_ICON_NAME: + g_value_set_string (value, box->priv->icon_name); + break; + + case PROP_STOCK_ID: + g_value_set_string (value, box->priv->stock_id); + break; + + case PROP_HINT: + g_value_set_string (value, box->priv->hint); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_hint_box_new: + * @hint: text to display as a user hint + * + * Creates a new widget that shows a text label showing @hint, + * decorated with a GIMP_ICON_DIALOG_INFORMATION wilber icon. + * + * Return value: a new widget + * + * Since GIMP 2.4 + **/ +GtkWidget * +gimp_hint_box_new (const gchar *hint) +{ + g_return_val_if_fail (hint != NULL, NULL); + + return g_object_new (GIMP_TYPE_HINT_BOX, + "hint", hint, + NULL); +} diff --git a/libgimpwidgets/gimphintbox.h b/libgimpwidgets/gimphintbox.h new file mode 100644 index 0000000..c5c050e --- /dev/null +++ b/libgimpwidgets/gimphintbox.h @@ -0,0 +1,75 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimphintbox.h + * Copyright (C) 2006 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_HINT_BOX_H__ +#define __GIMP_HINT_BOX_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_HINT_BOX (gimp_hint_box_get_type ()) +#define GIMP_HINT_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HINT_BOX, GimpHintBox)) +#define GIMP_HINT_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HINT_BOX, GimpHintBoxClass)) +#define GIMP_IS_HINT_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HINT_BOX)) +#define GIMP_IS_HINT_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HINT_BOX)) +#define GIMP_HINT_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HINT_BOX, GimpHintBoxClass)) + + +typedef struct _GimpHintBoxPrivate GimpHintBoxPrivate; +typedef struct _GimpHintBoxClass GimpHintBoxClass; + +struct _GimpHintBox +{ + GtkBox parent_instance; + + GimpHintBoxPrivate *priv; +}; + +struct _GimpHintBoxClass +{ + GtkBoxClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); + void (* _gimp_reserved5) (void); + void (* _gimp_reserved6) (void); + void (* _gimp_reserved7) (void); + void (* _gimp_reserved8) (void); +}; + + +GType gimp_hint_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_hint_box_new (const gchar *hint); + + +G_END_DECLS + +#endif /* __GIMP_HINT_BOX_H__ */ diff --git a/libgimpwidgets/gimpicons.c b/libgimpwidgets/gimpicons.c new file mode 100644 index 0000000..66921cf --- /dev/null +++ b/libgimpwidgets/gimpicons.c @@ -0,0 +1,651 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpicons.c + * Copyright (C) 2001-2015 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpicons.h" + +#include "icons/Color/gimp-icon-pixbufs.c" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpicons + * @title: GimpIcons + * @short_description: Prebuilt common menu/toolbar items and + * corresponding icons + * + * GIMP registers a set of menu/toolbar items and corresponding icons + * in addition to the standard GTK+ stock items. These can be used + * just like GTK+ stock items. GIMP also overrides a few of the GTK+ + * icons (namely the ones in dialog size). + * + * Stock icons may have a RTL variant which gets used for + * right-to-left locales. + **/ + + +#define LIBGIMP_DOMAIN GETTEXT_PACKAGE "-libgimp" +#define GIMP_TOILET_PAPER "gimp-toilet-paper" +#define GIMP_DEFAULT_ICON_THEME "Symbolic" + + +static GtkIconFactory *gimp_stock_factory = NULL; + + +static const GtkStockItem gimp_stock_items[] = +{ + { GIMP_STOCK_ANCHOR, N_("Anchor"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CENTER, N_("C_enter"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DUPLICATE, N_("_Duplicate"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LINKED, N_("Linked"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_PASTE_AS_NEW, N_("Paste as New"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_PASTE_INTO, N_("Paste Into"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_RESET, N_("_Reset"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_VISIBLE, N_("Visible"), 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_GRADIENT_LINEAR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_BILINEAR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_RADIAL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_SQUARE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_CONICAL_SYMMETRIC, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_CONICAL_ASYMMETRIC, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_SHAPEBURST_ANGULAR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_SHAPEBURST_SPHERICAL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_SHAPEBURST_DIMPLED, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_SPIRAL_CLOCKWISE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRADIENT_SPIRAL_ANTICLOCKWISE, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_GRAVITY_EAST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_NORTH, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_NORTH_EAST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_NORTH_WEST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_SOUTH, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_SOUTH_EAST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_SOUTH_WEST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRAVITY_WEST, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_HCENTER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_VCENTER, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_HCHAIN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_HCHAIN_BROKEN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_VCHAIN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_VCHAIN_BROKEN, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_SELECTION, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_REPLACE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_ADD, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_SUBTRACT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_INTERSECT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_STROKE, N_("_Stroke"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_TO_CHANNEL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_TO_PATH, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_PATH_STROKE, N_("_Stroke"), 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_CURVE_FREE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CURVE_SMOOTH, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_COLOR_PICKER_BLACK, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_COLOR_PICKER_GRAY, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_COLOR_PICKER_WHITE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_COLOR_TRIANGLE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_COLOR_PICK_FROM_SCREEN, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_CHAR_PICKER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LETTER_SPACING, N_("L_etter Spacing"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LINE_SPACING, N_("L_ine Spacing"), 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_TEXT_DIR_LTR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TEXT_DIR_RTL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_PATTERN, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_CONVERT_RGB, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CONVERT_GRAYSCALE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CONVERT_INDEXED, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_INVERT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_MERGE_DOWN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LAYER_TO_IMAGESIZE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_PLUGIN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_RESHOW_FILTER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_ROTATE_90, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_ROTATE_180, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_ROTATE_270, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_RESIZE, N_("Re_size"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SCALE, N_("_Scale"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_FLIP_HORIZONTAL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_FLIP_VERTICAL, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_IMAGES, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LAYERS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNELS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_PATHS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOLS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_OPTIONS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DEVICE_STATUS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_INPUT_DEVICE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CURSOR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SAMPLE_POINT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DYNAMICS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_PRESET, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_IMAGE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LAYER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TEXT_LAYER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_FLOATING_SELECTION, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL_RED, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL_GREEN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL_BLUE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL_GRAY, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL_INDEXED, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CHANNEL_ALPHA, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LAYER_MASK, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_PATH, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TEMPLATE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_COLORMAP, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_HISTOGRAM, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_HISTOGRAM_LINEAR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_HISTOGRAM_LOGARITHMIC, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_UNDO_HISTORY, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TRANSPARENCY, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_SELECTION_ALL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_NONE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_GROW, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_SHRINK, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SELECTION_BORDER, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_NAVIGATION, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_QUICK_MASK_OFF, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_QUICK_MASK_ON, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_CONTROLLER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CONTROLLER_KEYBOARD, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CONTROLLER_LINUX_INPUT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CONTROLLER_MIDI, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CONTROLLER_WHEEL, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_DISPLAY_FILTER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DISPLAY_FILTER_COLORBLIND, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DISPLAY_FILTER_CONTRAST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DISPLAY_FILTER_GAMMA, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DISPLAY_FILTER_LCMS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_DISPLAY_FILTER_PROOF, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_LIST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GRID, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_PORTRAIT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_LANDSCAPE, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_TOILET_PAPER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_GEGL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_WEB, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_VIDEO, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_SHAPE_CIRCLE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SHAPE_DIAMOND, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_SHAPE_SQUARE, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_CAP_BUTT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CAP_ROUND, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_CAP_SQUARE, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_JOIN_MITER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_JOIN_ROUND, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_JOIN_BEVEL, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_ERROR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_INFO, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_QUESTION, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_USER_MANUAL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_WARNING, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_WILBER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_WILBER_EEK, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_FRAME, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TEXTURE, NULL, 0, 0, LIBGIMP_DOMAIN }, + + { GIMP_STOCK_TOOL_AIRBRUSH, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_ALIGN, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_BLEND, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_BLUR, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_BRIGHTNESS_CONTRAST, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_BUCKET_FILL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_BY_COLOR_SELECT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_CAGE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_CLONE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_COLOR_BALANCE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_COLOR_PICKER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_COLORIZE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_CROP, N_("Cr_op"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_CURVES, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_DESATURATE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_DODGE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_ELLIPSE_SELECT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_ERASER, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_FLIP, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_FREE_SELECT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_FOREGROUND_SELECT, N_("_Select"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_FUZZY_SELECT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_HUE_SATURATION, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_HEAL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_INK, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_ISCISSORS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_LEVELS, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_MEASURE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_MOVE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_PAINTBRUSH, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_PATH, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_PENCIL, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_PERSPECTIVE, N_("_Transform"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_PERSPECTIVE_CLONE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_POSTERIZE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_RECT_SELECT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_ROTATE, N_("_Rotate"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_SCALE, N_("_Scale"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_SHEAR, N_("_Shear"), 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_SMUDGE, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_TEXT, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_THRESHOLD, NULL, 0, 0, LIBGIMP_DOMAIN }, + { GIMP_STOCK_TOOL_ZOOM, NULL, 0, 0, LIBGIMP_DOMAIN } +}; + +static const GtkStockItem gimp_compat_stock_items[] = +{ + { "gimp-indexed-palette", NULL, 0, 0, LIBGIMP_DOMAIN }, + { "gimp-qmask-off", NULL, 0, 0, LIBGIMP_DOMAIN }, + { "gimp-qmask-on", NULL, 0, 0, LIBGIMP_DOMAIN } +}; + + +static void +register_stock_icon (GtkIconFactory *factory, + const gchar *stock_id, + const gchar *icon_name) +{ + GtkIconSet *set = gtk_icon_set_new (); + GtkIconSource *source = gtk_icon_source_new (); + + gtk_icon_source_set_direction_wildcarded (source, TRUE); + gtk_icon_source_set_size_wildcarded (source, TRUE); + gtk_icon_source_set_state_wildcarded (source, TRUE); + gtk_icon_source_set_icon_name (source, icon_name); + + gtk_icon_set_add_source (set, source); + gtk_icon_source_free (source); + + gtk_icon_factory_add (factory, stock_id, set); + gtk_icon_set_unref (set); +} + +static void +register_bidi_stock_icon (GtkIconFactory *factory, + const gchar *stock_id, + const gchar *icon_name_ltr, + const gchar *icon_name_rtl) +{ + GtkIconSet *set = gtk_icon_set_new (); + GtkIconSource *source = gtk_icon_source_new (); + + gtk_icon_source_set_direction (source, GTK_TEXT_DIR_LTR); + gtk_icon_source_set_direction_wildcarded (source, FALSE); + gtk_icon_source_set_size_wildcarded (source, TRUE); + gtk_icon_source_set_state_wildcarded (source, TRUE); + gtk_icon_source_set_icon_name (source, icon_name_ltr); + + gtk_icon_set_add_source (set, source); + gtk_icon_source_free (source); + + source = gtk_icon_source_new (); + + gtk_icon_source_set_direction (source, GTK_TEXT_DIR_RTL); + gtk_icon_source_set_direction_wildcarded (source, FALSE); + gtk_icon_source_set_size_wildcarded (source, TRUE); + gtk_icon_source_set_state_wildcarded (source, TRUE); + gtk_icon_source_set_icon_name (source, icon_name_rtl); + + gtk_icon_set_add_source (set, source); + gtk_icon_source_free (source); + + gtk_icon_factory_add (factory, stock_id, set); + gtk_icon_set_unref (set); +} + + +static GFile *icon_theme_path = NULL; +static GFile *default_search_path = NULL; + + +static void +gimp_icons_change_icon_theme (GFile *new_search_path) +{ + GFile *old_search_path = g_file_get_parent (icon_theme_path); + + if (! default_search_path) + default_search_path = gimp_data_directory_file ("icons", NULL); + + if (! g_file_equal (new_search_path, old_search_path)) + { + GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); + + if (g_file_equal (old_search_path, default_search_path)) + { + /* if the old icon theme is in the default search path, + * simply prepend the new theme's path + */ + gchar *path_str = g_file_get_path (new_search_path); + + gtk_icon_theme_prepend_search_path (icon_theme, path_str); + g_free (path_str); + } + else + { + /* if the old theme is not in the default search path, + * we need to deal with the search path's first element + */ + gchar **paths; + gint n_paths; + + gtk_icon_theme_get_search_path (icon_theme, &paths, &n_paths); + + if (g_file_equal (new_search_path, default_search_path)) + { + /* when switching to a theme in the default path, remove + * the first search path element, the default search path + * is still in the search path + */ + gtk_icon_theme_set_search_path (icon_theme, + (const gchar **) paths + 1, + n_paths - 1); + } + else + { + /* when switching between two non-default search paths, replace + * the first element of the search path with the new + * theme's path + */ + g_free (paths[0]); + paths[0] = g_file_get_path (new_search_path); + + gtk_icon_theme_set_search_path (icon_theme, + (const gchar **) paths, n_paths); + } + + g_strfreev (paths); + } + } + + g_object_unref (old_search_path); +} + +static void +gimp_icons_notify_system_icon_theme (GObject *settings, + GParamSpec *param, + gpointer unused) +{ + GdkScreen *screen = gdk_screen_get_default (); + GValue value = G_VALUE_INIT; + + g_value_init (&value, G_TYPE_STRING); + + if (gdk_screen_get_setting (screen, "gtk-icon-theme-name", &value)) + { + const gchar *new_system_icon_theme = g_value_get_string (&value); + gchar *cur_system_icon_theme = NULL; + + g_object_get (settings, + "gtk-fallback-icon-theme", &cur_system_icon_theme, + NULL); + if (g_strcmp0 (cur_system_icon_theme, new_system_icon_theme)) + { + g_object_set (settings, + "gtk-fallback-icon-theme", new_system_icon_theme, + NULL); + + g_object_notify (settings, "gtk-icon-theme-name"); + } + + g_free (cur_system_icon_theme); + } + + g_value_unset (&value); +} + +static gboolean +gimp_icons_sanity_check (GFile *path, + const gchar *theme_name) +{ + gboolean exists = FALSE; + GFile *child = g_file_get_child (path, theme_name); + + if (g_file_query_exists (child, NULL)) + { + GFile *index = g_file_get_child (child, "index.theme"); + + if (g_file_query_exists (index, NULL)) + exists = TRUE; + else + g_printerr ("%s: Icon theme path has no '%s/index.theme': %s\n", + G_STRFUNC, theme_name, gimp_file_get_utf8_name (path)); + + g_object_unref (index); + } + else + g_printerr ("%s: Icon theme path has no '%s' subdirectory: %s\n", + G_STRFUNC, theme_name, gimp_file_get_utf8_name (path)); + + g_object_unref (child); + + return exists; +} + +void +gimp_icons_set_icon_theme (GFile *path) +{ + gchar *icon_theme_name; + GFile *search_path; + + g_return_if_fail (path == NULL || G_IS_FILE (path)); + + if (path) + path = g_object_ref (path); + else + path = gimp_data_directory_file ("icons", GIMP_DEFAULT_ICON_THEME, NULL); + + search_path = g_file_get_parent (path); + icon_theme_name = g_file_get_basename (path); + + if (gimp_icons_sanity_check (search_path, "hicolor") && + gimp_icons_sanity_check (search_path, icon_theme_name)) + { + if (icon_theme_path) + { + /* this is an icon theme change */ + gimp_icons_change_icon_theme (search_path); + + if (! g_file_equal (icon_theme_path, path)) + { + g_object_unref (icon_theme_path); + icon_theme_path = g_object_ref (path); + } + + g_object_set (gtk_settings_get_for_screen (gdk_screen_get_default ()), + "gtk-icon-theme-name", icon_theme_name, + NULL); + } + else + { + /* this is the first call upon initialization */ + icon_theme_path = g_object_ref (path); + } + } + + g_free (icon_theme_name); + g_object_unref (search_path); + g_object_unref (path); +} + +/** + * gimp_stock_init: + * + * Initializes the GIMP stock icon factory. + * + * You don't need to call this function as gimp_ui_init() already does + * this for you. + * + * Deprecated: 2.10: Use gimp_icons_init() instead. + */ +void +gimp_stock_init (void) +{ + gimp_icons_init (); +} + +/** + * gimp_icons_init: + * + * Initializes the GIMP stock icon factory. + * + * You don't need to call this function as gimp_ui_init() already does + * this for you. + */ +void +gimp_icons_init (void) +{ + static gboolean initialized = FALSE; + + GtkSettings *settings; + GdkPixbuf *pixbuf; + GError *error = NULL; + gchar *icons_dir; + gchar *system_icon_theme; + gchar *gimp_icon_theme; + gint i; + + if (initialized) + return; + + gimp_stock_factory = gtk_icon_factory_new (); + + for (i = 0; i < G_N_ELEMENTS (gimp_stock_items); i++) + { + register_stock_icon (gimp_stock_factory, + gimp_stock_items[i].stock_id, + gimp_stock_items[i].stock_id); + } + + register_bidi_stock_icon (gimp_stock_factory, + GIMP_STOCK_MENU_LEFT, + GIMP_STOCK_MENU_LEFT, GIMP_STOCK_MENU_RIGHT); + register_bidi_stock_icon (gimp_stock_factory, + GIMP_STOCK_MENU_RIGHT, + GIMP_STOCK_MENU_RIGHT, GIMP_STOCK_MENU_LEFT); + + register_stock_icon (gimp_stock_factory, + "gimp-indexed-palette", GIMP_STOCK_COLORMAP); + register_stock_icon (gimp_stock_factory, + "gimp-qmask-off", GIMP_STOCK_QUICK_MASK_OFF); + register_stock_icon (gimp_stock_factory, + "gimp-qmask-on", GIMP_STOCK_QUICK_MASK_ON); + register_stock_icon (gimp_stock_factory, + "gimp-tool-blend", GIMP_STOCK_TOOL_BLEND); + + gtk_icon_factory_add_default (gimp_stock_factory); + + gtk_stock_add_static (gimp_stock_items, + G_N_ELEMENTS (gimp_stock_items)); + gtk_stock_add_static (gimp_compat_stock_items, + G_N_ELEMENTS (gimp_compat_stock_items)); + + /* always prepend the default icon theme, it's never removed from + * the path again and acts as fallback for missing icons in other + * themes. + */ + if (! default_search_path) + default_search_path = gimp_data_directory_file ("icons", + NULL); + + icons_dir = g_file_get_path (default_search_path); + gtk_icon_theme_prepend_search_path (gtk_icon_theme_get_default (), + icons_dir); + g_free (icons_dir); + + /* if an icon theme was chosen before init(), change to it */ + if (icon_theme_path) + { + GFile *search_path = g_file_get_parent (icon_theme_path); + + if (!g_file_equal (search_path, default_search_path)) + { + gchar *icon_dir = g_file_get_path (search_path); + + gtk_icon_theme_prepend_search_path (gtk_icon_theme_get_default (), + icon_dir); + g_free (icon_dir); + } + g_object_unref (search_path); + + gimp_icon_theme = g_file_get_basename (icon_theme_path); + } + else + { + gimp_icon_theme = g_strdup (GIMP_DEFAULT_ICON_THEME); + } + + settings = gtk_settings_get_for_screen (gdk_screen_get_default ()); + + g_object_get (settings, "gtk-icon-theme-name", &system_icon_theme, NULL); + + g_object_set (settings, + "gtk-fallback-icon-theme", system_icon_theme, + "gtk-icon-theme-name", gimp_icon_theme, + NULL); + + g_free (gimp_icon_theme); + g_free (system_icon_theme); + + g_signal_connect (settings, "notify::gtk-icon-theme-name", + G_CALLBACK (gimp_icons_notify_system_icon_theme), NULL); + pixbuf = gdk_pixbuf_new_from_resource ("/org/gimp/icons/64/gimp-wilber-eek.png", + &error); + + if (pixbuf) + { + gtk_icon_theme_add_builtin_icon (GIMP_STOCK_WILBER_EEK, 64, pixbuf); + g_object_unref (pixbuf); + } + else + { + g_critical ("Failed to create icon image: %s", error->message); + g_clear_error (&error); + } + + initialized = TRUE; +} diff --git a/libgimpwidgets/gimpicons.h b/libgimpwidgets/gimpicons.h new file mode 100644 index 0000000..c297213 --- /dev/null +++ b/libgimpwidgets/gimpicons.h @@ -0,0 +1,680 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpicons.h + * Copyright (C) 2001-2015 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_ICONS_H__ +#define __GIMP_ICONS_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +/* random actions that don't fit in any category */ + +#define GIMP_ICON_ATTACH "gimp-attach" +#define GIMP_ICON_DETACH "gimp-detach" +#define GIMP_ICON_INVERT "gimp-invert" +#define GIMP_ICON_RECORD "media-record" +#define GIMP_ICON_RESET "gimp-reset" +#define GIMP_ICON_SHRED "gimp-shred" + + +/* random states/things that don't fit in any category */ + +#define GIMP_ICON_BUSINESS_CARD "gimp-business-card" +#define GIMP_ICON_CHAR_PICKER "gimp-char-picker" +#define GIMP_ICON_CURSOR "gimp-cursor" +#define GIMP_ICON_DISPLAY "gimp-display" +#define GIMP_ICON_GEGL "gimp-gegl" +#define GIMP_ICON_LINKED "gimp-linked" +#define GIMP_ICON_MARKER "gimp-marker" +#define GIMP_ICON_SMARTPHONE "gimp-smartphone" +#define GIMP_ICON_TRANSPARENCY "gimp-transparency" +#define GIMP_ICON_VIDEO "gimp-video" +#define GIMP_ICON_VISIBLE "gimp-visible" +#define GIMP_ICON_WEB "gimp-web" + + +/* random objects/entities that don't fit in any category */ + +#define GIMP_ICON_BRUSH GIMP_ICON_TOOL_PAINTBRUSH +#define GIMP_ICON_BUFFER GIMP_ICON_EDIT_PASTE +#define GIMP_ICON_COLORMAP "gimp-colormap" +#define GIMP_ICON_DYNAMICS "gimp-dynamics" +#define GIMP_ICON_FILE_MANAGER "gimp-file-manager" +#define GIMP_ICON_FONT "gtk-select-font" +#define GIMP_ICON_GRADIENT GIMP_ICON_TOOL_GRADIENT +#define GIMP_ICON_GRID "gimp-grid" +#define GIMP_ICON_INPUT_DEVICE "gimp-input-device" +#define GIMP_ICON_MYPAINT_BRUSH GIMP_ICON_TOOL_MYPAINT_BRUSH +#define GIMP_ICON_PALETTE "gtk-select-color" +#define GIMP_ICON_PATTERN "gimp-pattern" +#define GIMP_ICON_PLUGIN "gimp-plugin" +#define GIMP_ICON_SAMPLE_POINT "gimp-sample-point" +#define GIMP_ICON_SYMMETRY "gimp-symmetry" +#define GIMP_ICON_TEMPLATE "gimp-template" +#define GIMP_ICON_TOOL_PRESET "gimp-tool-preset" + + +/* not really icons */ + +#define GIMP_ICON_FRAME "gimp-frame" +#define GIMP_ICON_TEXTURE "gimp-texture" + + +/* icons that follow, or at least try to follow the FDO naming and + * category conventions; and groups of icons with a common prefix; + * all sorted alphabetically + * + * see also: + * https://specifications.freedesktop.org/icon-naming-spec/latest/ar01s04.html + * + * some icons are marked with "use FDO", these shall be renamed in 3.0 + * because we duplicated FDO standard icon names + */ + +#define GIMP_ICON_APPLICATION_EXIT "application-exit" + +#define GIMP_ICON_ASPECT_PORTRAIT "gimp-portrait" +#define GIMP_ICON_ASPECT_LANDSCAPE "gimp-landscape" + +#define GIMP_ICON_CAP_BUTT "gimp-cap-butt" +#define GIMP_ICON_CAP_ROUND "gimp-cap-round" +#define GIMP_ICON_CAP_SQUARE "gimp-cap-square" + +#define GIMP_ICON_CENTER "gimp-center" +#define GIMP_ICON_CENTER_HORIZONTAL "gimp-hcenter" +#define GIMP_ICON_CENTER_VERTICAL "gimp-vcenter" + +#define GIMP_ICON_CHAIN_HORIZONTAL "gimp-hchain" +#define GIMP_ICON_CHAIN_HORIZONTAL_BROKEN "gimp-hchain-broken" +#define GIMP_ICON_CHAIN_VERTICAL "gimp-vchain" +#define GIMP_ICON_CHAIN_VERTICAL_BROKEN "gimp-vchain-broken" + +#define GIMP_ICON_CHANNEL "gimp-channel" +#define GIMP_ICON_CHANNEL_ALPHA "gimp-channel-alpha" +#define GIMP_ICON_CHANNEL_BLUE "gimp-channel-blue" +#define GIMP_ICON_CHANNEL_GRAY "gimp-channel-gray" +#define GIMP_ICON_CHANNEL_GREEN "gimp-channel-green" +#define GIMP_ICON_CHANNEL_INDEXED "gimp-channel-indexed" +#define GIMP_ICON_CHANNEL_RED "gimp-channel-red" + +#define GIMP_ICON_CLOSE "gimp-close" +#define GIMP_ICON_CLOSE_ALL "gimp-close-all" + +#define GIMP_ICON_COLOR_PICKER_BLACK "gimp-color-picker-black" +#define GIMP_ICON_COLOR_PICKER_GRAY "gimp-color-picker-gray" +#define GIMP_ICON_COLOR_PICKER_WHITE "gimp-color-picker-white" +#define GIMP_ICON_COLOR_PICK_FROM_SCREEN "gimp-color-pick-from-screen" + +#define GIMP_ICON_COLOR_SELECTOR_CMYK "gimp-color-cmyk" +#define GIMP_ICON_COLOR_SELECTOR_TRIANGLE "gimp-color-triangle" +#define GIMP_ICON_COLOR_SELECTOR_WATER "gimp-color-water" + +#define GIMP_ICON_COLOR_SPACE_LINEAR "gimp-color-space-linear" +#define GIMP_ICON_COLOR_SPACE_NON_LINEAR "gimp-color-space-non-linear" +#define GIMP_ICON_COLOR_SPACE_PERCEPTUAL "gimp-color-space-perceptual" + +#define GIMP_ICON_COLORS_DEFAULT "gimp-default-colors" +#define GIMP_ICON_COLORS_SWAP "gimp-swap-colors" + +#define GIMP_ICON_CONTROLLER "gimp-controller" +#define GIMP_ICON_CONTROLLER_KEYBOARD "gimp-controller-keyboard" +#define GIMP_ICON_CONTROLLER_LINUX_INPUT "gimp-controller-linux-input" +#define GIMP_ICON_CONTROLLER_MIDI "gimp-controller-midi" +#define GIMP_ICON_CONTROLLER_MOUSE GIMP_ICON_CURSOR +#define GIMP_ICON_CONTROLLER_WHEEL "gimp-controller-wheel" + +#define GIMP_ICON_CONVERT_RGB "gimp-convert-rgb" +#define GIMP_ICON_CONVERT_GRAYSCALE "gimp-convert-grayscale" +#define GIMP_ICON_CONVERT_INDEXED "gimp-convert-indexed" +#define GIMP_ICON_CONVERT_PRECISION GIMP_ICON_CONVERT_RGB + +#define GIMP_ICON_CURVE_FREE "gimp-curve-free" +#define GIMP_ICON_CURVE_SMOOTH "gimp-curve-smooth" + +#define GIMP_ICON_DIALOG_CHANNELS "gimp-channels" +#define GIMP_ICON_DIALOG_DASHBOARD "gimp-dashboard" +#define GIMP_ICON_DIALOG_DEVICE_STATUS "gimp-device-status" +#define GIMP_ICON_DIALOG_ERROR "gimp-error" /* use FDO */ +#define GIMP_ICON_DIALOG_IMAGES "gimp-images" +#define GIMP_ICON_DIALOG_INFORMATION "gimp-info" /* use FDO */ +#define GIMP_ICON_DIALOG_LAYERS "gimp-layers" +#define GIMP_ICON_DIALOG_NAVIGATION "gimp-navigation" +#define GIMP_ICON_DIALOG_PATHS "gimp-paths" +#define GIMP_ICON_DIALOG_QUESTION "gimp-question" /* use FDO */ +#define GIMP_ICON_DIALOG_RESHOW_FILTER "gimp-reshow-filter" +#define GIMP_ICON_DIALOG_TOOLS "gimp-tools" +#define GIMP_ICON_DIALOG_TOOL_OPTIONS "gimp-tool-options" +#define GIMP_ICON_DIALOG_UNDO_HISTORY "gimp-undo-history" +#define GIMP_ICON_DIALOG_WARNING "gimp-warning" /* use FDO */ + +#define GIMP_ICON_DISPLAY_FILTER "gimp-display-filter" +#define GIMP_ICON_DISPLAY_FILTER_CLIP_WARNING "gimp-display-filter-clip-warning" +#define GIMP_ICON_DISPLAY_FILTER_COLORBLIND "gimp-display-filter-colorblind" +#define GIMP_ICON_DISPLAY_FILTER_CONTRAST "gimp-display-filter-contrast" +#define GIMP_ICON_DISPLAY_FILTER_GAMMA "gimp-display-filter-gamma" +#define GIMP_ICON_DISPLAY_FILTER_LCMS "gimp-display-filter-lcms" +#define GIMP_ICON_DISPLAY_FILTER_PROOF "gimp-display-filter-proof" + +#define GIMP_ICON_DOCUMENT_NEW "document-new" +#define GIMP_ICON_DOCUMENT_OPEN "document-open" +#define GIMP_ICON_DOCUMENT_OPEN_RECENT "document-open-recent" +#define GIMP_ICON_DOCUMENT_PAGE_SETUP "document-page-setup" +#define GIMP_ICON_DOCUMENT_PRINT "document-print" +#define GIMP_ICON_DOCUMENT_PRINT_RESOLUTION "document-print" +#define GIMP_ICON_DOCUMENT_PROPERTIES "document-properties" +#define GIMP_ICON_DOCUMENT_REVERT "document-revert" +#define GIMP_ICON_DOCUMENT_SAVE "document-save" +#define GIMP_ICON_DOCUMENT_SAVE_AS "document-save-as" + +#define GIMP_ICON_EDIT "gtk-edit" +#define GIMP_ICON_EDIT_CLEAR "edit-clear" +#define GIMP_ICON_EDIT_COPY "edit-copy" +#define GIMP_ICON_EDIT_CUT "edit-cut" +#define GIMP_ICON_EDIT_DELETE "edit-delete" +#define GIMP_ICON_EDIT_FIND "edit-find" +#define GIMP_ICON_EDIT_PASTE "edit-paste" +#define GIMP_ICON_EDIT_PASTE_AS_NEW "gimp-paste-as-new" +#define GIMP_ICON_EDIT_PASTE_INTO "gimp-paste-into" +#define GIMP_ICON_EDIT_REDO "edit-redo" +#define GIMP_ICON_EDIT_UNDO "edit-undo" + +#define GIMP_ICON_FILL_HORIZONTAL "gimp-hfill" +#define GIMP_ICON_FILL_VERTICAL "gimp-vfill" + +#define GIMP_ICON_FOLDER_NEW "folder-new" + +#define GIMP_ICON_FORMAT_INDENT_MORE "format-indent-more" +#define GIMP_ICON_FORMAT_INDENT_LESS "format-indent-less" +#define GIMP_ICON_FORMAT_JUSTIFY_CENTER "format-justify-center" +#define GIMP_ICON_FORMAT_JUSTIFY_FILL "format-justify-fill" +#define GIMP_ICON_FORMAT_JUSTIFY_LEFT "format-justify-left" +#define GIMP_ICON_FORMAT_JUSTIFY_RIGHT "format-justify-right" +#define GIMP_ICON_FORMAT_TEXT_BOLD "format-text-bold" +#define GIMP_ICON_FORMAT_TEXT_ITALIC "format-text-italic" +#define GIMP_ICON_FORMAT_TEXT_STRIKETHROUGH "format-text-strikethrough" +#define GIMP_ICON_FORMAT_TEXT_UNDERLINE "format-text-underline" +#define GIMP_ICON_FORMAT_TEXT_DIRECTION_LTR "gimp-text-dir-ltr" /* use FDO */ +#define GIMP_ICON_FORMAT_TEXT_DIRECTION_RTL "gimp-text-dir-rtl" /* use FDO */ +#define GIMP_ICON_FORMAT_TEXT_DIRECTION_TTB_RTL "gimp-text-dir-ttb-rtl" /* use FDO */ +#define GIMP_ICON_FORMAT_TEXT_DIRECTION_TTB_RTL_UPRIGHT "gimp-text-dir-ttb-rtl-upright" /* use FDO */ +#define GIMP_ICON_FORMAT_TEXT_DIRECTION_TTB_LTR "gimp-text-dir-ttb-ltr" /* use FDO */ +#define GIMP_ICON_FORMAT_TEXT_DIRECTION_TTB_LTR_UPRIGHT "gimp-text-dir-ttb-ltr-upright" /* use FDO */ +#define GIMP_ICON_FORMAT_TEXT_SPACING_LETTER "gimp-letter-spacing" +#define GIMP_ICON_FORMAT_TEXT_SPACING_LINE "gimp-line-spacing" + +#define GIMP_ICON_GRADIENT_LINEAR "gimp-gradient-linear" +#define GIMP_ICON_GRADIENT_BILINEAR "gimp-gradient-bilinear" +#define GIMP_ICON_GRADIENT_RADIAL "gimp-gradient-radial" +#define GIMP_ICON_GRADIENT_SQUARE "gimp-gradient-square" +#define GIMP_ICON_GRADIENT_CONICAL_SYMMETRIC "gimp-gradient-conical-symmetric" +#define GIMP_ICON_GRADIENT_CONICAL_ASYMMETRIC "gimp-gradient-conical-asymmetric" +#define GIMP_ICON_GRADIENT_SHAPEBURST_ANGULAR "gimp-gradient-shapeburst-angular" +#define GIMP_ICON_GRADIENT_SHAPEBURST_SPHERICAL "gimp-gradient-shapeburst-spherical" +#define GIMP_ICON_GRADIENT_SHAPEBURST_DIMPLED "gimp-gradient-shapeburst-dimpled" +#define GIMP_ICON_GRADIENT_SPIRAL_CLOCKWISE "gimp-gradient-spiral-clockwise" +#define GIMP_ICON_GRADIENT_SPIRAL_ANTICLOCKWISE "gimp-gradient-spiral-anticlockwise" + +#define GIMP_ICON_GRAVITY_EAST "gimp-gravity-east" +#define GIMP_ICON_GRAVITY_NORTH "gimp-gravity-north" +#define GIMP_ICON_GRAVITY_NORTH_EAST "gimp-gravity-north-east" +#define GIMP_ICON_GRAVITY_NORTH_WEST "gimp-gravity-north-west" +#define GIMP_ICON_GRAVITY_SOUTH "gimp-gravity-south" +#define GIMP_ICON_GRAVITY_SOUTH_EAST "gimp-gravity-south-east" +#define GIMP_ICON_GRAVITY_SOUTH_WEST "gimp-gravity-south-west" +#define GIMP_ICON_GRAVITY_WEST "gimp-gravity-west" + +#define GIMP_ICON_GO_BOTTOM "go-bottom" +#define GIMP_ICON_GO_DOWN "go-down" +#define GIMP_ICON_GO_FIRST "go-first" +#define GIMP_ICON_GO_HOME "go-home" +#define GIMP_ICON_GO_LAST "go-last" +#define GIMP_ICON_GO_TOP "go-top" +#define GIMP_ICON_GO_UP "go-up" +#define GIMP_ICON_GO_PREVIOUS "go-previous" +#define GIMP_ICON_GO_NEXT "go-next" + +#define GIMP_ICON_HELP "help" +#define GIMP_ICON_HELP_ABOUT "help-about" +#define GIMP_ICON_HELP_USER_MANUAL "gimp-user-manual" + +#define GIMP_ICON_HISTOGRAM "gimp-histogram" +#define GIMP_ICON_HISTOGRAM_LINEAR "gimp-histogram-linear" +#define GIMP_ICON_HISTOGRAM_LOGARITHMIC "gimp-histogram-logarithmic" + +#define GIMP_ICON_IMAGE "gimp-image" +#define GIMP_ICON_IMAGE_OPEN "gimp-image-open" +#define GIMP_ICON_IMAGE_RELOAD "gimp-image-reload" + +#define GIMP_ICON_JOIN_MITER "gimp-join-miter" +#define GIMP_ICON_JOIN_ROUND "gimp-join-round" +#define GIMP_ICON_JOIN_BEVEL "gimp-join-bevel" + +#define GIMP_ICON_LAYER "gimp-layer" +#define GIMP_ICON_LAYER_ANCHOR "gimp-anchor" +#define GIMP_ICON_LAYER_FLOATING_SELECTION "gimp-floating-selection" +#define GIMP_ICON_LAYER_MASK "gimp-layer-mask" +#define GIMP_ICON_LAYER_MERGE_DOWN "gimp-merge-down" +#define GIMP_ICON_LAYER_TEXT_LAYER "gimp-text-layer" +#define GIMP_ICON_LAYER_TO_IMAGESIZE "gimp-layer-to-imagesize" + +#define GIMP_ICON_LIST "gimp-list" +#define GIMP_ICON_LIST_ADD "list-add" +#define GIMP_ICON_LIST_REMOVE "list-remove" + +#define GIMP_ICON_MENU_LEFT "gimp-menu-left" +#define GIMP_ICON_MENU_RIGHT "gimp-menu-right" + +#define GIMP_ICON_OBJECT_DUPLICATE "gimp-duplicate" +#define GIMP_ICON_OBJECT_FLIP_HORIZONTAL "gimp-flip-horizontal" /* use FDO */ +#define GIMP_ICON_OBJECT_FLIP_VERTICAL "gimp-flip-vertical" /* use FDO */ +#define GIMP_ICON_OBJECT_RESIZE "gimp-resize" +#define GIMP_ICON_OBJECT_ROTATE_180 "gimp-rotate-180" +#define GIMP_ICON_OBJECT_ROTATE_270 "gimp-rotate-270" /* use FDO */ +#define GIMP_ICON_OBJECT_ROTATE_90 "gimp-rotate-90" /* use FDO */ +#define GIMP_ICON_OBJECT_SCALE "gimp-scale" + +#define GIMP_ICON_PATH "gimp-path" +#define GIMP_ICON_PATH_STROKE "gimp-path-stroke" + +#define GIMP_ICON_PIVOT_CENTER "gimp-pivot-center" +#define GIMP_ICON_PIVOT_EAST "gimp-pivot-east" +#define GIMP_ICON_PIVOT_NORTH "gimp-pivot-north" +#define GIMP_ICON_PIVOT_NORTH_EAST "gimp-pivot-north-east" +#define GIMP_ICON_PIVOT_NORTH_WEST "gimp-pivot-north-west" +#define GIMP_ICON_PIVOT_SOUTH "gimp-pivot-south" +#define GIMP_ICON_PIVOT_SOUTH_EAST "gimp-pivot-south-east" +#define GIMP_ICON_PIVOT_SOUTH_WEST "gimp-pivot-south-west" +#define GIMP_ICON_PIVOT_WEST "gimp-pivot-west" + +#define GIMP_ICON_PREFERENCES_SYSTEM "preferences-system" + +#define GIMP_ICON_PROCESS_STOP "process-stop" + +#define GIMP_ICON_QUICK_MASK_OFF "gimp-quick-mask-off" +#define GIMP_ICON_QUICK_MASK_ON "gimp-quick-mask-on" + +#define GIMP_ICON_SELECTION "gimp-selection" +#define GIMP_ICON_SELECTION_ADD "gimp-selection-add" +#define GIMP_ICON_SELECTION_ALL "gimp-selection-all" +#define GIMP_ICON_SELECTION_BORDER "gimp-selection-border" +#define GIMP_ICON_SELECTION_GROW "gimp-selection-grow" +#define GIMP_ICON_SELECTION_INTERSECT "gimp-selection-intersect" +#define GIMP_ICON_SELECTION_NONE "gimp-selection-none" +#define GIMP_ICON_SELECTION_REPLACE "gimp-selection-replace" +#define GIMP_ICON_SELECTION_SHRINK "gimp-selection-shrink" +#define GIMP_ICON_SELECTION_STROKE "gimp-selection-stroke" +#define GIMP_ICON_SELECTION_SUBTRACT "gimp-selection-subtract" +#define GIMP_ICON_SELECTION_TO_CHANNEL "gimp-selection-to-channel" +#define GIMP_ICON_SELECTION_TO_PATH "gimp-selection-to-path" + +#define GIMP_ICON_SHAPE_CIRCLE "gimp-shape-circle" +#define GIMP_ICON_SHAPE_DIAMOND "gimp-shape-diamond" +#define GIMP_ICON_SHAPE_SQUARE "gimp-shape-square" + +#define GIMP_ICON_SYSTEM_RUN "system-run" + +#define GIMP_ICON_TOOL_AIRBRUSH "gimp-tool-airbrush" +#define GIMP_ICON_TOOL_ALIGN "gimp-tool-align" +#define GIMP_ICON_TOOL_BLUR "gimp-tool-blur" +#define GIMP_ICON_TOOL_BRIGHTNESS_CONTRAST "gimp-tool-brightness-contrast" +#define GIMP_ICON_TOOL_BUCKET_FILL "gimp-tool-bucket-fill" +#define GIMP_ICON_TOOL_BY_COLOR_SELECT "gimp-tool-by-color-select" +#define GIMP_ICON_TOOL_CAGE "gimp-tool-cage" +#define GIMP_ICON_TOOL_CLONE "gimp-tool-clone" +#define GIMP_ICON_TOOL_COLORIZE "gimp-tool-colorize" +#define GIMP_ICON_TOOL_COLOR_BALANCE "gimp-tool-color-balance" +#define GIMP_ICON_TOOL_COLOR_PICKER "gimp-tool-color-picker" +#define GIMP_ICON_TOOL_COLOR_TEMPERATURE "gimp-tool-color-temperature" +#define GIMP_ICON_TOOL_CROP "gimp-tool-crop" +#define GIMP_ICON_TOOL_CURVES "gimp-tool-curves" +#define GIMP_ICON_TOOL_DESATURATE "gimp-tool-desaturate" +#define GIMP_ICON_TOOL_DODGE "gimp-tool-dodge" +#define GIMP_ICON_TOOL_ELLIPSE_SELECT "gimp-tool-ellipse-select" +#define GIMP_ICON_TOOL_ERASER "gimp-tool-eraser" +#define GIMP_ICON_TOOL_EXPOSURE "gimp-tool-exposure" +#define GIMP_ICON_TOOL_FLIP "gimp-tool-flip" +#define GIMP_ICON_TOOL_FOREGROUND_SELECT "gimp-tool-foreground-select" +#define GIMP_ICON_TOOL_FREE_SELECT "gimp-tool-free-select" +#define GIMP_ICON_TOOL_FUZZY_SELECT "gimp-tool-fuzzy-select" +#define GIMP_ICON_TOOL_GRADIENT "gimp-tool-gradient" +#define GIMP_ICON_TOOL_HANDLE_TRANSFORM "gimp-tool-handle-transform" +#define GIMP_ICON_TOOL_HEAL "gimp-tool-heal" +#define GIMP_ICON_TOOL_HUE_SATURATION "gimp-tool-hue-saturation" +#define GIMP_ICON_TOOL_INK "gimp-tool-ink" +#define GIMP_ICON_TOOL_ISCISSORS "gimp-tool-iscissors" +#define GIMP_ICON_TOOL_LEVELS "gimp-tool-levels" +#define GIMP_ICON_TOOL_MEASURE "gimp-tool-measure" +#define GIMP_ICON_TOOL_MOVE "gimp-tool-move" +#define GIMP_ICON_TOOL_MYPAINT_BRUSH "gimp-tool-mypaint-brush" +#define GIMP_ICON_TOOL_N_POINT_DEFORMATION "gimp-tool-n-point-deformation" +#define GIMP_ICON_TOOL_OFFSET "gimp-tool-offset" +#define GIMP_ICON_TOOL_PAINTBRUSH "gimp-tool-paintbrush" +#define GIMP_ICON_TOOL_PATH "gimp-tool-path" +#define GIMP_ICON_TOOL_PENCIL "gimp-tool-pencil" +#define GIMP_ICON_TOOL_PERSPECTIVE "gimp-tool-perspective" +#define GIMP_ICON_TOOL_PERSPECTIVE_CLONE "gimp-tool-perspective-clone" +#define GIMP_ICON_TOOL_POSTERIZE "gimp-tool-posterize" +#define GIMP_ICON_TOOL_RECT_SELECT "gimp-tool-rect-select" +#define GIMP_ICON_TOOL_ROTATE "gimp-tool-rotate" +#define GIMP_ICON_TOOL_SCALE "gimp-tool-scale" +#define GIMP_ICON_TOOL_SEAMLESS_CLONE "gimp-tool-seamless-clone" +#define GIMP_ICON_TOOL_SHADOWS_HIGHLIGHTS "gimp-tool-shadows-highlights" +#define GIMP_ICON_TOOL_SHEAR "gimp-tool-shear" +#define GIMP_ICON_TOOL_SMUDGE "gimp-tool-smudge" +#define GIMP_ICON_TOOL_TEXT "gimp-tool-text" +#define GIMP_ICON_TOOL_THRESHOLD "gimp-tool-threshold" +#define GIMP_ICON_TOOL_TRANSFORM_3D "gimp-tool-transform-3d" +#define GIMP_ICON_TOOL_UNIFIED_TRANSFORM "gimp-tool-unified-transform" +#define GIMP_ICON_TOOL_WARP "gimp-tool-warp" +#define GIMP_ICON_TOOL_ZOOM "gimp-tool-zoom" + +#define GIMP_ICON_TRANSFORM_3D_CAMERA "gimp-transform-3d-camera" +#define GIMP_ICON_TRANSFORM_3D_MOVE "gimp-transform-3d-move" +#define GIMP_ICON_TRANSFORM_3D_ROTATE "gimp-transform-3d-rotate" + +#define GIMP_ICON_VIEW_REFRESH "view-refresh" +#define GIMP_ICON_VIEW_FULLSCREEN "view-fullscreen" + +#define GIMP_ICON_WILBER "gimp-wilber" +#define GIMP_ICON_WILBER_EEK "gimp-wilber-eek" + +#define GIMP_ICON_WINDOW_CLOSE "window-close" +#define GIMP_ICON_WINDOW_MOVE_TO_SCREEN "gimp-move-to-screen" +#define GIMP_ICON_WINDOW_NEW "window-new" + +#define GIMP_ICON_ZOOM_IN "zoom-in" +#define GIMP_ICON_ZOOM_ORIGINAL "zoom-original" +#define GIMP_ICON_ZOOM_OUT "zoom-out" +#define GIMP_ICON_ZOOM_FIT_BEST "zoom-fit-best" +#define GIMP_ICON_ZOOM_FOLLOW_WINDOW "gimp-zoom-follow-window" + + +#ifndef GIMP_DISABLE_DEPRECATED + +/* in button size: */ + +#define GIMP_STOCK_ANCHOR "gimp-anchor" +#define GIMP_STOCK_CENTER "gimp-center" +#define GIMP_STOCK_DUPLICATE "gimp-duplicate" +#define GIMP_STOCK_LINKED "gimp-linked" +#define GIMP_STOCK_PASTE_AS_NEW "gimp-paste-as-new" +#define GIMP_STOCK_PASTE_INTO "gimp-paste-into" +#define GIMP_STOCK_RESET "gimp-reset" +#define GIMP_STOCK_VISIBLE "gimp-visible" + +#define GIMP_STOCK_GRADIENT_LINEAR "gimp-gradient-linear" +#define GIMP_STOCK_GRADIENT_BILINEAR "gimp-gradient-bilinear" +#define GIMP_STOCK_GRADIENT_RADIAL "gimp-gradient-radial" +#define GIMP_STOCK_GRADIENT_SQUARE "gimp-gradient-square" +#define GIMP_STOCK_GRADIENT_CONICAL_SYMMETRIC "gimp-gradient-conical-symmetric" +#define GIMP_STOCK_GRADIENT_CONICAL_ASYMMETRIC "gimp-gradient-conical-asymmetric" +#define GIMP_STOCK_GRADIENT_SHAPEBURST_ANGULAR "gimp-gradient-shapeburst-angular" +#define GIMP_STOCK_GRADIENT_SHAPEBURST_SPHERICAL "gimp-gradient-shapeburst-spherical" +#define GIMP_STOCK_GRADIENT_SHAPEBURST_DIMPLED "gimp-gradient-shapeburst-dimpled" +#define GIMP_STOCK_GRADIENT_SPIRAL_CLOCKWISE "gimp-gradient-spiral-clockwise" +#define GIMP_STOCK_GRADIENT_SPIRAL_ANTICLOCKWISE "gimp-gradient-spiral-anticlockwise" + +#define GIMP_STOCK_GRAVITY_EAST "gimp-gravity-east" +#define GIMP_STOCK_GRAVITY_NORTH "gimp-gravity-north" +#define GIMP_STOCK_GRAVITY_NORTH_EAST "gimp-gravity-north-east" +#define GIMP_STOCK_GRAVITY_NORTH_WEST "gimp-gravity-north-west" +#define GIMP_STOCK_GRAVITY_SOUTH "gimp-gravity-south" +#define GIMP_STOCK_GRAVITY_SOUTH_EAST "gimp-gravity-south-east" +#define GIMP_STOCK_GRAVITY_SOUTH_WEST "gimp-gravity-south-west" +#define GIMP_STOCK_GRAVITY_WEST "gimp-gravity-west" + +#define GIMP_STOCK_HCENTER "gimp-hcenter" +#define GIMP_STOCK_VCENTER "gimp-vcenter" + +#define GIMP_STOCK_HCHAIN "gimp-hchain" +#define GIMP_STOCK_HCHAIN_BROKEN "gimp-hchain-broken" +#define GIMP_STOCK_VCHAIN "gimp-vchain" +#define GIMP_STOCK_VCHAIN_BROKEN "gimp-vchain-broken" + +#define GIMP_STOCK_SELECTION "gimp-selection" +#define GIMP_STOCK_SELECTION_REPLACE "gimp-selection-replace" +#define GIMP_STOCK_SELECTION_ADD "gimp-selection-add" +#define GIMP_STOCK_SELECTION_SUBTRACT "gimp-selection-subtract" +#define GIMP_STOCK_SELECTION_INTERSECT "gimp-selection-intersect" +#define GIMP_STOCK_SELECTION_STROKE "gimp-selection-stroke" +#define GIMP_STOCK_SELECTION_TO_CHANNEL "gimp-selection-to-channel" +#define GIMP_STOCK_SELECTION_TO_PATH "gimp-selection-to-path" + +#define GIMP_STOCK_PATH_STROKE "gimp-path-stroke" + +#define GIMP_STOCK_CURVE_FREE "gimp-curve-free" +#define GIMP_STOCK_CURVE_SMOOTH "gimp-curve-smooth" + +#define GIMP_STOCK_COLOR_PICKER_BLACK "gimp-color-picker-black" +#define GIMP_STOCK_COLOR_PICKER_GRAY "gimp-color-picker-gray" +#define GIMP_STOCK_COLOR_PICKER_WHITE "gimp-color-picker-white" +#define GIMP_STOCK_COLOR_TRIANGLE "gimp-color-triangle" +#define GIMP_STOCK_COLOR_PICK_FROM_SCREEN "gimp-color-pick-from-screen" + +#define GIMP_STOCK_CHAR_PICKER "gimp-char-picker" +#define GIMP_STOCK_LETTER_SPACING "gimp-letter-spacing" +#define GIMP_STOCK_LINE_SPACING "gimp-line-spacing" +#define GIMP_STOCK_PATTERN "gimp-pattern" + +#define GIMP_STOCK_TEXT_DIR_LTR "gimp-text-dir-ltr" +#define GIMP_STOCK_TEXT_DIR_RTL "gimp-text-dir-rtl" + +#define GIMP_STOCK_TOOL_AIRBRUSH "gimp-tool-airbrush" +#define GIMP_STOCK_TOOL_ALIGN "gimp-tool-align" +#define GIMP_STOCK_TOOL_BLEND "gimp-tool-gradient" +#define GIMP_STOCK_TOOL_BLUR "gimp-tool-blur" +#define GIMP_STOCK_TOOL_BRIGHTNESS_CONTRAST "gimp-tool-brightness-contrast" +#define GIMP_STOCK_TOOL_BUCKET_FILL "gimp-tool-bucket-fill" +#define GIMP_STOCK_TOOL_BY_COLOR_SELECT "gimp-tool-by-color-select" +#define GIMP_STOCK_TOOL_CAGE "gimp-tool-cage" +#define GIMP_STOCK_TOOL_CLONE "gimp-tool-clone" +#define GIMP_STOCK_TOOL_COLOR_BALANCE "gimp-tool-color-balance" +#define GIMP_STOCK_TOOL_COLOR_PICKER "gimp-tool-color-picker" +#define GIMP_STOCK_TOOL_COLORIZE "gimp-tool-colorize" +#define GIMP_STOCK_TOOL_CROP "gimp-tool-crop" +#define GIMP_STOCK_TOOL_CURVES "gimp-tool-curves" +#define GIMP_STOCK_TOOL_DESATURATE "gimp-tool-desaturate" +#define GIMP_STOCK_TOOL_DODGE "gimp-tool-dodge" +#define GIMP_STOCK_TOOL_ELLIPSE_SELECT "gimp-tool-ellipse-select" +#define GIMP_STOCK_TOOL_ERASER "gimp-tool-eraser" +#define GIMP_STOCK_TOOL_FLIP "gimp-tool-flip" +#define GIMP_STOCK_TOOL_FREE_SELECT "gimp-tool-free-select" +#define GIMP_STOCK_TOOL_FOREGROUND_SELECT "gimp-tool-foreground-select" +#define GIMP_STOCK_TOOL_FUZZY_SELECT "gimp-tool-fuzzy-select" +#define GIMP_STOCK_TOOL_HEAL "gimp-tool-heal" +#define GIMP_STOCK_TOOL_HUE_SATURATION "gimp-tool-hue-saturation" +#define GIMP_STOCK_TOOL_INK "gimp-tool-ink" +#define GIMP_STOCK_TOOL_ISCISSORS "gimp-tool-iscissors" +#define GIMP_STOCK_TOOL_LEVELS "gimp-tool-levels" +#define GIMP_STOCK_TOOL_MEASURE "gimp-tool-measure" +#define GIMP_STOCK_TOOL_MOVE "gimp-tool-move" +#define GIMP_STOCK_TOOL_PAINTBRUSH "gimp-tool-paintbrush" +#define GIMP_STOCK_TOOL_PATH "gimp-tool-path" +#define GIMP_STOCK_TOOL_PENCIL "gimp-tool-pencil" +#define GIMP_STOCK_TOOL_PERSPECTIVE "gimp-tool-perspective" +#define GIMP_STOCK_TOOL_PERSPECTIVE_CLONE "gimp-tool-perspective-clone" +#define GIMP_STOCK_TOOL_POSTERIZE "gimp-tool-posterize" +#define GIMP_STOCK_TOOL_RECT_SELECT "gimp-tool-rect-select" +#define GIMP_STOCK_TOOL_ROTATE "gimp-tool-rotate" +#define GIMP_STOCK_TOOL_SCALE "gimp-tool-scale" +#define GIMP_STOCK_TOOL_SHEAR "gimp-tool-shear" +#define GIMP_STOCK_TOOL_SMUDGE "gimp-tool-smudge" +#define GIMP_STOCK_TOOL_TEXT "gimp-tool-text" +#define GIMP_STOCK_TOOL_THRESHOLD "gimp-tool-threshold" +#define GIMP_STOCK_TOOL_ZOOM "gimp-tool-zoom" + +/* in menu size: */ + +#define GIMP_STOCK_CONVERT_RGB "gimp-convert-rgb" +#define GIMP_STOCK_CONVERT_GRAYSCALE "gimp-convert-grayscale" +#define GIMP_STOCK_CONVERT_INDEXED "gimp-convert-indexed" +#define GIMP_STOCK_INVERT "gimp-invert" +#define GIMP_STOCK_MERGE_DOWN "gimp-merge-down" +#define GIMP_STOCK_LAYER_TO_IMAGESIZE "gimp-layer-to-imagesize" +#define GIMP_STOCK_PLUGIN "gimp-plugin" +#define GIMP_STOCK_UNDO_HISTORY "gimp-undo-history" +#define GIMP_STOCK_RESHOW_FILTER "gimp-reshow-filter" +#define GIMP_STOCK_ROTATE_90 "gimp-rotate-90" +#define GIMP_STOCK_ROTATE_180 "gimp-rotate-180" +#define GIMP_STOCK_ROTATE_270 "gimp-rotate-270" +#define GIMP_STOCK_RESIZE "gimp-resize" +#define GIMP_STOCK_SCALE "gimp-scale" +#define GIMP_STOCK_FLIP_HORIZONTAL "gimp-flip-horizontal" +#define GIMP_STOCK_FLIP_VERTICAL "gimp-flip-vertical" + +#define GIMP_STOCK_IMAGE "gimp-image" +#define GIMP_STOCK_LAYER "gimp-layer" +#define GIMP_STOCK_TEXT_LAYER "gimp-text-layer" +#define GIMP_STOCK_FLOATING_SELECTION "gimp-floating-selection" +#define GIMP_STOCK_CHANNEL "gimp-channel" +#define GIMP_STOCK_CHANNEL_RED "gimp-channel-red" +#define GIMP_STOCK_CHANNEL_GREEN "gimp-channel-green" +#define GIMP_STOCK_CHANNEL_BLUE "gimp-channel-blue" +#define GIMP_STOCK_CHANNEL_GRAY "gimp-channel-gray" +#define GIMP_STOCK_CHANNEL_INDEXED "gimp-channel-indexed" +#define GIMP_STOCK_CHANNEL_ALPHA "gimp-channel-alpha" +#define GIMP_STOCK_LAYER_MASK "gimp-layer-mask" +#define GIMP_STOCK_PATH "gimp-path" +#define GIMP_STOCK_TEMPLATE "gimp-template" +#define GIMP_STOCK_TRANSPARENCY "gimp-transparency" +#define GIMP_STOCK_COLORMAP "gimp-colormap" + +#define GIMP_STOCK_INDEXED_PALETTE "gimp-colormap" + +#define GIMP_STOCK_IMAGES "gimp-images" +#define GIMP_STOCK_LAYERS "gimp-layers" +#define GIMP_STOCK_CHANNELS "gimp-channels" +#define GIMP_STOCK_PATHS "gimp-paths" + +#define GIMP_STOCK_SELECTION_ALL "gimp-selection-all" +#define GIMP_STOCK_SELECTION_NONE "gimp-selection-none" +#define GIMP_STOCK_SELECTION_GROW "gimp-selection-grow" +#define GIMP_STOCK_SELECTION_SHRINK "gimp-selection-shrink" +#define GIMP_STOCK_SELECTION_BORDER "gimp-selection-border" + +#define GIMP_STOCK_NAVIGATION "gimp-navigation" +#define GIMP_STOCK_QUICK_MASK_OFF "gimp-quick-mask-off" +#define GIMP_STOCK_QUICK_MASK_ON "gimp-quick-mask-on" + +#define GIMP_STOCK_QMASK_OFF "gimp-quick-mask-off" +#define GIMP_STOCK_QMASK_ON "gimp-quick-mask-on" + +#define GIMP_STOCK_HISTOGRAM "gimp-histogram" +#define GIMP_STOCK_HISTOGRAM_LINEAR "gimp-histogram-linear" +#define GIMP_STOCK_HISTOGRAM_LOGARITHMIC "gimp-histogram-logarithmic" + +#define GIMP_STOCK_CLOSE "gimp-close" +#define GIMP_STOCK_MENU_LEFT "gimp-menu-left" +#define GIMP_STOCK_MENU_RIGHT "gimp-menu-right" +#define GIMP_STOCK_MOVE_TO_SCREEN "gimp-move-to-screen" +#define GIMP_STOCK_DEFAULT_COLORS "gimp-default-colors" +#define GIMP_STOCK_SWAP_COLORS "gimp-swap-colors" +#define GIMP_STOCK_ZOOM_FOLLOW_WINDOW "gimp-zoom-follow-window" + +#define GIMP_STOCK_TOOLS "gimp-tools" +#define GIMP_STOCK_TOOL_OPTIONS "gimp-tool-options" +#define GIMP_STOCK_DEVICE_STATUS "gimp-device-status" +#define GIMP_STOCK_INPUT_DEVICE "gimp-input-device" +#define GIMP_STOCK_CURSOR "gimp-cursor" +#define GIMP_STOCK_SAMPLE_POINT "gimp-sample-point" +#define GIMP_STOCK_DYNAMICS "gimp-dynamics" +#define GIMP_STOCK_TOOL_PRESET "gimp-tool-preset" + +#define GIMP_STOCK_CONTROLLER "gimp-controller" +#define GIMP_STOCK_CONTROLLER_KEYBOARD "gimp-controller-keyboard" +#define GIMP_STOCK_CONTROLLER_LINUX_INPUT "gimp-controller-linux-input" +#define GIMP_STOCK_CONTROLLER_MIDI "gimp-controller-midi" +#define GIMP_STOCK_CONTROLLER_WHEEL "gimp-controller-wheel" + +#define GIMP_STOCK_DISPLAY_FILTER "gimp-display-filter" +#define GIMP_STOCK_DISPLAY_FILTER_COLORBLIND "gimp-display-filter-colorblind" +#define GIMP_STOCK_DISPLAY_FILTER_CONTRAST "gimp-display-filter-contrast" +#define GIMP_STOCK_DISPLAY_FILTER_GAMMA "gimp-display-filter-gamma" +#define GIMP_STOCK_DISPLAY_FILTER_LCMS "gimp-display-filter-lcms" +#define GIMP_STOCK_DISPLAY_FILTER_PROOF "gimp-display-filter-proof" + +#define GIMP_STOCK_LIST "gimp-list" +#define GIMP_STOCK_GRID "gimp-grid" + +#define GIMP_STOCK_PORTRAIT "gimp-portrait" +#define GIMP_STOCK_LANDSCAPE "gimp-landscape" + +#define GIMP_STOCK_GEGL "gimp-gegl" +#define GIMP_STOCK_VIDEO "gimp-video" +#define GIMP_STOCK_WEB "gimp-web" + +#define GIMP_STOCK_SHAPE_CIRCLE "gimp-shape-circle" +#define GIMP_STOCK_SHAPE_DIAMOND "gimp-shape-diamond" +#define GIMP_STOCK_SHAPE_SQUARE "gimp-shape-square" + +#define GIMP_STOCK_CAP_BUTT "gimp-cap-butt" +#define GIMP_STOCK_CAP_ROUND "gimp-cap-round" +#define GIMP_STOCK_CAP_SQUARE "gimp-cap-square" + +#define GIMP_STOCK_JOIN_MITER "gimp-join-miter" +#define GIMP_STOCK_JOIN_ROUND "gimp-join-round" +#define GIMP_STOCK_JOIN_BEVEL "gimp-join-bevel" + +/* in dialog size: */ + +#define GIMP_STOCK_ERROR "gimp-error" +#define GIMP_STOCK_INFO "gimp-info" +#define GIMP_STOCK_QUESTION "gimp-question" +#define GIMP_STOCK_WARNING "gimp-warning" +#define GIMP_STOCK_WILBER "gimp-wilber" +#define GIMP_STOCK_WILBER_EEK "gimp-wilber-eek" +#define GIMP_STOCK_FRAME "gimp-frame" +#define GIMP_STOCK_TEXTURE "gimp-texture" +#define GIMP_STOCK_USER_MANUAL "gimp-user-manual" + +/* missing icons: */ + +#define GIMP_STOCK_BRUSH GIMP_STOCK_TOOL_PAINTBRUSH +#define GIMP_STOCK_BUFFER "edit-paste" +#define GIMP_STOCK_DETACH GTK_STOCK_CONVERT +#define GIMP_STOCK_FONT GTK_STOCK_SELECT_FONT +#define GIMP_STOCK_GRADIENT GIMP_STOCK_TOOL_BLEND +#define GIMP_STOCK_PALETTE GTK_STOCK_SELECT_COLOR +#define GIMP_STOCK_CONTROLLER_MOUSE GIMP_STOCK_CURSOR +#define GIMP_STOCK_PRINT_RESOLUTION "document-print" + +#define GIMP_STOCK_EDIT "gtk-edit" + +#endif /* GIMP_DISABLE_DEPRECATED */ + + +GIMP_DEPRECATED_FOR(gimp_icons_init) +void gimp_stock_init (void); + +void gimp_icons_init (void); + +void gimp_icons_set_icon_theme (GFile *path); + + +G_END_DECLS + +#endif /* __GIMP_ICONS_H__ */ diff --git a/libgimpwidgets/gimpintcombobox.c b/libgimpwidgets/gimpintcombobox.c new file mode 100644 index 0000000..6cd8909 --- /dev/null +++ b/libgimpwidgets/gimpintcombobox.c @@ -0,0 +1,978 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpintcombobox.c + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <libintl.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpintcombobox.h" +#include "gimpintstore.h" + + +/** + * SECTION: gimpintcombobox + * @title: GimpIntComboBox + * @short_description: A widget providing a popup menu of integer + * values (e.g. enums). + * + * A widget providing a popup menu of integer values (e.g. enums). + **/ + + +enum +{ + PROP_0, + PROP_ELLIPSIZE, + PROP_LABEL, + PROP_LAYOUT +}; + + +typedef struct +{ + GtkCellRenderer *pixbuf_renderer; + GtkCellRenderer *text_renderer; + + GtkCellRenderer *menu_pixbuf_renderer; + GtkCellRenderer *menu_text_renderer; + + PangoEllipsizeMode ellipsize; + gchar *label; + GtkCellRenderer *label_renderer; + GimpIntComboBoxLayout layout; + + GimpIntSensitivityFunc sensitivity_func; + gpointer sensitivity_data; + GDestroyNotify sensitivity_destroy; +} GimpIntComboBoxPrivate; + +#define GIMP_INT_COMBO_BOX_GET_PRIVATE(obj) \ + ((GimpIntComboBoxPrivate *) ((GimpIntComboBox *) (obj))->priv) + + +static void gimp_int_combo_box_finalize (GObject *object); +static void gimp_int_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_int_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_int_combo_box_create_cells (GimpIntComboBox *combo_box); +static void gimp_int_combo_box_data_func (GtkCellLayout *layout, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpIntComboBox, gimp_int_combo_box, + GTK_TYPE_COMBO_BOX) + +#define parent_class gimp_int_combo_box_parent_class + + +static void +gimp_int_combo_box_class_init (GimpIntComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_int_combo_box_finalize; + object_class->set_property = gimp_int_combo_box_set_property; + object_class->get_property = gimp_int_combo_box_get_property; + + /** + * GimpIntComboBox:ellipsize: + * + * Specifies the preferred place to ellipsize text in the combo-box, + * if the cell renderer does not have enough room to display the + * entire string. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, PROP_ELLIPSIZE, + g_param_spec_enum ("ellipsize", + "Ellipsize", + "Ellipsize mode for the used text cell renderer", + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + GIMP_PARAM_READWRITE)); + + /** + * GimpIntComboBox:label: + * + * Sets a label on the combo-box, see gimp_int_combo_box_set_label(). + * + * Since: 2.10 + */ + g_object_class_install_property (object_class, PROP_LABEL, + g_param_spec_string ("label", + "Label", + "An optional label to be displayed", + NULL, + GIMP_PARAM_READWRITE)); + + /** + * GimpIntComboBox:layout: + * + * Specifies the combo box layout. + * + * Since: 2.10 + */ + g_object_class_install_property (object_class, PROP_LAYOUT, + g_param_spec_enum ("layout", + "Layout", + "Combo box layout", + GIMP_TYPE_INT_COMBO_BOX_LAYOUT, + GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_int_combo_box_init (GimpIntComboBox *combo_box) +{ + GimpIntComboBoxPrivate *priv; + GtkListStore *store; + + combo_box->priv = priv = gimp_int_combo_box_get_instance_private (combo_box); + + store = gimp_int_store_new (); + gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store)); + g_object_unref (store); + + priv->layout = GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED; + + gimp_int_combo_box_create_cells (GIMP_INT_COMBO_BOX (combo_box)); +} + +static void +gimp_int_combo_box_finalize (GObject *object) +{ + GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (object); + + g_clear_pointer (&priv->label, g_free); + + if (priv->sensitivity_destroy) + { + GDestroyNotify d = priv->sensitivity_destroy; + + priv->sensitivity_destroy = NULL; + d (priv->sensitivity_data); + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_int_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ELLIPSIZE: + priv->ellipsize = g_value_get_enum (value); + if (priv->text_renderer) + { + g_object_set_property (G_OBJECT (priv->text_renderer), + pspec->name, value); + } + break; + case PROP_LABEL: + gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (object), + g_value_get_string (value)); + break; + case PROP_LAYOUT: + gimp_int_combo_box_set_layout (GIMP_INT_COMBO_BOX (object), + g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_int_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ELLIPSIZE: + g_value_set_enum (value, priv->ellipsize); + break; + case PROP_LABEL: + g_value_set_string (value, priv->label); + break; + case PROP_LAYOUT: + g_value_set_enum (value, priv->layout); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/** + * gimp_int_combo_box_new: + * @first_label: the label of the first item + * @first_value: the value of the first item + * @...: a %NULL terminated list of more label, value pairs + * + * Creates a GtkComboBox that has integer values associated with each + * item. The items to fill the combo box with are specified as a %NULL + * terminated list of label/value pairs. + * + * If you need to construct an empty #GimpIntComboBox, it's best to use + * g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL). + * + * Return value: a new #GimpIntComboBox. + * + * Since: 2.2 + **/ +GtkWidget * +gimp_int_combo_box_new (const gchar *first_label, + gint first_value, + ...) +{ + GtkWidget *combo_box; + va_list args; + + va_start (args, first_value); + + combo_box = gimp_int_combo_box_new_valist (first_label, first_value, args); + + va_end (args); + + return combo_box; +} + +/** + * gimp_int_combo_box_new_valist: + * @first_label: the label of the first item + * @first_value: the value of the first item + * @values: a va_list with more values + * + * A variant of gimp_int_combo_box_new() that takes a va_list of + * label/value pairs. Probably only useful for language bindings. + * + * Return value: a new #GimpIntComboBox. + * + * Since: 2.2 + **/ +GtkWidget * +gimp_int_combo_box_new_valist (const gchar *first_label, + gint first_value, + va_list values) +{ + GtkWidget *combo_box; + GtkListStore *store; + const gchar *label; + gint value; + + combo_box = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); + + for (label = first_label, value = first_value; + label; + label = va_arg (values, const gchar *), value = va_arg (values, gint)) + { + GtkTreeIter iter = { 0, }; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_VALUE, value, + GIMP_INT_STORE_LABEL, label, + -1); + } + + return combo_box; +} + +/** + * gimp_int_combo_box_new_array: + * @n_values: the number of values + * @labels: an array of labels (array length must be @n_values) + * + * A variant of gimp_int_combo_box_new() that takes an array of labels. + * The array indices are used as values. + * + * Return value: a new #GimpIntComboBox. + * + * Since: 2.2 + **/ +GtkWidget * +gimp_int_combo_box_new_array (gint n_values, + const gchar *labels[]) +{ + GtkWidget *combo_box; + GtkListStore *store; + gint i; + + g_return_val_if_fail (n_values >= 0, NULL); + g_return_val_if_fail (labels != NULL || n_values == 0, NULL); + + combo_box = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL); + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); + + for (i = 0; i < n_values; i++) + { + GtkTreeIter iter; + + if (labels[i]) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_VALUE, i, + GIMP_INT_STORE_LABEL, gettext (labels[i]), + -1); + } + } + + return combo_box; +} + +/** + * gimp_int_combo_box_prepend: + * @combo_box: a #GimpIntComboBox + * @...: pairs of column number and value, terminated with -1 + * + * This function provides a convenient way to prepend items to a + * #GimpIntComboBox. It prepends a row to the @combo_box's list store + * and calls gtk_list_store_set() for you. + * + * The column number must be taken from the enum #GimpIntStoreColumns. + * + * Since: 2.2 + **/ +void +gimp_int_combo_box_prepend (GimpIntComboBox *combo_box, + ...) +{ + GtkListStore *store; + GtkTreeIter iter; + va_list args; + + g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box)); + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); + + va_start (args, combo_box); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set_valist (store, &iter, args); + + va_end (args); +} + +/** + * gimp_int_combo_box_append: + * @combo_box: a #GimpIntComboBox + * @...: pairs of column number and value, terminated with -1 + * + * This function provides a convenient way to append items to a + * #GimpIntComboBox. It appends a row to the @combo_box's list store + * and calls gtk_list_store_set() for you. + * + * The column number must be taken from the enum #GimpIntStoreColumns. + * + * Since: 2.2 + **/ +void +gimp_int_combo_box_append (GimpIntComboBox *combo_box, + ...) +{ + GtkListStore *store; + GtkTreeIter iter; + va_list args; + + g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box)); + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); + + va_start (args, combo_box); + + gtk_list_store_append (store, &iter); + gtk_list_store_set_valist (store, &iter, args); + + va_end (args); +} + +/** + * gimp_int_combo_box_set_active: + * @combo_box: a #GimpIntComboBox + * @value: an integer value + * + * Looks up the item that belongs to the given @value and makes it the + * selected item in the @combo_box. + * + * Return value: %TRUE on success or %FALSE if there was no item for + * this value. + * + * Since: 2.2 + **/ +gboolean +gimp_int_combo_box_set_active (GimpIntComboBox *combo_box, + gint value) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + + if (gimp_int_store_lookup_by_value (model, value, &iter)) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_int_combo_box_get_active: + * @combo_box: a #GimpIntComboBox + * @value: return location for the integer value + * + * Retrieves the value of the selected (active) item in the @combo_box. + * + * Return value: %TRUE if @value has been set or %FALSE if no item was + * active. + * + * Since: 2.2 + **/ +gboolean +gimp_int_combo_box_get_active (GimpIntComboBox *combo_box, + gint *value) +{ + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) + { + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)), + &iter, + GIMP_INT_STORE_VALUE, value, + -1); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_int_combo_box_set_active_by_user_data: + * @combo_box: a #GimpIntComboBox + * @user_data: an integer value + * + * Looks up the item that has the given @user_data and makes it the + * selected item in the @combo_box. + * + * Return value: %TRUE on success or %FALSE if there was no item for + * this user-data. + * + * Since: 2.10 + **/ +gboolean +gimp_int_combo_box_set_active_by_user_data (GimpIntComboBox *combo_box, + gpointer user_data) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + + if (gimp_int_store_lookup_by_user_data (model, user_data, &iter)) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_int_combo_box_get_active_user_data: + * @combo_box: a #GimpIntComboBox + * @user_data: return location for the gpointer value + * + * Retrieves the user-data of the selected (active) item in the @combo_box. + * + * Return value: %TRUE if @user_data has been set or %FALSE if no item was + * active. + * + * Since: 2.10 + **/ +gboolean +gimp_int_combo_box_get_active_user_data (GimpIntComboBox *combo_box, + gpointer *user_data) +{ + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), FALSE); + g_return_val_if_fail (user_data != NULL, FALSE); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter)) + { + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)), + &iter, + GIMP_INT_STORE_USER_DATA, user_data, + -1); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_int_combo_box_connect: + * @combo_box: a #GimpIntComboBox + * @value: the value to set + * @callback: a callback to connect to the @combo_box's "changed" signal + * @data: a pointer passed as data to g_signal_connect() + * + * A convenience function that sets the initial @value of a + * #GimpIntComboBox and connects @callback to the "changed" + * signal. + * + * This function also calls the @callback once after setting the + * initial @value. This is often convenient when working with combo + * boxes that select a default active item, like for example + * gimp_drawable_combo_box_new(). If you pass an invalid initial + * @value, the @callback will be called with the default item active. + * + * Return value: the signal handler ID as returned by g_signal_connect() + * + * Since: 2.2 + **/ +gulong +gimp_int_combo_box_connect (GimpIntComboBox *combo_box, + gint value, + GCallback callback, + gpointer data) +{ + gulong handler = 0; + + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), 0); + + if (callback) + handler = g_signal_connect (combo_box, "changed", callback, data); + + if (! gimp_int_combo_box_set_active (combo_box, value)) + g_signal_emit_by_name (combo_box, "changed", NULL); + + return handler; +} + +/** + * gimp_int_combo_box_set_label: + * @combo_box: a #GimpIntComboBox + * @label: a string to be shown as label + * + * Sets a caption on the @combo_box that will be displayed + * left-aligned inside the box. When a label is set, the remaining + * contents of the box will be right-aligned. This is useful for + * places where screen estate is rare, like in tool options. + * + * Since: 2.10 + **/ +void +gimp_int_combo_box_set_label (GimpIntComboBox *combo_box, + const gchar *label) +{ + GimpIntComboBoxPrivate *priv; + + g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box)); + + priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box); + + if (label == priv->label) + return; + + g_free (priv->label); + + priv->label = g_strdup (label); + + gimp_int_combo_box_create_cells (combo_box); + + g_object_notify (G_OBJECT (combo_box), "label"); +} + +/** + * gimp_int_combo_box_get_label: + * @combo_box: a #GimpIntComboBox + * + * Returns the label previously set with gimp_int_combo_box_set_label(), + * or %NULL, + * + * Return value: the @combo_box' label. + * + * Since: 2.10 + **/ +const gchar * +gimp_int_combo_box_get_label (GimpIntComboBox *combo_box) +{ + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), NULL); + + return GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box)->label; +} + +/** + * gimp_int_combo_box_set_layout: + * @combo_box: a #GimpIntComboBox + * @layout: the combo box layout + * + * Sets the layout of @combo_box to @layout. + * + * Since: 2.10 + **/ +void +gimp_int_combo_box_set_layout (GimpIntComboBox *combo_box, + GimpIntComboBoxLayout layout) +{ + GimpIntComboBoxPrivate *priv; + + g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box)); + + priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box); + + if (layout == priv->layout) + return; + + priv->layout = layout; + + gimp_int_combo_box_create_cells (combo_box); + + g_object_notify (G_OBJECT (combo_box), "layout"); +} + +/** + * gimp_int_combo_box_get_layout: + * @combo_box: a #GimpIntComboBox + * + * Returns the layout of @combo_box + * + * Return value: the @combo_box's layout. + * + * Since: 2.10 + **/ +GimpIntComboBoxLayout +gimp_int_combo_box_get_layout (GimpIntComboBox *combo_box) +{ + g_return_val_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box), + GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED); + + return GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box)->layout; +} + +/** + * gimp_int_combo_box_set_sensitivity: + * @combo_box: a #GimpIntComboBox + * @func: a function that returns a boolean value, or %NULL to unset + * @data: data to pass to @func + * @destroy: destroy notification for @data + * + * Sets a function that is used to decide about the sensitivity of + * rows in the @combo_box. Use this if you want to set certain rows + * insensitive. + * + * Calling gtk_widget_queue_draw() on the @combo_box will cause the + * sensitivity to be updated. + * + * Since: 2.4 + **/ +void +gimp_int_combo_box_set_sensitivity (GimpIntComboBox *combo_box, + GimpIntSensitivityFunc func, + gpointer data, + GDestroyNotify destroy) +{ + GimpIntComboBoxPrivate *priv; + + g_return_if_fail (GIMP_IS_INT_COMBO_BOX (combo_box)); + + priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box); + + if (priv->sensitivity_destroy) + { + GDestroyNotify d = priv->sensitivity_destroy; + + priv->sensitivity_destroy = NULL; + d (priv->sensitivity_data); + } + + priv->sensitivity_func = func; + priv->sensitivity_data = data; + priv->sensitivity_destroy = destroy; + + gimp_int_combo_box_create_cells (combo_box); +} + + +/* private functions */ + +static void +queue_resize_cell_view (GtkContainer *container) +{ + GList *children = gtk_container_get_children (container); + GList *list; + + for (list = children; list; list = g_list_next (list)) + { + if (GTK_IS_CELL_VIEW (list->data)) + { + gtk_widget_queue_resize (list->data); + break; + } + else if (GTK_IS_CONTAINER (list->data)) + { + queue_resize_cell_view (list->data); + } + } + + g_list_free (children); +} + +static void +gimp_int_combo_box_create_cells (GimpIntComboBox *combo_box) +{ + GimpIntComboBoxPrivate *priv = GIMP_INT_COMBO_BOX_GET_PRIVATE (combo_box); + GtkCellLayout *layout; + + /* menu layout */ + + layout = GTK_CELL_LAYOUT (combo_box); + + gtk_cell_layout_clear (layout); + + priv->menu_pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (priv->menu_pixbuf_renderer, + "xpad", 2, + NULL); + + priv->menu_text_renderer = gtk_cell_renderer_text_new (); + + gtk_cell_layout_pack_start (layout, + priv->menu_pixbuf_renderer, FALSE); + gtk_cell_layout_pack_start (layout, + priv->menu_text_renderer, TRUE); + + gtk_cell_layout_set_attributes (layout, + priv->menu_pixbuf_renderer, + "icon-name", GIMP_INT_STORE_ICON_NAME, + "pixbuf", GIMP_INT_STORE_PIXBUF, + NULL); + gtk_cell_layout_set_attributes (layout, + priv->menu_text_renderer, + "text", GIMP_INT_STORE_LABEL, + NULL); + + if (priv->sensitivity_func) + { + gtk_cell_layout_set_cell_data_func (layout, + priv->menu_pixbuf_renderer, + gimp_int_combo_box_data_func, + priv, NULL); + + gtk_cell_layout_set_cell_data_func (layout, + priv->menu_text_renderer, + gimp_int_combo_box_data_func, + priv, NULL); + } + + /* combo box layout */ + + layout = GTK_CELL_LAYOUT (gtk_bin_get_child (GTK_BIN (combo_box))); + + gtk_cell_layout_clear (layout); + + if (priv->layout != GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY) + { + priv->text_renderer = gtk_cell_renderer_text_new (); + g_object_set (priv->text_renderer, + "ellipsize", priv->ellipsize, + NULL); + } + else + { + priv->text_renderer = NULL; + } + + priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + + if (priv->text_renderer) + { + g_object_set (priv->pixbuf_renderer, + "xpad", 2, + NULL); + } + + if (priv->label) + { + priv->label_renderer = gtk_cell_renderer_text_new (); + g_object_set (priv->label_renderer, + "text", priv->label, + NULL); + + gtk_cell_layout_pack_start (layout, + priv->label_renderer, FALSE); + + gtk_cell_layout_pack_end (layout, + priv->pixbuf_renderer, FALSE); + + if (priv->text_renderer) + { + gtk_cell_layout_pack_end (layout, + priv->text_renderer, TRUE); + + g_object_set (priv->text_renderer, + "xalign", 1.0, + NULL); + } + } + else + { + gtk_cell_layout_pack_start (layout, + priv->pixbuf_renderer, FALSE); + + if (priv->text_renderer) + { + gtk_cell_layout_pack_start (layout, + priv->text_renderer, TRUE); + } + } + + gtk_cell_layout_set_attributes (layout, + priv->pixbuf_renderer, + "icon-name", GIMP_INT_STORE_ICON_NAME, + NULL); + + if (priv->text_renderer) + { + gtk_cell_layout_set_attributes (layout, + priv->text_renderer, + "text", GIMP_INT_STORE_LABEL, + NULL); + } + + if (priv->layout == GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED || + priv->sensitivity_func) + { + gtk_cell_layout_set_cell_data_func (layout, + priv->pixbuf_renderer, + gimp_int_combo_box_data_func, + priv, NULL); + + if (priv->text_renderer) + { + gtk_cell_layout_set_cell_data_func (layout, + priv->text_renderer, + gimp_int_combo_box_data_func, + priv, NULL); + } + } + + /* HACK: GtkCellView doesn't invalidate itself when stuff is + * added/removed, work around this bug until GTK+ 2.24.19 + */ + if (gtk_check_version (2, 24, 19)) + { + GList *attached_menus; + + queue_resize_cell_view (GTK_CONTAINER (combo_box)); + + /* HACK HACK HACK OMG */ + attached_menus = g_object_get_data (G_OBJECT (combo_box), + "gtk-attached-menus"); + + for (; attached_menus; attached_menus = g_list_next (attached_menus)) + queue_resize_cell_view (attached_menus->data); + } +} + +static void +gimp_int_combo_box_data_func (GtkCellLayout *layout, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GimpIntComboBoxPrivate *priv = data; + + if (priv->layout == GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED && + cell == priv->text_renderer) + { + gchar *abbrev; + + gtk_tree_model_get (model, iter, + GIMP_INT_STORE_ABBREV, &abbrev, + -1); + + if (abbrev) + { + g_object_set (cell, + "text", abbrev, + NULL); + + g_free (abbrev); + } + } + + if (priv->sensitivity_func) + { + gint value; + gboolean sensitive; + + gtk_tree_model_get (model, iter, + GIMP_INT_STORE_VALUE, &value, + -1); + + sensitive = priv->sensitivity_func (value, priv->sensitivity_data); + + g_object_set (cell, + "sensitive", sensitive, + NULL); + } +} diff --git a/libgimpwidgets/gimpintcombobox.h b/libgimpwidgets/gimpintcombobox.h new file mode 100644 index 0000000..ca0e607 --- /dev/null +++ b/libgimpwidgets/gimpintcombobox.h @@ -0,0 +1,117 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpintcombobox.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_INT_COMBO_BOX_H__ +#define __GIMP_INT_COMBO_BOX_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_INT_COMBO_BOX (gimp_int_combo_box_get_type ()) +#define GIMP_INT_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INT_COMBO_BOX, GimpIntComboBox)) +#define GIMP_INT_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INT_COMBO_BOX, GimpIntComboBoxClass)) +#define GIMP_IS_INT_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INT_COMBO_BOX)) +#define GIMP_IS_INT_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INT_COMBO_BOX)) +#define GIMP_INT_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INT_COMBO_BOX, GimpIntComboBoxClass)) + + +typedef struct _GimpIntComboBoxClass GimpIntComboBoxClass; + +struct _GimpIntComboBox +{ + GtkComboBox parent_instance; + + /*< private >*/ + gpointer priv; + + /* Padding for future expansion (should have gone to the class) */ + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + +struct _GimpIntComboBoxClass +{ + GtkComboBoxClass parent_class; +}; + + +typedef gboolean (* GimpIntSensitivityFunc) (gint value, + gpointer data); + + + +GType gimp_int_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_int_combo_box_new (const gchar *first_label, + gint first_value, + ...) G_GNUC_NULL_TERMINATED; +GtkWidget * gimp_int_combo_box_new_valist (const gchar *first_label, + gint first_value, + va_list values); + +GtkWidget * gimp_int_combo_box_new_array (gint n_values, + const gchar *labels[]); + +void gimp_int_combo_box_prepend (GimpIntComboBox *combo_box, + ...); +void gimp_int_combo_box_append (GimpIntComboBox *combo_box, + ...); + +gboolean gimp_int_combo_box_set_active (GimpIntComboBox *combo_box, + gint value); +gboolean gimp_int_combo_box_get_active (GimpIntComboBox *combo_box, + gint *value); + +gboolean + gimp_int_combo_box_set_active_by_user_data (GimpIntComboBox *combo_box, + gpointer user_data); +gboolean + gimp_int_combo_box_get_active_user_data (GimpIntComboBox *combo_box, + gpointer *user_data); + +gulong gimp_int_combo_box_connect (GimpIntComboBox *combo_box, + gint value, + GCallback callback, + gpointer data); + +void gimp_int_combo_box_set_label (GimpIntComboBox *combo_box, + const gchar *label); +const gchar * gimp_int_combo_box_get_label (GimpIntComboBox *combo_box); + +void gimp_int_combo_box_set_layout (GimpIntComboBox *combo_box, + GimpIntComboBoxLayout layout); +GimpIntComboBoxLayout + gimp_int_combo_box_get_layout (GimpIntComboBox *combo_box); + +void gimp_int_combo_box_set_sensitivity (GimpIntComboBox *combo_box, + GimpIntSensitivityFunc func, + gpointer data, + GDestroyNotify destroy); + + +G_END_DECLS + +#endif /* __GIMP_INT_COMBO_BOX_H__ */ diff --git a/libgimpwidgets/gimpintstore.c b/libgimpwidgets/gimpintstore.c new file mode 100644 index 0000000..54c6cf9 --- /dev/null +++ b/libgimpwidgets/gimpintstore.c @@ -0,0 +1,351 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpintstore.c + * Copyright (C) 2004-2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpintstore.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpintstore + * @title: GimpIntStore + * @short_description: A model for integer based name-value pairs + * (e.g. enums) + * + * A model for integer based name-value pairs (e.g. enums) + **/ + + +enum +{ + PROP_0, + PROP_USER_DATA_TYPE +}; + +typedef struct +{ + GType user_data_type; +} GimpIntStorePrivate; + + +static void gimp_int_store_tree_model_init (GtkTreeModelIface *iface); + +static void gimp_int_store_constructed (GObject *object); +static void gimp_int_store_finalize (GObject *object); +static void gimp_int_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_int_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_int_store_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter); +static void gimp_int_store_row_deleted (GtkTreeModel *model, + GtkTreePath *path); +static void gimp_int_store_add_empty (GimpIntStore *store); + + +G_DEFINE_TYPE_WITH_CODE (GimpIntStore, gimp_int_store, GTK_TYPE_LIST_STORE, + G_ADD_PRIVATE (GimpIntStore) + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + gimp_int_store_tree_model_init)) + +#define GIMP_INT_STORE_GET_PRIVATE(obj) \ + ((GimpIntStorePrivate *) gimp_int_store_get_instance_private ((GimpIntStore *) (obj))) + +#define parent_class gimp_int_store_parent_class + +static GtkTreeModelIface *parent_iface = NULL; + + +static void +gimp_int_store_class_init (GimpIntStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_int_store_constructed; + object_class->finalize = gimp_int_store_finalize; + object_class->set_property = gimp_int_store_set_property; + object_class->get_property = gimp_int_store_get_property; + + /** + * GimpIntStore:user-data-type: + * + * Sets the #GType for the GIMP_INT_STORE_USER_DATA column. + * + * You need to set this property when constructing the store if you want + * to use the GIMP_INT_STORE_USER_DATA column and want to have the store + * handle ref-counting of your user data. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_USER_DATA_TYPE, + g_param_spec_gtype ("user-data-type", + "User Data Type", + "The GType of the user_data column", + G_TYPE_NONE, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_int_store_tree_model_init (GtkTreeModelIface *iface) +{ + parent_iface = g_type_interface_peek_parent (iface); + + iface->row_inserted = gimp_int_store_row_inserted; + iface->row_deleted = gimp_int_store_row_deleted; +} + +static void +gimp_int_store_init (GimpIntStore *store) +{ + store->empty_iter = NULL; +} + +static void +gimp_int_store_constructed (GObject *object) +{ + GimpIntStore *store = GIMP_INT_STORE (object); + GimpIntStorePrivate *priv = GIMP_INT_STORE_GET_PRIVATE (store); + GType types[GIMP_INT_STORE_NUM_COLUMNS]; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + types[GIMP_INT_STORE_VALUE] = G_TYPE_INT; + types[GIMP_INT_STORE_LABEL] = G_TYPE_STRING; + types[GIMP_INT_STORE_ICON_NAME] = G_TYPE_STRING; + types[GIMP_INT_STORE_PIXBUF] = GDK_TYPE_PIXBUF; + types[GIMP_INT_STORE_USER_DATA] = (priv->user_data_type != G_TYPE_NONE ? + priv->user_data_type : G_TYPE_POINTER); + types[GIMP_INT_STORE_ABBREV] = G_TYPE_STRING; + + gtk_list_store_set_column_types (GTK_LIST_STORE (store), + GIMP_INT_STORE_NUM_COLUMNS, types); + + gimp_int_store_add_empty (store); +} + +static void +gimp_int_store_finalize (GObject *object) +{ + GimpIntStore *store = GIMP_INT_STORE (object); + + if (store->empty_iter) + { + gtk_tree_iter_free (store->empty_iter); + store->empty_iter = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_int_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpIntStorePrivate *priv = GIMP_INT_STORE_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_USER_DATA_TYPE: + priv->user_data_type = g_value_get_gtype (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_int_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpIntStorePrivate *priv = GIMP_INT_STORE_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_USER_DATA_TYPE: + g_value_set_gtype (value, priv->user_data_type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_int_store_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter) +{ + GimpIntStore *store = GIMP_INT_STORE (model); + + if (parent_iface->row_inserted) + parent_iface->row_inserted (model, path, iter); + + if (store->empty_iter && + memcmp (iter, store->empty_iter, sizeof (GtkTreeIter))) + { + gtk_list_store_remove (GTK_LIST_STORE (store), store->empty_iter); + gtk_tree_iter_free (store->empty_iter); + store->empty_iter = NULL; + } +} + +static void +gimp_int_store_row_deleted (GtkTreeModel *model, + GtkTreePath *path) +{ + if (parent_iface->row_deleted) + parent_iface->row_deleted (model, path); +} + +static void +gimp_int_store_add_empty (GimpIntStore *store) +{ + GtkTreeIter iter = { 0, }; + + g_return_if_fail (store->empty_iter == NULL); + + gtk_list_store_prepend (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + GIMP_INT_STORE_VALUE, -1, + /* This string appears in an empty menu as in + * "nothing selected and nothing to select" + */ + GIMP_INT_STORE_LABEL, (_("(Empty)")), + -1); + + store->empty_iter = gtk_tree_iter_copy (&iter); +} + +/** + * gimp_int_store_new: + * + * Creates a #GtkListStore with a number of useful columns. + * #GimpIntStore is especially useful if the items you want to store + * are identified using an integer value. + * + * Return value: a new #GimpIntStore. + * + * Since: 2.2 + **/ +GtkListStore * +gimp_int_store_new (void) +{ + return g_object_new (GIMP_TYPE_INT_STORE, NULL); +} + +/** + * gimp_int_store_lookup_by_value: + * @model: a #GimpIntStore + * @value: an integer value to lookup in the @model + * @iter: return location for the iter of the given @value + * + * Iterate over the @model looking for @value. + * + * Return value: %TRUE if the value has been located and @iter is + * valid, %FALSE otherwise. + * + * Since: 2.2 + **/ +gboolean +gimp_int_store_lookup_by_value (GtkTreeModel *model, + gint value, + GtkTreeIter *iter) +{ + gboolean iter_valid; + + g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + gint this; + + gtk_tree_model_get (model, iter, + GIMP_INT_STORE_VALUE, &this, + -1); + if (this == value) + break; + } + + return iter_valid; +} + +/** + * gimp_int_store_lookup_by_user_data: + * @model: a #GimpIntStore + * @user_data: a gpointer "user-data" to lookup in the @model + * @iter: return location for the iter of the given @user_data + * + * Iterate over the @model looking for @user_data. + * + * Return value: %TRUE if the user-data has been located and @iter is + * valid, %FALSE otherwise. + * + * Since: 2.10 + **/ +gboolean +gimp_int_store_lookup_by_user_data (GtkTreeModel *model, + gpointer user_data, + GtkTreeIter *iter) +{ + gboolean iter_valid = FALSE; + + g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + gpointer this; + + gtk_tree_model_get (model, iter, + GIMP_INT_STORE_USER_DATA, &this, + -1); + if (this == user_data) + break; + } + + return (gboolean) iter_valid; +} diff --git a/libgimpwidgets/gimpintstore.h b/libgimpwidgets/gimpintstore.h new file mode 100644 index 0000000..c3d62ef --- /dev/null +++ b/libgimpwidgets/gimpintstore.h @@ -0,0 +1,104 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpintstore.c + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_INT_STORE_H__ +#define __GIMP_INT_STORE_H__ + +G_BEGIN_DECLS + + +/** + * GimpIntStoreColumns: + * @GIMP_INT_STORE_VALUE: the integer value + * @GIMP_INT_STORE_LABEL: a human-readable label + * @GIMP_INT_STORE_ICON_NAME: an icon name + * @GIMP_INT_STORE_PIXBUF: a #GdkPixbuf + * @GIMP_INT_STORE_USER_DATA: arbitrary user data + * @GIMP_INT_STORE_ABBREV: an abbreviated label + * @GIMP_INT_STORE_NUM_COLUMNS: the number of columns + * @GIMP_INT_STORE_STOCK_ID: compat alias for @GIMP_INT_STORE_ICON_NAME + * + * The column types of #GimpIntStore. + **/ +typedef enum +{ + GIMP_INT_STORE_VALUE, + GIMP_INT_STORE_LABEL, + GIMP_INT_STORE_ICON_NAME, + GIMP_INT_STORE_PIXBUF, + GIMP_INT_STORE_USER_DATA, + GIMP_INT_STORE_ABBREV, + GIMP_INT_STORE_NUM_COLUMNS, + + /* deprecated */ + GIMP_INT_STORE_STOCK_ID = GIMP_INT_STORE_ICON_NAME +} GimpIntStoreColumns; + + +#define GIMP_TYPE_INT_STORE (gimp_int_store_get_type ()) +#define GIMP_INT_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INT_STORE, GimpIntStore)) +#define GIMP_INT_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INT_STORE, GimpIntStoreClass)) +#define GIMP_IS_INT_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INT_STORE)) +#define GIMP_IS_INT_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INT_STORE)) +#define GIMP_INT_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INT_STORE, GimpIntStoreClass)) + + +typedef struct _GimpIntStoreClass GimpIntStoreClass; + +struct _GimpIntStore +{ + GtkListStore parent_instance; + + /*< private >*/ + GtkTreeIter *empty_iter; +}; + +struct _GimpIntStoreClass +{ + GtkListStoreClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_int_store_get_type (void) G_GNUC_CONST; + +GtkListStore * gimp_int_store_new (void); + +gboolean gimp_int_store_lookup_by_value (GtkTreeModel *model, + gint value, + GtkTreeIter *iter); +gboolean gimp_int_store_lookup_by_user_data (GtkTreeModel *model, + gpointer user_data, + GtkTreeIter *iter); + + +G_END_DECLS + +#endif /* __GIMP_INT_STORE_H__ */ diff --git a/libgimpwidgets/gimpmemsizeentry.c b/libgimpwidgets/gimpmemsizeentry.c new file mode 100644 index 0000000..1d925bb --- /dev/null +++ b/libgimpwidgets/gimpmemsizeentry.c @@ -0,0 +1,320 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpmemsizeentry.c + * Copyright (C) 2000-2003 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpmemsizeentry.h" +#include "gimpspinbutton.h" +#include "gimpwidgets.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpmemsizeentry + * @title: GimpMemSizeEntry + * @short_description: A composite widget to enter a memory size. + * + * Similar to a #GimpSizeEntry but instead of lengths, this widget is + * used to let the user enter memory sizes. A combo box allows one to + * switch between Kilobytes, Megabytes and Gigabytes. Used in the GIMP + * preferences dialog. + **/ + + +enum +{ + VALUE_CHANGED, + LAST_SIGNAL +}; + + +static void gimp_memsize_entry_finalize (GObject *object); + +static void gimp_memsize_entry_adj_callback (GtkAdjustment *adj, + GimpMemsizeEntry *entry); +static void gimp_memsize_entry_unit_callback (GtkWidget *widget, + GimpMemsizeEntry *entry); + +static guint64 gimp_memsize_entry_get_rounded_value (GimpMemsizeEntry *entry, + guint64 value); + +G_DEFINE_TYPE (GimpMemsizeEntry, gimp_memsize_entry, GTK_TYPE_BOX) + +#define parent_class gimp_memsize_entry_parent_class + +static guint gimp_memsize_entry_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_memsize_entry_class_init (GimpMemsizeEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_memsize_entry_finalize; + + klass->value_changed = NULL; + + gimp_memsize_entry_signals[VALUE_CHANGED] = + g_signal_new ("value-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpMemsizeEntryClass, value_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +gimp_memsize_entry_init (GimpMemsizeEntry *entry) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (entry), + GTK_ORIENTATION_HORIZONTAL); + + gtk_box_set_spacing (GTK_BOX (entry), 4); + + entry->value = 0; + entry->lower = 0; + entry->upper = 0; + entry->shift = 0; + entry->adjustment = NULL; + entry->menu = NULL; +} + +static void +gimp_memsize_entry_finalize (GObject *object) +{ + GimpMemsizeEntry *entry = (GimpMemsizeEntry *) object; + + g_clear_object (&entry->adjustment); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_memsize_entry_adj_callback (GtkAdjustment *adj, + GimpMemsizeEntry *entry) +{ + guint64 size = gtk_adjustment_get_value (adj); + + if (gimp_memsize_entry_get_rounded_value (entry, entry->value) != size) + /* Do not allow losing accuracy if the converted/displayed value + * stays the same. + */ + entry->value = size << entry->shift; + + g_signal_emit (entry, gimp_memsize_entry_signals[VALUE_CHANGED], 0); +} + +static void +gimp_memsize_entry_unit_callback (GtkWidget *widget, + GimpMemsizeEntry *entry) +{ + guint shift; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) &shift); + +#if _MSC_VER < 1300 +# define CAST (gint64) +#else +# define CAST +#endif + + if (shift != entry->shift) + { + entry->shift = shift; + + gtk_adjustment_configure (entry->adjustment, + gimp_memsize_entry_get_rounded_value (entry, entry->value), + CAST entry->lower >> shift, + CAST entry->upper >> shift, + gtk_adjustment_get_step_increment (entry->adjustment), + gtk_adjustment_get_page_increment (entry->adjustment), + gtk_adjustment_get_page_size (entry->adjustment)); + } + +#undef CAST +} + +/** + * gimp_memsize_entry_get_rounded_value: + * @entry: #GimpMemsizeEntry whose set unit is used. + * @value: value to convert to @entry unit, and rounded. + * + * Returns: the proper integer value to be displayed for current unit. + * This value has been appropriately rounded to the nearest + * integer, away from zero. + */ +static guint64 +gimp_memsize_entry_get_rounded_value (GimpMemsizeEntry *entry, + guint64 value) +{ + guint64 converted; + +#if _MSC_VER < 1300 +# define CAST (gint64) +#else +# define CAST +#endif + + converted = (CAST value >> entry->shift) + + ((CAST entry->value >> (entry->shift - 1)) & 1); + +#undef CAST + + return converted; +} + + +/** + * gimp_memsize_entry_new: + * @value: the initial value (in Bytes) + * @lower: the lower limit for the value (in Bytes) + * @upper: the upper limit for the value (in Bytes) + * + * Creates a new #GimpMemsizeEntry which is a #GtkHBox with a #GtkSpinButton + * and a #GtkOptionMenu all setup to allow the user to enter memory sizes. + * + * Returns: Pointer to the new #GimpMemsizeEntry. + **/ +GtkWidget * +gimp_memsize_entry_new (guint64 value, + guint64 lower, + guint64 upper) +{ + GimpMemsizeEntry *entry; + GtkAdjustment *adj; + guint shift; + +#if _MSC_VER < 1300 +# define CAST (gint64) +#else +# define CAST +#endif + + g_return_val_if_fail (value >= lower && value <= upper, NULL); + + entry = g_object_new (GIMP_TYPE_MEMSIZE_ENTRY, NULL); + + for (shift = 30; shift > 10; shift -= 10) + { + if (value > (G_GUINT64_CONSTANT (1) << shift) && + value % (G_GUINT64_CONSTANT (1) << shift) == 0) + break; + } + + entry->value = value; + entry->lower = lower; + entry->upper = upper; + entry->shift = shift; + + adj = (GtkAdjustment *) gtk_adjustment_new (gimp_memsize_entry_get_rounded_value (entry, + entry->value), + CAST (lower >> shift), + CAST (upper >> shift), + 1, 8, 0); + + entry->spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (entry->spinbutton), TRUE); + +#undef CAST + + entry->adjustment = GTK_ADJUSTMENT (adj); + g_object_ref_sink (entry->adjustment); + + gtk_entry_set_width_chars (GTK_ENTRY (entry->spinbutton), 7); + gtk_box_pack_start (GTK_BOX (entry), entry->spinbutton, FALSE, FALSE, 0); + gtk_widget_show (entry->spinbutton); + + g_signal_connect (entry->adjustment, "value-changed", + G_CALLBACK (gimp_memsize_entry_adj_callback), + entry); + + entry->menu = gimp_int_combo_box_new (_("Kibibyte"), 10, + _("Mebibyte"), 20, + _("Gibibyte"), 30, + NULL); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (entry->menu), shift); + + g_signal_connect (entry->menu, "changed", + G_CALLBACK (gimp_memsize_entry_unit_callback), + entry); + + gtk_box_pack_start (GTK_BOX (entry), entry->menu, FALSE, FALSE, 0); + gtk_widget_show (entry->menu); + + return GTK_WIDGET (entry); +} + +/** + * gimp_memsize_entry_set_value: + * @entry: a #GimpMemsizeEntry + * @value: the new value (in Bytes) + * + * Sets the @entry's value. Please note that the #GimpMemsizeEntry rounds + * the value to full Kilobytes. + **/ +void +gimp_memsize_entry_set_value (GimpMemsizeEntry *entry, + guint64 value) +{ + guint shift; + + g_return_if_fail (GIMP_IS_MEMSIZE_ENTRY (entry)); + g_return_if_fail (value >= entry->lower && value <= entry->upper); + + for (shift = 30; shift > 10; shift -= 10) + { + if (value > (G_GUINT64_CONSTANT (1) << shift) && + value % (G_GUINT64_CONSTANT (1) << shift) == 0) + break; + } + + if (shift != entry->shift) + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (entry->menu), shift); + + gtk_adjustment_set_value (entry->adjustment, + (gdouble) gimp_memsize_entry_get_rounded_value (entry, value)); + +#undef CASE +} + +/** + * gimp_memsize_entry_get_value: + * @entry: a #GimpMemsizeEntry + * + * Retrieves the current value from a #GimpMemsizeEntry. + * + * Returns: the current value of @entry (in Bytes). + **/ +guint64 +gimp_memsize_entry_get_value (GimpMemsizeEntry *entry) +{ + g_return_val_if_fail (GIMP_IS_MEMSIZE_ENTRY (entry), 0); + + return entry->value; +} diff --git a/libgimpwidgets/gimpmemsizeentry.h b/libgimpwidgets/gimpmemsizeentry.h new file mode 100644 index 0000000..61af7bf --- /dev/null +++ b/libgimpwidgets/gimpmemsizeentry.h @@ -0,0 +1,84 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpmemsizeentry.h + * Copyright (C) 2000-2003 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_MEMSIZE_ENTRY_H__ +#define __GIMP_MEMSIZE_ENTRY_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_MEMSIZE_ENTRY (gimp_memsize_entry_get_type ()) +#define GIMP_MEMSIZE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MEMSIZE_ENTRY, GimpMemsizeEntry)) +#define GIMP_MEMSIZE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MEMSIZE_ENTRY, GimpMemsizeEntryClass)) +#define GIMP_IS_MEMSIZE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MEMSIZE_ENTRY)) +#define GIMP_IS_MEMSIZE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MEMSIZE_ENTRY)) +#define GIMP_MEMSIZE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MEMSIZE_ENTRY, GimpMemsizeEntryClass)) + + +typedef struct _GimpMemsizeEntryClass GimpMemsizeEntryClass; + +struct _GimpMemsizeEntry +{ + GtkBox parent_instance; + + /*< private >*/ + guint64 value; + guint64 lower; + guint64 upper; + + guint shift; + + GtkAdjustment *adjustment; + GtkWidget *spinbutton; + GtkWidget *menu; +}; + +struct _GimpMemsizeEntryClass +{ + GtkBoxClass parent_class; + + void (* value_changed) (GimpMemsizeEntry *entry); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_memsize_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_memsize_entry_new (guint64 value, + guint64 lower, + guint64 upper); +void gimp_memsize_entry_set_value (GimpMemsizeEntry *entry, + guint64 value); +guint64 gimp_memsize_entry_get_value (GimpMemsizeEntry *entry); + + +G_END_DECLS + +#endif /* __GIMP_MEMSIZE_ENTRY_H__ */ diff --git a/libgimpwidgets/gimpnumberpairentry.c b/libgimpwidgets/gimpnumberpairentry.c new file mode 100644 index 0000000..c41fda7 --- /dev/null +++ b/libgimpwidgets/gimpnumberpairentry.c @@ -0,0 +1,1301 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpnumberpairentry.c + * Copyright (C) 2006 Simon Budig <simon@gimp.org> + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * Copyright (C) 2007 Martin Nordholts <martin@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpmath/gimpmath.h" + +#include "gimpwidgetstypes.h" + +#include "gimpicons.h" +#include "gimpnumberpairentry.h" + + +/** + * SECTION: gimpnumberpairentry + * @title: GimpNumberPairEntry + * @short_description: A #GtkEntry subclass to enter ratios. + * + * A #GtkEntry subclass to enter ratios. + **/ + + +#define EPSILON 0.000001 + + +enum +{ + NUMBERS_CHANGED, + RATIO_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_LEFT_NUMBER, + PROP_RIGHT_NUMBER, + PROP_DEFAULT_LEFT_NUMBER, + PROP_DEFAULT_RIGHT_NUMBER, + PROP_USER_OVERRIDE, + PROP_SEPARATORS, + PROP_DEFAULT_TEXT, + PROP_ALLOW_SIMPLIFICATION, + PROP_MIN_VALID_VALUE, + PROP_MAX_VALID_VALUE, + PROP_RATIO, + PROP_ASPECT +}; + +typedef enum +{ + PARSE_VALID, + PARSE_CLEAR, + PARSE_INVALID +} ParseResult; + +typedef struct +{ + /* The current number pair displayed in the widget. */ + gdouble left_number; + gdouble right_number; + + /* What number pair that should be displayed when not in + user_override mode. */ + gdouble default_left_number; + gdouble default_right_number; + + /* Whether or not the current value in the entry has been explicitly + * set by the user. + */ + gboolean user_override; + + /* Is the font style currently set to ITALIC or NORMAL ? */ + gboolean font_italic; + + /* What separators that are valid when parsing input, e.g. when the + * widget is used for aspect ratio, valid separators are typically + * ':' and '/'. + */ + gunichar *separators; + glong num_separators; + + /* A string to be shown in the entry when in automatic mode */ + gchar *default_text; + + /* Whether or to not to divide the numbers with the greatest common + * divisor when input ends in '='. + */ + gboolean allow_simplification; + + /* What range of values considered valid. */ + gdouble min_valid_value; + gdouble max_valid_value; +} GimpNumberPairEntryPrivate; + +#define GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE(obj) \ + ((GimpNumberPairEntryPrivate *) ((GimpNumberPairEntry *) (obj))->priv) + + +static void gimp_number_pair_entry_finalize (GObject *entry); + +static gboolean gimp_number_pair_entry_valid_separator (GimpNumberPairEntry *entry, + gunichar candidate); +static void gimp_number_pair_entry_ratio_to_fraction (gdouble ratio, + gdouble *numerator, + gdouble *denominator); + +static void gimp_number_pair_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_number_pair_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_number_pair_entry_changed (GimpNumberPairEntry *entry); +static void gimp_number_pair_entry_icon_press (GimpNumberPairEntry *entry); +static gboolean gimp_number_pair_entry_events (GtkWidget *widget, + GdkEvent *event); + +static void gimp_number_pair_entry_update_text (GimpNumberPairEntry *entry); + +static ParseResult gimp_number_pair_entry_parse_text (GimpNumberPairEntry *entry, + const gchar *text, + gdouble *left_value, + gdouble *right_value); +static gboolean gimp_number_pair_entry_numbers_in_range (GimpNumberPairEntry *entry, + gdouble left_number, + gdouble right_number); + +static gchar * gimp_number_pair_entry_strdup_number_pair_string + (GimpNumberPairEntry *entry, + gdouble left_number, + gdouble right_number); + + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpNumberPairEntry, gimp_number_pair_entry, + GTK_TYPE_ENTRY) + + +#define parent_class gimp_number_pair_entry_parent_class + +/* What the user shall end the input with when simplification is desired. */ +#define SIMPLIFICATION_CHAR ((gunichar) '=') + +#define DEFAULT_SEPARATOR ((gunichar) ',') + + +static guint entry_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_number_pair_entry_class_init (GimpNumberPairEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + entry_signals[NUMBERS_CHANGED] = + g_signal_new ("numbers-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpNumberPairEntryClass, numbers_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + entry_signals[RATIO_CHANGED] = + g_signal_new ("ratio-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpNumberPairEntryClass, ratio_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->set_property = gimp_number_pair_entry_set_property; + object_class->get_property = gimp_number_pair_entry_get_property; + object_class->finalize = gimp_number_pair_entry_finalize; + + klass->numbers_changed = NULL; + klass->ratio_changed = NULL; + + g_object_class_install_property (object_class, PROP_LEFT_NUMBER, + g_param_spec_double ("left-number", + "Left number", + "The left number", + G_MINDOUBLE, G_MAXDOUBLE, + 100.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_RIGHT_NUMBER, + g_param_spec_double ("right-number", + "Right number", + "The right number", + G_MINDOUBLE, G_MAXDOUBLE, + 100.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_DEFAULT_LEFT_NUMBER, + g_param_spec_double ("default-left-number", + "Default left number", + "The default left number", + G_MINDOUBLE, G_MAXDOUBLE, + 100.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_DEFAULT_RIGHT_NUMBER, + g_param_spec_double ("default-right-number", + "Default right number", + "The default right number", + G_MINDOUBLE, G_MAXDOUBLE, + 100.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_USER_OVERRIDE, + g_param_spec_boolean ("user-override", + "User override", + "Whether the widget is in 'user override' mode", + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SEPARATORS, + g_param_spec_string ("separators", + "Separators", + "A string of valid separators", + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DEFAULT_TEXT, + g_param_spec_string ("default-text", + "Default text", + "String to show when in automatic mode", + NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ALLOW_SIMPLIFICATION, + g_param_spec_boolean ("allow-simplification", + "Allow simplification", + "Whether to allow simplification", + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_MIN_VALID_VALUE, + g_param_spec_double ("min-valid-value", + "Min valid value", + "Minimum value valid when parsing input", + G_MINDOUBLE, G_MAXDOUBLE, + G_MINDOUBLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_MAX_VALID_VALUE, + g_param_spec_double ("max-valid-value", + "Max valid value", + "Maximum value valid when parsing input", + G_MINDOUBLE, G_MAXDOUBLE, + G_MAXDOUBLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_RATIO, + g_param_spec_double ("ratio", + "Ratio", + "The value as ratio", + G_MINDOUBLE, G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ASPECT, + g_param_spec_enum ("aspect", + "Aspect", + "The value as aspect", + GIMP_TYPE_ASPECT_TYPE, + GIMP_ASPECT_SQUARE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_number_pair_entry_init (GimpNumberPairEntry *entry) +{ + GimpNumberPairEntryPrivate *priv; + + entry->priv = gimp_number_pair_entry_get_instance_private (entry); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + priv->left_number = 1.0; + priv->right_number = 1.0; + priv->default_left_number = 1.0; + priv->default_right_number = 1.0; + priv->user_override = FALSE; + priv->font_italic = FALSE; + priv->separators = NULL; + priv->default_text = NULL; + priv->num_separators = 0; + priv->allow_simplification = FALSE; + priv->min_valid_value = G_MINDOUBLE; + priv->max_valid_value = G_MAXDOUBLE; + + g_signal_connect (entry, "changed", + G_CALLBACK (gimp_number_pair_entry_changed), + NULL); + g_signal_connect (entry, "icon-press", + G_CALLBACK (gimp_number_pair_entry_icon_press), + NULL); + g_signal_connect (entry, "focus-out-event", + G_CALLBACK (gimp_number_pair_entry_events), + NULL); + g_signal_connect (entry, "key-press-event", + G_CALLBACK (gimp_number_pair_entry_events), + NULL); + + gtk_widget_set_direction (GTK_WIDGET (entry), GTK_TEXT_DIR_LTR); + + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + GIMP_ICON_EDIT_CLEAR); +} + +static void +gimp_number_pair_entry_finalize (GObject *object) +{ + GimpNumberPairEntryPrivate *priv; + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (object); + + g_clear_pointer (&priv->separators, g_free); + priv->num_separators = 0; + + g_clear_pointer (&priv->default_text, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gimp_number_pair_entry_new: + * @separators: The allowed separators. + * @allow_simplification: Whether to do simplification on the entered term. + * @min_valid_value: The minimum allowed result value. + * @max_valid_value: The maximum allowed result value. + * + * Creates a new #GimpNumberPairEntry widget, which is a GtkEntry that + * accepts two numbers separated by a separator. Typical input example + * with a 'x' separator: "377x233". + * + * The widget supports simplification of the entered ratio when the + * input ends in '=', if "allow-simplification" is TRUE. + * + * The "separators" property contains a string of characters valid as + * separators when parsing input. The first separator is used when + * displaying the current values. + * + * It is possible to specify what range of values that shall be + * considered as valid when parsing user input, by changing + * "min-valid-value" and "max-valid-value". + * + * The first separator of @separators is used to display the current + * value. + * + * Return value: The new #GimpNumberPairEntry widget. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_number_pair_entry_new (const gchar *separators, + gboolean allow_simplification, + gdouble min_valid_value, + gdouble max_valid_value) +{ + return g_object_new (GIMP_TYPE_NUMBER_PAIR_ENTRY, + "separators", separators, + "allow-simplification", allow_simplification, + "min-valid-value", min_valid_value, + "max-valid-value", max_valid_value, + NULL); +} + +static void +gimp_number_pair_entry_ratio_to_fraction (gdouble ratio, + gdouble *numerator, + gdouble *denominator) +{ + gdouble remainder, next_cf; + gint p0, p1, p2; + gint q0, q1, q2; + + /* calculate the continued fraction to approximate the desired ratio */ + + p0 = 1; + q0 = 0; + p1 = floor (ratio); + q1 = 1; + + remainder = ratio - p1; + + while (fabs (remainder) >= 0.0001 && + fabs (((gdouble) p1 / q1) - ratio) > 0.0001) + { + remainder = 1.0 / remainder; + + next_cf = floor (remainder); + + p2 = next_cf * p1 + p0; + q2 = next_cf * q1 + q0; + + /* remember the last two fractions */ + p0 = p1; + q0 = q1; + p1 = p2; + q1 = q2; + + remainder = remainder - next_cf; + } + + /* only use the calculated fraction if it is "reasonable" */ + if (p1 < 1000 && q1 < 1000) + { + *numerator = p1; + *denominator = q1; + } + else + { + *numerator = ratio; + *denominator = 1.0; + } +} + +/** + * gimp_number_pair_entry_set_ratio: + * @entry: A #GimpNumberPairEntry widget. + * @ratio: Ratio to set in the widget. + * + * Sets the numbers of the #GimpNumberPairEntry to have the desired + * ratio. If the new ratio is different than the previous ratio, the + * "ratio-changed" signal is emitted. + * + * An attempt is made to convert the decimal number into a fraction + * with left_number and right_number < 1000. + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_set_ratio (GimpNumberPairEntry *entry, + gdouble ratio) +{ + gdouble numerator; + gdouble denominator; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + gimp_number_pair_entry_ratio_to_fraction (ratio, &numerator, &denominator); + + gimp_number_pair_entry_set_values (entry, numerator, denominator); +} + +/** + * gimp_number_pair_entry_get_ratio: + * @entry: A #GimpNumberPairEntry widget. + * + * Retrieves the ratio of the numbers displayed by a #GimpNumberPairEntry. + * + * Returns: The ratio value. + * + * Since: 2.4 + **/ +gdouble +gimp_number_pair_entry_get_ratio (GimpNumberPairEntry *entry) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), 1.0); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + return priv->left_number / priv->right_number; +} + +/** + * gimp_number_pair_entry_set_values: + * @entry: A #GimpNumberPairEntry widget. + * @left: Left number in the entry. + * @right: Right number in the entry. + * + * Forces setting the numbers displayed by a #GimpNumberPairEntry, + * ignoring if the user has set his/her own value. The state of + * user-override will not be changed. + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_set_values (GimpNumberPairEntry *entry, + gdouble left, + gdouble right) +{ + GimpNumberPairEntryPrivate *priv; + GimpAspectType old_aspect; + gdouble old_ratio; + gdouble old_left_number; + gdouble old_right_number; + gboolean numbers_changed = FALSE; + gboolean ratio_changed = FALSE; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + /* Store current values */ + + old_left_number = priv->left_number; + old_right_number = priv->right_number; + old_ratio = gimp_number_pair_entry_get_ratio (entry); + old_aspect = gimp_number_pair_entry_get_aspect (entry); + + + /* Freeze notification */ + + g_object_freeze_notify (G_OBJECT (entry)); + + + /* Set the new numbers and update the entry */ + + priv->left_number = left; + priv->right_number = right; + + g_object_notify (G_OBJECT (entry), "left-number"); + g_object_notify (G_OBJECT (entry), "right-number"); + + gimp_number_pair_entry_update_text (entry); + + + /* Find out what has changed */ + + if (fabs (old_ratio - gimp_number_pair_entry_get_ratio (entry)) > EPSILON) + { + g_object_notify (G_OBJECT (entry), "ratio"); + + ratio_changed = TRUE; + + if (old_aspect != gimp_number_pair_entry_get_aspect (entry)) + g_object_notify (G_OBJECT (entry), "aspect"); + } + + if (old_left_number != priv->left_number || + old_right_number != priv->right_number) + { + numbers_changed = TRUE; + } + + + /* Thaw */ + + g_object_thaw_notify (G_OBJECT (entry)); + + + /* Emit relevant signals */ + + if (numbers_changed) + g_signal_emit (entry, entry_signals[NUMBERS_CHANGED], 0); + + if (ratio_changed) + g_signal_emit (entry, entry_signals[RATIO_CHANGED], 0); +} + +/** + * gimp_number_pair_entry_get_values: + * @entry: A #GimpNumberPairEntry widget. + * @left: Pointer of where to store the left number (may be %NULL). + * @right: Pointer of to store the right number (may be %NULL). + * + * Gets the numbers displayed by a #GimpNumberPairEntry. + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_get_values (GimpNumberPairEntry *entry, + gdouble *left, + gdouble *right) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + if (left != NULL) + *left = priv->left_number; + + if (right != NULL) + *right = priv->right_number; +} + +/** + * gimp_number_pair_entry_set_default_text: + * @entry: A #GimpNumberPairEntry widget. + * @string: Default string. + * + * Causes the entry to show a given string when in automatic mode, + * instead of the default numbers. The only thing this does is making + * the #GimpNumberPairEntry showing this string, the internal state + * and API calls are not affected. + * + * Set the default string to %NULL to display default values as + * normal. + * + * Since: 2.4 + */ +void +gimp_number_pair_entry_set_default_text (GimpNumberPairEntry *entry, + const gchar *string) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + g_free (priv->default_text); + priv->default_text = g_strdup (string); + + gimp_number_pair_entry_update_text (entry); + + g_object_notify (G_OBJECT (entry), "default-text"); +} + +/** + * gimp_number_pair_entry_get_default_text: + * @entry: A #GimpNumberPairEntry widget. + * + * Returns: the string manually set to be shown, or %NULL if values are + * shown in a normal fashion. + * + * Since: 2.4 + */ +const gchar * +gimp_number_pair_entry_get_default_text (GimpNumberPairEntry *entry) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), NULL); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + return priv->default_text; +} + +/** + * gimp_number_pair_entry_set_aspect: + * @entry: A #GimpNumberPairEntry widget. + * @aspect: The new aspect. + * + * Sets the aspect of the ratio by swapping the left_number and + * right_number if necessary (or setting them to 1.0 in case that + * @aspect is %GIMP_ASPECT_SQUARE). + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_set_aspect (GimpNumberPairEntry *entry, + GimpAspectType aspect) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + if (gimp_number_pair_entry_get_aspect (entry) == aspect) + return; + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + switch (aspect) + { + case GIMP_ASPECT_SQUARE: + gimp_number_pair_entry_set_values (entry, + priv->left_number, + priv->left_number); + break; + + case GIMP_ASPECT_LANDSCAPE: + case GIMP_ASPECT_PORTRAIT: + gimp_number_pair_entry_set_values (entry, + priv->right_number, + priv->left_number); + break; + } +} + +/** + * gimp_number_pair_entry_get_aspect: + * @entry: A #GimpNumberPairEntry widget. + * + * Gets the aspect of the ratio displayed by a #GimpNumberPairEntry. + * + * Returns: The entry's current aspect. + * + * Since: 2.4 + **/ +GimpAspectType +gimp_number_pair_entry_get_aspect (GimpNumberPairEntry *entry) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), GIMP_ASPECT_SQUARE); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + if (priv->left_number > priv->right_number) + { + return GIMP_ASPECT_LANDSCAPE; + } + else if (priv->left_number < priv->right_number) + { + return GIMP_ASPECT_PORTRAIT; + } + else + { + return GIMP_ASPECT_SQUARE; + } +} + +static void +gimp_number_pair_entry_modify_font (GimpNumberPairEntry *entry, + gboolean italic) +{ + GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + GtkRcStyle *rc_style; + + if (priv->font_italic == italic) + return; + + rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (entry)); + + if (! rc_style->font_desc) + { + PangoContext *context; + PangoFontDescription *font_desc; + + context = gtk_widget_get_pango_context (GTK_WIDGET (entry)); + font_desc = pango_context_get_font_description (context); + + rc_style->font_desc = pango_font_description_copy (font_desc); + } + + pango_font_description_set_style (rc_style->font_desc, + italic ? + PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); + + gtk_widget_modify_style (GTK_WIDGET (entry), rc_style); + + gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + ! italic); + + priv->font_italic = italic; +} + + +/** + * gimp_number_pair_entry_set_user_override: + * @entry: A #GimpNumberPairEntry widget. + * @user_override: %TRUE sets the entry in user overridden mode, + * %FALSE disables. + * + * When the entry is not in user overridden mode, the values will + * change when the default values are changed. When in user overridden + * mode, setting default values will not affect the active values. + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_set_user_override (GimpNumberPairEntry *entry, + gboolean user_override) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + priv->user_override = user_override; + + if (! user_override) + { + gimp_number_pair_entry_set_default_values (entry, + priv->default_left_number, + priv->default_right_number); + } + + gimp_number_pair_entry_modify_font (entry, ! user_override); + + g_object_notify (G_OBJECT (entry), "user-override"); +} + +/** + * gimp_number_pair_entry_get_user_override: + * @entry: A #GimpNumberPairEntry widget. + * + * Returns: Whether or not the the widget is in user overridden mode. + * + * Since: 2.4 + **/ +gboolean +gimp_number_pair_entry_get_user_override (GimpNumberPairEntry *entry) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), FALSE); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + return priv->user_override; +} + +static void +gimp_number_pair_entry_changed (GimpNumberPairEntry *entry) +{ + gimp_number_pair_entry_modify_font (entry, FALSE); +} + +static void +gimp_number_pair_entry_icon_press (GimpNumberPairEntry *entry) +{ + gimp_number_pair_entry_set_user_override (entry, FALSE); + + gtk_editable_set_position (GTK_EDITABLE (entry), -1); +} + +static gboolean +gimp_number_pair_entry_events (GtkWidget *widget, + GdkEvent *event) +{ + GimpNumberPairEntry *entry; + GimpNumberPairEntryPrivate *priv; + gboolean force_user_override; + + entry = GIMP_NUMBER_PAIR_ENTRY (widget); + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + force_user_override = FALSE; + + switch (event->type) + { + case GDK_KEY_PRESS: + { + GdkEventKey *kevent = (GdkEventKey *) event; + + if (kevent->keyval != GDK_KEY_Return && + kevent->keyval != GDK_KEY_KP_Enter && + kevent->keyval != GDK_KEY_ISO_Enter) + break; + + /* If parsing was done due to widgets focus being lost, we only change + * to user-override mode if the values differ from the default ones. If + * Return was pressed however, we always switch to user-override mode. + */ + force_user_override = TRUE; + } + /* Fall through */ + + case GDK_FOCUS_CHANGE: + { + const gchar *text; + ParseResult parse_result; + gdouble left_value; + gdouble right_value; + + text = gtk_entry_get_text (GTK_ENTRY (entry)); + + parse_result = gimp_number_pair_entry_parse_text (entry, + text, + &left_value, + &right_value); + switch (parse_result) + { + case PARSE_VALID: + { + if (priv->left_number != left_value || + priv->right_number != right_value || + force_user_override) + { + gimp_number_pair_entry_set_values (entry, + left_value, + right_value); + + gimp_number_pair_entry_set_user_override (entry, TRUE); + } + } + break; + + case PARSE_CLEAR: + gimp_number_pair_entry_set_user_override (entry, FALSE); + break; + + default: + break; + } + + /* Make sure the entry text is up to date */ + + gimp_number_pair_entry_update_text (entry); + + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + } + break; + + default: + break; + } + + return FALSE; +} + +/** + * gimp_number_pair_entry_strdup_number_pair_string: + * @entry: + * @left_number: + * @right_number: + * + * Returns: allocated data, must be g_free:d. + **/ +static gchar * +gimp_number_pair_entry_strdup_number_pair_string (GimpNumberPairEntry *entry, + gdouble left_number, + gdouble right_number) +{ + GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + gchar sep[8]; + gint len; + + if (priv->num_separators > 0) + len = g_unichar_to_utf8 (priv->separators[0], sep); + else + len = g_unichar_to_utf8 (DEFAULT_SEPARATOR, sep); + + sep[len] = '\0'; + + return g_strdup_printf ("%g%s%g", left_number, sep, right_number); +} + +static void +gimp_number_pair_entry_update_text (GimpNumberPairEntry *entry) +{ + GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + gchar *buffer; + + if (! priv->user_override && + priv->default_text != NULL) + { + /* Instead of the numbers, show the string explicitly set by a + * client to show when in automatic mode. + */ + buffer = g_strdup (priv->default_text); + } + else + { + buffer = gimp_number_pair_entry_strdup_number_pair_string (entry, + priv->left_number, + priv->right_number); + } + + g_signal_handlers_block_by_func (entry, + gimp_number_pair_entry_changed, NULL); + + gtk_entry_set_text (GTK_ENTRY (entry), buffer); + g_free (buffer); + + g_signal_handlers_unblock_by_func (entry, + gimp_number_pair_entry_changed, NULL); + + gimp_number_pair_entry_modify_font (entry, ! priv->user_override); +} + +static gboolean +gimp_number_pair_entry_valid_separator (GimpNumberPairEntry *entry, + gunichar candidate) +{ + GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + if (priv->num_separators > 0) + { + gint i; + + for (i = 0; i < priv->num_separators; i++) + if (priv->separators[i] == candidate) + return TRUE; + } + else if (candidate == DEFAULT_SEPARATOR) + { + return TRUE; + } + + return FALSE; +} + +static ParseResult +gimp_number_pair_entry_parse_text (GimpNumberPairEntry *entry, + const gchar *text, + gdouble *left_value, + gdouble *right_value) +{ + GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + gdouble new_left_number; + gdouble new_right_number; + gboolean simplify = FALSE; + gchar *end; + + /* skip over whitespace */ + while (g_unichar_isspace (g_utf8_get_char (text))) + text = g_utf8_next_char (text); + + /* check if clear */ + if (! *text) + return PARSE_CLEAR; + + /* try to parse a number */ + new_left_number = strtod (text, &end); + + if (end == text) + return PARSE_INVALID; + else + text = end; + + /* skip over whitespace */ + while (g_unichar_isspace (g_utf8_get_char (text))) + text = g_utf8_next_char (text); + + /* check for a valid separator */ + if (! gimp_number_pair_entry_valid_separator (entry, g_utf8_get_char (text))) + return PARSE_INVALID; + else + text = g_utf8_next_char (text); + + /* try to parse another number */ + new_right_number = strtod (text, &end); + + if (end == text) + return PARSE_INVALID; + else + text = end; + + /* skip over whitespace */ + while (g_unichar_isspace (g_utf8_get_char (text))) + text = g_utf8_next_char (text); + + /* check for the simplification char */ + if (g_utf8_get_char (text) == SIMPLIFICATION_CHAR) + { + simplify = priv->allow_simplification; + text = g_utf8_next_char (text); + } + + /* skip over whitespace */ + while (g_unichar_isspace (g_utf8_get_char (text))) + text = g_utf8_next_char (text); + + /* check for trailing garbage */ + if (*text) + return PARSE_INVALID; + + if (! gimp_number_pair_entry_numbers_in_range (entry, + new_left_number, + new_right_number)) + return PARSE_INVALID; + + if (simplify && new_right_number != 0.0) + { + gimp_number_pair_entry_ratio_to_fraction (new_left_number / + new_right_number, + left_value, + right_value); + } + else + { + *left_value = new_left_number; + *right_value = new_right_number; + } + + return PARSE_VALID; +} + +static void +gimp_number_pair_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpNumberPairEntry *entry = GIMP_NUMBER_PAIR_ENTRY (object); + GimpNumberPairEntryPrivate *priv; + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + switch (property_id) + { + case PROP_LEFT_NUMBER: + gimp_number_pair_entry_set_values (entry, + g_value_get_double (value), + priv->right_number); + break; + case PROP_RIGHT_NUMBER: + gimp_number_pair_entry_set_values (entry, + priv->left_number, + g_value_get_double (value)); + break; + case PROP_DEFAULT_LEFT_NUMBER: + gimp_number_pair_entry_set_default_values (entry, + g_value_get_double (value), + priv->default_right_number); + break; + case PROP_DEFAULT_RIGHT_NUMBER: + gimp_number_pair_entry_set_default_values (entry, + priv->default_left_number, + g_value_get_double (value)); + break; + case PROP_USER_OVERRIDE: + gimp_number_pair_entry_set_user_override (entry, + g_value_get_boolean (value)); + break; + case PROP_SEPARATORS: + g_free (priv->separators); + priv->num_separators = 0; + if (g_value_get_string (value)) + priv->separators = g_utf8_to_ucs4 (g_value_get_string (value), -1, + NULL, &priv->num_separators, NULL); + else + priv->separators = NULL; + break; + case PROP_DEFAULT_TEXT: + gimp_number_pair_entry_set_default_text (entry, + g_value_get_string (value)); + break; + case PROP_ALLOW_SIMPLIFICATION: + priv->allow_simplification = g_value_get_boolean (value); + break; + case PROP_MIN_VALID_VALUE: + priv->min_valid_value = g_value_get_double (value); + break; + case PROP_MAX_VALID_VALUE: + priv->max_valid_value = g_value_get_double (value); + break; + case PROP_RATIO: + gimp_number_pair_entry_set_ratio (entry, g_value_get_double (value)); + break; + case PROP_ASPECT: + gimp_number_pair_entry_set_aspect (entry, g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_number_pair_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpNumberPairEntry *entry = GIMP_NUMBER_PAIR_ENTRY (object); + GimpNumberPairEntryPrivate *priv; + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + switch (property_id) + { + case PROP_LEFT_NUMBER: + g_value_set_double (value, priv->left_number); + break; + case PROP_RIGHT_NUMBER: + g_value_set_double (value, priv->right_number); + break; + case PROP_DEFAULT_LEFT_NUMBER: + g_value_set_double (value, priv->default_left_number); + break; + case PROP_DEFAULT_RIGHT_NUMBER: + g_value_set_double (value, priv->default_right_number); + break; + case PROP_USER_OVERRIDE: + g_value_set_boolean (value, priv->user_override); + break; + case PROP_SEPARATORS: + g_value_take_string (value, + g_ucs4_to_utf8 (priv->separators, + priv->num_separators, + NULL, NULL, NULL)); + break; + case PROP_ALLOW_SIMPLIFICATION: + g_value_set_boolean (value, priv->allow_simplification); + break; + case PROP_DEFAULT_TEXT: + g_value_set_string (value, priv->default_text); + break; + case PROP_MIN_VALID_VALUE: + g_value_set_double (value, priv->min_valid_value); + break; + case PROP_MAX_VALID_VALUE: + g_value_set_double (value, priv->max_valid_value); + break; + case PROP_RATIO: + g_value_set_double (value, gimp_number_pair_entry_get_ratio (entry)); + break; + case PROP_ASPECT: + g_value_set_enum (value, gimp_number_pair_entry_get_aspect (entry)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_number_pair_entry_set_default_values: + * @entry: A #GimpNumberPairEntry widget. + * @left: Default left value in the entry. + * @right: Default right value in the entry. + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_set_default_values (GimpNumberPairEntry *entry, + gdouble left, + gdouble right) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + priv->default_left_number = left; + priv->default_right_number = right; + + if (! priv->user_override) + { + gimp_number_pair_entry_set_values (entry, + priv->default_left_number, + priv->default_right_number); + } +} + +/** + * gimp_number_pair_entry_get_default_values: + * @entry: A #GimpNumberPairEntry widget. + * @left: Pointer of where to put left value. + * @right: Pointer of where to put right value. + * + * Since: 2.4 + **/ +void +gimp_number_pair_entry_get_default_values (GimpNumberPairEntry *entry, + gdouble *left, + gdouble *right) +{ + GimpNumberPairEntryPrivate *priv; + + g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry)); + + priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + if (left != NULL) + *left = priv->default_left_number; + + if (right != NULL) + *right = priv->default_right_number; +} + +static gboolean +gimp_number_pair_entry_numbers_in_range (GimpNumberPairEntry *entry, + gdouble left_number, + gdouble right_number) +{ + GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry); + + return (left_number >= priv->min_valid_value && + left_number <= priv->max_valid_value && + right_number >= priv->min_valid_value && + right_number <= priv->max_valid_value); +} diff --git a/libgimpwidgets/gimpnumberpairentry.h b/libgimpwidgets/gimpnumberpairentry.h new file mode 100644 index 0000000..fb46c0b --- /dev/null +++ b/libgimpwidgets/gimpnumberpairentry.h @@ -0,0 +1,104 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpratioentry.h + * Copyright (C) 2006 Simon Budig <simon@gimp.org> + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * Copyright (C) 2007 Martin Nordholts <martin@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_NUMBER_PAIR_ENTRY_H__ +#define __GIMP_NUMBER_PAIR_ENTRY_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_NUMBER_PAIR_ENTRY (gimp_number_pair_entry_get_type ()) +#define GIMP_NUMBER_PAIR_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_NUMBER_PAIR_ENTRY, GimpNumberPairEntry)) +#define GIMP_NUMBER_PAIR_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_NUMBER_PAIR_ENTRY, GimpNumberPairEntryClass)) +#define GIMP_IS_NUMBER_PAIR_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_NUMBER_PAIR_ENTRY)) +#define GIMP_IS_NUMBER_PAIR_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_NUMBER_PAIR_ENTRY)) +#define GIMP_NUMBER_PAIR_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_NUMBER_PAIR_AREA, GimpNumberPairEntryClass)) + + +typedef struct _GimpNumberPairEntryClass GimpNumberPairEntryClass; + + +struct _GimpNumberPairEntry +{ + GtkEntry parent_instance; + + gpointer priv; +}; + +struct _GimpNumberPairEntryClass +{ + GtkEntryClass parent_class; + + void (* numbers_changed) (GimpNumberPairEntry *entry); + void (* ratio_changed) (GimpNumberPairEntry *entry); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_number_pair_entry_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_number_pair_entry_new (const gchar *separators, + gboolean allow_simplification, + gdouble min_valid_value, + gdouble max_valid_value); +void gimp_number_pair_entry_set_default_values (GimpNumberPairEntry *entry, + gdouble left, + gdouble right); +void gimp_number_pair_entry_get_default_values (GimpNumberPairEntry *entry, + gdouble *left, + gdouble *right); +void gimp_number_pair_entry_set_values (GimpNumberPairEntry *entry, + gdouble left, + gdouble right); +void gimp_number_pair_entry_get_values (GimpNumberPairEntry *entry, + gdouble *left, + gdouble *right); + +void gimp_number_pair_entry_set_default_text (GimpNumberPairEntry *entry, + const gchar *string); +const gchar * gimp_number_pair_entry_get_default_text (GimpNumberPairEntry *entry); + +void gimp_number_pair_entry_set_ratio (GimpNumberPairEntry *entry, + gdouble ratio); +gdouble gimp_number_pair_entry_get_ratio (GimpNumberPairEntry *entry); + +void gimp_number_pair_entry_set_aspect (GimpNumberPairEntry *entry, + GimpAspectType aspect); +GimpAspectType gimp_number_pair_entry_get_aspect (GimpNumberPairEntry *entry); + +void gimp_number_pair_entry_set_user_override (GimpNumberPairEntry *entry, + gboolean user_override); +gboolean gimp_number_pair_entry_get_user_override (GimpNumberPairEntry *entry); + + +G_END_DECLS + +#endif /* __GIMP_NUMBER_PAIR_ENTRY_H__ */ diff --git a/libgimpwidgets/gimpoffsetarea.c b/libgimpwidgets/gimpoffsetarea.c new file mode 100644 index 0000000..3cf9521 --- /dev/null +++ b/libgimpwidgets/gimpoffsetarea.c @@ -0,0 +1,506 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpoffsetarea.c + * Copyright (C) 2001 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpwidgetsmarshal.h" +#include "gimpoffsetarea.h" + + +/** + * SECTION: gimpoffsetarea + * @title: GimpOffsetArea + * @short_description: Widget to control image offsets. + * + * Widget to control image offsets. + **/ + + +#define DRAWING_AREA_SIZE 200 + + +enum +{ + OFFSETS_CHANGED, + LAST_SIGNAL +}; + + +static void gimp_offset_area_resize (GimpOffsetArea *area); + +static void gimp_offset_area_realize (GtkWidget *widget); +static void gimp_offset_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_offset_area_event (GtkWidget *widget, + GdkEvent *event); +static gboolean gimp_offset_area_expose_event (GtkWidget *widget, + GdkEventExpose *eevent); + + +G_DEFINE_TYPE (GimpOffsetArea, gimp_offset_area, GTK_TYPE_DRAWING_AREA) + +#define parent_class gimp_offset_area_parent_class + +static guint gimp_offset_area_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_offset_area_class_init (GimpOffsetAreaClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gimp_offset_area_signals[OFFSETS_CHANGED] = + g_signal_new ("offsets-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpOffsetAreaClass, offsets_changed), + NULL, NULL, + _gimp_widgets_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + widget_class->size_allocate = gimp_offset_area_size_allocate; + widget_class->realize = gimp_offset_area_realize; + widget_class->event = gimp_offset_area_event; + widget_class->expose_event = gimp_offset_area_expose_event; +} + +static void +gimp_offset_area_init (GimpOffsetArea *area) +{ + area->orig_width = 0; + area->orig_height = 0; + area->width = 0; + area->height = 0; + area->offset_x = 0; + area->offset_y = 0; + area->display_ratio_x = 1.0; + area->display_ratio_y = 1.0; + + gtk_widget_add_events (GTK_WIDGET (area), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON1_MOTION_MASK); +} + +/** + * gimp_offset_area_new: + * @orig_width: the original width + * @orig_height: the original height + * + * Creates a new #GimpOffsetArea widget. A #GimpOffsetArea can be used + * when resizing an image or a drawable to allow the user to interactively + * specify the new offsets. + * + * Return value: the new #GimpOffsetArea widget. + **/ +GtkWidget * +gimp_offset_area_new (gint orig_width, + gint orig_height) +{ + GimpOffsetArea *area; + + g_return_val_if_fail (orig_width > 0, NULL); + g_return_val_if_fail (orig_height > 0, NULL); + + area = g_object_new (GIMP_TYPE_OFFSET_AREA, NULL); + + area->orig_width = area->width = orig_width; + area->orig_height = area->height = orig_height; + + gimp_offset_area_resize (area); + + return GTK_WIDGET (area); +} + +/** + * gimp_offset_area_set_pixbuf: + * @offset_area: a #GimpOffsetArea. + * @pixbuf: a #GdkPixbuf. + * + * Sets the pixbuf which represents the original image/drawable which + * is being offset. + * + * Since: 2.2 + **/ +void +gimp_offset_area_set_pixbuf (GimpOffsetArea *area, + GdkPixbuf *pixbuf) +{ + g_return_if_fail (GIMP_IS_OFFSET_AREA (area)); + g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); + + g_object_set_data_full (G_OBJECT (area), "pixbuf", + gdk_pixbuf_copy (pixbuf), + (GDestroyNotify) g_object_unref); + + gtk_widget_queue_draw (GTK_WIDGET (area)); +} + +/** + * gimp_offset_area_set_size: + * @offset_area: a #GimpOffsetArea. + * @width: the new width + * @height: the new height + * + * Sets the size of the image/drawable displayed by the #GimpOffsetArea. + * If the offsets change as a result of this change, the "offsets-changed" + * signal is emitted. + **/ +void +gimp_offset_area_set_size (GimpOffsetArea *area, + gint width, + gint height) +{ + g_return_if_fail (GIMP_IS_OFFSET_AREA (area)); + g_return_if_fail (width > 0 && height > 0); + + if (area->width != width || area->height != height) + { + gint offset_x; + gint offset_y; + + area->width = width; + area->height = height; + + if (area->orig_width <= area->width) + offset_x = CLAMP (area->offset_x, 0, area->width - area->orig_width); + else + offset_x = CLAMP (area->offset_x, area->width - area->orig_width, 0); + + if (area->orig_height <= area->height) + offset_y = CLAMP (area->offset_y, 0, area->height - area->orig_height); + else + offset_y = CLAMP (area->offset_y, area->height - area->orig_height, 0); + + if (offset_x != area->offset_x || offset_y != area->offset_y) + { + area->offset_x = offset_x; + area->offset_y = offset_y; + + g_signal_emit (area, + gimp_offset_area_signals[OFFSETS_CHANGED], 0, + offset_x, offset_y); + } + + gimp_offset_area_resize (area); + } +} + +/** + * gimp_offset_area_set_offsets: + * @offset_area: a #GimpOffsetArea. + * @offset_x: the X offset + * @offset_y: the Y offset + * + * Sets the offsets of the image/drawable displayed by the #GimpOffsetArea. + * It does not emit the "offsets-changed" signal. + **/ +void +gimp_offset_area_set_offsets (GimpOffsetArea *area, + gint offset_x, + gint offset_y) +{ + g_return_if_fail (GIMP_IS_OFFSET_AREA (area)); + + if (area->offset_x != offset_x || area->offset_y != offset_y) + { + if (area->orig_width <= area->width) + area->offset_x = CLAMP (offset_x, 0, area->width - area->orig_width); + else + area->offset_x = CLAMP (offset_x, area->width - area->orig_width, 0); + + if (area->orig_height <= area->height) + area->offset_y = CLAMP (offset_y, 0, area->height - area->orig_height); + else + area->offset_y = CLAMP (offset_y, area->height - area->orig_height, 0); + + gtk_widget_queue_draw (GTK_WIDGET (area)); + } +} + +static void +gimp_offset_area_resize (GimpOffsetArea *area) +{ + gint width; + gint height; + gdouble ratio; + + if (area->orig_width == 0 || area->orig_height == 0) + return; + + if (area->orig_width <= area->width) + width = area->width; + else + width = area->orig_width * 2 - area->width; + + if (area->orig_height <= area->height) + height = area->height; + else + height = area->orig_height * 2 - area->height; + + ratio = (gdouble) DRAWING_AREA_SIZE / (gdouble) MAX (width, height); + + width = ratio * (gdouble) width; + height = ratio * (gdouble) height; + + gtk_widget_set_size_request (GTK_WIDGET (area), width, height); + gtk_widget_queue_resize (GTK_WIDGET (area)); +} + +static void +gimp_offset_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpOffsetArea *area = GIMP_OFFSET_AREA (widget); + GdkPixbuf *pixbuf; + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + area->display_ratio_x = ((gdouble) allocation->width / + ((area->orig_width <= area->width) ? + area->width : + area->orig_width * 2 - area->width)); + + area->display_ratio_y = ((gdouble) allocation->height / + ((area->orig_height <= area->height) ? + area->height : + area->orig_height * 2 - area->height)); + + pixbuf = g_object_get_data (G_OBJECT (area), "pixbuf"); + + if (pixbuf) + { + GdkPixbuf *copy; + gint pixbuf_width; + gint pixbuf_height; + + pixbuf_width = area->display_ratio_x * area->orig_width; + pixbuf_width = MAX (pixbuf_width, 1); + + pixbuf_height = area->display_ratio_y * area->orig_height; + pixbuf_height = MAX (pixbuf_height, 1); + + copy = g_object_get_data (G_OBJECT (area), "pixbuf-copy"); + + if (copy && + (pixbuf_width != gdk_pixbuf_get_width (copy) || + pixbuf_height != gdk_pixbuf_get_height (copy))) + { + copy = NULL; + } + + if (! copy) + { + copy = gdk_pixbuf_scale_simple (pixbuf, pixbuf_width, pixbuf_height, + GDK_INTERP_NEAREST); + + g_object_set_data_full (G_OBJECT (area), "pixbuf-copy", + copy, (GDestroyNotify) g_object_unref); + } + } +} + +static void +gimp_offset_area_realize (GtkWidget *widget) +{ + GdkCursor *cursor; + + GTK_WIDGET_CLASS (parent_class)->realize (widget); + + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), + GDK_FLEUR); + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_cursor_unref (cursor); +} + +static gboolean +gimp_offset_area_event (GtkWidget *widget, + GdkEvent *event) +{ + static gint orig_offset_x = 0; + static gint orig_offset_y = 0; + static gint start_x = 0; + static gint start_y = 0; + + GimpOffsetArea *area = GIMP_OFFSET_AREA (widget); + gint offset_x; + gint offset_y; + + if (area->orig_width == 0 || area->orig_height == 0) + return FALSE; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) + { + gtk_grab_add (widget); + + orig_offset_x = area->offset_x; + orig_offset_y = area->offset_y; + start_x = event->button.x; + start_y = event->button.y; + } + break; + + case GDK_MOTION_NOTIFY: + offset_x = (orig_offset_x + + (event->motion.x - start_x) / area->display_ratio_x); + offset_y = (orig_offset_y + + (event->motion.y - start_y) / area->display_ratio_y); + + if (area->offset_x != offset_x || area->offset_y != offset_y) + { + gimp_offset_area_set_offsets (area, offset_x, offset_y); + + g_signal_emit (area, + gimp_offset_area_signals[OFFSETS_CHANGED], 0, + area->offset_x, area->offset_y); + } + break; + + case GDK_BUTTON_RELEASE: + if (event->button.button == 1) + { + gtk_grab_remove (widget); + + start_x = start_y = 0; + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +static gboolean +gimp_offset_area_expose_event (GtkWidget *widget, + GdkEventExpose *eevent) +{ + GimpOffsetArea *area = GIMP_OFFSET_AREA (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GdkWindow *window = gtk_widget_get_window (widget); + cairo_t *cr; + GtkAllocation allocation; + GdkPixbuf *pixbuf; + gint w, h; + gint x, y; + + cr = gdk_cairo_create (eevent->window); + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + x = (area->display_ratio_x * + ((area->orig_width <= area->width) ? + area->offset_x : + area->offset_x + area->orig_width - area->width)); + + y = (area->display_ratio_y * + ((area->orig_height <= area->height) ? + area->offset_y : + area->offset_y + area->orig_height - area->height)); + + w = area->display_ratio_x * area->orig_width; + w = MAX (w, 1); + + h = area->display_ratio_y * area->orig_height; + h = MAX (h, 1); + + pixbuf = g_object_get_data (G_OBJECT (widget), "pixbuf-copy"); + + if (pixbuf) + { + gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y); + cairo_paint (cr); + + cairo_rectangle (cr, x + 0.5, y + 0.5, w - 1, h - 1); + cairo_set_line_width (cr, 1.0); + gdk_cairo_set_source_color (cr, &style->black); + cairo_stroke (cr); + } + else + { + gtk_paint_shadow (style, window, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + NULL, widget, NULL, + x, y, w, h); + } + + if (area->orig_width > area->width || area->orig_height > area->height) + { + gint line_width; + + if (area->orig_width > area->width) + { + x = area->display_ratio_x * (area->orig_width - area->width); + w = area->display_ratio_x * area->width; + } + else + { + x = -1; + w = allocation.width + 2; + } + + if (area->orig_height > area->height) + { + y = area->display_ratio_y * (area->orig_height - area->height); + h = area->display_ratio_y * area->height; + } + else + { + y = -1; + h = allocation.height + 2; + } + + w = MAX (w, 1); + h = MAX (h, 1); + + line_width = MIN (3, MIN (w, h)); + + cairo_rectangle (cr, + x + line_width / 2.0, + y + line_width / 2.0, + MAX (w - line_width, 1), + MAX (h - line_width, 1)); + + cairo_set_line_width (cr, line_width); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke (cr); + } + + cairo_destroy (cr); + + return FALSE; +} diff --git a/libgimpwidgets/gimpoffsetarea.h b/libgimpwidgets/gimpoffsetarea.h new file mode 100644 index 0000000..5c5705a --- /dev/null +++ b/libgimpwidgets/gimpoffsetarea.h @@ -0,0 +1,91 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpoffsetarea.h + * Copyright (C) 2001 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_OFFSET_AREA_H__ +#define __GIMP_OFFSET_AREA_H__ + +G_BEGIN_DECLS + + +/* For information look into the C source or the html documentation */ + +#define GIMP_TYPE_OFFSET_AREA (gimp_offset_area_get_type ()) +#define GIMP_OFFSET_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OFFSET_AREA, GimpOffsetArea)) +#define GIMP_OFFSET_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OFFSET_AREA, GimpOffsetAreaClass)) +#define GIMP_IS_OFFSET_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OFFSET_AREA)) +#define GIMP_IS_OFFSET_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OFFSET_AREA)) +#define GIMP_OFFSET_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OFFSET_AREA, GimpOffsetAreaClass)) + + +typedef struct _GimpOffsetAreaClass GimpOffsetAreaClass; + +struct _GimpOffsetArea +{ + GtkDrawingArea parent_instance; + + gint orig_width; + gint orig_height; + gint width; + gint height; + gint offset_x; + gint offset_y; + gdouble display_ratio_x; + gdouble display_ratio_y; +}; + +struct _GimpOffsetAreaClass +{ + GtkDrawingAreaClass parent_class; + + void (* offsets_changed) (GimpOffsetArea *offset_area, + gint offset_x, + gint offset_y); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_offset_area_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_offset_area_new (gint orig_width, + gint orig_height); +void gimp_offset_area_set_pixbuf (GimpOffsetArea *offset_area, + GdkPixbuf *pixbuf); + +void gimp_offset_area_set_size (GimpOffsetArea *offset_area, + gint width, + gint height); +void gimp_offset_area_set_offsets (GimpOffsetArea *offset_area, + gint offset_x, + gint offset_y); + + +G_END_DECLS + +#endif /* __GIMP_OFFSET_AREA_H__ */ diff --git a/libgimpwidgets/gimpoldwidgets.c b/libgimpwidgets/gimpoldwidgets.c new file mode 100644 index 0000000..0b7bcb3 --- /dev/null +++ b/libgimpwidgets/gimpoldwidgets.c @@ -0,0 +1,650 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpoldwidgets.c + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +/* FIXME: #undef GTK_DISABLE_DEPRECATED */ +#undef GTK_DISABLE_DEPRECATED +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpoldwidgets.h" +#include "gimppixmap.h" +#include "gimpunitmenu.h" +#include "gimp3migration.h" + + +/** + * SECTION: gimpoldwidgets + * @title: GimpOldWidgets + * @short_description: Old API that is still available but declared + * as deprecated. + * @see_also: #GimpIntComboBox + * + * These functions are not defined if you #define GIMP_DISABLE_DEPRECATED. + **/ + + +/* + * Widget Constructors + */ + +/** + * gimp_option_menu_new: + * @menu_only: %TRUE if the function should return a #GtkMenu only. + * @...: A %NULL-terminated @va_list describing the menu items. + * + * Convenience function to create a #GtkOptionMenu or a #GtkMenu. + * + * Returns: A #GtkOptionMenu or a #GtkMenu (depending on @menu_only). + **/ +GtkWidget * +gimp_option_menu_new (gboolean menu_only, + + /* specify menu items as va_list: + * const gchar *label, + * GCallback callback, + * gpointer callback_data, + * gpointer item_data, + * GtkWidget **widget_ptr, + * gboolean active + */ + + ...) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + /* menu item variables */ + const gchar *label; + GCallback callback; + gpointer callback_data; + gpointer item_data; + GtkWidget **widget_ptr; + gboolean active; + + va_list args; + gint i; + gint initial_index; + + menu = gtk_menu_new (); + + /* create the menu items */ + initial_index = 0; + + va_start (args, menu_only); + label = va_arg (args, const gchar *); + + for (i = 0; label; i++) + { + callback = va_arg (args, GCallback); + callback_data = va_arg (args, gpointer); + item_data = va_arg (args, gpointer); + widget_ptr = va_arg (args, GtkWidget **); + active = va_arg (args, gboolean); + + if (strcmp (label, "---")) + { + menuitem = gtk_menu_item_new_with_label (label); + + g_signal_connect (menuitem, "activate", + callback, + callback_data); + + if (item_data) + { + g_object_set_data (G_OBJECT (menuitem), "gimp-item-data", + item_data); + + /* backward compat */ + g_object_set_data (G_OBJECT (menuitem), "user_data", item_data); + } + } + else + { + menuitem = gtk_menu_item_new (); + + gtk_widget_set_sensitive (menuitem, FALSE); + } + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + if (widget_ptr) + *widget_ptr = menuitem; + + gtk_widget_show (menuitem); + + /* remember the initial menu item */ + if (active) + initial_index = i; + + label = va_arg (args, const gchar *); + } + va_end (args); + + if (! menu_only) + { + GtkWidget *optionmenu; + + optionmenu = gtk_option_menu_new (); + gtk_option_menu_set_menu (GTK_OPTION_MENU (optionmenu), menu); + + /* select the initial menu item */ + gtk_option_menu_set_history (GTK_OPTION_MENU (optionmenu), initial_index); + + return optionmenu; + } + + return menu; +} + +/** + * gimp_option_menu_new2: + * @menu_only: %TRUE if the function should return a #GtkMenu only. + * @menu_item_callback: The callback each menu item's "activate" signal will + * be connected with. + * @menu_item_callback_data: + * The data which will be passed to g_signal_connect(). + * @initial: The @item_data of the initially selected menu item. + * @...: A %NULL-terminated @va_list describing the menu items. + * + * Convenience function to create a #GtkOptionMenu or a #GtkMenu. + * + * Returns: A #GtkOptionMenu or a #GtkMenu (depending on @menu_only). + **/ +GtkWidget * +gimp_option_menu_new2 (gboolean menu_only, + GCallback menu_item_callback, + gpointer callback_data, + gpointer initial, /* item_data */ + + /* specify menu items as va_list: + * const gchar *label, + * gpointer item_data, + * GtkWidget **widget_ptr, + */ + + ...) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + /* menu item variables */ + const gchar *label; + gpointer item_data; + GtkWidget **widget_ptr; + + va_list args; + gint i; + gint initial_index; + + menu = gtk_menu_new (); + + /* create the menu items */ + initial_index = 0; + + va_start (args, initial); + label = va_arg (args, const gchar *); + + for (i = 0; label; i++) + { + item_data = va_arg (args, gpointer); + widget_ptr = va_arg (args, GtkWidget **); + + if (strcmp (label, "---")) + { + menuitem = gtk_menu_item_new_with_label (label); + + g_signal_connect (menuitem, "activate", + menu_item_callback, + callback_data); + + if (item_data) + { + g_object_set_data (G_OBJECT (menuitem), "gimp-item-data", + item_data); + + /* backward compat */ + g_object_set_data (G_OBJECT (menuitem), "user_data", item_data); + } + } + else + { + menuitem = gtk_menu_item_new (); + + gtk_widget_set_sensitive (menuitem, FALSE); + } + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + if (widget_ptr) + *widget_ptr = menuitem; + + gtk_widget_show (menuitem); + + /* remember the initial menu item */ + if (item_data == initial) + initial_index = i; + + label = va_arg (args, const gchar *); + } + va_end (args); + + if (! menu_only) + { + GtkWidget *optionmenu; + + optionmenu = gtk_option_menu_new (); + gtk_option_menu_set_menu (GTK_OPTION_MENU (optionmenu), menu); + + /* select the initial menu item */ + gtk_option_menu_set_history (GTK_OPTION_MENU (optionmenu), initial_index); + + return optionmenu; + } + + return menu; +} + +/** + * gimp_int_option_menu_new: + * @menu_only: %TRUE if the function should return a #GtkMenu only. + * @menu_item_callback: The callback each menu item's "activate" signal will + * be connected with. + * @menu_item_callback_data: + * The data which will be passed to g_signal_connect(). + * @initial: The @item_data of the initially selected menu item. + * @...: A %NULL-terminated @va_list describing the menu items. + * + * Convenience function to create a #GtkOptionMenu or a #GtkMenu. This + * function does the same thing as the deprecated function + * gimp_option_menu_new2(), but it takes integers as @item_data + * instead of pointers, since that is a very common case (mapping an + * enum to a menu). + * + * Returns: A #GtkOptionMenu or a #GtkMenu (depending on @menu_only). + **/ +GtkWidget * +gimp_int_option_menu_new (gboolean menu_only, + GCallback menu_item_callback, + gpointer callback_data, + gint initial, /* item_data */ + + /* specify menu items as va_list: + * const gchar *label, + * gint item_data, + * GtkWidget **widget_ptr, + */ + + ...) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + /* menu item variables */ + const gchar *label; + gint item_data; + gpointer item_ptr; + GtkWidget **widget_ptr; + + va_list args; + gint i; + gint initial_index; + + menu = gtk_menu_new (); + + /* create the menu items */ + initial_index = 0; + + va_start (args, initial); + label = va_arg (args, const gchar *); + + for (i = 0; label; i++) + { + item_data = va_arg (args, gint); + widget_ptr = va_arg (args, GtkWidget **); + + item_ptr = GINT_TO_POINTER (item_data); + + if (strcmp (label, "---")) + { + menuitem = gtk_menu_item_new_with_label (label); + + g_signal_connect (menuitem, "activate", + menu_item_callback, + callback_data); + + if (item_data) + { + g_object_set_data (G_OBJECT (menuitem), "gimp-item-data", + item_ptr); + + /* backward compat */ + g_object_set_data (G_OBJECT (menuitem), "user_data", item_ptr); + } + } + else + { + menuitem = gtk_menu_item_new (); + + gtk_widget_set_sensitive (menuitem, FALSE); + } + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + if (widget_ptr) + *widget_ptr = menuitem; + + gtk_widget_show (menuitem); + + /* remember the initial menu item */ + if (item_data == initial) + initial_index = i; + + label = va_arg (args, const gchar *); + } + va_end (args); + + if (! menu_only) + { + GtkWidget *optionmenu; + + optionmenu = gtk_option_menu_new (); + gtk_option_menu_set_menu (GTK_OPTION_MENU (optionmenu), menu); + + /* select the initial menu item */ + gtk_option_menu_set_history (GTK_OPTION_MENU (optionmenu), initial_index); + + return optionmenu; + } + + return menu; +} + +/** + * gimp_option_menu_set_history: + * @option_menu: A #GtkOptionMenu as returned by gimp_option_menu_new() or + * gimp_option_menu_new2(). + * @item_data: The @item_data of the menu item you want to select. + * + * Iterates over all entries in a #GtkOptionMenu and selects the one + * with the matching @item_data. Probably only makes sense to use with + * a #GtkOptionMenu that was created using gimp_option_menu_new() or + * gimp_option_menu_new2(). + **/ +void +gimp_option_menu_set_history (GtkOptionMenu *option_menu, + gpointer item_data) +{ + GList *children; + GList *list; + gint history = 0; + + g_return_if_fail (GTK_IS_OPTION_MENU (option_menu)); + + children = gtk_container_get_children (GTK_CONTAINER (option_menu->menu)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *menu_item = GTK_WIDGET (list->data); + + if (GTK_IS_LABEL (gtk_bin_get_child (GTK_BIN (menu_item))) && + g_object_get_data (G_OBJECT (menu_item), + "gimp-item-data") == item_data) + { + break; + } + + history++; + } + + if (list) + gtk_option_menu_set_history (option_menu, history); + + g_list_free (children); +} + +/** + * gimp_int_option_menu_set_history: + * @option_menu: A #GtkOptionMenu as returned by gimp_int_option_menu_new(). + * @item_data: The @item_data of the menu item you want to select. + * + * Iterates over all entries in a #GtkOptionMenu and selects the one with the + * matching @item_data. Probably only makes sense to use with a #GtkOptionMenu + * that was created using gimp_int_option_menu_new(). This function does the + * same thing as gimp_option_menu_set_history(), but takes integers as + * @item_data instead of pointers. + **/ +void +gimp_int_option_menu_set_history (GtkOptionMenu *option_menu, + gint item_data) +{ + g_return_if_fail (GTK_IS_OPTION_MENU (option_menu)); + + gimp_option_menu_set_history (option_menu, GINT_TO_POINTER (item_data)); +} + +/** + * gimp_option_menu_set_sensitive: + * @option_menu: a #GtkOptionMenu as returned by gimp_option_menu_new() or + * gimp_option_menu_new2(). + * @callback: a function called for each item in the menu to determine the + * the sensitivity state. + * @callback_data: data to pass to the @callback function. + * + * Calls the given @callback for each item in the menu and passes it the + * item_data and the @callback_data. The menu item's sensitivity is set + * according to the return value of this function. + **/ +void +gimp_option_menu_set_sensitive (GtkOptionMenu *option_menu, + GimpOptionMenuSensitivityCallback callback, + gpointer callback_data) +{ + GList *children; + GList *list; + + g_return_if_fail (GTK_IS_OPTION_MENU (option_menu)); + g_return_if_fail (callback != NULL); + + children = gtk_container_get_children (GTK_CONTAINER (option_menu->menu)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *menu_item = GTK_WIDGET (list->data); + + if (GTK_IS_LABEL (gtk_bin_get_child (GTK_BIN (menu_item)))) + { + gpointer item_data; + gboolean sensitive; + + item_data = g_object_get_data (G_OBJECT (menu_item), + "gimp-item-data"); + sensitive = callback (item_data, callback_data); + gtk_widget_set_sensitive (menu_item, sensitive); + } + } + + g_list_free (children); +} + +/** + * gimp_int_option_menu_set_sensitive: + * @option_menu: a #GtkOptionMenu as returned by gimp_option_menu_new() or + * gimp_option_menu_new2(). + * @callback: a function called for each item in the menu to determine the + * the sensitivity state. + * @callback_data: data to pass to the @callback function. + * + * Calls the given @callback for each item in the menu and passes it the + * item_data and the @callback_data. The menu item's sensitivity is set + * according to the return value of this function. This function does the + * same thing as gimp_option_menu_set_sensitive(), but takes integers as + * @item_data instead of pointers. + **/ +void +gimp_int_option_menu_set_sensitive (GtkOptionMenu *option_menu, + GimpIntOptionMenuSensitivityCallback callback, + gpointer callback_data) +{ + GList *children; + GList *list; + + g_return_if_fail (GTK_IS_OPTION_MENU (option_menu)); + g_return_if_fail (callback != NULL); + + children = gtk_container_get_children (GTK_CONTAINER (option_menu->menu)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *menu_item = GTK_WIDGET (list->data); + + if (GTK_IS_LABEL (gtk_bin_get_child (GTK_BIN (menu_item)))) + { + gint item_data; + gboolean sensitive; + + item_data = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), + "gimp-item-data")); + sensitive = callback (item_data, callback_data); + gtk_widget_set_sensitive (menu_item, sensitive); + } + } + + g_list_free (children); +} + + +/** + * gimp_menu_item_update: + * @widget: A #GtkMenuItem. + * @data: A pointer to a #gint variable which will store the value of + * GPOINTER_TO_INT (g_object_get_data (@widget, "gimp-item-data")). + **/ +void +gimp_menu_item_update (GtkWidget *widget, + gpointer data) +{ + gint *item_val = (gint *) data; + + *item_val = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-item-data")); +} + + +/** + * gimp_pixmap_button_new: + * @xpm_data: The XPM data which will be passed to gimp_pixmap_new(). + * @text: An optional text which will appear right of the pixmap. + * + * Convenience function that creates a #GtkButton with a #GimpPixmap + * and an optional #GtkLabel. + * + * Returns: The new #GtkButton. + **/ +GtkWidget * +gimp_pixmap_button_new (gchar **xpm_data, + const gchar *text) +{ + GtkWidget *button; + GtkWidget *pixmap; + + button = gtk_button_new (); + pixmap = gimp_pixmap_new (xpm_data); + + if (text) + { + GtkWidget *abox; + GtkWidget *hbox; + GtkWidget *label; + + abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (button), abox); + gtk_widget_show (abox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (abox), hbox); + gtk_widget_show (hbox); + + gtk_box_pack_start (GTK_BOX (hbox), pixmap, FALSE, FALSE, 4); + gtk_widget_show (pixmap); + + label = gtk_label_new_with_mnemonic (text); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), button); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 4); + gtk_widget_show (label); + } + else + { + gtk_container_add (GTK_CONTAINER (button), pixmap); + gtk_widget_show (pixmap); + } + + + return button; +} + + +/** + * gimp_unit_menu_update: + * @widget: A #GimpUnitMenu. + * @data: A pointer to a #GimpUnit variable which will store the unit menu's + * value. + * + * This callback can set the number of decimal digits of an arbitrary number + * of #GtkSpinButton's. To use this functionality, attach the spinbuttons + * as list of data pointers attached with g_object_set_data() with the + * "set_digits" key. + * + * See gimp_toggle_button_sensitive_update() for a description of how + * to set up the list. + * + * Deprecated: use #GimpUnitComboBox instead. + **/ +void +gimp_unit_menu_update (GtkWidget *widget, + gpointer data) +{ + GimpUnit *val = (GimpUnit *) data; + GtkWidget *spinbutton; + gint digits; + + *val = gimp_unit_menu_get_unit (GIMP_UNIT_MENU (widget)); + + digits = ((*val == GIMP_UNIT_PIXEL) ? 0 : + ((*val == GIMP_UNIT_PERCENT) ? 2 : + (MIN (6, MAX (3, gimp_unit_get_digits (*val)))))); + + digits += gimp_unit_menu_get_pixel_digits (GIMP_UNIT_MENU (widget)); + + spinbutton = g_object_get_data (G_OBJECT (widget), "set_digits"); + while (spinbutton) + { + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spinbutton), digits); + spinbutton = g_object_get_data (G_OBJECT (spinbutton), "set_digits"); + } +} diff --git a/libgimpwidgets/gimpoldwidgets.h b/libgimpwidgets/gimpoldwidgets.h new file mode 100644 index 0000000..3c7a525 --- /dev/null +++ b/libgimpwidgets/gimpoldwidgets.h @@ -0,0 +1,122 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpoldwidgets.h + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* These functions are deprecated and should not be used in newly + * written code. + */ + +#ifndef GIMP_DISABLE_DEPRECATED + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_OLD_WIDGETS_H__ +#define __GIMP_OLD_WIDGETS_H__ + +G_BEGIN_DECLS + + +/* + * Widget Constructors + */ + +GtkWidget * gimp_int_option_menu_new (gboolean menu_only, + GCallback menu_item_callback, + gpointer menu_item_callback_data, + gint initial, /* item_data */ + + /* specify menu items as va_list: + * gchar *label, + * gint item_data, + * GtkWidget **widget_ptr, + */ + + ...) G_GNUC_NULL_TERMINATED; + +void gimp_int_option_menu_set_history (GtkOptionMenu *option_menu, + gint item_data); + +typedef gboolean (* GimpIntOptionMenuSensitivityCallback) (gint item_data, + gpointer callback_data); + +void gimp_int_option_menu_set_sensitive (GtkOptionMenu *option_menu, + GimpIntOptionMenuSensitivityCallback callback, + gpointer callback_data); + + +GtkWidget * gimp_option_menu_new (gboolean menu_only, + + /* specify menu items as va_list: + * gchar *label, + * GCallback callback, + * gpointer callback_data, + * gpointer item_data, + * GtkWidget **widget_ptr, + * gboolean active + */ + + ...) G_GNUC_NULL_TERMINATED; +GtkWidget * gimp_option_menu_new2 (gboolean menu_only, + GCallback menu_item_callback, + gpointer menu_item_callback_data, + gpointer initial, /* item_data */ + + /* specify menu items as va_list: + * gchar *label, + * gpointer item_data, + * GtkWidget **widget_ptr, + */ + ...) G_GNUC_NULL_TERMINATED; +void gimp_option_menu_set_history (GtkOptionMenu *option_menu, + gpointer item_data); + + +typedef gboolean (* GimpOptionMenuSensitivityCallback) (gpointer item_data, + gpointer callback_data); + +void gimp_option_menu_set_sensitive (GtkOptionMenu *option_menu, + GimpOptionMenuSensitivityCallback callback, + gpointer callback_data); + + +void gimp_menu_item_update (GtkWidget *widget, + gpointer data); + +GtkWidget * gimp_pixmap_button_new (gchar **xpm_data, + const gchar *text); + + +/* + * Standard Callbacks + */ + +void gimp_toggle_button_sensitive_update (GtkToggleButton *toggle_button); + +void gimp_unit_menu_update (GtkWidget *widget, + gpointer data); + + +G_END_DECLS + +#endif /* __GIMP_OLD_WIDGETS_H__ */ + +#endif /* GIMP_DISABLE_DEPRECATED */ diff --git a/libgimpwidgets/gimppageselector.c b/libgimpwidgets/gimppageselector.c new file mode 100644 index 0000000..c882781 --- /dev/null +++ b/libgimpwidgets/gimppageselector.c @@ -0,0 +1,1327 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppageselector.c + * Copyright (C) 2005 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "gimpwidgetstypes.h" + +#include "gimpicons.h" +#include "gimppageselector.h" +#include "gimppropwidgets.h" +#include "gimpwidgets.h" +#include "gimp3migration.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimppageselector + * @title: GimpPageSelector + * @short_description: A widget to select pages from multi-page things. + * + * Use this for example for specifying what pages to import from + * a PDF or PS document. + **/ + + +enum +{ + SELECTION_CHANGED, + ACTIVATE, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_N_PAGES, + PROP_TARGET +}; + +enum +{ + COLUMN_PAGE_NO, + COLUMN_THUMBNAIL, + COLUMN_LABEL, + COLUMN_LABEL_SET +}; + + +typedef struct +{ + gint n_pages; + GimpPageSelectorTarget target; + + GtkListStore *store; + GtkWidget *view; + + GtkWidget *count_label; + GtkWidget *range_entry; + + GdkPixbuf *default_thumbnail; +} GimpPageSelectorPrivate; + +#define GIMP_PAGE_SELECTOR_GET_PRIVATE(obj) \ + ((GimpPageSelectorPrivate *) ((GimpPageSelector *) (obj))->priv) + + +static void gimp_page_selector_finalize (GObject *object); +static void gimp_page_selector_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_page_selector_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_page_selector_selection_changed (GtkIconView *icon_view, + GimpPageSelector *selector); +static void gimp_page_selector_item_activated (GtkIconView *icon_view, + GtkTreePath *path, + GimpPageSelector *selector); +static gboolean gimp_page_selector_range_focus_out (GtkEntry *entry, + GdkEventFocus *fevent, + GimpPageSelector *selector); +static void gimp_page_selector_range_activate (GtkEntry *entry, + GimpPageSelector *selector); +static gint gimp_page_selector_int_compare (gconstpointer a, + gconstpointer b); +static void gimp_page_selector_print_range (GString *string, + gint start, + gint end); + +static GdkPixbuf * gimp_page_selector_add_frame (GtkWidget *widget, + GdkPixbuf *pixbuf); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPageSelector, gimp_page_selector, GTK_TYPE_BOX) + +#define parent_class gimp_page_selector_parent_class + +static guint selector_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_page_selector_class_init (GimpPageSelectorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gimp_page_selector_finalize; + object_class->get_property = gimp_page_selector_get_property; + object_class->set_property = gimp_page_selector_set_property; + + klass->selection_changed = NULL; + klass->activate = NULL; + + /** + * GimpPageSelector::selection-changed: + * @widget: the object which received the signal. + * + * This signal is emitted whenever the set of selected pages changes. + * + * Since: 2.4 + **/ + selector_signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPageSelectorClass, selection_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GimpPageSelector::activate: + * @widget: the object which received the signal. + * + * The "activate" signal on GimpPageSelector is an action signal. It + * is emitted when a user double-clicks an item in the page selection. + * + * Since: 2.4 + */ + selector_signals[ACTIVATE] = + g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpPageSelectorClass, activate), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + widget_class->activate_signal = selector_signals[ACTIVATE]; + + /** + * GimpPageSelector:n-pages: + * + * The number of pages of the document to open. + * + * Since: 2.4 + **/ + g_object_class_install_property (object_class, PROP_N_PAGES, + g_param_spec_int ("n-pages", + "N Pages", + "The number of pages to open", + 0, G_MAXINT, 0, + GIMP_PARAM_READWRITE)); + + /** + * GimpPageSelector:target: + * + * The target to open the document to. + * + * Since: 2.4 + **/ + g_object_class_install_property (object_class, PROP_TARGET, + g_param_spec_enum ("target", + "Target", + "the target to open to", + GIMP_TYPE_PAGE_SELECTOR_TARGET, + GIMP_PAGE_SELECTOR_TARGET_LAYERS, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_page_selector_init (GimpPageSelector *selector) +{ + GimpPageSelectorPrivate *priv; + GtkWidget *vbox; + GtkWidget *sw; + GtkWidget *hbox; + GtkWidget *hbbox; + GtkWidget *button; + GtkWidget *label; + GtkWidget *combo; + + selector->priv = gimp_page_selector_get_instance_private (selector); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + priv->n_pages = 0; + priv->target = GIMP_PAGE_SELECTOR_TARGET_LAYERS; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (selector), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (selector), 12); + + /* Pages */ + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (selector), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_show (sw); + + priv->store = gtk_list_store_new (4, + G_TYPE_INT, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_BOOLEAN); + + priv->view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (priv->store)); + gtk_icon_view_set_text_column (GTK_ICON_VIEW (priv->view), + COLUMN_LABEL); + gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (priv->view), + COLUMN_THUMBNAIL); + gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (priv->view), + GTK_SELECTION_MULTIPLE); + gtk_container_add (GTK_CONTAINER (sw), priv->view); + gtk_widget_show (priv->view); + + g_signal_connect (priv->view, "selection-changed", + G_CALLBACK (gimp_page_selector_selection_changed), + selector); + g_signal_connect (priv->view, "item-activated", + G_CALLBACK (gimp_page_selector_item_activated), + selector); + + /* Count label */ + + priv->count_label = gtk_label_new (_("Nothing selected")); + gtk_label_set_xalign (GTK_LABEL (priv->count_label), 0.0); + gimp_label_set_attributes (GTK_LABEL (priv->count_label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (vbox), priv->count_label, FALSE, FALSE, 0); + gtk_widget_show (priv->count_label); + + /* Select all button & range entry */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (selector), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + hbbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0); + gtk_widget_show (hbbox); + + button = gtk_button_new_with_mnemonic (_("Select _All")); + gtk_box_pack_start (GTK_BOX (hbbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_page_selector_select_all), + selector); + + priv->range_entry = gtk_entry_new (); + gtk_widget_set_size_request (priv->range_entry, 80, -1); + gtk_box_pack_end (GTK_BOX (hbox), priv->range_entry, TRUE, TRUE, 0); + gtk_widget_show (priv->range_entry); + + g_signal_connect (priv->range_entry, "focus-out-event", + G_CALLBACK (gimp_page_selector_range_focus_out), + selector); + g_signal_connect (priv->range_entry, "activate", + G_CALLBACK (gimp_page_selector_range_activate), + selector); + + label = gtk_label_new_with_mnemonic (_("Select _range:")); + gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->range_entry); + + /* Target combo */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (selector), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("Open _pages as")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = gimp_prop_enum_combo_box_new (G_OBJECT (selector), "target", -1, -1); + gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + priv->default_thumbnail = + gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "text-x-generic", 32, 0, NULL); +} + +static void +gimp_page_selector_finalize (GObject *object) +{ + GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (object); + + g_clear_object (&priv->default_thumbnail); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_page_selector_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_N_PAGES: + g_value_set_int (value, priv->n_pages); + break; + case PROP_TARGET: + g_value_set_enum (value, priv->target); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_page_selector_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPageSelector *selector = GIMP_PAGE_SELECTOR (object); + GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_N_PAGES: + gimp_page_selector_set_n_pages (selector, g_value_get_int (value)); + break; + case PROP_TARGET: + priv->target = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +/** + * gimp_page_selector_new: + * + * Creates a new #GimpPageSelector widget. + * + * Returns: Pointer to the new #GimpPageSelector widget. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_page_selector_new (void) +{ + return g_object_new (GIMP_TYPE_PAGE_SELECTOR, NULL); +} + +/** + * gimp_page_selector_set_n_pages: + * @selector: Pointer to a #GimpPageSelector. + * @n_pages: The number of pages. + * + * Sets the number of pages in the document to open. + * + * Since: 2.4 + **/ +void +gimp_page_selector_set_n_pages (GimpPageSelector *selector, + gint n_pages) +{ + GimpPageSelectorPrivate *priv; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + g_return_if_fail (n_pages >= 0); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + if (n_pages != priv->n_pages) + { + GtkTreeIter iter; + gint i; + + if (n_pages < priv->n_pages) + { + for (i = n_pages; i < priv->n_pages; i++) + { + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, n_pages); + gtk_list_store_remove (priv->store, &iter); + } + } + else + { + for (i = priv->n_pages; i < n_pages; i++) + { + gchar *text; + + text = g_strdup_printf (_("Page %d"), i + 1); + + gtk_list_store_append (priv->store, &iter); + gtk_list_store_set (priv->store, &iter, + COLUMN_PAGE_NO, i, + COLUMN_THUMBNAIL, priv->default_thumbnail, + COLUMN_LABEL, text, + COLUMN_LABEL_SET, FALSE, + -1); + + g_free (text); + } + } + + priv->n_pages = n_pages; + + g_object_notify (G_OBJECT (selector), "n-pages"); + } +} + +/** + * gimp_page_selector_get_n_pages: + * @selector: Pointer to a #GimpPageSelector. + * + * Returns: the number of pages in the document to open. + * + * Since: 2.4 + **/ +gint +gimp_page_selector_get_n_pages (GimpPageSelector *selector) +{ + GimpPageSelectorPrivate *priv; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), 0); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + return priv->n_pages; +} + +/** + * gimp_page_selector_set_target: + * @selector: Pointer to a #GimpPageSelector. + * @target: How to open the selected pages. + * + * Since: 2.4 + **/ +void +gimp_page_selector_set_target (GimpPageSelector *selector, + GimpPageSelectorTarget target) +{ + GimpPageSelectorPrivate *priv; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + g_return_if_fail (target <= GIMP_PAGE_SELECTOR_TARGET_IMAGES); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + if (target != priv->target) + { + priv->target = target; + + g_object_notify (G_OBJECT (selector), "target"); + } +} + +/** + * gimp_page_selector_get_target: + * @selector: Pointer to a #GimpPageSelector. + * + * Returns: How the selected pages should be opened. + * + * Since: 2.4 + **/ +GimpPageSelectorTarget +gimp_page_selector_get_target (GimpPageSelector *selector) +{ + GimpPageSelectorPrivate *priv; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), + GIMP_PAGE_SELECTOR_TARGET_LAYERS); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + return priv->target; +} + +/** + * gimp_page_selector_set_page_thumbnail: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to set the thumbnail for. + * @thumbnail: The thumbnail pixbuf. + * + * Sets the thumbnail for given @page_no. A default "page" icon will + * be used if no page thumbnail is set. + * + * Since: 2.4 + **/ +void +gimp_page_selector_set_page_thumbnail (GimpPageSelector *selector, + gint page_no, + GdkPixbuf *thumbnail) +{ + GimpPageSelectorPrivate *priv; + GtkTreeIter iter; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + g_return_if_fail (thumbnail == NULL || GDK_IS_PIXBUF (thumbnail)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_if_fail (page_no >= 0 && page_no < priv->n_pages); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + + if (! thumbnail) + { + thumbnail = g_object_ref (priv->default_thumbnail); + } + else + { + thumbnail = gimp_page_selector_add_frame (GTK_WIDGET (selector), + thumbnail); + } + + gtk_list_store_set (priv->store, &iter, + COLUMN_THUMBNAIL, thumbnail, + -1); + g_clear_object (&thumbnail); +} + +/** + * gimp_page_selector_get_page_thumbnail: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to get the thumbnail for. + * + * Returns: The page's thumbnail, or %NULL if none is set. The returned + * pixbuf is owned by #GimpPageSelector and must not be + * unref'ed when no longer needed. + * + * Since: 2.4 + **/ +GdkPixbuf * +gimp_page_selector_get_page_thumbnail (GimpPageSelector *selector, + gint page_no) +{ + GimpPageSelectorPrivate *priv; + GdkPixbuf *thumbnail; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_val_if_fail (page_no >= 0 && page_no < priv->n_pages, NULL); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter, + COLUMN_THUMBNAIL, &thumbnail, + -1); + + if (thumbnail) + g_object_unref (thumbnail); + + if (thumbnail == priv->default_thumbnail) + return NULL; + + return thumbnail; +} + +/** + * gimp_page_selector_set_page_label: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to set the label for. + * @label: The label. + * + * Sets the label of the specified page. + * + * Since: 2.4 + **/ +void +gimp_page_selector_set_page_label (GimpPageSelector *selector, + gint page_no, + const gchar *label) +{ + GimpPageSelectorPrivate *priv; + GtkTreeIter iter; + gchar *tmp; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_if_fail (page_no >= 0 && page_no < priv->n_pages); + + if (! label) + tmp = g_strdup_printf (_("Page %d"), page_no + 1); + else + tmp = (gchar *) label; + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + gtk_list_store_set (priv->store, &iter, + COLUMN_LABEL, tmp, + COLUMN_LABEL_SET, label != NULL, + -1); + + if (! label) + g_free (tmp); +} + +/** + * gimp_page_selector_get_page_label: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to get the thumbnail for. + * + * Returns: The page's label, or %NULL if none is set. This is a newly + * allocated string that should be g_free()'d when no longer + * needed. + * + * Since: 2.4 + **/ +gchar * +gimp_page_selector_get_page_label (GimpPageSelector *selector, + gint page_no) +{ + GimpPageSelectorPrivate *priv; + GtkTreeIter iter; + gchar *label; + gboolean label_set; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_val_if_fail (page_no >= 0 && page_no < priv->n_pages, NULL); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter, + COLUMN_LABEL, &label, + COLUMN_LABEL_SET, &label_set, + -1); + + if (! label_set) + { + g_free (label); + label = NULL; + } + + return label; +} + +/** + * gimp_page_selector_select_all: + * @selector: Pointer to a #GimpPageSelector. + * + * Selects all pages. + * + * Since: 2.4 + **/ +void +gimp_page_selector_select_all (GimpPageSelector *selector) +{ + GimpPageSelectorPrivate *priv; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + gtk_icon_view_select_all (GTK_ICON_VIEW (priv->view)); +} + +/** + * gimp_page_selector_unselect_all: + * @selector: Pointer to a #GimpPageSelector. + * + * Unselects all pages. + * + * Since: 2.4 + **/ +void +gimp_page_selector_unselect_all (GimpPageSelector *selector) +{ + GimpPageSelectorPrivate *priv; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + gtk_icon_view_unselect_all (GTK_ICON_VIEW (priv->view)); +} + +/** + * gimp_page_selector_select_page: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to select. + * + * Adds a page to the selection. + * + * Since: 2.4 + **/ +void +gimp_page_selector_select_page (GimpPageSelector *selector, + gint page_no) +{ + GimpPageSelectorPrivate *priv; + GtkTreeIter iter; + GtkTreePath *path; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_if_fail (page_no >= 0 && page_no < priv->n_pages); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter); + + gtk_icon_view_select_path (GTK_ICON_VIEW (priv->view), path); + + gtk_tree_path_free (path); +} + +/** + * gimp_page_selector_unselect_page: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to unselect. + * + * Removes a page from the selection. + * + * Since: 2.4 + **/ +void +gimp_page_selector_unselect_page (GimpPageSelector *selector, + gint page_no) +{ + GimpPageSelectorPrivate *priv; + GtkTreeIter iter; + GtkTreePath *path; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_if_fail (page_no >= 0 && page_no < priv->n_pages); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter); + + gtk_icon_view_unselect_path (GTK_ICON_VIEW (priv->view), path); + + gtk_tree_path_free (path); +} + +/** + * gimp_page_selector_page_is_selected: + * @selector: Pointer to a #GimpPageSelector. + * @page_no: The number of the page to check. + * + * Returns: %TRUE if the page is selected, %FALSE otherwise. + * + * Since: 2.4 + **/ +gboolean +gimp_page_selector_page_is_selected (GimpPageSelector *selector, + gint page_no) +{ + GimpPageSelectorPrivate *priv; + GtkTreeIter iter; + GtkTreePath *path; + gboolean selected; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), FALSE); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + g_return_val_if_fail (page_no >= 0 && page_no < priv->n_pages, FALSE); + + gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store), + &iter, NULL, page_no); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter); + + selected = gtk_icon_view_path_is_selected (GTK_ICON_VIEW (priv->view), + path); + + gtk_tree_path_free (path); + + return selected; +} + +/** + * gimp_page_selector_get_selected_pages: + * @selector: Pointer to a #GimpPageSelector. + * @n_selected_pages: Returns the number of selected pages. + * + * Returns: A sorted array of page numbers of selected pages. Use g_free() if + * you don't need the array any longer. + * + * Since: 2.4 + **/ +gint * +gimp_page_selector_get_selected_pages (GimpPageSelector *selector, + gint *n_selected_pages) +{ + GimpPageSelectorPrivate *priv; + GList *selected; + GList *list; + gint *array; + gint i; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL); + g_return_val_if_fail (n_selected_pages != NULL, NULL); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (priv->view)); + + *n_selected_pages = g_list_length (selected); + array = g_new0 (gint, *n_selected_pages); + + for (list = selected, i = 0; list; list = g_list_next (list), i++) + { + gint *indices = gtk_tree_path_get_indices (list->data); + + array[i] = indices[0]; + } + + qsort (array, *n_selected_pages, sizeof (gint), + gimp_page_selector_int_compare); + + g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free); + + return array; +} + +/** + * gimp_page_selector_select_range: + * @selector: Pointer to a #GimpPageSelector. + * @range: A string representing the set of selected pages. + * + * Selects the pages described by @range. The range string is a + * user-editable list of pages and ranges, e.g. "1,3,5-7,9-12,14". + * Note that the page numbering in the range string starts with 1, + * not 0. + * + * Invalid pages and ranges will be silently ignored, duplicate and + * overlapping pages and ranges will be merged. + * + * Since: 2.4 + **/ +void +gimp_page_selector_select_range (GimpPageSelector *selector, + const gchar *range) +{ + GimpPageSelectorPrivate *priv; + gchar **ranges; + + g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector)); + + priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + + if (! range) + range = ""; + + g_signal_handlers_block_by_func (priv->view, + gimp_page_selector_selection_changed, + selector); + + gimp_page_selector_unselect_all (selector); + + ranges = g_strsplit (range, ",", -1); + + if (ranges) + { + gint i; + + for (i = 0; ranges[i] != NULL; i++) + { + gchar *range = g_strstrip (ranges[i]); + gchar *dash; + + dash = strchr (range, '-'); + + if (dash) + { + gchar *from; + gchar *to; + gint page_from = -1; + gint page_to = -1; + + *dash = '\0'; + + from = g_strstrip (range); + to = g_strstrip (dash + 1); + + if (sscanf (from, "%i", &page_from) != 1 && strlen (from) == 0) + page_from = 1; + + if (sscanf (to, "%i", &page_to) != 1 && strlen (to) == 0) + page_to = priv->n_pages; + + if (page_from > 0 && + page_to > 0 && + page_from <= page_to && + page_from <= priv->n_pages) + { + gint page_no; + + page_from = MAX (page_from, 1) - 1; + page_to = MIN (page_to, priv->n_pages) - 1; + + for (page_no = page_from; page_no <= page_to; page_no++) + gimp_page_selector_select_page (selector, page_no); + } + } + else + { + gint page_no; + + if (sscanf (range, "%i", &page_no) == 1 && + page_no >= 1 && + page_no <= priv->n_pages) + { + gimp_page_selector_select_page (selector, page_no - 1); + } + } + } + + g_strfreev (ranges); + } + + g_signal_handlers_unblock_by_func (priv->view, + gimp_page_selector_selection_changed, + selector); + + gimp_page_selector_selection_changed (GTK_ICON_VIEW (priv->view), selector); +} + +/** + * gimp_page_selector_get_selected_range: + * @selector: Pointer to a #GimpPageSelector. + * + * Returns: A newly allocated string representing the set of selected + * pages. See gimp_page_selector_select_range() for the + * format of the string. + * + * Since: 2.4 + **/ +gchar * +gimp_page_selector_get_selected_range (GimpPageSelector *selector) +{ + gint *pages; + gint n_pages; + GString *string; + + g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL); + + string = g_string_new (""); + + pages = gimp_page_selector_get_selected_pages (selector, &n_pages); + + if (pages) + { + gint range_start, range_end; + gint last_printed; + gint i; + + range_start = pages[0]; + range_end = pages[0]; + last_printed = -1; + + for (i = 1; i < n_pages; i++) + { + if (pages[i] > range_end + 1) + { + gimp_page_selector_print_range (string, + range_start, range_end); + + last_printed = range_end; + range_start = pages[i]; + } + + range_end = pages[i]; + } + + if (range_end != last_printed) + gimp_page_selector_print_range (string, range_start, range_end); + + g_free (pages); + } + + return g_string_free (string, FALSE); +} + + +/* private functions */ + +static void +gimp_page_selector_selection_changed (GtkIconView *icon_view, + GimpPageSelector *selector) +{ + GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector); + GList *selected; + gint n_selected; + gchar *range; + + selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (priv->view)); + n_selected = g_list_length (selected); + g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free); + + if (n_selected == 0) + { + gtk_label_set_text (GTK_LABEL (priv->count_label), + _("Nothing selected")); + } + else if (n_selected == 1) + { + gtk_label_set_text (GTK_LABEL (priv->count_label), + _("One page selected")); + } + else + { + gchar *text; + + if (n_selected == priv->n_pages) + text = g_strdup_printf (ngettext ("%d page selected", + "All %d pages selected", n_selected), + n_selected); + else + text = g_strdup_printf (ngettext ("%d page selected", + "%d pages selected", + n_selected), + n_selected); + + gtk_label_set_text (GTK_LABEL (priv->count_label), text); + g_free (text); + } + + range = gimp_page_selector_get_selected_range (selector); + gtk_entry_set_text (GTK_ENTRY (priv->range_entry), range); + g_free (range); + + gtk_editable_set_position (GTK_EDITABLE (priv->range_entry), -1); + + g_signal_emit (selector, selector_signals[SELECTION_CHANGED], 0); +} + +static void +gimp_page_selector_item_activated (GtkIconView *icon_view, + GtkTreePath *path, + GimpPageSelector *selector) +{ + g_signal_emit (selector, selector_signals[ACTIVATE], 0); +} + +static gboolean +gimp_page_selector_range_focus_out (GtkEntry *entry, + GdkEventFocus *fevent, + GimpPageSelector *selector) +{ + gimp_page_selector_range_activate (entry, selector); + + return FALSE; +} + +static void +gimp_page_selector_range_activate (GtkEntry *entry, + GimpPageSelector *selector) +{ + gimp_page_selector_select_range (selector, gtk_entry_get_text (entry)); +} + +static gint +gimp_page_selector_int_compare (gconstpointer a, + gconstpointer b) +{ + return *(gint*)a - *(gint*)b; +} + +static void +gimp_page_selector_print_range (GString *string, + gint start, + gint end) +{ + if (string->len != 0) + g_string_append_c (string, ','); + + if (start == end) + g_string_append_printf (string, "%d", start + 1); + else + g_string_append_printf (string, "%d-%d", start + 1, end + 1); +} + +static void +draw_frame_row (GdkPixbuf *frame_image, + gint target_width, + gint source_width, + gint source_v_position, + gint dest_v_position, + GdkPixbuf *result_pixbuf, + gint left_offset, + gint height) +{ + gint remaining_width = target_width; + gint h_offset = 0; + + while (remaining_width > 0) + { + gint slab_width = (remaining_width > source_width ? + source_width : remaining_width); + + gdk_pixbuf_copy_area (frame_image, + left_offset, source_v_position, + slab_width, height, + result_pixbuf, + left_offset + h_offset, dest_v_position); + + remaining_width -= slab_width; + h_offset += slab_width; + } +} + +/* utility to draw the middle section of the frame in a loop */ +static void +draw_frame_column (GdkPixbuf *frame_image, + gint target_height, + gint source_height, + gint source_h_position, + gint dest_h_position, + GdkPixbuf *result_pixbuf, + gint top_offset, int width) +{ + gint remaining_height = target_height; + gint v_offset = 0; + + while (remaining_height > 0) + { + gint slab_height = (remaining_height > source_height ? + source_height : remaining_height); + + gdk_pixbuf_copy_area (frame_image, + source_h_position, top_offset, + width, slab_height, + result_pixbuf, + dest_h_position, top_offset + v_offset); + + remaining_height -= slab_height; + v_offset += slab_height; + } +} + +static GdkPixbuf * +stretch_frame_image (GdkPixbuf *frame_image, + gint left_offset, + gint top_offset, + gint right_offset, + gint bottom_offset, + gint dest_width, + gint dest_height) +{ + GdkPixbuf *pixbuf; + gint frame_width, frame_height; + gint target_width, target_frame_width; + gint target_height, target_frame_height; + + frame_width = gdk_pixbuf_get_width (frame_image); + frame_height = gdk_pixbuf_get_height (frame_image ); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + dest_width, dest_height); + gdk_pixbuf_fill (pixbuf, 0); + + target_width = dest_width - left_offset - right_offset; + target_height = dest_height - top_offset - bottom_offset; + target_frame_width = frame_width - left_offset - right_offset; + target_frame_height = frame_height - top_offset - bottom_offset; + + left_offset += MIN (target_width / 4, target_frame_width / 4); + right_offset += MIN (target_width / 4, target_frame_width / 4); + top_offset += MIN (target_height / 4, target_frame_height / 4); + bottom_offset += MIN (target_height / 4, target_frame_height / 4); + + target_width = dest_width - left_offset - right_offset; + target_height = dest_height - top_offset - bottom_offset; + target_frame_width = frame_width - left_offset - right_offset; + target_frame_height = frame_height - top_offset - bottom_offset; + + /* draw the left top corner and top row */ + gdk_pixbuf_copy_area (frame_image, + 0, 0, left_offset, top_offset, + pixbuf, 0, 0); + draw_frame_row (frame_image, target_width, target_frame_width, + 0, 0, + pixbuf, + left_offset, top_offset); + + /* draw the right top corner and left column */ + gdk_pixbuf_copy_area (frame_image, + frame_width - right_offset, 0, + right_offset, top_offset, + + pixbuf, + dest_width - right_offset, 0); + draw_frame_column (frame_image, target_height, target_frame_height, 0, 0, + pixbuf, top_offset, left_offset); + + /* draw the bottom right corner and bottom row */ + gdk_pixbuf_copy_area (frame_image, + frame_width - right_offset, frame_height - bottom_offset, + right_offset, bottom_offset, + pixbuf, + dest_width - right_offset, dest_height - bottom_offset); + draw_frame_row (frame_image, target_width, target_frame_width, + frame_height - bottom_offset, dest_height - bottom_offset, + pixbuf, left_offset, bottom_offset); + + /* draw the bottom left corner and the right column */ + gdk_pixbuf_copy_area (frame_image, + 0, frame_height - bottom_offset, + left_offset, bottom_offset, + pixbuf, + 0, dest_height - bottom_offset); + draw_frame_column (frame_image, target_height, target_frame_height, + frame_width - right_offset, dest_width - right_offset, + pixbuf, top_offset, right_offset); + + return pixbuf; +} + +#define FRAME_LEFT 2 +#define FRAME_TOP 2 +#define FRAME_RIGHT 4 +#define FRAME_BOTTOM 4 + +static GdkPixbuf * +gimp_page_selector_add_frame (GtkWidget *widget, + GdkPixbuf *pixbuf) +{ + GdkPixbuf *frame; + gint width, height; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + frame = g_object_get_data (G_OBJECT (widget), "frame"); + + if (! frame) + { + GError *error = NULL; + + frame = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + GIMP_ICON_FRAME, 64, 0, &error); + if (error) + { + g_printerr ("%s: %s\n", G_STRFUNC, error->message); + g_error_free (error); + } + g_return_val_if_fail (frame, NULL); + g_object_set_data_full (G_OBJECT (widget), "frame", frame, + (GDestroyNotify) g_object_unref); + } + + frame = stretch_frame_image (frame, + FRAME_LEFT, + FRAME_TOP, + FRAME_RIGHT, + FRAME_BOTTOM, + width + FRAME_LEFT + FRAME_RIGHT, + height + FRAME_TOP + FRAME_BOTTOM); + + gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height, + frame, FRAME_LEFT, FRAME_TOP); + + return frame; +} diff --git a/libgimpwidgets/gimppageselector.h b/libgimpwidgets/gimppageselector.h new file mode 100644 index 0000000..1ac6de0 --- /dev/null +++ b/libgimpwidgets/gimppageselector.h @@ -0,0 +1,105 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppageselector.h + * Copyright (C) 2005 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PAGE_SELECTOR_H__ +#define __GIMP_PAGE_SELECTOR_H__ + +G_BEGIN_DECLS + +#define GIMP_TYPE_PAGE_SELECTOR (gimp_page_selector_get_type ()) +#define GIMP_PAGE_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAGE_SELECTOR, GimpPageSelector)) +#define GIMP_PAGE_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAGE_SELECTOR, GimpPageSelectorClass)) +#define GIMP_IS_PAGE_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAGE_SELECTOR)) +#define GIMP_IS_PAGE_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAGE_SELECTOR)) +#define GIMP_PAGE_SELECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAGE_SELECTOR, GimpPageSelectorClass)) + + +typedef struct _GimpPageSelectorClass GimpPageSelectorClass; + +struct _GimpPageSelector +{ + GtkBox parent_instance; + + gpointer priv; +}; + +struct _GimpPageSelectorClass +{ + GtkBoxClass parent_class; + + void (* selection_changed) (GimpPageSelector *selector); + void (* activate) (GimpPageSelector *selector); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_page_selector_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_page_selector_new (void); + +void gimp_page_selector_set_n_pages (GimpPageSelector *selector, + gint n_pages); +gint gimp_page_selector_get_n_pages (GimpPageSelector *selector); + +void gimp_page_selector_set_target (GimpPageSelector *selector, + GimpPageSelectorTarget target); +GimpPageSelectorTarget + gimp_page_selector_get_target (GimpPageSelector *selector); + +void gimp_page_selector_set_page_thumbnail (GimpPageSelector *selector, + gint page_no, + GdkPixbuf *thumbnail); +GdkPixbuf * gimp_page_selector_get_page_thumbnail (GimpPageSelector *selector, + gint page_no); + +void gimp_page_selector_set_page_label (GimpPageSelector *selector, + gint page_no, + const gchar *label); +gchar * gimp_page_selector_get_page_label (GimpPageSelector *selector, + gint page_no); + +void gimp_page_selector_select_all (GimpPageSelector *selector); +void gimp_page_selector_unselect_all (GimpPageSelector *selector); +void gimp_page_selector_select_page (GimpPageSelector *selector, + gint page_no); +void gimp_page_selector_unselect_page (GimpPageSelector *selector, + gint page_no); +gboolean gimp_page_selector_page_is_selected (GimpPageSelector *selector, + gint page_no); +gint * gimp_page_selector_get_selected_pages (GimpPageSelector *selector, + gint *n_selected_pages); + +void gimp_page_selector_select_range (GimpPageSelector *selector, + const gchar *range); +gchar * gimp_page_selector_get_selected_range (GimpPageSelector *selector); + +G_END_DECLS + +#endif /* __GIMP_PAGE_SELECTOR_H__ */ diff --git a/libgimpwidgets/gimppatheditor.c b/libgimpwidgets/gimppatheditor.c new file mode 100644 index 0000000..4abc31d --- /dev/null +++ b/libgimpwidgets/gimppatheditor.c @@ -0,0 +1,876 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppatheditor.c + * Copyright (C) 1999-2004 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpfileentry.h" + +#include "gimphelpui.h" +#include "gimpicons.h" +#include "gimppatheditor.h" +#include "gimp3migration.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimppatheditor + * @title: GimpPathEditor + * @short_description: Widget for editing a file search path. + * @see_also: #GimpFileEntry, #G_SEARCHPATH_SEPARATOR + * + * This widget is used to edit file search paths. + * + * It shows a list of all directories which are in the search + * path. You can click a directory to select it. The widget provides a + * #GimpFileEntry to change the currently selected directory. + * + * There are buttons to add or delete directories as well as "up" and + * "down" buttons to change the order in which the directories will be + * searched. + * + * Whenever the user adds, deletes, changes or reorders a directory of + * the search path, the "path_changed" signal will be emitted. + **/ + + +enum +{ + PATH_CHANGED, + WRITABLE_CHANGED, + LAST_SIGNAL +}; + +enum +{ + COLUMN_UTF8, + COLUMN_DIRECTORY, + COLUMN_WRITABLE, + NUM_COLUMNS +}; + + +static void gimp_path_editor_new_clicked (GtkWidget *widget, + GimpPathEditor *editor); +static void gimp_path_editor_move_clicked (GtkWidget *widget, + GimpPathEditor *editor); +static void gimp_path_editor_delete_clicked (GtkWidget *widget, + GimpPathEditor *editor); +static void gimp_path_editor_file_entry_changed (GtkWidget *widget, + GimpPathEditor *editor); +static void gimp_path_editor_selection_changed (GtkTreeSelection *sel, + GimpPathEditor *editor); +static void gimp_path_editor_writable_toggled (GtkCellRendererToggle *toggle, + gchar *path_str, + GimpPathEditor *editor); + + +G_DEFINE_TYPE (GimpPathEditor, gimp_path_editor, GTK_TYPE_BOX) + +#define parent_class gimp_path_editor_parent_class + +static guint gimp_path_editor_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_path_editor_class_init (GimpPathEditorClass *klass) +{ + /** + * GimpPathEditor::path-changed: + * + * This signal is emitted whenever the user adds, deletes, modifies + * or reorders an element of the search path. + **/ + gimp_path_editor_signals[PATH_CHANGED] = + g_signal_new ("path-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPathEditorClass, path_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GimpPathEditor::writable-changed: + * + * This signal is emitted whenever the "writable" column of a directory + * is changed, either by the user clicking on it or by calling + * gimp_path_editor_set_dir_writable(). + **/ + gimp_path_editor_signals[WRITABLE_CHANGED] = + g_signal_new ("writable-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPathEditorClass, writable_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->path_changed = NULL; + klass->writable_changed = NULL; +} + +static void +gimp_path_editor_init (GimpPathEditor *editor) +{ + GtkWidget *button_box; + GtkWidget *button; + GtkWidget *image; + GtkWidget *scrolled_window; + GtkWidget *tv; + GtkTreeViewColumn *col; + GtkCellRenderer *renderer; + + editor->file_entry = NULL; + editor->sel_path = NULL; + editor->num_items = 0; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + editor->upper_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (editor), editor->upper_hbox, FALSE, TRUE, 0); + gtk_widget_show (editor->upper_hbox); + + button_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (button_box), TRUE); + gtk_box_pack_start (GTK_BOX (editor->upper_hbox), button_box, FALSE, TRUE, 0); + gtk_widget_show (button_box); + + editor->new_button = button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (button_box), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_DOCUMENT_NEW, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_path_editor_new_clicked), + editor); + + gimp_help_set_help_data (editor->new_button, + _("Add a new folder"), + NULL); + + editor->up_button = button = gtk_button_new (); + gtk_widget_set_sensitive (button, FALSE); + gtk_box_pack_start (GTK_BOX (button_box), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_GO_UP, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_path_editor_move_clicked), + editor); + + gimp_help_set_help_data (editor->up_button, + _("Move the selected folder up"), + NULL); + + editor->down_button = button = gtk_button_new (); + gtk_widget_set_sensitive (button, FALSE); + gtk_box_pack_start (GTK_BOX (button_box), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_GO_DOWN, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_path_editor_move_clicked), + editor); + + gimp_help_set_help_data (editor->down_button, + _("Move the selected folder down"), + NULL); + + editor->delete_button = button = gtk_button_new (); + gtk_widget_set_sensitive (button, FALSE); + gtk_box_pack_start (GTK_BOX (button_box), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_EDIT_DELETE, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_path_editor_delete_clicked), + editor); + + gimp_help_set_help_data (editor->delete_button, + _("Remove the selected folder from the list"), + NULL); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 2); + gtk_widget_show (scrolled_window); + + editor->dir_list = gtk_list_store_new (NUM_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN); + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (editor->dir_list)); + g_object_unref (editor->dir_list); + + renderer = gtk_cell_renderer_toggle_new (); + + g_signal_connect (renderer, "toggled", + G_CALLBACK (gimp_path_editor_writable_toggled), + editor); + + editor->writable_column = col = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (col, _("Writable")); + gtk_tree_view_column_pack_start (col, renderer, FALSE); + gtk_tree_view_column_add_attribute (col, renderer, "active", COLUMN_WRITABLE); + + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), col); + + gtk_tree_view_column_set_visible (col, FALSE); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tv), + -1, _("Folder"), + gtk_cell_renderer_text_new (), + "text", COLUMN_UTF8, + NULL); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tv), TRUE); + + gtk_container_add (GTK_CONTAINER (scrolled_window), tv); + gtk_widget_show (tv); + + editor->sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv)); + g_signal_connect (editor->sel, "changed", + G_CALLBACK (gimp_path_editor_selection_changed), + editor); +} + +/** + * gimp_path_editor_new: + * @title: The title of the #GtkFileChooser dialog which can be popped up. + * @path: The initial search path. + * + * Creates a new #GimpPathEditor widget. + * + * The elements of the initial search path must be separated with the + * #G_SEARCHPATH_SEPARATOR character. + * + * Returns: A pointer to the new #GimpPathEditor widget. + **/ +GtkWidget * +gimp_path_editor_new (const gchar *title, + const gchar *path) +{ + GimpPathEditor *editor; + + g_return_val_if_fail (title != NULL, NULL); + + editor = g_object_new (GIMP_TYPE_PATH_EDITOR, NULL); + + editor->file_entry = gimp_file_entry_new (title, "", TRUE, TRUE); + gtk_widget_set_sensitive (editor->file_entry, FALSE); + gtk_box_pack_start (GTK_BOX (editor->upper_hbox), editor->file_entry, + TRUE, TRUE, 0); + gtk_widget_show (editor->file_entry); + + g_signal_connect (editor->file_entry, "filename-changed", + G_CALLBACK (gimp_path_editor_file_entry_changed), + editor); + + if (path) + gimp_path_editor_set_path (editor, path); + + return GTK_WIDGET (editor); +} + +/** + * gimp_path_editor_get_path: + * @editor: The path editor you want to get the search path from. + * + * The elements of the returned search path string are separated with the + * #G_SEARCHPATH_SEPARATOR character. + * + * Note that you have to g_free() the returned string. + * + * Returns: The search path the user has selected in the path editor. + **/ +gchar * +gimp_path_editor_get_path (GimpPathEditor *editor) +{ + GtkTreeModel *model; + GString *path; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_val_if_fail (GIMP_IS_PATH_EDITOR (editor), g_strdup ("")); + + model = GTK_TREE_MODEL (editor->dir_list); + + path = g_string_new (""); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *dir; + + gtk_tree_model_get (model, &iter, + COLUMN_DIRECTORY, &dir, + -1); + + if (path->len > 0) + g_string_append_c (path, G_SEARCHPATH_SEPARATOR); + + g_string_append (path, dir); + + g_free (dir); + } + + return g_string_free (path, FALSE); +} + +/** + * gimp_path_editor_set_path: + * @editor: The path editor you want to set the search path from. + * @path: The new path to set. + * + * The elements of the initial search path must be separated with the + * #G_SEARCHPATH_SEPARATOR character. + **/ +void +gimp_path_editor_set_path (GimpPathEditor *editor, + const gchar *path) +{ + gchar *old_path; + GList *path_list; + GList *list; + + g_return_if_fail (GIMP_IS_PATH_EDITOR (editor)); + + old_path = gimp_path_editor_get_path (editor); + + if (old_path && path && strcmp (old_path, path) == 0) + { + g_free (old_path); + return; + } + + g_free (old_path); + + path_list = gimp_path_parse (path, 256, FALSE, NULL); + + gtk_list_store_clear (editor->dir_list); + + for (list = path_list; list; list = g_list_next (list)) + { + gchar *directory = list->data; + gchar *utf8; + GtkTreeIter iter; + + utf8 = g_filename_to_utf8 (directory, -1, NULL, NULL, NULL); + + gtk_list_store_append (editor->dir_list, &iter); + gtk_list_store_set (editor->dir_list, &iter, + COLUMN_UTF8, utf8, + COLUMN_DIRECTORY, directory, + COLUMN_WRITABLE, FALSE, + -1); + + g_free (utf8); + + editor->num_items++; + } + + gimp_path_free (path_list); + + g_signal_emit (editor, gimp_path_editor_signals[PATH_CHANGED], 0); +} + +gchar * +gimp_path_editor_get_writable_path (GimpPathEditor *editor) +{ + GtkTreeModel *model; + GString *path; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_val_if_fail (GIMP_IS_PATH_EDITOR (editor), g_strdup ("")); + + model = GTK_TREE_MODEL (editor->dir_list); + + path = g_string_new (""); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *dir; + gboolean dir_writable; + + gtk_tree_model_get (model, &iter, + COLUMN_DIRECTORY, &dir, + COLUMN_WRITABLE, &dir_writable, + -1); + + if (dir_writable) + { + if (path->len > 0) + g_string_append_c (path, G_SEARCHPATH_SEPARATOR); + + g_string_append (path, dir); + } + + g_free (dir); + } + + return g_string_free (path, FALSE); +} + +void +gimp_path_editor_set_writable_path (GimpPathEditor *editor, + const gchar *path) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + GList *path_list; + gboolean writable_changed = FALSE; + + g_return_if_fail (GIMP_IS_PATH_EDITOR (editor)); + + gtk_tree_view_column_set_visible (editor->writable_column, TRUE); + + path_list = gimp_path_parse (path, 256, FALSE, NULL); + + model = GTK_TREE_MODEL (editor->dir_list); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *dir; + gboolean dir_writable; + gboolean new_writable = FALSE; + + gtk_tree_model_get (model, &iter, + COLUMN_DIRECTORY, &dir, + COLUMN_WRITABLE, &dir_writable, + -1); + + if (g_list_find_custom (path_list, dir, (GCompareFunc) strcmp)) + new_writable = TRUE; + + g_free (dir); + + if (dir_writable != new_writable) + { + gtk_list_store_set (editor->dir_list, &iter, + COLUMN_WRITABLE, new_writable, + -1); + + writable_changed = TRUE; + } + } + + gimp_path_free (path_list); + + if (writable_changed) + g_signal_emit (editor, gimp_path_editor_signals[WRITABLE_CHANGED], 0); +} + +gboolean +gimp_path_editor_get_dir_writable (GimpPathEditor *editor, + const gchar *directory) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_val_if_fail (GIMP_IS_PATH_EDITOR (editor), FALSE); + g_return_val_if_fail (directory != NULL, FALSE); + + model = GTK_TREE_MODEL (editor->dir_list); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *dir; + gboolean dir_writable; + + gtk_tree_model_get (model, &iter, + COLUMN_DIRECTORY, &dir, + COLUMN_WRITABLE, &dir_writable, + -1); + + if (! strcmp (dir, directory)) + { + g_free (dir); + + return dir_writable; + } + + g_free (dir); + } + + return FALSE; +} + +void +gimp_path_editor_set_dir_writable (GimpPathEditor *editor, + const gchar *directory, + gboolean writable) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_if_fail (GIMP_IS_PATH_EDITOR (editor)); + g_return_if_fail (directory != NULL); + + model = GTK_TREE_MODEL (editor->dir_list); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *dir; + gboolean dir_writable; + + gtk_tree_model_get (model, &iter, + COLUMN_DIRECTORY, &dir, + COLUMN_WRITABLE, &dir_writable, + -1); + + if (! strcmp (dir, directory) && dir_writable != writable) + { + gtk_list_store_set (editor->dir_list, &iter, + COLUMN_WRITABLE, writable ? TRUE : FALSE, + -1); + + g_signal_emit (editor, gimp_path_editor_signals[WRITABLE_CHANGED], 0); + + g_free (dir); + break; + } + + g_free (dir); + } +} + + +/* private functions */ + +static void +gimp_path_editor_new_clicked (GtkWidget *widget, + GimpPathEditor *editor) +{ + if (editor->sel_path) + { + g_signal_handlers_block_by_func (editor->sel, + gimp_path_editor_selection_changed, + editor); + + gtk_tree_selection_unselect_path (editor->sel, editor->sel_path); + + g_signal_handlers_unblock_by_func (editor->sel, + gimp_path_editor_selection_changed, + editor); + + gtk_tree_path_free (editor->sel_path); + editor->sel_path = NULL; + } + + gtk_widget_set_sensitive (editor->delete_button, FALSE); + gtk_widget_set_sensitive (editor->up_button, FALSE); + gtk_widget_set_sensitive (editor->down_button, FALSE); + gtk_widget_set_sensitive (editor->file_entry, TRUE); + + gtk_editable_set_position + (GTK_EDITABLE (GIMP_FILE_ENTRY (editor->file_entry)->entry), -1); + gtk_widget_grab_focus + (GTK_WIDGET (GIMP_FILE_ENTRY (editor->file_entry)->entry)); +} + +static void +gimp_path_editor_move_clicked (GtkWidget *widget, + GimpPathEditor *editor) +{ + GtkTreePath *path; + GtkTreeModel *model; + GtkTreeIter iter1, iter2; + gchar *utf81, *utf82; + gchar *dir1, *dir2; + gboolean writable1, writable2; + + if (editor->sel_path == NULL) + return; + + path = gtk_tree_path_copy (editor->sel_path); + + if (widget == editor->up_button) + gtk_tree_path_prev (path); + else + gtk_tree_path_next (path); + + model = GTK_TREE_MODEL (editor->dir_list); + + gtk_tree_model_get_iter (model, &iter1, editor->sel_path); + gtk_tree_model_get_iter (model, &iter2, path); + + gtk_tree_model_get (model, &iter1, + COLUMN_UTF8, &utf81, + COLUMN_DIRECTORY, &dir1, + COLUMN_WRITABLE, &writable1, + -1); + gtk_tree_model_get (model, &iter2, + COLUMN_UTF8, &utf82, + COLUMN_DIRECTORY, &dir2, + COLUMN_WRITABLE, &writable2, + -1); + + gtk_list_store_set (editor->dir_list, &iter1, + COLUMN_UTF8, utf82, + COLUMN_DIRECTORY, dir2, + COLUMN_WRITABLE, writable2, + -1); + gtk_list_store_set (editor->dir_list, &iter2, + COLUMN_UTF8, utf81, + COLUMN_DIRECTORY, dir1, + COLUMN_WRITABLE, writable1, + -1); + + g_free (utf81); + g_free (utf82); + + g_free (dir2); + g_free (dir1); + + gtk_tree_selection_select_iter (editor->sel, &iter2); + + g_signal_emit (editor, gimp_path_editor_signals[PATH_CHANGED], 0); +} + +static void +gimp_path_editor_delete_clicked (GtkWidget *widget, + GimpPathEditor *editor) +{ + GtkTreeIter iter; + gboolean dir_writable; + + if (editor->sel_path == NULL) + return; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->dir_list), &iter, + editor->sel_path); + + gtk_tree_model_get (GTK_TREE_MODEL (editor->dir_list), &iter, + COLUMN_WRITABLE, &dir_writable, + -1); + + gtk_list_store_remove (editor->dir_list, &iter); + + editor->num_items--; + + if (editor->num_items == 0) + { + gtk_tree_path_free (editor->sel_path); + editor->sel_path = NULL; + + g_signal_handlers_block_by_func (editor->file_entry, + gimp_path_editor_file_entry_changed, + editor); + + gimp_file_entry_set_filename (GIMP_FILE_ENTRY (editor->file_entry), ""); + + g_signal_handlers_unblock_by_func (editor->file_entry, + gimp_path_editor_file_entry_changed, + editor); + + gtk_widget_set_sensitive (editor->delete_button, FALSE); + gtk_widget_set_sensitive (editor->up_button, FALSE); + gtk_widget_set_sensitive (editor->down_button, FALSE); + gtk_widget_set_sensitive (editor->file_entry, FALSE); + } + else + { + gint *indices; + + indices = gtk_tree_path_get_indices (editor->sel_path); + if ((indices[0] == editor->num_items) && (indices[0] > 0)) + gtk_tree_path_prev (editor->sel_path); + + gtk_tree_selection_select_path (editor->sel, editor->sel_path); + } + + g_signal_emit (editor, gimp_path_editor_signals[PATH_CHANGED], 0); + + if (dir_writable) + g_signal_emit (editor, gimp_path_editor_signals[WRITABLE_CHANGED], 0); +} + +static void +gimp_path_editor_file_entry_changed (GtkWidget *widget, + GimpPathEditor *editor) +{ + gchar *dir; + gchar *utf8; + GtkTreeIter iter; + + dir = gimp_file_entry_get_filename (GIMP_FILE_ENTRY (widget)); + if (strcmp (dir, "") == 0) + { + g_free (dir); + return; + } + + utf8 = g_filename_display_name (dir); + + if (editor->sel_path == NULL) + { + gtk_list_store_append (editor->dir_list, &iter); + gtk_list_store_set (editor->dir_list, &iter, + COLUMN_UTF8, utf8, + COLUMN_DIRECTORY, dir, + COLUMN_WRITABLE, FALSE, + -1); + editor->num_items++; + + gtk_tree_selection_select_iter (editor->sel, &iter); + } + else + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->dir_list), &iter, + editor->sel_path); + gtk_list_store_set (editor->dir_list, &iter, + COLUMN_UTF8, utf8, + COLUMN_DIRECTORY, dir, + -1); + } + + g_free (dir); + g_free (utf8); + + g_signal_emit (editor, gimp_path_editor_signals[PATH_CHANGED], 0); +} + +static void +gimp_path_editor_selection_changed (GtkTreeSelection *sel, + GimpPathEditor *editor) +{ + GtkTreeIter iter; + gchar *directory; + gint *indices; + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + gtk_tree_model_get (GTK_TREE_MODEL (editor->dir_list), &iter, + COLUMN_DIRECTORY, &directory, + -1); + + g_signal_handlers_block_by_func (editor->file_entry, + gimp_path_editor_file_entry_changed, + editor); + + gimp_file_entry_set_filename (GIMP_FILE_ENTRY (editor->file_entry), + directory); + + g_signal_handlers_unblock_by_func (editor->file_entry, + gimp_path_editor_file_entry_changed, + editor); + + g_free (directory); + + if (editor->sel_path) + gtk_tree_path_free (editor->sel_path); + + editor->sel_path = + gtk_tree_model_get_path (GTK_TREE_MODEL (editor->dir_list), &iter); + + indices = gtk_tree_path_get_indices (editor->sel_path); + + gtk_widget_set_sensitive (editor->delete_button, TRUE); + gtk_widget_set_sensitive (editor->up_button, (indices[0] > 0)); + gtk_widget_set_sensitive (editor->down_button, + (indices[0] < (editor->num_items - 1))); + gtk_widget_set_sensitive (editor->file_entry, TRUE); + } + else + { + g_signal_handlers_block_by_func (sel, + gimp_path_editor_selection_changed, + editor); + + gtk_tree_selection_select_path (editor->sel, editor->sel_path); + + g_signal_handlers_unblock_by_func (sel, + gimp_path_editor_selection_changed, + editor); + } +} + +static void +gimp_path_editor_writable_toggled (GtkCellRendererToggle *toggle, + gchar *path_str, + GimpPathEditor *editor) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->dir_list), &iter, path)) + { + gboolean dir_writable; + + gtk_tree_model_get (GTK_TREE_MODEL (editor->dir_list), &iter, + COLUMN_WRITABLE, &dir_writable, + -1); + + gtk_list_store_set (editor->dir_list, &iter, + COLUMN_WRITABLE, ! dir_writable, + -1); + + g_signal_emit (editor, gimp_path_editor_signals[WRITABLE_CHANGED], 0); + } + + gtk_tree_path_free (path); +} diff --git a/libgimpwidgets/gimppatheditor.h b/libgimpwidgets/gimppatheditor.h new file mode 100644 index 0000000..2686501 --- /dev/null +++ b/libgimpwidgets/gimppatheditor.h @@ -0,0 +1,105 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppatheditor.h + * Copyright (C) 1999-2004 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PATH_EDITOR_H__ +#define __GIMP_PATH_EDITOR_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_PATH_EDITOR (gimp_path_editor_get_type ()) +#define GIMP_PATH_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PATH_EDITOR, GimpPathEditor)) +#define GIMP_PATH_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PATH_EDITOR, GimpPathEditorClass)) +#define GIMP_IS_PATH_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_PATH_EDITOR)) +#define GIMP_IS_PATH_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PATH_EDITOR)) +#define GIMP_PATH_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PATH_EDITOR, GimpPathEditorClass)) + + +typedef struct _GimpPathEditorClass GimpPathEditorClass; + +struct _GimpPathEditor +{ + GtkBox parent_instance; + + GtkWidget *upper_hbox; + + GtkWidget *new_button; + GtkWidget *up_button; + GtkWidget *down_button; + GtkWidget *delete_button; + + GtkWidget *file_entry; + + GtkListStore *dir_list; + + GtkTreeSelection *sel; + GtkTreePath *sel_path; + + GtkTreeViewColumn *writable_column; + + gint num_items; +}; + +struct _GimpPathEditorClass +{ + GtkBoxClass parent_class; + + void (* path_changed) (GimpPathEditor *editor); + void (* writable_changed) (GimpPathEditor *editor); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +/* For information look into the C source or the html documentation */ + +GType gimp_path_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_path_editor_new (const gchar *title, + const gchar *path); + +gchar * gimp_path_editor_get_path (GimpPathEditor *editor); +void gimp_path_editor_set_path (GimpPathEditor *editor, + const gchar *path); + +gchar * gimp_path_editor_get_writable_path (GimpPathEditor *editor); +void gimp_path_editor_set_writable_path (GimpPathEditor *editor, + const gchar *path); + +gboolean gimp_path_editor_get_dir_writable (GimpPathEditor *editor, + const gchar *directory); +void gimp_path_editor_set_dir_writable (GimpPathEditor *editor, + const gchar *directory, + gboolean writable); + +G_END_DECLS + +#endif /* __GIMP_PATH_EDITOR_H__ */ diff --git a/libgimpwidgets/gimppickbutton-default.c b/libgimpwidgets/gimppickbutton-default.c new file mode 100644 index 0000000..3d55f32 --- /dev/null +++ b/libgimpwidgets/gimppickbutton-default.c @@ -0,0 +1,368 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on gtk+/gtk/gtkcolorsel.c + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#ifdef G_OS_WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcairo-utils.h" +#include "gimphelpui.h" +#include "gimpicons.h" +#include "gimppickbutton.h" +#include "gimppickbutton-default.h" +#include "gimpwidgetsutils.h" + +#include "cursors/gimp-color-picker-cursors.c" + +#include "libgimp/libgimp-intl.h" + + +static gboolean gimp_pick_button_mouse_press (GtkWidget *invisible, + GdkEventButton *event, + GimpPickButton *button); +static gboolean gimp_pick_button_key_press (GtkWidget *invisible, + GdkEventKey *event, + GimpPickButton *button); +static gboolean gimp_pick_button_mouse_motion (GtkWidget *invisible, + GdkEventMotion *event, + GimpPickButton *button); +static gboolean gimp_pick_button_mouse_release (GtkWidget *invisible, + GdkEventButton *event, + GimpPickButton *button); +static void gimp_pick_button_shutdown (GimpPickButton *button); +static void gimp_pick_button_pick (GdkScreen *screen, + gint x_root, + gint y_root, + GimpPickButton *button); + + +static GdkCursor * +make_cursor (GdkDisplay *display) +{ + GdkPixbuf *pixbuf; + GError *error = NULL; + + pixbuf = gdk_pixbuf_new_from_resource ("/org/gimp/color-picker-cursors/cursor-color-picker.png", + &error); + + if (pixbuf) + { + GdkCursor *cursor = gdk_cursor_new_from_pixbuf (display, pixbuf, 1, 30); + + g_object_unref (pixbuf); + + return cursor; + } + else + { + g_critical ("Failed to create cursor image: %s", error->message); + g_clear_error (&error); + } + + return NULL; +} + +static gboolean +gimp_pick_button_mouse_press (GtkWidget *invisible, + GdkEventButton *event, + GimpPickButton *button) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 1) + { + g_signal_connect (invisible, "motion-notify-event", + G_CALLBACK (gimp_pick_button_mouse_motion), + button); + g_signal_connect (invisible, "button-release-event", + G_CALLBACK (gimp_pick_button_mouse_release), + button); + + g_signal_handlers_disconnect_by_func (invisible, + gimp_pick_button_mouse_press, + button); + g_signal_handlers_disconnect_by_func (invisible, + gimp_pick_button_key_press, + button); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gimp_pick_button_key_press (GtkWidget *invisible, + GdkEventKey *event, + GimpPickButton *button) +{ + if (event->keyval == GDK_KEY_Escape) + { + gimp_pick_button_shutdown (button); + + g_signal_handlers_disconnect_by_func (invisible, + gimp_pick_button_mouse_press, + button); + g_signal_handlers_disconnect_by_func (invisible, + gimp_pick_button_key_press, + button); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gimp_pick_button_mouse_motion (GtkWidget *invisible, + GdkEventMotion *event, + GimpPickButton *button) +{ + gint x_root; + gint y_root; + + gdk_window_get_origin (event->window, &x_root, &y_root); + x_root += event->x; + y_root += event->y; + + gimp_pick_button_pick (gdk_event_get_screen ((GdkEvent *) event), + x_root, y_root, button); + + return TRUE; +} + +static gboolean +gimp_pick_button_mouse_release (GtkWidget *invisible, + GdkEventButton *event, + GimpPickButton *button) +{ + gint x_root; + gint y_root; + + if (event->button != 1) + return FALSE; + + gdk_window_get_origin (event->window, &x_root, &y_root); + x_root += event->x; + y_root += event->y; + + gimp_pick_button_pick (gdk_event_get_screen ((GdkEvent *) event), + x_root, y_root, button); + + gimp_pick_button_shutdown (button); + + g_signal_handlers_disconnect_by_func (invisible, + gimp_pick_button_mouse_motion, + button); + g_signal_handlers_disconnect_by_func (invisible, + gimp_pick_button_mouse_release, + button); + + return TRUE; +} + +static void +gimp_pick_button_shutdown (GimpPickButton *button) +{ + GdkDisplay *display = gtk_widget_get_display (button->grab_widget); + guint32 timestamp = gtk_get_current_event_time (); + + gdk_display_keyboard_ungrab (display, timestamp); + gdk_display_pointer_ungrab (display, timestamp); + + gtk_grab_remove (button->grab_widget); +} + +static void +gimp_pick_button_pick (GdkScreen *screen, + gint x_root, + gint y_root, + GimpPickButton *button) +{ + GimpRGB rgb; + GimpColorProfile *monitor_profile; + gint monitor; + +#ifdef G_OS_WIN32 + + HDC hdc; + RECT rect; + COLORREF win32_color; + + /* For MS Windows, use native GDI functions to get the pixel, as + * cairo does not handle the case where you have multiple monitors + * with a monitor on the left or above the primary monitor. That + * scenario create a cairo primary surface with negative extent, + * which is not handled properly (bug 740634). + */ + + hdc = GetDC (HWND_DESKTOP); + GetClipBox (hdc, &rect); + win32_color = GetPixel (hdc, x_root + rect.left, y_root + rect.top); + ReleaseDC (HWND_DESKTOP, hdc); + + gimp_rgba_set_uchar (&rgb, + GetRValue (win32_color), + GetGValue (win32_color), + GetBValue (win32_color), + 255); + +#else + + GdkWindow *window; + gint x_window; + gint y_window; + cairo_surface_t *image; + cairo_t *cr; + guchar *data; + guchar color[3]; + + /* we try to pick from the local window under the cursor, and fall back to + * picking from the root window if this fails (i.e., if the cursor is not + * under a local window). on wayland, picking from the root window is not + * supported, so this at least allows us to pick from local windows. see + * bug #780375. + */ + window = gdk_display_get_window_at_pointer (gdk_screen_get_display (screen), + &x_window, &y_window); + + if (! window) + { + window = gdk_screen_get_root_window (screen); + x_window = x_root; + y_window = y_root; + } + + image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 1, 1); + + cr = cairo_create (image); + + gdk_cairo_set_source_window (cr, window, -x_window, -y_window); + cairo_paint (cr); + + cairo_destroy (cr); + + data = cairo_image_surface_get_data (image); + GIMP_CAIRO_RGB24_GET_PIXEL (data, color[0], color[1], color[2]); + + cairo_surface_destroy (image); + + gimp_rgba_set_uchar (&rgb, color[0], color[1], color[2], 255); + +#endif + + monitor = gdk_screen_get_monitor_at_point (screen, x_root, y_root); + monitor_profile = gimp_screen_get_color_profile (screen, monitor); + + if (monitor_profile) + { + GimpColorProfile *srgb_profile; + GimpColorTransform *transform; + const Babl *format; + GimpColorTransformFlags flags = 0; + + format = babl_format ("R'G'B'A double"); + + flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE; + flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION; + + srgb_profile = gimp_color_profile_new_rgb_srgb (); + transform = gimp_color_transform_new (monitor_profile, format, + srgb_profile, format, + GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL, + flags); + g_object_unref (srgb_profile); + + if (transform) + { + gimp_color_transform_process_pixels (transform, + format, &rgb, + format, &rgb, + 1); + gimp_rgb_clamp (&rgb); + + g_object_unref (transform); + } + } + + g_signal_emit_by_name (button, "color-picked", &rgb); +} + +/* entry point to this file, called from gimppickbutton.c */ +void +_gimp_pick_button_default_pick (GimpPickButton *button) +{ + GtkWidget *widget; + guint32 timestamp; + + if (! button->cursor) + button->cursor = make_cursor (gtk_widget_get_display (GTK_WIDGET (button))); + + if (! button->grab_widget) + { + button->grab_widget = gtk_invisible_new (); + + gtk_widget_add_events (button->grab_widget, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK); + + gtk_widget_show (button->grab_widget); + } + + widget = button->grab_widget; + timestamp = gtk_get_current_event_time (); + + if (gdk_keyboard_grab (gtk_widget_get_window (widget), FALSE, + timestamp) != GDK_GRAB_SUCCESS) + { + g_warning ("Failed to grab keyboard to do eyedropper"); + return; + } + + if (gdk_pointer_grab (gtk_widget_get_window (widget), FALSE, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK, + NULL, + button->cursor, + timestamp) != GDK_GRAB_SUCCESS) + { + gdk_display_keyboard_ungrab (gtk_widget_get_display (widget), timestamp); + g_warning ("Failed to grab pointer to do eyedropper"); + return; + } + + gtk_grab_add (widget); + + g_signal_connect (widget, "button-press-event", + G_CALLBACK (gimp_pick_button_mouse_press), + button); + g_signal_connect (widget, "key-press-event", + G_CALLBACK (gimp_pick_button_key_press), + button); +} diff --git a/libgimpwidgets/gimppickbutton-default.h b/libgimpwidgets/gimppickbutton-default.h new file mode 100644 index 0000000..8b252b8 --- /dev/null +++ b/libgimpwidgets/gimppickbutton-default.h @@ -0,0 +1,25 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-default.h + * Copyright (C) 2017 Jehan <jehan@gimp.org> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* Private header file which is not meant to be exported. */ +#ifndef __GIMP_PICK_BUTTON_DEFAULT_H__ +#define __GIMP_PICK_BUTTON_DEFAULT_H__ + +void _gimp_pick_button_default_pick (GimpPickButton *button); + +#endif /* __GIMP_PICK_BUTTON_DEFAULT_H__ */ + + diff --git a/libgimpwidgets/gimppickbutton-kwin.c b/libgimpwidgets/gimppickbutton-kwin.c new file mode 100644 index 0000000..ec09bfc --- /dev/null +++ b/libgimpwidgets/gimppickbutton-kwin.c @@ -0,0 +1,112 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-kwin.c + * Copyright (C) 2017 Jehan <jehan@gimp.org> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" +#include "gimppickbutton.h" +#include "gimppickbutton-default.h" +#include "gimppickbutton-kwin.h" + +#include "libgimp/libgimp-intl.h" + +gboolean +_gimp_pick_button_kwin_available (void) +{ + GDBusProxy *proxy = NULL; + gboolean available = FALSE; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.kde.KWin", + "/ColorPicker", + "org.kde.kwin.ColorPicker", + NULL, NULL); + + if (proxy) + { + GError *error = NULL; + + g_dbus_proxy_call_sync (proxy, "org.freedesktop.DBus.Peer.Ping", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (! error) + available = TRUE; + + g_clear_error (&error); + g_object_unref (proxy); + } + + return available; +} + +/* entry point to this file, called from gimppickbutton.c */ +void +_gimp_pick_button_kwin_pick (GimpPickButton *button) +{ + GDBusProxy *proxy = NULL; + GError *error = NULL; + GVariant *retval; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.kde.KWin", + "/ColorPicker", + "org.kde.kwin.ColorPicker", + NULL, NULL); + g_return_if_fail (proxy); + + retval = g_dbus_proxy_call_sync (proxy, "pick", NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (retval) + { + GimpRGB rgb; + guint32 color; + + g_variant_get (retval, "((u))", &color); + g_variant_unref (retval); + /* Returned value is ARGB stored in uint32. */ + gimp_rgba_set_uchar (&rgb, + (color >> 16 ) & 0xff, /* Red */ + (color >> 8) & 0xff, /* Green */ + color & 0xff, /* Blue: least significant byte. */ + (color >> 24) & 0xff); /* Alpha: most significant byte. */ + g_signal_emit_by_name (button, "color-picked", &rgb); + } + else + { + /* I had failure of KDE's color picking API. So let's just + * fallback to the default color picking when this happens. This + * will at least work on X11. + * See: https://bugs.kde.org/show_bug.cgi?id=387720 + */ + if (error) + g_warning ("KWin backend for color picking failed with error: %s", + error->message); + _gimp_pick_button_default_pick (GIMP_PICK_BUTTON (button)); + } + g_clear_error (&error); + g_object_unref (proxy); +} diff --git a/libgimpwidgets/gimppickbutton-kwin.h b/libgimpwidgets/gimppickbutton-kwin.h new file mode 100644 index 0000000..4f596af --- /dev/null +++ b/libgimpwidgets/gimppickbutton-kwin.h @@ -0,0 +1,25 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-kwin.h + * Copyright (C) 2017 Jehan <jehan@gimp.org> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* Private header file which is not meant to be exported. */ +#ifndef __GIMP_PICK_BUTTON_KWIN_H__ +#define __GIMP_PICK_BUTTON_KWIN_H__ + +gboolean _gimp_pick_button_kwin_available (void); +void _gimp_pick_button_kwin_pick (GimpPickButton *button); + +#endif /* __GIMP_PICK_BUTTON_KWIN_H__ */ + diff --git a/libgimpwidgets/gimppickbutton-quartz.c b/libgimpwidgets/gimppickbutton-quartz.c new file mode 100644 index 0000000..5d581d3 --- /dev/null +++ b/libgimpwidgets/gimppickbutton-quartz.c @@ -0,0 +1,485 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-quartz.c + * Copyright (C) 2015 Kristian Rietveld <kris@loopnest.org> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" +#include "gimppickbutton.h" +#include "gimppickbutton-quartz.h" + +#include "cursors/gimp-color-picker-cursors.c" + +#ifdef GDK_WINDOWING_QUARTZ +#import <AppKit/AppKit.h> +#include <Carbon/Carbon.h> /* For virtual key codes ... */ +#include <ApplicationServices/ApplicationServices.h> +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 +@protocol NSWindowDelegate +@end +#endif + +@interface NSWindow (GIMPExt) +- (NSRect) convertRectToScreen: (NSRect)aRect; +@end +@implementation NSWindow (GIMPExt) +- (NSRect) convertRectToScreen: (NSRect)aRect +{ + NSRect result = aRect; + NSPoint origin = result.origin; + result.origin = [self convertBaseToScreen:origin]; + return result; +} +@end +#endif + + +@interface GimpPickWindowController : NSObject +{ + GimpPickButton *button; + NSMutableArray *windows; +} + +@property (nonatomic, assign) BOOL firstBecameKey; +@property (readonly, retain) NSCursor *cursor; + +- (id)initWithButton:(GimpPickButton *)_button; +- (void)updateKeyWindow; +- (void)shutdown; +@end + +@interface GimpPickView : NSView +{ + GimpPickButton *button; + GimpPickWindowController *controller; +} + +@property (readonly,assign) NSTrackingArea *area; + +- (id)initWithButton:(GimpPickButton *)_button controller:(GimpPickWindowController *)controller; +@end + +@implementation GimpPickView + +@synthesize area; + +- (id)initWithButton:(GimpPickButton *)_button controller:(GimpPickWindowController *)_controller +{ + self = [super init]; + + if (self) + { + button = _button; + controller = _controller; + } + + return self; +} + +- (void)dealloc +{ + [self removeTrackingArea:self.area]; + + [super dealloc]; +} + +- (void)viewDidMoveToWindow +{ + NSTrackingAreaOptions options; + + [super viewDidMoveToWindow]; + + if ([self window] == nil) + return; + + options = NSTrackingMouseEnteredAndExited | + NSTrackingMouseMoved | + NSTrackingActiveAlways; + + /* Add assume inside if mouse pointer is above this window */ + if (NSPointInRect ([NSEvent mouseLocation], self.window.frame)) + options |= NSTrackingAssumeInside; + + area = [[NSTrackingArea alloc] initWithRect:self.bounds + options:options + owner:self + userInfo:nil]; + [self addTrackingArea:self.area]; +} + +- (void)mouseEntered:(NSEvent *)event +{ + /* We handle the mouse cursor manually, see also the comment in + * [GimpPickWindow windowDidBecomeMain below]. + */ + if (controller.cursor) + [controller.cursor push]; +} + +- (void)mouseExited:(NSEvent *)event +{ + if (controller.cursor) + [controller.cursor pop]; + + [controller updateKeyWindow]; +} + +- (void)mouseMoved:(NSEvent *)event +{ + [self pickColor:event]; +} + +- (void)mouseUp:(NSEvent *)event +{ + [self pickColor:event]; + + [controller shutdown]; +} + +- (void)rightMouseUp:(NSEvent *)event +{ + [self mouseUp:event]; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + [self mouseUp:event]; +} + +- (void)keyDown:(NSEvent *)event +{ + if (event.keyCode == kVK_Escape) + [controller shutdown]; +} + +- (void)pickColor:(NSEvent *)event +{ + CGImageRef root_image_ref; + CFDataRef pixel_data; + const guchar *data; + GimpRGB rgb; + NSPoint point; + GimpColorProfile *profile = NULL; + CGColorSpaceRef color_space = NULL; + + /* The event gives us a point in Cocoa window coordinates. The function + * CGWindowListCreateImage expects a rectangle in screen coordinates + * with the origin in the upper left (contrary to Cocoa). The origin is + * on the screen showing the menu bar (this is the screen at index 0 in the + * screens array). So, after converting the rectangle to Cocoa screen + * coordinates, we use the height of this particular screen to translate + * to the coordinate space expected by CGWindowListCreateImage. + */ + point = event.locationInWindow; + NSRect rect = NSMakeRect (point.x, point.y, + 1, 1); + rect = [self.window convertRectToScreen:rect]; + rect.origin.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.origin.y; + + root_image_ref = CGWindowListCreateImage (rect, + kCGWindowListOptionOnScreenOnly, + kCGNullWindowID, + kCGWindowImageDefault); + pixel_data = CGDataProviderCopyData (CGImageGetDataProvider (root_image_ref)); + data = CFDataGetBytePtr (pixel_data); + + color_space = CGImageGetColorSpace (root_image_ref); + if (color_space) + { + CFDataRef icc_data = NULL; + + icc_data = CGColorSpaceCopyICCProfile (color_space); + if (icc_data) + { + UInt8 *buffer = g_malloc (CFDataGetLength (icc_data)); + + CFDataGetBytes (icc_data, CFRangeMake (0, CFDataGetLength (icc_data)), + buffer); + + profile = gimp_color_profile_new_from_icc_profile (buffer, + CFDataGetLength (icc_data), + NULL); + g_free (buffer); + CFRelease (icc_data); + } + } + + gimp_rgba_set_uchar (&rgb, data[2], data[1], data[0], 255); + if (profile) + { + GimpColorProfile *srgb_profile; + GimpColorTransform *transform; + const Babl *format; + GimpColorTransformFlags flags = 0; + + format = babl_format ("R'G'B'A double"); + + flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE; + flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION; + + srgb_profile = gimp_color_profile_new_rgb_srgb (); + transform = gimp_color_transform_new (profile, format, + srgb_profile, format, + GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL, + flags); + + if (transform) + { + gimp_color_transform_process_pixels (transform, + format, &rgb, + format, &rgb, + 1); + gimp_rgb_clamp (&rgb); + + g_object_unref (transform); + } + g_object_unref (srgb_profile); + g_object_unref (profile); + } + + CGImageRelease (root_image_ref); + CFRelease (pixel_data); + + g_signal_emit_by_name (button, "color-picked", &rgb); +} +@end + + +@interface GimpPickWindow : NSWindow <NSWindowDelegate> +{ + GimpPickWindowController *controller; +} + +- (id)initWithButton:(GimpPickButton *)button forScreen:(NSScreen *)screen withController:(GimpPickWindowController *)_controller; +@end + +@implementation GimpPickWindow +- (id)initWithButton:(GimpPickButton *)button forScreen:(NSScreen *)screen withController:(GimpPickWindowController *)_controller +{ + self = [super initWithContentRect:screen.frame + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + + if (self) + { + GimpPickView *view; + + controller = _controller; + + [self setDelegate:self]; + + [self setAlphaValue:0.0]; +#if 0 + /* Useful for debugging purposes */ + [self setBackgroundColor:[NSColor redColor]]; + [self setAlphaValue:0.2]; +#endif + [self setIgnoresMouseEvents:NO]; + [self setAcceptsMouseMovedEvents:YES]; + [self setHasShadow:NO]; + [self setOpaque:NO]; + + /* Set the highest level, so on top of everything */ + [self setLevel:NSScreenSaverWindowLevel]; + + view = [[GimpPickView alloc] initWithButton:button controller:controller]; + [self setContentView:view]; + [self makeFirstResponder:view]; + [view release]; + + [self disableCursorRects]; + } + + return self; +} + +/* Borderless windows cannot become key/main by default, so we force it + * to make it so. We need this to receive events. + */ +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +- (void)windowDidBecomeKey:(NSNotification *)aNotification +{ + /* We cannot use the usual Cocoa method for handling cursor updates, + * since the GDK Quartz backend is interfering. Additionally, because + * one of the screen-spanning windows pops up under the mouse pointer this + * view will not receive a MouseEntered event. So, we synthesize such + * an event here and the view can set the mouse pointer in response to + * this. So, this event only has to be synthesized once and only for + * the window that pops up under the mouse cursor. Synthesizing multiple + * times messes up the mouse cursor stack. + * + * We cannot set the mouse pointer at this moment, because the GDK window + * will still receive an MouseExited event in which turn it will modify + * the cursor. So, with this synthesized event we also ensure we set + * the mouse cursor *after* the GDK window has manipulated the cursor. + */ + NSEvent *event; + + if (!controller.firstBecameKey || + !NSPointInRect ([NSEvent mouseLocation], self.frame)) + return; + + controller.firstBecameKey = NO; + + event = [NSEvent enterExitEventWithType:NSMouseEntered + location:[self mouseLocationOutsideOfEventStream] + modifierFlags:0 + timestamp:[[NSApp currentEvent] timestamp] + windowNumber:self.windowNumber + context:nil + eventNumber:0 + trackingNumber:(NSInteger)[[self contentView] area] + userData:nil]; + + [NSApp postEvent:event atStart:NO]; +} +@end + + +/* To properly handle multi-monitor setups we need to create a + * GimpPickWindow for each monitor (NSScreen). This is necessary because + * a window on Mac OS X (tested on 10.9) cannot span more than one + * monitor, so any approach that attempts to create one large window + * spanning all monitors cannot work. So, we have to create multiple + * windows in case of multi-monitor setups and these different windows + * are managed by GimpPickWindowController. + */ +@implementation GimpPickWindowController + +@synthesize firstBecameKey; +@synthesize cursor; + +- (id)initWithButton:(GimpPickButton *)_button; +{ + self = [super init]; + + if (self) + { + firstBecameKey = YES; + button = _button; + cursor = [GimpPickWindowController makePickCursor]; + + windows = [[NSMutableArray alloc] init]; + + for (NSScreen *screen in [NSScreen screens]) + { + GimpPickWindow *window; + + window = [[GimpPickWindow alloc] initWithButton:button + forScreen:screen + withController:self]; + + [window orderFrontRegardless]; + [window makeMainWindow]; + + [windows addObject:window]; + } + + [self updateKeyWindow]; + } + + return self; +} + +- (void)updateKeyWindow +{ + for (GimpPickWindow *window in windows) + { + if (NSPointInRect ([NSEvent mouseLocation], window.frame)) + [window makeKeyWindow]; + } +} + +- (void)shutdown +{ + GtkWidget *window; + + for (GimpPickWindow *window in windows) + [window close]; + + [windows release]; + + if (cursor) + [cursor release]; + + /* Give focus back to the window containing the pick button */ + window = gtk_widget_get_toplevel (GTK_WIDGET (button)); + gtk_window_present_with_time (GTK_WINDOW (window), GDK_CURRENT_TIME); + + [self release]; +} + ++ (NSCursor *)makePickCursor +{ + GBytes *bytes = NULL; + GError *error = NULL; + + bytes = g_resources_lookup_data ("/org/gimp/color-picker-cursors-raw/cursor-color-picker.png", + G_RESOURCE_LOOKUP_FLAGS_NONE, &error); + + if (bytes) + { + NSData *data = [NSData dataWithBytes:g_bytes_get_data (bytes, NULL) + length:g_bytes_get_size (bytes)]; + NSImage *image = [[NSImage alloc] initWithData:data]; + NSCursor *cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(1, 30)]; + + [image release]; + g_bytes_unref (bytes); + + return [cursor retain]; + } + else + { + g_critical ("Failed to create cursor image: %s", error->message); + g_clear_error (&error); + } + + return NULL; +} +@end + +/* entry point to this file, called from gimppickbutton.c */ +void +_gimp_pick_button_quartz_pick (GimpPickButton *button) +{ + GimpPickWindowController *controller; + NSAutoreleasePool *pool; + + pool = [[NSAutoreleasePool alloc] init]; + + controller = [[GimpPickWindowController alloc] initWithButton:button]; + + [pool release]; +} diff --git a/libgimpwidgets/gimppickbutton-quartz.h b/libgimpwidgets/gimppickbutton-quartz.h new file mode 100644 index 0000000..53f1ce4 --- /dev/null +++ b/libgimpwidgets/gimppickbutton-quartz.h @@ -0,0 +1,25 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-quartz.h + * Copyright (C) 2017 Jehan <jehan@gimp.org> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* Private header file which is not meant to be exported. */ +#ifndef __GIMP_PICK_BUTTON_QUARTZ_H__ +#define __GIMP_PICK_BUTTON_QUARTZ_H__ + +void _gimp_pick_button_quartz_pick (GimpPickButton *button); + +#endif /* __GIMP_PICK_BUTTON_QUARTZ_H__ */ + + diff --git a/libgimpwidgets/gimppickbutton-win32.c b/libgimpwidgets/gimppickbutton-win32.c new file mode 100644 index 0000000..d5ca8b7 --- /dev/null +++ b/libgimpwidgets/gimppickbutton-win32.c @@ -0,0 +1,1135 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-win32.c + * Copyright (C) 2022 Luca Bacci <luca.bacci@outlook.com> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" +#include "gimppickbutton.h" +#include "gimppickbutton-win32.h" + +#include <sdkddkver.h> +#include <windows.h> +#include <windowsx.h> + +#include <stdint.h> + +/* + * NOTES: + * + * This implementation is based on gtk/gtkcolorpickerwin32.c from GTK. + * + * We install a low-level mouse hook so that color picking continues + * even when switching active windows. + * + * Beyond that, we also create keep-above, input-only HWNDs and place + * them on the screen to cover each monitor. This is done to show our + * custom cursor and to avoid giving input to other windows: that way + * the desktop appears "frozen" while picking the color. + * + * Finally, we also set up a low-level keyboard hook to dismiss color- + * picking mode whenever the user presses <ESC>. + * + * Note that low-level hooks for mouse and keyboard do not use any DLL + * injection and are thus non-invasive. + * + * For GTK4: consider using an appropriate GDK surface for input-only + * windows. This'd enable us to also get Wintab input when CXO_SYSTEM + * is not set. + */ + +typedef struct +{ + HMONITOR handle; + wchar_t *device; + HDC hdc; + POINT screen_origin; + int logical_width; + int logical_height; + int physical_width; + int physical_height; +} MonitorData; + +GList *pickers; +HHOOK mouse_hook; +HHOOK keyboard_hook; + +ATOM notif_window_class; +HWND notif_window_handle; +GArray *monitors; + +ATOM input_window_class; +GArray *input_window_handles; + +/* Utils */ +static HMODULE this_module (void); +static MonitorData * monitors_find_for_logical_point (POINT logical); +static MonitorData * monitors_find_for_physical_point (POINT physical); +static POINT logical_to_physical (MonitorData *data, + POINT logical); +static void destroy_window (gpointer ptr); + +/* Mouse cursor */ +static HCURSOR create_cursor_from_rgba32_pixbuf (GdkPixbuf *pixbuf, + POINT hotspot); +static HCURSOR create_cursor (void); + +/* Low-level mouse hook */ +static LRESULT CALLBACK mouse_procedure (int nCode, + WPARAM wParam, + LPARAM lParam); +static gboolean ensure_mouse_hook (void); +static void remove_mouse_hook (void); + +/* Low-level keyboard hook */ +static LRESULT CALLBACK keyboard_procedure (int nCode, + WPARAM wParam, + LPARAM lParam); +static gboolean ensure_keyboard_hook (void); +static void remove_keyboard_hook (void); + +/* Input-only window */ +static LRESULT CALLBACK input_window_procedure (HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); +static gboolean ensure_input_window_class (void); +static void remove_input_window_class (void); +static HWND create_input_window (POINT origin, + int width, + int height); + +/* Hidden notification window */ +static LRESULT CALLBACK notif_window_procedure (HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); +static gboolean ensure_notif_window_class (void); +static void remove_notif_window_class (void); +static gboolean ensure_notif_window (void); +static void remove_notif_window (void); + +/* Monitor enumeration and discovery */ +static void monitor_data_free (gpointer ptr); +static BOOL CALLBACK enum_monitor_callback (HMONITOR hMonitor, + HDC hDC, + RECT *pRect, + LPARAM lParam); +static GArray* enumerate_monitors (void); + +/* GimpPickButtonWin32 */ +static void ensure_input_windows (void); +static void remove_input_windows (void); +static void ensure_monitors (void); +static void remove_monitors (void); +static void ensure_screen_data (void); +static void remove_screen_data (void); +static void screen_changed (void); +static void ensure_screen_tracking (void); +static void remove_screen_tracking (void); +static GimpRGB pick_color_with_gdi (POINT physical_point); +static void user_picked (MonitorData *monitor, + POINT physical_point); +void _gimp_pick_button_win32_pick (GimpPickButton *button); +static void stop_picking (void); + +/* {{{ Utils */ + +/* Gets a handle to the module containing this code. + * Works regardless if we're building a shared or + * static library */ + +static HMODULE +this_module (void) +{ + extern IMAGE_DOS_HEADER __ImageBase; + return (HMODULE) &__ImageBase; +} + +static MonitorData* +monitors_find_for_logical_point (POINT logical) +{ + HMONITOR monitor_handle; + guint i; + + monitor_handle = MonitorFromPoint (logical, MONITOR_DEFAULTTONULL); + if (!monitor_handle) + return NULL; + + ensure_monitors (); + + for (i = 0; i < monitors->len; i++) + { + MonitorData *data = &g_array_index (monitors, MonitorData, i); + + if (data->handle == monitor_handle) + return data; + } + + return NULL; +} + +static MonitorData* +monitors_find_for_physical_point (POINT physical) +{ + guint i; + + ensure_monitors (); + + for (i = 0; i < monitors->len; i++) + { + MonitorData *data = &g_array_index (monitors, MonitorData, i); + RECT physical_rect; + + physical_rect.left = data->screen_origin.x; + physical_rect.top = data->screen_origin.y; + physical_rect.right = physical_rect.left + data->physical_width; + physical_rect.bottom = physical_rect.top + data->physical_height; + + /* TODO: tolerance */ + if (PtInRect (&physical_rect, physical)) + return data; + } + + return NULL; +} + +static POINT +logical_to_physical (MonitorData *data, + POINT logical) +{ + POINT physical = logical; + + if (data && + (data->logical_width != data->physical_width) && + data->logical_width > 0 && data->physical_width > 0) + { + double dpi_scale = (double) data->physical_width / + (double) data->logical_width; + + physical.x = logical.x * dpi_scale; + physical.y = logical.y * dpi_scale; + } + + return physical; +} + +static void +destroy_window (gpointer ptr) +{ + HWND *hwnd = (HWND*) ptr; + DestroyWindow (*hwnd); +} + +/* }}} + * {{{ Mouse cursor */ + +static HCURSOR +create_cursor_from_rgba32_pixbuf (GdkPixbuf *pixbuf, + POINT hotspot) +{ + struct { + BITMAPV5HEADER header; + RGBQUAD colors[2]; + } info; + HBITMAP bitmap = NULL; + uint8_t *bitmap_data = NULL; + HBITMAP mask = NULL; + uint8_t *mask_data = NULL; + unsigned int mask_stride; + HDC hdc = NULL; + int width; + int height; + int size; + int stride; + const uint8_t *pixbuf_data = NULL; + int i_offset; + int j_offset; + int i; + int j; + ICONINFO icon_info; + HICON icon = NULL; + + if (gdk_pixbuf_get_n_channels (pixbuf) != 4) + goto cleanup; + + hdc = GetDC (NULL); + if (!hdc) + goto cleanup; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + stride = gdk_pixbuf_get_rowstride (pixbuf); + size = MAX (width, height); + pixbuf_data = gdk_pixbuf_read_pixels (pixbuf); + + memset (&info, 0, sizeof (info)); + info.header.bV5Size = sizeof (info.header); + info.header.bV5Width = size; + info.header.bV5Height = size; + /* Since Windows XP the OS supports mouse cursors as 32bpp + * bitmaps with alpha channel that are laid out as BGRA32 + * (assuming little-endian) */ + info.header.bV5Planes = 1; + info.header.bV5BitCount = 32; + info.header.bV5Compression = BI_BITFIELDS; + info.header.bV5BlueMask = 0x000000FF; + info.header.bV5GreenMask = 0x0000FF00; + info.header.bV5RedMask = 0x00FF0000; + info.header.bV5AlphaMask = 0xFF000000; + + bitmap = CreateDIBSection (hdc, (BITMAPINFO*) &info, DIB_RGB_COLORS, + (void**) &bitmap_data, NULL, 0); + if (!bitmap || !bitmap_data) + { + g_warning ("CreateDIBSection failed with error code %u", + (unsigned) GetLastError ()); + goto cleanup; + } + + /* We also need to provide a proper mask HBITMAP, otherwise + * CreateIconIndirect() fails with ERROR_INVALID_PARAMETER. + * This mask bitmap is a bitfield indicating whether the */ + memset (&info, 0, sizeof (info)); + info.header.bV5Size = sizeof (info.header); + info.header.bV5Width = size; + info.header.bV5Height = size; + info.header.bV5Planes = 1; + info.header.bV5BitCount = 1; + info.header.bV5Compression = BI_RGB; + info.colors[0] = (RGBQUAD){0x00, 0x00, 0x00}; + info.colors[1] = (RGBQUAD){0xFF, 0xFF, 0xFF}; + + mask = CreateDIBSection (hdc, (BITMAPINFO*) &info, DIB_RGB_COLORS, + (void**) &mask_data, NULL, 0); + if (!mask || !mask_data) + { + g_warning ("CreateDIBSection failed with error code %u", + (unsigned) GetLastError ()); + goto cleanup; + } + + /* MSDN says mask rows are aligned to LONG boundaries */ + mask_stride = (((size + 31) & ~31) >> 3); + + if (width > height) + { + i_offset = 0; + j_offset = (width - height) / 2; + } + else + { + i_offset = (height - width) / 2; + j_offset = 0; + } + + for (j = 0; j < height; j++) /* loop over all the bitmap rows */ + { + uint8_t *bitmap_row = bitmap_data + 4 * ((j + j_offset) * size + i_offset); + uint8_t *mask_byte = mask_data + (j + j_offset) * mask_stride + i_offset / 8; + unsigned int mask_bit = (0x80 >> (i_offset % 8)); + const uint8_t *pixbuf_row = pixbuf_data + (height - j - 1) * stride; + + for (i = 0; i < width; i++) /* loop over the current bitmap row */ + { + uint8_t *bitmap_pixel = bitmap_row + 4 * i; + const uint8_t *pixbuf_pixel = pixbuf_row + 4 * i; + + /* Assign to destination pixel from source pixel + * by also swapping channels as appropriate */ + bitmap_pixel[0] = pixbuf_pixel[2]; + bitmap_pixel[1] = pixbuf_pixel[1]; + bitmap_pixel[2] = pixbuf_pixel[0]; + bitmap_pixel[3] = pixbuf_pixel[3]; + + if (pixbuf_pixel[3] == 0) + mask_byte[0] |= mask_bit; /* turn ON bit */ + else + mask_byte[0] &= ~mask_bit; /* turn OFF bit */ + + mask_bit >>= 1; + if (mask_bit == 0) + { + mask_bit = 0x80; + mask_byte++; + } + } + } + + memset (&icon_info, 0, sizeof (icon_info)); + icon_info.fIcon = FALSE; + icon_info.xHotspot = hotspot.x; + icon_info.yHotspot = hotspot.y; + icon_info.hbmMask = mask; + icon_info.hbmColor = bitmap; + + icon = CreateIconIndirect (&icon_info); + if (!icon) + { + g_warning ("CreateIconIndirect failed with error code %u", + (unsigned) GetLastError ()); + goto cleanup; + } + +cleanup: + if (mask) + DeleteObject (mask); + + if (bitmap) + DeleteObject (bitmap); + + if (hdc) + ReleaseDC (NULL, hdc); + + return (HCURSOR) icon; +} + +static HCURSOR +create_cursor (void) +{ + GdkPixbuf *pixbuf = NULL; + GError *error = NULL; + HCURSOR cursor = NULL; + + pixbuf = gdk_pixbuf_new_from_resource ("/org/gimp/color-picker-cursors/cursor-color-picker.png", + &error); + if (!pixbuf) + { + g_critical ("gdk_pixbuf_new_from_resource failed: %s", + error ? error->message : "unknown error"); + goto cleanup; + } + g_clear_error (&error); + + cursor = create_cursor_from_rgba32_pixbuf (pixbuf, (POINT){ 1, 30 }); + +cleanup: + g_clear_error (&error); + g_clear_object (&pixbuf); + + return cursor; +} + +/* }}} + * {{{ Low-level mouse hook */ + +/* This mouse procedure can detect clicks made on any + * application window. Countrary to mouse capture, this + * method continues to work even after switching active + * windows. */ + +static LRESULT CALLBACK +mouse_procedure (int nCode, + WPARAM wParam, + LPARAM lParam) +{ + if (nCode == HC_ACTION) + { + MSLLHOOKSTRUCT *info = (MSLLHOOKSTRUCT*) lParam; + + switch (wParam) + { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + { + POINT physical; + MonitorData *data; + + if (pickers == NULL) + break; + + /* A low-level mouse hook always receives points in + * per-monitor DPI-aware screen coordinates, regardless of + * the DPI awareness setting of the application. */ + physical = info->pt; + + data = monitors_find_for_physical_point (physical); + if (!data) + g_message ("Captured point (%ld, %ld) doesn't belong to any monitor", + (long) physical.x, (long) physical.y); + + user_picked (data, physical); + + /* It's safe to remove a hook from within its callback. + * Anyway this can even be called from an idle callback, + * as the hook does nothing if there are no pickers. + * (In that case also the ensure functions have to be + * scheduled in an idle callback) */ + stop_picking (); + + return (wParam == WM_XBUTTONDOWN) ? TRUE : 0; + } + break; + default: + break; + } + } + + return CallNextHookEx (NULL, nCode, wParam, lParam); +} + +static gboolean +ensure_mouse_hook (void) +{ + if (!mouse_hook) + { + mouse_hook = SetWindowsHookEx (WH_MOUSE_LL, mouse_procedure, this_module (), 0); + if (!mouse_hook) + { + g_warning ("SetWindowsHookEx failed with error code %u", + (unsigned) GetLastError ()); + return FALSE; + } + } + + return TRUE; +} + +static void +remove_mouse_hook (void) +{ + if (mouse_hook) + { + if (!UnhookWindowsHookEx (mouse_hook)) + { + g_warning ("UnhookWindowsHookEx failed with error code %u", + (unsigned) GetLastError ()); + return; + } + + mouse_hook = NULL; + } +} + +/* }}} + * {{{ Low-level keyboard hook */ + +/* This is used to stop picking anytime the user presses ESC */ + +static LRESULT CALLBACK +keyboard_procedure (int nCode, + WPARAM wParam, + LPARAM lParam) +{ + if (nCode == HC_ACTION) + { + KBDLLHOOKSTRUCT *info = (KBDLLHOOKSTRUCT*) lParam; + + switch (wParam) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (info->vkCode == VK_ESCAPE) + { + stop_picking (); + return 1; + } + break; + default: + break; + } + } + + return CallNextHookEx(NULL, nCode, wParam, lParam); +} + +static gboolean +ensure_keyboard_hook (void) +{ + if (!keyboard_hook) + { + keyboard_hook = SetWindowsHookEx (WH_KEYBOARD_LL, keyboard_procedure, this_module (), 0); + if (!keyboard_hook) + { + g_warning ("SetWindowsHookEx failed with error code %u", + (unsigned) GetLastError ()); + return FALSE; + } + } + + return TRUE; +} + +static void +remove_keyboard_hook (void) +{ + if (keyboard_hook) + { + if (!UnhookWindowsHookEx (keyboard_hook)) + { + g_warning ("UnhookWindowsHookEx failed with error code %u", + (unsigned) GetLastError ()); + return; + } + + keyboard_hook = NULL; + } +} + +/* }}} + * {{{ Input-only windows */ + +/* Those input-only windows are placed to cover each monitor on + * the screen and serve two purposes: display our custom mouse + * cursor and freeze the desktop by avoiding interaction of the + * mouse with other applications */ + +static LRESULT CALLBACK +input_window_procedure (HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + switch (uMsg) + { + case WM_NCCREATE: + /* The shell automatically hides the taskbar when a window + * covers the entire area of a monitor (fullscreened). In + * order to avoid that, we can set a special property on + * the window + * See the docs for ITaskbarList2::MarkFullscreenWindow() + * on MSDN for more informations */ + + if (!SetPropW (hwnd, L"NonRudeHWND", (HANDLE) TRUE)) + g_warning_once ("SetPropW failed with error code %u", + (unsigned) GetLastError ()); + break; + + case WM_NCDESTROY: + /* We have to remove window properties manually before the + * window gets destroyed */ + + if (!RemovePropW (hwnd, L"NonRudeHWND")) + g_warning_once ("SetPropW failed with error code %u", + (unsigned) GetLastError ()); + break; + + /* Avoid any drawing at all. Here we block processing of the + * default window procedure, although we set the NULL_BRUSH + * as the window class's background brush, so even the default + * window procedure wouldn't draw anything at all */ + + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + #if 1 + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + EndPaint(hwnd, &ps); + #else + UINT flags = RDW_VALIDATE | + RDW_NOFRAME | + RDW_NOERASE; + RedrawWindow (hwnd, NULL, NULL, flags); + #endif + } + return 0; + #if 0 + case WM_SYNCPAINT: + return 0; + #endif + + case WM_MOUSEACTIVATE: + /* This can be useful in case we want to avoid + * using a mouse hook */ + return MA_NOACTIVATE; + + /* Anytime we detect a mouse click, pick the color. Note that + * this is rarely used as the mouse hook would process the + * clicks before that */ + + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + { + POINT logical = (POINT){GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam)}; + MonitorData *data = monitors_find_for_logical_point (logical); + POINT physical = logical_to_physical (data, logical); + + if (!data) + g_message ("Captured point (%ld, %ld) doesn't belong to any monitor", + (long) logical.x, (long) logical.y); + + user_picked (data, physical); + + stop_picking (); + } + + return (uMsg == WM_XBUTTONDOWN) ? TRUE : 0; + default: + break; + } + + return DefWindowProcW (hwnd, uMsg, wParam, lParam); +} + +static gboolean +ensure_input_window_class (void) +{ + if (!input_window_class) + { + WNDCLASSEXW wndclassex; + HCURSOR cursor = create_cursor (); + + memset (&wndclassex, 0, sizeof (wndclassex)); + wndclassex.cbSize = sizeof (wndclassex); + wndclassex.hInstance = this_module (); + wndclassex.lpszClassName = L"GimpPickButtonInputWindowClass"; + wndclassex.lpfnWndProc = input_window_procedure; + wndclassex.hbrBackground = GetStockObject (NULL_BRUSH); + wndclassex.hCursor = cursor ? + cursor : + LoadImageW (NULL, + (LPCWSTR)(guintptr)IDC_CROSS, + IMAGE_CURSOR, 0, 0, + LR_DEFAULTSIZE | LR_SHARED); + + input_window_class = RegisterClassExW (&wndclassex); + if (input_window_class == 0) + { + g_warning ("RegisterClassExW failed with error code %u", + (unsigned) GetLastError ()); + + if (cursor) + DestroyCursor (cursor); + + return FALSE; + } + } + + return TRUE; +} + +static void +remove_input_window_class (void) +{ + if (input_window_class) + { + LPCWSTR name = (LPCWSTR)(guintptr)input_window_class; + UnregisterClassW (name, this_module ()); + input_window_class = 0; + } +} + +/* create_input_window expects logical screen coordinates */ + +static HWND +create_input_window (POINT origin, + int width, + int height) +{ + DWORD stylex = WS_EX_NOACTIVATE | WS_EX_TOPMOST; + LPCWSTR wclass; + LPCWSTR title = L"Gimp Input Window"; + DWORD style = WS_POPUP; + HWND hwnd; + + if (!ensure_input_window_class ()) + return FALSE; + + /* This must go after the ensure_input_window_class */ + wclass = (LPCWSTR)(guintptr)input_window_class; + + hwnd = CreateWindowExW (stylex, wclass, title, style, + origin.x, origin.y, width, height, + NULL, NULL, this_module (), NULL); + if (hwnd == NULL) + { + g_warning_once ("CreateWindowExW failed with error code %u", + (unsigned) GetLastError ()); + return FALSE; + } + + ShowWindow (hwnd, SW_SHOWNOACTIVATE); + + return hwnd; +} + +/* }}} + * {{{ Hidden notification window */ + +/* We setup a hidden window to listen for WM_DISPLAYCAHNGE + * messages and reposition the input-only windows on the + * screen */ + +static LRESULT CALLBACK +notif_window_procedure (HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + switch (uMsg) + { + case WM_DISPLAYCHANGE: + screen_changed (); + break; + default: + break; + } + + return DefWindowProcW (hwnd, uMsg, wParam, lParam); +} + +static gboolean +ensure_notif_window_class (void) +{ + if (!notif_window_class) + { + WNDCLASSEXW wndclassex; + + memset (&wndclassex, 0, sizeof (wndclassex)); + wndclassex.cbSize = sizeof (wndclassex); + wndclassex.hInstance = this_module (); + wndclassex.lpszClassName = L"GimpPickButtonNotifWindowClass"; + wndclassex.lpfnWndProc = notif_window_procedure; + + notif_window_class = RegisterClassExW (&wndclassex); + if (notif_window_class == 0) + { + g_warning ("RegisterClassExW failed with error code %u", + (unsigned) GetLastError ()); + return FALSE; + } + } + + return TRUE; +} + +static void +remove_notif_window_class (void) +{ + if (notif_window_class) + { + LPCWSTR name = (LPCWSTR)(guintptr)notif_window_class; + UnregisterClassW (name, this_module ()); + notif_window_class = 0; + } +} + +static gboolean +ensure_notif_window (void) +{ + if (!ensure_notif_window_class ()) + return FALSE; + + if (!notif_window_handle) + { + DWORD stylex = 0; + LPCWSTR wclass = (LPCWSTR)(guintptr)notif_window_class; + LPCWSTR title = L"Gimp Notifications Window"; + DWORD style = WS_POPUP; + + notif_window_handle = CreateWindowExW (stylex, wclass, + title, style, + 0, 0, 1, 1, + NULL, NULL, + this_module (), + NULL); + if (notif_window_handle == NULL) + { + g_warning_once ("CreateWindowExW failed with error code %u", + (unsigned) GetLastError ()); + return FALSE; + } + } + + return TRUE; +} + +static void +remove_notif_window (void) +{ + if (notif_window_handle) + { + DestroyWindow (notif_window_handle); + notif_window_handle = NULL; + } + + remove_notif_window_class (); +} + +/* }}} + * {{{ Monitor enumeration and discovery */ + +static void +monitor_data_free (gpointer ptr) +{ + MonitorData *data = (MonitorData*) ptr; + + if (data->device) + free (data->device); +} + +static BOOL CALLBACK +enum_monitor_callback (HMONITOR hMonitor, + HDC hDC, + RECT *pRect, + LPARAM lParam) +{ + GArray *result = (GArray*) lParam; + MonitorData data; + MONITORINFOEXW info; + DEVMODEW devmode; + + const BOOL CALLBACK_CONTINUE = TRUE; + const BOOL CALLBACK_STOP = FALSE; + + if (!pRect) + return CALLBACK_CONTINUE; + + if (IsRectEmpty (pRect)) + return CALLBACK_CONTINUE; + + memset (&data, 0, sizeof (data)); + data.handle = hMonitor; + data.hdc = hDC; + data.screen_origin.x = pRect->left; + data.screen_origin.y = pRect->top; + data.logical_width = pRect->right - pRect->left; + data.logical_height = pRect->bottom - pRect->top; + + memset (&info, 0, sizeof (info)); + info.cbSize = sizeof (info); + if (!GetMonitorInfoW (hMonitor, (MONITORINFO*) &info)) + { + g_warning_once ("GetMonitorInfo failed with error code %u", + (unsigned) GetLastError ()); + return CALLBACK_CONTINUE; + } + + data.device = _wcsdup (info.szDevice); + + memset (&devmode, 0, sizeof (devmode)); + devmode.dmSize = sizeof (devmode); + if (!EnumDisplaySettingsExW (info.szDevice, + ENUM_CURRENT_SETTINGS, + &devmode, EDS_ROTATEDMODE)) + { + g_warning_once ("EnumDisplaySettingsEx failed with error code %u", + (unsigned) GetLastError ()); + return CALLBACK_CONTINUE; + } + + if (devmode.dmPelsWidth) + data.physical_width = devmode.dmPelsWidth; + + if (devmode.dmPelsHeight) + data.physical_height = devmode.dmPelsHeight; + + g_array_append_val (result, data); + + if (result->len >= 100) + return CALLBACK_STOP; + + return CALLBACK_CONTINUE; +} + +static GArray* +enumerate_monitors (void) +{ + int count_monitors; + guint length_hint; + GArray *result; + + count_monitors = GetSystemMetrics (SM_CMONITORS); + + if (count_monitors > 0 && count_monitors < 100) + length_hint = (guint) count_monitors; + else + length_hint = 1; + + result = g_array_sized_new (FALSE, TRUE, sizeof (MonitorData), length_hint); + g_array_set_clear_func (result, monitor_data_free); + + if (!EnumDisplayMonitors (NULL, NULL, enum_monitor_callback, (LPARAM) result)) + { + g_warning ("EnumDisplayMonitors failed with error code %u", + (unsigned) GetLastError ()); + } + + return result; +} + +static void +ensure_input_windows (void) +{ + ensure_monitors (); + + if (!input_window_handles) + { + guint i; + + input_window_handles = g_array_sized_new (FALSE, TRUE, + sizeof (HWND), + monitors->len); + g_array_set_clear_func (input_window_handles, destroy_window); + + for (i = 0; i < monitors->len; i++) + { + MonitorData *data = &g_array_index (monitors, MonitorData, i); + HWND hwnd = create_input_window (data->screen_origin, + data->logical_width, + data->logical_height); + + if (hwnd) + g_array_append_val (input_window_handles, hwnd); + } + } +} + +static void +remove_input_windows (void) +{ + if (input_window_handles) + { + g_array_free (input_window_handles, TRUE); + input_window_handles = NULL; + } +} + +static void +ensure_monitors (void) +{ + if (!monitors) + monitors = enumerate_monitors (); +} + +static void +remove_monitors (void) +{ + if (monitors) + { + g_array_free (monitors, TRUE); + monitors = NULL; + } +} + +static void +ensure_screen_data (void) +{ + ensure_monitors (); + ensure_input_windows (); +} + +static void +remove_screen_data (void) +{ + remove_input_windows (); + remove_monitors (); +} + +static void +screen_changed (void) +{ + remove_screen_data (); + ensure_screen_data (); +} + +static void +ensure_screen_tracking (void) +{ + ensure_notif_window (); + screen_changed (); +} + +static void +remove_screen_tracking (void) +{ + remove_notif_window (); + remove_screen_data (); +} + +/* }}} + * {{{ GimpPickButtonWin32 */ + +/* pick_color_with_gdi is based on the GDI GetPixel() function. + * Note that GDI only returns 8bit per-channel color data, but + * as of today there's no documented method to retrieve colors + * at higher bit depths. */ + +static GimpRGB +pick_color_with_gdi (POINT physical_point) +{ + GimpRGB rgb; + COLORREF color; + HDC hdc; + + hdc = GetDC (HWND_DESKTOP); + + if (!(GetDeviceCaps (hdc, RASTERCAPS) & RC_BITBLT)) + g_warning_once ("RC_BITBLT capability missing from device context"); + + color = GetPixel (hdc, physical_point.x, physical_point.y); + + gimp_rgba_set_uchar (&rgb, GetRValue (color), GetGValue (color), GetBValue (color), 255); + + ReleaseDC (HWND_DESKTOP, hdc); + + return rgb; +} + +static void +user_picked (MonitorData *monitor, + POINT physical_point) +{ + GimpRGB rgb; + GList *l; + + /* Currently unused */ + (void) monitor; + + rgb = pick_color_with_gdi (physical_point); + + for (l = pickers; l != NULL; l = l->next) + { + GimpPickButton *button = GIMP_PICK_BUTTON (l->data); + g_signal_emit_by_name (button, "color-picked", &rgb); + } +} + +/* entry point to this file, called from gimppickbutton.c */ +void +_gimp_pick_button_win32_pick (GimpPickButton *button) +{ + ensure_mouse_hook (); + ensure_keyboard_hook (); + ensure_screen_tracking (); + + if (g_list_find (pickers, button)) + return; + + pickers = g_list_prepend (pickers, + g_object_ref (button)); +} + +static void +stop_picking (void) +{ + remove_screen_tracking (); + remove_keyboard_hook (); + remove_mouse_hook (); + + g_list_free_full (pickers, g_object_unref); + pickers = NULL; +} diff --git a/libgimpwidgets/gimppickbutton-win32.h b/libgimpwidgets/gimppickbutton-win32.h new file mode 100644 index 0000000..eaeaa77 --- /dev/null +++ b/libgimpwidgets/gimppickbutton-win32.h @@ -0,0 +1,23 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-win32.h + * Copyright (C) 2022 Luca Bacci <luca.bacci@outlook.com> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* Private header file which is not meant to be exported. */ +#ifndef __GIMP_PICK_BUTTON_WIN32_H__ +#define __GIMP_PICK_BUTTON_WIN32_H__ + +G_GNUC_INTERNAL void _gimp_pick_button_win32_pick (GimpPickButton *button); + +#endif /* __GIMP_PICK_BUTTON_WIN32_H__ */ diff --git a/libgimpwidgets/gimppickbutton-xdg.c b/libgimpwidgets/gimppickbutton-xdg.c new file mode 100644 index 0000000..4ef42ab --- /dev/null +++ b/libgimpwidgets/gimppickbutton-xdg.c @@ -0,0 +1,152 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-xdg.c + * Copyright (C) 2021 Niels De Graef <nielsdegraef@gmail.com> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" +#include "gimppickbutton.h" +#include "gimppickbutton-default.h" +#include "gimppickbutton-xdg.h" + +#include "libgimp/libgimp-intl.h" + +gboolean +_gimp_pick_button_xdg_available (void) +{ + GDBusProxy *proxy = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Screenshot", + NULL, NULL); + + if (proxy) + { + GError *error = NULL; + + g_dbus_proxy_call_sync (proxy, "org.freedesktop.DBus.Peer.Ping", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (! error) + return TRUE; + + g_clear_error (&error); + + g_object_unref (proxy); + proxy = NULL; + } + + return FALSE; +} + +static void +pick_color_xdg_dbus_signal (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + GimpPickButton *button) +{ + if (g_strcmp0 (signal_name, "Response") == 0) + { + GVariant *results; + guint32 response; + + g_variant_get (parameters, "(u@a{sv})", + &response, + &results); + + /* Possible values: + * 0: Success, the request is carried out + * 1: The user cancelled the interaction + * 2: The user interaction was ended in some other way + * Cf. https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.Request.xml + */ + if (response == 0) + { + GimpRGB color; + + if (g_variant_lookup (results, "color", "(ddd)", &color.r, &color.g, &color.b)) + { + g_signal_emit_by_name (button, "color-picked", &color); + } + } + + g_variant_unref (results); + /* Quit anyway. */ + gtk_main_quit (); + } +} + +/* entry point to this file, called from gimppickbutton.c */ +void +_gimp_pick_button_xdg_pick (GimpPickButton *button) +{ + GDBusProxy *proxy = NULL; + GVariant *retval = NULL; + gchar *opath = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Screenshot", + NULL, NULL); + if (!proxy) + { + return; + } + + retval = g_dbus_proxy_call_sync (proxy, "PickColor", + g_variant_new ("(sa{sv})", "", NULL), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL); + g_clear_object (&proxy); + if (retval) + { + g_variant_get (retval, "(o)", &opath); + g_variant_unref (retval); + } + + if (opath) + { + GDBusProxy *proxy2 = NULL; + + proxy2 = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.freedesktop.portal.Desktop", + opath, + "org.freedesktop.portal.Request", + NULL, NULL); + g_signal_connect (proxy2, "g-signal", + G_CALLBACK (pick_color_xdg_dbus_signal), + button); + + gtk_main (); + g_object_unref (proxy2); + g_free (opath); + } +} diff --git a/libgimpwidgets/gimppickbutton-xdg.h b/libgimpwidgets/gimppickbutton-xdg.h new file mode 100644 index 0000000..394665e --- /dev/null +++ b/libgimpwidgets/gimppickbutton-xdg.h @@ -0,0 +1,25 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton-xdg.h + * Copyright (C) 2021 Niels De Graef <nielsdegraef@gmail.com> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* Private header file which is not meant to be exported. */ +#ifndef __GIMP_PICK_BUTTON_XDG_H__ +#define __GIMP_PICK_BUTTON_XDG_H__ + +gboolean _gimp_pick_button_xdg_available (void); +void _gimp_pick_button_xdg_pick (GimpPickButton *button); + +#endif /* __GIMP_PICK_BUTTON_XDG_H__ */ + diff --git a/libgimpwidgets/gimppickbutton.c b/libgimpwidgets/gimppickbutton.c new file mode 100644 index 0000000..1611f5d --- /dev/null +++ b/libgimpwidgets/gimppickbutton.c @@ -0,0 +1,188 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton.c + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on gtk+/gtk/gtkcolorsel.c + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimpcairo-utils.h" +#include "gimphelpui.h" +#include "gimpicons.h" +#include "gimppickbutton.h" + +#if defined (GDK_WINDOWING_QUARTZ) +#include "gimppickbutton-quartz.h" +#elif defined (GDK_WINDOWING_WIN32) +#include "gimppickbutton-win32.h" +#else +#include "gimppickbutton-default.h" +#include "gimppickbutton-kwin.h" +#include "gimppickbutton-xdg.h" +#endif + +#include "libgimp/libgimp-intl.h" + +/** + * SECTION: gimppickbutton + * @title: GimpPickButton + * @short_description: Widget to pick a color from screen. + * + * #GimpPickButton is a specialized button. When clicked, it changes + * the cursor to a color-picker pipette and allows the user to pick a + * color from any point on the screen. + **/ + + +enum +{ + COLOR_PICKED, + LAST_SIGNAL +}; + +static void gimp_pick_button_dispose (GObject *object); + +static void gimp_pick_button_clicked (GtkButton *button); + + +G_DEFINE_TYPE (GimpPickButton, gimp_pick_button, GTK_TYPE_BUTTON) + +#define parent_class gimp_pick_button_parent_class + +static guint pick_button_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_pick_button_class_init (GimpPickButtonClass* klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + + /** + * GimpPickButton::color-picked: + * @gimppickbutton: the object which received the signal. + * @arg1: pointer to a #GimpRGB structure that holds the picked color + * + * This signal is emitted when the user has picked a color. + **/ + pick_button_signals[COLOR_PICKED] = + g_signal_new ("color-picked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPickButtonClass, color_picked), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + object_class->dispose = gimp_pick_button_dispose; + + button_class->clicked = gimp_pick_button_clicked; + + klass->color_picked = NULL; +} + +static void +gimp_pick_button_init (GimpPickButton *button) +{ + GtkWidget *image; + + image = gtk_image_new_from_icon_name (GIMP_ICON_COLOR_PICK_FROM_SCREEN, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + gimp_help_set_help_data (GTK_WIDGET (button), + _("Click the eyedropper, then click a color " + "anywhere on your screen to select that color."), + NULL); +} + +static void +gimp_pick_button_dispose (GObject *object) +{ + GimpPickButton *button = GIMP_PICK_BUTTON (object); + + if (button->cursor) + { + gdk_cursor_unref (button->cursor); + button->cursor = NULL; + } + + if (button->grab_widget) + { + gtk_widget_destroy (button->grab_widget); + button->grab_widget = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_pick_button_clicked (GtkButton *button) +{ +#if defined (GDK_WINDOWING_QUARTZ) + _gimp_pick_button_quartz_pick (GIMP_PICK_BUTTON (button)); +#elif defined (GDK_WINDOWING_WIN32) + _gimp_pick_button_win32_pick (GIMP_PICK_BUTTON (button)); +#elif defined (GDK_WINDOWING_X11) + /* It's a bit weird as we use the default pick code both in first and + * last cases. It's because when running GIMP on X11 in particular, + * the portals don't return color space information. So the returned + * color is in the display space, not in the current image space and + * we have no way to convert the data back (well if running on X11, we + * could still get a profile from the display, but if there are + * several displays, we can't know for sure where the color was picked + * from.). + * See: https://github.com/flatpak/xdg-desktop-portal/issues/862 + */ + _gimp_pick_button_default_pick (GIMP_PICK_BUTTON (button)); +#else + if (_gimp_pick_button_xdg_available ()) + _gimp_pick_button_xdg_pick (GIMP_PICK_BUTTON (button)); + else if (_gimp_pick_button_kwin_available ()) + _gimp_pick_button_kwin_pick (GIMP_PICK_BUTTON (button)); + else + _gimp_pick_button_default_pick (GIMP_PICK_BUTTON (button)); +#endif +} + + +/* public functions */ + +/** + * gimp_pick_button_new: + * + * Creates a new #GimpPickButton widget. + * + * Returns: A new #GimpPickButton widget. + **/ +GtkWidget * +gimp_pick_button_new (void) +{ + return g_object_new (GIMP_TYPE_PICK_BUTTON, NULL); +} diff --git a/libgimpwidgets/gimppickbutton.h b/libgimpwidgets/gimppickbutton.h new file mode 100644 index 0000000..e5114a0 --- /dev/null +++ b/libgimpwidgets/gimppickbutton.h @@ -0,0 +1,69 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppickbutton.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * based on gtk-2-0/gtk/gtkcolorsel.c + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PICK_BUTTON_H__ +#define __GIMP_PICK_BUTTON_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_PICK_BUTTON (gimp_pick_button_get_type ()) +#define GIMP_PICK_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PICK_BUTTON, GimpPickButton)) +#define GIMP_PICK_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PICK_BUTTON, GimpPickButtonClass)) +#define GIMP_IS_PICK_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PICK_BUTTON)) +#define GIMP_IS_PICK_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PICK_BUTTON)) +#define GIMP_PICK_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PICK_BUTTON, GimpPickButtonClass)) + + +typedef struct _GimpPickButtonClass GimpPickButtonClass; + +struct _GimpPickButton +{ + GtkButton parent_instance; + + /*< private >*/ + GdkCursor *cursor; + GtkWidget *grab_widget; +}; + +struct _GimpPickButtonClass +{ + GtkButtonClass parent_class; + + void (* color_picked) (GimpPickButton *button, + const GimpRGB *color); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_pick_button_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_pick_button_new (void); + + +G_END_DECLS + +#endif /* __GIMP_PICK_BUTTON_H__ */ diff --git a/libgimpwidgets/gimppixmap.c b/libgimpwidgets/gimppixmap.c new file mode 100644 index 0000000..d841885 --- /dev/null +++ b/libgimpwidgets/gimppixmap.c @@ -0,0 +1,179 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppixmap.c + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#undef GSEAL_ENABLE +#undef GTK_DISABLE_DEPRECATED +#undef GDK_DISABLE_DEPRECATED + +#include <stdio.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimppixmap.h" + + +/** + * SECTION: gimppixmap + * @title: GimpPixmap + * @short_description: Widget which creates a #GtkPixmap from XPM data. + * @see_also: gimp_pixmap_button_new(), #GtkPixmap + * + * Widget which creates a #GtkPixmap from XPM data. + * + * Use this widget instead of #GtkPixmap if you don't want to worry + * about the parent container's "realized" state. + * + * Note that the drawback of the easy interface is that the actual + * #GdkPixmap and it's mask have to be constructed every time you call + * gimp_pixmap_new() and cannot be cached in memory without doing bad + * hacks. + **/ + + +static void gimp_pixmap_realize (GtkWidget *widget); +static void gimp_pixmap_create_from_xpm_d (GimpPixmap *pixmap); + + +G_DEFINE_TYPE (GimpPixmap, gimp_pixmap, GTK_TYPE_IMAGE) + +#define parent_class gimp_pixmap_parent_class + + +static void +gimp_pixmap_class_init (GimpPixmapClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->realize = gimp_pixmap_realize; +} + +static void +gimp_pixmap_init (GimpPixmap *pixmap) +{ + pixmap->xpm_data = NULL; +} + +/** + * gimp_pixmap_new: + * @xpm_data: A pointer to a XPM data structure as found in XPM files. + * + * Creates a new #GimpPixmap widget. + * + * Returns: A pointer to the new #GimpPixmap widget. + **/ +GtkWidget * +gimp_pixmap_new (gchar **xpm_data) +{ + GimpPixmap *pixmap = g_object_new (GIMP_TYPE_PIXMAP, NULL); + + gimp_pixmap_set (pixmap, xpm_data); + + return GTK_WIDGET (pixmap); +} + +/** + * gimp_pixmap_set: + * @pixmap: The pixmap widget you want to set the new xpm_data for. + * @xpm_data: A pointer to a XPM data structure as found in XPM files. + * + * Sets a new image for an existing #GimpPixmap widget. + **/ +void +gimp_pixmap_set (GimpPixmap *pixmap, + gchar **xpm_data) +{ + g_return_if_fail (GIMP_IS_PIXMAP (pixmap)); + + pixmap->xpm_data = xpm_data; + + GTK_WIDGET (pixmap)->requisition.width = 0; + GTK_WIDGET (pixmap)->requisition.height = 0; + + if (! gtk_widget_get_realized (GTK_WIDGET (pixmap))) + { + if (xpm_data) + { + gint width, height; + + if (sscanf (xpm_data[0], "%d %d", &width, &height) != 2) + { + g_warning ("%s: passed pointer is no XPM data", G_STRFUNC); + } + else + { + gint xpad, ypad; + + gtk_misc_get_padding (GTK_MISC (pixmap), &xpad, &ypad); + + GTK_WIDGET (pixmap)->requisition.width = width + xpad * 2; + GTK_WIDGET (pixmap)->requisition.height = height + ypad * 2; + } + } + } + else + { + gimp_pixmap_create_from_xpm_d (pixmap); + } +} + +static void +gimp_pixmap_realize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (parent_class)->realize) + GTK_WIDGET_CLASS (parent_class)->realize (widget); + + gimp_pixmap_create_from_xpm_d (GIMP_PIXMAP (widget)); +} + +static void +gimp_pixmap_create_from_xpm_d (GimpPixmap *pixmap) +{ + GtkStyle *style; + GdkPixmap *gdk_pixmap = NULL; + GdkBitmap *mask = NULL; + + if (pixmap->xpm_data) + { + GtkWidget *widget; + + widget = GTK_WIDGET (pixmap); + + style = gtk_widget_get_style (widget); + + gdk_pixmap = gdk_pixmap_create_from_xpm_d (gtk_widget_get_window (widget), + &mask, + &style->bg[GTK_STATE_NORMAL], + pixmap->xpm_data); + } + + gtk_image_set_from_pixmap (GTK_IMAGE (pixmap), gdk_pixmap, mask); + + if (gdk_pixmap) + g_object_unref (gdk_pixmap); + + if (mask) + g_object_unref (mask); +} diff --git a/libgimpwidgets/gimppixmap.h b/libgimpwidgets/gimppixmap.h new file mode 100644 index 0000000..3b71389 --- /dev/null +++ b/libgimpwidgets/gimppixmap.h @@ -0,0 +1,78 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppixmap.h + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef GIMP_DISABLE_DEPRECATED + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PIXMAP_H__ +#define __GIMP_PIXMAP_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_PIXMAP (gimp_pixmap_get_type ()) +#define GIMP_PIXMAP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PIXMAP, GimpPixmap)) +#define GIMP_PIXMAP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PIXMAP, GimpPixmapClass)) +#define GIMP_IS_PIXMAP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PIXMAP)) +#define GIMP_IS_PIXMAP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PIXMAP)) +#define GIMP_PIXMAP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PIXMAP, GimpPixmapClass)) + + +typedef struct _GimpPixmapClass GimpPixmapClass; + +struct _GimpPixmap +{ + GtkImage parent_instance; + + gchar **xpm_data; +}; + +struct _GimpPixmapClass +{ + GtkImageClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_pixmap_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_pixmap_new (gchar **xpm_data); + +void gimp_pixmap_set (GimpPixmap *pixmap, + gchar **xpm_data); + + +G_END_DECLS + +#endif /* __GIMP_PIXMAP_H__ */ + +#endif /* GIMP_DISABLE_DEPRECATED */ + diff --git a/libgimpwidgets/gimppreview.c b/libgimpwidgets/gimppreview.c new file mode 100644 index 0000000..75b38ea --- /dev/null +++ b/libgimpwidgets/gimppreview.c @@ -0,0 +1,851 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppreview.c + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "gimpwidgets.h" + +#include "gimppreview.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimppreview + * @title: GimpPreview + * @short_description: A widget providing a #GimpPreviewArea plus + * framework to update the preview. + * + * A widget providing a #GimpPreviewArea plus framework to update the + * preview. + **/ + + +#define DEFAULT_SIZE 200 +#define PREVIEW_TIMEOUT 200 + + +enum +{ + INVALIDATED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_UPDATE +}; + +typedef struct +{ + GtkWidget *controls; +} GimpPreviewPrivate; + +#define GIMP_PREVIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIMP_TYPE_PREVIEW, GimpPreviewPrivate)) + + +static void gimp_preview_class_init (GimpPreviewClass *klass); +static void gimp_preview_init (GimpPreview *preview); +static void gimp_preview_dispose (GObject *object); +static void gimp_preview_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_preview_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_preview_direction_changed (GtkWidget *widget, + GtkTextDirection prev_dir); +static gboolean gimp_preview_popup_menu (GtkWidget *widget); + +static void gimp_preview_area_realize (GtkWidget *widget, + GimpPreview *preview); +static void gimp_preview_area_unrealize (GtkWidget *widget, + GimpPreview *preview); +static void gimp_preview_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpPreview *preview); +static void gimp_preview_area_set_cursor (GimpPreview *preview); +static gboolean gimp_preview_area_event (GtkWidget *area, + GdkEvent *event, + GimpPreview *preview); + +static void gimp_preview_toggle_callback (GtkWidget *toggle, + GimpPreview *preview); + +static void gimp_preview_notify_checks (GimpPreview *preview); + +static gboolean gimp_preview_invalidate_now (GimpPreview *preview); +static void gimp_preview_real_set_cursor (GimpPreview *preview); +static void gimp_preview_real_transform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y); +static void gimp_preview_real_untransform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y); + + +static guint preview_signals[LAST_SIGNAL] = { 0 }; + +static GtkBoxClass *parent_class = NULL; + + +GType +gimp_preview_get_type (void) +{ + static GType preview_type = 0; + + if (! preview_type) + { + const GTypeInfo preview_info = + { + sizeof (GimpPreviewClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gimp_preview_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GimpPreview), + 0, /* n_preallocs */ + (GInstanceInitFunc) gimp_preview_init, + }; + + preview_type = g_type_register_static (GTK_TYPE_BOX, + "GimpPreview", + &preview_info, + G_TYPE_FLAG_ABSTRACT); + } + + return preview_type; +} + +static void +gimp_preview_class_init (GimpPreviewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + preview_signals[INVALIDATED] = + g_signal_new ("invalidated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPreviewClass, invalidated), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_preview_dispose; + object_class->get_property = gimp_preview_get_property; + object_class->set_property = gimp_preview_set_property; + + widget_class->direction_changed = gimp_preview_direction_changed; + widget_class->popup_menu = gimp_preview_popup_menu; + + klass->draw = NULL; + klass->draw_thumb = NULL; + klass->draw_buffer = NULL; + klass->set_cursor = gimp_preview_real_set_cursor; + klass->transform = gimp_preview_real_transform; + klass->untransform = gimp_preview_real_untransform; + + g_type_class_add_private (object_class, sizeof (GimpPreviewPrivate)); + + g_object_class_install_property (object_class, + PROP_UPDATE, + g_param_spec_boolean ("update", + "Update", + "Whether the preview should update automatically", + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("size", + "Size", + "The preview's size", + 1, 1024, + DEFAULT_SIZE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_preview_init (GimpPreview *preview) +{ + GimpPreviewPrivate *priv = GIMP_PREVIEW_GET_PRIVATE (preview); + GtkWidget *frame; + gdouble xalign = 0.0; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (preview), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_homogeneous (GTK_BOX (preview), FALSE); + gtk_box_set_spacing (GTK_BOX (preview), 6); + + if (gtk_widget_get_direction (GTK_WIDGET (preview)) == GTK_TEXT_DIR_RTL) + xalign = 1.0; + + preview->frame = gtk_aspect_frame_new (NULL, xalign, 0.0, 1.0, TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (preview->frame), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (preview), preview->frame, TRUE, TRUE, 0); + gtk_widget_show (preview->frame); + + preview->table = gtk_table_new (3, 2, FALSE); + gtk_table_set_row_spacing (GTK_TABLE (preview->table), 1, 3); + gtk_container_add (GTK_CONTAINER (preview->frame), preview->table); + gtk_widget_show (preview->table); + + preview->timeout_id = 0; + + preview->xmin = preview->ymin = 0; + preview->xmax = preview->ymax = 1; + preview->width = preview->xmax - preview->xmin; + preview->height = preview->ymax - preview->ymin; + + preview->xoff = 0; + preview->yoff = 0; + + preview->default_cursor = NULL; + + /* preview area */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_table_attach (GTK_TABLE (preview->table), frame, 0, 1, 0, 1, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + gtk_widget_show (frame); + + preview->area = gimp_preview_area_new (); + gtk_container_add (GTK_CONTAINER (frame), preview->area); + gtk_widget_show (preview->area); + + g_signal_connect_swapped (preview->area, "notify::check-size", + G_CALLBACK (gimp_preview_notify_checks), + preview); + g_signal_connect_swapped (preview->area, "notify::check-type", + G_CALLBACK (gimp_preview_notify_checks), + preview); + + gtk_widget_add_events (preview->area, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_MOTION_MASK); + + g_signal_connect (preview->area, "event", + G_CALLBACK (gimp_preview_area_event), + preview); + + g_signal_connect (preview->area, "realize", + G_CALLBACK (gimp_preview_area_realize), + preview); + g_signal_connect (preview->area, "unrealize", + G_CALLBACK (gimp_preview_area_unrealize), + preview); + + g_signal_connect_data (preview->area, "realize", + G_CALLBACK (gimp_preview_area_set_cursor), + preview, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED); + + g_signal_connect (preview->area, "size-allocate", + G_CALLBACK (gimp_preview_area_size_allocate), + preview); + + g_signal_connect_data (preview->area, "size-allocate", + G_CALLBACK (gimp_preview_area_set_cursor), + preview, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED); + + priv->controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_table_attach (GTK_TABLE (preview->table), priv->controls, 0, 2, 2, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + gtk_widget_show (priv->controls); + + /* toggle button to (de)activate the instant preview */ + preview->toggle = gtk_check_button_new_with_mnemonic (_("_Preview")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (preview->toggle), + preview->update_preview); + gtk_box_pack_start (GTK_BOX (priv->controls), preview->toggle, TRUE, TRUE, 0); + gtk_widget_show (preview->toggle); + + g_signal_connect (preview->toggle, "toggled", + G_CALLBACK (gimp_preview_toggle_callback), + preview); +} + +static void +gimp_preview_dispose (GObject *object) +{ + GimpPreview *preview = GIMP_PREVIEW (object); + + if (preview->timeout_id) + { + g_source_remove (preview->timeout_id); + preview->timeout_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_preview_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPreview *preview = GIMP_PREVIEW (object); + + switch (property_id) + { + case PROP_UPDATE: + g_value_set_boolean (value, preview->update_preview); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_preview_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPreview *preview = GIMP_PREVIEW (object); + + switch (property_id) + { + case PROP_UPDATE: + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (preview->toggle), + g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_preview_direction_changed (GtkWidget *widget, + GtkTextDirection prev_dir) +{ + GimpPreview *preview = GIMP_PREVIEW (widget); + gdouble xalign = 0.0; + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + xalign = 1.0; + + gtk_aspect_frame_set (GTK_ASPECT_FRAME (preview->frame), + xalign, 0.0, 1.0, TRUE); +} + +static gboolean +gimp_preview_popup_menu (GtkWidget *widget) +{ + GimpPreview *preview = GIMP_PREVIEW (widget); + + gimp_preview_area_menu_popup (GIMP_PREVIEW_AREA (preview->area), NULL); + + return TRUE; +} + +static void +gimp_preview_area_realize (GtkWidget *widget, + GimpPreview *preview) +{ + GdkDisplay *display = gtk_widget_get_display (widget); + + g_return_if_fail (preview->cursor_busy == NULL); + + preview->cursor_busy = gdk_cursor_new_for_display (display, GDK_WATCH); + +} + +static void +gimp_preview_area_unrealize (GtkWidget *widget, + GimpPreview *preview) +{ + if (preview->cursor_busy) + { + gdk_cursor_unref (preview->cursor_busy); + preview->cursor_busy = NULL; + } +} + +static void +gimp_preview_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpPreview *preview) +{ + gint width = preview->xmax - preview->xmin; + gint height = preview->ymax - preview->ymin; + + preview->width = MIN (width, allocation->width); + preview->height = MIN (height, allocation->height); + + gimp_preview_draw (preview); + gimp_preview_invalidate (preview); +} + +static void +gimp_preview_area_set_cursor (GimpPreview *preview) +{ + GIMP_PREVIEW_GET_CLASS (preview)->set_cursor (preview); +} + +static gboolean +gimp_preview_area_event (GtkWidget *area, + GdkEvent *event, + GimpPreview *preview) +{ + GdkEventButton *button_event = (GdkEventButton *) event; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + switch (button_event->button) + { + case 3: + gimp_preview_area_menu_popup (GIMP_PREVIEW_AREA (area), button_event); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +static void +gimp_preview_toggle_callback (GtkWidget *toggle, + GimpPreview *preview) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle))) + { + preview->update_preview = TRUE; + + g_object_notify (G_OBJECT (preview), "update"); + + if (preview->timeout_id) + g_source_remove (preview->timeout_id); + + gimp_preview_invalidate_now (preview); + } + else + { + preview->update_preview = FALSE; + + g_object_notify (G_OBJECT (preview), "update"); + + gimp_preview_draw (preview); + } +} + +static void +gimp_preview_notify_checks (GimpPreview *preview) +{ + gimp_preview_draw (preview); + gimp_preview_invalidate (preview); +} + +static gboolean +gimp_preview_invalidate_now (GimpPreview *preview) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (preview)); + GimpPreviewClass *class = GIMP_PREVIEW_GET_CLASS (preview); + + gimp_preview_draw (preview); + + preview->timeout_id = 0; + + if (toplevel && gtk_widget_get_realized (toplevel)) + { + gdk_window_set_cursor (gtk_widget_get_window (toplevel), + preview->cursor_busy); + gdk_window_set_cursor (gtk_widget_get_window (preview->area), + preview->cursor_busy); + + gdk_flush (); + + g_signal_emit (preview, preview_signals[INVALIDATED], 0); + + class->set_cursor (preview); + gdk_window_set_cursor (gtk_widget_get_window (toplevel), NULL); + } + else + { + g_signal_emit (preview, preview_signals[INVALIDATED], 0); + } + + return FALSE; +} + +static void +gimp_preview_real_set_cursor (GimpPreview *preview) +{ + if (gtk_widget_get_realized (preview->area)) + gdk_window_set_cursor (gtk_widget_get_window (preview->area), + preview->default_cursor); +} + +static void +gimp_preview_real_transform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + *dest_x = src_x - preview->xoff - preview->xmin; + *dest_y = src_y - preview->yoff - preview->ymin; +} + +static void +gimp_preview_real_untransform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + *dest_x = src_x + preview->xoff + preview->xmin; + *dest_y = src_y + preview->yoff + preview->ymin; +} + +/** + * gimp_preview_set_update: + * @preview: a #GimpPreview widget + * @update: %TRUE if the preview should invalidate itself when being + * scrolled or when gimp_preview_invalidate() is being called + * + * Sets the state of the "Preview" check button. + * + * Since: 2.2 + **/ +void +gimp_preview_set_update (GimpPreview *preview, + gboolean update) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + + g_object_set (preview, + "update", update, + NULL); +} + +/** + * gimp_preview_get_update: + * @preview: a #GimpPreview widget + * + * Return value: the state of the "Preview" check button. + * + * Since: 2.2 + **/ +gboolean +gimp_preview_get_update (GimpPreview *preview) +{ + g_return_val_if_fail (GIMP_IS_PREVIEW (preview), FALSE); + + return preview->update_preview; +} + +/** + * gimp_preview_set_bounds: + * @preview: a #GimpPreview widget + * @xmin: the minimum X value + * @ymin: the minimum Y value + * @xmax: the maximum X value + * @ymax: the maximum Y value + * + * Sets the lower and upper limits for the previewed area. The + * difference between the upper and lower value is used to set the + * maximum size of the #GimpPreviewArea used in the @preview. + * + * Since: 2.2 + **/ +void +gimp_preview_set_bounds (GimpPreview *preview, + gint xmin, + gint ymin, + gint xmax, + gint ymax) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + g_return_if_fail (xmax > xmin); + g_return_if_fail (ymax > ymin); + + preview->xmin = xmin; + preview->ymin = ymin; + preview->xmax = xmax; + preview->ymax = ymax; + + gimp_preview_area_set_max_size (GIMP_PREVIEW_AREA (preview->area), + xmax - xmin, + ymax - ymin); +} + +/** + * gimp_preview_get_size: + * @preview: a #GimpPreview widget + * @width: return location for the preview area width + * @height: return location for the preview area height + * + * Since: 2.2 + **/ +void +gimp_preview_get_size (GimpPreview *preview, + gint *width, + gint *height) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + + if (width) + *width = preview->width; + + if (height) + *height = preview->height; +} + +/** + * gimp_preview_get_position: + * @preview: a #GimpPreview widget + * @x: return location for the horizontal offset + * @y: return location for the vertical offset + * + * Since: 2.2 + **/ +void +gimp_preview_get_position (GimpPreview *preview, + gint *x, + gint *y) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + + if (x) + *x = preview->xoff + preview->xmin; + + if (y) + *y = preview->yoff + preview->ymin; +} + +/** + * gimp_preview_transform: + * @preview: a #GimpPreview widget + * @src_x: horizontal position on the previewed image + * @src_y: vertical position on the previewed image + * @dest_x: returns the transformed horizontal position + * @dest_y: returns the transformed vertical position + * + * Transforms from image to widget coordinates. + * + * Since: 2.4 + **/ +void +gimp_preview_transform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + g_return_if_fail (dest_x != NULL && dest_y != NULL); + + GIMP_PREVIEW_GET_CLASS (preview)->transform (preview, + src_x, src_y, dest_x, dest_y); +} + +/** + * gimp_preview_untransform: + * @preview: a #GimpPreview widget + * @src_x: horizontal position relative to the preview area's origin + * @src_y: vertical position relative to preview area's origin + * @dest_x: returns the untransformed horizontal position + * @dest_y: returns the untransformed vertical position + * + * Transforms from widget to image coordinates. + * + * Since: 2.4 + **/ +void +gimp_preview_untransform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + g_return_if_fail (dest_x != NULL && dest_y != NULL); + + GIMP_PREVIEW_GET_CLASS (preview)->untransform (preview, + src_x, src_y, dest_x, dest_y); +} + +/** + * gimp_preview_get_area: + * @preview: a #GimpPreview widget + * + * In most cases, you shouldn't need to access the #GimpPreviewArea + * that is being used in the @preview. Sometimes however, you need to. + * For example if you want to receive mouse events from the area. In + * such cases, use gimp_preview_get_area(). + * + * Return value: a pointer to the #GimpPreviewArea used in the @preview. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_preview_get_area (GimpPreview *preview) +{ + g_return_val_if_fail (GIMP_IS_PREVIEW (preview), NULL); + + return preview->area; +} + +/** + * gimp_preview_draw: + * @preview: a #GimpPreview widget + * + * Calls the GimpPreview::draw method. GimpPreview itself doesn't + * implement a default draw method so the behaviour is determined by + * the derived class implementing this method. + * + * #GimpDrawablePreview implements gimp_preview_draw() by drawing the + * original, unmodified drawable to the @preview. + * + * Since: 2.2 + **/ +void +gimp_preview_draw (GimpPreview *preview) +{ + GimpPreviewClass *class = GIMP_PREVIEW_GET_CLASS (preview); + + if (class->draw) + class->draw (preview); +} + +/** + * gimp_preview_draw_buffer: + * @preview: a #GimpPreview widget + * @buffer: a pixel buffer the size of the preview + * @rowstride: the @buffer's rowstride + * + * Calls the GimpPreview::draw_buffer method. GimpPreview itself + * doesn't implement this method so the behaviour is determined by the + * derived class implementing this method. + * + * Since: 2.2 + **/ +void +gimp_preview_draw_buffer (GimpPreview *preview, + const guchar *buffer, + gint rowstride) +{ + GimpPreviewClass *class = GIMP_PREVIEW_GET_CLASS (preview); + + if (class->draw_buffer) + class->draw_buffer (preview, buffer, rowstride); +} + +/** + * gimp_preview_invalidate: + * @preview: a #GimpPreview widget + * + * This function starts or renews a short low-priority timeout. When + * the timeout expires, the GimpPreview::invalidated signal is emitted + * which will usually cause the @preview to be updated. + * + * This function does nothing unless the "Preview" button is checked. + * + * During the emission of the signal a busy cursor is set on the + * toplevel window containing the @preview and on the preview area + * itself. + * + * Since: 2.2 + **/ +void +gimp_preview_invalidate (GimpPreview *preview) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + + if (preview->update_preview) + { + if (preview->timeout_id) + g_source_remove (preview->timeout_id); + + preview->timeout_id = + g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, PREVIEW_TIMEOUT, + (GSourceFunc) gimp_preview_invalidate_now, + preview, NULL); + } +} + +/** + * gimp_preview_set_default_cursor: + * @preview: a #GimpPreview widget + * @cursor: a #GdkCursor or %NULL + * + * Sets the default mouse cursor for the preview. Note that this will + * be overridden by a %GDK_FLEUR if the preview has scrollbars, or by a + * %GDK_WATCH when the preview is invalidated. + * + * Since: 2.2 + **/ +void +gimp_preview_set_default_cursor (GimpPreview *preview, + GdkCursor *cursor) +{ + g_return_if_fail (GIMP_IS_PREVIEW (preview)); + + g_set_object (&preview->default_cursor, cursor); +} + +/** + * gimp_preview_get_controls: + * @preview: a #GimpPreview widget + * + * Gives access to the #GtkHBox at the bottom of the preview that + * contains the update toggle. Derived widgets can use this function + * if they need to add controls to this area. + * + * Return value: the #GtkHBox at the bottom of the preview. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_preview_get_controls (GimpPreview *preview) +{ + g_return_val_if_fail (GIMP_IS_PREVIEW (preview), NULL); + + return GIMP_PREVIEW_GET_PRIVATE (preview)->controls; +} diff --git a/libgimpwidgets/gimppreview.h b/libgimpwidgets/gimppreview.h new file mode 100644 index 0000000..25d2f78 --- /dev/null +++ b/libgimpwidgets/gimppreview.h @@ -0,0 +1,149 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppreview.h + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PREVIEW_H__ +#define __GIMP_PREVIEW_H__ + +G_BEGIN_DECLS + + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_PREVIEW (gimp_preview_get_type ()) +#define GIMP_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PREVIEW, GimpPreview)) +#define GIMP_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PREVIEW, GimpPreviewClass)) +#define GIMP_IS_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PREVIEW)) +#define GIMP_IS_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PREVIEW)) +#define GIMP_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PREVIEW, GimpPreviewClass)) + + +typedef struct _GimpPreviewClass GimpPreviewClass; + +struct _GimpPreview +{ + GtkBox parent_instance; + + gboolean update_preview; + + /*< protected >*/ + GtkWidget *area; + GtkWidget *table; + GtkWidget *frame; + GtkWidget *toggle; + GdkCursor *cursor_busy; + GdkCursor *default_cursor; + + /*< private >*/ + gint xoff, yoff; + gint xmin, xmax, ymin, ymax; + gint width, height; + + guint timeout_id; +}; + +struct _GimpPreviewClass +{ + GtkBoxClass parent_class; + + /* virtual methods */ + void (* draw) (GimpPreview *preview); + void (* draw_thumb) (GimpPreview *preview, + GimpPreviewArea *area, + gint width, + gint height); + void (* draw_buffer) (GimpPreview *preview, + const guchar *buffer, + gint rowstride); + void (* set_cursor) (GimpPreview *preview); + + /* signal */ + void (* invalidated) (GimpPreview *preview); + + /* virtual methods */ + void (* transform) (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y); + void (* untransform) (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y); + + /* Padding for future expansion */ + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_preview_get_type (void) G_GNUC_CONST; + +void gimp_preview_set_update (GimpPreview *preview, + gboolean update); +gboolean gimp_preview_get_update (GimpPreview *preview); + +void gimp_preview_set_bounds (GimpPreview *preview, + gint xmin, + gint ymin, + gint xmax, + gint ymax); + +void gimp_preview_get_position (GimpPreview *preview, + gint *x, + gint *y); +void gimp_preview_get_size (GimpPreview *preview, + gint *width, + gint *height); + +void gimp_preview_transform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y); +void gimp_preview_untransform (GimpPreview *preview, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y); + +GtkWidget * gimp_preview_get_area (GimpPreview *preview); + +void gimp_preview_draw (GimpPreview *preview); +void gimp_preview_draw_buffer (GimpPreview *preview, + const guchar *buffer, + gint rowstride); + +void gimp_preview_invalidate (GimpPreview *preview); + +void gimp_preview_set_default_cursor (GimpPreview *preview, + GdkCursor *cursor); + +GtkWidget * gimp_preview_get_controls (GimpPreview *preview); + + +G_END_DECLS + +#endif /* __GIMP_PREVIEW_H__ */ diff --git a/libgimpwidgets/gimppreviewarea.c b/libgimpwidgets/gimppreviewarea.c new file mode 100644 index 0000000..7715ce0 --- /dev/null +++ b/libgimpwidgets/gimppreviewarea.c @@ -0,0 +1,1956 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpcolor/gimpcolor.h" + +#include "gimpwidgetstypes.h" + +#include "gimppreviewarea.h" +#include "gimpwidgetsutils.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimppreviewarea + * @title: GimpPreviewArea + * @short_description: A general purpose preview widget which caches + * its pixel data. + * + * A general purpose preview widget which caches its pixel data. + **/ + + +enum +{ + PROP_0, + PROP_CHECK_SIZE, + PROP_CHECK_TYPE +}; + + +#define DEFAULT_CHECK_SIZE GIMP_CHECK_SIZE_MEDIUM_CHECKS +#define DEFAULT_CHECK_TYPE GIMP_CHECK_TYPE_GRAY_CHECKS + +#define CHECK_COLOR(area, row, col) \ + (((((area)->offset_y + (row)) & size) ^ \ + (((area)->offset_x + (col)) & size)) ? dark : light) + + +typedef struct _GimpPreviewAreaPrivate GimpPreviewAreaPrivate; + +struct _GimpPreviewAreaPrivate +{ + GimpColorConfig *config; + GimpColorTransform *transform; +}; + +#define GET_PRIVATE(obj) \ + ((GimpPreviewAreaPrivate *) gimp_preview_area_get_instance_private ((GimpPreviewArea *) (obj))) + + +static void gimp_preview_area_dispose (GObject *object); +static void gimp_preview_area_finalize (GObject *object); +static void gimp_preview_area_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_preview_area_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_preview_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_preview_area_expose (GtkWidget *widget, + GdkEventExpose *event); + +static void gimp_preview_area_queue_draw (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height); +static gint gimp_preview_area_image_type_bytes (GimpImageType type); + +static void gimp_preview_area_create_transform (GimpPreviewArea *area); +static void gimp_preview_area_destroy_transform (GimpPreviewArea *area); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPreviewArea, gimp_preview_area, + GTK_TYPE_DRAWING_AREA) + +#define parent_class gimp_preview_area_parent_class + + +static void +gimp_preview_area_class_init (GimpPreviewAreaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_preview_area_dispose; + object_class->finalize = gimp_preview_area_finalize; + object_class->set_property = gimp_preview_area_set_property; + object_class->get_property = gimp_preview_area_get_property; + + widget_class->size_allocate = gimp_preview_area_size_allocate; + widget_class->expose_event = gimp_preview_area_expose; + + g_object_class_install_property (object_class, PROP_CHECK_SIZE, + g_param_spec_enum ("check-size", + _("Check Size"), + "The size of the checkerboard pattern indicating transparency", + GIMP_TYPE_CHECK_SIZE, + DEFAULT_CHECK_SIZE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CHECK_TYPE, + g_param_spec_enum ("check-type", + _("Check Style"), + "The colors of the checkerboard pattern indicating transparency", + GIMP_TYPE_CHECK_TYPE, + DEFAULT_CHECK_TYPE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_preview_area_init (GimpPreviewArea *area) +{ + area->check_size = DEFAULT_CHECK_SIZE; + area->check_type = DEFAULT_CHECK_TYPE; + area->buf = NULL; + area->colormap = NULL; + area->offset_x = 0; + area->offset_y = 0; + area->width = 0; + area->height = 0; + area->rowstride = 0; + area->max_width = -1; + area->max_height = -1; + + gimp_widget_track_monitor (GTK_WIDGET (area), + G_CALLBACK (gimp_preview_area_destroy_transform), + NULL); +} + +static void +gimp_preview_area_dispose (GObject *object) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (object); + + gimp_preview_area_set_color_config (area, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_preview_area_finalize (GObject *object) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (object); + + g_clear_pointer (&area->buf, g_free); + g_clear_pointer (&area->colormap, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_preview_area_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (object); + + switch (property_id) + { + case PROP_CHECK_SIZE: + area->check_size = g_value_get_enum (value); + break; + case PROP_CHECK_TYPE: + area->check_type = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_preview_area_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (object); + + switch (property_id) + { + case PROP_CHECK_SIZE: + g_value_set_enum (value, area->check_size); + break; + case PROP_CHECK_TYPE: + g_value_set_enum (value, area->check_type); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_preview_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (widget); + gint width; + gint height; + + if (GTK_WIDGET_CLASS (parent_class)->size_allocate) + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + width = (area->max_width > 0 ? + MIN (allocation->width, area->max_width) : allocation->width); + height = (area->max_height > 0 ? + MIN (allocation->height, area->max_height) : allocation->height); + + if (width != area->width || height != area->height) + { + if (area->buf) + { + g_free (area->buf); + + area->buf = NULL; + area->rowstride = 0; + } + + area->width = width; + area->height = height; + } +} + +static gboolean +gimp_preview_area_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (widget); + GimpPreviewAreaPrivate *priv = GET_PRIVATE (area); + GtkAllocation allocation; + GdkPixbuf *pixbuf; + GdkRectangle rect; + cairo_t *cr; + + if (! area->buf) + return FALSE; + + gtk_widget_get_allocation (widget, &allocation); + + rect.x = (allocation.width - area->width) / 2; + rect.y = (allocation.height - area->height) / 2; + rect.width = area->width; + rect.height = area->height; + + if (! priv->transform) + gimp_preview_area_create_transform (area); + + if (priv->transform) + { + const Babl *format = babl_format ("R'G'B' u8"); + gint rowstride = ((area->width * 3) + 3) & ~3; + guchar *buf = g_new (guchar, rowstride * area->height); + guchar *src = area->buf; + guchar *dest = buf; + gint i; + + for (i = 0; i < area->height; i++) + { + gimp_color_transform_process_pixels (priv->transform, + format, src, + format, dest, + area->width); + + src += area->rowstride; + dest += rowstride; + } + + pixbuf = gdk_pixbuf_new_from_data (buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + rect.width, + rect.height, + rowstride, + (GdkPixbufDestroyNotify) g_free, NULL); + } + else + { + pixbuf = gdk_pixbuf_new_from_data (area->buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + rect.width, + rect.height, + area->rowstride, + NULL, NULL); + } + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, rect.x, rect.y); + cairo_paint (cr); + + cairo_destroy (cr); + g_object_unref (pixbuf); + + return FALSE; +} + +static void +gimp_preview_area_queue_draw (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height) +{ + GtkWidget *widget = GTK_WIDGET (area); + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + x += (allocation.width - area->width) / 2; + y += (allocation.height - area->height) / 2; + + gtk_widget_queue_draw_area (widget, x, y, width, height); +} + +static gint +gimp_preview_area_image_type_bytes (GimpImageType type) +{ + switch (type) + { + case GIMP_GRAY_IMAGE: + case GIMP_INDEXED_IMAGE: + return 1; + + case GIMP_GRAYA_IMAGE: + case GIMP_INDEXEDA_IMAGE: + return 2; + + case GIMP_RGB_IMAGE: + return 3; + + case GIMP_RGBA_IMAGE: + return 4; + + default: + g_return_val_if_reached (0); + break; + } +} + +static void +gimp_preview_area_create_transform (GimpPreviewArea *area) +{ + GimpPreviewAreaPrivate *priv = GET_PRIVATE (area); + + if (priv->config) + { + static GimpColorProfile *profile = NULL; + + const Babl *format = babl_format ("R'G'B' u8"); + + if (G_UNLIKELY (! profile)) + profile = gimp_color_profile_new_rgb_srgb (); + + priv->transform = gimp_widget_get_color_transform (GTK_WIDGET (area), + priv->config, + profile, + format, + format); + } +} + +static void +gimp_preview_area_destroy_transform (GimpPreviewArea *area) +{ + GimpPreviewAreaPrivate *priv = GET_PRIVATE (area); + + if (priv->transform) + { + g_object_unref (priv->transform); + priv->transform = NULL; + } + + gtk_widget_queue_draw (GTK_WIDGET (area)); +} + + +/** + * gimp_preview_area_new: + * + * Creates a new #GimpPreviewArea widget. + * + * Return value: a new #GimpPreviewArea widget. + * + * Since GIMP 2.2 + **/ +GtkWidget * +gimp_preview_area_new (void) +{ + return g_object_new (GIMP_TYPE_PREVIEW_AREA, NULL); +} + +/** + * gimp_preview_area_draw: + * @area: a #GimpPreviewArea widget. + * @x: x offset in preview + * @y: y offset in preview + * @width: buffer width + * @height: buffer height + * @type: the #GimpImageType of @buf + * @buf: a #guchar buffer that contains the preview pixel data. + * @rowstride: rowstride of @buf + * + * Draws @buf on @area and queues a redraw on the given rectangle. + * + * Since GIMP 2.2 + **/ +void +gimp_preview_area_draw (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + GimpImageType type, + const guchar *buf, + gint rowstride) +{ + const guchar *src; + guchar *dest; + guint size; + guchar light; + guchar dark; + gint row; + gint col; + + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + g_return_if_fail (width >= 0 && height >= 0); + + if (width == 0 || height == 0) + return; + + g_return_if_fail (buf != NULL); + g_return_if_fail (rowstride > 0); + + if (x + width < 0 || x >= area->width) + return; + + if (y + height < 0 || y >= area->height) + return; + + if (x < 0) + { + gint bpp = gimp_preview_area_image_type_bytes (type); + + buf -= x * bpp; + width += x; + + x = 0; + } + + if (x + width > area->width) + width = area->width - x; + + if (y < 0) + { + buf -= y * rowstride; + height += y; + + y = 0; + } + + if (y + height > area->height) + height = area->height - y; + + if (! area->buf) + { + area->rowstride = ((area->width * 3) + 3) & ~3; + area->buf = g_new (guchar, area->rowstride * area->height); + } + + size = 1 << (2 + area->check_size); + gimp_checks_get_shades (area->check_type, &light, &dark); + + src = buf; + dest = area->buf + x * 3 + y * area->rowstride; + + switch (type) + { + case GIMP_RGB_IMAGE: + for (row = 0; row < height; row++) + { + memcpy (dest, src, 3 * width); + + src += rowstride; + dest += area->rowstride; + } + break; + + case GIMP_RGBA_IMAGE: + for (row = y; row < y + height; row++) + { + const guchar *s = src; + guchar *d = dest; + + for (col = x; col < x + width; col++, s += 4, d+= 3) + { + switch (s[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + break; + + default: + { + register guint alpha = s[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (s[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (s[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (s[2] - check) * alpha) >> 8; + } + break; + } + } + + src += rowstride; + dest += area->rowstride; + } + break; + + case GIMP_GRAY_IMAGE: + for (row = 0; row < height; row++) + { + const guchar *s = src; + guchar *d = dest; + + for (col = 0; col < width; col++, s++, d += 3) + { + d[0] = d[1] = d[2] = s[0]; + } + + src += rowstride; + dest += area->rowstride; + } + break; + + case GIMP_GRAYA_IMAGE: + for (row = y; row < y + height; row++) + { + const guchar *s = src; + guchar *d = dest; + + for (col = x; col < x + width; col++, s += 2, d+= 3) + { + switch (s[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = d[1] = d[2] = s[0]; + break; + + default: + { + register guint alpha = s[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = d[1] = d[2] = + ((check << 8) + (s[0] - check) * alpha) >> 8; + } + break; + } + } + + src += rowstride; + dest += area->rowstride; + } + break; + + case GIMP_INDEXED_IMAGE: + g_return_if_fail (area->colormap != NULL); + for (row = 0; row < height; row++) + { + const guchar *s = src; + guchar *d = dest; + + for (col = 0; col < width; col++, s++, d += 3) + { + const guchar *colormap = area->colormap + 3 * s[0]; + + d[0] = colormap[0]; + d[1] = colormap[1]; + d[2] = colormap[2]; + } + + src += rowstride; + dest += area->rowstride; + } + break; + + case GIMP_INDEXEDA_IMAGE: + g_return_if_fail (area->colormap != NULL); + for (row = y; row < y + height; row++) + { + const guchar *s = src; + guchar *d = dest; + + for (col = x; col < x + width; col++, s += 2, d += 3) + { + const guchar *colormap = area->colormap + 3 * s[0]; + + switch (s[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = colormap[0]; + d[1] = colormap[1]; + d[2] = colormap[2]; + break; + + default: + { + register guint alpha = s[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (colormap[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (colormap[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (colormap[2] - check) * alpha) >> 8; + } + break; + } + } + + src += rowstride; + dest += area->rowstride; + } + break; + } + + gimp_preview_area_queue_draw (area, x, y, width, height); +} + +/** + * gimp_preview_area_blend: + * @area: a #GimpPreviewArea widget. + * @x: x offset in preview + * @y: y offset in preview + * @width: buffer width + * @height: buffer height + * @type: the #GimpImageType of @buf1 and @buf2 + * @buf1: a #guchar buffer that contains the pixel data for + * the lower layer + * @rowstride1: rowstride of @buf1 + * @buf2: a #guchar buffer that contains the pixel data for + * the upper layer + * @rowstride2: rowstride of @buf2 + * @opacity: The opacity of the first layer. + * + * Composites @buf1 on @buf2 with the given @opacity, draws the result + * to @area and queues a redraw on the given rectangle. + * + * Since GIMP 2.2 + **/ +void +gimp_preview_area_blend (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + GimpImageType type, + const guchar *buf1, + gint rowstride1, + const guchar *buf2, + gint rowstride2, + guchar opacity) +{ + const guchar *src1; + const guchar *src2; + guchar *dest; + guint size; + guchar light; + guchar dark; + gint row; + gint col; + gint i; + + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + g_return_if_fail (width >= 0 && height >= 0); + + if (width == 0 || height == 0) + return; + + g_return_if_fail (buf1 != NULL); + g_return_if_fail (buf2 != NULL); + g_return_if_fail (rowstride1 > 0); + g_return_if_fail (rowstride2 > 0); + + switch (opacity) + { + case 0: + gimp_preview_area_draw (area, x, y, width, height, + type, buf1, rowstride1); + return; + + case 255: + gimp_preview_area_draw (area, x, y, width, height, + type, buf2, rowstride2); + return; + + default: + break; + } + + if (x + width < 0 || x >= area->width) + return; + + if (y + height < 0 || y >= area->height) + return; + + if (x < 0) + { + gint bpp = gimp_preview_area_image_type_bytes (type); + + buf1 -= x * bpp; + buf2 -= x * bpp; + width += x; + + x = 0; + } + + if (x + width > area->width) + width = area->width - x; + + if (y < 0) + { + buf1 -= y * rowstride1; + buf2 -= y * rowstride2; + height += y; + + y = 0; + } + + if (y + height > area->height) + height = area->height - y; + + if (! area->buf) + { + area->rowstride = ((area->width * 3) + 3) & ~3; + area->buf = g_new (guchar, area->rowstride * area->height); + } + + size = 1 << (2 + area->check_size); + gimp_checks_get_shades (area->check_type, &light, &dark); + + src1 = buf1; + src2 = buf2; + dest = area->buf + x * 3 + y * area->rowstride; + + switch (type) + { + case GIMP_RGB_IMAGE: + for (row = 0; row < height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 3, s2 += 3, d+= 3) + { + d[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8; + d[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8; + d[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * opacity) >> 8; + } + + src1 += rowstride1; + src2 += rowstride2; + dest += area->rowstride; + } + break; + + case GIMP_RGBA_IMAGE: + for (row = y; row < y + height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 4, s2 += 4, d+= 3) + { + guchar inter[4]; + + if (s1[3] == s2[3]) + { + inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8; + inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8; + inter[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * opacity) >> 8; + inter[3] = s1[3]; + } + else + { + inter[3] = ((s1[3] << 8) + (s2[3] - s1[3]) * opacity) >> 8; + + if (inter[3]) + { + for (i = 0; i < 3; i++) + { + gushort a = s1[i] * s1[3]; + gushort b = s2[i] * s2[3]; + + inter[i] = + (((a << 8) + (b - a) * opacity) >> 8) / inter[3]; + } + } + } + + switch (inter[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = inter[0]; + d[1] = inter[1]; + d[2] = inter[2]; + break; + + default: + { + register guint alpha = inter[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (inter[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (inter[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (inter[2] - check) * alpha) >> 8; + } + break; + } + } + + src1 += rowstride1; + src2 += rowstride2; + dest += area->rowstride; + } + break; + + case GIMP_GRAY_IMAGE: + for (row = 0; row < height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + guchar *d = dest; + + for (col = 0; col < width; col++, s1++, s2++, d += 3) + { + d[0] = d[1] = d[2] = + ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8; + } + + src1 += rowstride1; + src2 += rowstride2; + dest += area->rowstride; + } + break; + + case GIMP_GRAYA_IMAGE: + for (row = y; row < y + height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 2, s2 += 2, d+= 3) + { + guchar inter[2] = { 0, }; + + if (s1[1] == s2[1]) + { + inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8; + inter[1] = s1[1]; + } + else + { + inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8; + + if (inter[1]) + { + gushort a = s1[0] * s1[1]; + gushort b = s2[0] * s2[1]; + + inter[0] = + (((a << 8) + (b - a) * opacity) >> 8) / inter[1]; + } + } + + switch (inter[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = d[1] = d[2] = inter[0]; + break; + + default: + { + register guint alpha = inter[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = d[1] = d[2] = + ((check << 8) + (inter[0] - check) * alpha) >> 8; + } + break; + } + } + + src1 += rowstride1; + src2 += rowstride2; + dest += area->rowstride; + } + break; + + case GIMP_INDEXED_IMAGE: + g_return_if_fail (area->colormap != NULL); + for (row = 0; row < height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + guchar *d = dest; + + for (col = 0; col < width; col++, s1++, s2++, d += 3) + { + const guchar *cmap1 = area->colormap + 3 * s1[0]; + const guchar *cmap2 = area->colormap + 3 * s2[0]; + + d[0] = ((cmap1[0] << 8) + (cmap2[0] - cmap1[0]) * opacity) >> 8; + d[1] = ((cmap1[1] << 8) + (cmap2[1] - cmap1[1]) * opacity) >> 8; + d[2] = ((cmap1[2] << 8) + (cmap2[2] - cmap1[2]) * opacity) >> 8; + } + + src1 += rowstride1; + src2 += rowstride2; + dest += area->rowstride; + } + break; + + case GIMP_INDEXEDA_IMAGE: + g_return_if_fail (area->colormap != NULL); + for (row = y; row < y + height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 2, s2 += 2, d += 3) + { + const guchar *cmap1 = area->colormap + 3 * s1[0]; + const guchar *cmap2 = area->colormap + 3 * s2[0]; + guchar inter[4]; + + if (s1[1] == s2[1]) + { + inter[0] = (((cmap1[0] << 8) + + (cmap2[0] - cmap1[0]) * opacity) >> 8); + inter[1] = (((cmap1[1] << 8) + + (cmap2[1] - cmap1[1]) * opacity) >> 8); + inter[2] = (((cmap1[2] << 8) + + (cmap2[2] - cmap1[2]) * opacity) >> 8); + inter[3] = s1[1]; + } + else + { + inter[3] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8; + + if (inter[3]) + { + for (i = 0; i < 3; i++) + { + gushort a = cmap1[i] * s1[1]; + gushort b = cmap2[i] * s2[1]; + + inter[i] = + (((a << 8) + (b - a) * opacity) >> 8) / inter[3]; + } + } + } + + switch (inter[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = inter[0]; + d[1] = inter[1]; + d[2] = inter[2]; + break; + + default: + { + register guint alpha = inter[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (inter[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (inter[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (inter[2] - check) * alpha) >> 8; + } + break; + } + } + + src1 += rowstride1; + src2 += rowstride2; + dest += area->rowstride; + } + break; + } + + gimp_preview_area_queue_draw (area, x, y, width, height); +} + +/** + * gimp_preview_area_mask: + * @area: a #GimpPreviewArea widget. + * @x: x offset in preview + * @y: y offset in preview + * @width: buffer width + * @height: buffer height + * @type: the #GimpImageType of @buf1 and @buf2 + * @buf1: a #guchar buffer that contains the pixel data for + * the lower layer + * @rowstride1: rowstride of @buf1 + * @buf2: a #guchar buffer that contains the pixel data for + * the upper layer + * @rowstride2: rowstride of @buf2 + * @mask: a #guchar buffer representing the mask of the second + * layer. + * @rowstride_mask: rowstride for the mask. + * + * Composites @buf1 on @buf2 with the given @mask, draws the result on + * @area and queues a redraw on the given rectangle. + * + * Since GIMP 2.2 + **/ +void +gimp_preview_area_mask (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + GimpImageType type, + const guchar *buf1, + gint rowstride1, + const guchar *buf2, + gint rowstride2, + const guchar *mask, + gint rowstride_mask) +{ + const guchar *src1; + const guchar *src2; + const guchar *src_mask; + guchar *dest; + guint size; + guchar light; + guchar dark; + gint row; + gint col; + gint i; + + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + g_return_if_fail (width >= 0 && height >= 0); + + if (width == 0 || height == 0) + return; + + g_return_if_fail (buf1 != NULL); + g_return_if_fail (buf2 != NULL); + g_return_if_fail (mask != NULL); + g_return_if_fail (rowstride1 > 0); + g_return_if_fail (rowstride2 > 0); + g_return_if_fail (rowstride_mask > 0); + + if (x + width < 0 || x >= area->width) + return; + + if (y + height < 0 || y >= area->height) + return; + + if (x < 0) + { + gint bpp = gimp_preview_area_image_type_bytes (type); + + buf1 -= x * bpp; + buf2 -= x * bpp; + mask -= x; + width += x; + + x = 0; + } + + if (x + width > area->width) + width = area->width - x; + + if (y < 0) + { + buf1 -= y * rowstride1; + buf2 -= y * rowstride2; + mask -= y * rowstride_mask; + height += y; + + y = 0; + } + + if (y + height > area->height) + height = area->height - y; + + if (! area->buf) + { + area->rowstride = ((area->width * 3) + 3) & ~3; + area->buf = g_new (guchar, area->rowstride * area->height); + } + + size = 1 << (2 + area->check_size); + gimp_checks_get_shades (area->check_type, &light, &dark); + + src1 = buf1; + src2 = buf2; + src_mask = mask; + dest = area->buf + x * 3 + y * area->rowstride; + + switch (type) + { + case GIMP_RGB_IMAGE: + for (row = 0; row < height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + const guchar *m = src_mask; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 3, s2 += 3, m++, d+= 3) + { + d[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8; + d[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8; + d[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * m[0]) >> 8; + } + + src1 += rowstride1; + src2 += rowstride2; + src_mask += rowstride_mask; + dest += area->rowstride; + } + break; + + case GIMP_RGBA_IMAGE: + for (row = y; row < y + height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + const guchar *m = src_mask; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 4, s2 += 4, m++, d+= 3) + { + switch (m[0]) + { + case 0: + switch (s1[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = s1[0]; + d[1] = s1[1]; + d[2] = s1[2]; + break; + + default: + { + register guint alpha = s1[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (s1[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (s1[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (s1[2] - check) * alpha) >> 8; + } + break; + } + break; + + case 255: + switch (s2[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = s2[0]; + d[1] = s2[1]; + d[2] = s2[2]; + break; + + default: + { + register guint alpha = s2[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (s2[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (s2[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (s2[2] - check) * alpha) >> 8; + } + break; + } + break; + + default: + { + guchar inter[4]; + + if (s1[3] == s2[3]) + { + inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8; + inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8; + inter[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * m[0]) >> 8; + inter[3] = s1[3]; + } + else + { + inter[3] = ((s1[3] << 8) + (s2[3] - s1[3]) * m[0]) >> 8; + + if (inter[3]) + { + for (i = 0; i < 3; i++) + { + gushort a = s1[i] * s1[3]; + gushort b = s2[i] * s2[3]; + + inter[i] = + (((a << 8) + (b - a) * m[0]) >> 8) / inter[3]; + } + } + } + + switch (inter[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = inter[0]; + d[1] = inter[1]; + d[2] = inter[2]; + break; + + default: + { + register guint alpha = inter[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = (((check << 8) + + (inter[0] - check) * alpha) >> 8); + d[1] = (((check << 8) + + (inter[1] - check) * alpha) >> 8); + d[2] = (((check << 8) + + (inter[2] - check) * alpha) >> 8); + } + break; + } + } + break; + } + } + + src1 += rowstride1; + src2 += rowstride2; + src_mask += rowstride_mask; + dest += area->rowstride; + } + break; + + case GIMP_GRAY_IMAGE: + for (row = 0; row < height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + const guchar *m = src_mask; + guchar *d = dest; + + for (col = 0; col < width; col++, s1++, s2++, m++, d += 3) + d[0] = d[1] = d[2] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8; + + src1 += rowstride1; + src2 += rowstride2; + src_mask += rowstride_mask; + dest += area->rowstride; + } + break; + + case GIMP_GRAYA_IMAGE: + for (row = y; row < y + height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + const guchar *m = src_mask; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 2, s2 += 2, m++, d+= 3) + { + switch (m[0]) + { + case 0: + switch (s1[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = d[1] = d[2] = s1[0]; + break; + + default: + { + register guint alpha = s1[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = d[1] = d[2] = + ((check << 8) + (s1[0] - check) * alpha) >> 8; + } + break; + } + break; + + case 255: + switch (s2[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = d[1] = d[2] = s2[0]; + break; + + default: + { + register guint alpha = s2[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = d[1] = d[2] = + ((check << 8) + (s2[0] - check) * alpha) >> 8; + } + break; + } + break; + + default: + { + guchar inter[2] = { 0, }; + + if (s1[1] == s2[1]) + { + inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8; + inter[1] = s1[1]; + } + else + { + inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8; + + if (inter[1]) + { + gushort a = s1[0] * s1[1]; + gushort b = s2[0] * s2[1]; + + inter[0] = + (((a << 8) + (b - a) * m[0]) >> 8) / inter[1]; + } + } + + switch (inter[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = d[1] = d[2] = inter[0]; + break; + + default: + { + register guint alpha = inter[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = d[1] = d[2] = + ((check << 8) + (inter[0] - check) * alpha) >> 8; + } + break; + } + } + break; + } + } + + src1 += rowstride1; + src2 += rowstride2; + src_mask += rowstride_mask; + dest += area->rowstride; + } + break; + + case GIMP_INDEXED_IMAGE: + g_return_if_fail (area->colormap != NULL); + for (row = 0; row < height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + const guchar *m = src_mask; + guchar *d = dest; + + for (col = 0; col < width; col++, s1++, s2++, m++, d += 3) + { + const guchar *cmap1 = area->colormap + 3 * s1[0]; + const guchar *cmap2 = area->colormap + 3 * s2[0]; + + d[0] = ((cmap1[0] << 8) + (cmap2[0] - cmap1[0]) * m[0]) >> 8; + d[1] = ((cmap1[1] << 8) + (cmap2[1] - cmap1[1]) * m[0]) >> 8; + d[2] = ((cmap1[2] << 8) + (cmap2[2] - cmap1[2]) * m[0]) >> 8; + } + + src1 += rowstride1; + src2 += rowstride2; + src_mask += rowstride_mask; + dest += area->rowstride; + } + break; + + case GIMP_INDEXEDA_IMAGE: + g_return_if_fail (area->colormap != NULL); + for (row = y; row < y + height; row++) + { + const guchar *s1 = src1; + const guchar *s2 = src2; + const guchar *m = src_mask; + guchar *d = dest; + + for (col = x; col < x + width; col++, s1 += 2, s2 += 2, m++, d += 3) + { + const guchar *cmap1 = area->colormap + 3 * s1[0]; + const guchar *cmap2 = area->colormap + 3 * s2[0]; + + switch (m[0]) + { + case 0: + switch (s1[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = cmap1[0]; + d[1] = cmap1[1]; + d[2] = cmap1[2]; + break; + + default: + { + register guint alpha = s1[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (cmap1[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (cmap1[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (cmap1[2] - check) * alpha) >> 8; + } + break; + } + break; + + case 255: + switch (s2[1]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = cmap2[0]; + d[1] = cmap2[1]; + d[2] = cmap2[2]; + break; + + default: + { + register guint alpha = s2[1] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = ((check << 8) + (cmap2[0] - check) * alpha) >> 8; + d[1] = ((check << 8) + (cmap2[1] - check) * alpha) >> 8; + d[2] = ((check << 8) + (cmap2[2] - check) * alpha) >> 8; + } + break; + } + break; + + default: + { + guchar inter[4]; + + if (s1[1] == s2[1]) + { + inter[0] = (((cmap1[0] << 8) + + (cmap2[0] - cmap1[0]) * m[0]) >> 8); + inter[1] = (((cmap1[1] << 8) + + (cmap2[1] - cmap1[1]) * m[0]) >> 8); + inter[2] = (((cmap1[2] << 8) + + (cmap2[2] - cmap1[2]) * m[0]) >> 8); + inter[3] = s1[1]; + } + else + { + inter[3] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8; + + if (inter[3]) + { + for (i = 0 ; i < 3 ; i++) + { + gushort a = cmap1[i] * s1[1]; + gushort b = cmap2[i] * s2[1]; + + inter[i] = ((((a << 8) + (b - a) * m[0]) >> 8) + / inter[3]); + } + } + } + + switch (inter[3]) + { + case 0: + d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col); + break; + + case 255: + d[0] = inter[0]; + d[1] = inter[1]; + d[2] = inter[2]; + break; + + default: + { + register guint alpha = inter[3] + 1; + register guint check = CHECK_COLOR (area, row, col); + + d[0] = + ((check << 8) + (inter[0] - check) * alpha) >> 8; + d[1] = + ((check << 8) + (inter[1] - check) * alpha) >> 8; + d[2] = + ((check << 8) + (inter[2] - check) * alpha) >> 8; + } + break; + } + } + break; + } + } + + src1 += rowstride1; + src2 += rowstride2; + src_mask += rowstride_mask; + dest += area->rowstride; + } + break; + } + + gimp_preview_area_queue_draw (area, x, y, width, height); +} + +/** + * gimp_preview_area_fill: + * @area: a #GimpPreviewArea widget. + * @x: x offset in preview + * @y: y offset in preview + * @width: width of the rectangle to fill + * @height: height of the rectangle to fill + * @red: red component of the fill color (0-255) + * @green: green component of the fill color (0-255) + * @blue: red component of the fill color (0-255) + * + * Fills the given rectangle of @area in the given color and queues a + * redraw. + * + * Since GIMP 2.2 + **/ +void +gimp_preview_area_fill (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + guchar red, + guchar green, + guchar blue) +{ + guchar *dest; + guchar *d; + gint row; + gint col; + + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + g_return_if_fail (width >= 0 && height >= 0); + + if (width == 0 || height == 0) + return; + + if (x + width < 0 || x >= area->width) + return; + + if (y + height < 0 || y >= area->height) + return; + + if (x < 0) + { + width += x; + x = 0; + } + + if (x + width > area->width) + width = area->width - x; + + if (y < 0) + { + height += y; + y = 0; + } + + if (y + height > area->height) + height = area->height - y; + + if (! area->buf) + { + area->rowstride = ((area->width * 3) + 3) & ~3; + area->buf = g_new (guchar, area->rowstride * area->height); + } + + dest = area->buf + x * 3 + y * area->rowstride; + + /* colorize first row */ + for (col = 0, d = dest; col < width; col++, d+= 3) + { + d[0] = red; + d[1] = green; + d[2] = blue; + } + + /* copy first row to remaining rows */ + for (row = 1, d = dest; row < height; row++) + { + d += area->rowstride; + memcpy (d, dest, width * 3); + } + + gimp_preview_area_queue_draw (area, x, y, width, height); +} + +/** + * gimp_preview_area_set_offsets: + * @area: a #GimpPreviewArea + * @x: horizontal offset + * @y: vertical offset + * + * Sets the offsets of the previewed area. This information is used + * when drawing the checkerboard and to determine the dither offsets. + * + * Since: 2.2 + **/ +void +gimp_preview_area_set_offsets (GimpPreviewArea *area, + gint x, + gint y) +{ + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + + area->offset_x = x; + area->offset_y = y; +} + +/** + * gimp_preview_area_set_colormap: + * @area: a #GimpPreviewArea + * @colormap: a #guchar buffer that contains the colormap + * @num_colors: the number of colors in the colormap + * + * Sets the colormap for the #GimpPreviewArea widget. You need to + * call this function before you use gimp_preview_area_draw() with + * an image type of %GIMP_INDEXED_IMAGE or %GIMP_INDEXEDA_IMAGE. + * + * Since GIMP 2.2 + **/ +void +gimp_preview_area_set_colormap (GimpPreviewArea *area, + const guchar *colormap, + gint num_colors) +{ + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + g_return_if_fail (colormap != NULL || num_colors == 0); + g_return_if_fail (num_colors >= 0 && num_colors <= 256); + + if (num_colors > 0) + { + if (area->colormap) + memset (area->colormap, 0, 3 * 256); + else + area->colormap = g_new0 (guchar, 3 * 256); + + memcpy (area->colormap, colormap, 3 * num_colors); + } + else + { + g_free (area->colormap); + area->colormap = NULL; + } +} + +/** + * gimp_preview_area_set_color_config: + * @area: a #GimpPreviewArea widget. + * @config: a #GimpColorConfig object. + * + * Sets the color management configuration to use with this preview area. + * + * Since: 2.10 + */ +void +gimp_preview_area_set_color_config (GimpPreviewArea *area, + GimpColorConfig *config) +{ + GimpPreviewAreaPrivate *priv; + + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + priv = GET_PRIVATE (area); + + if (config != priv->config) + { + if (priv->config) + { + g_signal_handlers_disconnect_by_func (priv->config, + gimp_preview_area_destroy_transform, + area); + + gimp_preview_area_destroy_transform (area); + } + + g_set_object (&priv->config, config); + + if (priv->config) + { + g_signal_connect_swapped (priv->config, "notify", + G_CALLBACK (gimp_preview_area_destroy_transform), + area); + } + } +} + +/** + * gimp_preview_area_set_max_size: + * @area: a #GimpPreviewArea widget + * @width: the maximum width in pixels or -1 to unset the limit + * @height: the maximum height in pixels or -1 to unset the limit + * + * Usually a #GimpPreviewArea fills the size that it is + * allocated. This function allows you to limit the preview area to a + * maximum size. If a larger size is allocated for the widget, the + * preview will draw itself centered into the allocated area. + * + * Since: 2.2 + **/ +void +gimp_preview_area_set_max_size (GimpPreviewArea *area, + gint width, + gint height) +{ + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + + area->max_width = width; + area->max_height = height; +} + + + +/* popup menu */ + +static void +gimp_preview_area_menu_toggled (GtkWidget *item, + GimpPreviewArea *area) +{ + gboolean active = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)); + + if (active) + { + const gchar *name = g_object_get_data (G_OBJECT (item), + "gimp-preview-area-prop-name"); + if (name) + { + gint value = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), + "gimp-preview-area-prop-value")); + g_object_set (area, + name, value, + NULL); + } + } +} + +static GtkWidget * +gimp_preview_area_menu_new (GimpPreviewArea *area, + const gchar *property) +{ + GParamSpec *pspec; + GEnumClass *enum_class; + GEnumValue *enum_value; + GtkWidget *menu; + GtkWidget *item; + GSList *group = NULL; + gint value; + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (area), property); + + g_return_val_if_fail (G_IS_PARAM_SPEC_ENUM (pspec), NULL); + + g_object_get (area, + property, &value, + NULL); + + enum_class = G_PARAM_SPEC_ENUM (pspec)->enum_class; + + menu = gtk_menu_new (); + + for (enum_value = enum_class->values; enum_value->value_name; enum_value++) + { + const gchar *name = gimp_enum_value_get_desc (enum_class, enum_value); + + item = gtk_radio_menu_item_new_with_label (group, name); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + g_object_set_data (G_OBJECT (item), + "gimp-preview-area-prop-name", + (gpointer) property); + + g_object_set_data (G_OBJECT (item), + "gimp-preview-area-prop-value", + GINT_TO_POINTER (enum_value->value)); + + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), + (enum_value->value == value)); + + g_signal_connect (item, "toggled", + G_CALLBACK (gimp_preview_area_menu_toggled), + area); + + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + } + + item = gtk_menu_item_new_with_label (g_param_spec_get_nick (pspec)); + + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu); + + gtk_widget_show (item); + + return item; +} + +/** + * gimp_preview_area_menu_popup: + * @area: a #GimpPreviewArea + * @event: the button event that causes the menu to popup or %NULL + * + * Creates a popup menu that allows one to configure the size and type of + * the checkerboard pattern that the @area uses to visualize transparency. + * + * Since: 2.2 + **/ +void +gimp_preview_area_menu_popup (GimpPreviewArea *area, + GdkEventButton *event) +{ + GtkWidget *menu; + + g_return_if_fail (GIMP_IS_PREVIEW_AREA (area)); + + menu = gtk_menu_new (); + gtk_menu_set_screen (GTK_MENU (menu), + gtk_widget_get_screen (GTK_WIDGET (area))); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), + gimp_preview_area_menu_new (area, "check-type")); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), + gimp_preview_area_menu_new (area, "check-size")); + + if (event) + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, event->button, event->time); + else + gtk_menu_popup (GTK_MENU (menu), + NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ()); +} diff --git a/libgimpwidgets/gimppreviewarea.h b/libgimpwidgets/gimppreviewarea.h new file mode 100644 index 0000000..cfcf1b2 --- /dev/null +++ b/libgimpwidgets/gimppreviewarea.h @@ -0,0 +1,133 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PREVIEW_AREA_H__ +#define __GIMP_PREVIEW_AREA_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_PREVIEW_AREA (gimp_preview_area_get_type ()) +#define GIMP_PREVIEW_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PREVIEW_AREA, GimpPreviewArea)) +#define GIMP_PREVIEW_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PREVIEW_AREA, GimpPreviewAreaClass)) +#define GIMP_IS_PREVIEW_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PREVIEW_AREA)) +#define GIMP_IS_PREVIEW_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PREVIEW_AREA)) +#define GIMP_PREVIEW_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PREVIEW_AREA, GimpPreviewArea)) + + +typedef struct _GimpPreviewAreaClass GimpPreviewAreaClass; + +struct _GimpPreviewArea +{ + GtkDrawingArea parent_instance; + + GimpCheckSize check_size; + GimpCheckType check_type; + gint width; + gint height; + gint rowstride; + gint offset_x; + gint offset_y; + gint max_width; + gint max_height; + guchar *buf; + guchar *colormap; +}; + +struct _GimpPreviewAreaClass +{ + GtkDrawingAreaClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_preview_area_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_preview_area_new (void); + +void gimp_preview_area_draw (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + GimpImageType type, + const guchar *buf, + gint rowstride); +void gimp_preview_area_blend (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + GimpImageType type, + const guchar *buf1, + gint rowstride1, + const guchar *buf2, + gint rowstride2, + guchar opacity); +void gimp_preview_area_mask (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + GimpImageType type, + const guchar *buf1, + gint rowstride1, + const guchar *buf2, + gint rowstride2, + const guchar *mask, + gint rowstride_mask); +void gimp_preview_area_fill (GimpPreviewArea *area, + gint x, + gint y, + gint width, + gint height, + guchar red, + guchar green, + guchar blue); + +void gimp_preview_area_set_offsets (GimpPreviewArea *area, + gint x, + gint y); + +void gimp_preview_area_set_colormap (GimpPreviewArea *area, + const guchar *colormap, + gint num_colors); + +void gimp_preview_area_set_color_config (GimpPreviewArea *area, + GimpColorConfig *config); + +void gimp_preview_area_set_max_size (GimpPreviewArea *area, + gint width, + gint height); + +void gimp_preview_area_menu_popup (GimpPreviewArea *area, + GdkEventButton *event); + + +G_END_DECLS + +#endif /* __GIMP_PREVIEW_AREA_H__ */ diff --git a/libgimpwidgets/gimppropwidgets.c b/libgimpwidgets/gimppropwidgets.c new file mode 100644 index 0000000..4304a9e --- /dev/null +++ b/libgimpwidgets/gimppropwidgets.c @@ -0,0 +1,4352 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppropwidgets.c + * Copyright (C) 2002-2007 Michael Natterer <mitch@gimp.org> + * Sven Neumann <sven@gimp.org> + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +/* FIXME: #undef GTK_DISABLE_DEPRECATED */ +#undef GTK_DISABLE_DEPRECATED +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpoldwidgets.h" +#include "gimppropwidgets.h" +#include "gimpspinbutton.h" +#include "gimpunitmenu.h" + +#define GIMP_DISABLE_DEPRECATED +#include "gimpwidgets.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimppropwidgets + * @title: GimpPropWidgets + * @short_description: Editable views on #GObject properties. + * + * Editable views on #GObject properties. + **/ + + +/* utility function prototypes */ + +static void set_param_spec (GObject *object, + GtkWidget *widget, + GParamSpec *param_spec); +static void set_radio_spec (GObject *object, + GParamSpec *param_spec); +static GParamSpec * get_param_spec (GObject *object); + +static GParamSpec * find_param_spec (GObject *object, + const gchar *property_name, + const gchar *strloc); +static GParamSpec * check_param_spec (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc); +static GParamSpec * check_param_spec_w (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc); + +static gboolean get_numeric_values (GObject *object, + GParamSpec *param_spec, + gdouble *value, + gdouble *lower, + gdouble *upper, + const gchar *strloc); + +static void connect_notify (GObject *config, + const gchar *property_name, + GCallback callback, + gpointer callback_data); + + +/******************/ +/* check button */ +/******************/ + +static void gimp_prop_check_button_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_check_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button); + +/** + * gimp_prop_check_button_new: + * @config: Object to which property is attached. + * @property_name: Name of boolean property controlled by checkbutton. + * @label: Label to give checkbutton (including mnemonic). + * + * Creates a #GtkCheckButton that displays and sets the specified + * boolean property. + * If @label is #NULL, the @property_name's nick will be used as label + * of the returned button. + * + * Return value: The newly created #GtkCheckButton widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_check_button_new (GObject *config, + const gchar *property_name, + const gchar *label) +{ + GParamSpec *param_spec; + GtkWidget *button; + gboolean value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_BOOLEAN, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! label) + label = g_param_spec_get_nick (param_spec); + + g_object_get (config, + property_name, &value, + NULL); + + button = gtk_check_button_new_with_mnemonic (label); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), value); + + set_param_spec (G_OBJECT (button), button, param_spec); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_prop_check_button_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_check_button_notify), + button); + + return button; +} + +static void +gimp_prop_check_button_callback (GtkWidget *widget, + GObject *config) +{ + GParamSpec *param_spec; + gboolean value; + gboolean v; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + value = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + { + g_object_set (config, param_spec->name, value, NULL); + + gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget)); + } +} + +static void +gimp_prop_check_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button) +{ + gboolean value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) != value) + { + g_signal_handlers_block_by_func (button, + gimp_prop_check_button_callback, + config); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), value); + gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (button)); + + g_signal_handlers_unblock_by_func (button, + gimp_prop_check_button_callback, + config); + } +} + + +static void gimp_prop_enum_check_button_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_enum_check_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button); + +/** + * gimp_prop_enum_check_button_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property controlled by checkbutton. + * @label: Label to give checkbutton (including mnemonic). + * @false_value: Enum value corresponding to unchecked state. + * @true_value: Enum value corresponding to checked state. + * + * Creates a #GtkCheckButton that displays and sets the specified + * property of type Enum. Note that this widget only allows two values + * for the enum, one corresponding to the "checked" state and the + * other to the "unchecked" state. + * If @label is #NULL, the @property_name's nick will be used as label + * of the returned button. + * + * Return value: The newly created #GtkCheckButton widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_enum_check_button_new (GObject *config, + const gchar *property_name, + const gchar *label, + gint false_value, + gint true_value) +{ + GParamSpec *param_spec; + GtkWidget *button; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! label) + label = g_param_spec_get_nick (param_spec); + + g_object_get (config, + property_name, &value, + NULL); + + button = gtk_check_button_new_with_mnemonic (label); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + value == true_value); + + if (value != false_value && value != true_value) + gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (button), TRUE); + + set_param_spec (G_OBJECT (button), button, param_spec); + + g_object_set_data (G_OBJECT (button), "false-value", + GINT_TO_POINTER (false_value)); + g_object_set_data (G_OBJECT (button), "true-value", + GINT_TO_POINTER (true_value)); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_prop_enum_check_button_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_enum_check_button_notify), + button); + + return button; +} + +static void +gimp_prop_enum_check_button_callback (GtkWidget *widget, + GObject *config) +{ + GParamSpec *param_spec; + gint false_value; + gint true_value; + gint value; + gint v; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + false_value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "false-value")); + true_value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "true-value")); + + value = (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? + true_value : false_value); + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + { + g_object_set (config, param_spec->name, value, NULL); + + gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (widget), FALSE); + + gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget)); + } +} + +static void +gimp_prop_enum_check_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button) +{ + gint value; + gint false_value; + gint true_value; + gboolean active = FALSE; + gboolean inconsistent = FALSE; + + g_object_get (config, + param_spec->name, &value, + NULL); + + false_value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "false-value")); + true_value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "true-value")); + + if (value == true_value) + active = TRUE; + else if (value != false_value) + inconsistent = TRUE; + + gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (button), + inconsistent); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) != active) + { + g_signal_handlers_block_by_func (button, + gimp_prop_enum_check_button_callback, + config); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), active); + gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (button)); + + g_signal_handlers_unblock_by_func (button, + gimp_prop_enum_check_button_callback, + config); + } +} + + +/*************************/ +/* int/enum combo box */ +/*************************/ + +static void gimp_prop_int_combo_box_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_int_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *widget); + +static void gimp_prop_pointer_combo_box_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_pointer_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo_box); + +/** + * gimp_prop_int_combo_box_new: + * @config: Object to which property is attached. + * @property_name: Name of int property controlled by combo box. + * @store: #GimpIntStore holding list of labels, values, etc. + * + * Creates a #GimpIntComboBox widget to display and set the specified + * property. The contents of the widget are determined by @store, + * which should be created using gimp_int_store_new(). + * + * Return value: The newly created #GimpIntComboBox widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_int_combo_box_new (GObject *config, + const gchar *property_name, + GimpIntStore *store) +{ + GParamSpec *param_spec; + GtkWidget *combo_box; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_INT, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + combo_box = g_object_new (GIMP_TYPE_INT_COMBO_BOX, + "model", store, + NULL); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo_box), value); + + g_signal_connect (combo_box, "changed", + G_CALLBACK (gimp_prop_int_combo_box_callback), + config); + + set_param_spec (G_OBJECT (combo_box), combo_box, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_int_combo_box_notify), + combo_box); + + return combo_box; +} + +/** + * gimp_prop_pointer_combo_box_new: + * @config: Object to which property is attached. + * @property_name: Name of GType/gpointer property controlled by combo box. + * @store: #GimpIntStore holding list of labels, values, etc. + * + * Creates a #GimpIntComboBox widget to display and set the specified + * property. The contents of the widget are determined by @store, + * which should be created using gimp_int_store_new(). + * Values are GType/gpointer data, and therefore must be stored in the + * "user-data" column, instead of the usual "value" column. + * + * Return value: The newly created #GimpIntComboBox widget. + * + * Since: 2.10 + */ +GtkWidget * +gimp_prop_pointer_combo_box_new (GObject *config, + const gchar *property_name, + GimpIntStore *store) +{ + GParamSpec *param_spec; + GtkWidget *combo_box; + gpointer property_value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_GTYPE, G_STRFUNC); + if (! param_spec) + { + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_POINTER, G_STRFUNC); + if (! param_spec) + return NULL; + } + + g_object_get (config, + property_name, &property_value, + NULL); + + /* We use a GimpIntComboBox but we cannot store gpointer in the + * "value" column, because gpointer is not a subset of gint. Instead + * we store the value in the "user-data" column which is a gpointer. + */ + combo_box = g_object_new (GIMP_TYPE_INT_COMBO_BOX, + "model", store, + NULL); + + gimp_int_combo_box_set_active_by_user_data (GIMP_INT_COMBO_BOX (combo_box), + property_value); + + g_signal_connect (combo_box, "changed", + G_CALLBACK (gimp_prop_pointer_combo_box_callback), + config); + + set_param_spec (G_OBJECT (combo_box), combo_box, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_pointer_combo_box_notify), + combo_box); + + return combo_box; +} + +/** + * gimp_prop_enum_combo_box_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property controlled by combo box. + * @minimum: Smallest allowed value of enum. + * @maximum: Largest allowed value of enum. + * + * Creates a #GimpIntComboBox widget to display and set the specified + * enum property. The @mimimum_value and @maximum_value give the + * possibility of restricting the allowed range to a subset of the + * enum. If the two values are equal (e.g., 0, 0), then the full + * range of the Enum is used. + * + * Return value: The newly created #GimpEnumComboBox widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_enum_combo_box_new (GObject *config, + const gchar *property_name, + gint minimum, + gint maximum) +{ + GParamSpec *param_spec; + GtkListStore *store = NULL; + GtkWidget *combo_box; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + if (minimum != maximum) + { + store = gimp_enum_store_new_with_range (param_spec->value_type, + minimum, maximum); + } + else if (param_spec->value_type == GIMP_TYPE_DESATURATE_MODE) + { + /* this is a bad hack, if we get more of those, we should probably + * think of something less ugly + */ + store = gimp_enum_store_new_with_values (param_spec->value_type, + 5, + GIMP_DESATURATE_LUMINANCE, + GIMP_DESATURATE_LUMA, + GIMP_DESATURATE_LIGHTNESS, + GIMP_DESATURATE_AVERAGE, + GIMP_DESATURATE_VALUE); + } + else if (param_spec->value_type == GIMP_TYPE_SELECT_CRITERION) + { + /* ditto */ + store = gimp_enum_store_new_with_values (param_spec->value_type, + 12, + GIMP_SELECT_CRITERION_COMPOSITE, + GIMP_SELECT_CRITERION_R, + GIMP_SELECT_CRITERION_G, + GIMP_SELECT_CRITERION_B, + GIMP_SELECT_CRITERION_A, + GIMP_SELECT_CRITERION_H, + GIMP_SELECT_CRITERION_S, + GIMP_SELECT_CRITERION_V, + GIMP_SELECT_CRITERION_LCH_L, + GIMP_SELECT_CRITERION_LCH_C, + GIMP_SELECT_CRITERION_LCH_H); + } + + if (store) + { + combo_box = g_object_new (GIMP_TYPE_ENUM_COMBO_BOX, + "model", store, + NULL); + g_object_unref (store); + } + else + { + combo_box = gimp_enum_combo_box_new (param_spec->value_type); + } + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo_box), value); + + g_signal_connect (combo_box, "changed", + G_CALLBACK (gimp_prop_int_combo_box_callback), + config); + + set_param_spec (G_OBJECT (combo_box), combo_box, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_int_combo_box_notify), + combo_box); + + return combo_box; +} + +static void +gimp_prop_int_combo_box_callback (GtkWidget *widget, + GObject *config) +{ + GParamSpec *param_spec; + gint value; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value)) + { + gint v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_int_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo_box) +{ + gint value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (combo_box, + gimp_prop_int_combo_box_callback, + config); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo_box), value); + + g_signal_handlers_unblock_by_func (combo_box, + gimp_prop_int_combo_box_callback, + config); +} + +static void +gimp_prop_pointer_combo_box_callback (GtkWidget *widget, + GObject *config) +{ + GParamSpec *param_spec; + gpointer value; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + if (gimp_int_combo_box_get_active_user_data (GIMP_INT_COMBO_BOX (widget), + &value)) + { + gpointer v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_pointer_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo_box) +{ + gpointer value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (combo_box, + gimp_prop_pointer_combo_box_callback, + config); + + gimp_int_combo_box_set_active_by_user_data (GIMP_INT_COMBO_BOX (combo_box), + value); + + g_signal_handlers_unblock_by_func (combo_box, + gimp_prop_pointer_combo_box_callback, + config); +} + +/************************/ +/* boolean combo box */ +/************************/ + +static void gimp_prop_boolean_combo_box_callback (GtkWidget *combo, + GObject *config); +static void gimp_prop_boolean_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo); + + +/** + * gimp_prop_boolean_combo_box_new: + * @config: Object to which property is attached. + * @property_name: Name of boolean property controlled by combo box. + * @true_text: Label used for entry corresponding to %TRUE value. + * @false_text: Label used for entry corresponding to %FALSE value. + * + * Creates a #GtkComboBox widget to display and set the specified + * boolean property. The combo box will have two entries, one + * displaying the @true_text label, the other displaying the + * @false_text label. + * + * Return value: The newly created #GtkComboBox widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_boolean_combo_box_new (GObject *config, + const gchar *property_name, + const gchar *true_text, + const gchar *false_text) +{ + GParamSpec *param_spec; + GtkWidget *combo; + gboolean value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_BOOLEAN, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + combo = gimp_int_combo_box_new (true_text, TRUE, + false_text, FALSE, + NULL); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), value); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_prop_boolean_combo_box_callback), + config); + + set_param_spec (G_OBJECT (combo), combo, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_boolean_combo_box_notify), + combo); + + return combo; +} + +static void +gimp_prop_boolean_combo_box_callback (GtkWidget *combo, + GObject *config) +{ + GParamSpec *param_spec; + gint value; + + param_spec = get_param_spec (G_OBJECT (combo)); + if (! param_spec) + return; + + if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &value)) + { + gint v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_boolean_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo) +{ + gboolean value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (combo, + gimp_prop_boolean_combo_box_callback, + config); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), value); + + g_signal_handlers_unblock_by_func (combo, + gimp_prop_boolean_combo_box_callback, + config); +} + + +/*****************/ +/* radio boxes */ +/*****************/ + +static void gimp_prop_radio_button_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_radio_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button); + + +/** + * gimp_prop_enum_radio_frame_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property controlled by the radio buttons. + * @title: Label for the frame holding the buttons + * @minimum: Smallest value of enum to be included. + * @maximum: Largest value of enum to be included. + * + * Creates a group of radio buttons which function to set and display + * the specified enum property. The @minimum and @maximum arguments + * allow only a subset of the enum to be used. If the two arguments + * are equal (e.g., 0, 0), then the full range of the enum will be used. + * If @title is #NULL, the @property_name's nick will be used as label + * of the returned frame. + * + * Return value: A #GimpFrame containing the radio buttons. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_enum_radio_frame_new (GObject *config, + const gchar *property_name, + const gchar *title, + gint minimum, + gint maximum) +{ + GParamSpec *param_spec; + GtkWidget *frame; + GtkWidget *button; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! title) + title = g_param_spec_get_nick (param_spec); + + g_object_get (config, + property_name, &value, + NULL); + + if (minimum != maximum) + { + frame = gimp_enum_radio_frame_new_with_range (param_spec->value_type, + minimum, maximum, + gtk_label_new (title), + G_CALLBACK (gimp_prop_radio_button_callback), + config, + &button); + } + else + { + frame = gimp_enum_radio_frame_new (param_spec->value_type, + gtk_label_new (title), + G_CALLBACK (gimp_prop_radio_button_callback), + config, + &button); + } + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), value); + + set_radio_spec (G_OBJECT (button), param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_radio_button_notify), + button); + + g_object_set_data (G_OBJECT (frame), "radio-button", button); + + return frame; +} + +/** + * gimp_prop_enum_radio_box_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property controlled by the radio buttons. + * @minimum: Smallest value of enum to be included. + * @maximum: Largest value of enum to be included. + * + * Creates a group of radio buttons which function to set and display + * the specified enum property. The @minimum and @maximum arguments + * allow only a subset of the enum to be used. If the two arguments + * are equal (e.g., 0, 0), then the full range of the enum will be used. + * If you want to assign a label to the group of radio buttons, use + * gimp_prop_enum_radio_frame_new() instead of this function. + * + * Return value: A #GtkVBox containing the radio buttons. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_enum_radio_box_new (GObject *config, + const gchar *property_name, + gint minimum, + gint maximum) +{ + GParamSpec *param_spec; + GtkWidget *vbox; + GtkWidget *button; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + if (minimum != maximum) + { + vbox = gimp_enum_radio_box_new_with_range (param_spec->value_type, + minimum, maximum, + G_CALLBACK (gimp_prop_radio_button_callback), + config, + &button); + } + else + { + vbox = gimp_enum_radio_box_new (param_spec->value_type, + G_CALLBACK (gimp_prop_radio_button_callback), + config, + &button); + } + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), value); + + set_radio_spec (G_OBJECT (button), param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_radio_button_notify), + button); + + g_object_set_data (G_OBJECT (vbox), "radio-button", button); + + return vbox; +} + + +/***********/ +/* label */ +/***********/ + +static void gimp_prop_enum_label_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *label); + +/** + * gimp_prop_enum_label_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property to be displayed. + * + * Return value: The newly created #GimpEnumLabel widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_enum_label_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *label; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + label = gimp_enum_label_new (param_spec->value_type, value); + + set_param_spec (G_OBJECT (label), label, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_enum_label_notify), + label); + + return label; +} + +static void +gimp_prop_enum_label_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *label) +{ + gint value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + gimp_enum_label_set_value (GIMP_ENUM_LABEL (label), value); +} + + +/** + * gimp_prop_boolean_radio_frame_new: + * @config: Object to which property is attached. + * @property_name: Name of boolean property controlled by the radio buttons. + * @title: Label for the frame. + * @true_text: Label for the button corresponding to %TRUE. + * @false_text: Label for the button corresponding to %FALSE. + * + * Creates a pair of radio buttons which function to set and display + * the specified boolean property. + * If @title is #NULL, the @property_name's nick will be used as label + * of the returned frame. + * + * Return value: A #GimpFrame containing the radio buttons. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_boolean_radio_frame_new (GObject *config, + const gchar *property_name, + const gchar *title, + const gchar *true_text, + const gchar *false_text) +{ + GParamSpec *param_spec; + GtkWidget *frame; + GtkWidget *button; + gboolean value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_BOOLEAN, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! title) + title = g_param_spec_get_nick (param_spec); + + g_object_get (config, + property_name, &value, + NULL); + + frame = + gimp_int_radio_group_new (TRUE, title, + G_CALLBACK (gimp_prop_radio_button_callback), + config, value, + + false_text, FALSE, &button, + true_text, TRUE, NULL, + + NULL); + + set_radio_spec (G_OBJECT (button), param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_radio_button_notify), + button); + + g_object_set_data (G_OBJECT (frame), "radio-button", button); + + return frame; +} + +/** + * gimp_prop_enum_stock_box_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property controlled by the radio buttons. + * @stock_prefix: The prefix of the group of stock ids to use. + * @minimum: Smallest value of enum to be included. + * @maximum: Largest value of enum to be included. + * + * Creates a horizontal box of radio buttons with stock icons, which + * function to set and display the value of the specified Enum + * property. The stock_id for each icon is created by appending the + * enum_value's nick to the given @stock_prefix. See + * gimp_enum_stock_box_new() for more information. + * + * Return value: A #libgimpwidgets-gimpenumstockbox containing the radio buttons. + * + * Since: 2.4 + * + * Deprecated: 2.10 + */ +GtkWidget * +gimp_prop_enum_stock_box_new (GObject *config, + const gchar *property_name, + const gchar *stock_prefix, + gint minimum, + gint maximum) +{ + return gimp_prop_enum_icon_box_new (config, property_name, + stock_prefix, minimum, maximum); +} + +/** + * gimp_prop_enum_icon_box_new: + * @config: Object to which property is attached. + * @property_name: Name of enum property controlled by the radio buttons. + * @icon_prefix: The prefix of the group of icon names to use. + * @minimum: Smallest value of enum to be included. + * @maximum: Largest value of enum to be included. + * + * Creates a horizontal box of radio buttons with named icons, which + * function to set and display the value of the specified Enum + * property. The icon name for each icon is created by appending the + * enum_value's nick to the given @icon_prefix. See + * gimp_enum_icon_box_new() for more information. + * + * Return value: A #libgimpwidgets-gimpenumiconbox containing the radio buttons. + * + * Since: 2.10 + */ +GtkWidget * +gimp_prop_enum_icon_box_new (GObject *config, + const gchar *property_name, + const gchar *icon_prefix, + gint minimum, + gint maximum) +{ + GParamSpec *param_spec; + GtkWidget *box; + GtkWidget *button; + gint value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + if (minimum != maximum) + { + box = gimp_enum_icon_box_new_with_range (param_spec->value_type, + minimum, maximum, + icon_prefix, + GTK_ICON_SIZE_MENU, + G_CALLBACK (gimp_prop_radio_button_callback), + config, + &button); + } + else + { + box = gimp_enum_icon_box_new (param_spec->value_type, + icon_prefix, + GTK_ICON_SIZE_MENU, + G_CALLBACK (gimp_prop_radio_button_callback), + config, + &button); + } + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), value); + + set_radio_spec (G_OBJECT (button), param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_radio_button_notify), + button); + + return box; +} + +static void +gimp_prop_radio_button_callback (GtkWidget *widget, + GObject *config) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + GParamSpec *param_spec; + gint value; + gint v; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-item-data")); + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_radio_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button) +{ + gint value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), value); +} + + +/*****************/ +/* adjustments */ +/*****************/ + +static void gimp_prop_adjustment_callback (GtkAdjustment *adjustment, + GObject *config); +static void gimp_prop_adjustment_notify (GObject *config, + GParamSpec *param_spec, + GtkAdjustment *adjustment); + +/** + * gimp_prop_spin_button_new: + * @config: Object to which property is attached. + * @property_name: Name of double property controlled by the spin button. + * @step_increment: Step size. + * @page_increment: Page size. + * @digits: Number of digits after decimal point to display. + * + * Creates a spin button to set and display the value of the + * specified double property. + * + * Return value: A new #libgimpwidgets-gimpspinbutton. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_spin_button_new (GObject *config, + const gchar *property_name, + gdouble step_increment, + gdouble page_increment, + gint digits) +{ + GParamSpec *param_spec; + GtkWidget *spinbutton; + GtkAdjustment *adjustment; + gdouble value; + gdouble lower; + gdouble upper; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! get_numeric_values (config, + param_spec, &value, &lower, &upper, G_STRFUNC)) + return NULL; + + if (! G_IS_PARAM_SPEC_DOUBLE (param_spec)) + digits = 0; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (value, lower, upper, + step_increment, page_increment, 0); + + spinbutton = gimp_spin_button_new (adjustment, step_increment, digits); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + set_param_spec (G_OBJECT (adjustment), spinbutton, param_spec); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_prop_adjustment_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_adjustment_notify), + adjustment); + + return spinbutton; +} + +/** + * gimp_prop_hscale_new: + * @config: Object to which property is attached. + * @property_name: Name of integer or double property controlled by the scale. + * @step_increment: Step size. + * @page_increment: Page size. + * @digits: Number of digits after decimal point to display. + * + * Creates a horizontal scale to control the value of the specified + * integer or double property. + * + * Return value: A new #GtkScale. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_hscale_new (GObject *config, + const gchar *property_name, + gdouble step_increment, + gdouble page_increment, + gint digits) +{ + GParamSpec *param_spec; + GtkWidget *scale; + GtkAdjustment *adjustment; + gdouble value; + gdouble lower; + gdouble upper; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! get_numeric_values (config, + param_spec, &value, &lower, &upper, G_STRFUNC)) + return NULL; + + if (! G_IS_PARAM_SPEC_DOUBLE (param_spec)) + digits = 0; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (value, lower, upper, + step_increment, page_increment, 0.0); + + scale = g_object_new (GTK_TYPE_SCALE, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "adjustment", adjustment, + "digits", digits, + NULL); + + set_param_spec (G_OBJECT (adjustment), scale, param_spec); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_prop_adjustment_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_adjustment_notify), + adjustment); + + return scale; +} + +/** + * gimp_prop_scale_entry_new: + * @config: Object to which property is attached. + * @property_name: Name of double property controlled by the spin button. + * @table: The #GtkTable the widgets will be attached to. + * @column: The column to start with. + * @row: The row to attach the widgets. + * @label: The text for the #GtkLabel which will appear left of + * the #GtkHScale. + * @step_increment: Step size. + * @page_increment: Page size. + * @digits: Number of digits after decimal point to display. + * @limit_scale: %FALSE if the range of possible values of the + * GtkHScale should be the same as of the GtkSpinButton. + * @lower_limit: The scale's lower boundary if @scale_limits is %TRUE. + * @upper_limit: The scale's upper boundary if @scale_limits is %TRUE. + * + * Creates a #libgimpwidgets-gimpscaleentry (slider and spin button) + * to set and display the value of the specified double property. See + * gimp_scale_entry_new() for more information. + * If @label is #NULL, the @property_name's nick will be used as label + * of the returned object. + * + * Note that the @scale_limits boolean is the inverse of + * gimp_scale_entry_new()'s "constrain" parameter. + * + * Return value: The #GtkSpinButton's #GtkAdjustment. + * + * Since: 2.4 + */ +GtkObject * +gimp_prop_scale_entry_new (GObject *config, + const gchar *property_name, + GtkTable *table, + gint column, + gint row, + const gchar *label, + gdouble step_increment, + gdouble page_increment, + gint digits, + gboolean limit_scale, + gdouble lower_limit, + gdouble upper_limit) +{ + GParamSpec *param_spec; + GtkObject *adjustment; + const gchar *tooltip; + gdouble value; + gdouble lower; + gdouble upper; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! get_numeric_values (config, + param_spec, &value, &lower, &upper, G_STRFUNC)) + return NULL; + + if (! label) + label = g_param_spec_get_nick (param_spec); + + tooltip = g_param_spec_get_blurb (param_spec); + + if (! limit_scale) + { + adjustment = gimp_scale_entry_new (table, column, row, + label, -1, -1, + value, lower, upper, + step_increment, page_increment, + digits, + TRUE, 0.0, 0.0, + tooltip, + NULL); + } + else + { + adjustment = gimp_scale_entry_new (table, column, row, + label, -1, -1, + value, lower_limit, upper_limit, + step_increment, page_increment, + digits, + FALSE, lower, upper, + tooltip, + NULL); + } + + set_param_spec (G_OBJECT (adjustment), NULL, param_spec); + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_prop_adjustment_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_adjustment_notify), + adjustment); + + return adjustment; +} + +static void +gimp_prop_widget_set_factor (GtkWidget *widget, + GtkAdjustment *adjustment, + gdouble factor, + gdouble step_increment, + gdouble page_increment, + gint digits) +{ + gdouble *factor_store; + gdouble old_factor = 1.0; + gdouble f; + + g_return_if_fail (widget == NULL || GTK_IS_SPIN_BUTTON (widget)); + g_return_if_fail (widget != NULL || GTK_IS_ADJUSTMENT (adjustment)); + g_return_if_fail (factor != 0.0); + g_return_if_fail (digits >= 0); + + if (! adjustment) + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget)); + + g_return_if_fail (get_param_spec (G_OBJECT (adjustment)) != NULL); + + factor_store = g_object_get_data (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor"); + if (factor_store) + { + old_factor = *factor_store; + } + else + { + factor_store = g_new (gdouble, 1); + g_object_set_data_full (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor", + factor_store, (GDestroyNotify) g_free); + } + + *factor_store = factor; + + f = factor / old_factor; + + if (step_increment <= 0) + step_increment = f * gtk_adjustment_get_step_increment (adjustment); + + if (page_increment <= 0) + page_increment = f * gtk_adjustment_get_page_increment (adjustment); + + gtk_adjustment_configure (adjustment, + f * gtk_adjustment_get_value (adjustment), + f * gtk_adjustment_get_lower (adjustment), + f * gtk_adjustment_get_upper (adjustment), + step_increment, + page_increment, + f * gtk_adjustment_get_page_size (adjustment)); + + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), digits); +} + +/** + * gimp_prop_opacity_entry_new: + * @config: Object to which property is attached. + * @property_name: Name of double property controlled by the spin button. + * @table: The #GtkTable the widgets will be attached to. + * @column: The column to start with. + * @row: The row to attach the widgets. + * @label: The text for the #GtkLabel which will appear left of the + * #GtkHScale. + * + * Creates a #libgimpwidgets-gimpscaleentry (slider and spin button) + * to set and display the value of the specified double property, + * which should represent an "opacity" variable with range 0 to 100. + * See gimp_scale_entry_new() for more information. + * + * Return value: The #GtkSpinButton's #GtkAdjustment. + * + * Since: 2.4 + */ +GtkObject * +gimp_prop_opacity_entry_new (GObject *config, + const gchar *property_name, + GtkTable *table, + gint column, + gint row, + const gchar *label) +{ + GtkObject *adjustment; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + adjustment = gimp_prop_scale_entry_new (config, property_name, + table, column, row, label, + 0.01, 0.1, 1, + FALSE, 0.0, 0.0); + + if (adjustment) + { + gimp_prop_widget_set_factor (GIMP_SCALE_ENTRY_SPINBUTTON (adjustment), + GTK_ADJUSTMENT (adjustment), + 100.0, 0.0, 0.0, 1); + } + + return adjustment; +} + + +static void +gimp_prop_adjustment_callback (GtkAdjustment *adjustment, + GObject *config) +{ + GParamSpec *param_spec; + gdouble value; + gdouble *factor; + + param_spec = get_param_spec (G_OBJECT (adjustment)); + if (! param_spec) + return; + + value = gtk_adjustment_get_value (adjustment); + + factor = g_object_get_data (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor"); + if (factor) + value /= *factor; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (gint) value) + g_object_set (config, param_spec->name, (gint) value, NULL); + } + else if (G_IS_PARAM_SPEC_UINT (param_spec)) + { + guint v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (guint) value) + g_object_set (config, param_spec->name, (guint) value, NULL); + } + else if (G_IS_PARAM_SPEC_LONG (param_spec)) + { + glong v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (glong) value) + g_object_set (config, param_spec->name, (glong) value, NULL); + } + else if (G_IS_PARAM_SPEC_ULONG (param_spec)) + { + gulong v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (gulong) value) + g_object_set (config, param_spec->name, (gulong) value, NULL); + } + else if (G_IS_PARAM_SPEC_INT64 (param_spec)) + { + gint64 v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (gint64) value) + g_object_set (config, param_spec->name, (gint64) value, NULL); + } + else if (G_IS_PARAM_SPEC_UINT64 (param_spec)) + { + guint64 v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (guint64) value) + g_object_set (config, param_spec->name, (guint64) value, NULL); + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + gdouble v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_adjustment_notify (GObject *config, + GParamSpec *param_spec, + GtkAdjustment *adjustment) +{ + gdouble value; + gdouble *factor; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint int_value; + + g_object_get (config, param_spec->name, &int_value, NULL); + + value = int_value; + } + else if (G_IS_PARAM_SPEC_UINT (param_spec)) + { + guint uint_value; + + g_object_get (config, param_spec->name, &uint_value, NULL); + + value = uint_value; + } + else if (G_IS_PARAM_SPEC_LONG (param_spec)) + { + glong long_value; + + g_object_get (config, param_spec->name, &long_value, NULL); + + value = long_value; + } + else if (G_IS_PARAM_SPEC_ULONG (param_spec)) + { + gulong ulong_value; + + g_object_get (config, param_spec->name, &ulong_value, NULL); + + value = ulong_value; + } + else if (G_IS_PARAM_SPEC_INT64 (param_spec)) + { + gint64 int64_value; + + g_object_get (config, param_spec->name, &int64_value, NULL); + + value = int64_value; + } + else if (G_IS_PARAM_SPEC_UINT64 (param_spec)) + { + guint64 uint64_value; + + g_object_get (config, param_spec->name, &uint64_value, NULL); + +#if defined _MSC_VER && (_MSC_VER < 1300) + value = (gint64) uint64_value; +#else + value = uint64_value; +#endif + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + g_object_get (config, param_spec->name, &value, NULL); + } + else + { + g_warning ("%s: unhandled param spec of type %s", + G_STRFUNC, G_PARAM_SPEC_TYPE_NAME (param_spec)); + return; + } + + factor = g_object_get_data (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor"); + if (factor) + value *= *factor; + + if (gtk_adjustment_get_value (adjustment) != value) + { + g_signal_handlers_block_by_func (adjustment, + gimp_prop_adjustment_callback, + config); + + gtk_adjustment_set_value (adjustment, value); + + g_signal_handlers_unblock_by_func (adjustment, + gimp_prop_adjustment_callback, + config); + } +} + + +/*************/ +/* memsize */ +/*************/ + +static void gimp_prop_memsize_callback (GimpMemsizeEntry *entry, + GObject *config); +static void gimp_prop_memsize_notify (GObject *config, + GParamSpec *param_spec, + GimpMemsizeEntry *entry); + +/** + * gimp_prop_memsize_entry_new: + * @config: Object to which property is attached. + * @property_name: Name of memsize property. + * + * Creates a #GimpMemsizeEntry (spin button and option menu) to set + * and display the value of the specified memsize property. See + * gimp_memsize_entry_new() for more information. + * + * Return value: A new #GimpMemsizeEntry. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_memsize_entry_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GParamSpecUInt64 *uint64_spec; + GtkWidget *entry; + guint64 value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_MEMSIZE, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + uint64_spec = G_PARAM_SPEC_UINT64 (param_spec); + + g_return_val_if_fail (uint64_spec->minimum <= GIMP_MAX_MEMSIZE, NULL); + g_return_val_if_fail (uint64_spec->maximum <= GIMP_MAX_MEMSIZE, NULL); + + entry = gimp_memsize_entry_new (value, + uint64_spec->minimum, + uint64_spec->maximum); + + set_param_spec (G_OBJECT (entry), + GIMP_MEMSIZE_ENTRY (entry)->spinbutton, + param_spec); + + g_signal_connect (entry, "value-changed", + G_CALLBACK (gimp_prop_memsize_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_memsize_notify), + entry); + + return entry; +} + + +static void +gimp_prop_memsize_callback (GimpMemsizeEntry *entry, + GObject *config) +{ + GParamSpec *param_spec; + guint64 value; + guint64 v; + + param_spec = get_param_spec (G_OBJECT (entry)); + if (! param_spec) + return; + + g_return_if_fail (G_IS_PARAM_SPEC_UINT64 (param_spec)); + + value = gimp_memsize_entry_get_value (entry); + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); +} + +static void +gimp_prop_memsize_notify (GObject *config, + GParamSpec *param_spec, + GimpMemsizeEntry *entry) +{ + guint64 value; + + g_return_if_fail (G_IS_PARAM_SPEC_UINT64 (param_spec)); + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (entry->value != value) + { + g_signal_handlers_block_by_func (entry, + gimp_prop_memsize_callback, + config); + + gimp_memsize_entry_set_value (entry, value); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_memsize_callback, + config); + } +} + + +/***********/ +/* label */ +/***********/ + +static void gimp_prop_label_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *label); + +/** + * gimp_prop_label_new: + * @config: Object to which property is attached. + * @property_name: Name of string property. + * + * Creates a #GtkLabel to display the value of the specified property. + * The property should be a string property or at least transformable + * to a string. If the user should be able to edit the string, use + * gimp_prop_entry_new() instead. + * + * Return value: A new #GtkLabel widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_label_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *label; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + + if (! param_spec) + return NULL; + + if (! g_value_type_transformable (param_spec->value_type, G_TYPE_STRING)) + { + g_warning ("%s: property '%s' of %s is not transformable to string", + G_STRLOC, + param_spec->name, + g_type_name (param_spec->owner_type)); + return NULL; + } + + label = gtk_label_new (NULL); + + set_param_spec (G_OBJECT (label), label, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_label_notify), + label); + + gimp_prop_label_notify (config, param_spec, label); + + return label; +} + +static void +gimp_prop_label_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *label) +{ + GValue value = G_VALUE_INIT; + + g_value_init (&value, param_spec->value_type); + + g_object_get_property (config, param_spec->name, &value); + + if (G_VALUE_HOLDS_STRING (&value)) + { + const gchar *str = g_value_get_string (&value); + + gtk_label_set_text (GTK_LABEL (label), str ? str : ""); + } + else + { + GValue str_value = G_VALUE_INIT; + const gchar *str; + + g_value_init (&str_value, G_TYPE_STRING); + g_value_transform (&value, &str_value); + + str = g_value_get_string (&str_value); + + gtk_label_set_text (GTK_LABEL (label), str ? str : ""); + + g_value_unset (&str_value); + } + + g_value_unset (&value); +} + + +/***********/ +/* entry */ +/***********/ + +static void gimp_prop_entry_callback (GtkWidget *entry, + GObject *config); +static void gimp_prop_entry_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *entry); + +/** + * gimp_prop_entry_new: + * @config: Object to which property is attached. + * @property_name: Name of string property. + * @max_len: Maximum allowed length of string. + * + * Creates a #GtkEntry to set and display the value of the specified + * string property. + * + * Return value: A new #GtkEntry widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_entry_new (GObject *config, + const gchar *property_name, + gint max_len) +{ + GParamSpec *param_spec; + GtkWidget *entry; + gchar *value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + entry = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry), value ? value : ""); + + g_free (value); + + if (max_len > 0) + gtk_entry_set_max_length (GTK_ENTRY (entry), max_len); + + gtk_editable_set_editable (GTK_EDITABLE (entry), + param_spec->flags & G_PARAM_WRITABLE); + + set_param_spec (G_OBJECT (entry), entry, param_spec); + + g_signal_connect (entry, "changed", + G_CALLBACK (gimp_prop_entry_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_entry_notify), + entry); + + return entry; +} + +static void +gimp_prop_entry_callback (GtkWidget *entry, + GObject *config) +{ + GParamSpec *param_spec; + const gchar *value; + gchar *v; + + param_spec = get_param_spec (G_OBJECT (entry)); + if (! param_spec) + return; + + value = gtk_entry_get_text (GTK_ENTRY (entry)); + + g_object_get (config, param_spec->name, &v, NULL); + + if (g_strcmp0 (v, value)) + { + g_signal_handlers_block_by_func (config, + gimp_prop_entry_notify, + entry); + + g_object_set (config, param_spec->name, value, NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_entry_notify, + entry); + } + + g_free (v); +} + +static void +gimp_prop_entry_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *entry) +{ + gchar *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (entry, + gimp_prop_entry_callback, + config); + + gtk_entry_set_text (GTK_ENTRY (entry), value ? value : ""); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_entry_callback, + config); + + g_free (value); +} + + +/*****************/ +/* text buffer */ +/*****************/ + +static void gimp_prop_text_buffer_callback (GtkTextBuffer *text_buffer, + GObject *config); +static void gimp_prop_text_buffer_notify (GObject *config, + GParamSpec *param_spec, + GtkTextBuffer *text_buffer); + +/** + * gimp_prop_text_buffer_new: + * @config: Object to which property is attached. + * @property_name: Name of string property. + * @max_len: Maximum allowed length of text (in characters). + * + * Creates a #GtkTextBuffer to set and display the value of the + * specified string property. Unless the string is expected to + * contain multiple lines or a large amount of text, use + * gimp_prop_entry_new() instead. See #GtkTextView for information on + * how to insert a text buffer into a visible widget. + * + * If @max_len is 0 or negative, the text buffer allows an unlimited + * number of characters to be entered. + * + * Return value: A new #GtkTextBuffer. + * + * Since: 2.4 + */ +GtkTextBuffer * +gimp_prop_text_buffer_new (GObject *config, + const gchar *property_name, + gint max_len) +{ + GParamSpec *param_spec; + GtkTextBuffer *text_buffer; + gchar *value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + text_buffer = gtk_text_buffer_new (NULL); + gtk_text_buffer_set_text (text_buffer, value ? value : "", -1); + + g_free (value); + + if (max_len > 0) + g_object_set_data (G_OBJECT (text_buffer), "max-len", + GINT_TO_POINTER (max_len)); + + set_param_spec (G_OBJECT (text_buffer), NULL, param_spec); + + g_signal_connect (text_buffer, "changed", + G_CALLBACK (gimp_prop_text_buffer_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_text_buffer_notify), + text_buffer); + + return text_buffer; +} + +static void +gimp_prop_text_buffer_callback (GtkTextBuffer *text_buffer, + GObject *config) +{ + GParamSpec *param_spec; + GtkTextIter start_iter; + GtkTextIter end_iter; + gchar *text; + gint max_len; + + param_spec = get_param_spec (G_OBJECT (text_buffer)); + if (! param_spec) + return; + + gtk_text_buffer_get_bounds (text_buffer, &start_iter, &end_iter); + text = gtk_text_buffer_get_text (text_buffer, &start_iter, &end_iter, FALSE); + + max_len = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (text_buffer), + "max-length")); + + if (max_len > 0 && g_utf8_strlen (text, -1) > max_len) + { + g_message (dngettext (GETTEXT_PACKAGE "-libgimp", + "This text input field is limited to %d character.", + "This text input field is limited to %d characters.", + max_len), max_len); + + gtk_text_buffer_get_iter_at_offset (text_buffer, + &start_iter, max_len - 1); + gtk_text_buffer_get_end_iter (text_buffer, &end_iter); + + /* this calls us recursively, but in the else branch */ + gtk_text_buffer_delete (text_buffer, &start_iter, &end_iter); + } + else + { + gchar *v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (g_strcmp0 (v, text)) + { + g_signal_handlers_block_by_func (config, + gimp_prop_text_buffer_notify, + text_buffer); + + g_object_set (config, param_spec->name, text, NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_text_buffer_notify, + text_buffer); + } + + g_free (v); + } + + g_free (text); +} + +static void +gimp_prop_text_buffer_notify (GObject *config, + GParamSpec *param_spec, + GtkTextBuffer *text_buffer) +{ + gchar *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (text_buffer, + gimp_prop_text_buffer_callback, + config); + + gtk_text_buffer_set_text (text_buffer, value ? value : "", -1); + + g_signal_handlers_unblock_by_func (text_buffer, + gimp_prop_text_buffer_callback, + config); + + g_free (value); +} + + +/***********************/ +/* string combo box */ +/***********************/ + +static void gimp_prop_string_combo_box_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_string_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *widget); + +/** + * gimp_prop_string_combo_box_new: + * @config: Object to which property is attached. + * @property_name: Name of int property controlled by combo box. + * @model: #GtkTreeStore holding list of values + * @id_column: column in @store that holds string IDs + * @label_column: column in @store that holds labels to use in the combo-box + * + * Creates a #GimpStringComboBox widget to display and set the + * specified property. The contents of the widget are determined by + * @store. + * + * Return value: The newly created #GimpStringComboBox widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_string_combo_box_new (GObject *config, + const gchar *property_name, + GtkTreeModel *model, + gint id_column, + gint label_column) +{ + GParamSpec *param_spec; + GtkWidget *combo_box; + gchar *value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + combo_box = gimp_string_combo_box_new (model, id_column, label_column); + + gimp_string_combo_box_set_active (GIMP_STRING_COMBO_BOX (combo_box), value); + + g_signal_connect (combo_box, "changed", + G_CALLBACK (gimp_prop_string_combo_box_callback), + config); + + set_param_spec (G_OBJECT (combo_box), combo_box, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_string_combo_box_notify), + combo_box); + + return combo_box; +} + +static void +gimp_prop_string_combo_box_callback (GtkWidget *widget, + GObject *config) +{ + GParamSpec *param_spec; + gchar *value; + gchar *v; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + value = gimp_string_combo_box_get_active (GIMP_STRING_COMBO_BOX (widget)); + + g_object_get (config, param_spec->name, &v, NULL); + + if (g_strcmp0 (v, value)) + g_object_set (config, param_spec->name, value, NULL); + + g_free (value); + g_free (v); +} + +static void +gimp_prop_string_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo_box) +{ + gchar *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (combo_box, + gimp_prop_string_combo_box_callback, + config); + + gimp_string_combo_box_set_active (GIMP_STRING_COMBO_BOX (combo_box), value); + + g_signal_handlers_unblock_by_func (combo_box, + gimp_prop_string_combo_box_callback, + config); + + g_free (value); +} + + +/*************************/ +/* file chooser button */ +/*************************/ + + +static GtkWidget * gimp_prop_file_chooser_button_setup (GtkWidget *button, + GObject *config, + GParamSpec *param_spec); +static void gimp_prop_file_chooser_button_callback (GtkFileChooser *button, + GObject *config); +static void gimp_prop_file_chooser_button_notify (GObject *config, + GParamSpec *param_spec, + GtkFileChooser *button); + + +/** + * gimp_prop_file_chooser_button_new: + * @config: object to which property is attached. + * @property_name: name of path property. + * @title: the title of the browse dialog. + * @action: the open mode for the widget. + * + * Creates a #GtkFileChooserButton to edit the specified path property. + * + * Note that #GtkFileChooserButton implements the #GtkFileChooser + * interface; you can use the #GtkFileChooser API with it. + * + * Return value: A new #GtkFileChooserButton. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_file_chooser_button_new (GObject *config, + const gchar *property_name, + const gchar *title, + GtkFileChooserAction action) +{ + GParamSpec *param_spec; + GtkWidget *button; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_CONFIG_PATH, G_STRFUNC); + if (! param_spec) + return NULL; + + button = gtk_file_chooser_button_new (title, action); + + return gimp_prop_file_chooser_button_setup (button, config, param_spec); +} + +/** + * gimp_prop_file_chooser_button_new_with_dialog: + * @config: object to which property is attached. + * @property_name: name of path property. + * @dialog: the #GtkFileChooserDialog widget to use. + * + * Creates a #GtkFileChooserButton to edit the specified path property. + * + * The button uses @dialog as it's file-picking window. Note that @dialog + * must be a #GtkFileChooserDialog (or subclass) and must not have + * %GTK_DIALOG_DESTROY_WITH_PARENT set. + * + * Note that #GtkFileChooserButton implements the #GtkFileChooser + * interface; you can use the #GtkFileChooser API with it. + * + * Return value: A new #GtkFileChooserButton. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_file_chooser_button_new_with_dialog (GObject *config, + const gchar *property_name, + GtkWidget *dialog) +{ + GParamSpec *param_spec; + GtkWidget *button; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL); + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_CONFIG_PATH, G_STRFUNC); + if (! param_spec) + return NULL; + + button = gtk_file_chooser_button_new_with_dialog (dialog); + + return gimp_prop_file_chooser_button_setup (button, config, param_spec); +} + +static GtkWidget * +gimp_prop_file_chooser_button_setup (GtkWidget *button, + GObject *config, + GParamSpec *param_spec) +{ + gchar *value; + GFile *file = NULL; + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (value) + { + file = gimp_file_new_for_config_path (value, NULL); + g_free (value); + } + + if (file) + { + gchar *basename = g_file_get_basename (file); + + if (basename && basename[0] == '.') + gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (button), TRUE); + + g_free (basename); + + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (button), file, NULL); + g_object_unref (file); + } + + set_param_spec (G_OBJECT (button), button, param_spec); + + g_signal_connect (button, "file-set", + G_CALLBACK (gimp_prop_file_chooser_button_callback), + config); + + connect_notify (config, param_spec->name, + G_CALLBACK (gimp_prop_file_chooser_button_notify), + button); + + return button; +} + +static void +gimp_prop_file_chooser_button_callback (GtkFileChooser *button, + GObject *config) +{ + GParamSpec *param_spec; + GFile *file; + gchar *value = NULL; + gchar *v; + + param_spec = get_param_spec (G_OBJECT (button)); + if (! param_spec) + return; + + file = gtk_file_chooser_get_file (button); + + if (file) + { + value = gimp_file_get_config_path (file, NULL); + g_object_unref (file); + } + + g_object_get (config, param_spec->name, &v, NULL); + + if (g_strcmp0 (v, value)) + { + g_signal_handlers_block_by_func (config, + gimp_prop_file_chooser_button_notify, + button); + + g_object_set (config, param_spec->name, value, NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_file_chooser_button_notify, + button); + } + + g_free (value); + g_free (v); +} + +static void +gimp_prop_file_chooser_button_notify (GObject *config, + GParamSpec *param_spec, + GtkFileChooser *button) +{ + gchar *value; + GFile *file = NULL; + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (value) + { + file = gimp_file_new_for_config_path (value, NULL); + g_free (value); + } + + g_signal_handlers_block_by_func (button, + gimp_prop_file_chooser_button_callback, + config); + + if (file) + { + gtk_file_chooser_set_file (button, file, NULL); + g_object_unref (file); + } + else + { + gtk_file_chooser_unselect_all (button); + } + + g_signal_handlers_unblock_by_func (button, + gimp_prop_file_chooser_button_callback, + config); +} + + +/*****************/ +/* path editor */ +/*****************/ + +static void gimp_prop_path_editor_path_callback (GimpPathEditor *editor, + GObject *config); +static void gimp_prop_path_editor_writable_callback (GimpPathEditor *editor, + GObject *config); +static void gimp_prop_path_editor_path_notify (GObject *config, + GParamSpec *param_spec, + GimpPathEditor *editor); +static void gimp_prop_path_editor_writable_notify (GObject *config, + GParamSpec *param_spec, + GimpPathEditor *editor); + +GtkWidget * +gimp_prop_path_editor_new (GObject *config, + const gchar *path_property_name, + const gchar *writable_property_name, + const gchar *filesel_title) +{ + GParamSpec *path_param_spec; + GParamSpec *writable_param_spec = NULL; + GtkWidget *editor; + gchar *value; + gchar *filename; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (path_property_name != NULL, NULL); + + path_param_spec = check_param_spec_w (config, path_property_name, + GIMP_TYPE_PARAM_CONFIG_PATH, G_STRFUNC); + if (! path_param_spec) + return NULL; + + if (writable_property_name) + { + writable_param_spec = check_param_spec_w (config, writable_property_name, + GIMP_TYPE_PARAM_CONFIG_PATH, + G_STRFUNC); + if (! writable_param_spec) + return NULL; + } + + g_object_get (config, + path_property_name, &value, + NULL); + + filename = value ? gimp_config_path_expand (value, TRUE, NULL) : NULL; + g_free (value); + + editor = gimp_path_editor_new (filesel_title, filename); + g_free (filename); + + if (writable_property_name) + { + g_object_get (config, + writable_property_name, &value, + NULL); + + filename = value ? gimp_config_path_expand (value, TRUE, NULL) : NULL; + g_free (value); + + gimp_path_editor_set_writable_path (GIMP_PATH_EDITOR (editor), filename); + g_free (filename); + } + + g_object_set_data (G_OBJECT (editor), "gimp-config-param-spec-path", + path_param_spec); + + g_signal_connect (editor, "path-changed", + G_CALLBACK (gimp_prop_path_editor_path_callback), + config); + + connect_notify (config, path_property_name, + G_CALLBACK (gimp_prop_path_editor_path_notify), + editor); + + if (writable_property_name) + { + g_object_set_data (G_OBJECT (editor), "gimp-config-param-spec-writable", + writable_param_spec); + + g_signal_connect (editor, "writable-changed", + G_CALLBACK (gimp_prop_path_editor_writable_callback), + config); + + connect_notify (config, writable_property_name, + G_CALLBACK (gimp_prop_path_editor_writable_notify), + editor); + } + + return editor; +} + +static void +gimp_prop_path_editor_path_callback (GimpPathEditor *editor, + GObject *config) +{ + GParamSpec *path_param_spec; + GParamSpec *writable_param_spec; + gchar *value; + gchar *utf8; + + path_param_spec = g_object_get_data (G_OBJECT (editor), + "gimp-config-param-spec-path"); + writable_param_spec = g_object_get_data (G_OBJECT (editor), + "gimp-config-param-spec-writable"); + if (! path_param_spec) + return; + + value = gimp_path_editor_get_path (editor); + utf8 = value ? gimp_config_path_unexpand (value, TRUE, NULL) : NULL; + g_free (value); + + g_signal_handlers_block_by_func (config, + gimp_prop_path_editor_path_notify, + editor); + + g_object_set (config, + path_param_spec->name, utf8, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_path_editor_path_notify, + editor); + + g_free (utf8); + + if (writable_param_spec) + { + value = gimp_path_editor_get_writable_path (editor); + utf8 = value ? gimp_config_path_unexpand (value, TRUE, NULL) : NULL; + g_free (value); + + g_signal_handlers_block_by_func (config, + gimp_prop_path_editor_writable_notify, + editor); + + g_object_set (config, + writable_param_spec->name, utf8, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_path_editor_writable_notify, + editor); + + g_free (utf8); + } +} + +static void +gimp_prop_path_editor_writable_callback (GimpPathEditor *editor, + GObject *config) +{ + GParamSpec *param_spec; + gchar *value; + gchar *utf8; + + param_spec = g_object_get_data (G_OBJECT (editor), + "gimp-config-param-spec-writable"); + if (! param_spec) + return; + + value = gimp_path_editor_get_writable_path (editor); + utf8 = value ? gimp_config_path_unexpand (value, TRUE, NULL) : NULL; + g_free (value); + + g_signal_handlers_block_by_func (config, + gimp_prop_path_editor_writable_notify, + editor); + + g_object_set (config, + param_spec->name, utf8, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_path_editor_writable_notify, + editor); + + g_free (utf8); +} + +static void +gimp_prop_path_editor_path_notify (GObject *config, + GParamSpec *param_spec, + GimpPathEditor *editor) +{ + gchar *value; + gchar *filename; + + g_object_get (config, + param_spec->name, &value, + NULL); + + filename = value ? gimp_config_path_expand (value, TRUE, NULL) : NULL; + g_free (value); + + g_signal_handlers_block_by_func (editor, + gimp_prop_path_editor_path_callback, + config); + + gimp_path_editor_set_path (editor, filename); + + g_signal_handlers_unblock_by_func (editor, + gimp_prop_path_editor_path_callback, + config); + + g_free (filename); +} + +static void +gimp_prop_path_editor_writable_notify (GObject *config, + GParamSpec *param_spec, + GimpPathEditor *editor) +{ + gchar *value; + gchar *filename; + + g_object_get (config, + param_spec->name, &value, + NULL); + + filename = value ? gimp_config_path_expand (value, TRUE, NULL) : NULL; + g_free (value); + + g_signal_handlers_block_by_func (editor, + gimp_prop_path_editor_writable_callback, + config); + + gimp_path_editor_set_writable_path (editor, filename); + + g_signal_handlers_unblock_by_func (editor, + gimp_prop_path_editor_writable_callback, + config); + + g_free (filename); +} + + +/***************/ +/* sizeentry */ +/***************/ + +static void gimp_prop_size_entry_callback (GimpSizeEntry *entry, + GObject *config); +static void gimp_prop_size_entry_notify (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry); +static void gimp_prop_size_entry_notify_unit (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry); +static gint gimp_prop_size_entry_num_chars (gdouble lower, + gdouble upper); + + +/** + * gimp_prop_size_entry_new: + * @config: Object to which property is attached. + * @property_name: Name of int or double property. + * @property_is_pixel: When %TRUE, the property value is in pixels, + * and in the selected unit otherwise. + * @unit_property_name: Name of unit property. + * @unit_format: A printf-like unit-format string as is used with + * gimp_unit_menu_new(). + * @update_policy: How the automatic pixel <-> real-world-unit + * calculations should be done. + * @resolution: The resolution (in dpi) for the field. + * + * Creates a #GimpSizeEntry to set and display the specified double or + * int property, and its associated unit property. Note that this + * function is only suitable for creating a size entry holding a + * single value. Use gimp_prop_coordinates_new() to create a size + * entry holding two values. + * + * Return value: A new #GimpSizeEntry widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_size_entry_new (GObject *config, + const gchar *property_name, + gboolean property_is_pixel, + const gchar *unit_property_name, + const gchar *unit_format, + GimpSizeEntryUpdatePolicy update_policy, + gdouble resolution) +{ + GtkWidget *entry; + GParamSpec *param_spec; + GParamSpec *unit_param_spec; + gboolean show_pixels; + gboolean show_percent; + gdouble value; + gdouble lower; + gdouble upper; + GimpUnit unit_value; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! get_numeric_values (config, + param_spec, &value, &lower, &upper, G_STRFUNC)) + return NULL; + + if (unit_property_name) + { + GValue value = G_VALUE_INIT; + + unit_param_spec = check_param_spec_w (config, unit_property_name, + GIMP_TYPE_PARAM_UNIT, G_STRFUNC); + if (! unit_param_spec) + return NULL; + + g_value_init (&value, unit_param_spec->value_type); + + g_value_set_int (&value, GIMP_UNIT_PIXEL); + show_pixels = (g_param_value_validate (unit_param_spec, + &value) == FALSE); + + g_value_set_int (&value, GIMP_UNIT_PERCENT); + show_percent = (g_param_value_validate (unit_param_spec, + &value) == FALSE); + + g_value_unset (&value); + + g_object_get (config, + unit_property_name, &unit_value, + NULL); + } + else + { + unit_param_spec = NULL; + unit_value = GIMP_UNIT_INCH; + show_pixels = FALSE; + show_percent = FALSE; + } + + entry = gimp_size_entry_new (1, unit_value, unit_format, + show_pixels, show_percent, FALSE, + gimp_prop_size_entry_num_chars (lower, upper) + 1 + + gimp_unit_get_scaled_digits (unit_value, resolution), + update_policy); + gtk_table_set_col_spacing (GTK_TABLE (entry), 1, 2); + + set_param_spec (NULL, + gimp_size_entry_get_help_widget (GIMP_SIZE_ENTRY (entry), 0), + param_spec); + + if (unit_param_spec) + set_param_spec (NULL, GIMP_SIZE_ENTRY (entry)->unitmenu, unit_param_spec); + + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (entry), unit_value); + + if (update_policy == GIMP_SIZE_ENTRY_UPDATE_SIZE) + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 0, + resolution, FALSE); + + gimp_size_entry_set_value_boundaries (GIMP_SIZE_ENTRY (entry), 0, + lower, upper); + + g_object_set_data (G_OBJECT (entry), "value-is-pixel", + GINT_TO_POINTER (property_is_pixel ? TRUE : FALSE)); + + if (property_is_pixel) + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (entry), 0, value); + else + gimp_size_entry_set_value (GIMP_SIZE_ENTRY (entry), 0, value); + + g_object_set_data (G_OBJECT (entry), "gimp-config-param-spec", + param_spec); + + g_signal_connect (entry, "refval-changed", + G_CALLBACK (gimp_prop_size_entry_callback), + config); + g_signal_connect (entry, "value-changed", + G_CALLBACK (gimp_prop_size_entry_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_size_entry_notify), + entry); + + if (unit_property_name) + { + g_object_set_data (G_OBJECT (entry), "gimp-config-param-spec-unit", + unit_param_spec); + + g_signal_connect (entry, "unit-changed", + G_CALLBACK (gimp_prop_size_entry_callback), + config); + + connect_notify (config, unit_property_name, + G_CALLBACK (gimp_prop_size_entry_notify_unit), + entry); + } + + return entry; +} + +static void +gimp_prop_size_entry_callback (GimpSizeEntry *entry, + GObject *config) +{ + GParamSpec *param_spec; + GParamSpec *unit_param_spec; + gdouble value; + gboolean value_is_pixel; + GimpUnit unit_value; + + param_spec = g_object_get_data (G_OBJECT (entry), "gimp-config-param-spec"); + if (! param_spec) + return; + + unit_param_spec = g_object_get_data (G_OBJECT (entry), + "gimp-config-param-spec-unit"); + + value_is_pixel = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (entry), + "value-is-pixel")); + + if (value_is_pixel) + value = gimp_size_entry_get_refval (entry, 0); + else + value = gimp_size_entry_get_value (entry, 0); + + unit_value = gimp_size_entry_get_unit (entry); + + if (unit_param_spec) + { + GimpUnit old_unit; + + g_object_get (config, + unit_param_spec->name, &old_unit, + NULL); + + if (unit_value == old_unit) + unit_param_spec = NULL; + } + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + g_object_set (config, + param_spec->name, ROUND (value), + + unit_param_spec ? + unit_param_spec->name : NULL, unit_value, + + NULL); + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + g_object_set (config, + param_spec->name, value, + + unit_param_spec ? + unit_param_spec->name : NULL, unit_value, + + NULL); + } +} + +static void +gimp_prop_size_entry_notify (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry) +{ + gdouble value; + gdouble entry_value; + gboolean value_is_pixel; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint int_value; + + g_object_get (config, + param_spec->name, &int_value, + NULL); + + value = int_value; + } + else + { + g_object_get (config, + param_spec->name, &value, + NULL); + } + + value_is_pixel = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (entry), + "value-is-pixel")); + + if (value_is_pixel) + entry_value = gimp_size_entry_get_refval (entry, 0); + else + entry_value = gimp_size_entry_get_value (entry, 0); + + if (value != entry_value) + { + g_signal_handlers_block_by_func (entry, + gimp_prop_size_entry_callback, + config); + + if (value_is_pixel) + gimp_size_entry_set_refval (entry, 0, value); + else + gimp_size_entry_set_value (entry, 0, value); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_size_entry_callback, + config); + } +} + +static void +gimp_prop_size_entry_notify_unit (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry) +{ + GimpUnit value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (value != gimp_size_entry_get_unit (entry)) + { + g_signal_handlers_block_by_func (entry, + gimp_prop_size_entry_callback, + config); + + gimp_size_entry_set_unit (entry, value); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_size_entry_callback, + config); + } +} + +static gint +gimp_prop_size_entry_num_chars (gdouble lower, + gdouble upper) +{ + gint lower_chars = log (fabs (lower)) / log (10); + gint upper_chars = log (fabs (upper)) / log (10); + + if (lower < 0.0) + lower_chars++; + + if (upper < 0.0) + upper_chars++; + + return MAX (lower_chars, upper_chars); +} + + +/*****************/ +/* coordinates */ +/*****************/ + +static void gimp_prop_coordinates_callback (GimpSizeEntry *entry, + GObject *config); +static void gimp_prop_coordinates_notify_x (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry); +static void gimp_prop_coordinates_notify_y (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry); +static void gimp_prop_coordinates_notify_unit (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry); + + +/** + * gimp_prop_coordinates_new: + * @config: Object to which property is attached. + * @x_property_name: Name of int or double property for X coordinate. + * @y_property_name: Name of int or double property for Y coordinate. + * @unit_property_name: Name of unit property. + * @unit_format: A printf-like unit-format string as is used with + * gimp_unit_menu_new(). + * @update_policy: How the automatic pixel <-> real-world-unit + * calculations should be done. + * @xresolution: The resolution (in dpi) for the X coordinate. + * @yresolution: The resolution (in dpi) for the Y coordinate. + * @has_chainbutton: Whether to add a chainbutton to the size entry. + * + * Creates a #GimpSizeEntry to set and display two double or int + * properties, which will usually represent X and Y coordinates, and + * their associated unit property. + * + * Return value: A new #GimpSizeEntry widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_coordinates_new (GObject *config, + const gchar *x_property_name, + const gchar *y_property_name, + const gchar *unit_property_name, + const gchar *unit_format, + GimpSizeEntryUpdatePolicy update_policy, + gdouble xresolution, + gdouble yresolution, + gboolean has_chainbutton) +{ + GtkWidget *entry; + GtkWidget *chainbutton = NULL; + + entry = gimp_size_entry_new (2, GIMP_UNIT_INCH, unit_format, + FALSE, FALSE, TRUE, 10, + update_policy); + + if (has_chainbutton) + { + chainbutton = gimp_chain_button_new (GIMP_CHAIN_BOTTOM); + gtk_table_attach_defaults (GTK_TABLE (entry), chainbutton, 1, 3, 3, 4); + gtk_widget_show (chainbutton); + } + + if (! gimp_prop_coordinates_connect (config, + x_property_name, + y_property_name, + unit_property_name, + entry, + chainbutton, + xresolution, + yresolution)) + { + gtk_widget_destroy (entry); + return NULL; + } + + return entry; +} + +gboolean +gimp_prop_coordinates_connect (GObject *config, + const gchar *x_property_name, + const gchar *y_property_name, + const gchar *unit_property_name, + GtkWidget *entry, + GtkWidget *chainbutton, + gdouble xresolution, + gdouble yresolution) +{ + GParamSpec *x_param_spec; + GParamSpec *y_param_spec; + GParamSpec *unit_param_spec; + gdouble x_value, x_lower, x_upper; + gdouble y_value, y_lower, y_upper; + GimpUnit unit_value; + gdouble *old_x_value; + gdouble *old_y_value; + GimpUnit *old_unit_value; + gboolean chain_checked; + + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (entry), FALSE); + g_return_val_if_fail (GIMP_SIZE_ENTRY (entry)->number_of_fields == 2, FALSE); + g_return_val_if_fail (chainbutton == NULL || + GIMP_IS_CHAIN_BUTTON (chainbutton), FALSE); + + x_param_spec = find_param_spec (config, x_property_name, G_STRFUNC); + if (! x_param_spec) + return FALSE; + + y_param_spec = find_param_spec (config, y_property_name, G_STRFUNC); + if (! y_param_spec) + return FALSE; + + if (! get_numeric_values (config, x_param_spec, + &x_value, &x_lower, &x_upper, G_STRFUNC) || + ! get_numeric_values (config, y_param_spec, + &y_value, &y_lower, &y_upper, G_STRFUNC)) + return FALSE; + + if (unit_property_name) + { + unit_param_spec = check_param_spec_w (config, unit_property_name, + GIMP_TYPE_PARAM_UNIT, G_STRFUNC); + if (! unit_param_spec) + return FALSE; + + g_object_get (config, + unit_property_name, &unit_value, + NULL); + } + else + { + unit_param_spec = NULL; + unit_value = GIMP_UNIT_INCH; + } + + set_param_spec (NULL, + gimp_size_entry_get_help_widget (GIMP_SIZE_ENTRY (entry), 0), + x_param_spec); + set_param_spec (NULL, + gimp_size_entry_get_help_widget (GIMP_SIZE_ENTRY (entry), 1), + y_param_spec); + + if (unit_param_spec) + set_param_spec (NULL, + GIMP_SIZE_ENTRY (entry)->unitmenu, unit_param_spec); + + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (entry), unit_value); + + switch (GIMP_SIZE_ENTRY (entry)->update_policy) + { + case GIMP_SIZE_ENTRY_UPDATE_SIZE: + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 0, + xresolution, FALSE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (entry), 1, + yresolution, FALSE); + chain_checked = (ABS (x_value - y_value) < 1); + break; + + case GIMP_SIZE_ENTRY_UPDATE_RESOLUTION: + chain_checked = (ABS (x_value - y_value) < GIMP_MIN_RESOLUTION); + break; + + default: + chain_checked = (x_value == y_value); + break; + } + + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (entry), 0, + x_lower, x_upper); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (entry), 1, + y_lower, y_upper); + + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (entry), 0, x_value); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (entry), 1, y_value); + + g_object_set_data (G_OBJECT (entry), "gimp-config-param-spec-x", + x_param_spec); + g_object_set_data (G_OBJECT (entry), "gimp-config-param-spec-y", + y_param_spec); + + old_x_value = g_new0 (gdouble, 1); + *old_x_value = x_value; + g_object_set_data_full (G_OBJECT (entry), "old-x-value", + old_x_value, + (GDestroyNotify) g_free); + + old_y_value = g_new0 (gdouble, 1); + *old_y_value = y_value; + g_object_set_data_full (G_OBJECT (entry), "old-y-value", + old_y_value, + (GDestroyNotify) g_free); + + if (chainbutton) + { + if (chain_checked) + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chainbutton), TRUE); + + g_object_set_data (G_OBJECT (entry), "chainbutton", chainbutton); + } + + g_signal_connect (entry, "value-changed", + G_CALLBACK (gimp_prop_coordinates_callback), + config); + g_signal_connect (entry, "refval-changed", + G_CALLBACK (gimp_prop_coordinates_callback), + config); + + connect_notify (config, x_property_name, + G_CALLBACK (gimp_prop_coordinates_notify_x), + entry); + connect_notify (config, y_property_name, + G_CALLBACK (gimp_prop_coordinates_notify_y), + entry); + + if (unit_property_name) + { + g_object_set_data (G_OBJECT (entry), "gimp-config-param-spec-unit", + unit_param_spec); + + old_unit_value = g_new0 (GimpUnit, 1); + *old_unit_value = unit_value; + g_object_set_data_full (G_OBJECT (entry), "old-unit-value", + old_unit_value, + (GDestroyNotify) g_free); + + g_signal_connect (entry, "unit-changed", + G_CALLBACK (gimp_prop_coordinates_callback), + config); + + connect_notify (config, unit_property_name, + G_CALLBACK (gimp_prop_coordinates_notify_unit), + entry); + } + + return TRUE; +} + +static void +gimp_prop_coordinates_callback (GimpSizeEntry *entry, + GObject *config) +{ + GParamSpec *x_param_spec; + GParamSpec *y_param_spec; + GParamSpec *unit_param_spec; + gdouble x_value; + gdouble y_value; + GimpUnit unit_value; + gdouble *old_x_value; + gdouble *old_y_value; + GimpUnit *old_unit_value; + gboolean backwards; + + x_param_spec = g_object_get_data (G_OBJECT (entry), + "gimp-config-param-spec-x"); + y_param_spec = g_object_get_data (G_OBJECT (entry), + "gimp-config-param-spec-y"); + + if (! x_param_spec || ! y_param_spec) + return; + + unit_param_spec = g_object_get_data (G_OBJECT (entry), + "gimp-config-param-spec-unit"); + + x_value = gimp_size_entry_get_refval (entry, 0); + y_value = gimp_size_entry_get_refval (entry, 1); + unit_value = gimp_size_entry_get_unit (entry); + + old_x_value = g_object_get_data (G_OBJECT (entry), "old-x-value"); + old_y_value = g_object_get_data (G_OBJECT (entry), "old-y-value"); + old_unit_value = g_object_get_data (G_OBJECT (entry), "old-unit-value"); + + if (! old_x_value || ! old_y_value || (unit_param_spec && ! old_unit_value)) + return; + + /* + * FIXME: if the entry was created using gimp_coordinates_new, then + * the chain button is handled automatically and the following block + * of code is unnecessary (and, in fact, redundant). + */ + if (x_value != y_value) + { + GtkWidget *chainbutton; + + chainbutton = g_object_get_data (G_OBJECT (entry), "chainbutton"); + + if (chainbutton && + gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chainbutton)) && + ! g_object_get_data (G_OBJECT (chainbutton), "constrains-ratio")) + { + if (x_value != *old_x_value) + y_value = x_value; + else if (y_value != *old_y_value) + x_value = y_value; + } + } + + backwards = (*old_x_value == x_value); + + if (*old_x_value == x_value && + *old_y_value == y_value && + (old_unit_value == NULL || *old_unit_value == unit_value)) + return; + + *old_x_value = x_value; + *old_y_value = y_value; + + if (old_unit_value) + *old_unit_value = unit_value; + + if (unit_param_spec) + g_object_set (config, + unit_param_spec->name, unit_value, + NULL); + + if (G_IS_PARAM_SPEC_INT (x_param_spec) && + G_IS_PARAM_SPEC_INT (y_param_spec)) + { + if (backwards) + g_object_set (config, + y_param_spec->name, ROUND (y_value), + x_param_spec->name, ROUND (x_value), + NULL); + else + g_object_set (config, + x_param_spec->name, ROUND (x_value), + y_param_spec->name, ROUND (y_value), + NULL); + + } + else if (G_IS_PARAM_SPEC_DOUBLE (x_param_spec) && + G_IS_PARAM_SPEC_DOUBLE (y_param_spec)) + { + if (backwards) + g_object_set (config, + y_param_spec->name, y_value, + x_param_spec->name, x_value, + NULL); + else + g_object_set (config, + x_param_spec->name, x_value, + y_param_spec->name, y_value, + NULL); + } +} + +static void +gimp_prop_coordinates_notify_x (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry) +{ + gdouble value; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint int_value; + + g_object_get (config, + param_spec->name, &int_value, + NULL); + + value = int_value; + } + else + { + g_object_get (config, + param_spec->name, &value, + NULL); + } + + if (value != gimp_size_entry_get_refval (entry, 0)) + { + gdouble *old_x_value = g_object_get_data (G_OBJECT (entry), + "old-x-value"); + + g_signal_handlers_block_by_func (entry, + gimp_prop_coordinates_callback, + config); + + gimp_size_entry_set_refval (entry, 0, value); + + if (old_x_value) + *old_x_value = value; + + g_signal_emit_by_name (entry, "value-changed", + gimp_size_entry_get_value (entry, 0)); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_coordinates_callback, + config); + } +} + +static void +gimp_prop_coordinates_notify_y (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry) +{ + gdouble value; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint int_value; + + g_object_get (config, + param_spec->name, &int_value, + NULL); + + value = int_value; + } + else + { + g_object_get (config, + param_spec->name, &value, + NULL); + } + + if (value != gimp_size_entry_get_refval (entry, 1)) + { + gdouble *old_y_value = g_object_get_data (G_OBJECT (entry), + "old-y-value"); + + g_signal_handlers_block_by_func (entry, + gimp_prop_coordinates_callback, + config); + + gimp_size_entry_set_refval (entry, 1, value); + + if (old_y_value) + *old_y_value = value; + + g_signal_emit_by_name (entry, "value-changed", + gimp_size_entry_get_value (entry, 1)); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_coordinates_callback, + config); + } +} + +static void +gimp_prop_coordinates_notify_unit (GObject *config, + GParamSpec *param_spec, + GimpSizeEntry *entry) +{ + GimpUnit value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (value != gimp_size_entry_get_unit (entry)) + { + g_signal_handlers_block_by_func (entry, + gimp_prop_coordinates_callback, + config); + + gimp_size_entry_set_unit (entry, value); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_coordinates_callback, + config); + } +} + + +/****************/ +/* color area */ +/****************/ + +static void gimp_prop_color_area_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_color_area_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *area); + +/** + * gimp_prop_color_area_new: + * @config: Object to which property is attached. + * @property_name: Name of RGB property. + * @width: Width of color area. + * @height: Height of color area. + * @type: How transparency is represented. + * + * Creates a #GimpColorArea to set and display the value of an RGB + * property. + * + * Return value: A new #GimpColorArea widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_color_area_new (GObject *config, + const gchar *property_name, + gint width, + gint height, + GimpColorAreaType type) +{ + GParamSpec *param_spec; + GtkWidget *area; + GimpRGB *value; + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_RGB, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + area = gimp_color_area_new (value, type, + GDK_BUTTON1_MASK | GDK_BUTTON2_MASK); + gtk_widget_set_size_request (area, width, height); + + g_free (value); + + set_param_spec (G_OBJECT (area), area, param_spec); + + g_signal_connect (area, "color-changed", + G_CALLBACK (gimp_prop_color_area_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_color_area_notify), + area); + + return area; +} + +static void +gimp_prop_color_area_callback (GtkWidget *area, + GObject *config) +{ + GParamSpec *param_spec; + GimpRGB value; + + param_spec = get_param_spec (G_OBJECT (area)); + if (! param_spec) + return; + + gimp_color_area_get_color (GIMP_COLOR_AREA (area), &value); + + g_signal_handlers_block_by_func (config, + gimp_prop_color_area_notify, + area); + + g_object_set (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_color_area_notify, + area); +} + +static void +gimp_prop_color_area_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *area) +{ + GimpRGB *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (area, + gimp_prop_color_area_callback, + config); + + gimp_color_area_set_color (GIMP_COLOR_AREA (area), value); + + g_free (value); + + g_signal_handlers_unblock_by_func (area, + gimp_prop_color_area_callback, + config); +} + + +/********************/ +/* unit combo box */ +/********************/ + +static void gimp_prop_unit_combo_box_callback (GtkWidget *combo, + GObject *config); +static void gimp_prop_unit_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo); + +/** + * gimp_prop_unit_combo_box_new: + * @config: Object to which property is attached. + * @property_name: Name of Unit property. + * + * Creates a #GimpUnitComboBox to set and display the value of a Unit + * property. See gimp_unit_combo_box_new() for more information. + * + * Return value: A new #GimpUnitComboBox widget. + * + * Since: 2.8 + */ +GtkWidget * +gimp_prop_unit_combo_box_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *combo; + GtkTreeModel *model; + GimpUnit unit; + GValue value = G_VALUE_INIT; + gboolean show_pixels; + gboolean show_percent; + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_UNIT, G_STRFUNC); + if (! param_spec) + return NULL; + + g_value_init (&value, param_spec->value_type); + + g_value_set_int (&value, GIMP_UNIT_PIXEL); + show_pixels = (g_param_value_validate (param_spec, &value) == FALSE); + + g_value_set_int (&value, GIMP_UNIT_PERCENT); + show_percent = (g_param_value_validate (param_spec, &value) == FALSE); + + g_value_unset (&value); + + g_object_get (config, + property_name, &unit, + NULL); + + combo = gimp_unit_combo_box_new (); + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gimp_unit_store_set_has_pixels (GIMP_UNIT_STORE (model), show_pixels); + gimp_unit_store_set_has_percent (GIMP_UNIT_STORE (model), show_percent); + + gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (combo), unit); + + set_param_spec (G_OBJECT (combo), combo, param_spec); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_prop_unit_combo_box_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_unit_combo_box_notify), + combo); + + return combo; +} + +static void +gimp_prop_unit_combo_box_callback (GtkWidget *combo, + GObject *config) +{ + GParamSpec *param_spec; + GimpUnit value; + GimpUnit v; + + param_spec = get_param_spec (G_OBJECT (combo)); + if (! param_spec) + return; + + value = gimp_unit_combo_box_get_active (GIMP_UNIT_COMBO_BOX (combo)); + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + { + /* FIXME gimp_unit_menu_update (menu, &unit); */ + + g_signal_handlers_block_by_func (config, + gimp_prop_unit_combo_box_notify, + combo); + + g_object_set (config, param_spec->name, value, NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_unit_combo_box_notify, + combo); + } +} + +static void +gimp_prop_unit_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo) +{ + GimpUnit unit; + + g_object_get (config, + param_spec->name, &unit, + NULL); + + g_signal_handlers_block_by_func (combo, + gimp_prop_unit_combo_box_callback, + config); + + gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (combo), unit); + + /* FIXME gimp_unit_menu_update (menu, &unit); */ + + g_signal_handlers_unblock_by_func (combo, + gimp_prop_unit_combo_box_callback, + config); +} + + +/***************/ +/* unit menu */ +/***************/ + +static void gimp_prop_unit_menu_callback (GtkWidget *menu, + GObject *config); +static void gimp_prop_unit_menu_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *menu); + +/** + * gimp_prop_unit_menu_new: + * @config: Object to which property is attached. + * @property_name: Name of Unit property. + * @unit_format: A printf-like format string which is used to create + * the unit strings. + * + * Creates a #GimpUnitMenu to set and display the value of a Unit + * property. See gimp_unit_menu_new() for more information. + * + * Return value: A new #GimpUnitMenu widget. + * + * Since: 2.4 + * + * Deprecated: 2.10 + */ +GtkWidget * +gimp_prop_unit_menu_new (GObject *config, + const gchar *property_name, + const gchar *unit_format) +{ + GParamSpec *param_spec; + GtkWidget *menu; + GimpUnit unit; + GValue value = G_VALUE_INIT; + gboolean show_pixels; + gboolean show_percent; + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_UNIT, G_STRFUNC); + if (! param_spec) + return NULL; + + g_value_init (&value, param_spec->value_type); + + g_value_set_int (&value, GIMP_UNIT_PIXEL); + show_pixels = (g_param_value_validate (param_spec, &value) == FALSE); + + g_value_set_int (&value, GIMP_UNIT_PERCENT); + show_percent = (g_param_value_validate (param_spec, &value) == FALSE); + + g_value_unset (&value); + + g_object_get (config, + property_name, &unit, + NULL); + + menu = gimp_unit_menu_new (unit_format, + unit, show_pixels, show_percent, TRUE); + + set_param_spec (G_OBJECT (menu), menu, param_spec); + + g_signal_connect (menu, "unit-changed", + G_CALLBACK (gimp_prop_unit_menu_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_unit_menu_notify), + menu); + + return menu; +} + +static void +gimp_prop_unit_menu_callback (GtkWidget *menu, + GObject *config) +{ + GParamSpec *param_spec; + GimpUnit unit; + + param_spec = get_param_spec (G_OBJECT (menu)); + if (! param_spec) + return; + + gimp_unit_menu_update (menu, &unit); + + g_signal_handlers_block_by_func (config, + gimp_prop_unit_menu_notify, + menu); + + g_object_set (config, + param_spec->name, unit, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_unit_menu_notify, + menu); +} + +static void +gimp_prop_unit_menu_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *menu) +{ + GimpUnit unit; + + g_object_get (config, + param_spec->name, &unit, + NULL); + + g_signal_handlers_block_by_func (menu, + gimp_prop_unit_menu_callback, + config); + + gimp_unit_menu_set_unit (GIMP_UNIT_MENU (menu), unit); + gimp_unit_menu_update (menu, &unit); + + g_signal_handlers_unblock_by_func (menu, + gimp_prop_unit_menu_callback, + config); +} + + +/***************/ +/* icon name */ +/***************/ + +static void gimp_prop_icon_image_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *image); + +/** + * gimp_prop_stock_image_new: + * @config: Object to which property is attached. + * @property_name: Name of string property. + * @icon_size: Size of desired stock image. + * + * Creates a widget to display a stock image representing the value of the + * specified string property, which should encode a Stock ID. + * See gtk_image_new_from_stock() for more information. + * + * Return value: A new #GtkImage widget. + * + * Since: 2.4 + * + * Deprecated: 2.10 + */ +GtkWidget * +gimp_prop_stock_image_new (GObject *config, + const gchar *property_name, + GtkIconSize icon_size) +{ + return gimp_prop_icon_image_new (config, property_name, icon_size); +} + +/** + * gimp_prop_icon_image_new: + * @config: Object to which property is attached. + * @property_name: Name of string property. + * @icon_size: Size of desired icon image. + * + * Creates a widget to display a icon image representing the value of the + * specified string property, which should encode an icon name. + * See gtk_image_new_from_icon_name() for more information. + * + * Return value: A new #GtkImage widget. + * + * Since: 2.10 + */ +GtkWidget * +gimp_prop_icon_image_new (GObject *config, + const gchar *property_name, + GtkIconSize icon_size) +{ + GParamSpec *param_spec; + GtkWidget *image; + gchar *icon_name; + + param_spec = check_param_spec (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &icon_name, + NULL); + + image = gtk_image_new_from_icon_name (icon_name, icon_size); + + if (icon_name) + g_free (icon_name); + + set_param_spec (G_OBJECT (image), image, param_spec); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_icon_image_notify), + image); + + return image; +} + +static void +gimp_prop_icon_image_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *image) +{ + gchar *icon_name; + GtkIconSize icon_size; + + g_object_get (config, + param_spec->name, &icon_name, + NULL); + + gtk_image_get_icon_name (GTK_IMAGE (image), NULL, &icon_size); + gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, icon_size); + + if (icon_name) + g_free (icon_name); +} + + +/**************/ +/* expander */ +/**************/ + +static void gimp_prop_expanded_notify (GtkExpander *expander, + GParamSpec *param_spec, + GObject *config); +static void gimp_prop_expander_notify (GObject *config, + GParamSpec *param_spec, + GtkExpander *expander); + + +/** + * gimp_prop_expander_new: + * @config: Object to which property is attached. + * @property_name: Name of boolean property. + * @label: Label for expander. + * + * Creates a #GtkExpander controlled by the specified boolean property. + * A value of %TRUE for the property corresponds to the expanded state + * for the widget. + * If @label is #NULL, the @property_name's nick will be used as label + * of the returned widget. + * + * Return value: A new #GtkExpander widget. + * + * Since: 2.4 + */ +GtkWidget * +gimp_prop_expander_new (GObject *config, + const gchar *property_name, + const gchar *label) +{ + GParamSpec *param_spec; + GtkWidget *expander; + gboolean value; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_BOOLEAN, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! label) + label = g_param_spec_get_nick (param_spec); + + g_object_get (config, + property_name, &value, + NULL); + + expander = g_object_new (GTK_TYPE_EXPANDER, + "label", label, + "expanded", value, + NULL); + + set_param_spec (G_OBJECT (expander), expander, param_spec); + + g_signal_connect (expander, "notify::expanded", + G_CALLBACK (gimp_prop_expanded_notify), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_expander_notify), + expander); + + return expander; +} + +static void +gimp_prop_expanded_notify (GtkExpander *expander, + GParamSpec *param_spec, + GObject *config) +{ + param_spec = get_param_spec (G_OBJECT (expander)); + if (! param_spec) + return; + + g_object_set (config, + param_spec->name, gtk_expander_get_expanded (expander), + NULL); +} + +static void +gimp_prop_expander_notify (GObject *config, + GParamSpec *param_spec, + GtkExpander *expander) +{ + gboolean value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + if (gtk_expander_get_expanded (expander) != value) + { + g_signal_handlers_block_by_func (expander, + gimp_prop_expanded_notify, + config); + + gtk_expander_set_expanded (expander, value); + + g_signal_handlers_unblock_by_func (expander, + gimp_prop_expanded_notify, + config); + } +} + + +/*******************************/ +/* private utility functions */ +/*******************************/ + +static GQuark gimp_prop_widgets_param_spec_quark (void) G_GNUC_CONST; + +#define PARAM_SPEC_QUARK (gimp_prop_widgets_param_spec_quark ()) + +static GQuark +gimp_prop_widgets_param_spec_quark (void) +{ + static GQuark param_spec_quark = 0; + + if (! param_spec_quark) + param_spec_quark = g_quark_from_static_string ("gimp-config-param-spec"); + + return param_spec_quark; +} + +static void +set_param_spec (GObject *object, + GtkWidget *widget, + GParamSpec *param_spec) +{ + if (object) + { + g_object_set_qdata (object, PARAM_SPEC_QUARK, param_spec); + } + + if (widget) + { + const gchar *blurb = g_param_spec_get_blurb (param_spec); + + if (blurb) + gimp_help_set_help_data (widget, blurb, NULL); + } +} + +static void +set_radio_spec (GObject *object, + GParamSpec *param_spec) +{ + GSList *list; + + for (list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (object)); + list; + list = g_slist_next (list)) + { + set_param_spec (list->data, NULL, param_spec); + } +} + +static GParamSpec * +get_param_spec (GObject *object) +{ + return g_object_get_qdata (object, PARAM_SPEC_QUARK); +} + +static GParamSpec * +find_param_spec (GObject *object, + const gchar *property_name, + const gchar *strloc) +{ + GParamSpec *param_spec; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), + property_name); + + if (! param_spec) + g_warning ("%s: %s has no property named '%s'", + strloc, + g_type_name (G_TYPE_FROM_INSTANCE (object)), + property_name); + + return param_spec; +} + +static GParamSpec * +check_param_spec (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc) +{ + GParamSpec *param_spec; + + param_spec = find_param_spec (object, property_name, strloc); + + if (param_spec && ! g_type_is_a (G_TYPE_FROM_INSTANCE (param_spec), type)) + { + g_warning ("%s: property '%s' of %s is not a %s", + strloc, + param_spec->name, + g_type_name (param_spec->owner_type), + g_type_name (type)); + return NULL; + } + + return param_spec; +} + +static GParamSpec * +check_param_spec_w (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc) +{ + GParamSpec *param_spec; + + param_spec = check_param_spec (object, property_name, type, strloc); + + if (param_spec && + (param_spec->flags & G_PARAM_WRITABLE) == 0) + { + g_warning ("%s: property '%s' of %s is not writable", + strloc, + param_spec->name, + g_type_name (param_spec->owner_type)); + return NULL; + } + + return param_spec; +} + +static gboolean +get_numeric_values (GObject *object, + GParamSpec *param_spec, + gdouble *value, + gdouble *lower, + gdouble *upper, + const gchar *strloc) +{ + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + GParamSpecInt *int_spec = G_PARAM_SPEC_INT (param_spec); + gint int_value; + + g_object_get (object, param_spec->name, &int_value, NULL); + + *value = int_value; + *lower = int_spec->minimum; + *upper = int_spec->maximum; + } + else if (G_IS_PARAM_SPEC_UINT (param_spec)) + { + GParamSpecUInt *uint_spec = G_PARAM_SPEC_UINT (param_spec); + guint uint_value; + + g_object_get (object, param_spec->name, &uint_value, NULL); + + *value = uint_value; + *lower = uint_spec->minimum; + *upper = uint_spec->maximum; + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + GParamSpecDouble *double_spec = G_PARAM_SPEC_DOUBLE (param_spec); + + g_object_get (object, param_spec->name, value, NULL); + + *lower = double_spec->minimum; + *upper = double_spec->maximum; + } + else + { + g_warning ("%s: property '%s' of %s is not numeric", + strloc, + param_spec->name, + g_type_name (G_TYPE_FROM_INSTANCE (object))); + return FALSE; + } + + return TRUE; +} + +static void +connect_notify (GObject *config, + const gchar *property_name, + GCallback callback, + gpointer callback_data) +{ + gchar *notify_name; + + notify_name = g_strconcat ("notify::", property_name, NULL); + + g_signal_connect_object (config, notify_name, callback, callback_data, 0); + + g_free (notify_name); +} diff --git a/libgimpwidgets/gimppropwidgets.h b/libgimpwidgets/gimppropwidgets.h new file mode 100644 index 0000000..c8702a5 --- /dev/null +++ b/libgimpwidgets/gimppropwidgets.h @@ -0,0 +1,243 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimppropwidgets.h + * Copyright (C) 2002 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_PROP_WIDGETS_H__ +#define __GIMP_PROP_WIDGETS_H__ + +G_BEGIN_DECLS + + +/* GParamBoolean */ + +GtkWidget * gimp_prop_check_button_new (GObject *config, + const gchar *property_name, + const gchar *label); +GtkWidget * gimp_prop_boolean_combo_box_new (GObject *config, + const gchar *property_name, + const gchar *true_text, + const gchar *false_text); +GtkWidget * gimp_prop_boolean_radio_frame_new (GObject *config, + const gchar *property_name, + const gchar *title, + const gchar *true_text, + const gchar *false_text); + +GtkWidget * gimp_prop_expander_new (GObject *config, + const gchar *property_name, + const gchar *label); + + +/* GParamInt */ + +GtkWidget * gimp_prop_int_combo_box_new (GObject *config, + const gchar *property_name, + GimpIntStore *store); + +/* GParamGType */ + +GtkWidget * gimp_prop_pointer_combo_box_new (GObject *config, + const gchar *property_name, + GimpIntStore *store); + +/* GParamEnum */ + +GtkWidget * gimp_prop_enum_combo_box_new (GObject *config, + const gchar *property_name, + gint minimum, + gint maximum); + +GtkWidget * gimp_prop_enum_check_button_new (GObject *config, + const gchar *property_name, + const gchar *label, + gint false_value, + gint true_value); + +GtkWidget * gimp_prop_enum_radio_frame_new (GObject *config, + const gchar *property_name, + const gchar *title, + gint minimum, + gint maximum); +GtkWidget * gimp_prop_enum_radio_box_new (GObject *config, + const gchar *property_name, + gint minimum, + gint maximum); + +GIMP_DEPRECATED_FOR(gimp_prop_enum_icon_box_new) +GtkWidget * gimp_prop_enum_stock_box_new (GObject *config, + const gchar *property_name, + const gchar *stock_prefix, + gint minimum, + gint maximum); + +GtkWidget * gimp_prop_enum_icon_box_new (GObject *config, + const gchar *property_name, + const gchar *icon_prefix, + gint minimum, + gint maximum); + +GtkWidget * gimp_prop_enum_label_new (GObject *config, + const gchar *property_name); + + +/* GParamInt, GParamUInt, GParamLong, GParamULong, GParamDouble */ + +GtkWidget * gimp_prop_spin_button_new (GObject *config, + const gchar *property_name, + gdouble step_increment, + gdouble page_increment, + gint digits); + +GtkWidget * gimp_prop_hscale_new (GObject *config, + const gchar *property_name, + gdouble step_increment, + gdouble page_increment, + gint digits); + +GtkObject * gimp_prop_scale_entry_new (GObject *config, + const gchar *property_name, + GtkTable *table, + gint column, + gint row, + const gchar *label, + gdouble step_increment, + gdouble page_increment, + gint digits, + gboolean limit_scale, + gdouble lower_limit, + gdouble upper_limit); + +/* special form of gimp_prop_scale_entry_new() for GParamDouble */ + +GtkObject * gimp_prop_opacity_entry_new (GObject *config, + const gchar *property_name, + GtkTable *table, + gint column, + gint row, + const gchar *label); + + +/* GimpParamMemsize */ + +GtkWidget * gimp_prop_memsize_entry_new (GObject *config, + const gchar *property_name); + + +/* GParamString */ + +GtkWidget * gimp_prop_label_new (GObject *config, + const gchar *property_name); +GtkWidget * gimp_prop_entry_new (GObject *config, + const gchar *property_name, + gint max_len); +GtkTextBuffer * gimp_prop_text_buffer_new (GObject *config, + const gchar *property_name, + gint max_len); +GtkWidget * gimp_prop_string_combo_box_new (GObject *config, + const gchar *property_name, + GtkTreeModel *model, + gint id_column, + gint label_column); + + +/* GimpParamPath */ + +GtkWidget * gimp_prop_file_chooser_button_new (GObject *config, + const gchar *property_name, + const gchar *title, + GtkFileChooserAction action); +GtkWidget * gimp_prop_file_chooser_button_new_with_dialog (GObject *config, + const gchar *property_name, + + GtkWidget *dialog); +GtkWidget * gimp_prop_path_editor_new (GObject *config, + const gchar *path_property_name, + const gchar *writable_property_name, + const gchar *filesel_title); + + +/* GParamInt, GParamUInt, GParamDouble unit: GimpParamUnit */ + +GtkWidget * gimp_prop_size_entry_new (GObject *config, + const gchar *property_name, + gboolean property_is_pixel, + const gchar *unit_property_name, + const gchar *unit_format, + GimpSizeEntryUpdatePolicy update_policy, + gdouble resolution); + + +/* x,y: GParamInt, GParamDouble unit: GimpParamUnit */ + +GtkWidget * gimp_prop_coordinates_new (GObject *config, + const gchar *x_property_name, + const gchar *y_property_name, + const gchar *unit_property_name, + const gchar *unit_format, + GimpSizeEntryUpdatePolicy update_policy, + gdouble xresolution, + gdouble yresolution, + gboolean has_chainbutton); +gboolean gimp_prop_coordinates_connect (GObject *config, + const gchar *x_property_name, + const gchar *y_property_name, + const gchar *unit_property_name, + GtkWidget *sizeentry, + GtkWidget *chainbutton, + gdouble xresolution, + gdouble yresolution); + + +/* GimpParamColor */ + +GtkWidget * gimp_prop_color_area_new (GObject *config, + const gchar *property_name, + gint width, + gint height, + GimpColorAreaType type); + +/* GimpParamUnit */ + +GtkWidget * gimp_prop_unit_combo_box_new (GObject *config, + const gchar *property_name); +GIMP_DEPRECATED_FOR(gimp_prop_unit_combo_box_new) +GtkWidget * gimp_prop_unit_menu_new (GObject *config, + const gchar *property_name, + const gchar *unit_format); + + +/* GParamString (icon name) */ + +GIMP_DEPRECATED_FOR(gimp_prop_stock_image_new) +GtkWidget * gimp_prop_stock_image_new (GObject *config, + const gchar *property_name, + GtkIconSize icon_size); +GtkWidget * gimp_prop_icon_image_new (GObject *config, + const gchar *property_name, + GtkIconSize icon_size); + + +G_END_DECLS + +#endif /* __GIMP_PROP_WIDGETS_H__ */ diff --git a/libgimpwidgets/gimpquerybox.c b/libgimpwidgets/gimpquerybox.c new file mode 100644 index 0000000..f26e609 --- /dev/null +++ b/libgimpwidgets/gimpquerybox.c @@ -0,0 +1,699 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpquerybox.c + * Copyright (C) 1999-2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpdialog.h" +#include "gimppixmap.h" +#include "gimpquerybox.h" +#include "gimpsizeentry.h" +#include "gimpspinbutton.h" +#include "gimpwidgets.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpquerybox + * @title: GimpQueryBox + * @short_description: Some simple dialogs to enter a single int, + * double, string or boolean value. + * @see_also: #GimpSizeEntry, #GimpUnitMenu + * + * These functions provide simple dialogs for entering a single + * string, integer, double, boolean or pixel size value. + * + * They return a pointer to a #GtkDialog which has to be shown with + * gtk_widget_show() by the caller. + * + * The dialogs contain an entry widget for the kind of value they ask + * for and "OK" and "Cancel" buttons. On "Cancel", all query boxes + * except the boolean one silently destroy themselves. On "OK" the + * user defined callback function is called and returns the entered + * value. + **/ + + +/* + * String, integer, double and size query boxes + */ + +typedef struct _QueryBox QueryBox; + +struct _QueryBox +{ + GtkWidget *qbox; + GtkWidget *vbox; + GtkWidget *entry; + GObject *object; + gulong response_handler; + GCallback callback; + gpointer callback_data; +}; + + +static QueryBox * create_query_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + GCallback response_callback, + const gchar *icon_name, + const gchar *message, + const gchar *ok_button, + const gchar *cancel_button, + GObject *object, + const gchar *signal, + GCallback callback, + gpointer callback_data); + +static void query_box_disconnect (QueryBox *query_box); +static void query_box_destroy (QueryBox *query_box); + +static void string_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box); +static void int_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box); +static void double_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box); +static void size_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box); +static void boolean_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box); + +static void query_box_cancel_callback (QueryBox *query_box); + + +/* + * create a generic query box without any entry widget + */ +static QueryBox * +create_query_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + GCallback response_callback, + const gchar *icon_name, + const gchar *message, + const gchar *ok_button, + const gchar *cancel_button, + GObject *object, + const gchar *signal, + GCallback callback, + gpointer callback_data) +{ + QueryBox *query_box; + GtkWidget *hbox = NULL; + GtkWidget *label; + + /* make sure the object / signal passed are valid + */ + g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL); + g_return_val_if_fail (object == NULL || G_IS_OBJECT (object), NULL); + g_return_val_if_fail (object == NULL || signal != NULL, NULL); + + query_box = g_slice_new0 (QueryBox); + + query_box->qbox = gimp_dialog_new (title, "gimp-query-box", + parent, 0, + help_func, help_id, + + cancel_button, GTK_RESPONSE_CANCEL, + ok_button, GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (query_box->qbox), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + query_box->response_handler = + g_signal_connect (query_box->qbox, "response", + G_CALLBACK (response_callback), + query_box); + + g_signal_connect (query_box->qbox, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &query_box->qbox); + + /* if we are associated with an object, connect to the provided signal + */ + if (object) + { + GClosure *closure; + + closure = g_cclosure_new_swap (G_CALLBACK (query_box_cancel_callback), + query_box, NULL); + g_object_watch_closure (G_OBJECT (query_box->qbox), closure); + + g_signal_connect_closure (object, signal, closure, FALSE); + } + + if (icon_name) + { + GtkWidget *content_area; + GtkWidget *image; + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (query_box->qbox)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + } + + query_box->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + + g_object_set_data (G_OBJECT (query_box->qbox), "gimp-query-box-vbox", + query_box->vbox); + + if (hbox) + { + gtk_box_pack_start (GTK_BOX (hbox), query_box->vbox, FALSE, FALSE, 0); + } + else + { + GtkWidget *content_area; + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (query_box->qbox)); + + gtk_container_set_border_width (GTK_CONTAINER (query_box->vbox), 12); + gtk_box_pack_start (GTK_BOX (content_area), query_box->vbox, + TRUE, TRUE, 0); + } + + gtk_widget_show (query_box->vbox); + + if (message) + { + label = gtk_label_new (message); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (query_box->vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + } + + query_box->entry = NULL; + query_box->object = object; + query_box->callback = callback; + query_box->callback_data = callback_data; + + return query_box; +} + +/** + * gimp_query_string_box: + * @title: The query box dialog's title. + * @parent: The dialog's parent widget. + * @help_func: The help function to show this dialog's help page. + * @help_id: A string identifying this dialog's help page. + * @message: A string which will be shown above the dialog's entry widget. + * @initial: The initial value. + * @object: The object this query box is associated with. + * @signal: The object's signal which will cause the query box to be closed. + * @callback: The function which will be called when the user selects "OK". + * @data: The callback's user data. + * + * Creates a new #GtkDialog that queries the user for a string value. + * + * Returns: A pointer to the new #GtkDialog. + **/ +GtkWidget * +gimp_query_string_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + const gchar *initial, + GObject *object, + const gchar *signal, + GimpQueryStringCallback callback, + gpointer data) +{ + QueryBox *query_box; + GtkWidget *entry; + + query_box = create_query_box (title, parent, help_func, help_id, + G_CALLBACK (string_query_box_response), + "dialog-question", + message, + _("_OK"), _("_Cancel"), + object, signal, + G_CALLBACK (callback), data); + + if (! query_box) + return NULL; + + entry = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry), initial ? initial : ""); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_box_pack_start (GTK_BOX (query_box->vbox), entry, FALSE, FALSE, 0); + gtk_widget_grab_focus (entry); + gtk_widget_show (entry); + + query_box->entry = entry; + + return query_box->qbox; +} + +/** + * gimp_query_int_box: + * @title: The query box dialog's title. + * @parent: The dialog's parent widget. + * @help_func: The help function to show this dialog's help page. + * @help_id: A string identifying this dialog's help page. + * @message: A string which will be shown above the dialog's entry widget. + * @initial: The initial value. + * @lower: The lower boundary of the range of possible values. + * @upper: The upper boundray of the range of possible values. + * @object: The object this query box is associated with. + * @signal: The object's signal which will cause the query box to be closed. + * @callback: The function which will be called when the user selects "OK". + * @data: The callback's user data. + * + * Creates a new #GtkDialog that queries the user for an integer value. + * + * Returns: A pointer to the new #GtkDialog. + **/ +GtkWidget * +gimp_query_int_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + gint initial, + gint lower, + gint upper, + GObject *object, + const gchar *signal, + GimpQueryIntCallback callback, + gpointer data) +{ + QueryBox *query_box; + GtkWidget *spinbutton; + GtkAdjustment *adjustment; + + query_box = create_query_box (title, parent, help_func, help_id, + G_CALLBACK (int_query_box_response), + "dialog-question", + message, + _("_OK"), _("_Cancel"), + object, signal, + G_CALLBACK (callback), data); + + if (! query_box) + return NULL; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (initial, lower, upper, 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (query_box->vbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_grab_focus (spinbutton); + gtk_widget_show (spinbutton); + + query_box->entry = spinbutton; + + return query_box->qbox; +} + +/** + * gimp_query_double_box: + * @title: The query box dialog's title. + * @parent: The dialog's parent widget. + * @help_func: The help function to show this dialog's help page. + * @help_id: A string identifying this dialog's help page. + * @message: A string which will be shown above the dialog's entry widget. + * @initial: The initial value. + * @lower: The lower boundary of the range of possible values. + * @upper: The upper boundray of the range of possible values. + * @digits: The number of decimal digits the #GtkSpinButton will provide. + * @object: The object this query box is associated with. + * @signal: The object's signal which will cause the query box to be closed. + * @callback: The function which will be called when the user selects "OK". + * @data: The callback's user data. + * + * Creates a new #GtkDialog that queries the user for a double value. + * + * Returns: A pointer to the new #GtkDialog. + **/ +GtkWidget * +gimp_query_double_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + gdouble initial, + gdouble lower, + gdouble upper, + gint digits, + GObject *object, + const gchar *signal, + GimpQueryDoubleCallback callback, + gpointer data) +{ + QueryBox *query_box; + GtkWidget *spinbutton; + GtkAdjustment *adjustment; + + query_box = create_query_box (title, parent, help_func, help_id, + G_CALLBACK (double_query_box_response), + "dialog-question", + message, + _("_OK"), _("_Cancel"), + object, signal, + G_CALLBACK (callback), data); + + if (! query_box) + return NULL; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (initial, lower, upper, 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (query_box->vbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_grab_focus (spinbutton); + gtk_widget_show (spinbutton); + + query_box->entry = spinbutton; + + return query_box->qbox; +} + +/** + * gimp_query_size_box: + * @title: The query box dialog's title. + * @parent: The dialog's parent widget. + * @help_func: The help function to show this dialog's help page. + * @help_id: A string identifying this dialog's help page. + * @message: A string which will be shown above the dialog's entry widget. + * @initial: The initial value. + * @lower: The lower boundary of the range of possible values. + * @upper: The upper boundray of the range of possible values. + * @digits: The number of decimal digits the #GimpSizeEntry provide in + * "pixel" mode. + * @unit: The unit initially shown by the #GimpUnitMenu. + * @resolution: The resolution (in dpi) which will be used for pixel/unit + * calculations. + * @dot_for_dot: %TRUE if the #GimpUnitMenu's initial unit should be "pixels". + * @object: The object this query box is associated with. + * @signal: The object's signal which will cause the query box + * to be closed. + * @callback: The function which will be called when the user selects "OK". + * @data: The callback's user data. + * + * Creates a new #GtkDialog that queries the user for a size using a + * #GimpSizeEntry. + * + * Returns: A pointer to the new #GtkDialog. + **/ +GtkWidget * +gimp_query_size_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + gdouble initial, + gdouble lower, + gdouble upper, + gint digits, + GimpUnit unit, + gdouble resolution, + gboolean dot_for_dot, + GObject *object, + const gchar *signal, + GimpQuerySizeCallback callback, + gpointer data) +{ + QueryBox *query_box; + GtkWidget *sizeentry; + GtkWidget *spinbutton; + + query_box = create_query_box (title, parent, help_func, help_id, + G_CALLBACK (size_query_box_response), + "dialog-question", + message, + _("_OK"), _("_Cancel"), + object, signal, + G_CALLBACK (callback), data); + + if (! query_box) + return NULL; + + sizeentry = gimp_size_entry_new (1, unit, "%p", TRUE, FALSE, FALSE, 12, + GIMP_SIZE_ENTRY_UPDATE_SIZE); + if (dot_for_dot) + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (sizeentry), GIMP_UNIT_PIXEL); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (sizeentry), 0, + resolution, FALSE); + gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (sizeentry), 0, digits); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (sizeentry), 0, + lower, upper); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (sizeentry), 0, initial); + + spinbutton = gimp_size_entry_get_help_widget (GIMP_SIZE_ENTRY (sizeentry), 0); + gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE); + + gtk_box_pack_start (GTK_BOX (query_box->vbox), sizeentry, FALSE, FALSE, 0); + gimp_size_entry_grab_focus (GIMP_SIZE_ENTRY (sizeentry)); + gtk_widget_show (sizeentry); + + query_box->entry = sizeentry; + + return query_box->qbox; +} + +/** + * gimp_query_boolean_box: + * @title: The query box dialog's title. + * @parent: The dialog's parent widget. + * @help_func: The help function to show this dialog's help page. + * @help_id: A string identifying this dialog's help page. + * @icon_name: An icon name to specify an icon to appear on the left + * on the dialog's message. + * @message: A string which will be shown in the query box. + * @true_button: The string to be shown in the dialog's left button. + * @false_button: The string to be shown in the dialog's right button. + * @object: The object this query box is associated with. + * @signal: The object's signal which will cause the query box + * to be closed. + * @callback: The function which will be called when the user clicks one + * of the buttons. + * @data: The callback's user data. + * + * Creates a new #GtkDialog that asks the user to do a boolean decision. + * + * Returns: A pointer to the new #GtkDialog. + **/ +GtkWidget * +gimp_query_boolean_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *icon_name, + const gchar *message, + const gchar *true_button, + const gchar *false_button, + GObject *object, + const gchar *signal, + GimpQueryBooleanCallback callback, + gpointer data) +{ + QueryBox *query_box; + + query_box = create_query_box (title, parent, help_func, help_id, + G_CALLBACK (boolean_query_box_response), + icon_name, + message, + true_button, false_button, + object, signal, + G_CALLBACK (callback), data); + + if (! query_box) + return NULL; + + return query_box->qbox; +} + + +/* + * private functions + */ + +static void +query_box_disconnect (QueryBox *query_box) +{ + gtk_widget_set_sensitive (query_box->qbox, FALSE); + + /* disconnect the response callback to avoid that it may be run twice */ + if (query_box->response_handler) + { + g_signal_handler_disconnect (query_box->qbox, + query_box->response_handler); + + query_box->response_handler = 0; + } + + /* disconnect, if we are connected to some signal */ + if (query_box->object) + g_signal_handlers_disconnect_by_func (query_box->object, + query_box_cancel_callback, + query_box); +} + +static void +query_box_destroy (QueryBox *query_box) +{ + /* Destroy the box */ + if (query_box->qbox) + gtk_widget_destroy (query_box->qbox); + + g_slice_free (QueryBox, query_box); +} + +static void +string_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box) +{ + const gchar *string; + + query_box_disconnect (query_box); + + /* Get the entry data */ + string = gtk_entry_get_text (GTK_ENTRY (query_box->entry)); + + /* Call the user defined callback */ + if (response_id == GTK_RESPONSE_OK) + (* (GimpQueryStringCallback) query_box->callback) (query_box->qbox, + string, + query_box->callback_data); + + query_box_destroy (query_box); +} + +static void +int_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box) +{ + gint value; + + query_box_disconnect (query_box); + + /* Get the spinbutton data */ + value = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (query_box->entry)); + + /* Call the user defined callback */ + if (response_id == GTK_RESPONSE_OK) + (* (GimpQueryIntCallback) query_box->callback) (query_box->qbox, + value, + query_box->callback_data); + + query_box_destroy (query_box); +} + +static void +double_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box) +{ + gdouble value; + + query_box_disconnect (query_box); + + /* Get the spinbutton data */ + value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (query_box->entry)); + + /* Call the user defined callback */ + if (response_id == GTK_RESPONSE_OK) + (* (GimpQueryDoubleCallback) query_box->callback) (query_box->qbox, + value, + query_box->callback_data); + + query_box_destroy (query_box); +} + +static void +size_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box) +{ + gdouble size; + GimpUnit unit; + + query_box_disconnect (query_box); + + /* Get the sizeentry data */ + size = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (query_box->entry), 0); + unit = gimp_size_entry_get_unit (GIMP_SIZE_ENTRY (query_box->entry)); + + /* Call the user defined callback */ + if (response_id == GTK_RESPONSE_OK) + (* (GimpQuerySizeCallback) query_box->callback) (query_box->qbox, + size, + unit, + query_box->callback_data); + + query_box_destroy (query_box); +} + +static void +boolean_query_box_response (GtkWidget *widget, + gint response_id, + QueryBox *query_box) +{ + query_box_disconnect (query_box); + + /* Call the user defined callback */ + (* (GimpQueryBooleanCallback) query_box->callback) (query_box->qbox, + (response_id == + GTK_RESPONSE_OK), + query_box->callback_data); + + query_box_destroy (query_box); +} + +static void +query_box_cancel_callback (QueryBox *query_box) +{ + query_box_disconnect (query_box); + query_box_destroy (query_box); +} diff --git a/libgimpwidgets/gimpquerybox.h b/libgimpwidgets/gimpquerybox.h new file mode 100644 index 0000000..3535629 --- /dev/null +++ b/libgimpwidgets/gimpquerybox.h @@ -0,0 +1,180 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpquerybox.h + * Copyright (C) 1999-2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_QUERY_BOX_H__ +#define __GIMP_QUERY_BOX_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +/** + * GimpQueryStringCallback: + * @query_box: The query box. + * @string: The entered string. + * @data: The user data. + * + * Note that you must not g_free() the passed string. + **/ +typedef void (* GimpQueryStringCallback) (GtkWidget *query_box, + const gchar *string, + gpointer data); + +/** + * GimpQueryIntCallback: + * @query_box: The query box. + * @value: The entered integer value. + * @data: The user data. + * + * The callback for an int query box. + **/ +typedef void (* GimpQueryIntCallback) (GtkWidget *query_box, + gint value, + gpointer data); + +/** + * GimpQueryDoubleCallback: + * @query_box: The query box. + * @value: The entered double value. + * @data: The user data. + * + * The callback for a double query box. + **/ +typedef void (* GimpQueryDoubleCallback) (GtkWidget *query_box, + gdouble value, + gpointer data); + +/** + * GimpQuerySizeCallback: + * @query_box: The query box. + * @size: The entered size in pixels. + * @unit: The selected unit from the #GimpUnitMenu. + * @data: The user data. + * + * The callback for a size query box. + **/ +typedef void (* GimpQuerySizeCallback) (GtkWidget *query_box, + gdouble size, + GimpUnit unit, + gpointer data); + +/** + * GimpQueryBooleanCallback: + * @query_box: The query box. + * @value: The entered boolean value. + * @data: The user data. + * + * The callback for a boolean query box. + **/ +typedef void (* GimpQueryBooleanCallback) (GtkWidget *query_box, + gboolean value, + gpointer data); + + +/** + * GIMP_QUERY_BOX_VBOX: + * @qbox: The query box. + * + * A macro to access the #GtkVBox in a #libgimpwidgets-gimpquerybox. + * Useful if you want to add more widgets. + **/ +#define GIMP_QUERY_BOX_VBOX(qbox) g_object_get_data (G_OBJECT (qbox), \ + "gimp-query-box-vbox") + + +/* some simple query dialogs */ +GtkWidget * gimp_query_string_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + const gchar *initial, + GObject *object, + const gchar *signal, + GimpQueryStringCallback callback, + gpointer data); + +GtkWidget * gimp_query_int_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + gint initial, + gint lower, + gint upper, + GObject *object, + const gchar *signal, + GimpQueryIntCallback callback, + gpointer data); + +GtkWidget * gimp_query_double_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + gdouble initial, + gdouble lower, + gdouble upper, + gint digits, + GObject *object, + const gchar *signal, + GimpQueryDoubleCallback callback, + gpointer data); + +GtkWidget * gimp_query_size_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *message, + gdouble initial, + gdouble lower, + gdouble upper, + gint digits, + GimpUnit unit, + gdouble resolution, + gboolean dot_for_dot, + GObject *object, + const gchar *signal, + GimpQuerySizeCallback callback, + gpointer data); + +GtkWidget * gimp_query_boolean_box (const gchar *title, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + const gchar *icon_name, + const gchar *message, + const gchar *true_button, + const gchar *false_button, + GObject *object, + const gchar *signal, + GimpQueryBooleanCallback callback, + gpointer data); + + +G_END_DECLS + +#endif /* __GIMP_QUERY_BOX_H__ */ diff --git a/libgimpwidgets/gimpruler.c b/libgimpwidgets/gimpruler.c new file mode 100644 index 0000000..db9f538 --- /dev/null +++ b/libgimpwidgets/gimpruler.c @@ -0,0 +1,1465 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "gimpwidgetstypes.h" + +#include "gimpruler.h" + + +/** + * SECTION: gimpruler + * @title: GimpRuler + * @short_description: A ruler widget with configurable unit and orientation. + * + * A ruler widget with configurable unit and orientation. + **/ + + +#define DEFAULT_RULER_FONT_SCALE PANGO_SCALE_SMALL +#define MINIMUM_INCR 5 +#define IMMEDIATE_REDRAW_THRESHOLD 20 + + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_UNIT, + PROP_LOWER, + PROP_UPPER, + PROP_POSITION, + PROP_MAX_SIZE +}; + + +/* All distances below are in 1/72nd's of an inch. (According to + * Adobe that's a point, but points are really 1/72.27 in.) + */ +typedef struct +{ + GtkOrientation orientation; + GimpUnit unit; + gdouble lower; + gdouble upper; + gdouble position; + gdouble max_size; + + GdkWindow *input_window; + cairo_surface_t *backing_store; + gboolean backing_store_valid; + GdkRectangle last_pos_rect; + guint pos_redraw_idle_id; + PangoLayout *layout; + gdouble font_scale; + + GList *track_widgets; +} GimpRulerPrivate; + +#define GIMP_RULER_GET_PRIVATE(ruler) \ + ((GimpRulerPrivate *) gimp_ruler_get_instance_private ((GimpRuler *) (ruler))) + + +typedef struct +{ + const gdouble ruler_scale[16]; + const gint subdivide[5]; +} RulerMetric; + +static const RulerMetric ruler_metric_decimal = +{ + { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 }, + { 1, 5, 10, 50, 100 } +}; + +static const RulerMetric ruler_metric_inches = +{ + { 1, 2, 6, 12, 36, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 }, + { 1, 4, 8, 16, 12 * 16 } +}; + +static const RulerMetric ruler_metric_feet = +{ + { 1, 2, 6, 12, 36, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 }, + { 1, 3, 6, 12, 12 * 8 } +}; + +static const RulerMetric ruler_metric_yards = +{ + { 1, 2, 6, 12, 36, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 }, + { 1, 3, 6, 12, 12 * 12 } +}; + + +static void gimp_ruler_dispose (GObject *object); +static void gimp_ruler_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_ruler_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_ruler_realize (GtkWidget *widget); +static void gimp_ruler_unrealize (GtkWidget *widget); +static void gimp_ruler_map (GtkWidget *widget); +static void gimp_ruler_unmap (GtkWidget *widget); +static void gimp_ruler_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_ruler_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_ruler_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_ruler_motion_notify (GtkWidget *widget, + GdkEventMotion *event); +static gboolean gimp_ruler_expose (GtkWidget *widget, + GdkEventExpose *event); + +static void gimp_ruler_draw_ticks (GimpRuler *ruler); +static GdkRectangle gimp_ruler_get_pos_rect (GimpRuler *ruler, + gdouble position); +static gboolean gimp_ruler_idle_queue_pos_redraw (gpointer data); +static void gimp_ruler_queue_pos_redraw (GimpRuler *ruler); +static void gimp_ruler_draw_pos (GimpRuler *ruler, + cairo_t *cr); +static void gimp_ruler_make_pixmap (GimpRuler *ruler); +static PangoLayout * gimp_ruler_get_layout (GtkWidget *widget, + const gchar *text); +static const RulerMetric * + gimp_ruler_get_metric (GimpUnit unit); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpRuler, gimp_ruler, GTK_TYPE_WIDGET) + +#define parent_class gimp_ruler_parent_class + + +static void +gimp_ruler_class_init (GimpRulerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_ruler_dispose; + object_class->set_property = gimp_ruler_set_property; + object_class->get_property = gimp_ruler_get_property; + + widget_class->realize = gimp_ruler_realize; + widget_class->unrealize = gimp_ruler_unrealize; + widget_class->map = gimp_ruler_map; + widget_class->unmap = gimp_ruler_unmap; + widget_class->size_allocate = gimp_ruler_size_allocate; + widget_class->size_request = gimp_ruler_size_request; + widget_class->style_set = gimp_ruler_style_set; + widget_class->motion_notify_event = gimp_ruler_motion_notify; + widget_class->expose_event = gimp_ruler_expose; + + g_object_class_install_property (object_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", + "Orientation", + "The orientation of the ruler", + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_UNIT, + gimp_param_spec_unit ("unit", + "Unit", + "Unit of ruler", + TRUE, TRUE, + GIMP_UNIT_PIXEL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_LOWER, + g_param_spec_double ("lower", + "Lower", + "Lower limit of ruler", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_UPPER, + g_param_spec_double ("upper", + "Upper", + "Upper limit of ruler", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_POSITION, + g_param_spec_double ("position", + "Position", + "Position of mark on the ruler", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_MAX_SIZE, + g_param_spec_double ("max-size", + "Max Size", + "Maximum size of the ruler", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_double ("font-scale", + "Font Scale", + "The size of the used font", + 0.0, + G_MAXDOUBLE, + DEFAULT_RULER_FONT_SCALE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_ruler_init (GimpRuler *ruler) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + + gtk_widget_set_has_window (GTK_WIDGET (ruler), FALSE); + + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + priv->unit = GIMP_UNIT_PIXEL; + priv->lower = 0; + priv->upper = 0; + priv->position = 0; + priv->max_size = 0; + + priv->backing_store = NULL; + priv->backing_store_valid = FALSE; + + priv->last_pos_rect.x = 0; + priv->last_pos_rect.y = 0; + priv->last_pos_rect.width = 0; + priv->last_pos_rect.height = 0; + priv->pos_redraw_idle_id = 0; + + priv->font_scale = DEFAULT_RULER_FONT_SCALE; +} + +static void +gimp_ruler_dispose (GObject *object) +{ + GimpRuler *ruler = GIMP_RULER (object); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + + while (priv->track_widgets) + gimp_ruler_remove_track_widget (ruler, priv->track_widgets->data); + + if (priv->pos_redraw_idle_id) + { + g_source_remove (priv->pos_redraw_idle_id); + priv->pos_redraw_idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_ruler_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpRuler *ruler = GIMP_RULER (object); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + + switch (prop_id) + { + case PROP_ORIENTATION: + priv->orientation = g_value_get_enum (value); + gtk_widget_queue_resize (GTK_WIDGET (ruler)); + break; + + case PROP_UNIT: + gimp_ruler_set_unit (ruler, g_value_get_int (value)); + break; + + case PROP_LOWER: + gimp_ruler_set_range (ruler, + g_value_get_double (value), + priv->upper, + priv->max_size); + break; + case PROP_UPPER: + gimp_ruler_set_range (ruler, + priv->lower, + g_value_get_double (value), + priv->max_size); + break; + + case PROP_POSITION: + gimp_ruler_set_position (ruler, g_value_get_double (value)); + break; + + case PROP_MAX_SIZE: + gimp_ruler_set_range (ruler, + priv->lower, + priv->upper, + g_value_get_double (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_ruler_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpRuler *ruler = GIMP_RULER (object); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + + case PROP_UNIT: + g_value_set_int (value, priv->unit); + break; + + case PROP_LOWER: + g_value_set_double (value, priv->lower); + break; + + case PROP_UPPER: + g_value_set_double (value, priv->upper); + break; + + case PROP_POSITION: + g_value_set_double (value, priv->position); + break; + + case PROP_MAX_SIZE: + g_value_set_double (value, priv->max_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * gimp_ruler_new: + * @orientation: the ruler's orientation. + * + * Creates a new ruler. + * + * Return value: a new #GimpRuler widget. + * + * Since: 2.8 + **/ +GtkWidget * +gimp_ruler_new (GtkOrientation orientation) +{ + return g_object_new (GIMP_TYPE_RULER, + "orientation", orientation, + NULL); +} + +static void +gimp_ruler_update_position (GimpRuler *ruler, + gdouble x, + gdouble y) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkAllocation allocation; + gdouble lower; + gdouble upper; + + gtk_widget_get_allocation (GTK_WIDGET (ruler), &allocation); + gimp_ruler_get_range (ruler, &lower, &upper, NULL); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + gimp_ruler_set_position (ruler, + lower + + (upper - lower) * x / allocation.width); + } + else + { + gimp_ruler_set_position (ruler, + lower + + (upper - lower) * y / allocation.height); + } +} + +/* Returns TRUE if a translation should be done */ +static gboolean +gtk_widget_get_translation_to_window (GtkWidget *widget, + GdkWindow *window, + int *x, + int *y) +{ + GdkWindow *w, *widget_window; + + if (! gtk_widget_get_has_window (widget)) + { + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + *x = -allocation.x; + *y = -allocation.y; + } + else + { + *x = 0; + *y = 0; + } + + widget_window = gtk_widget_get_window (widget); + + for (w = window; + w && w != widget_window; + w = gdk_window_get_effective_parent (w)) + { + gdouble px, py; + + gdk_window_coords_to_parent (w, *x, *y, &px, &py); + + *x += px; + *y += py; + } + + if (w == NULL) + { + *x = 0; + *y = 0; + return FALSE; + } + + return TRUE; +} + +static void +gimp_ruler_event_to_widget_coords (GtkWidget *widget, + GdkWindow *window, + gdouble event_x, + gdouble event_y, + gint *widget_x, + gint *widget_y) +{ + gint tx, ty; + + if (gtk_widget_get_translation_to_window (widget, window, &tx, &ty)) + { + event_x += tx; + event_y += ty; + } + + *widget_x = event_x; + *widget_y = event_y; +} + +static gboolean +gimp_ruler_track_widget_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent, + GimpRuler *ruler) +{ + gint widget_x; + gint widget_y; + gint ruler_x; + gint ruler_y; + + widget = gtk_get_event_widget ((GdkEvent *) mevent); + + gimp_ruler_event_to_widget_coords (widget, mevent->window, + mevent->x, mevent->y, + &widget_x, &widget_y); + + if (gtk_widget_translate_coordinates (widget, GTK_WIDGET (ruler), + widget_x, widget_y, + &ruler_x, &ruler_y)) + { + gimp_ruler_update_position (ruler, ruler_x, ruler_y); + } + + return FALSE; +} + +/** + * gimp_ruler_add_track_widget: + * @ruler: a #GimpRuler + * @widget: the track widget to add + * + * Adds a "track widget" to the ruler. The ruler will connect to + * GtkWidget:motion-notify-event: on the track widget and update its + * position marker accordingly. The marker is correctly updated also + * for the track widget's children, regardless of whether they are + * ordinary children of off-screen children. + * + * Since: 2.8 + */ +void +gimp_ruler_add_track_widget (GimpRuler *ruler, + GtkWidget *widget) +{ + GimpRulerPrivate *priv; + + g_return_if_fail (GIMP_IS_RULER (ruler)); + g_return_if_fail (GTK_IS_WIDGET (ruler)); + + priv = GIMP_RULER_GET_PRIVATE (ruler); + + g_return_if_fail (g_list_find (priv->track_widgets, widget) == NULL); + + priv->track_widgets = g_list_prepend (priv->track_widgets, widget); + + g_signal_connect (widget, "motion-notify-event", + G_CALLBACK (gimp_ruler_track_widget_motion_notify), + ruler); + g_signal_connect_swapped (widget, "destroy", + G_CALLBACK (gimp_ruler_remove_track_widget), + ruler); +} + +/** + * gimp_ruler_remove_track_widget: + * @ruler: a #GimpRuler + * @widget: the track widget to remove + * + * Removes a previously added track widget from the ruler. See + * gimp_ruler_add_track_widget(). + * + * Since: 2.8 + */ +void +gimp_ruler_remove_track_widget (GimpRuler *ruler, + GtkWidget *widget) +{ + GimpRulerPrivate *priv; + + g_return_if_fail (GIMP_IS_RULER (ruler)); + g_return_if_fail (GTK_IS_WIDGET (ruler)); + + priv = GIMP_RULER_GET_PRIVATE (ruler); + + g_return_if_fail (g_list_find (priv->track_widgets, widget) != NULL); + + priv->track_widgets = g_list_remove (priv->track_widgets, widget); + + g_signal_handlers_disconnect_by_func (widget, + gimp_ruler_track_widget_motion_notify, + ruler); + g_signal_handlers_disconnect_by_func (widget, + gimp_ruler_remove_track_widget, + ruler); +} + +/** + * gimp_ruler_set_unit: + * @ruler: a #GimpRuler + * @unit: the #GimpUnit to set the ruler to + * + * This sets the unit of the ruler. + * + * Since: 2.8 + */ +void +gimp_ruler_set_unit (GimpRuler *ruler, + GimpUnit unit) +{ + GimpRulerPrivate *priv; + + g_return_if_fail (GIMP_IS_RULER (ruler)); + + priv = GIMP_RULER_GET_PRIVATE (ruler); + + if (priv->unit != unit) + { + priv->unit = unit; + g_object_notify (G_OBJECT (ruler), "unit"); + + priv->backing_store_valid = FALSE; + gtk_widget_queue_draw (GTK_WIDGET (ruler)); + } +} + +/** + * gimp_ruler_get_unit: + * @ruler: a #GimpRuler + * + * Return value: the unit currently used in the @ruler widget. + * + * Since: 2.8 + **/ +GimpUnit +gimp_ruler_get_unit (GimpRuler *ruler) +{ + g_return_val_if_fail (GIMP_IS_RULER (ruler), 0); + + return GIMP_RULER_GET_PRIVATE (ruler)->unit; +} + +/** + * gimp_ruler_set_position: + * @ruler: a #GimpRuler + * @position: the position to set the ruler to + * + * This sets the position of the ruler. + * + * Since: 2.8 + */ +void +gimp_ruler_set_position (GimpRuler *ruler, + gdouble position) +{ + GimpRulerPrivate *priv; + + g_return_if_fail (GIMP_IS_RULER (ruler)); + + priv = GIMP_RULER_GET_PRIVATE (ruler); + + if (priv->position != position) + { + GdkRectangle rect; + gint xdiff, ydiff; + + priv->position = position; + g_object_notify (G_OBJECT (ruler), "position"); + + rect = gimp_ruler_get_pos_rect (ruler, priv->position); + + xdiff = rect.x - priv->last_pos_rect.x; + ydiff = rect.y - priv->last_pos_rect.y; + + /* + * If the position has changed far enough, queue a redraw immediately. + * Otherwise, we only queue a redraw in a low priority idle handler, to + * allow for other things (like updating the canvas) to run. + * + * TODO: This might not be necessary any more in GTK3 with the frame + * clock. Investigate this more after the port to GTK3. + */ + if (priv->last_pos_rect.width != 0 && + priv->last_pos_rect.height != 0 && + (ABS (xdiff) > IMMEDIATE_REDRAW_THRESHOLD || + ABS (ydiff) > IMMEDIATE_REDRAW_THRESHOLD)) + { + if (priv->pos_redraw_idle_id) + { + g_source_remove (priv->pos_redraw_idle_id); + priv->pos_redraw_idle_id = 0; + } + + gimp_ruler_queue_pos_redraw (ruler); + } + else if (! priv->pos_redraw_idle_id) + { + priv->pos_redraw_idle_id = + g_idle_add_full (G_PRIORITY_LOW, + gimp_ruler_idle_queue_pos_redraw, + ruler, NULL); + } + } +} + +/** + * gimp_ruler_get_position: + * @ruler: a #GimpRuler + * + * Return value: the current position of the @ruler widget. + * + * Since: 2.8 + **/ +gdouble +gimp_ruler_get_position (GimpRuler *ruler) +{ + g_return_val_if_fail (GIMP_IS_RULER (ruler), 0.0); + + return GIMP_RULER_GET_PRIVATE (ruler)->position; +} + +/** + * gimp_ruler_set_range: + * @ruler: a #GimpRuler + * @lower: the lower limit of the ruler + * @upper: the upper limit of the ruler + * @max_size: the maximum size of the ruler used when calculating the space to + * leave for the text + * + * This sets the range of the ruler. + * + * Since: 2.8 + */ +void +gimp_ruler_set_range (GimpRuler *ruler, + gdouble lower, + gdouble upper, + gdouble max_size) +{ + GimpRulerPrivate *priv; + + g_return_if_fail (GIMP_IS_RULER (ruler)); + + priv = GIMP_RULER_GET_PRIVATE (ruler); + + g_object_freeze_notify (G_OBJECT (ruler)); + if (priv->lower != lower) + { + priv->lower = lower; + g_object_notify (G_OBJECT (ruler), "lower"); + } + if (priv->upper != upper) + { + priv->upper = upper; + g_object_notify (G_OBJECT (ruler), "upper"); + } + if (priv->max_size != max_size) + { + priv->max_size = max_size; + g_object_notify (G_OBJECT (ruler), "max-size"); + } + g_object_thaw_notify (G_OBJECT (ruler)); + + priv->backing_store_valid = FALSE; + gtk_widget_queue_draw (GTK_WIDGET (ruler)); +} + +/** + * gimp_ruler_get_range: + * @ruler: a #GimpRuler + * @lower: location to store lower limit of the ruler, or %NULL + * @upper: location to store upper limit of the ruler, or %NULL + * @max_size: location to store the maximum size of the ruler used when + * calculating the space to leave for the text, or %NULL. + * + * Retrieves values indicating the range and current position of a #GimpRuler. + * See gimp_ruler_set_range(). + * + * Since: 2.8 + **/ +void +gimp_ruler_get_range (GimpRuler *ruler, + gdouble *lower, + gdouble *upper, + gdouble *max_size) +{ + GimpRulerPrivate *priv; + + g_return_if_fail (GIMP_IS_RULER (ruler)); + + priv = GIMP_RULER_GET_PRIVATE (ruler); + + if (lower) + *lower = priv->lower; + if (upper) + *upper = priv->upper; + if (max_size) + *max_size = priv->max_size; +} + +static void +gimp_ruler_realize (GtkWidget *widget) +{ + GimpRuler *ruler = GIMP_RULER (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkAllocation allocation; + GdkWindowAttr attributes; + gint attributes_mask; + + GTK_WIDGET_CLASS (gimp_ruler_parent_class)->realize (widget); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = (gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK); + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + priv->input_window = gdk_window_new (gtk_widget_get_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (priv->input_window, ruler); + + gimp_ruler_make_pixmap (ruler); +} + +static void +gimp_ruler_unrealize (GtkWidget *widget) +{ + GimpRuler *ruler = GIMP_RULER (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + + g_clear_pointer (&priv->backing_store, cairo_surface_destroy); + priv->backing_store_valid = FALSE; + + g_clear_object (&priv->layout); + + g_clear_pointer (&priv->input_window, gdk_window_destroy); + + GTK_WIDGET_CLASS (gimp_ruler_parent_class)->unrealize (widget); +} + +static void +gimp_ruler_map (GtkWidget *widget) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (widget); + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + if (priv->input_window) + gdk_window_show (priv->input_window); +} + +static void +gimp_ruler_unmap (GtkWidget *widget) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (widget); + + if (priv->input_window) + gdk_window_hide (priv->input_window); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_ruler_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpRuler *ruler = GIMP_RULER (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkAllocation widget_allocation; + gboolean resized; + + gtk_widget_get_allocation (widget, &widget_allocation); + + resized = (widget_allocation.width != allocation->width || + widget_allocation.height != allocation->height); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (priv->input_window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + if (resized) + gimp_ruler_make_pixmap (ruler); + } +} + +static void +gimp_ruler_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (widget); + GtkStyle *style = gtk_widget_get_style (widget); + PangoLayout *layout; + PangoRectangle ink_rect; + gint size; + + layout = gimp_ruler_get_layout (widget, "0123456789"); + pango_layout_get_pixel_extents (layout, &ink_rect, NULL); + + size = 2 + ink_rect.height * 1.7; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + requisition->width = style->xthickness * 2 + 1; + requisition->height = style->ythickness * 2 + size; + } + else + { + requisition->width = style->xthickness * 2 + size; + requisition->height = style->ythickness * 2 + 1; + } +} + +static void +gimp_ruler_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (widget); + + GTK_WIDGET_CLASS (gimp_ruler_parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "font-scale", &priv->font_scale, + NULL); + + priv->backing_store_valid = FALSE; + + g_clear_object (&priv->layout); +} + +static gboolean +gimp_ruler_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GimpRuler *ruler = GIMP_RULER (widget); + + gimp_ruler_update_position (ruler, event->x, event->y); + + return FALSE; +} + +static gboolean +gimp_ruler_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + if (gtk_widget_is_drawable (widget)) + { + GimpRuler *ruler = GIMP_RULER (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkAllocation allocation; + cairo_t *cr; + + if (! priv->backing_store_valid) + gimp_ruler_draw_ticks (ruler); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + cairo_set_source_surface (cr, priv->backing_store, + allocation.x, allocation.y); + cairo_paint (cr); + + gimp_ruler_draw_pos (ruler, cr); + + cairo_destroy (cr); + } + + return FALSE; +} + +static void +gimp_ruler_draw_ticks (GimpRuler *ruler) +{ + GtkWidget *widget = GTK_WIDGET (ruler); + GtkStyle *style = gtk_widget_get_style (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkStateType state = gtk_widget_get_state (widget); + GtkAllocation allocation; + cairo_t *cr; + gint i; + gint width, height; + gint xthickness; + gint ythickness; + gint length, ideal_length; + gdouble lower, upper; /* Upper and lower limits, in ruler units */ + gdouble increment; /* Number of pixels per unit */ + gint scale; /* Number of units per major unit */ + gdouble start, end, cur; + gchar unit_str[32]; + gint digit_height; + gint digit_offset; + gint text_size; + gint pos; + gdouble max_size; + GimpUnit unit; + PangoLayout *layout; + PangoRectangle logical_rect, ink_rect; + const RulerMetric *ruler_metric; + + if (! gtk_widget_is_drawable (widget)) + return; + + gtk_widget_get_allocation (widget, &allocation); + + xthickness = style->xthickness; + ythickness = style->ythickness; + + layout = gimp_ruler_get_layout (widget, "0123456789"); + pango_layout_get_extents (layout, &ink_rect, &logical_rect); + + digit_height = PANGO_PIXELS (ink_rect.height) + 2; + digit_offset = ink_rect.y; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + width = allocation.width; + height = allocation.height - ythickness * 2; + } + else + { + width = allocation.height; + height = allocation.width - ythickness * 2; + } + + cr = cairo_create (priv->backing_store); + gdk_cairo_set_source_color (cr, &style->bg[state]); + +#if 0 + gtk_paint_box (style, priv->backing_store, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, widget, + priv->orientation == GTK_ORIENTATION_HORIZONTAL ? + "hruler" : "vruler", + 0, 0, + allocation.width, allocation.height); +#else + cairo_paint (cr); +#endif + + gdk_cairo_set_source_color (cr, &style->fg[state]); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + cairo_rectangle (cr, + xthickness, + height + ythickness, + allocation.width - 2 * xthickness, + 1); + } + else + { + cairo_rectangle (cr, + height + xthickness, + ythickness, + 1, + allocation.height - 2 * ythickness); + } + + gimp_ruler_get_range (ruler, &lower, &upper, &max_size); + + if ((upper - lower) == 0) + goto out; + + increment = (gdouble) width / (upper - lower); + + /* determine the scale + * use the maximum extents of the ruler to determine the largest + * possible number to be displayed. Calculate the height in pixels + * of this displayed text. Use this height to find a scale which + * leaves sufficient room for drawing the ruler. + * + * We calculate the text size as for the vruler instead of + * actually measuring the text width, so that the result for the + * scale looks consistent with an accompanying vruler. + */ + scale = ceil (max_size); + g_snprintf (unit_str, sizeof (unit_str), "%d", scale); + text_size = strlen (unit_str) * digit_height + 1; + + unit = gimp_ruler_get_unit (ruler); + + ruler_metric = gimp_ruler_get_metric (unit); + + for (scale = 0; scale < G_N_ELEMENTS (ruler_metric->ruler_scale); scale++) + if (ruler_metric->ruler_scale[scale] * fabs (increment) > 2 * text_size) + break; + + if (scale == G_N_ELEMENTS (ruler_metric->ruler_scale)) + scale = G_N_ELEMENTS (ruler_metric->ruler_scale) - 1; + + /* drawing starts here */ + length = 0; + for (i = G_N_ELEMENTS (ruler_metric->subdivide) - 1; i >= 0; i--) + { + gdouble subd_incr; + + /* hack to get proper subdivisions at full pixels */ + if (unit == GIMP_UNIT_PIXEL && scale == 1 && i == 1) + subd_incr = 1.0; + else + subd_incr = ((gdouble) ruler_metric->ruler_scale[scale] / + (gdouble) ruler_metric->subdivide[i]); + + if (subd_incr * fabs (increment) <= MINIMUM_INCR) + continue; + + /* don't subdivide pixels */ + if (unit == GIMP_UNIT_PIXEL && subd_incr < 1.0) + continue; + + /* Calculate the length of the tickmarks. Make sure that + * this length increases for each set of ticks + */ + ideal_length = height / (i + 1) - 1; + if (ideal_length > ++length) + length = ideal_length; + + if (lower < upper) + { + start = floor (lower / subd_incr) * subd_incr; + end = ceil (upper / subd_incr) * subd_incr; + } + else + { + start = floor (upper / subd_incr) * subd_incr; + end = ceil (lower / subd_incr) * subd_incr; + } + + for (cur = start; cur <= end; cur += subd_incr) + { + pos = ROUND ((cur - lower) * increment); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + cairo_rectangle (cr, + pos, height + ythickness - length, + 1, length); + } + else + { + cairo_rectangle (cr, + height + xthickness - length, pos, + length, 1); + } + + /* draw label */ + if (i == 0) + { + g_snprintf (unit_str, sizeof (unit_str), "%d", (int) cur); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + pango_layout_set_text (layout, unit_str, -1); + pango_layout_get_extents (layout, &logical_rect, NULL); + +#if 0 + gtk_paint_layout (style, + priv->backing_store, + state, + FALSE, + NULL, + widget, + "hruler", + pos + 2, + ythickness + PANGO_PIXELS (logical_rect.y - digit_offset), + layout); +#else + cairo_move_to (cr, + pos + 2, + ythickness + PANGO_PIXELS (logical_rect.y - digit_offset)); + pango_cairo_show_layout (cr, layout); +#endif + } + else + { + gint j; + + for (j = 0; j < (int) strlen (unit_str); j++) + { + pango_layout_set_text (layout, unit_str + j, 1); + pango_layout_get_extents (layout, NULL, &logical_rect); + +#if 0 + gtk_paint_layout (style, + priv->backing_store, + state, + FALSE, + NULL, + widget, + "vruler", + xthickness + 1, + pos + digit_height * j + 2 + PANGO_PIXELS (logical_rect.y - digit_offset), + layout); +#else + cairo_move_to (cr, + xthickness + 1, + pos + digit_height * j + 2 + PANGO_PIXELS (logical_rect.y - digit_offset)); + pango_cairo_show_layout (cr, layout); +#endif + + } + } + } + } + } + + cairo_fill (cr); + + priv->backing_store_valid = TRUE; + +out: + cairo_destroy (cr); +} + +static GdkRectangle +gimp_ruler_get_pos_rect (GimpRuler *ruler, + gdouble position) +{ + GtkWidget *widget = GTK_WIDGET (ruler); + GtkStyle *style = gtk_widget_get_style (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkAllocation allocation; + gint width, height; + gint xthickness; + gint ythickness; + gdouble upper, lower; + gdouble increment; + GdkRectangle rect = { 0, }; + + if (! gtk_widget_is_drawable (widget)) + return rect; + + gtk_widget_get_allocation (widget, &allocation); + + xthickness = style->xthickness; + ythickness = style->ythickness; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + width = allocation.width; + height = allocation.height - ythickness * 2; + + rect.width = height / 2 + 2; + rect.width |= 1; /* make sure it's odd */ + rect.height = rect.width / 2 + 1; + } + else + { + width = allocation.width - xthickness * 2; + height = allocation.height; + + rect.height = width / 2 + 2; + rect.height |= 1; /* make sure it's odd */ + rect.width = rect.height / 2 + 1; + } + + gimp_ruler_get_range (ruler, &lower, &upper, NULL); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + increment = (gdouble) width / (upper - lower); + + rect.x = ROUND ((position - lower) * increment) + (xthickness - rect.width) / 2 - 1; + rect.y = (height + rect.height) / 2 + ythickness; + } + else + { + increment = (gdouble) height / (upper - lower); + + rect.x = (width + rect.width) / 2 + xthickness; + rect.y = ROUND ((position - lower) * increment) + (ythickness - rect.height) / 2 - 1; + } + + rect.x += allocation.x; + rect.y += allocation.y; + + return rect; +} + +static gboolean +gimp_ruler_idle_queue_pos_redraw (gpointer data) +{ + GimpRuler *ruler = data; + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + + gimp_ruler_queue_pos_redraw (ruler); + + priv->pos_redraw_idle_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +gimp_ruler_queue_pos_redraw (GimpRuler *ruler) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + const GdkRectangle rect = gimp_ruler_get_pos_rect (ruler, priv->position); + + gtk_widget_queue_draw_area (GTK_WIDGET (ruler), + rect.x, + rect.y, + rect.width, + rect.height); + + if (priv->last_pos_rect.width != 0 && + priv->last_pos_rect.height != 0) + { + gtk_widget_queue_draw_area (GTK_WIDGET (ruler), + priv->last_pos_rect.x, + priv->last_pos_rect.y, + priv->last_pos_rect.width, + priv->last_pos_rect.height); + + priv->last_pos_rect.x = 0; + priv->last_pos_rect.y = 0; + priv->last_pos_rect.width = 0; + priv->last_pos_rect.height = 0; + } +} + +static void +gimp_ruler_draw_pos (GimpRuler *ruler, + cairo_t *cr) +{ + GtkWidget *widget = GTK_WIDGET (ruler); + GtkStyle *style = gtk_widget_get_style (widget); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkStateType state = gtk_widget_get_state (widget); + GdkRectangle pos_rect; + + if (! gtk_widget_is_drawable (widget)) + return; + + pos_rect = gimp_ruler_get_pos_rect (ruler, gimp_ruler_get_position (ruler)); + + if ((pos_rect.width > 0) && (pos_rect.height > 0)) + { + gdk_cairo_set_source_color (cr, &style->fg[state]); + + cairo_move_to (cr, pos_rect.x, pos_rect.y); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + cairo_line_to (cr, pos_rect.x + pos_rect.width / 2.0, + pos_rect.y + pos_rect.height); + cairo_line_to (cr, pos_rect.x + pos_rect.width, + pos_rect.y); + } + else + { + cairo_line_to (cr, pos_rect.x + pos_rect.width, + pos_rect.y + pos_rect.height / 2.0); + cairo_line_to (cr, pos_rect.x, + pos_rect.y + pos_rect.height); + } + + cairo_fill (cr); + } + + if (priv->last_pos_rect.width != 0 && + priv->last_pos_rect.height != 0) + { + gdk_rectangle_union (&priv->last_pos_rect, + &pos_rect, + &priv->last_pos_rect); + } + else + { + priv->last_pos_rect = pos_rect; + } +} + +static void +gimp_ruler_make_pixmap (GimpRuler *ruler) +{ + GtkWidget *widget = GTK_WIDGET (ruler); + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler); + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + if (priv->backing_store) + cairo_surface_destroy (priv->backing_store); + + priv->backing_store = + gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR, + allocation.width, + allocation.height); + + priv->backing_store_valid = FALSE; +} + +static PangoLayout * +gimp_ruler_create_layout (GtkWidget *widget, + const gchar *text) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (widget); + PangoLayout *layout; + PangoAttrList *attrs; + PangoAttribute *attr; + + layout = gtk_widget_create_pango_layout (widget, text); + + attrs = pango_attr_list_new (); + + attr = pango_attr_scale_new (priv->font_scale); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attrs, attr); + + pango_layout_set_attributes (layout, attrs); + pango_attr_list_unref (attrs); + + return layout; +} + +static PangoLayout * +gimp_ruler_get_layout (GtkWidget *widget, + const gchar *text) +{ + GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (widget); + + if (priv->layout) + { + pango_layout_set_text (priv->layout, text, -1); + return priv->layout; + } + + priv->layout = gimp_ruler_create_layout (widget, text); + + return priv->layout; +} + +#define FACTOR_EPSILON 0.0000001 +#define FACTOR_EQUAL(u, f) (ABS (f - gimp_unit_get_factor (u)) < FACTOR_EPSILON) + +static const RulerMetric * +gimp_ruler_get_metric (GimpUnit unit) +{ + /* not enabled until we double checked the metrics */ + return &ruler_metric_decimal; + + if (unit == GIMP_UNIT_INCH) + { + return &ruler_metric_inches; + } + else if (FACTOR_EQUAL (unit, 0.083333)) + { + return &ruler_metric_feet; + } + else if (FACTOR_EQUAL (unit, 0.027778)) + { + return &ruler_metric_yards; + } + + return &ruler_metric_decimal; +} diff --git a/libgimpwidgets/gimpruler.h b/libgimpwidgets/gimpruler.h new file mode 100644 index 0000000..8e4a363 --- /dev/null +++ b/libgimpwidgets/gimpruler.h @@ -0,0 +1,81 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_RULER_H__ +#define __GIMP_RULER_H__ + +G_BEGIN_DECLS + +#define GIMP_TYPE_RULER (gimp_ruler_get_type ()) +#define GIMP_RULER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RULER, GimpRuler)) +#define GIMP_RULER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_RULER, GimpRulerClass)) +#define GIMP_IS_RULER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_RULER)) +#define GIMP_IS_RULER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_RULER)) +#define GIMP_RULER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_RULER, GimpRulerClass)) + + +typedef struct _GimpRulerClass GimpRulerClass; + +struct _GimpRuler +{ + GtkWidget parent_instance; +}; + +struct _GimpRulerClass +{ + GtkWidgetClass parent_class; + + /* Padding for future expansion */ + void (*_gimp_reserved1) (void); + void (*_gimp_reserved2) (void); + void (*_gimp_reserved3) (void); + void (*_gimp_reserved4) (void); +}; + + +GType gimp_ruler_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_ruler_new (GtkOrientation orientation); + +void gimp_ruler_add_track_widget (GimpRuler *ruler, + GtkWidget *widget); +void gimp_ruler_remove_track_widget (GimpRuler *ruler, + GtkWidget *widget); + +void gimp_ruler_set_unit (GimpRuler *ruler, + GimpUnit unit); +GimpUnit gimp_ruler_get_unit (GimpRuler *ruler); +void gimp_ruler_set_position (GimpRuler *ruler, + gdouble position); +gdouble gimp_ruler_get_position (GimpRuler *ruler); +void gimp_ruler_set_range (GimpRuler *ruler, + gdouble lower, + gdouble upper, + gdouble max_size); +void gimp_ruler_get_range (GimpRuler *ruler, + gdouble *lower, + gdouble *upper, + gdouble *max_size); + +G_END_DECLS + +#endif /* __GIMP_RULER_H__ */ diff --git a/libgimpwidgets/gimpscaleentry.c b/libgimpwidgets/gimpscaleentry.c new file mode 100644 index 0000000..8112434 --- /dev/null +++ b/libgimpwidgets/gimpscaleentry.c @@ -0,0 +1,479 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpscaleentry.c + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" + +#include "gimpspinbutton.h" +#include "gimpwidgets.h" + + +static gboolean gimp_scale_entry_linear_to_log (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data); +static gboolean gimp_scale_entry_log_to_linear (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data); + +static GtkAdjustment * gimp_scale_entry_new_internal (gboolean color_scale, + GtkTable *table, + gint column, + gint row, + const gchar *text, + gint scale_width, + gint spinbutton_width, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + guint digits, + gboolean constrain, + gdouble unconstrained_lower, + gdouble unconstrained_upper, + const gchar *tooltip, + const gchar *help_id); + + +static gboolean +gimp_scale_entry_linear_to_log (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + GtkAdjustment *spin_adjustment; + gdouble value = g_value_get_double (from_value); + + spin_adjustment = GTK_ADJUSTMENT (g_binding_get_source (binding)); + + if (gtk_adjustment_get_lower (spin_adjustment) <= 0.0) + value = log (value - gtk_adjustment_get_lower (spin_adjustment) + 0.1); + else + value = log (value); + + g_value_set_double (to_value, value); + + return TRUE; +} + +static gboolean +gimp_scale_entry_log_to_linear (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + GtkAdjustment *spin_adjustment; + gdouble value = g_value_get_double (from_value); + + spin_adjustment = GTK_ADJUSTMENT (g_binding_get_source (binding)); + + value = exp (value); + + if (gtk_adjustment_get_lower (spin_adjustment) <= 0.0) + value += gtk_adjustment_get_lower (spin_adjustment) - 0.1; + + g_value_set_double (to_value, value); + + return TRUE; +} + +static GtkAdjustment * +gimp_scale_entry_new_internal (gboolean color_scale, + GtkTable *table, + gint column, + gint row, + const gchar *text, + gint scale_width, + gint spinbutton_width, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + guint digits, + gboolean constrain, + gdouble unconstrained_lower, + gdouble unconstrained_upper, + const gchar *tooltip, + const gchar *help_id) +{ + GtkWidget *label; + GtkWidget *scale; + GtkWidget *spinbutton; + GtkAdjustment *scale_adjustment; + GtkAdjustment *spin_adjustment; + GBinding *binding; + + label = gtk_label_new_with_mnemonic (text); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_widget_show (label); + + scale_adjustment = (GtkAdjustment *) + gtk_adjustment_new (value, lower, upper, + step_increment, page_increment, 0.0); + + if (! constrain && + unconstrained_lower <= lower && + unconstrained_upper >= upper) + { + spin_adjustment = (GtkAdjustment *) + gtk_adjustment_new (value, + unconstrained_lower, + unconstrained_upper, + step_increment, page_increment, 0.0); + } + else + { + spin_adjustment = (GtkAdjustment *) + gtk_adjustment_new (value, lower, upper, + step_increment, page_increment, 0.0); + } + + binding = g_object_bind_property (G_OBJECT (spin_adjustment), "value", + G_OBJECT (scale_adjustment), "value", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + spinbutton = gimp_spin_button_new (spin_adjustment, step_increment, digits); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_widget_show (spinbutton); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); + + if (spinbutton_width > 0) + { + if (spinbutton_width < 17) + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), spinbutton_width); + else + gtk_widget_set_size_request (spinbutton, spinbutton_width, -1); + } + + if (color_scale) + { + scale = gimp_color_scale_new (GTK_ORIENTATION_HORIZONTAL, + GIMP_COLOR_SELECTOR_VALUE); + + gtk_range_set_adjustment (GTK_RANGE (scale), scale_adjustment); + } + else + { + scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, scale_adjustment); + } + + if (scale_width > 0) + gtk_widget_set_size_request (scale, scale_width, -1); + gtk_scale_set_digits (GTK_SCALE (scale), digits); + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + gtk_widget_show (scale); + + gtk_table_attach (GTK_TABLE (table), label, + column, column + 1, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + + gtk_table_attach (GTK_TABLE (table), scale, + column + 1, column + 2, row, row + 1, + GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + + gtk_table_attach (GTK_TABLE (table), spinbutton, + column + 2, column + 3, row, row + 1, + GTK_FILL | GTK_SHRINK, GTK_SHRINK, 0, 0); + + if (tooltip || help_id) + { + gimp_help_set_help_data (label, tooltip, help_id); + gimp_help_set_help_data (scale, tooltip, help_id); + gimp_help_set_help_data (spinbutton, tooltip, help_id); + } + + g_object_set_data (G_OBJECT (spin_adjustment), "label", label); + g_object_set_data (G_OBJECT (spin_adjustment), "scale", scale); + g_object_set_data (G_OBJECT (spin_adjustment), "spinbutton", spinbutton); + g_object_set_data (G_OBJECT (spin_adjustment), "binding", binding); + + return spin_adjustment; +} + +/** + * gimp_scale_entry_new: + * @table: The #GtkTable the widgets will be attached to. + * @column: The column to start with. + * @row: The row to attach the widgets. + * @text: The text for the #GtkLabel which will appear + * left of the #GtkHScale. + * @scale_width: The minimum horizontal size of the #GtkHScale. + * @spinbutton_width: The minimum horizontal size of the #GtkSpinButton. + * @value: The initial value. + * @lower: The lower boundary. + * @upper: The upper boundary. + * @step_increment: The step increment. + * @page_increment: The page increment. + * @digits: The number of decimal digits. + * @constrain: %TRUE if the range of possible values of the + * #GtkSpinButton should be the same as of the #GtkHScale. + * @unconstrained_lower: The spinbutton's lower boundary + * if @constrain == %FALSE. + * @unconstrained_upper: The spinbutton's upper boundary + * if @constrain == %FALSE. + * @tooltip: A tooltip message for the scale and the spinbutton. + * @help_id: The widgets' help_id (see gimp_help_set_help_data()). + * + * This function creates a #GtkLabel, a #GtkHScale and a #GtkSpinButton and + * attaches them to a 3-column #GtkTable. + * + * Returns: The #GtkSpinButton's #GtkAdjustment. + **/ +GtkObject * +gimp_scale_entry_new (GtkTable *table, + gint column, + gint row, + const gchar *text, + gint scale_width, + gint spinbutton_width, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + guint digits, + gboolean constrain, + gdouble unconstrained_lower, + gdouble unconstrained_upper, + const gchar *tooltip, + const gchar *help_id) +{ + return (GtkObject *) + gimp_scale_entry_new_internal (FALSE, + table, column, row, + text, scale_width, spinbutton_width, + value, lower, upper, + step_increment, page_increment, + digits, + constrain, + unconstrained_lower, + unconstrained_upper, + tooltip, help_id); +} + +/** + * gimp_color_scale_entry_new: + * @table: The #GtkTable the widgets will be attached to. + * @column: The column to start with. + * @row: The row to attach the widgets. + * @text: The text for the #GtkLabel which will appear + * left of the #GtkHScale. + * @scale_width: The minimum horizontal size of the #GtkHScale. + * @spinbutton_width: The minimum horizontal size of the #GtkSpinButton. + * @value: The initial value. + * @lower: The lower boundary. + * @upper: The upper boundary. + * @step_increment: The step increment. + * @page_increment: The page increment. + * @digits: The number of decimal digits. + * @tooltip: A tooltip message for the scale and the spinbutton. + * @help_id: The widgets' help_id (see gimp_help_set_help_data()). + * + * This function creates a #GtkLabel, a #GimpColorScale and a + * #GtkSpinButton and attaches them to a 3-column #GtkTable. + * + * Returns: The #GtkSpinButton's #GtkAdjustment. + **/ +GtkObject * +gimp_color_scale_entry_new (GtkTable *table, + gint column, + gint row, + const gchar *text, + gint scale_width, + gint spinbutton_width, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + guint digits, + const gchar *tooltip, + const gchar *help_id) +{ + return (GtkObject *) + gimp_scale_entry_new_internal (TRUE, + table, column, row, + text, scale_width, spinbutton_width, + value, lower, upper, + step_increment, page_increment, + digits, + TRUE, 0.0, 0.0, + tooltip, help_id); +} + +/** + * gimp_scale_entry_set_logarithmic: + * @adjustment: a #GtkAdjustment as returned by gimp_scale_entry_new() + * @logarithmic: a boolean value to set or reset logarithmic behaviour + * of the scale widget + * + * Sets whether the scale_entry's scale widget will behave in a linear + * or logharithmic fashion. Useful when an entry has to attend large + * ranges, but smaller selections on that range require a finer + * adjustment. + * + * Since: 2.2 + **/ +void +gimp_scale_entry_set_logarithmic (GtkObject *adjustment, + gboolean logarithmic) +{ + GtkAdjustment *spin_adj; + GtkAdjustment *scale_adj; + GBinding *binding; + + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + spin_adj = GTK_ADJUSTMENT (adjustment); + scale_adj = GIMP_SCALE_ENTRY_SCALE_ADJ (adjustment); + binding = g_object_get_data (G_OBJECT (adjustment), "binding"); + + g_return_if_fail (GTK_IS_ADJUSTMENT (scale_adj)); + g_return_if_fail (G_IS_BINDING (binding)); + + logarithmic = logarithmic ? TRUE : FALSE; + + if (logarithmic == gimp_scale_entry_get_logarithmic (adjustment)) + return; + + g_object_unref (binding); + + if (logarithmic) + { + gdouble correction; + gdouble log_value, log_lower, log_upper; + gdouble log_step_increment, log_page_increment; + + correction = (gtk_adjustment_get_lower (scale_adj) > 0 ? + 0 : 0.1 + - gtk_adjustment_get_lower (scale_adj)); + + log_value = log (gtk_adjustment_get_value (scale_adj) + correction); + log_lower = log (gtk_adjustment_get_lower (scale_adj) + correction); + log_upper = log (gtk_adjustment_get_upper (scale_adj) + correction); + log_step_increment = + (log_upper - log_lower) / ((gtk_adjustment_get_upper (scale_adj) - + gtk_adjustment_get_lower (scale_adj)) / + gtk_adjustment_get_step_increment (scale_adj)); + log_page_increment = + (log_upper - log_lower) / ((gtk_adjustment_get_upper (scale_adj) - + gtk_adjustment_get_lower (scale_adj)) / + gtk_adjustment_get_page_increment (scale_adj)); + + gtk_adjustment_configure (scale_adj, + log_value, log_lower, log_upper, + log_step_increment, log_page_increment, 0.0); + + binding = g_object_bind_property_full (G_OBJECT (spin_adj), "value", + G_OBJECT (scale_adj), "value", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE, + gimp_scale_entry_linear_to_log, + gimp_scale_entry_log_to_linear, + NULL, NULL); + } + else + { + gdouble lower, upper; + + lower = exp (gtk_adjustment_get_lower (scale_adj)); + upper = exp (gtk_adjustment_get_upper (scale_adj)); + + if (gtk_adjustment_get_lower (spin_adj) <= 0.0) + { + lower += - 0.1 + gtk_adjustment_get_lower (spin_adj); + upper += - 0.1 + gtk_adjustment_get_lower (spin_adj); + } + + gtk_adjustment_configure (scale_adj, + gtk_adjustment_get_value (spin_adj), + lower, upper, + gtk_adjustment_get_step_increment (spin_adj), + gtk_adjustment_get_page_increment (spin_adj), + 0.0); + + binding = g_object_bind_property (G_OBJECT (spin_adj), "value", + G_OBJECT (scale_adj), "value", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + } + + g_object_set_data (G_OBJECT (spin_adj), "binding", binding); + + g_object_set_data (G_OBJECT (adjustment), "logarithmic", + GINT_TO_POINTER (logarithmic)); +} + +/** + * gimp_scale_entry_get_logarithmic: + * @adjustment: a #GtkAdjustment as returned by gimp_scale_entry_new() + * + * Return value: %TRUE if the the entry's scale widget will behave in + * logharithmic fashion, %FALSE for linear behaviour. + * + * Since: 2.2 + **/ +gboolean +gimp_scale_entry_get_logarithmic (GtkObject *adjustment) +{ + return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (adjustment), + "logarithmic")); +} + +/** + * gimp_scale_entry_set_sensitive: + * @adjustment: a #GtkAdjustment returned by gimp_scale_entry_new() + * @sensitive: a boolean value with the same semantics as the @sensitive + * parameter of gtk_widget_set_sensitive() + * + * Sets the sensitivity of the scale_entry's #GtkLabel, #GtkHScale and + * #GtkSpinButton. + **/ +void +gimp_scale_entry_set_sensitive (GtkObject *adjustment, + gboolean sensitive) +{ + GtkWidget *widget; + + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + widget = GIMP_SCALE_ENTRY_LABEL (adjustment); + if (widget) + gtk_widget_set_sensitive (widget, sensitive); + + widget = GIMP_SCALE_ENTRY_SCALE (adjustment); + if (widget) + gtk_widget_set_sensitive (widget, sensitive); + + widget = GIMP_SCALE_ENTRY_SPINBUTTON (adjustment); + if (widget) + gtk_widget_set_sensitive (widget, sensitive); +} diff --git a/libgimpwidgets/gimpscaleentry.h b/libgimpwidgets/gimpscaleentry.h new file mode 100644 index 0000000..b02d56c --- /dev/null +++ b/libgimpwidgets/gimpscaleentry.h @@ -0,0 +1,125 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpscaleentry.h + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org>, + * 2008 Bill Skaggs <weskaggs@primate.ucdavis.edu> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_SCALE_ENTRY_H__ +#define __GIMP_SCALE_ENTRY_H__ + +G_BEGIN_DECLS + + +/** + * GIMP_SCALE_ENTRY_LABEL: + * @adj: The #GtkAdjustment returned by gimp_scale_entry_new(). + * + * Returns: the scale_entry's #GtkLabel. + **/ +#define GIMP_SCALE_ENTRY_LABEL(adj) \ + (g_object_get_data (G_OBJECT (adj), "label")) + +/** + * GIMP_SCALE_ENTRY_SCALE: + * @adj: The #GtkAdjustment returned by gimp_scale_entry_new(). + * + * Returns: the scale_entry's #GtkHScale. + **/ +#define GIMP_SCALE_ENTRY_SCALE(adj) \ + (g_object_get_data (G_OBJECT (adj), "scale")) + +/** + * GIMP_SCALE_ENTRY_SCALE_ADJ: + * @adj: The #GtkAdjustment returned by gimp_scale_entry_new(). + * + * Returns: the #GtkAdjustment of the scale_entry's #GtkHScale. + **/ +#define GIMP_SCALE_ENTRY_SCALE_ADJ(adj) \ + gtk_range_get_adjustment \ + (GTK_RANGE (g_object_get_data (G_OBJECT (adj), "scale"))) + +/** + * GIMP_SCALE_ENTRY_SPINBUTTON: + * @adj: The #GtkAdjustment returned by gimp_scale_entry_new(). + * + * Returns: the scale_entry's #GtkSpinButton. + **/ +#define GIMP_SCALE_ENTRY_SPINBUTTON(adj) \ + (g_object_get_data (G_OBJECT (adj), "spinbutton")) + +/** + * GIMP_SCALE_ENTRY_SPINBUTTON_ADJ: + * @adj: The #GtkAdjustment returned by gimp_scale_entry_new(). + * + * Returns: the #GtkAdjustment of the scale_entry's #GtkSpinButton. + **/ +#define GIMP_SCALE_ENTRY_SPINBUTTON_ADJ(adj) \ + gtk_spin_button_get_adjustment \ + (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (adj), "spinbutton"))) + + +GtkObject * gimp_scale_entry_new (GtkTable *table, + gint column, + gint row, + const gchar *text, + gint scale_width, + gint spinbutton_width, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + guint digits, + gboolean constrain, + gdouble unconstrained_lower, + gdouble unconstrained_upper, + const gchar *tooltip, + const gchar *help_id); + +GtkObject * gimp_color_scale_entry_new (GtkTable *table, + gint column, + gint row, + const gchar *text, + gint scale_width, + gint spinbutton_width, + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + guint digits, + const gchar *tooltip, + const gchar *help_id); + +void gimp_scale_entry_set_sensitive (GtkObject *adjustment, + gboolean sensitive); + +void gimp_scale_entry_set_logarithmic (GtkObject *adjustment, + gboolean logarithmic); +gboolean gimp_scale_entry_get_logarithmic (GtkObject *adjustment); + + + +G_END_DECLS + +#endif /* __GIMP_SCALE_ENTRY_H__ */ diff --git a/libgimpwidgets/gimpscrolledpreview.c b/libgimpwidgets/gimpscrolledpreview.c new file mode 100644 index 0000000..db2426e --- /dev/null +++ b/libgimpwidgets/gimpscrolledpreview.c @@ -0,0 +1,917 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpscrolledpreview.c + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "libgimpmath/gimpmath.h" + +#include "gimpwidgetstypes.h" + +#include "gimpicons.h" +#include "gimppreviewarea.h" +#include "gimpscrolledpreview.h" +#include "gimp3migration.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpscrolledpreview + * @title: GimpScrolledPreview + * @short_description: A widget providing a #GimpPreview enhanced by + * scrolling capabilities. + * + * A widget providing a #GimpPreview enhanced by scrolling capabilities. + **/ + + +#define POPUP_SIZE 100 + + +typedef struct +{ + GtkPolicyType hscr_policy; + GtkPolicyType vscr_policy; + gint drag_x; + gint drag_y; + gint drag_xoff; + gint drag_yoff; + gboolean in_drag; + gint frozen; +} GimpScrolledPreviewPrivate; + +#define GIMP_SCROLLED_PREVIEW_GET_PRIVATE(obj) \ + ((GimpScrolledPreviewPrivate *) ((GimpScrolledPreview *) (obj))->priv) + + +static void gimp_scrolled_preview_class_init (GimpScrolledPreviewClass *klass); +static void gimp_scrolled_preview_init (GimpScrolledPreview *preview); +static void gimp_scrolled_preview_dispose (GObject *object); + +static void gimp_scrolled_preview_area_realize (GtkWidget *widget, + GimpScrolledPreview *preview); +static void gimp_scrolled_preview_area_unrealize (GtkWidget *widget, + GimpScrolledPreview *preview); +static void gimp_scrolled_preview_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpScrolledPreview *preview); +static gboolean gimp_scrolled_preview_area_event (GtkWidget *area, + GdkEvent *event, + GimpScrolledPreview *preview); + +static void gimp_scrolled_preview_h_scroll (GtkAdjustment *hadj, + GimpPreview *preview); +static void gimp_scrolled_preview_v_scroll (GtkAdjustment *vadj, + GimpPreview *preview); + +static gboolean gimp_scrolled_preview_nav_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpScrolledPreview *preview); + +static gboolean gimp_scrolled_preview_nav_popup_event (GtkWidget *widget, + GdkEvent *event, + GimpScrolledPreview *preview); +static gboolean gimp_scrolled_preview_nav_popup_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpScrolledPreview *preview); + +static void gimp_scrolled_preview_set_cursor (GimpPreview *preview); + + +static GimpPreviewClass *parent_class = NULL; + + +GType +gimp_scrolled_preview_get_type (void) +{ + static GType preview_type = 0; + + if (! preview_type) + { + const GTypeInfo preview_info = + { + sizeof (GimpScrolledPreviewClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gimp_scrolled_preview_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GimpScrolledPreview), + 0, /* n_preallocs */ + (GInstanceInitFunc) gimp_scrolled_preview_init, + }; + + preview_type = g_type_register_static (GIMP_TYPE_PREVIEW, + "GimpScrolledPreview", + &preview_info, + G_TYPE_FLAG_ABSTRACT); + } + + return preview_type; +} + +static void +gimp_scrolled_preview_class_init (GimpScrolledPreviewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPreviewClass *preview_class = GIMP_PREVIEW_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->dispose = gimp_scrolled_preview_dispose; + + preview_class->set_cursor = gimp_scrolled_preview_set_cursor; + + g_type_class_add_private (object_class, sizeof (GimpScrolledPreviewPrivate)); +} + +static void +gimp_scrolled_preview_init (GimpScrolledPreview *preview) +{ + GimpScrolledPreviewPrivate *priv; + GtkWidget *image; + GtkAdjustment *adj; + + preview->priv = G_TYPE_INSTANCE_GET_PRIVATE (preview, + GIMP_TYPE_SCROLLED_PREVIEW, + GimpScrolledPreviewPrivate); + + priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + preview->nav_popup = NULL; + + priv->hscr_policy = GTK_POLICY_AUTOMATIC; + priv->vscr_policy = GTK_POLICY_AUTOMATIC; + + priv->in_drag = FALSE; + priv->frozen = 1; /* we are frozen during init */ + + /* scrollbars */ + adj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, GIMP_PREVIEW (preview)->width - 1, 1.0, + GIMP_PREVIEW (preview)->width, + GIMP_PREVIEW (preview)->width)); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_scrolled_preview_h_scroll), + preview); + + preview->hscr = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, adj); + gtk_table_attach (GTK_TABLE (GIMP_PREVIEW (preview)->table), + preview->hscr, 0, 1, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + adj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, GIMP_PREVIEW (preview)->height - 1, 1.0, + GIMP_PREVIEW (preview)->height, + GIMP_PREVIEW (preview)->height)); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_scrolled_preview_v_scroll), + preview); + + preview->vscr = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, adj); + gtk_table_attach (GTK_TABLE (GIMP_PREVIEW (preview)->table), + preview->vscr, 1, 2, 0, 1, + GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + + /* Connect after here so that plug-ins get a chance to override the + * default behavior. See bug #364432. + */ + g_signal_connect_after (GIMP_PREVIEW (preview)->area, "event", + G_CALLBACK (gimp_scrolled_preview_area_event), + preview); + + g_signal_connect (GIMP_PREVIEW (preview)->area, "realize", + G_CALLBACK (gimp_scrolled_preview_area_realize), + preview); + g_signal_connect (GIMP_PREVIEW (preview)->area, "unrealize", + G_CALLBACK (gimp_scrolled_preview_area_unrealize), + preview); + + g_signal_connect (GIMP_PREVIEW (preview)->area, "size-allocate", + G_CALLBACK (gimp_scrolled_preview_area_size_allocate), + preview); + + /* navigation icon */ + preview->nav_icon = gtk_event_box_new (); + gtk_table_attach (GTK_TABLE (GIMP_PREVIEW(preview)->table), + preview->nav_icon, 1,2, 1,2, + GTK_SHRINK, GTK_SHRINK, 0, 0); + + image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_NAVIGATION, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (preview->nav_icon), image); + gtk_widget_show (image); + + g_signal_connect (preview->nav_icon, "button-press-event", + G_CALLBACK (gimp_scrolled_preview_nav_button_press), + preview); + + priv->frozen = 0; /* thaw without actually calling draw/invalidate */ +} + +static void +gimp_scrolled_preview_dispose (GObject *object) +{ + GimpScrolledPreview *preview = GIMP_SCROLLED_PREVIEW (object); + + if (preview->nav_popup) + { + gtk_widget_destroy (preview->nav_popup); + preview->nav_popup = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_scrolled_preview_area_realize (GtkWidget *widget, + GimpScrolledPreview *preview) +{ + GdkDisplay *display = gtk_widget_get_display (widget); + + g_return_if_fail (preview->cursor_move == NULL); + + preview->cursor_move = gdk_cursor_new_for_display (display, GDK_HAND1); +} + +static void +gimp_scrolled_preview_area_unrealize (GtkWidget *widget, + GimpScrolledPreview *preview) +{ + if (preview->cursor_move) + { + gdk_cursor_unref (preview->cursor_move); + preview->cursor_move = NULL; + } +} + +static void +gimp_scrolled_preview_hscr_update (GimpScrolledPreview *preview) +{ + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + gint width; + + width = GIMP_PREVIEW (preview)->xmax - GIMP_PREVIEW (preview)->xmin; + + gtk_adjustment_configure (adj, + gtk_adjustment_get_value (adj), + 0, width, + 1.0, + MAX (GIMP_PREVIEW (preview)->width / 2.0, 1.0), + GIMP_PREVIEW (preview)->width); +} + +static void +gimp_scrolled_preview_vscr_update (GimpScrolledPreview *preview) +{ + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + gint height; + + height = GIMP_PREVIEW (preview)->ymax - GIMP_PREVIEW (preview)->ymin; + + gtk_adjustment_configure (adj, + gtk_adjustment_get_value (adj), + 0, height, + 1.0, + MAX (GIMP_PREVIEW (preview)->height / 2.0, 1.0), + GIMP_PREVIEW (preview)->height); +} + +static void +gimp_scrolled_preview_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpScrolledPreview *preview) +{ + GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + gint width = GIMP_PREVIEW (preview)->xmax - GIMP_PREVIEW (preview)->xmin; + gint height = GIMP_PREVIEW (preview)->ymax - GIMP_PREVIEW (preview)->ymin; + + gimp_scrolled_preview_freeze (preview); + + GIMP_PREVIEW (preview)->width = MIN (width, allocation->width); + GIMP_PREVIEW (preview)->height = MIN (height, allocation->height); + + gimp_scrolled_preview_hscr_update (preview); + + switch (priv->hscr_policy) + { + case GTK_POLICY_AUTOMATIC: + gtk_widget_set_visible (preview->hscr, + width > GIMP_PREVIEW (preview)->width); + break; + + case GTK_POLICY_ALWAYS: + gtk_widget_show (preview->hscr); + break; + + case GTK_POLICY_NEVER: + gtk_widget_hide (preview->hscr); + break; + } + + gimp_scrolled_preview_vscr_update (preview); + + switch (priv->vscr_policy) + { + case GTK_POLICY_AUTOMATIC: + gtk_widget_set_visible (preview->vscr, + height > GIMP_PREVIEW (preview)->height); + break; + + case GTK_POLICY_ALWAYS: + gtk_widget_show (preview->vscr); + break; + + case GTK_POLICY_NEVER: + gtk_widget_hide (preview->vscr); + break; + } + + gtk_widget_set_visible (preview->nav_icon, + gtk_widget_get_visible (preview->vscr) && + gtk_widget_get_visible (preview->hscr) && + GIMP_PREVIEW_GET_CLASS (preview)->draw_thumb); + + gimp_scrolled_preview_thaw (preview); +} + +static gboolean +gimp_scrolled_preview_area_event (GtkWidget *area, + GdkEvent *event, + GimpScrolledPreview *preview) +{ + GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + GdkEventButton *button_event = (GdkEventButton *) event; + GdkCursor *cursor; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + switch (button_event->button) + { + case 1: + case 2: + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (area), + GDK_FLEUR); + + if (gdk_pointer_grab (gtk_widget_get_window (area), TRUE, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK, + NULL, cursor, + gdk_event_get_time (event)) == GDK_GRAB_SUCCESS) + { + gtk_widget_get_pointer (area, &priv->drag_x, &priv->drag_y); + + priv->drag_xoff = GIMP_PREVIEW (preview)->xoff; + priv->drag_yoff = GIMP_PREVIEW (preview)->yoff; + priv->in_drag = TRUE; + gtk_grab_add (area); + } + + gdk_cursor_unref (cursor); + + break; + + case 3: + return TRUE; + } + break; + + case GDK_BUTTON_RELEASE: + if (priv->in_drag && + (button_event->button == 1 || button_event->button == 2)) + { + gdk_display_pointer_ungrab (gtk_widget_get_display (area), + gdk_event_get_time (event)); + + gtk_grab_remove (area); + priv->in_drag = FALSE; + } + break; + + case GDK_MOTION_NOTIFY: + if (priv->in_drag) + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + GtkAdjustment *hadj; + GtkAdjustment *vadj; + gint x, y; + + hadj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + vadj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + + gtk_widget_get_pointer (area, &x, &y); + + x = priv->drag_xoff - (x - priv->drag_x); + y = priv->drag_yoff - (y - priv->drag_y); + + x = CLAMP (x, + gtk_adjustment_get_lower (hadj), + gtk_adjustment_get_upper (hadj) - + gtk_adjustment_get_page_size (hadj)); + y = CLAMP (y, + gtk_adjustment_get_lower (vadj), + gtk_adjustment_get_upper (vadj) - + gtk_adjustment_get_page_size (vadj)); + + if (GIMP_PREVIEW (preview)->xoff != x || + GIMP_PREVIEW (preview)->yoff != y) + { + gtk_adjustment_set_value (hadj, x); + gtk_adjustment_set_value (vadj, y); + + gimp_preview_draw (GIMP_PREVIEW (preview)); + gimp_preview_invalidate (GIMP_PREVIEW (preview)); + } + + gdk_event_request_motions (mevent); + } + break; + + case GDK_SCROLL: + { + GdkEventScroll *sevent = (GdkEventScroll *) event; + GdkScrollDirection direction = sevent->direction; + GtkAdjustment *adj; + gfloat value; + + /* Ctrl-Scroll is reserved for zooming */ + if (sevent->state & GDK_CONTROL_MASK) + return FALSE; + + if (sevent->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_UP: + case GDK_SCROLL_DOWN: + default: + adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + break; + + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_LEFT: + adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + break; + } + + value = gtk_adjustment_get_value (adj); + + switch (direction) + { + case GDK_SCROLL_UP: + case GDK_SCROLL_LEFT: + value -= gtk_adjustment_get_page_increment (adj) / 2; + break; + + case GDK_SCROLL_DOWN: + case GDK_SCROLL_RIGHT: + value += gtk_adjustment_get_page_increment (adj) / 2; + break; + } + + gtk_adjustment_set_value (adj, + CLAMP (value, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_page_size (adj))); + } + break; + + default: + break; + } + + return FALSE; +} + +static void +gimp_scrolled_preview_h_scroll (GtkAdjustment *hadj, + GimpPreview *preview) +{ + GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + preview->xoff = gtk_adjustment_get_value (hadj); + + gimp_preview_area_set_offsets (GIMP_PREVIEW_AREA (preview->area), + preview->xoff, preview->yoff); + + if (! (priv->in_drag || priv->frozen)) + { + gimp_preview_draw (preview); + gimp_preview_invalidate (preview); + } +} + +static void +gimp_scrolled_preview_v_scroll (GtkAdjustment *vadj, + GimpPreview *preview) +{ + GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + preview->yoff = gtk_adjustment_get_value (vadj); + + gimp_preview_area_set_offsets (GIMP_PREVIEW_AREA (preview->area), + preview->xoff, preview->yoff); + + if (! (priv->in_drag || priv->frozen)) + { + gimp_preview_draw (preview); + gimp_preview_invalidate (preview); + } +} + +static gboolean +gimp_scrolled_preview_nav_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpScrolledPreview *preview) +{ + GimpPreview *gimp_preview = GIMP_PREVIEW (preview); + GtkAdjustment *adj; + + if (preview->nav_popup) + return TRUE; + + if (event->type == GDK_BUTTON_PRESS && event->button == 1) + { + GtkStyle *style = gtk_widget_get_style (widget); + GtkWidget *outer; + GtkWidget *inner; + GtkWidget *area; + GdkCursor *cursor; + gint x, y; + gdouble h, v; + + preview->nav_popup = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_screen (GTK_WINDOW (preview->nav_popup), + gtk_widget_get_screen (widget)); + + outer = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (outer), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (preview->nav_popup), outer); + gtk_widget_show (outer); + + inner = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (inner), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (outer), inner); + gtk_widget_show (inner); + + area = g_object_new (GIMP_TYPE_PREVIEW_AREA, + "check-size", GIMP_CHECK_SIZE_SMALL_CHECKS, + "check-type", GIMP_PREVIEW_AREA (gimp_preview->area)->check_type, + NULL); + + gtk_container_add (GTK_CONTAINER (inner), area); + + g_signal_connect (area, "event", + G_CALLBACK (gimp_scrolled_preview_nav_popup_event), + preview); + g_signal_connect_after (area, "expose-event", + G_CALLBACK (gimp_scrolled_preview_nav_popup_expose), + preview); + + GIMP_PREVIEW_GET_CLASS (preview)->draw_thumb (gimp_preview, + GIMP_PREVIEW_AREA (area), + POPUP_SIZE, POPUP_SIZE); + gtk_widget_realize (area); + gtk_widget_show (area); + + gdk_window_get_origin (gtk_widget_get_window (widget), &x, &y); + + adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + h = ((gtk_adjustment_get_value (adj) / + gtk_adjustment_get_upper (adj)) + + (gtk_adjustment_get_page_size (adj) / + gtk_adjustment_get_upper (adj)) / 2.0); + + adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + v = ((gtk_adjustment_get_value (adj) / + gtk_adjustment_get_upper (adj)) + + (gtk_adjustment_get_page_size (adj) / + gtk_adjustment_get_upper (adj)) / 2.0); + + x += event->x - h * (gdouble) GIMP_PREVIEW_AREA (area)->width; + y += event->y - v * (gdouble) GIMP_PREVIEW_AREA (area)->height; + + gtk_window_move (GTK_WINDOW (preview->nav_popup), + x - 2 * style->xthickness, + y - 2 * style->ythickness); + + gtk_widget_show (preview->nav_popup); + + gtk_grab_add (area); + + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), + GDK_FLEUR); + + gdk_pointer_grab (gtk_widget_get_window (area), TRUE, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK, + gtk_widget_get_window (area), cursor, + event->time); + + gdk_cursor_unref (cursor); + } + + return TRUE; +} + +static gboolean +gimp_scrolled_preview_nav_popup_event (GtkWidget *widget, + GdkEvent *event, + GimpScrolledPreview *preview) +{ + switch (event->type) + { + case GDK_BUTTON_RELEASE: + { + GdkEventButton *button_event = (GdkEventButton *) event; + + if (button_event->button == 1) + { + gtk_grab_remove (widget); + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + button_event->time); + + gtk_widget_destroy (preview->nav_popup); + preview->nav_popup = NULL; + } + } + break; + + case GDK_MOTION_NOTIFY: + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + GtkAdjustment *hadj; + GtkAdjustment *vadj; + GtkAllocation allocation; + gint cx, cy; + gdouble x, y; + + hadj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + vadj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + + gtk_widget_get_allocation (widget, &allocation); + + gtk_widget_get_pointer (widget, &cx, &cy); + + x = cx * (gtk_adjustment_get_upper (hadj) - + gtk_adjustment_get_lower (hadj)) / allocation.width; + y = cy * (gtk_adjustment_get_upper (vadj) - + gtk_adjustment_get_lower (vadj)) / allocation.height; + + x += (gtk_adjustment_get_lower (hadj) - + gtk_adjustment_get_page_size (hadj) / 2); + y += (gtk_adjustment_get_lower (vadj) - + gtk_adjustment_get_page_size (vadj) / 2); + + gtk_adjustment_set_value (hadj, + CLAMP (x, + gtk_adjustment_get_lower (hadj), + gtk_adjustment_get_upper (hadj) - + gtk_adjustment_get_page_size (hadj))); + gtk_adjustment_set_value (vadj, + CLAMP (y, + gtk_adjustment_get_lower (vadj), + gtk_adjustment_get_upper (vadj) - + gtk_adjustment_get_page_size (vadj))); + + gtk_widget_queue_draw (widget); + gdk_window_process_updates (gtk_widget_get_window (widget), FALSE); + + gdk_event_request_motions (mevent); + } + break; + + default: + break; + } + + return FALSE; +} + +static gboolean +gimp_scrolled_preview_nav_popup_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpScrolledPreview *preview) +{ + GtkAdjustment *adj; + GtkAllocation allocation; + cairo_t *cr; + gdouble x, y; + gdouble w, h; + + adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + + gtk_widget_get_allocation (widget, &allocation); + + x = (gtk_adjustment_get_value (adj) / + (gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_lower (adj))); + w = (gtk_adjustment_get_page_size (adj) / + (gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_lower (adj))); + + adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + + y = (gtk_adjustment_get_value (adj) / + (gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_lower (adj))); + h = (gtk_adjustment_get_page_size (adj) / + (gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_lower (adj))); + + if (w >= 1.0 && h >= 1.0) + return FALSE; + + x = floor (x * (gdouble) allocation.width); + y = floor (y * (gdouble) allocation.height); + w = MAX (1, ceil (w * (gdouble) allocation.width)); + h = MAX (1, ceil (h * (gdouble) allocation.height)); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_rectangle (cr, + 0, 0, allocation.width, allocation.height); + + cairo_rectangle (cr, x, y, w, h); + + cairo_set_source_rgba (cr, 0, 0, 0, 0.5); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill (cr); + + cairo_rectangle (cr, x, y, w, h); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_set_line_width (cr, 2); + cairo_stroke (cr); + + cairo_destroy (cr); + + return FALSE; +} + +static void +gimp_scrolled_preview_set_cursor (GimpPreview *preview) +{ + if (! gtk_widget_get_realized (preview->area)) + return; + + if (preview->xmax - preview->xmin > preview->width || + preview->ymax - preview->ymin > preview->height) + { + gdk_window_set_cursor (gtk_widget_get_window (preview->area), + GIMP_SCROLLED_PREVIEW (preview)->cursor_move); + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (preview->area), + preview->default_cursor); + } +} + +/** + * gimp_scrolled_preview_set_position: + * @preview: a #GimpScrolledPreview + * @x: horizontal scroll offset + * @y: vertical scroll offset + * + * Since: 2.4 + **/ +void +gimp_scrolled_preview_set_position (GimpScrolledPreview *preview, + gint x, + gint y) +{ + GtkAdjustment *adj; + + g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview)); + + gimp_scrolled_preview_freeze (preview); + + gimp_scrolled_preview_hscr_update (preview); + gimp_scrolled_preview_vscr_update (preview); + + adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr)); + gtk_adjustment_set_value (adj, x - GIMP_PREVIEW (preview)->xmin); + + adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr)); + gtk_adjustment_set_value (adj, y - GIMP_PREVIEW (preview)->ymin); + + gimp_scrolled_preview_thaw (preview); +} + +/** + * gimp_scrolled_preview_set_policy + * @preview: a #GimpScrolledPreview + * @hscrollbar_policy: policy for horizontal scrollbar + * @vscrollbar_policy: policy for vertical scrollbar + * + * Since: 2.4 + **/ +void +gimp_scrolled_preview_set_policy (GimpScrolledPreview *preview, + GtkPolicyType hscrollbar_policy, + GtkPolicyType vscrollbar_policy) +{ + GimpScrolledPreviewPrivate *priv; + + g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview)); + + priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + priv->hscr_policy = hscrollbar_policy; + priv->vscr_policy = vscrollbar_policy; + + gtk_widget_queue_resize (GIMP_PREVIEW (preview)->area); +} + + +/** + * gimp_scrolled_preview_freeze: + * @preview: a #GimpScrolledPreview + * + * While the @preview is frozen, it is not going to redraw itself in + * response to scroll events. + * + * This function should only be used to implement widgets derived from + * #GimpScrolledPreview. There is no point in calling this from a plug-in. + * + * Since: 2.4 + **/ +void +gimp_scrolled_preview_freeze (GimpScrolledPreview *preview) +{ + GimpScrolledPreviewPrivate *priv; + + g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview)); + + priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + priv->frozen++; +} + +/** + * gimp_scrolled_preview_thaw: + * @preview: a #GimpScrolledPreview + * + * While the @preview is frozen, it is not going to redraw itself in + * response to scroll events. + * + * This function should only be used to implement widgets derived from + * #GimpScrolledPreview. There is no point in calling this from a plug-in. + * + * Since: 2.4 + **/ +void +gimp_scrolled_preview_thaw (GimpScrolledPreview *preview) +{ + GimpScrolledPreviewPrivate *priv; + + g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview)); + + priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview); + + g_return_if_fail (priv->frozen > 0); + + priv->frozen--; + + if (! priv->frozen) + { + gimp_preview_draw (GIMP_PREVIEW (preview)); + gimp_preview_invalidate (GIMP_PREVIEW (preview)); + } +} diff --git a/libgimpwidgets/gimpscrolledpreview.h b/libgimpwidgets/gimpscrolledpreview.h new file mode 100644 index 0000000..92a8087 --- /dev/null +++ b/libgimpwidgets/gimpscrolledpreview.h @@ -0,0 +1,90 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpscrolledpreview.h + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_SCROLLED_PREVIEW_H__ +#define __GIMP_SCROLLED_PREVIEW_H__ + +#include "gimppreview.h" + +G_BEGIN_DECLS + + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_SCROLLED_PREVIEW (gimp_scrolled_preview_get_type ()) +#define GIMP_SCROLLED_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SCROLLED_PREVIEW, GimpScrolledPreview)) +#define GIMP_SCROLLED_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SCROLLED_PREVIEW, GimpScrolledPreviewClass)) +#define GIMP_IS_SCROLLED_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SCROLLED_PREVIEW)) +#define GIMP_IS_SCROLLED_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SCROLLED_PREVIEW)) +#define GIMP_SCROLLED_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SCROLLED_PREVIEW, GimpScrolledPreviewClass)) + + +typedef struct _GimpScrolledPreviewClass GimpScrolledPreviewClass; + +struct _GimpScrolledPreview +{ + GimpPreview parent_instance; + + /*< protected >*/ + GtkWidget *hscr; + GtkWidget *vscr; + GtkWidget *nav_icon; + GtkWidget *nav_popup; + GdkCursor *cursor_move; + gpointer nav_gc; /* unused */ + + /*< private >*/ + gpointer priv; +}; + +struct _GimpScrolledPreviewClass +{ + GimpPreviewClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_scrolled_preview_get_type (void) G_GNUC_CONST; + +void gimp_scrolled_preview_set_position (GimpScrolledPreview *preview, + gint x, + gint y); +void gimp_scrolled_preview_set_policy (GimpScrolledPreview *preview, + GtkPolicyType hscrollbar_policy, + GtkPolicyType vscrollbar_policy); + +/* only for use from derived widgets */ +void gimp_scrolled_preview_freeze (GimpScrolledPreview *preview); +void gimp_scrolled_preview_thaw (GimpScrolledPreview *preview); + + +G_END_DECLS + +#endif /* __GIMP_SCROLLED_PREVIEW_H__ */ diff --git a/libgimpwidgets/gimpsizeentry.c b/libgimpwidgets/gimpsizeentry.c new file mode 100644 index 0000000..497899b --- /dev/null +++ b/libgimpwidgets/gimpsizeentry.c @@ -0,0 +1,1594 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpsizeentry.c + * Copyright (C) 1999-2000 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. 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 "gimpwidgets.h" + +#include "gimpeevl.h" +#include "gimpsizeentry.h" + + +/** + * SECTION: gimpsizeentry + * @title: GimpSizeEntry + * @short_description: Widget for entering pixel values and resolutions. + * @see_also: #GimpUnit, #GimpUnitComboBox, gimp_coordinates_new() + * + * This widget is used to enter pixel distances/sizes and resolutions. + * + * You can specify the number of fields the widget should provide. For + * each field automatic mappings are performed between the field's + * "reference value" and its "value". + * + * There is a #GimpUnitComboBox right of the entry fields which lets + * you specify the #GimpUnit of the displayed values. + * + * For each field, there can be one or two #GtkSpinButton's to enter + * "value" and "reference value". If you specify @show_refval as + * #FALSE in gimp_size_entry_new() there will be only one + * #GtkSpinButton and the #GimpUnitComboBox will contain an item for + * selecting GIMP_UNIT_PIXEL. + * + * The "reference value" is either of GIMP_UNIT_PIXEL or dpi, + * depending on which #GimpSizeEntryUpdatePolicy you specify in + * gimp_size_entry_new(). The "value" is either the size in pixels + * mapped to the size in a real-world-unit (see #GimpUnit) or the dpi + * value mapped to pixels per real-world-unit. + **/ + + +#define SIZE_MAX_VALUE 500000.0 + +#define GIMP_SIZE_ENTRY_DIGITS(unit) (MIN (gimp_unit_get_digits (unit), 5) + 1) + + +enum +{ + VALUE_CHANGED, + REFVAL_CHANGED, + UNIT_CHANGED, + LAST_SIGNAL +}; + + +struct _GimpSizeEntryField +{ + GimpSizeEntry *gse; + + gdouble resolution; + gdouble lower; + gdouble upper; + + GtkAdjustment *value_adjustment; + GtkWidget *value_spinbutton; + gdouble value; + gdouble min_value; + gdouble max_value; + + GtkAdjustment *refval_adjustment; + GtkWidget *refval_spinbutton; + gdouble refval; + gdouble min_refval; + gdouble max_refval; + gint refval_digits; + + gint stop_recursion; +}; + + +static void gimp_size_entry_finalize (GObject *object); +static void gimp_size_entry_update_value (GimpSizeEntryField *gsef, + gdouble value); +static void gimp_size_entry_value_callback (GtkWidget *widget, + gpointer data); +static void gimp_size_entry_update_refval (GimpSizeEntryField *gsef, + gdouble refval); +static void gimp_size_entry_refval_callback (GtkWidget *widget, + gpointer data); +static void gimp_size_entry_update_unit (GimpSizeEntry *gse, + GimpUnit unit); +static void gimp_size_entry_unit_callback (GtkWidget *widget, + GimpSizeEntry *sizeentry); +static void gimp_size_entry_attach_eevl (GtkSpinButton *spin_button, + GimpSizeEntryField *gsef); +static gint gimp_size_entry_eevl_input_callback (GtkSpinButton *spinner, + gdouble *return_val, + gpointer *data); +static gboolean gimp_size_entry_eevl_unit_resolver (const gchar *ident, + GimpEevlQuantity *factor, + gdouble *offset, + gpointer data); + + +G_DEFINE_TYPE (GimpSizeEntry, gimp_size_entry, GTK_TYPE_TABLE) + +#define parent_class gimp_size_entry_parent_class + +static guint gimp_size_entry_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_size_entry_class_init (GimpSizeEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gimp_size_entry_signals[VALUE_CHANGED] = + g_signal_new ("value-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpSizeEntryClass, value_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_size_entry_signals[REFVAL_CHANGED] = + g_signal_new ("refval-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpSizeEntryClass, refval_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_size_entry_signals[UNIT_CHANGED] = + g_signal_new ("unit-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpSizeEntryClass, unit_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->finalize = gimp_size_entry_finalize; + + klass->value_changed = NULL; + klass->refval_changed = NULL; + klass->unit_changed = NULL; +} + +static void +gimp_size_entry_init (GimpSizeEntry *gse) +{ + gse->fields = NULL; + gse->number_of_fields = 0; + gse->unitmenu = NULL; + gse->unit = GIMP_UNIT_PIXEL; + gse->menu_show_pixels = TRUE; + gse->menu_show_percent = TRUE; + gse->show_refval = FALSE; + gse->update_policy = GIMP_SIZE_ENTRY_UPDATE_NONE; +} + +static void +gimp_size_entry_finalize (GObject *object) +{ + GimpSizeEntry *gse = GIMP_SIZE_ENTRY (object); + + if (gse->fields) + { + GSList *list; + + for (list = gse->fields; list; list = list->next) + g_slice_free (GimpSizeEntryField, list->data); + + g_slist_free (gse->fields); + gse->fields = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gimp_size_entry_new: + * @number_of_fields: The number of input fields. + * @unit: The initial unit. + * @unit_format: A printf-like unit-format string as is used with + * gimp_unit_menu_new(). + * @menu_show_pixels: %TRUE if the unit menu should contain an item for + * GIMP_UNIT_PIXEL (ignored if the @update_policy is not + * GIMP_SIZE_ENTRY_UPDATE_NONE). + * @menu_show_percent: %TRUE if the unit menu should contain an item for + * GIMP_UNIT_PERCENT. + * @show_refval: %TRUE if you want an extra "reference value" + * spinbutton per input field. + * @spinbutton_width: The minimal horizontal size of the #GtkSpinButton's. + * @update_policy: How the automatic pixel <-> real-world-unit + * calculations should be done. + * + * Creates a new #GimpSizeEntry widget. + * + * To have all automatic calculations performed correctly, set up the + * widget in the following order: + * + * 1. gimp_size_entry_new() + * + * 2. (for each additional input field) gimp_size_entry_add_field() + * + * 3. gimp_size_entry_set_unit() + * + * For each input field: + * + * 4. gimp_size_entry_set_resolution() + * + * 5. gimp_size_entry_set_refval_boundaries() + * (or gimp_size_entry_set_value_boundaries()) + * + * 6. gimp_size_entry_set_size() + * + * 7. gimp_size_entry_set_refval() (or gimp_size_entry_set_value()) + * + * The #GimpSizeEntry is derived from #GtkTable and will have + * an empty border of one cell width on each side plus an empty column left + * of the #GimpUnitComboBox to allow the caller to add labels or a + * #GimpChainButton. + * + * Returns: A Pointer to the new #GimpSizeEntry widget. + **/ +GtkWidget * +gimp_size_entry_new (gint number_of_fields, + GimpUnit unit, + const gchar *unit_format, + gboolean menu_show_pixels, + gboolean menu_show_percent, + gboolean show_refval, + gint spinbutton_width, + GimpSizeEntryUpdatePolicy update_policy) +{ + GimpSizeEntry *gse; + GimpUnitStore *store; + gint i; + + g_return_val_if_fail ((number_of_fields >= 0) && (number_of_fields <= 16), + NULL); + + gse = g_object_new (GIMP_TYPE_SIZE_ENTRY, NULL); + + gse->number_of_fields = number_of_fields; + gse->unit = unit; + gse->show_refval = show_refval; + gse->update_policy = update_policy; + + gtk_table_resize (GTK_TABLE (gse), + 1 + gse->show_refval + 2, + number_of_fields + 1 + 3); + + /* show the 'pixels' menu entry only if we are a 'size' sizeentry and + * don't have the reference value spinbutton + */ + if ((update_policy == GIMP_SIZE_ENTRY_UPDATE_RESOLUTION) || + (show_refval == TRUE)) + gse->menu_show_pixels = FALSE; + else + gse->menu_show_pixels = menu_show_pixels; + + /* show the 'percent' menu entry only if we are a 'size' sizeentry + */ + if (update_policy == GIMP_SIZE_ENTRY_UPDATE_RESOLUTION) + gse->menu_show_percent = FALSE; + else + gse->menu_show_percent = menu_show_percent; + + for (i = 0; i < number_of_fields; i++) + { + GimpSizeEntryField *gsef = g_slice_new0 (GimpSizeEntryField); + gint digits; + + gse->fields = g_slist_append (gse->fields, gsef); + + gsef->gse = gse; + gsef->resolution = 1.0; /* just to avoid division by zero */ + gsef->lower = 0.0; + gsef->upper = 100.0; + gsef->value = 0; + gsef->min_value = 0; + gsef->max_value = SIZE_MAX_VALUE; + gsef->refval_adjustment = NULL; + gsef->value_adjustment = NULL; + gsef->refval = 0; + gsef->min_refval = 0; + gsef->max_refval = SIZE_MAX_VALUE; + gsef->refval_digits = + (update_policy == GIMP_SIZE_ENTRY_UPDATE_SIZE) ? 0 : 3; + gsef->stop_recursion = 0; + + digits = ((unit == GIMP_UNIT_PIXEL) ? + gsef->refval_digits : ((unit == GIMP_UNIT_PERCENT) ? + 2 : GIMP_SIZE_ENTRY_DIGITS (unit))); + + gsef->value_adjustment = (GtkAdjustment *) + gtk_adjustment_new (gsef->value, + gsef->min_value, gsef->max_value, + 1.0, 10.0, 0.0); + gsef->value_spinbutton = gimp_spin_button_new (gsef->value_adjustment, + 1.0, digits); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (gsef->value_spinbutton), + TRUE); + + gimp_size_entry_attach_eevl (GTK_SPIN_BUTTON (gsef->value_spinbutton), + gsef); + + if (spinbutton_width > 0) + { + if (spinbutton_width < 17) + gtk_entry_set_width_chars (GTK_ENTRY (gsef->value_spinbutton), + spinbutton_width); + else + gtk_widget_set_size_request (gsef->value_spinbutton, + spinbutton_width, -1); + } + + gtk_table_attach_defaults (GTK_TABLE (gse), gsef->value_spinbutton, + i+1, i+2, + gse->show_refval+1, gse->show_refval+2); + g_signal_connect (gsef->value_adjustment, "value-changed", + G_CALLBACK (gimp_size_entry_value_callback), + gsef); + + gtk_widget_show (gsef->value_spinbutton); + + if (gse->show_refval) + { + gsef->refval_adjustment = (GtkAdjustment *) + gtk_adjustment_new (gsef->refval, + gsef->min_refval, gsef->max_refval, + 1.0, 10.0, 0.0); + gsef->refval_spinbutton = gimp_spin_button_new (gsef->refval_adjustment, + 1.0, + gsef->refval_digits); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (gsef->refval_spinbutton), + TRUE); + + gtk_widget_set_size_request (gsef->refval_spinbutton, + spinbutton_width, -1); + gtk_table_attach_defaults (GTK_TABLE (gse), gsef->refval_spinbutton, + i + 1, i + 2, 1, 2); + g_signal_connect (gsef->refval_adjustment, + "value-changed", + G_CALLBACK (gimp_size_entry_refval_callback), + gsef); + + gtk_widget_show (gsef->refval_spinbutton); + } + + if (gse->menu_show_pixels && (unit == GIMP_UNIT_PIXEL) && + ! gse->show_refval) + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + gsef->refval_digits); + } + + store = gimp_unit_store_new (gse->number_of_fields); + gimp_unit_store_set_has_pixels (store, gse->menu_show_pixels); + gimp_unit_store_set_has_percent (store, gse->menu_show_percent); + + if (unit_format) + { + gchar *short_format = g_strdup (unit_format); + gchar *p; + + p = strstr (short_format, "%s"); + if (p) + strcpy (p, "%a"); + + p = strstr (short_format, "%p"); + if (p) + strcpy (p, "%a"); + + g_object_set (store, + "short-format", short_format, + "long-format", unit_format, + NULL); + + g_free (short_format); + } + + gse->unitmenu = gimp_unit_combo_box_new_with_model (store); + g_object_unref (store); + + gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (gse->unitmenu), unit); + + gtk_table_attach (GTK_TABLE (gse), gse->unitmenu, + i+2, i+3, + gse->show_refval+1, gse->show_refval+2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + g_signal_connect (gse->unitmenu, "changed", + G_CALLBACK (gimp_size_entry_unit_callback), + gse); + gtk_widget_show (gse->unitmenu); + + return GTK_WIDGET (gse); +} + + +/** + * gimp_size_entry_add_field: + * @gse: The sizeentry you want to add a field to. + * @value_spinbutton: The spinbutton to display the field's value. + * @refval_spinbutton: The spinbutton to display the field's reference value. + * + * Adds an input field to the #GimpSizeEntry. + * + * The new input field will have the index 0. If you specified @show_refval + * as %TRUE in gimp_size_entry_new() you have to pass an additional + * #GtkSpinButton to hold the reference value. If @show_refval was %FALSE, + * @refval_spinbutton will be ignored. + **/ +void +gimp_size_entry_add_field (GimpSizeEntry *gse, + GtkSpinButton *value_spinbutton, + GtkSpinButton *refval_spinbutton) +{ + GimpSizeEntryField *gsef; + gint digits; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail (GTK_IS_SPIN_BUTTON (value_spinbutton)); + + if (gse->show_refval) + { + g_return_if_fail (GTK_IS_SPIN_BUTTON (refval_spinbutton)); + } + + gsef = g_slice_new0 (GimpSizeEntryField); + + gse->fields = g_slist_prepend (gse->fields, gsef); + gse->number_of_fields++; + + gsef->gse = gse; + gsef->resolution = 1.0; /* just to avoid division by zero */ + gsef->lower = 0.0; + gsef->upper = 100.0; + gsef->value = 0; + gsef->min_value = 0; + gsef->max_value = SIZE_MAX_VALUE; + gsef->refval = 0; + gsef->min_refval = 0; + gsef->max_refval = SIZE_MAX_VALUE; + gsef->refval_digits = + (gse->update_policy == GIMP_SIZE_ENTRY_UPDATE_SIZE) ? 0 : 3; + gsef->stop_recursion = 0; + + gsef->value_adjustment = gtk_spin_button_get_adjustment (value_spinbutton); + gsef->value_spinbutton = GTK_WIDGET (value_spinbutton); + g_signal_connect (gsef->value_adjustment, "value-changed", + G_CALLBACK (gimp_size_entry_value_callback), + gsef); + + gimp_size_entry_attach_eevl (GTK_SPIN_BUTTON (gsef->value_spinbutton), + gsef); + + if (gse->show_refval) + { + gsef->refval_adjustment = gtk_spin_button_get_adjustment (refval_spinbutton); + gsef->refval_spinbutton = GTK_WIDGET (refval_spinbutton); + g_signal_connect (gsef->refval_adjustment, "value-changed", + G_CALLBACK (gimp_size_entry_refval_callback), + gsef); + } + + digits = ((gse->unit == GIMP_UNIT_PIXEL) ? gsef->refval_digits : + (gse->unit == GIMP_UNIT_PERCENT) ? 2 : + GIMP_SIZE_ENTRY_DIGITS (gse->unit)); + + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (value_spinbutton), digits); + + if (gse->menu_show_pixels && + !gse->show_refval && + (gse->unit == GIMP_UNIT_PIXEL)) + { + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + gsef->refval_digits); + } +} + +/** + * gimp_size_entry_attach_label: + * @gse: The sizeentry you want to add a label to. + * @text: The text of the label. + * @row: The row where the label will be attached. + * @column: The column where the label will be attached. + * @alignment: The horizontal alignment of the label. + * + * Attaches a #GtkLabel to the #GimpSizeEntry (which is a #GtkTable). + * + * Returns: A pointer to the new #GtkLabel widget. + **/ +GtkWidget * +gimp_size_entry_attach_label (GimpSizeEntry *gse, + const gchar *text, + gint row, + gint column, + gfloat alignment) +{ + GtkWidget *label; + + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), NULL); + g_return_val_if_fail (text != NULL, NULL); + + label = gtk_label_new_with_mnemonic (text); + + if (column == 0) + { + GList *children; + GList *list; + + children = gtk_container_get_children (GTK_CONTAINER (gse)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + gint left_attach; + gint top_attach; + + gtk_container_child_get (GTK_CONTAINER (gse), child, + "left-attach", &left_attach, + "top-attach", &top_attach, + NULL); + + if (left_attach == 1 && top_attach == row) + { + gtk_label_set_mnemonic_widget (GTK_LABEL (label), child); + break; + } + } + + g_list_free (children); + } + + gtk_label_set_xalign (GTK_LABEL (label), alignment); + + gtk_table_attach (GTK_TABLE (gse), label, column, column+1, row, row+1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + return label; +} + + +/** + * gimp_size_entry_set_resolution: + * @gse: The sizeentry you want to set a resolution for. + * @field: The index of the field you want to set the resolution for. + * @resolution: The new resolution (in dpi) for the chosen @field. + * @keep_size: %TRUE if the @field's size in pixels should stay the same. + * %FALSE if the @field's size in units should stay the same. + * + * Sets the resolution (in dpi) for field # @field of the #GimpSizeEntry. + * + * The @resolution passed will be clamped to fit in + * [#GIMP_MIN_RESOLUTION..#GIMP_MAX_RESOLUTION]. + * + * This function does nothing if the #GimpSizeEntryUpdatePolicy specified in + * gimp_size_entry_new() doesn't equal to #GIMP_SIZE_ENTRY_UPDATE_SIZE. + **/ +void +gimp_size_entry_set_resolution (GimpSizeEntry *gse, + gint field, + gdouble resolution, + gboolean keep_size) +{ + GimpSizeEntryField *gsef; + gfloat val; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + + resolution = CLAMP (resolution, GIMP_MIN_RESOLUTION, GIMP_MAX_RESOLUTION); + + gsef = (GimpSizeEntryField*) g_slist_nth_data (gse->fields, field); + gsef->resolution = resolution; + + val = gsef->value; + + gsef->stop_recursion = 0; + gimp_size_entry_set_refval_boundaries (gse, field, + gsef->min_refval, gsef->max_refval); + + if (! keep_size) + gimp_size_entry_set_value (gse, field, val); +} + + +/** + * gimp_size_entry_set_size: + * @gse: The sizeentry you want to set a size for. + * @field: The index of the field you want to set the size for. + * @lower: The reference value which will be treated as 0%. + * @upper: The reference value which will be treated as 100%. + * + * Sets the pixel values for field # @field of the #GimpSizeEntry + * which will be treated as 0% and 100%. + * + * These values will be used if you specified @menu_show_percent as %TRUE + * in gimp_size_entry_new() and the user has selected GIMP_UNIT_PERCENT in + * the #GimpSizeEntry's #GimpUnitComboBox. + * + * This function does nothing if the #GimpSizeEntryUpdatePolicy specified in + * gimp_size_entry_new() doesn't equal to GIMP_SIZE_ENTRY_UPDATE_SIZE. + **/ +void +gimp_size_entry_set_size (GimpSizeEntry *gse, + gint field, + gdouble lower, + gdouble upper) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + g_return_if_fail (lower <= upper); + + gsef = (GimpSizeEntryField*) g_slist_nth_data (gse->fields, field); + gsef->lower = lower; + gsef->upper = upper; + + gimp_size_entry_set_refval (gse, field, gsef->refval); +} + + +/** + * gimp_size_entry_set_value_boundaries: + * @gse: The sizeentry you want to set value boundaries for. + * @field: The index of the field you want to set value boundaries for. + * @lower: The new lower boundary of the value of the chosen @field. + * @upper: The new upper boundary of the value of the chosen @field. + * + * Limits the range of possible values which can be entered in field # @field + * of the #GimpSizeEntry. + * + * The current value of the @field will be clamped to fit in the @field's + * new boundaries. + * + * NOTE: In most cases you won't be interested in this function because the + * #GimpSizeEntry's purpose is to shield the programmer from unit + * calculations. Use gimp_size_entry_set_refval_boundaries() instead. + * Whatever you do, don't mix these calls. A size entry should either + * be clamped by the value or the reference value. + **/ +void +gimp_size_entry_set_value_boundaries (GimpSizeEntry *gse, + gint field, + gdouble lower, + gdouble upper) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + g_return_if_fail (lower <= upper); + + gsef = (GimpSizeEntryField*) g_slist_nth_data (gse->fields, field); + gsef->min_value = lower; + gsef->max_value = upper; + + g_object_freeze_notify (G_OBJECT (gsef->value_adjustment)); + + gtk_adjustment_set_lower (gsef->value_adjustment, gsef->min_value); + gtk_adjustment_set_upper (gsef->value_adjustment, gsef->max_value); + + if (gsef->stop_recursion) /* this is a hack (but useful ;-) */ + { + g_object_thaw_notify (G_OBJECT (gsef->value_adjustment)); + return; + } + + gsef->stop_recursion++; + switch (gsef->gse->update_policy) + { + case GIMP_SIZE_ENTRY_UPDATE_NONE: + break; + + case GIMP_SIZE_ENTRY_UPDATE_SIZE: + switch (gse->unit) + { + case GIMP_UNIT_PIXEL: + gimp_size_entry_set_refval_boundaries (gse, field, + gsef->min_value, + gsef->max_value); + break; + case GIMP_UNIT_PERCENT: + gimp_size_entry_set_refval_boundaries (gse, field, + gsef->lower + + (gsef->upper - gsef->lower) * + gsef->min_value / 100, + gsef->lower + + (gsef->upper - gsef->lower) * + gsef->max_value / 100); + break; + default: + gimp_size_entry_set_refval_boundaries (gse, field, + gsef->min_value * + gsef->resolution / + gimp_unit_get_factor (gse->unit), + gsef->max_value * + gsef->resolution / + gimp_unit_get_factor (gse->unit)); + break; + } + break; + + case GIMP_SIZE_ENTRY_UPDATE_RESOLUTION: + gimp_size_entry_set_refval_boundaries (gse, field, + gsef->min_value * + gimp_unit_get_factor (gse->unit), + gsef->max_value * + gimp_unit_get_factor (gse->unit)); + break; + + default: + break; + } + gsef->stop_recursion--; + + gimp_size_entry_set_value (gse, field, gsef->value); + + g_object_thaw_notify (G_OBJECT (gsef->value_adjustment)); +} + +/** + * gimp_size_entry_get_value: + * @gse: The sizeentry you want to know a value of. + * @field: The index of the field you want to know the value of. + * + * Returns the value of field # @field of the #GimpSizeEntry. + * + * The @value returned is a distance or resolution + * in the #GimpUnit the user has selected in the #GimpSizeEntry's + * #GimpUnitComboBox. + * + * NOTE: In most cases you won't be interested in this value because the + * #GimpSizeEntry's purpose is to shield the programmer from unit + * calculations. Use gimp_size_entry_get_refval() instead. + * + * Returns: The value of the chosen @field. + **/ +gdouble +gimp_size_entry_get_value (GimpSizeEntry *gse, + gint field) +{ + GimpSizeEntryField *gsef; + + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), 0); + g_return_val_if_fail ((field >= 0) && (field < gse->number_of_fields), 0); + + gsef = (GimpSizeEntryField *) g_slist_nth_data (gse->fields, field); + return gsef->value; +} + +static void +gimp_size_entry_update_value (GimpSizeEntryField *gsef, + gdouble value) +{ + if (gsef->stop_recursion > 1) + return; + + gsef->value = value; + + switch (gsef->gse->update_policy) + { + case GIMP_SIZE_ENTRY_UPDATE_NONE: + break; + + case GIMP_SIZE_ENTRY_UPDATE_SIZE: + switch (gsef->gse->unit) + { + case GIMP_UNIT_PIXEL: + gsef->refval = value; + break; + case GIMP_UNIT_PERCENT: + gsef->refval = + CLAMP (gsef->lower + (gsef->upper - gsef->lower) * value / 100, + gsef->min_refval, gsef->max_refval); + break; + default: + gsef->refval = + CLAMP (value * gsef->resolution / + gimp_unit_get_factor (gsef->gse->unit), + gsef->min_refval, gsef->max_refval); + break; + } + if (gsef->gse->show_refval) + gtk_adjustment_set_value (gsef->refval_adjustment, gsef->refval); + break; + + case GIMP_SIZE_ENTRY_UPDATE_RESOLUTION: + gsef->refval = + CLAMP (value * gimp_unit_get_factor (gsef->gse->unit), + gsef->min_refval, gsef->max_refval); + if (gsef->gse->show_refval) + gtk_adjustment_set_value (gsef->refval_adjustment, gsef->refval); + break; + + default: + break; + } + + g_signal_emit (gsef->gse, gimp_size_entry_signals[VALUE_CHANGED], 0); +} + +/** + * gimp_size_entry_set_value: + * @gse: The sizeentry you want to set a value for. + * @field: The index of the field you want to set a value for. + * @value: The new value for @field. + * + * Sets the value for field # @field of the #GimpSizeEntry. + * + * The @value passed is treated to be a distance or resolution + * in the #GimpUnit the user has selected in the #GimpSizeEntry's + * #GimpUnitComboBox. + * + * NOTE: In most cases you won't be interested in this value because the + * #GimpSizeEntry's purpose is to shield the programmer from unit + * calculations. Use gimp_size_entry_set_refval() instead. + **/ +void +gimp_size_entry_set_value (GimpSizeEntry *gse, + gint field, + gdouble value) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + + gsef = (GimpSizeEntryField *) g_slist_nth_data (gse->fields, field); + + value = CLAMP (value, gsef->min_value, gsef->max_value); + + gtk_adjustment_set_value (gsef->value_adjustment, value); + gimp_size_entry_update_value (gsef, value); +} + + +static void +gimp_size_entry_value_callback (GtkWidget *widget, + gpointer data) +{ + GimpSizeEntryField *gsef; + gdouble new_value; + + gsef = (GimpSizeEntryField *) data; + + new_value = gtk_adjustment_get_value (GTK_ADJUSTMENT (widget)); + + if (gsef->value != new_value) + gimp_size_entry_update_value (gsef, new_value); +} + + +/** + * gimp_size_entry_set_refval_boundaries: + * @gse: The sizeentry you want to set the reference value boundaries for. + * @field: The index of the field you want to set the reference value + * boundaries for. + * @lower: The new lower boundary of the reference value of the chosen @field. + * @upper: The new upper boundary of the reference value of the chosen @field. + * + * Limits the range of possible reference values which can be entered in + * field # @field of the #GimpSizeEntry. + * + * The current reference value of the @field will be clamped to fit in the + * @field's new boundaries. + **/ +void +gimp_size_entry_set_refval_boundaries (GimpSizeEntry *gse, + gint field, + gdouble lower, + gdouble upper) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + g_return_if_fail (lower <= upper); + + gsef = (GimpSizeEntryField *) g_slist_nth_data (gse->fields, field); + gsef->min_refval = lower; + gsef->max_refval = upper; + + if (gse->show_refval) + { + g_object_freeze_notify (G_OBJECT (gsef->refval_adjustment)); + + gtk_adjustment_set_lower (gsef->refval_adjustment, gsef->min_refval); + gtk_adjustment_set_upper (gsef->refval_adjustment, gsef->max_refval); + } + + if (gsef->stop_recursion) /* this is a hack (but useful ;-) */ + { + if (gse->show_refval) + g_object_thaw_notify (G_OBJECT (gsef->refval_adjustment)); + + return; + } + + gsef->stop_recursion++; + switch (gsef->gse->update_policy) + { + case GIMP_SIZE_ENTRY_UPDATE_NONE: + break; + + case GIMP_SIZE_ENTRY_UPDATE_SIZE: + switch (gse->unit) + { + case GIMP_UNIT_PIXEL: + gimp_size_entry_set_value_boundaries (gse, field, + gsef->min_refval, + gsef->max_refval); + break; + case GIMP_UNIT_PERCENT: + gimp_size_entry_set_value_boundaries (gse, field, + 100 * (gsef->min_refval - + gsef->lower) / + (gsef->upper - gsef->lower), + 100 * (gsef->max_refval - + gsef->lower) / + (gsef->upper - gsef->lower)); + break; + default: + gimp_size_entry_set_value_boundaries (gse, field, + gsef->min_refval * + gimp_unit_get_factor (gse->unit) / + gsef->resolution, + gsef->max_refval * + gimp_unit_get_factor (gse->unit) / + gsef->resolution); + break; + } + break; + + case GIMP_SIZE_ENTRY_UPDATE_RESOLUTION: + gimp_size_entry_set_value_boundaries (gse, field, + gsef->min_refval / + gimp_unit_get_factor (gse->unit), + gsef->max_refval / + gimp_unit_get_factor (gse->unit)); + break; + + default: + break; + } + gsef->stop_recursion--; + + gimp_size_entry_set_refval (gse, field, gsef->refval); + + if (gse->show_refval) + g_object_thaw_notify (G_OBJECT (gsef->refval_adjustment)); +} + +/** + * gimp_size_entry_set_refval_digits: + * @gse: The sizeentry you want to set the reference value digits for. + * @field: The index of the field you want to set the reference value for. + * @digits: The new number of decimal digits for the #GtkSpinButton which + * displays @field's reference value. + * + * Sets the decimal digits of field # @field of the #GimpSizeEntry to + * @digits. + * + * If you don't specify this value explicitly, the reference value's number + * of digits will equal to 0 for #GIMP_SIZE_ENTRY_UPDATE_SIZE and to 2 for + * #GIMP_SIZE_ENTRY_UPDATE_RESOLUTION. + **/ +void +gimp_size_entry_set_refval_digits (GimpSizeEntry *gse, + gint field, + gint digits) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + g_return_if_fail ((digits >= 0) && (digits <= 6)); + + gsef = (GimpSizeEntryField*) g_slist_nth_data (gse->fields, field); + gsef->refval_digits = digits; + + if (gse->update_policy == GIMP_SIZE_ENTRY_UPDATE_SIZE) + { + if (gse->show_refval) + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->refval_spinbutton), + gsef->refval_digits); + else if (gse->unit == GIMP_UNIT_PIXEL) + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + gsef->refval_digits); + } +} + +/** + * gimp_size_entry_get_refval: + * @gse: The sizeentry you want to know a reference value of. + * @field: The index of the field you want to know the reference value of. + * + * Returns the reference value for field # @field of the #GimpSizeEntry. + * + * The reference value is either a distance in pixels or a resolution + * in dpi, depending on which #GimpSizeEntryUpdatePolicy you chose in + * gimp_size_entry_new(). + * + * Returns: The reference value of the chosen @field. + **/ +gdouble +gimp_size_entry_get_refval (GimpSizeEntry *gse, + gint field) +{ + GimpSizeEntryField *gsef; + + /* return 1.0 to avoid division by zero */ + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), 1.0); + g_return_val_if_fail ((field >= 0) && (field < gse->number_of_fields), 1.0); + + gsef = (GimpSizeEntryField*) g_slist_nth_data (gse->fields, field); + return gsef->refval; +} + +static void +gimp_size_entry_update_refval (GimpSizeEntryField *gsef, + gdouble refval) +{ + if (gsef->stop_recursion > 1) + return; + + gsef->refval = refval; + + switch (gsef->gse->update_policy) + { + case GIMP_SIZE_ENTRY_UPDATE_NONE: + break; + + case GIMP_SIZE_ENTRY_UPDATE_SIZE: + switch (gsef->gse->unit) + { + case GIMP_UNIT_PIXEL: + gsef->value = refval; + break; + case GIMP_UNIT_PERCENT: + gsef->value = + CLAMP (100 * (refval - gsef->lower) / (gsef->upper - gsef->lower), + gsef->min_value, gsef->max_value); + break; + default: + gsef->value = + CLAMP (refval * gimp_unit_get_factor (gsef->gse->unit) / + gsef->resolution, + gsef->min_value, gsef->max_value); + break; + } + gtk_adjustment_set_value (gsef->value_adjustment, gsef->value); + break; + + case GIMP_SIZE_ENTRY_UPDATE_RESOLUTION: + gsef->value = + CLAMP (refval / gimp_unit_get_factor (gsef->gse->unit), + gsef->min_value, gsef->max_value); + gtk_adjustment_set_value (gsef->value_adjustment, gsef->value); + break; + + default: + break; + } + + g_signal_emit (gsef->gse, gimp_size_entry_signals[REFVAL_CHANGED], 0); +} + +/** + * gimp_size_entry_set_refval: + * @gse: The sizeentry you want to set a reference value for. + * @field: The index of the field you want to set the reference value for. + * @refval: The new reference value for @field. + * + * Sets the reference value for field # @field of the #GimpSizeEntry. + * + * The @refval passed is either a distance in pixels or a resolution in dpi, + * depending on which #GimpSizeEntryUpdatePolicy you chose in + * gimp_size_entry_new(). + **/ +void +gimp_size_entry_set_refval (GimpSizeEntry *gse, + gint field, + gdouble refval) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail ((field >= 0) && (field < gse->number_of_fields)); + + gsef = (GimpSizeEntryField *) g_slist_nth_data (gse->fields, field); + + refval = CLAMP (refval, gsef->min_refval, gsef->max_refval); + + if (gse->show_refval) + gtk_adjustment_set_value (gsef->refval_adjustment, refval); + + gimp_size_entry_update_refval (gsef, refval); +} + +static void +gimp_size_entry_refval_callback (GtkWidget *widget, + gpointer data) +{ + GimpSizeEntryField *gsef; + gdouble new_refval; + + gsef = (GimpSizeEntryField *) data; + + new_refval = gtk_adjustment_get_value (GTK_ADJUSTMENT (widget)); + + if (gsef->refval != new_refval) + gimp_size_entry_update_refval (gsef, new_refval); +} + + +/** + * gimp_size_entry_get_unit: + * @gse: The sizeentry you want to know the unit of. + * + * Returns the #GimpUnit the user has selected in the #GimpSizeEntry's + * #GimpUnitComboBox. + * + * Returns: The sizeentry's unit. + **/ +GimpUnit +gimp_size_entry_get_unit (GimpSizeEntry *gse) +{ + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), GIMP_UNIT_INCH); + + return gse->unit; +} + +static void +gimp_size_entry_update_unit (GimpSizeEntry *gse, + GimpUnit unit) +{ + GimpSizeEntryField *gsef; + gint i; + gint digits; + + gse->unit = unit; + + digits = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (gse), + "gimp-pixel-digits")); + + for (i = 0; i < gse->number_of_fields; i++) + { + gsef = (GimpSizeEntryField *) g_slist_nth_data (gse->fields, i); + + if (gse->update_policy == GIMP_SIZE_ENTRY_UPDATE_SIZE) + { + if (unit == GIMP_UNIT_PIXEL) + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + gsef->refval_digits + digits); + else if (unit == GIMP_UNIT_PERCENT) + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + 2 + digits); + else + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + GIMP_SIZE_ENTRY_DIGITS (unit) + digits); + } + else if (gse->update_policy == GIMP_SIZE_ENTRY_UPDATE_RESOLUTION) + { + digits = (gimp_unit_get_digits (GIMP_UNIT_INCH) - + gimp_unit_get_digits (unit)); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (gsef->value_spinbutton), + MAX (3 + digits, 3)); + } + + gsef->stop_recursion = 0; /* hack !!! */ + + gimp_size_entry_set_refval_boundaries (gse, i, + gsef->min_refval, + gsef->max_refval); + } + + g_signal_emit (gse, gimp_size_entry_signals[UNIT_CHANGED], 0); +} + + +/** + * gimp_size_entry_set_unit: + * @gse: The sizeentry you want to change the unit for. + * @unit: The new unit. + * + * Sets the #GimpSizeEntry's unit. The reference value for all fields will + * stay the same but the value in units or pixels per unit will change + * according to which #GimpSizeEntryUpdatePolicy you chose in + * gimp_size_entry_new(). + **/ +void +gimp_size_entry_set_unit (GimpSizeEntry *gse, + GimpUnit unit) +{ + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + g_return_if_fail (gse->menu_show_pixels || (unit != GIMP_UNIT_PIXEL)); + g_return_if_fail (gse->menu_show_percent || (unit != GIMP_UNIT_PERCENT)); + + gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (gse->unitmenu), unit); + gimp_size_entry_update_unit (gse, unit); +} + +static void +gimp_size_entry_unit_callback (GtkWidget *widget, + GimpSizeEntry *gse) +{ + GimpUnit new_unit; + + new_unit = gimp_unit_combo_box_get_active (GIMP_UNIT_COMBO_BOX (widget)); + + if (gse->unit != new_unit) + gimp_size_entry_update_unit (gse, new_unit); +} + +/** + * gimp_size_entry_attach_eevl: + * @spin_button: one of the size_entry's spinbuttons. + * @gsef: a size entry field. + * + * Hooks in the GimpEevl unit expression parser into the + * #GtkSpinButton of the #GimpSizeEntryField. + **/ +static void +gimp_size_entry_attach_eevl (GtkSpinButton *spin_button, + GimpSizeEntryField *gsef) +{ + gtk_spin_button_set_numeric (spin_button, FALSE); + gtk_spin_button_set_update_policy (spin_button, GTK_UPDATE_IF_VALID); + + g_signal_connect_after (spin_button, "input", + G_CALLBACK (gimp_size_entry_eevl_input_callback), + gsef); +} + +static gint +gimp_size_entry_eevl_input_callback (GtkSpinButton *spinner, + gdouble *return_val, + gpointer *data) +{ + GimpSizeEntryField *gsef = (GimpSizeEntryField *) data; + GimpEevlOptions options = GIMP_EEVL_OPTIONS_INIT; + gboolean success = FALSE; + const gchar *error_pos = 0; + GError *error = NULL; + GimpEevlQuantity result; + + g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spinner), FALSE); + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gsef->gse), FALSE); + + options.unit_resolver_proc = gimp_size_entry_eevl_unit_resolver; + options.data = data; + + /* enable ratio expressions when there are two fields */ + if (gsef->gse->number_of_fields == 2) + { + GimpSizeEntryField *other_gsef; + GimpEevlQuantity default_unit_factor; + gdouble default_unit_offset; + + options.ratio_expressions = TRUE; + + if (gsef == gsef->gse->fields->data) + { + other_gsef = gsef->gse->fields->next->data; + + options.ratio_invert = FALSE; + } + else + { + other_gsef = gsef->gse->fields->data; + + options.ratio_invert = TRUE; + } + + options.unit_resolver_proc (NULL, + &default_unit_factor, &default_unit_offset, + options.data); + + options.ratio_quantity.value = other_gsef->value / + default_unit_factor.value; + options.ratio_quantity.dimension = default_unit_factor.dimension; + } + + success = gimp_eevl_evaluate (gtk_entry_get_text (GTK_ENTRY (spinner)), + &options, + &result, + &error_pos, + &error); + if (! success) + { + if (error && error_pos) + { + g_printerr ("ERROR: %s at '%s'\n", + error->message, + *error_pos ? error_pos : "<End of input>"); + } + else + { + g_printerr ("ERROR: Expression evaluation failed without error.\n"); + } + + gtk_widget_error_bell (GTK_WIDGET (spinner)); + return GTK_INPUT_ERROR; + } + else if (result.dimension != 1 && gsef->gse->unit != GIMP_UNIT_PERCENT) + { + g_printerr ("ERROR: result has wrong dimension (expected 1, got %d)\n", result.dimension); + + gtk_widget_error_bell (GTK_WIDGET (spinner)); + return GTK_INPUT_ERROR; + } + else if (result.dimension != 0 && gsef->gse->unit == GIMP_UNIT_PERCENT) + { + g_printerr ("ERROR: result has wrong dimension (expected 0, got %d)\n", result.dimension); + + gtk_widget_error_bell (GTK_WIDGET (spinner)); + return GTK_INPUT_ERROR; + } + else + { + /* transform back to UI-unit */ + GimpEevlQuantity ui_unit; + GtkAdjustment *adj; + gdouble val; + + switch (gsef->gse->unit) + { + case GIMP_UNIT_PIXEL: + ui_unit.value = gsef->resolution; + ui_unit.dimension = 1; + break; + case GIMP_UNIT_PERCENT: + ui_unit.value = 1.0; + ui_unit.dimension = 0; + break; + default: + ui_unit.value = gimp_unit_get_factor(gsef->gse->unit); + ui_unit.dimension = 1; + break; + } + + *return_val = result.value * ui_unit.value; + + /* CLAMP() to adjustment bounds, or too large/small values + * will make the validation machinery revert to the old value. + * See bug #694477. + */ + adj = gtk_spin_button_get_adjustment (spinner); + + val = CLAMP (*return_val, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj)); + + if (val != *return_val) + { + gtk_widget_error_bell (GTK_WIDGET (spinner)); + *return_val = val; + } + + return TRUE; + } +} + +static gboolean +gimp_size_entry_eevl_unit_resolver (const gchar *identifier, + GimpEevlQuantity *factor, + gdouble *offset, + gpointer data) +{ + GimpSizeEntryField *gsef = (GimpSizeEntryField *) data; + gboolean resolve_default_unit = (identifier == NULL); + GimpUnit unit; + + g_return_val_if_fail (gsef, FALSE); + g_return_val_if_fail (factor != NULL, FALSE); + g_return_val_if_fail (offset != NULL, FALSE); + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gsef->gse), FALSE); + + *offset = 0.0; + + for (unit = 0; + unit <= gimp_unit_get_number_of_units (); + unit++) + { + /* Hack to handle percent within the loop */ + if (unit == gimp_unit_get_number_of_units ()) + unit = GIMP_UNIT_PERCENT; + + if ((resolve_default_unit && unit == gsef->gse->unit) || + (identifier && + (strcmp (gimp_unit_get_symbol (unit), identifier) == 0 || + strcmp (gimp_unit_get_abbreviation (unit), identifier) == 0))) + { + switch (unit) + { + case GIMP_UNIT_PERCENT: + if (gsef->gse->unit == GIMP_UNIT_PERCENT) + { + factor->value = 1; + factor->dimension = 0; + } + else + { + /* gsef->upper contains the '100%'-value */ + factor->value = 100*gsef->resolution/(gsef->upper - gsef->lower); + /* gsef->lower contains the '0%'-value */ + *offset = gsef->lower/gsef->resolution; + factor->dimension = 1; + } + /* return here, don't perform percentage conversion */ + return TRUE; + case GIMP_UNIT_PIXEL: + factor->value = gsef->resolution; + break; + default: + factor->value = gimp_unit_get_factor (unit); + break; + } + + if (gsef->gse->unit == GIMP_UNIT_PERCENT) + { + /* map non-percentages onto percent */ + factor->value = gsef->upper/(100*gsef->resolution); + factor->dimension = 0; + } + else + { + factor->dimension = 1; + } + + /* We are done */ + return TRUE; + } + } + + return FALSE; +} + +/** + * gimp_size_entry_show_unit_menu: + * @gse: a #GimpSizeEntry + * @show: Boolean + * + * Controls whether a unit menu is shown in the size entry. If + * @show is #TRUE, the menu is shown; otherwise it is hidden. + * + * Since: 2.4 + **/ +void +gimp_size_entry_show_unit_menu (GimpSizeEntry *gse, + gboolean show) +{ + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + + gtk_widget_set_visible (gse->unitmenu, show); +} + + +/** + * gimp_size_entry_set_pixel_digits: + * @gse: a #GimpSizeEntry + * @digits: the number of digits to display for a pixel size + * + * This function allows you set up a #GimpSizeEntry so that sub-pixel + * sizes can be entered. + **/ +void +gimp_size_entry_set_pixel_digits (GimpSizeEntry *gse, + gint digits) +{ + GimpUnitComboBox *combo; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + + combo = GIMP_UNIT_COMBO_BOX (gse->unitmenu); + + g_object_set_data (G_OBJECT (gse), "gimp-pixel-digits", + GINT_TO_POINTER (digits)); + gimp_size_entry_update_unit (gse, gimp_unit_combo_box_get_active (combo)); +} + + +/** + * gimp_size_entry_grab_focus: + * @gse: The sizeentry you want to grab the keyboard focus. + * + * This function is rather ugly and just a workaround for the fact that + * it's impossible to implement gtk_widget_grab_focus() for a #GtkTable. + **/ +void +gimp_size_entry_grab_focus (GimpSizeEntry *gse) +{ + GimpSizeEntryField *gsef; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + + gsef = gse->fields->data; + if (gsef) + gtk_widget_grab_focus (gse->show_refval ? + gsef->refval_spinbutton : gsef->value_spinbutton); +} + +/** + * gimp_size_entry_set_activates_default: + * @gse: A #GimpSizeEntry + * @setting: %TRUE to activate window's default widget on Enter keypress + * + * Iterates over all entries in the #GimpSizeEntry and calls + * gtk_entry_set_activates_default() on them. + * + * Since: 2.4 + **/ +void +gimp_size_entry_set_activates_default (GimpSizeEntry *gse, + gboolean setting) +{ + GSList *list; + + g_return_if_fail (GIMP_IS_SIZE_ENTRY (gse)); + + for (list = gse->fields; list; list = g_slist_next (list)) + { + GimpSizeEntryField *gsef = list->data; + + if (gsef->value_spinbutton) + gtk_entry_set_activates_default (GTK_ENTRY (gsef->value_spinbutton), + setting); + + if (gsef->refval_spinbutton) + gtk_entry_set_activates_default (GTK_ENTRY (gsef->refval_spinbutton), + setting); + } +} + +/** + * gimp_size_entry_get_help_widget: + * @gse: a #GimpSizeEntry + * @field: the index of the widget you want to get a pointer to + * + * You shouldn't fiddle with the internals of a #GimpSizeEntry but + * if you want to set tooltips using gimp_help_set_help_data() you + * can use this function to get a pointer to the spinbuttons. + * + * Return value: a #GtkWidget pointer that you can attach a tooltip to. + **/ +GtkWidget * +gimp_size_entry_get_help_widget (GimpSizeEntry *gse, + gint field) +{ + GimpSizeEntryField *gsef; + + g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), NULL); + g_return_val_if_fail ((field >= 0) && (field < gse->number_of_fields), NULL); + + gsef = g_slist_nth_data (gse->fields, field); + if (!gsef) + return NULL; + + return (gsef->refval_spinbutton ? + gsef->refval_spinbutton : gsef->value_spinbutton); +} diff --git a/libgimpwidgets/gimpsizeentry.h b/libgimpwidgets/gimpsizeentry.h new file mode 100644 index 0000000..2ead052 --- /dev/null +++ b/libgimpwidgets/gimpsizeentry.h @@ -0,0 +1,155 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpsizeentry.h + * Copyright (C) 1999-2000 Sven Neumann <sven@gimp.org> + * Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_SIZE_ENTRY_H__ +#define __GIMP_SIZE_ENTRY_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_SIZE_ENTRY (gimp_size_entry_get_type ()) +#define GIMP_SIZE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SIZE_ENTRY, GimpSizeEntry)) +#define GIMP_SIZE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SIZE_ENTRY, GimpSizeEntryClass)) +#define GIMP_IS_SIZE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_SIZE_ENTRY)) +#define GIMP_IS_SIZE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SIZE_ENTRY)) +#define GIMP_SIZE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SIZE_ENTRY, GimpSizeEntryClass)) + + +typedef struct _GimpSizeEntryClass GimpSizeEntryClass; + +typedef struct _GimpSizeEntryField GimpSizeEntryField; + +struct _GimpSizeEntry +{ + GtkTable parent_instance; + + GSList *fields; + gint number_of_fields; + + GtkWidget *unitmenu; + GimpUnit unit; + gboolean menu_show_pixels; + gboolean menu_show_percent; + + gboolean show_refval; + GimpSizeEntryUpdatePolicy update_policy; +}; + +struct _GimpSizeEntryClass +{ + GtkTableClass parent_class; + + void (* value_changed) (GimpSizeEntry *gse); + void (* refval_changed) (GimpSizeEntry *gse); + void (* unit_changed) (GimpSizeEntry *gse); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +/* For information look into the C source or the html documentation */ + +GType gimp_size_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_size_entry_new (gint number_of_fields, + GimpUnit unit, + const gchar *unit_format, + gboolean menu_show_pixels, + gboolean menu_show_percent, + gboolean show_refval, + gint spinbutton_width, + GimpSizeEntryUpdatePolicy update_policy); + +void gimp_size_entry_add_field (GimpSizeEntry *gse, + GtkSpinButton *value_spinbutton, + GtkSpinButton *refval_spinbutton); + +GtkWidget * gimp_size_entry_attach_label (GimpSizeEntry *gse, + const gchar *text, + gint row, + gint column, + gfloat alignment); + +void gimp_size_entry_set_resolution (GimpSizeEntry *gse, + gint field, + gdouble resolution, + gboolean keep_size); + +void gimp_size_entry_set_size (GimpSizeEntry *gse, + gint field, + gdouble lower, + gdouble upper); + +void gimp_size_entry_set_value_boundaries (GimpSizeEntry *gse, + gint field, + gdouble lower, + gdouble upper); + +gdouble gimp_size_entry_get_value (GimpSizeEntry *gse, + gint field); +void gimp_size_entry_set_value (GimpSizeEntry *gse, + gint field, + gdouble value); + +void gimp_size_entry_set_refval_boundaries (GimpSizeEntry *gse, + gint field, + gdouble lower, + gdouble upper); +void gimp_size_entry_set_refval_digits (GimpSizeEntry *gse, + gint field, + gint digits); + +gdouble gimp_size_entry_get_refval (GimpSizeEntry *gse, + gint field); +void gimp_size_entry_set_refval (GimpSizeEntry *gse, + gint field, + gdouble refval); + +GimpUnit gimp_size_entry_get_unit (GimpSizeEntry *gse); +void gimp_size_entry_set_unit (GimpSizeEntry *gse, + GimpUnit unit); +void gimp_size_entry_show_unit_menu (GimpSizeEntry *gse, + gboolean show); + +void gimp_size_entry_set_pixel_digits (GimpSizeEntry *gse, + gint digits); + +void gimp_size_entry_grab_focus (GimpSizeEntry *gse); +void gimp_size_entry_set_activates_default (GimpSizeEntry *gse, + gboolean setting); +GtkWidget * gimp_size_entry_get_help_widget (GimpSizeEntry *gse, + gint field); + + +G_END_DECLS + +#endif /* __GIMP_SIZE_ENTRY_H__ */ diff --git a/libgimpwidgets/gimpspinbutton.c b/libgimpwidgets/gimpspinbutton.c new file mode 100644 index 0000000..4d87e9d --- /dev/null +++ b/libgimpwidgets/gimpspinbutton.c @@ -0,0 +1,385 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpspinbutton.c + * Copyright (C) 2018 Ell + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpmath/gimpmath.h" + +#include "gimpwidgetstypes.h" + +#include "gimp3migration.h" +#include "gimpspinbutton.h" + + +/** + * SECTION: gimpspinbutton + * @title: GimpSpinButton + * @short_description: A #GtkSpinButton with a some tweaked functionality. + * + * #GimpSpinButton is a drop-in replacement for #GtkSpinButton, with the + * following changes: + * + * - When the spin-button loses focus, its adjustment value is only + * updated if the entry text has been changed. + * + * - When the spin-button's "wrap" property is TRUE, values input through the + * entry are wrapped around. + * + * - Modifiers can be used during scrolling for smaller/bigger increments. + **/ + + +#define MAX_DIGITS 20 + + +struct _GimpSpinButtonPrivate +{ + gboolean changed; +}; + + +/* local function prototypes */ + +static gboolean gimp_spin_button_scroll (GtkWidget *widget, + GdkEventScroll *event); +static gboolean gimp_spin_button_key_press (GtkWidget *widget, + GdkEventKey *event); +static gboolean gimp_spin_button_focus_in (GtkWidget *widget, + GdkEventFocus *event); +static gboolean gimp_spin_button_focus_out (GtkWidget *widget, + GdkEventFocus *event); + +static gint gimp_spin_button_input (GtkSpinButton *spin_button, + gdouble *new_value); + +static void gimp_spin_button_changed (GtkEditable *editable, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSpinButton, gimp_spin_button, + GTK_TYPE_SPIN_BUTTON) + +#define parent_class gimp_spin_button_parent_class + + +/* private functions */ + + +static void +gimp_spin_button_class_init (GimpSpinButtonClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkSpinButtonClass *spin_button_class = GTK_SPIN_BUTTON_CLASS (klass); + + widget_class->scroll_event = gimp_spin_button_scroll; + widget_class->key_press_event = gimp_spin_button_key_press; + widget_class->focus_in_event = gimp_spin_button_focus_in; + widget_class->focus_out_event = gimp_spin_button_focus_out; + + spin_button_class->input = gimp_spin_button_input; +} + +static void +gimp_spin_button_init (GimpSpinButton *spin_button) +{ + spin_button->priv = gimp_spin_button_get_instance_private (spin_button); + + g_signal_connect (spin_button, "changed", + G_CALLBACK (gimp_spin_button_changed), + NULL); +} + +static gboolean +gimp_spin_button_scroll (GtkWidget *widget, + GdkEventScroll *event) +{ + if (event->direction == GDK_SCROLL_UP || + event->direction == GDK_SCROLL_DOWN) + { + GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); + GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); + gdouble step_inc; + gdouble page_inc; + gint digits; + gdouble step; + + step_inc = gtk_adjustment_get_step_increment (adjustment); + page_inc = gtk_adjustment_get_page_increment (adjustment); + digits = gtk_spin_button_get_digits (spin_button); + + if (event->state & GDK_SHIFT_MASK) + { + step = step_inc * step_inc / page_inc; + step = MAX (step, pow (10.0, -digits)); + } + else if (event->state & GDK_CONTROL_MASK) + { + step = page_inc; + } + else + { + step = step_inc; + } + + if (event->direction == GDK_SCROLL_DOWN) + step = -step; + + if (! gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + gtk_spin_button_spin (spin_button, GTK_SPIN_USER_DEFINED, step); + + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event); +} + +static gboolean +gimp_spin_button_key_press (GtkWidget *widget, + GdkEventKey *event) +{ + switch (event->keyval) + { + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + case GDK_KEY_Escape: + { + GtkEntry *entry = GTK_ENTRY (widget); + GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); + gchar *text; + gboolean changed; + + text = g_strdup (gtk_entry_get_text (entry)); + + if (event->keyval == GDK_KEY_Escape) + { + gtk_spin_button_set_value ( + spin_button, + gtk_spin_button_get_value (spin_button)); + } + else + { + gtk_spin_button_update (spin_button); + } + + changed = strcmp (gtk_entry_get_text (entry), text); + + g_free (text); + + if (changed) + { + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + + return TRUE; + } + } + break; + } + + return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event); +} + +static gboolean +gimp_spin_button_focus_in (GtkWidget *widget, + GdkEventFocus *event) +{ + GimpSpinButton *spin_button = GIMP_SPIN_BUTTON (widget); + + spin_button->priv->changed = FALSE; + + return GTK_WIDGET_CLASS (parent_class)->focus_in_event (widget, event); +} + +static gboolean +gimp_spin_button_focus_out (GtkWidget *widget, + GdkEventFocus *event) +{ + GimpSpinButton *spin_button = GIMP_SPIN_BUTTON (widget); + gboolean editable; + gboolean result; + + editable = gtk_editable_get_editable (GTK_EDITABLE (widget)); + + if (! spin_button->priv->changed) + gtk_editable_set_editable (GTK_EDITABLE (widget), FALSE); + + result = GTK_WIDGET_CLASS (parent_class)->focus_out_event (widget, event); + + if (! spin_button->priv->changed) + gtk_editable_set_editable (GTK_EDITABLE (widget), editable); + + return result; +} + +static gint +gimp_spin_button_input (GtkSpinButton *spin_button, + gdouble *new_value) +{ + if (gtk_spin_button_get_wrap (spin_button)) + { + gdouble value; + gdouble min; + gdouble max; + gchar *endptr; + + value = g_strtod (gtk_entry_get_text (GTK_ENTRY (spin_button)), &endptr); + + if (*endptr) + return FALSE; + + gtk_spin_button_get_range (spin_button, &min, &max); + + if (min < max) + { + gdouble rem; + + rem = fmod (value - min, max - min); + + if (rem < 0.0) + rem += max - min; + + if (rem == 0.0) + value = CLAMP (value, min, max); + else + value = min + rem; + } + else + { + value = min; + } + + *new_value = value; + + return TRUE; + } + + return FALSE; +} + +static void +gimp_spin_button_changed (GtkEditable *editable, + gpointer data) +{ + GimpSpinButton *spin_button = GIMP_SPIN_BUTTON (editable); + + spin_button->priv->changed = TRUE; +} + + +/* public functions */ + + +/** + * gimp_spin_button_new: + * @adjustment: (allow-none): the #GtkAdjustment object that this spin + * button should use, or %NULL + * @climb_rate: specifies by how much the rate of change in the + * value will accelerate if you continue to hold + * down an up/down button or arrow key + * @digits: the number of decimal places to display + * + * Creates a new #GimpSpinButton. + * + * Returns: The new spin button as a #GtkWidget + * + * Since: 2.10.10 + */ +GtkWidget * +gimp_spin_button_new_ (GtkAdjustment *adjustment, + gdouble climb_rate, + guint digits) +{ + GtkWidget *spin_button; + + g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment), + NULL); + + spin_button = g_object_new (GIMP_TYPE_SPIN_BUTTON, NULL); + + gtk_spin_button_configure (GTK_SPIN_BUTTON (spin_button), + adjustment, climb_rate, digits); + + return spin_button; +} + +/** + * gimp_spin_button_new_with_range: + * @min: Minimum allowable value + * @max: Maximum allowable value + * @step: Increment added or subtracted by spinning the widget + * + * This is a convenience constructor that allows creation of a numeric + * #GimpSpinButton without manually creating an adjustment. The value is + * initially set to the minimum value and a page increment of 10 * @step + * is the default. The precision of the spin button is equivalent to the + * precision of @step. + * + * Note that the way in which the precision is derived works best if @step + * is a power of ten. If the resulting precision is not suitable for your + * needs, use gtk_spin_button_set_digits() to correct it. + * + * Returns: The new spin button as a #GtkWidget + * + * Since: 2.10.10 + */ +GtkWidget * +gimp_spin_button_new_with_range (gdouble min, + gdouble max, + gdouble step) +{ + GtkAdjustment *adjustment; + GtkWidget *spin_button; + gint digits; + + g_return_val_if_fail (min <= max, NULL); + g_return_val_if_fail (step != 0.0, NULL); + + spin_button = g_object_new (GIMP_TYPE_SPIN_BUTTON, NULL); + + adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (min, min, max, + step, 10.0 * step, 0.0)); + + if (fabs (step) >= 1.0 || step == 0.0) + { + digits = 0; + } + else + { + digits = abs ((gint) floor (log10 (fabs (step)))); + + if (digits > MAX_DIGITS) + digits = MAX_DIGITS; + } + + gtk_spin_button_configure (GTK_SPIN_BUTTON (spin_button), + adjustment, step, digits); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin_button), TRUE); + + return spin_button; +} diff --git a/libgimpwidgets/gimpspinbutton.h b/libgimpwidgets/gimpspinbutton.h new file mode 100644 index 0000000..ea77cd6 --- /dev/null +++ b/libgimpwidgets/gimpspinbutton.h @@ -0,0 +1,90 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpspinbutton.h + * Copyright (C) 2018 Ell + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_SPIN_BUTTON_H__ +#define __GIMP_SPIN_BUTTON_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_SPIN_BUTTON (gimp_spin_button_get_type ()) +#define GIMP_SPIN_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SPIN_BUTTON, GimpSpinButton)) +#define GIMP_SPIN_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SPIN_BUTTON, GimpSpinButtonClass)) +#define GIMP_IS_SPIN_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SPIN_BUTTON)) +#define GIMP_IS_SPIN_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SPIN_BUTTON)) +#define GIMP_SPIN_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SPIN_BUTTON, GimpSpinButtonClass)) + + +typedef struct _GimpSpinButtonPrivate GimpSpinButtonPrivate; +typedef struct _GimpSpinButtonClass GimpSpinButtonClass; + +struct _GimpSpinButton +{ + GtkSpinButton parent_instance; + + GimpSpinButtonPrivate *priv; +}; + +struct _GimpSpinButtonClass +{ + GtkSpinButtonClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_spin_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_spin_button_new_ (GtkAdjustment *adjustment, + gdouble climb_rate, + guint digits); +GtkWidget * gimp_spin_button_new_with_range (gdouble min, + gdouble max, + gdouble step); + + +/* compatibility magic, expanding to either the old (deprecated) + * gimp_spin_button_new(), defined in gimpwidgets.h, or the new + * gimp_spin_button_new(), defined here, based on the number of arguments. + */ +#define gimp_spin_button_new(...) gimp_spin_button_new_I (__VA_ARGS__, \ + 9, , , , , , 3) +#define gimp_spin_button_new_I(_1, _2, _3, _4, _5, _6, _7, _8, _9, n, ...) \ + gimp_spin_button_new_I_##n (_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define gimp_spin_button_new_I_3(_1, _2, _3, _4, _5, _6, _7, _8, _9) \ + gimp_spin_button_new_ (_1, _2, _3) +#define gimp_spin_button_new_I_9(_1, _2, _3, _4, _5, _6, _7, _8, _9) \ + gimp_spin_button_new (_1, _2, _3, _4, _5, _6, _7, _8, _9) + + +G_END_DECLS + +#endif /* __GIMP_SPIN_BUTTON_H__ */ diff --git a/libgimpwidgets/gimpstringcombobox.c b/libgimpwidgets/gimpstringcombobox.c new file mode 100644 index 0000000..56208a3 --- /dev/null +++ b/libgimpwidgets/gimpstringcombobox.c @@ -0,0 +1,363 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpstringcombobox.c + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpstringcombobox.h" + + +/** + * SECTION: gimpstringcombobox + * @title: GimpStringComboBox + * @short_description: A #GtkComboBox subclass to select strings. + * + * A #GtkComboBox subclass to select strings. + **/ + + +enum +{ + PROP_0, + PROP_ID_COLUMN, + PROP_LABEL_COLUMN, + PROP_ELLIPSIZE +}; + + +typedef struct +{ + gint id_column; + gint label_column; + GtkCellRenderer *text_renderer; +} GimpStringComboBoxPrivate; + +#define GIMP_STRING_COMBO_BOX_GET_PRIVATE(obj) \ + ((GimpStringComboBoxPrivate *) ((GimpStringComboBox *) (obj))->priv) + + +static void gimp_string_combo_box_constructed (GObject *object); +static void gimp_string_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_string_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpStringComboBox, gimp_string_combo_box, + GTK_TYPE_COMBO_BOX) + +#define parent_class gimp_string_combo_box_parent_class + + +static void +gimp_string_combo_box_class_init (GimpStringComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_string_combo_box_constructed; + object_class->set_property = gimp_string_combo_box_set_property; + object_class->get_property = gimp_string_combo_box_get_property; + + /** + * GimpStringComboBox:id-column: + * + * The column in the associated GtkTreeModel that holds unique + * string IDs. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_ID_COLUMN, + g_param_spec_int ("id-column", + "ID Column", + "The model column that holds the ID", + 0, G_MAXINT, + 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + /** + * GimpStringComboBox:id-column: + * + * The column in the associated GtkTreeModel that holds strings to + * be used as labels in the combo-box. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_LABEL_COLUMN, + g_param_spec_int ("label-column", + "Label Column", + "The model column that holds the label", + 0, G_MAXINT, + 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * GimpStringComboBox:ellipsize: + * + * Specifies the preferred place to ellipsize text in the combo-box, + * if the cell renderer does not have enough room to display the + * entire string. + * + * Since: 2.4 + */ + g_object_class_install_property (object_class, + PROP_ELLIPSIZE, + g_param_spec_enum ("ellipsize", + "Ellipsize", + "Ellipsize mode for the text cell renderer", + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_string_combo_box_init (GimpStringComboBox *combo_box) +{ + combo_box->priv = gimp_string_combo_box_get_instance_private (combo_box); +} + +static void +gimp_string_combo_box_constructed (GObject *object) +{ + GimpStringComboBoxPrivate *priv = GIMP_STRING_COMBO_BOX_GET_PRIVATE (object); + GtkCellRenderer *cell; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + priv->text_renderer = cell = gtk_cell_renderer_text_new (); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (object), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (object), cell, + "text", priv->label_column, + NULL); +} + +static void +gimp_string_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpStringComboBoxPrivate *priv = GIMP_STRING_COMBO_BOX_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ID_COLUMN: + priv->id_column = g_value_get_int (value); + break; + + case PROP_LABEL_COLUMN: + priv->label_column = g_value_get_int (value); + break; + + case PROP_ELLIPSIZE: + g_object_set_property (G_OBJECT (priv->text_renderer), + pspec->name, value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_string_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpStringComboBoxPrivate *priv = GIMP_STRING_COMBO_BOX_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_ID_COLUMN: + g_value_set_int (value, priv->id_column); + break; + + case PROP_LABEL_COLUMN: + g_value_set_int (value, priv->label_column); + break; + + case PROP_ELLIPSIZE: + g_object_get_property (G_OBJECT (priv->text_renderer), + pspec->name, value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_string_model_lookup (GtkTreeModel *model, + gint column, + const gchar *id, + GtkTreeIter *iter) +{ + GValue value = G_VALUE_INIT; + gboolean iter_valid; + + /* This lookup could be backed up by a hash table or some other + * data structure instead of doing a list traversal. But since this + * is a GtkComboBox, there shouldn't be many entries anyway... + */ + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + const gchar *str; + + gtk_tree_model_get_value (model, iter, column, &value); + + str = g_value_get_string (&value); + + if (str && strcmp (str, id) == 0) + { + g_value_unset (&value); + break; + } + + g_value_unset (&value); + } + + return iter_valid; +} + + +/** + * gimp_string_combo_box_new: + * @model: a #GtkTreeModel + * @id_column: the model column of the ID + * @label_column: the modl column of the label + * + * Return value: a new #GimpStringComboBox. + * + * Since: 2.4 + **/ +GtkWidget * +gimp_string_combo_box_new (GtkTreeModel *model, + gint id_column, + gint label_column) +{ + g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL); + g_return_val_if_fail (gtk_tree_model_get_column_type (model, + id_column) == G_TYPE_STRING, NULL); + g_return_val_if_fail (gtk_tree_model_get_column_type (model, + label_column) == G_TYPE_STRING, NULL); + + return g_object_new (GIMP_TYPE_STRING_COMBO_BOX, + "model", model, + "id-column", id_column, + "label-column", label_column, + NULL); +} + +/** + * gimp_string_combo_box_set_active: + * @combo_box: a #GimpStringComboBox + * @id: the ID of the item to select + * + * Looks up the item that belongs to the given @id and makes it the + * selected item in the @combo_box. + * + * Return value: %TRUE on success or %FALSE if there was no item for + * this value. + * + * Since: 2.4 + **/ +gboolean +gimp_string_combo_box_set_active (GimpStringComboBox *combo_box, + const gchar *id) +{ + g_return_val_if_fail (GIMP_IS_STRING_COMBO_BOX (combo_box), FALSE); + + if (id) + { + GtkTreeModel *model; + GtkTreeIter iter; + gint column; + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + + column = GIMP_STRING_COMBO_BOX_GET_PRIVATE (combo_box)->id_column; + + if (gimp_string_model_lookup (model, column, id, &iter)) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter); + return TRUE; + } + + return FALSE; + } + else + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), -1); + + return TRUE; + } +} + +/** + * gimp_string_combo_box_get_active: + * @combo_box: a #GimpStringComboBox + * + * Retrieves the value of the selected (active) item in the @combo_box. + * + * Return value: newly allocated ID string or %NULL if nothing was selected + * + * Since: 2.4 + **/ +gchar * +gimp_string_combo_box_get_active (GimpStringComboBox *combo_box) +{ + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_STRING_COMBO_BOX (combo_box), NULL); + + 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)); + gchar *value; + gint column; + + column = GIMP_STRING_COMBO_BOX_GET_PRIVATE (combo_box)->id_column; + + gtk_tree_model_get (model, &iter, + column, &value, + -1); + + return value; + } + + return NULL; +} diff --git a/libgimpwidgets/gimpstringcombobox.h b/libgimpwidgets/gimpstringcombobox.h new file mode 100644 index 0000000..6dff2c6 --- /dev/null +++ b/libgimpwidgets/gimpstringcombobox.h @@ -0,0 +1,74 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpstringcombobox.h + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_STRING_COMBO_BOX_H__ +#define __GIMP_STRING_COMBO_BOX_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_STRING_COMBO_BOX (gimp_string_combo_box_get_type ()) +#define GIMP_STRING_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STRING_COMBO_BOX, GimpStringComboBox)) +#define GIMP_STRING_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STRING_COMBO_BOX, GimpStringComboBoxClass)) +#define GIMP_IS_STRING_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STRING_COMBO_BOX)) +#define GIMP_IS_STRING_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STRING_COMBO_BOX)) +#define GIMP_STRING_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STRING_COMBO_BOX, GimpStringComboBoxClass)) + + +typedef struct _GimpStringComboBoxClass GimpStringComboBoxClass; + +struct _GimpStringComboBox +{ + GtkComboBox parent_instance; + + /*< private >*/ + gpointer priv; +}; + +struct _GimpStringComboBoxClass +{ + GtkComboBoxClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_string_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_string_combo_box_new (GtkTreeModel *model, + gint id_column, + gint label_column); +gboolean gimp_string_combo_box_set_active (GimpStringComboBox *combo_box, + const gchar *id); +gchar * gimp_string_combo_box_get_active (GimpStringComboBox *combo_box); + + +G_END_DECLS + +#endif /* __GIMP_STRING_COMBO_BOX_H__ */ diff --git a/libgimpwidgets/gimpunitcombobox.c b/libgimpwidgets/gimpunitcombobox.c new file mode 100644 index 0000000..fff5577 --- /dev/null +++ b/libgimpwidgets/gimpunitcombobox.c @@ -0,0 +1,218 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimpunitcombobox.c + * Copyright (C) 2004, 2008 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimpunitcombobox.h" +#include "gimpunitstore.h" + + +/** + * SECTION: gimpunitcombobox + * @title: GimpUnitComboBox + * @short_description: A #GtkComboBox to select a #GimpUnit. + * @see_also: #GimpUnit, #GimpUnitStore + * + * #GimpUnitComboBox selects units stored in a #GimpUnitStore. + * It replaces the deprecated #GimpUnitMenu. + **/ + + +static void gimp_unit_combo_box_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static void gimp_unit_combo_box_popup_shown (GtkWidget *widget, + const GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpUnitComboBox, gimp_unit_combo_box, GTK_TYPE_COMBO_BOX) + +#define parent_class gimp_unit_combo_box_parent_class + + +static void +gimp_unit_combo_box_class_init (GimpUnitComboBoxClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->style_set = gimp_unit_combo_box_style_set; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_double ("label-scale", + "Label Scale", + "The scale for the text cell renderer", + 0.0, + G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READABLE)); +} + +static void +gimp_unit_combo_box_init (GimpUnitComboBox *combo) +{ + GtkCellLayout *layout = GTK_CELL_LAYOUT (combo); + GtkCellRenderer *cell; + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (layout, cell, TRUE); + gtk_cell_layout_set_attributes (layout, cell, + "text", GIMP_UNIT_STORE_UNIT_LONG_FORMAT, + NULL); + + g_signal_connect (combo, "notify::popup-shown", + G_CALLBACK (gimp_unit_combo_box_popup_shown), + NULL); +} + +static void +gimp_unit_combo_box_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GtkCellLayout *layout; + GtkCellRenderer *cell; + gdouble scale; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, "label-scale", &scale, NULL); + + /* hackedehack ... */ + layout = GTK_CELL_LAYOUT (gtk_bin_get_child (GTK_BIN (widget))); + gtk_cell_layout_clear (layout); + + cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, + "scale", scale, + NULL); + gtk_cell_layout_pack_start (layout, cell, TRUE); + gtk_cell_layout_set_attributes (layout, cell, + "text", GIMP_UNIT_STORE_UNIT_SHORT_FORMAT, + NULL); +} + +static void +gimp_unit_combo_box_popup_shown (GtkWidget *widget, + const GParamSpec *pspec) +{ + GimpUnitStore *store; + gboolean shown; + + g_object_get (widget, + "model", &store, + "popup-shown", &shown, + NULL); + + if (store) + { + if (shown) + _gimp_unit_store_sync_units (store); + + g_object_unref (store); + } +} + + +/** + * gimp_unit_combo_box_new: + * + * Return value: a new #GimpUnitComboBox. + **/ +GtkWidget * +gimp_unit_combo_box_new (void) +{ + GtkWidget *combo_box; + GimpUnitStore *store; + + store = gimp_unit_store_new (0); + + combo_box = g_object_new (GIMP_TYPE_UNIT_COMBO_BOX, + "model", store, + NULL); + + g_object_unref (store); + + return combo_box; +} + +/** + * gimp_unit_combo_box_new_with_model: + * @model: a GimpUnitStore + * + * Return value: a new #GimpUnitComboBox. + **/ +GtkWidget * +gimp_unit_combo_box_new_with_model (GimpUnitStore *model) +{ + return g_object_new (GIMP_TYPE_UNIT_COMBO_BOX, + "model", model, + NULL); +} + +GimpUnit +gimp_unit_combo_box_get_active (GimpUnitComboBox *combo) +{ + GtkTreeIter iter; + gint unit; + + g_return_val_if_fail (GIMP_IS_UNIT_COMBO_BOX (combo), -1); + + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter); + + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)), &iter, + GIMP_UNIT_STORE_UNIT, &unit, + -1); + + return (GimpUnit) unit; +} + +void +gimp_unit_combo_box_set_active (GimpUnitComboBox *combo, + GimpUnit unit) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_if_fail (GIMP_IS_UNIT_COMBO_BOX (combo)); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + _gimp_unit_store_sync_units (GIMP_UNIT_STORE (model)); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gint iter_unit; + + gtk_tree_model_get (model, &iter, + GIMP_UNIT_STORE_UNIT, &iter_unit, + -1); + + if (unit == (GimpUnit) iter_unit) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + break; + } + } +} diff --git a/libgimpwidgets/gimpunitcombobox.h b/libgimpwidgets/gimpunitcombobox.h new file mode 100644 index 0000000..0439369 --- /dev/null +++ b/libgimpwidgets/gimpunitcombobox.h @@ -0,0 +1,71 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimpunitcombobox.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_UNIT_COMBO_BOX_H__ +#define __GIMP_UNIT_COMBO_BOX_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_UNIT_COMBO_BOX (gimp_unit_combo_box_get_type ()) +#define GIMP_UNIT_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNIT_COMBO_BOX, GimpUnitComboBox)) +#define GIMP_UNIT_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNIT_COMBO_BOX, GimpUnitComboBoxClass)) +#define GIMP_IS_UNIT_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNIT_COMBO_BOX)) +#define GIMP_IS_UNIT_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNIT_COMBO_BOX)) +#define GIMP_UNIT_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNIT_COMBO_BOX, GimpUnitComboBoxClass)) + + +typedef struct _GimpUnitComboBoxClass GimpUnitComboBoxClass; + +struct _GimpUnitComboBox +{ + GtkComboBox parent_instance; +}; + +struct _GimpUnitComboBoxClass +{ + GtkComboBoxClass parent_class; + + /* Padding for future expansion */ + void (*_gimp_reserved1) (void); + void (*_gimp_reserved2) (void); + void (*_gimp_reserved3) (void); + void (*_gimp_reserved4) (void); +}; + + +GType gimp_unit_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_unit_combo_box_new (void); +GtkWidget * gimp_unit_combo_box_new_with_model (GimpUnitStore *model); + +GimpUnit gimp_unit_combo_box_get_active (GimpUnitComboBox *combo); +void gimp_unit_combo_box_set_active (GimpUnitComboBox *combo, + GimpUnit unit); + + +G_END_DECLS + +#endif /* __GIMP_UNIT_COMBO_BOX_H__ */ diff --git a/libgimpwidgets/gimpunitmenu.c b/libgimpwidgets/gimpunitmenu.c new file mode 100644 index 0000000..3777a62 --- /dev/null +++ b/libgimpwidgets/gimpunitmenu.c @@ -0,0 +1,633 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimpunitmenu.c + * Copyright (C) 1999 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#undef GSEAL_ENABLE + +#include <gegl.h> +/* FIXME: #undef GTK_DISABLE_DEPRECATED */ +#undef GTK_DISABLE_DEPRECATED +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpdialog.h" +#include "gimphelpui.h" +#include "gimpwidgets.h" + +#undef GIMP_DISABLE_DEPRECATED +#include "gimpoldwidgets.h" +#include "gimpunitmenu.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpunitmenu + * @title: GimpUnitMenu + * @short_description: Widget for selecting a #GimpUnit. + * @see_also: #GimpUnit, #GimpSizeEntry, gimp_coordinates_new() + * + * This widget provides a #GtkOptionMenu which contains a list of + * #GimpUnit's. + * + * You can specify the string that will be displayed for each unit by + * passing a printf-like @format string to gimp_unit_menu_new(). + * + * The constructor also lets you choose if the menu should contain + * items for GIMP_UNIT_PIXEL, GIMP_UNIT_PERCENT and a "More..." item + * which will pop up a dialog for selecting user-defined units. + * + * Whenever the user selects a unit from the menu or the dialog, the + * "unit_changed" signal will be emitted. + **/ + + +enum +{ + UNIT_CHANGED, + LAST_SIGNAL +}; + +enum +{ + UNIT_COLUMN, + FACTOR_COLUMN, + DATA_COLUMN, + NUM_COLUMNS +}; + + +static void gimp_unit_menu_finalize (GObject *object); +static void gimp_unit_menu_callback (GtkWidget *widget, + gpointer data); + + +G_DEFINE_TYPE (GimpUnitMenu, gimp_unit_menu, GTK_TYPE_OPTION_MENU) + +#define parent_class gimp_unit_menu_parent_class + +static guint gimp_unit_menu_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_unit_menu_class_init (GimpUnitMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + /** + * GimpUnitMenu::unit-changed: + * + * This signal is emitted whenever the user selects a #GimpUnit from + * the #GimpUnitMenu. + **/ + gimp_unit_menu_signals[UNIT_CHANGED] = + g_signal_new ("unit-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpUnitMenuClass, unit_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->finalize = gimp_unit_menu_finalize; + + klass->unit_changed = NULL; +} + +static void +gimp_unit_menu_init (GimpUnitMenu *menu) +{ + menu->format = NULL; + menu->unit = GIMP_UNIT_PIXEL; + menu->show_pixels = FALSE; + menu->show_percent = FALSE; + menu->selection = NULL; + menu->tv = NULL; +} + +static void +gimp_unit_menu_finalize (GObject *object) +{ + GimpUnitMenu *menu = GIMP_UNIT_MENU (object); + + g_clear_pointer (&menu->format, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gimp_unit_menu_new: + * @format: A printf-like format string which is used to create the unit + * strings. + * @unit: The initially selected unit. + * @show_pixels: %TRUE if the unit menu should contain an item for + * GIMP_UNIT_PIXEL. + * @show_percent: %TRUE in the unit menu should contain an item for + * GIMP_UNIT_PERCENT. + * @show_custom: %TRUE if the unit menu should contain a "More..." item for + * opening the user-defined-unit selection dialog. + * + * Creates a new #GimpUnitMenu widget. + * + * For the @format string's possible expansions, see gimp_unit_format_string(). + * + * Returns: A pointer to the new #GimpUnitMenu widget. + **/ +GtkWidget * +gimp_unit_menu_new (const gchar *format, + GimpUnit unit, + gboolean show_pixels, + gboolean show_percent, + gboolean show_custom) +{ + GimpUnitMenu *unit_menu; + GtkWidget *menu; + GtkWidget *menuitem; + gchar *string; + GimpUnit u; + + g_return_val_if_fail (((unit >= GIMP_UNIT_PIXEL) && + (unit < gimp_unit_get_number_of_units ())) || + (unit == GIMP_UNIT_PERCENT), NULL); + + if ((unit >= gimp_unit_get_number_of_built_in_units ()) && + (unit != GIMP_UNIT_PERCENT)) + show_custom = TRUE; + + unit_menu = g_object_new (GIMP_TYPE_UNIT_MENU, NULL); + + unit_menu->format = g_strdup (format); + unit_menu->show_pixels = show_pixels; + unit_menu->show_percent = show_percent; + + menu = gtk_menu_new (); + for (u = show_pixels ? GIMP_UNIT_PIXEL : GIMP_UNIT_INCH; + u < gimp_unit_get_number_of_built_in_units (); + u++) + { + /* special cases "pixels" and "percent" */ + if (u == GIMP_UNIT_INCH) + { + if (show_percent) + { + string = gimp_unit_format_string (format, GIMP_UNIT_PERCENT); + menuitem = gtk_menu_item_new_with_label (string); + g_free (string); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + g_object_set_data (G_OBJECT (menuitem), "gimp_unit_menu", + GINT_TO_POINTER (GIMP_UNIT_PERCENT)); + gtk_widget_show (menuitem); + + g_signal_connect (menuitem, "activate", + G_CALLBACK (gimp_unit_menu_callback), + unit_menu); + } + + if (show_pixels || show_percent) + { + menuitem = gtk_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_widget_show (menuitem); + } + } + + string = gimp_unit_format_string (format, u); + menuitem = gtk_menu_item_new_with_label (string); + g_free (string); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + g_object_set_data (G_OBJECT (menuitem), "gimp_unit_menu", + GINT_TO_POINTER (u)); + gtk_widget_show (menuitem); + + g_signal_connect (menuitem, "activate", + G_CALLBACK (gimp_unit_menu_callback), + unit_menu); + } + + if ((unit >= gimp_unit_get_number_of_built_in_units ()) && + (unit != GIMP_UNIT_PERCENT)) + { + menuitem = gtk_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_widget_show (menuitem); + + string = gimp_unit_format_string (format, unit); + menuitem = gtk_menu_item_new_with_label (string); + g_free (string); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + g_object_set_data (G_OBJECT (menuitem), "gimp_unit_menu", + GINT_TO_POINTER (unit)); + gtk_widget_show (menuitem); + + g_signal_connect (menuitem, "activate", + G_CALLBACK (gimp_unit_menu_callback), + unit_menu); + } + + if (show_custom) + { + menuitem = gtk_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_widget_show (menuitem); + + menuitem = gtk_menu_item_new_with_label (_("More...")); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + g_object_set_data (G_OBJECT (menuitem), "gimp_unit_menu", + GINT_TO_POINTER (GIMP_UNIT_PERCENT + 1)); + gtk_widget_show (menuitem); + + g_signal_connect (menuitem, "activate", + G_CALLBACK (gimp_unit_menu_callback), + unit_menu); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (unit_menu), menu); + + unit_menu->unit = unit; + gtk_option_menu_set_history (GTK_OPTION_MENU (unit_menu), + (unit == GIMP_UNIT_PIXEL) ? 0 : + ((unit == GIMP_UNIT_PERCENT) ? + (show_pixels ? 1 : 0) : + (((show_pixels || show_percent) ? 2 : 0) + + ((show_pixels && show_percent) ? 1 : 0) + + ((unit < GIMP_UNIT_END) ? + (unit - 1) : GIMP_UNIT_END)))); + + return GTK_WIDGET (unit_menu); +} + +/** + * gimp_unit_menu_set_unit: + * @menu: The unit menu you want to set the unit for. + * @unit: The new unit. + * + * Sets a new #GimpUnit for the specified #GimpUnitMenu. + **/ +void +gimp_unit_menu_set_unit (GimpUnitMenu *menu, + GimpUnit unit) +{ + GtkWidget *menuitem = NULL; + GList *items; + gint user_unit; + + g_return_if_fail (GIMP_IS_UNIT_MENU (menu)); + g_return_if_fail (((unit >= GIMP_UNIT_PIXEL) && + ((unit > GIMP_UNIT_PIXEL) || menu->show_pixels) && + (unit < gimp_unit_get_number_of_units ())) || + ((unit == GIMP_UNIT_PERCENT) && menu->show_percent)); + + if (unit == menu->unit) + return; + + items = GTK_MENU_SHELL (GTK_OPTION_MENU (menu)->menu)->children; + user_unit = (GIMP_UNIT_END + + (((menu->show_pixels || menu->show_percent) ? 2 : 0) + + ((menu->show_pixels && menu->show_percent) ? 1 : 0))); + + if ((unit >= GIMP_UNIT_END) && (unit != GIMP_UNIT_PERCENT)) + { + gchar *string; + + if ((g_list_length (items) - 3) >= user_unit) + { + gtk_widget_destroy (GTK_WIDGET (g_list_nth_data (items, + user_unit - 1))); + gtk_widget_destroy (GTK_WIDGET (g_list_nth_data (items, + user_unit - 1))); + } + + menuitem = gtk_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (GTK_OPTION_MENU (menu)->menu), + menuitem); + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_menu_reorder_child (GTK_MENU (GTK_OPTION_MENU (menu)->menu), + menuitem, user_unit - 1); + gtk_widget_show (menuitem); + + string = gimp_unit_format_string (menu->format, unit); + menuitem = gtk_menu_item_new_with_label (string); + g_free (string); + + gtk_menu_shell_append (GTK_MENU_SHELL (GTK_OPTION_MENU (menu)->menu), + menuitem); + g_object_set_data (G_OBJECT (menuitem), "gimp_unit_menu", + GINT_TO_POINTER (unit)); + gtk_menu_reorder_child (GTK_MENU (GTK_OPTION_MENU (menu)->menu), + menuitem, user_unit); + gtk_widget_show (menuitem); + + g_signal_connect (menuitem, "activate", + G_CALLBACK (gimp_unit_menu_callback), + menu); + } + + menu->unit = unit; + gtk_option_menu_set_history (GTK_OPTION_MENU (menu), + (unit == GIMP_UNIT_PIXEL) ? 0 : + ((unit == GIMP_UNIT_PERCENT) ? + (menu->show_pixels ? 1 : 0) : + (((menu->show_pixels || + menu->show_percent) ? 2 : 0) + + ((menu->show_pixels && + menu->show_percent) ? 1 : 0) + + ((unit < GIMP_UNIT_END) ? + (unit - 1) : GIMP_UNIT_END)))); + + g_signal_emit (menu, gimp_unit_menu_signals[UNIT_CHANGED], 0); +} + +/** + * gimp_unit_menu_get_unit: + * @menu: The unit menu you want to know the unit of. + * + * Returns the #GimpUnit the user has selected from the #GimpUnitMenu. + * + * Returns: The unit the user has selected. + **/ +GimpUnit +gimp_unit_menu_get_unit (GimpUnitMenu *menu) +{ + g_return_val_if_fail (GIMP_IS_UNIT_MENU (menu), GIMP_UNIT_INCH); + + return menu->unit; +} + + +/** + * gimp_unit_menu_set_pixel_digits: + * @menu: a #GimpUnitMenu + * @digits: the number of digits to display for a pixel size + * + * A GimpUnitMenu can be setup to control the number of digits shown + * by attached spinbuttons. Please refer to the documentation of + * gimp_unit_menu_update() to see how this is done. + * + * This function specifies the number of digits shown for a size in + * pixels. Usually this is 0 (only full pixels). If you want to allow + * the user to specify sub-pixel sizes using the attached spinbuttons, + * specify the number of digits after the decimal point here. You + * should do this after attaching your spinbuttons. + **/ +void +gimp_unit_menu_set_pixel_digits (GimpUnitMenu *menu, + gint digits) +{ + GimpUnit unit; + + g_return_if_fail (GIMP_IS_UNIT_MENU (menu)); + + menu->pixel_digits = digits; + + gimp_unit_menu_update (GTK_WIDGET (menu), &unit); +} + +/** + * gimp_unit_menu_get_pixel_digits: + * @menu: a #GimpUnitMenu + * + * Retrieve the number of digits for a pixel size as set by + * gimp_unit_menu_set_pixel_digits(). + * + * Return value: the configured number of digits for a pixel size + **/ +gint +gimp_unit_menu_get_pixel_digits (GimpUnitMenu *menu) +{ + g_return_val_if_fail (GIMP_IS_UNIT_MENU (menu), 0); + + return menu->pixel_digits; +} + +/* private callback of gimp_unit_menu_create_selection () */ +static void +gimp_unit_menu_selection_response (GtkWidget *widget, + gint response_id, + GimpUnitMenu *menu) +{ + if (response_id == GTK_RESPONSE_OK) + { + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (menu->tv)); + if (menu->selection && gtk_tree_selection_get_selected (sel, &model, + &iter)) + { + GValue val = G_VALUE_INIT; + GimpUnit unit; + + gtk_tree_model_get_value (model, &iter, 2, &val); + unit = (GimpUnit) g_value_get_int (&val); + g_value_unset (&val); + + gimp_unit_menu_set_unit (menu, unit); + } + } + + gtk_widget_destroy (menu->selection); +} + +static void +gimp_unit_menu_selection_row_activated_callback (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpUnitMenu *menu) +{ + gtk_dialog_response (GTK_DIALOG (menu->selection), GTK_RESPONSE_OK); +} + +/* private function of gimp_unit_menu_callback () */ +static void +gimp_unit_menu_create_selection (GimpUnitMenu *menu) +{ + GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (menu)); + GtkWidget *vbox; + GtkWidget *scrolled_win; + GtkListStore *list; + GtkTreeSelection *sel; + GtkTreeIter iter; + GtkTreePath *path; + GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT; + GimpUnit unit; + gint num_units; + + if (gtk_window_get_modal (GTK_WINDOW (parent))) + flags |= GTK_DIALOG_MODAL; + + menu->selection = gimp_dialog_new (_("Unit Selection"), "gimp-unit-selection", + parent, flags, + gimp_standard_help_func, + "gimp-unit-dialog", + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (menu->selection), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_object_add_weak_pointer (G_OBJECT (menu->selection), + (gpointer) &menu->selection); + + g_signal_connect (menu->selection, "response", + G_CALLBACK (gimp_unit_menu_selection_response), + menu); + + g_signal_connect_object (menu, "unmap", + G_CALLBACK (gtk_widget_destroy), + menu->selection, G_CONNECT_SWAPPED); + + /* the main vbox */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 2); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (menu->selection))), + vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + /* the selection list */ + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_NEVER, + GTK_POLICY_ALWAYS); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0); + gtk_widget_show (scrolled_win); + + list = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_INT); + menu->tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list)); + g_object_unref (list); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (menu->tv), + -1, _("Unit"), + gtk_cell_renderer_text_new (), + "text", UNIT_COLUMN, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (menu->tv), + -1, _("Factor"), + gtk_cell_renderer_text_new (), + "text", FACTOR_COLUMN, NULL); + + /* the unit lines */ + num_units = gimp_unit_get_number_of_units (); + for (unit = GIMP_UNIT_END; unit < num_units; unit++) + { + gchar *string; + + gtk_list_store_append (list, &iter); + + string = gimp_unit_format_string (menu->format, unit); + gtk_list_store_set (list, &iter, + UNIT_COLUMN, string, + -1); + g_free (string); + + string = gimp_unit_format_string ("(%f)", unit); + gtk_list_store_set (list, &iter, + FACTOR_COLUMN, string, + -1); + g_free (string); + + gtk_list_store_set (list, &iter, DATA_COLUMN, unit, -1); + } + + gtk_widget_set_size_request (menu->tv, -1, 150); + + gtk_container_add (GTK_CONTAINER (scrolled_win), menu->tv); + + g_signal_connect (menu->tv, "row-activated", + G_CALLBACK (gimp_unit_menu_selection_row_activated_callback), + menu); + + gtk_widget_show (menu->tv); + + g_signal_connect (menu->tv, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &menu->tv); + + gtk_widget_show (vbox); + gtk_widget_show (menu->selection); + + if (menu->unit >= GIMP_UNIT_END) + { + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, menu->unit - GIMP_UNIT_END); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (menu->tv)); + gtk_tree_selection_select_path (sel, path); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (menu->tv), path, NULL, + FALSE, 0.0, 0.0); + } +} + +static void +gimp_unit_menu_callback (GtkWidget *widget, + gpointer data) +{ + GimpUnitMenu *menu = data; + GimpUnit new_unit; + + new_unit = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "gimp_unit_menu")); + + if (menu->unit == new_unit) + return; + + /* was "More..." selected? */ + if (new_unit == (GIMP_UNIT_PERCENT + 1)) + { + gtk_option_menu_set_history (GTK_OPTION_MENU (menu), + (menu->unit == GIMP_UNIT_PIXEL) ? 0 : + ((menu->unit == GIMP_UNIT_PERCENT) ? + (menu->show_pixels ? 1 : 0) : + ((menu->show_pixels || + menu->show_percent ? 2 : 0) + + (menu->show_pixels && + menu->show_percent ? 1 : 0) + + ((menu->unit < GIMP_UNIT_END) ? + menu->unit - 1 : GIMP_UNIT_END)))); + if (! menu->selection) + gimp_unit_menu_create_selection (menu); + return; + } + else if (menu->selection) + { + gtk_widget_destroy (menu->selection); + } + + gimp_unit_menu_set_unit (menu, new_unit); +} diff --git a/libgimpwidgets/gimpunitmenu.h b/libgimpwidgets/gimpunitmenu.h new file mode 100644 index 0000000..99c371d --- /dev/null +++ b/libgimpwidgets/gimpunitmenu.h @@ -0,0 +1,105 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpunitmenu.h + * Copyright (C) 1999 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef GIMP_DISABLE_DEPRECATED + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_UNIT_MENU_H__ +#define __GIMP_UNIT_MENU_H__ + +#ifdef GTK_DISABLE_DEPRECATED +#undef GTK_DISABLE_DEPRECATED +#include <gtk/gtkoptionmenu.h> +#define GTK_DISABLE_DEPRECATED +#endif + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +#define GIMP_TYPE_UNIT_MENU (gimp_unit_menu_get_type ()) +#define GIMP_UNIT_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNIT_MENU, GimpUnitMenu)) +#define GIMP_UNIT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNIT_MENU, GimpUnitMenuClass)) +#define GIMP_IS_UNIT_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_UNIT_MENU)) +#define GIMP_IS_UNIT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNIT_MENU)) +#define GIMP_UNIT_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNIT_MENU, GimpUnitMenuClass)) + + +typedef struct _GimpUnitMenuClass GimpUnitMenuClass; + +struct _GimpUnitMenu +{ + GtkOptionMenu parent_instance; + + /* public (read only) */ + gchar *format; + GimpUnit unit; + gint pixel_digits; + + gboolean show_pixels; + gboolean show_percent; + + /* private */ + GtkWidget *selection; + GtkWidget *tv; +}; + +struct _GimpUnitMenuClass +{ + GtkOptionMenuClass parent_class; + + void (* unit_changed) (GimpUnitMenu *menu); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_unit_menu_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_unit_menu_new (const gchar *format, + GimpUnit unit, + gboolean show_pixels, + gboolean show_percent, + gboolean show_custom); + +void gimp_unit_menu_set_unit (GimpUnitMenu *menu, + GimpUnit unit); + +GimpUnit gimp_unit_menu_get_unit (GimpUnitMenu *menu); + +void gimp_unit_menu_set_pixel_digits (GimpUnitMenu *menu, + gint digits); +gint gimp_unit_menu_get_pixel_digits (GimpUnitMenu *menu); + + +G_END_DECLS + +#endif /* __GIMP_UNIT_MENU_H__ */ + +#endif /* GIMP_DISABLE_DEPRECATED */ diff --git a/libgimpwidgets/gimpunitstore.c b/libgimpwidgets/gimpunitstore.c new file mode 100644 index 0000000..40ecc97 --- /dev/null +++ b/libgimpwidgets/gimpunitstore.c @@ -0,0 +1,937 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpunitstore.c + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpunitstore.h" + + +/** + * SECTION: gimpunitstore + * @title: GimpUnitStore + * @short_description: A model for units + * + * A model for #GimpUnit views + **/ + + +enum +{ + PROP_0, + PROP_NUM_VALUES, + PROP_HAS_PIXELS, + PROP_HAS_PERCENT, + PROP_SHORT_FORMAT, + PROP_LONG_FORMAT +}; + +typedef struct +{ + gint num_values; + gboolean has_pixels; + gboolean has_percent; + + gchar *short_format; + gchar *long_format; + + gdouble *values; + gdouble *resolutions; + + GimpUnit synced_unit; +} GimpUnitStorePrivate; + +#define GET_PRIVATE(obj) ((GimpUnitStorePrivate *) gimp_unit_store_get_instance_private ((GimpUnitStore *) (obj))) + + +static void gimp_unit_store_tree_model_init (GtkTreeModelIface *iface); + +static void gimp_unit_store_finalize (GObject *object); +static void gimp_unit_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_unit_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static GtkTreeModelFlags gimp_unit_store_get_flags (GtkTreeModel *tree_model); +static gint gimp_unit_store_get_n_columns (GtkTreeModel *tree_model); +static GType gimp_unit_store_get_column_type (GtkTreeModel *tree_model, + gint index); +static gboolean gimp_unit_store_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path); +static GtkTreePath *gimp_unit_store_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static void gimp_unit_store_tree_model_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value); +static gboolean gimp_unit_store_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean gimp_unit_store_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent); +static gboolean gimp_unit_store_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gint gimp_unit_store_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean gimp_unit_store_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n); +static gboolean gimp_unit_store_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child); + + +G_DEFINE_TYPE_WITH_CODE (GimpUnitStore, gimp_unit_store, G_TYPE_OBJECT, + G_ADD_PRIVATE (GimpUnitStore) + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + gimp_unit_store_tree_model_init)) + +#define parent_class gimp_unit_store_parent_class + + +static GType column_types[GIMP_UNIT_STORE_UNIT_COLUMNS] = +{ + G_TYPE_INVALID, + G_TYPE_DOUBLE, + G_TYPE_INT, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING +}; + + +static void +gimp_unit_store_class_init (GimpUnitStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + column_types[GIMP_UNIT_STORE_UNIT] = GIMP_TYPE_UNIT; + + object_class->finalize = gimp_unit_store_finalize; + object_class->set_property = gimp_unit_store_set_property; + object_class->get_property = gimp_unit_store_get_property; + + g_object_class_install_property (object_class, PROP_NUM_VALUES, + g_param_spec_int ("num-values", + "Num Values", + "The number of values this store provides", + 0, G_MAXINT, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_HAS_PIXELS, + g_param_spec_boolean ("has-pixels", + "Has Pixels", + "Whether the store has GIMP_UNIT_PIXELS", + TRUE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HAS_PERCENT, + g_param_spec_boolean ("has-percent", + "Has Percent", + "Whether the store has GIMP_UNIT_PERCENT", + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SHORT_FORMAT, + g_param_spec_string ("short-format", + "Short Format", + "Format string for a short label", + "%a", + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_LONG_FORMAT, + g_param_spec_string ("long-format", + "Long Format", + "Format string for a long label", + "%p", + GIMP_PARAM_READWRITE)); +} + +static void +gimp_unit_store_init (GimpUnitStore *store) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (store); + + private->has_pixels = TRUE; + private->has_percent = FALSE; + private->short_format = g_strdup ("%a"); + private->long_format = g_strdup ("%p"); + private->synced_unit = gimp_unit_get_number_of_units () - 1; +} + +static void +gimp_unit_store_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = gimp_unit_store_get_flags; + iface->get_n_columns = gimp_unit_store_get_n_columns; + iface->get_column_type = gimp_unit_store_get_column_type; + iface->get_iter = gimp_unit_store_get_iter; + iface->get_path = gimp_unit_store_get_path; + iface->get_value = gimp_unit_store_tree_model_get_value; + iface->iter_next = gimp_unit_store_iter_next; + iface->iter_children = gimp_unit_store_iter_children; + iface->iter_has_child = gimp_unit_store_iter_has_child; + iface->iter_n_children = gimp_unit_store_iter_n_children; + iface->iter_nth_child = gimp_unit_store_iter_nth_child; + iface->iter_parent = gimp_unit_store_iter_parent; +} + +static void +gimp_unit_store_finalize (GObject *object) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->short_format, g_free); + g_clear_pointer (&private->long_format, g_free); + + g_clear_pointer (&private->values, g_free); + g_clear_pointer (&private->resolutions, g_free); + private->num_values = 0; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_unit_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_NUM_VALUES: + g_return_if_fail (private->num_values == 0); + private->num_values = g_value_get_int (value); + if (private->num_values) + { + private->values = g_new0 (gdouble, private->num_values); + private->resolutions = g_new0 (gdouble, private->num_values); + } + break; + case PROP_HAS_PIXELS: + gimp_unit_store_set_has_pixels (GIMP_UNIT_STORE (object), + g_value_get_boolean (value)); + break; + case PROP_HAS_PERCENT: + gimp_unit_store_set_has_percent (GIMP_UNIT_STORE (object), + g_value_get_boolean (value)); + break; + case PROP_SHORT_FORMAT: + g_free (private->short_format); + private->short_format = g_value_dup_string (value); + if (! private->short_format) + private->short_format = g_strdup ("%a"); + break; + case PROP_LONG_FORMAT: + g_free (private->long_format); + private->long_format = g_value_dup_string (value); + if (! private->long_format) + private->long_format = g_strdup ("%a"); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_unit_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_NUM_VALUES: + g_value_set_int (value, private->num_values); + break; + case PROP_HAS_PIXELS: + g_value_set_boolean (value, private->has_pixels); + break; + case PROP_HAS_PERCENT: + g_value_set_boolean (value, private->has_percent); + break; + case PROP_SHORT_FORMAT: + g_value_set_string (value, private->short_format); + break; + case PROP_LONG_FORMAT: + g_value_set_string (value, private->long_format); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GtkTreeModelFlags +gimp_unit_store_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY; +} + +static gint +gimp_unit_store_get_n_columns (GtkTreeModel *tree_model) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + + return GIMP_UNIT_STORE_UNIT_COLUMNS + private->num_values; +} + +static GType +gimp_unit_store_get_column_type (GtkTreeModel *tree_model, + gint index) +{ + g_return_val_if_fail (index >= 0, G_TYPE_INVALID); + + if (index < GIMP_UNIT_STORE_UNIT_COLUMNS) + return column_types[index]; + + return G_TYPE_DOUBLE; +} + +static gboolean +gimp_unit_store_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + gint index; + GimpUnit unit; + + g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); + + index = gtk_tree_path_get_indices (path)[0]; + + unit = index; + + if (! private->has_pixels) + unit++; + + if (private->has_percent) + { + unit--; + + if (private->has_pixels) + { + if (index == 0) + unit = GIMP_UNIT_PIXEL; + else if (index == 1) + unit = GIMP_UNIT_PERCENT; + } + else + { + if (index == 0) + unit = GIMP_UNIT_PERCENT; + } + } + + if ((unit >= 0 && unit < gimp_unit_get_number_of_units ()) || + ((unit == GIMP_UNIT_PERCENT && private->has_percent))) + { + iter->user_data = GINT_TO_POINTER (unit); + return TRUE; + } + + return FALSE; +} + +static GtkTreePath * +gimp_unit_store_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + GtkTreePath *path = gtk_tree_path_new (); + GimpUnit unit = GPOINTER_TO_INT (iter->user_data); + gint index; + + index = unit; + + if (! private->has_pixels) + index--; + + if (private->has_percent) + { + index++; + + if (private->has_pixels) + { + if (unit == GIMP_UNIT_PIXEL) + index = 0; + else if (unit == GIMP_UNIT_PERCENT) + index = 1; + } + else + { + if (unit == GIMP_UNIT_PERCENT) + index = 0; + } + } + + gtk_tree_path_append_index (path, index); + + return path; +} + +static void +gimp_unit_store_tree_model_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + GimpUnit unit; + + g_return_if_fail (column >= 0 && + column < GIMP_UNIT_STORE_UNIT_COLUMNS + private->num_values); + + g_value_init (value, + column < GIMP_UNIT_STORE_UNIT_COLUMNS ? + column_types[column] : + G_TYPE_DOUBLE); + + unit = GPOINTER_TO_INT (iter->user_data); + + if ((unit >= 0 && unit < gimp_unit_get_number_of_units ()) || + ((unit == GIMP_UNIT_PERCENT && private->has_percent))) + { + switch (column) + { + case GIMP_UNIT_STORE_UNIT: + g_value_set_int (value, unit); + break; + case GIMP_UNIT_STORE_UNIT_FACTOR: + g_value_set_double (value, gimp_unit_get_factor (unit)); + break; + case GIMP_UNIT_STORE_UNIT_DIGITS: + g_value_set_int (value, gimp_unit_get_digits (unit)); + break; + case GIMP_UNIT_STORE_UNIT_IDENTIFIER: + g_value_set_static_string (value, gimp_unit_get_identifier (unit)); + break; + case GIMP_UNIT_STORE_UNIT_SYMBOL: + g_value_set_static_string (value, gimp_unit_get_symbol (unit)); + break; + case GIMP_UNIT_STORE_UNIT_ABBREVIATION: + g_value_set_static_string (value, gimp_unit_get_abbreviation (unit)); + break; + case GIMP_UNIT_STORE_UNIT_SINGULAR: + g_value_set_static_string (value, gimp_unit_get_singular (unit)); + break; + case GIMP_UNIT_STORE_UNIT_PLURAL: + g_value_set_static_string (value, gimp_unit_get_plural (unit)); + break; + case GIMP_UNIT_STORE_UNIT_SHORT_FORMAT: + g_value_take_string (value, + gimp_unit_format_string (private->short_format, + unit)); + break; + case GIMP_UNIT_STORE_UNIT_LONG_FORMAT: + g_value_take_string (value, + gimp_unit_format_string (private->long_format, + unit)); + break; + + default: + column -= GIMP_UNIT_STORE_UNIT_COLUMNS; + if (unit == GIMP_UNIT_PIXEL) + { + g_value_set_double (value, private->values[column]); + } + else if (private->resolutions[column]) + { + g_value_set_double (value, + private->values[column] * + gimp_unit_get_factor (unit) / + private->resolutions[column]); + } + break; + } + } +} + +static gboolean +gimp_unit_store_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + GimpUnit unit = GPOINTER_TO_INT (iter->user_data); + + if (unit == GIMP_UNIT_PIXEL && private->has_percent) + { + unit = GIMP_UNIT_PERCENT; + } + else if (unit == GIMP_UNIT_PERCENT) + { + unit = GIMP_UNIT_INCH; + } + else if (unit >= 0 && unit < gimp_unit_get_number_of_units () - 1) + { + unit++; + } + else + { + return FALSE; + } + + iter->user_data = GINT_TO_POINTER (unit); + + return TRUE; +} + +static gboolean +gimp_unit_store_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + GimpUnit unit; + + /* this is a list, nodes have no children */ + if (parent) + return FALSE; + + if (private->has_pixels) + { + unit = GIMP_UNIT_PIXEL; + } + else if (private->has_percent) + { + unit = GIMP_UNIT_PERCENT; + } + else + { + unit = GIMP_UNIT_INCH; + } + + iter->user_data = GINT_TO_POINTER (unit); + + return TRUE; +} + +static gboolean +gimp_unit_store_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + return FALSE; +} + +static gint +gimp_unit_store_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + gint n_children; + + if (iter) + return 0; + + n_children = gimp_unit_get_number_of_units (); + + if (! private->has_pixels) + n_children--; + + if (private->has_percent) + n_children++; + + return n_children; +} + +static gboolean +gimp_unit_store_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + GimpUnitStorePrivate *private = GET_PRIVATE (tree_model); + gint n_children; + + if (parent) + return FALSE; + + n_children = gimp_unit_store_iter_n_children (tree_model, NULL); + + if (n >= 0 && n < n_children) + { + GimpUnit unit = n; + + if (! private->has_pixels) + unit++; + + if (private->has_percent) + { + unit--; + + if (private->has_pixels) + { + if (n == 0) + unit = GIMP_UNIT_PIXEL; + else if (n == 1) + unit = GIMP_UNIT_PERCENT; + } + else + { + if (n == 0) + unit = GIMP_UNIT_PERCENT; + } + } + + iter->user_data = GINT_TO_POINTER (unit); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gimp_unit_store_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + return FALSE; +} + + +GimpUnitStore * +gimp_unit_store_new (gint num_values) +{ + return g_object_new (GIMP_TYPE_UNIT_STORE, + "num-values", num_values, + NULL); +} + +void +gimp_unit_store_set_has_pixels (GimpUnitStore *store, + gboolean has_pixels) +{ + GimpUnitStorePrivate *private; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + has_pixels = has_pixels ? TRUE : FALSE; + + if (has_pixels != private->has_pixels) + { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreePath *deleted_path = NULL; + + if (! has_pixels) + { + GtkTreeIter iter; + + gtk_tree_model_get_iter_first (model, &iter); + deleted_path = gtk_tree_model_get_path (model, &iter); + } + + private->has_pixels = has_pixels; + + if (has_pixels) + { + GtkTreePath *path; + GtkTreeIter iter; + + gtk_tree_model_get_iter_first (model, &iter); + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_model_row_inserted (model, path, &iter); + gtk_tree_path_free (path); + } + else if (deleted_path) + { + gtk_tree_model_row_deleted (model, deleted_path); + gtk_tree_path_free (deleted_path); + } + + g_object_notify (G_OBJECT (store), "has-pixels"); + } +} + +gboolean +gimp_unit_store_get_has_pixels (GimpUnitStore *store) +{ + GimpUnitStorePrivate *private; + + g_return_val_if_fail (GIMP_IS_UNIT_STORE (store), FALSE); + + private = GET_PRIVATE (store); + + return private->has_pixels; +} + +void +gimp_unit_store_set_has_percent (GimpUnitStore *store, + gboolean has_percent) +{ + GimpUnitStorePrivate *private; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + has_percent = has_percent ? TRUE : FALSE; + + if (has_percent != private->has_percent) + { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreePath *deleted_path = NULL; + + if (! has_percent) + { + GtkTreeIter iter; + + gtk_tree_model_get_iter_first (model, &iter); + if (private->has_pixels) + gtk_tree_model_iter_next (model, &iter); + deleted_path = gtk_tree_model_get_path (model, &iter); + } + + private->has_percent = has_percent; + + if (has_percent) + { + GtkTreePath *path; + GtkTreeIter iter; + + gtk_tree_model_get_iter_first (model, &iter); + if (private->has_pixels) + gtk_tree_model_iter_next (model, &iter); + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_model_row_inserted (model, path, &iter); + gtk_tree_path_free (path); + } + else if (deleted_path) + { + gtk_tree_model_row_deleted (model, deleted_path); + gtk_tree_path_free (deleted_path); + } + + g_object_notify (G_OBJECT (store), "has-percent"); + } +} + +gboolean +gimp_unit_store_get_has_percent (GimpUnitStore *store) +{ + GimpUnitStorePrivate *private; + + g_return_val_if_fail (GIMP_IS_UNIT_STORE (store), FALSE); + + private = GET_PRIVATE (store); + + return private->has_percent; +} + +void +gimp_unit_store_set_pixel_value (GimpUnitStore *store, + gint index, + gdouble value) +{ + GimpUnitStorePrivate *private; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + g_return_if_fail (index > 0 && index < private->num_values); + + private->values[index] = value; +} + +void +gimp_unit_store_set_pixel_values (GimpUnitStore *store, + gdouble first_value, + ...) +{ + GimpUnitStorePrivate *private; + va_list args; + gint i; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + va_start (args, first_value); + + for (i = 0; i < private->num_values; ) + { + private->values[i] = first_value; + + if (++i < private->num_values) + first_value = va_arg (args, gdouble); + } + + va_end (args); +} + +void +gimp_unit_store_set_resolution (GimpUnitStore *store, + gint index, + gdouble resolution) +{ + GimpUnitStorePrivate *private; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + g_return_if_fail (index > 0 && index < private->num_values); + + private->resolutions[index] = resolution; +} + +void +gimp_unit_store_set_resolutions (GimpUnitStore *store, + gdouble first_resolution, + ...) +{ + GimpUnitStorePrivate *private; + va_list args; + gint i; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + va_start (args, first_resolution); + + for (i = 0; i < private->num_values; ) + { + private->resolutions[i] = first_resolution; + + if (++i < private->num_values) + first_resolution = va_arg (args, gdouble); + } + + va_end (args); +} + +gdouble +gimp_unit_store_get_value (GimpUnitStore *store, + GimpUnit unit, + gint index) +{ + GimpUnitStorePrivate *private; + GtkTreeIter iter; + GValue value = G_VALUE_INIT; + + g_return_val_if_fail (GIMP_IS_UNIT_STORE (store), 0.0); + + private = GET_PRIVATE (store); + + g_return_val_if_fail (index >= 0 && index < private->num_values, 0.0); + + iter.user_data = GINT_TO_POINTER (unit); + + gimp_unit_store_tree_model_get_value (GTK_TREE_MODEL (store), + &iter, + GIMP_UNIT_STORE_FIRST_VALUE + index, + &value); + + return g_value_get_double (&value); +} + +void +gimp_unit_store_get_values (GimpUnitStore *store, + GimpUnit unit, + gdouble *first_value, + ...) +{ + GimpUnitStorePrivate *private; + va_list args; + gint i; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + + va_start (args, first_value); + + for (i = 0; i < private->num_values; ) + { + if (first_value) + *first_value = gimp_unit_store_get_value (store, unit, i); + + if (++i < private->num_values) + first_value = va_arg (args, gdouble *); + } + + va_end (args); +} + +void +_gimp_unit_store_sync_units (GimpUnitStore *store) +{ + GimpUnitStorePrivate *private; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_if_fail (GIMP_IS_UNIT_STORE (store)); + + private = GET_PRIVATE (store); + model = GTK_TREE_MODEL (store); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gint unit; + + gtk_tree_model_get (model, &iter, + GIMP_UNIT_STORE_UNIT, &unit, + -1); + + if (unit != GIMP_UNIT_PERCENT && + unit > private->synced_unit) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_model_row_inserted (model, path, &iter); + gtk_tree_path_free (path); + } + } + + private->synced_unit = gimp_unit_get_number_of_units () - 1; +} diff --git a/libgimpwidgets/gimpunitstore.h b/libgimpwidgets/gimpunitstore.h new file mode 100644 index 0000000..318449e --- /dev/null +++ b/libgimpwidgets/gimpunitstore.h @@ -0,0 +1,113 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpunitstore.h + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_UNIT_STORE_H__ +#define __GIMP_UNIT_STORE_H__ + +G_BEGIN_DECLS + + +enum +{ + GIMP_UNIT_STORE_UNIT, + GIMP_UNIT_STORE_UNIT_FACTOR, + GIMP_UNIT_STORE_UNIT_DIGITS, + GIMP_UNIT_STORE_UNIT_IDENTIFIER, + GIMP_UNIT_STORE_UNIT_SYMBOL, + GIMP_UNIT_STORE_UNIT_ABBREVIATION, + GIMP_UNIT_STORE_UNIT_SINGULAR, + GIMP_UNIT_STORE_UNIT_PLURAL, + GIMP_UNIT_STORE_UNIT_SHORT_FORMAT, + GIMP_UNIT_STORE_UNIT_LONG_FORMAT, + GIMP_UNIT_STORE_UNIT_COLUMNS, + GIMP_UNIT_STORE_FIRST_VALUE = GIMP_UNIT_STORE_UNIT_COLUMNS +}; + + +#define GIMP_TYPE_UNIT_STORE (gimp_unit_store_get_type ()) +#define GIMP_UNIT_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNIT_STORE, GimpUnitStore)) +#define GIMP_UNIT_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNIT_STORE, GimpUnitStoreClass)) +#define GIMP_IS_UNIT_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNIT_STORE)) +#define GIMP_IS_UNIT_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNIT_STORE)) +#define GIMP_UNIT_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNIT_STORE, GimpUnitStoreClass)) + + +typedef struct _GimpUnitStoreClass GimpUnitStoreClass; + +struct _GimpUnitStore +{ + GObject parent_instance; +}; + +struct _GimpUnitStoreClass +{ + GObjectClass parent_class; + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + + +GType gimp_unit_store_get_type (void) G_GNUC_CONST; + +GimpUnitStore * gimp_unit_store_new (gint num_values); + +void gimp_unit_store_set_has_pixels (GimpUnitStore *store, + gboolean has_pixels); +gboolean gimp_unit_store_get_has_pixels (GimpUnitStore *store); + +void gimp_unit_store_set_has_percent (GimpUnitStore *store, + gboolean has_percent); +gboolean gimp_unit_store_get_has_percent (GimpUnitStore *store); + +void gimp_unit_store_set_pixel_value (GimpUnitStore *store, + gint index, + gdouble value); +void gimp_unit_store_set_pixel_values (GimpUnitStore *store, + gdouble first_value, + ...); +void gimp_unit_store_set_resolution (GimpUnitStore *store, + gint index, + gdouble resolution); +void gimp_unit_store_set_resolutions (GimpUnitStore *store, + gdouble first_resolution, + ...); +gdouble gimp_unit_store_get_value (GimpUnitStore *store, + GimpUnit unit, + gint index); +void gimp_unit_store_get_values (GimpUnitStore *store, + GimpUnit unit, + gdouble *first_value, + ...); + +void _gimp_unit_store_sync_units (GimpUnitStore *store); + + +G_END_DECLS + +#endif /* __GIMP_UNIT_STORE_H__ */ diff --git a/libgimpwidgets/gimpwidgets-error.c b/libgimpwidgets/gimpwidgets-error.c new file mode 100644 index 0000000..bda4187 --- /dev/null +++ b/libgimpwidgets/gimpwidgets-error.c @@ -0,0 +1,40 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets-error.c + * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <glib.h> + +#include "gimpwidgets-error.h" + + +/** + * gimp_widgets_error_quark: + * + * This function is never called directly. Use GIMP_WIDGETS_ERROR() instead. + * + * Return value: the #GQuark that defines the GIMP widgets error domain. + **/ +GQuark +gimp_widgets_error_quark (void) +{ + return g_quark_from_static_string ("gimp-widgets-error-quark"); +} diff --git a/libgimpwidgets/gimpwidgets-error.h b/libgimpwidgets/gimpwidgets-error.h new file mode 100644 index 0000000..8722106 --- /dev/null +++ b/libgimpwidgets/gimpwidgets-error.h @@ -0,0 +1,58 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets-error.h + * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_WIDGETS_ERROR_H__ +#define __GIMP_WIDGETS_ERROR_H__ + +G_BEGIN_DECLS + + +/** + * GimpWidgetsError: + * @GIMP_WIDGETS_PARSE_ERROR: A parse error has occured + * + * Types of errors returned by libgimpwidgets functions + **/ +typedef enum +{ + GIMP_WIDGETS_PARSE_ERROR +} GimpWidgetsError; + + +/** + * GIMP_WIDGETS_ERROR: + * + * The GIMP widgets error domain. + * + * Since: 2.8 + */ +#define GIMP_WIDGETS_ERROR (gimp_widgets_error_quark ()) + +GQuark gimp_widgets_error_quark (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_WIDGETS_ERROR_H__ */ diff --git a/libgimpwidgets/gimpwidgets-private.c b/libgimpwidgets/gimpwidgets-private.c new file mode 100644 index 0000000..7c856c5 --- /dev/null +++ b/libgimpwidgets/gimpwidgets-private.c @@ -0,0 +1,106 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets-private.c + * Copyright (C) 2003 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <babl/babl.h> +#include <gtk/gtk.h> + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpicons.h" +#include "gimpwidgets-private.h" + +#include "libgimp/libgimp-intl.h" + + +static gboolean gimp_widgets_initialized = FALSE; + +GimpHelpFunc _gimp_standard_help_func = NULL; +GimpGetColorFunc _gimp_get_foreground_func = NULL; +GimpGetColorFunc _gimp_get_background_func = NULL; +GimpEnsureModulesFunc _gimp_ensure_modules_func = NULL; + + +static void +gimp_widgets_init_foreign_enums (void) +{ + static const GimpEnumDesc input_mode_descs[] = + { + { GDK_MODE_DISABLED, NC_("input-mode", "Disabled"), NULL }, + { GDK_MODE_SCREEN, NC_("input-mode", "Screen"), NULL }, + { GDK_MODE_WINDOW, NC_("input-mode", "Window"), NULL }, + { 0, NULL, NULL } + }; + + gimp_type_set_translation_domain (GDK_TYPE_INPUT_MODE, + GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (GDK_TYPE_INPUT_MODE, "input-mode"); + gimp_enum_set_value_descriptions (GDK_TYPE_INPUT_MODE, input_mode_descs); +} + +void +gimp_widgets_init (GimpHelpFunc standard_help_func, + GimpGetColorFunc get_foreground_func, + GimpGetColorFunc get_background_func, + GimpEnsureModulesFunc ensure_modules_func) +{ + g_return_if_fail (standard_help_func != NULL); + + if (gimp_widgets_initialized) + g_error ("gimp_widgets_init() must only be called once!"); + + _gimp_standard_help_func = standard_help_func; + _gimp_get_foreground_func = get_foreground_func; + _gimp_get_background_func = get_background_func; + _gimp_ensure_modules_func = ensure_modules_func; + + babl_init (); /* color selectors use babl */ + + gimp_icons_init (); + + gtk_window_set_default_icon_name (GIMP_ICON_WILBER); + + gimp_widgets_init_foreign_enums (); + + gimp_widgets_initialized = TRUE; +} + +/* clean up babl (in particular, so that the fish cache is constructed) if the + * compiler supports destructors + */ +#ifdef HAVE_FUNC_ATTRIBUTE_DESTRUCTOR + +__attribute__ ((destructor)) +static void +gimp_widgets_exit (void) +{ + if (gimp_widgets_initialized) + babl_exit (); +} + +#elif defined (__GNUC__) + +#warning babl_init() not paired with babl_exit() + +#endif diff --git a/libgimpwidgets/gimpwidgets-private.h b/libgimpwidgets/gimpwidgets-private.h new file mode 100644 index 0000000..4cb1245 --- /dev/null +++ b/libgimpwidgets/gimpwidgets-private.h @@ -0,0 +1,47 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets-private.h + * Copyright (C) 2003 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_WIDGETS_PRIVATE_H__ +#define __GIMP_WIDGETS_PRIVATE_H__ + + +typedef gboolean (* GimpGetColorFunc) (GimpRGB *color); +typedef void (* GimpEnsureModulesFunc) (void); + + +extern GimpHelpFunc _gimp_standard_help_func; +extern GimpGetColorFunc _gimp_get_foreground_func; +extern GimpGetColorFunc _gimp_get_background_func; +extern GimpEnsureModulesFunc _gimp_ensure_modules_func; + + +G_BEGIN_DECLS + + +void gimp_widgets_init (GimpHelpFunc standard_help_func, + GimpGetColorFunc get_foreground_func, + GimpGetColorFunc get_background_func, + GimpEnsureModulesFunc ensure_modules_func); + + +G_END_DECLS + +#endif /* __GIMP_WIDGETS_PRIVATE_H__ */ diff --git a/libgimpwidgets/gimpwidgets.c b/libgimpwidgets/gimpwidgets.c new file mode 100644 index 0000000..2958de9 --- /dev/null +++ b/libgimpwidgets/gimpwidgets.c @@ -0,0 +1,997 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets.c + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgets.h" + +#include "libgimp/libgimp-intl.h" + + +/* hack: declare prototype here instead of #undef GIMP_DISABLE_DEPRECATED */ +void gimp_toggle_button_sensitive_update (GtkToggleButton *toggle_button); + + +/** + * SECTION: gimpwidgets + * @title: GimpWidgets + * @short_description: A collection of convenient widget constructors, + * standard callbacks and helper functions. + * + * A collection of convenient widget constructors, standard callbacks + * and helper functions. + **/ + + +/** + * gimp_radio_group_new: + * @in_frame: %TRUE if you want a #GtkFrame around the radio button group. + * @frame_title: The title of the Frame or %NULL if you don't want a title. + * @...: A %NULL-terminated @va_list describing the radio buttons. + * + * Convenience function to create a group of radio buttons embedded into + * a #GtkFrame or #GtkVBox. + * + * Returns: A #GtkFrame or #GtkVBox (depending on @in_frame). + **/ +GtkWidget * +gimp_radio_group_new (gboolean in_frame, + const gchar *frame_title, + + /* specify radio buttons as va_list: + * const gchar *label, + * GCallback callback, + * gpointer callback_data, + * gpointer item_data, + * GtkWidget **widget_ptr, + * gboolean active, + */ + + ...) +{ + GtkWidget *vbox; + GtkWidget *button; + GSList *group; + + /* radio button variables */ + const gchar *label; + GCallback callback; + gpointer callback_data; + gpointer item_data; + GtkWidget **widget_ptr; + gboolean active; + + va_list args; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + + group = NULL; + + /* create the radio buttons */ + va_start (args, frame_title); + label = va_arg (args, const gchar *); + while (label) + { + callback = va_arg (args, GCallback); + callback_data = va_arg (args, gpointer); + item_data = va_arg (args, gpointer); + widget_ptr = va_arg (args, GtkWidget **); + active = va_arg (args, gboolean); + + if (label != (gpointer) 1) + button = gtk_radio_button_new_with_mnemonic (group, label); + else + button = gtk_radio_button_new (group); + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + + if (item_data) + { + g_object_set_data (G_OBJECT (button), "gimp-item-data", item_data); + + /* backward compatibility */ + g_object_set_data (G_OBJECT (button), "user_data", item_data); + } + + if (widget_ptr) + *widget_ptr = button; + + if (active) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + g_signal_connect (button, "toggled", + callback, + callback_data); + + gtk_widget_show (button); + + label = va_arg (args, const gchar *); + } + va_end (args); + + if (in_frame) + { + GtkWidget *frame; + + frame = gimp_frame_new (frame_title); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + return frame; + } + + return vbox; +} + +/** + * gimp_radio_group_new2: + * @in_frame: %TRUE if you want a #GtkFrame around the + * radio button group. + * @frame_title: The title of the Frame or %NULL if you don't want + * a title. + * @radio_button_callback: The callback each button's "toggled" signal will + * be connected with. + * @radio_button_callback_data: + * The data which will be passed to g_signal_connect(). + * @initial: The @item_data of the initially pressed radio button. + * @...: A %NULL-terminated @va_list describing + * the radio buttons. + * + * Convenience function to create a group of radio buttons embedded into + * a #GtkFrame or #GtkVBox. + * + * Returns: A #GtkFrame or #GtkVBox (depending on @in_frame). + **/ +GtkWidget * +gimp_radio_group_new2 (gboolean in_frame, + const gchar *frame_title, + GCallback radio_button_callback, + gpointer callback_data, + gpointer initial, /* item_data */ + + /* specify radio buttons as va_list: + * const gchar *label, + * gpointer item_data, + * GtkWidget **widget_ptr, + */ + + ...) +{ + GtkWidget *vbox; + GtkWidget *button; + GSList *group; + + /* radio button variables */ + const gchar *label; + gpointer item_data; + GtkWidget **widget_ptr; + + va_list args; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + + group = NULL; + + /* create the radio buttons */ + va_start (args, initial); + label = va_arg (args, const gchar *); + + while (label) + { + item_data = va_arg (args, gpointer); + widget_ptr = va_arg (args, GtkWidget **); + + if (label != (gpointer) 1) + button = gtk_radio_button_new_with_mnemonic (group, label); + else + button = gtk_radio_button_new (group); + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + + if (item_data) + { + g_object_set_data (G_OBJECT (button), "gimp-item-data", item_data); + + /* backward compatibility */ + g_object_set_data (G_OBJECT (button), "user_data", item_data); + } + + if (widget_ptr) + *widget_ptr = button; + + if (initial == item_data) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + g_signal_connect (button, "toggled", + radio_button_callback, + callback_data); + + gtk_widget_show (button); + + label = va_arg (args, const gchar *); + } + va_end (args); + + if (in_frame) + { + GtkWidget *frame; + + frame = gimp_frame_new (frame_title); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + return frame; + } + + return vbox; +} + +/** + * gimp_int_radio_group_new: + * @in_frame: %TRUE if you want a #GtkFrame around the + * radio button group. + * @frame_title: The title of the Frame or %NULL if you don't want + * a title. + * @radio_button_callback: The callback each button's "toggled" signal will + * be connected with. + * @radio_button_callback_data: + * The data which will be passed to g_signal_connect(). + * @initial: The @item_data of the initially pressed radio button. + * @...: A %NULL-terminated @va_list describing + * the radio buttons. + * + * Convenience function to create a group of radio buttons embedded into + * a #GtkFrame or #GtkVBox. This function does the same thing as + * gimp_radio_group_new2(), but it takes integers as @item_data instead of + * pointers, since that is a very common case (mapping an enum to a radio + * group). + * + * Returns: A #GtkFrame or #GtkVBox (depending on @in_frame). + **/ +GtkWidget * +gimp_int_radio_group_new (gboolean in_frame, + const gchar *frame_title, + GCallback radio_button_callback, + gpointer callback_data, + gint initial, /* item_data */ + + /* specify radio buttons as va_list: + * const gchar *label, + * gint item_data, + * GtkWidget **widget_ptr, + */ + + ...) +{ + GtkWidget *vbox; + GtkWidget *button; + GSList *group; + + /* radio button variables */ + const gchar *label; + gint item_data; + gpointer item_ptr; + GtkWidget **widget_ptr; + + va_list args; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + + group = NULL; + + /* create the radio buttons */ + va_start (args, initial); + label = va_arg (args, const gchar *); + + while (label) + { + item_data = va_arg (args, gint); + widget_ptr = va_arg (args, GtkWidget **); + + item_ptr = GINT_TO_POINTER (item_data); + + if (label != GINT_TO_POINTER (1)) + button = gtk_radio_button_new_with_mnemonic (group, label); + else + button = gtk_radio_button_new (group); + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + + if (item_data) + { + g_object_set_data (G_OBJECT (button), "gimp-item-data", item_ptr); + + /* backward compatibility */ + g_object_set_data (G_OBJECT (button), "user_data", item_ptr); + } + + if (widget_ptr) + *widget_ptr = button; + + if (initial == item_data) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + g_signal_connect (button, "toggled", + radio_button_callback, + callback_data); + + gtk_widget_show (button); + + label = va_arg (args, const gchar *); + } + va_end (args); + + if (in_frame) + { + GtkWidget *frame; + + frame = gimp_frame_new (frame_title); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + return frame; + } + + return vbox; +} + +/** + * gimp_radio_group_set_active: + * @radio_button: Pointer to a #GtkRadioButton. + * @item_data: The @item_data of the radio button you want to select. + * + * Calls gtk_toggle_button_set_active() with the radio button that was + * created with a matching @item_data. + **/ +void +gimp_radio_group_set_active (GtkRadioButton *radio_button, + gpointer item_data) +{ + GtkWidget *button; + GSList *group; + + g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button)); + + for (group = gtk_radio_button_get_group (radio_button); + group; + group = g_slist_next (group)) + { + button = GTK_WIDGET (group->data); + + if (g_object_get_data (G_OBJECT (button), "gimp-item-data") == item_data) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + return; + } + } +} + +/** + * gimp_int_radio_group_set_active: + * @radio_button: Pointer to a #GtkRadioButton. + * @item_data: The @item_data of the radio button you want to select. + * + * Calls gtk_toggle_button_set_active() with the radio button that was created + * with a matching @item_data. This function does the same thing as + * gimp_radio_group_set_active(), but takes integers as @item_data instead + * of pointers. + **/ +void +gimp_int_radio_group_set_active (GtkRadioButton *radio_button, + gint item_data) +{ + g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button)); + + gimp_radio_group_set_active (radio_button, GINT_TO_POINTER (item_data)); +} + +/** + * gimp_spin_button_new: + * @adjustment: Returns the spinbutton's #GtkAdjustment. + * @value: The initial value of the spinbutton. + * @lower: The lower boundary. + * @upper: The upper boundary. + * @step_increment: The spinbutton's step increment. + * @page_increment: The spinbutton's page increment (mouse button 2). + * @page_size: Ignored, spin buttons must always have a zero page size. + * @climb_rate: The spinbutton's climb rate. + * @digits: The spinbutton's number of decimal digits. + * + * This function is a shortcut for gtk_adjustment_new() and a + * subsequent gtk_spin_button_new(). It also calls + * gtk_spin_button_set_numeric() so that non-numeric text cannot be + * entered. + * + * Deprecated: 2.10: Use gtk_spin_button_new() instead. + * + * Returns: A #GtkSpinButton and its #GtkAdjustment. + **/ +GtkWidget * +gimp_spin_button_new (GtkObject **adjustment, /* return value */ + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size, + gdouble climb_rate, + guint digits) +{ + GtkWidget *spinbutton; + + *adjustment = gtk_adjustment_new (value, lower, upper, + step_increment, page_increment, 0); + + spinbutton = gimp_spin_button_new (GTK_ADJUSTMENT (*adjustment), + climb_rate, digits); + + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + return spinbutton; +} + +static void +gimp_random_seed_update (GtkWidget *widget, + gpointer data) +{ + GtkWidget *spinbutton = data; + + /* Generate a new seed if the "New Seed" button was clicked or + * of the "Randomize" toggle is activated + */ + if (! GTK_IS_TOGGLE_BUTTON (widget) || + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinbutton), + (guint) g_random_int ()); + } +} + +/** + * gimp_random_seed_new: + * @seed: A pointer to the variable which stores the random seed. + * @random_seed: A pointer to a boolean indicating whether seed should be + * initialised randomly or not. + * + * Creates a widget that allows the user to control how the random number + * generator is initialized. + * + * Returns: A #GtkHBox containing a #GtkSpinButton for the seed and + * a #GtkButton for setting a random seed. + **/ +GtkWidget * +gimp_random_seed_new (guint *seed, + gboolean *random_seed) +{ + GtkWidget *hbox; + GtkWidget *toggle; + GtkWidget *spinbutton; + GtkAdjustment *adj; + GtkWidget *button; + + g_return_val_if_fail (seed != NULL, NULL); + g_return_val_if_fail (random_seed != NULL, NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + + /* If we're being asked to generate a random seed, generate one. */ + if (*random_seed) + *seed = g_random_int (); + + adj = (GtkAdjustment *) + gtk_adjustment_new (*seed, 0, (guint32) -1, 1, 10, 0); + spinbutton = gimp_spin_button_new (adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + g_signal_connect (adj, "value-changed", + G_CALLBACK (gimp_uint_adjustment_update), + seed); + + gimp_help_set_help_data (spinbutton, + _("Use this value for random number generator " + "seed - this allows you to repeat a " + "given \"random\" operation"), NULL); + + button = gtk_button_new_with_mnemonic (_("_New Seed")); + gtk_misc_set_padding (GTK_MISC (gtk_bin_get_child (GTK_BIN (button))), 2, 0); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + /* Send spinbutton as data so that we can change the value in + * gimp_random_seed_update() + */ + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_random_seed_update), + spinbutton); + + gimp_help_set_help_data (button, + _("Seed random number generator with a generated " + "random number"), + NULL); + + toggle = gtk_check_button_new_with_mnemonic (_("_Randomize")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), *random_seed); + gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + random_seed); + + /* Need to create a new seed when the "Randomize" toggle is activated */ + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_random_seed_update), + spinbutton); + + g_object_set_data (G_OBJECT (hbox), "spinbutton", spinbutton); + g_object_set_data (G_OBJECT (hbox), "button", button); + g_object_set_data (G_OBJECT (hbox), "toggle", toggle); + + g_object_bind_property (toggle, "active", + spinbutton, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + g_object_bind_property (toggle, "active", + button, "sensitive", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + return hbox; +} + +typedef struct +{ + GimpChainButton *chainbutton; + gboolean chain_constrains_ratio; + gdouble orig_x; + gdouble orig_y; + gdouble last_x; + gdouble last_y; +} GimpCoordinatesData; + +static void +gimp_coordinates_callback (GtkWidget *widget, + GimpCoordinatesData *data) +{ + gdouble new_x; + gdouble new_y; + + new_x = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0); + new_y = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1); + + if (gimp_chain_button_get_active (data->chainbutton)) + { + if (data->chain_constrains_ratio) + { + if ((data->orig_x != 0) && (data->orig_y != 0)) + { + g_signal_handlers_block_by_func (widget, + gimp_coordinates_callback, + data); + + if (ROUND (new_x) != ROUND (data->last_x)) + { + data->last_x = new_x; + new_y = (new_x * data->orig_y) / data->orig_x; + + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 1, + new_y); + data->last_y + = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1); + } + else if (ROUND (new_y) != ROUND (data->last_y)) + { + data->last_y = new_y; + new_x = (new_y * data->orig_x) / data->orig_y; + + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 0, + new_x); + data->last_x + = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0); + } + + g_signal_handlers_unblock_by_func (widget, + gimp_coordinates_callback, + data); + } + } + else + { + if (new_x != data->last_x) + { + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 1, new_x); + data->last_y = data->last_x + = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1); + } + else if (new_y != data->last_y) + { + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 0, new_y); + data->last_x = data->last_y + = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0); + } + } + } + else + { + if (new_x != data->last_x) + data->last_x = new_x; + if (new_y != data->last_y) + data->last_y = new_y; + } +} + +static void +gimp_coordinates_data_free (GimpCoordinatesData *data) +{ + g_slice_free (GimpCoordinatesData, data); +} + +static void +gimp_coordinates_chainbutton_toggled (GimpChainButton *button, + GimpSizeEntry *entry) +{ + if (gimp_chain_button_get_active (button)) + { + GimpCoordinatesData *data; + + data = g_object_get_data (G_OBJECT (entry), "coordinates-data"); + + data->orig_x = gimp_size_entry_get_refval (entry, 0); + data->orig_y = gimp_size_entry_get_refval (entry, 1); + } +} + +/** + * gimp_coordinates_new: + * @unit: The initial unit of the #GimpUnitMenu. + * @unit_format: A printf-like unit-format string as is used with + * gimp_unit_menu_new(). + * @menu_show_pixels: %TRUE if the #GimpUnitMenu should contain an item + * for GIMP_UNIT_PIXEL. + * @menu_show_percent: %TRUE if the #GimpUnitMenu should contain an item + * for GIMP_UNIT_PERCENT. + * @spinbutton_width: The horizontal size of the #GimpSizeEntry's + * #GtkSpinButton's. + * @update_policy: The update policy for the #GimpSizeEntry. + * @chainbutton_active: %TRUE if the attached #GimpChainButton should be + * active. + * @chain_constrains_ratio: %TRUE if the chainbutton should constrain the + * fields' aspect ratio. If %FALSE, the values will + * be constrained. + * @xlabel: The label for the X coordinate. + * @x: The initial value of the X coordinate. + * @xres: The horizontal resolution in DPI. + * @lower_boundary_x: The lower boundary of the X coordinate. + * @upper_boundary_x: The upper boundary of the X coordinate. + * @xsize_0: The X value which will be treated as 0%. + * @xsize_100: The X value which will be treated as 100%. + * @ylabel: The label for the Y coordinate. + * @y: The initial value of the Y coordinate. + * @yres: The vertical resolution in DPI. + * @lower_boundary_y: The lower boundary of the Y coordinate. + * @upper_boundary_y: The upper boundary of the Y coordinate. + * @ysize_0: The Y value which will be treated as 0%. + * @ysize_100: The Y value which will be treated as 100%. + * + * Convenience function that creates a #GimpSizeEntry with two fields for x/y + * coordinates/sizes with a #GimpChainButton attached to constrain either the + * two fields' values or the ratio between them. + * + * Returns: The new #GimpSizeEntry. + **/ +GtkWidget * +gimp_coordinates_new (GimpUnit unit, + const gchar *unit_format, + gboolean menu_show_pixels, + gboolean menu_show_percent, + gint spinbutton_width, + GimpSizeEntryUpdatePolicy update_policy, + + gboolean chainbutton_active, + gboolean chain_constrains_ratio, + + const gchar *xlabel, + gdouble x, + gdouble xres, + gdouble lower_boundary_x, + gdouble upper_boundary_x, + gdouble xsize_0, /* % */ + gdouble xsize_100, /* % */ + + const gchar *ylabel, + gdouble y, + gdouble yres, + gdouble lower_boundary_y, + gdouble upper_boundary_y, + gdouble ysize_0, /* % */ + gdouble ysize_100 /* % */) +{ + GimpCoordinatesData *data; + GtkAdjustment *adjustment; + GtkWidget *spinbutton; + GtkWidget *sizeentry; + GtkWidget *chainbutton; + + adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 0, 1, 1, 10, 0); + spinbutton = gimp_spin_button_new (adjustment, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + if (spinbutton_width > 0) + { + if (spinbutton_width < 17) + gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), spinbutton_width); + else + gtk_widget_set_size_request (spinbutton, spinbutton_width, -1); + } + + sizeentry = gimp_size_entry_new (1, unit, unit_format, + menu_show_pixels, + menu_show_percent, + FALSE, + spinbutton_width, + update_policy); + gtk_table_set_col_spacing (GTK_TABLE (sizeentry), 0, 4); + gtk_table_set_col_spacing (GTK_TABLE (sizeentry), 2, 4); + gimp_size_entry_add_field (GIMP_SIZE_ENTRY (sizeentry), + GTK_SPIN_BUTTON (spinbutton), NULL); + gtk_table_attach_defaults (GTK_TABLE (sizeentry), spinbutton, 1, 2, 0, 1); + gtk_widget_show (spinbutton); + + gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (sizeentry), + (update_policy == GIMP_SIZE_ENTRY_UPDATE_RESOLUTION) || + (menu_show_pixels == FALSE) ? + GIMP_UNIT_INCH : GIMP_UNIT_PIXEL); + + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (sizeentry), 0, xres, TRUE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (sizeentry), 1, yres, TRUE); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (sizeentry), 0, + lower_boundary_x, upper_boundary_x); + gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (sizeentry), 1, + lower_boundary_y, upper_boundary_y); + + if (menu_show_percent) + { + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (sizeentry), 0, + xsize_0, xsize_100); + gimp_size_entry_set_size (GIMP_SIZE_ENTRY (sizeentry), 1, + ysize_0, ysize_100); + } + + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (sizeentry), 0, x); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (sizeentry), 1, y); + + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + xlabel, 0, 0, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + ylabel, 1, 0, 0.0); + + chainbutton = gimp_chain_button_new (GIMP_CHAIN_RIGHT); + + if (chainbutton_active) + gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chainbutton), TRUE); + + gtk_table_attach (GTK_TABLE (sizeentry), chainbutton, 2, 3, 0, 2, + GTK_SHRINK | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (chainbutton); + + data = g_slice_new (GimpCoordinatesData); + + data->chainbutton = GIMP_CHAIN_BUTTON (chainbutton); + data->chain_constrains_ratio = chain_constrains_ratio; + data->orig_x = x; + data->orig_y = y; + data->last_x = x; + data->last_y = y; + + g_object_set_data_full (G_OBJECT (sizeentry), "coordinates-data", + data, + (GDestroyNotify) gimp_coordinates_data_free); + + g_signal_connect (sizeentry, "value-changed", + G_CALLBACK (gimp_coordinates_callback), + data); + + g_object_set_data (G_OBJECT (sizeentry), "chainbutton", chainbutton); + + g_signal_connect (chainbutton, "toggled", + G_CALLBACK (gimp_coordinates_chainbutton_toggled), + sizeentry); + + return sizeentry; +} + + +/* + * Standard Callbacks + */ + +/** + * gimp_toggle_button_sensitive_update: + * @toggle_button: The #GtkToggleButton the "set_sensitive" and + * "inverse_sensitive" lists are attached to. + * + * If you attached a pointer to a #GtkWidget with g_object_set_data() and + * the "set_sensitive" key to the #GtkToggleButton, the sensitive state of + * the attached widget will be set according to the toggle button's + * "active" state. + * + * You can attach an arbitrary list of widgets by attaching another + * "set_sensitive" data pointer to the first widget (and so on...). + * + * This function can also set the sensitive state according to the toggle + * button's inverse "active" state by attaching widgets with the + * "inverse_sensitive" key. + * + * Deprecated: use g_object_bind_property() instead of using the + * "set_sensitive" and "inverse_sensitive" data pointers. + **/ +void +gimp_toggle_button_sensitive_update (GtkToggleButton *toggle_button) +{ + GtkWidget *set_sensitive; + gboolean active; + + active = gtk_toggle_button_get_active (toggle_button); + + set_sensitive = + g_object_get_data (G_OBJECT (toggle_button), "set_sensitive"); + while (set_sensitive) + { + gtk_widget_set_sensitive (set_sensitive, active); + set_sensitive = + g_object_get_data (G_OBJECT (set_sensitive), "set_sensitive"); + } + + set_sensitive = + g_object_get_data (G_OBJECT (toggle_button), "inverse_sensitive"); + while (set_sensitive) + { + gtk_widget_set_sensitive (set_sensitive, ! active); + set_sensitive = + g_object_get_data (G_OBJECT (set_sensitive), "inverse_sensitive"); + } +} + +/** + * gimp_toggle_button_update: + * @widget: A #GtkToggleButton. + * @data: A pointer to a #gint variable which will store the value of + * gtk_toggle_button_get_active(). + * + * Note that this function calls gimp_toggle_button_sensitive_update() + * which is a deprecated hack you shouldn't use. See that function's + * documentation for a proper replacement of its functionality. + **/ +void +gimp_toggle_button_update (GtkWidget *widget, + gpointer data) +{ + gint *toggle_val = (gint *) data; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + *toggle_val = TRUE; + else + *toggle_val = FALSE; + + gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget)); +} + +/** + * gimp_radio_button_update: + * @widget: A #GtkRadioButton. + * @data: A pointer to a #gint variable which will store the value of + * GPOINTER_TO_INT (g_object_get_data (@widget, "gimp-item-data")). + * + * Note that this function calls gimp_toggle_button_sensitive_update() + * which is a deprecated hack you shouldn't use. See that function's + * documentation for a proper replacement of its functionality. + **/ +void +gimp_radio_button_update (GtkWidget *widget, + gpointer data) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + gint *toggle_val = (gint *) data; + + *toggle_val = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-item-data")); + } + + gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget)); +} + +/** + * gimp_int_adjustment_update: + * @adjustment: A #GtkAdjustment. + * @data: A pointer to a #gint variable which will store the + * @adjustment's value. + * + * Note that the #GtkAdjustment's value (which is a #gdouble) will be + * rounded with RINT(). + **/ +void +gimp_int_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gint *val = (gint *) data; + + *val = RINT (gtk_adjustment_get_value (adjustment)); +} + +/** + * gimp_uint_adjustment_update: + * @adjustment: A #GtkAdjustment. + * @data: A pointer to a #guint variable which will store the + * @adjustment's value. + * + * Note that the #GtkAdjustment's value (which is a #gdouble) will be rounded + * with (#guint) (value + 0.5). + **/ +void +gimp_uint_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + guint *val = (guint *) data; + + *val = (guint) (gtk_adjustment_get_value (adjustment) + 0.5); +} + +/** + * gimp_float_adjustment_update: + * @adjustment: A #GtkAdjustment. + * @data: A pointer to a #gfloat variable which will store the + * @adjustment's value. + **/ +void +gimp_float_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gfloat *val = (gfloat *) data; + + *val = gtk_adjustment_get_value (adjustment); + +} + +/** + * gimp_double_adjustment_update: + * @adjustment: A #GtkAdjustment. + * @data: A pointer to a #gdouble variable which will store the + * @adjustment's value. + **/ +void +gimp_double_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gdouble *val = (gdouble *) data; + + *val = gtk_adjustment_get_value (adjustment); +} diff --git a/libgimpwidgets/gimpwidgets.def b/libgimpwidgets/gimpwidgets.def new file mode 100644 index 0000000..ec1e44c --- /dev/null +++ b/libgimpwidgets/gimpwidgets.def @@ -0,0 +1,470 @@ +EXPORTS + gdk_cairo_get_clip_rectangle + gdk_event_triggers_context_menu + gdk_keymap_get_modifier_mask + gdk_screen_get_monitor_workarea + gimp_aspect_type_get_type + gimp_browser_add_search_types + gimp_browser_get_type + gimp_browser_new + gimp_browser_set_widget + gimp_browser_show_message + gimp_busy_box_get_message + gimp_busy_box_get_type + gimp_busy_box_new + gimp_busy_box_set_message + gimp_button_extended_clicked + gimp_button_get_type + gimp_button_new + gimp_cairo_set_focus_line_pattern + gimp_cairo_surface_create_from_pixbuf + gimp_cell_renderer_color_get_type + gimp_cell_renderer_color_new + gimp_cell_renderer_toggle_clicked + gimp_cell_renderer_toggle_get_type + gimp_cell_renderer_toggle_new + gimp_chain_button_get_active + gimp_chain_button_get_icon_size + gimp_chain_button_get_type + gimp_chain_button_new + gimp_chain_button_set_active + gimp_chain_button_set_icon_size + gimp_chain_position_get_type + gimp_color_area_get_color + gimp_color_area_get_type + gimp_color_area_has_alpha + gimp_color_area_new + gimp_color_area_set_color + gimp_color_area_set_color_config + gimp_color_area_set_draw_border + gimp_color_area_set_type + gimp_color_area_type_get_type + gimp_color_button_get_color + gimp_color_button_get_title + gimp_color_button_get_type + gimp_color_button_get_ui_manager + gimp_color_button_get_update + gimp_color_button_has_alpha + gimp_color_button_new + gimp_color_button_set_color + gimp_color_button_set_color_config + gimp_color_button_set_title + gimp_color_button_set_type + gimp_color_button_set_update + gimp_color_display_changed + gimp_color_display_clone + gimp_color_display_configure + gimp_color_display_configure_reset + gimp_color_display_convert + gimp_color_display_convert_buffer + gimp_color_display_convert_surface + gimp_color_display_get_config + gimp_color_display_get_enabled + gimp_color_display_get_managed + gimp_color_display_get_type + gimp_color_display_load_state + gimp_color_display_new + gimp_color_display_save_state + gimp_color_display_set_enabled + gimp_color_display_stack_add + gimp_color_display_stack_changed + gimp_color_display_stack_clone + gimp_color_display_stack_convert + gimp_color_display_stack_convert_buffer + gimp_color_display_stack_convert_surface + gimp_color_display_stack_get_type + gimp_color_display_stack_new + gimp_color_display_stack_remove + gimp_color_display_stack_reorder_down + gimp_color_display_stack_reorder_up + gimp_color_hex_entry_get_color + gimp_color_hex_entry_get_type + gimp_color_hex_entry_new + gimp_color_hex_entry_set_color + gimp_color_notebook_get_type + gimp_color_notebook_set_has_page + gimp_color_picker_cursors_get_resource + gimp_color_profile_chooser_dialog_get_type + gimp_color_profile_chooser_dialog_new + gimp_color_profile_combo_box_add + gimp_color_profile_combo_box_add_file + gimp_color_profile_combo_box_get_active + gimp_color_profile_combo_box_get_active_file + gimp_color_profile_combo_box_get_type + gimp_color_profile_combo_box_new + gimp_color_profile_combo_box_new_with_model + gimp_color_profile_combo_box_set_active + gimp_color_profile_combo_box_set_active_file + gimp_color_profile_store_add + gimp_color_profile_store_add_file + gimp_color_profile_store_get_type + gimp_color_profile_store_new + gimp_color_profile_view_get_type + gimp_color_profile_view_new + gimp_color_profile_view_set_error + gimp_color_profile_view_set_profile + gimp_color_scale_entry_new + gimp_color_scale_get_type + gimp_color_scale_new + gimp_color_scale_set_channel + gimp_color_scale_set_color + gimp_color_scale_set_color_config + gimp_color_scales_get_show_rgb_u8 + gimp_color_scales_get_type + gimp_color_scales_set_show_rgb_u8 + gimp_color_select_get_type + gimp_color_selection_color_changed + gimp_color_selection_get_color + gimp_color_selection_get_old_color + gimp_color_selection_get_show_alpha + gimp_color_selection_get_type + gimp_color_selection_new + gimp_color_selection_reset + gimp_color_selection_set_color + gimp_color_selection_set_config + gimp_color_selection_set_old_color + gimp_color_selection_set_show_alpha + gimp_color_selector_channel_changed + gimp_color_selector_channel_get_type + gimp_color_selector_color_changed + gimp_color_selector_get_channel + gimp_color_selector_get_color + gimp_color_selector_get_model_visible + gimp_color_selector_get_show_alpha + gimp_color_selector_get_toggles_sensitive + gimp_color_selector_get_toggles_visible + gimp_color_selector_get_type + gimp_color_selector_model_get_type + gimp_color_selector_model_visible_changed + gimp_color_selector_new + gimp_color_selector_set_channel + gimp_color_selector_set_color + gimp_color_selector_set_config + gimp_color_selector_set_model_visible + gimp_color_selector_set_show_alpha + gimp_color_selector_set_toggles_sensitive + gimp_color_selector_set_toggles_visible + gimp_context_help + gimp_controller_event + gimp_controller_get_event_blurb + gimp_controller_get_event_name + gimp_controller_get_n_events + gimp_controller_get_type + gimp_controller_new + gimp_coordinates_new + gimp_dialog_add_button + gimp_dialog_add_buttons + gimp_dialog_add_buttons_valist + gimp_dialog_get_type + gimp_dialog_new + gimp_dialog_new_valist + gimp_dialog_run + gimp_dialogs_show_help_button + gimp_double_adjustment_update + gimp_eevl_evaluate + gimp_enum_combo_box_get_type + gimp_enum_combo_box_new + gimp_enum_combo_box_new_with_model + gimp_enum_combo_box_set_icon_prefix + gimp_enum_combo_box_set_stock_prefix + gimp_enum_icon_box_new + gimp_enum_icon_box_new_with_range + gimp_enum_icon_box_set_child_padding + gimp_enum_label_get_type + gimp_enum_label_new + gimp_enum_label_set_value + gimp_enum_radio_box_new + gimp_enum_radio_box_new_with_range + gimp_enum_radio_frame_new + gimp_enum_radio_frame_new_with_range + gimp_enum_stock_box_new + gimp_enum_stock_box_new_with_range + gimp_enum_stock_box_set_child_padding + gimp_enum_store_get_type + gimp_enum_store_new + gimp_enum_store_new_with_range + gimp_enum_store_new_with_values + gimp_enum_store_new_with_values_valist + gimp_enum_store_set_icon_prefix + gimp_enum_store_set_stock_prefix + gimp_file_entry_get_filename + gimp_file_entry_get_type + gimp_file_entry_new + gimp_file_entry_set_filename + gimp_float_adjustment_update + gimp_frame_get_type + gimp_frame_new + gimp_get_monitor_at_pointer + gimp_help_connect + gimp_help_disable_tooltips + gimp_help_enable_tooltips + gimp_help_id_quark + gimp_help_set_help_data + gimp_help_set_help_data_with_markup + gimp_hint_box_get_type + gimp_hint_box_new + gimp_icon_pixbufs_get_resource + gimp_icons_init + gimp_icons_set_icon_theme + gimp_int_adjustment_update + gimp_int_combo_box_append + gimp_int_combo_box_connect + gimp_int_combo_box_get_active + gimp_int_combo_box_get_active_user_data + gimp_int_combo_box_get_label + gimp_int_combo_box_get_layout + gimp_int_combo_box_get_type + gimp_int_combo_box_layout_get_type + gimp_int_combo_box_new + gimp_int_combo_box_new_array + gimp_int_combo_box_new_valist + gimp_int_combo_box_prepend + gimp_int_combo_box_set_active + gimp_int_combo_box_set_active_by_user_data + gimp_int_combo_box_set_label + gimp_int_combo_box_set_layout + gimp_int_combo_box_set_sensitivity + gimp_int_option_menu_new + gimp_int_option_menu_set_history + gimp_int_option_menu_set_sensitive + gimp_int_radio_group_new + gimp_int_radio_group_set_active + gimp_int_store_get_type + gimp_int_store_lookup_by_user_data + gimp_int_store_lookup_by_value + gimp_int_store_new + gimp_label_set_attributes + gimp_memsize_entry_get_type + gimp_memsize_entry_get_value + gimp_memsize_entry_new + gimp_memsize_entry_set_value + gimp_menu_item_update + gimp_number_pair_entry_get_aspect + gimp_number_pair_entry_get_default_text + gimp_number_pair_entry_get_default_values + gimp_number_pair_entry_get_ratio + gimp_number_pair_entry_get_type + gimp_number_pair_entry_get_user_override + gimp_number_pair_entry_get_values + gimp_number_pair_entry_new + gimp_number_pair_entry_set_aspect + gimp_number_pair_entry_set_default_text + gimp_number_pair_entry_set_default_values + gimp_number_pair_entry_set_ratio + gimp_number_pair_entry_set_user_override + gimp_number_pair_entry_set_values + gimp_offset_area_get_type + gimp_offset_area_new + gimp_offset_area_set_offsets + gimp_offset_area_set_pixbuf + gimp_offset_area_set_size + gimp_option_menu_new + gimp_option_menu_new2 + gimp_option_menu_set_history + gimp_option_menu_set_sensitive + gimp_page_selector_get_n_pages + gimp_page_selector_get_page_label + gimp_page_selector_get_page_thumbnail + gimp_page_selector_get_selected_pages + gimp_page_selector_get_selected_range + gimp_page_selector_get_target + gimp_page_selector_get_type + gimp_page_selector_new + gimp_page_selector_page_is_selected + gimp_page_selector_select_all + gimp_page_selector_select_page + gimp_page_selector_select_range + gimp_page_selector_set_n_pages + gimp_page_selector_set_page_label + gimp_page_selector_set_page_thumbnail + gimp_page_selector_set_target + gimp_page_selector_target_get_type + gimp_page_selector_unselect_all + gimp_page_selector_unselect_page + gimp_path_editor_get_dir_writable + gimp_path_editor_get_path + gimp_path_editor_get_type + gimp_path_editor_get_writable_path + gimp_path_editor_new + gimp_path_editor_set_dir_writable + gimp_path_editor_set_path + gimp_path_editor_set_writable_path + gimp_pick_button_get_type + gimp_pick_button_new + gimp_pixmap_button_new + gimp_pixmap_get_type + gimp_pixmap_new + gimp_pixmap_set + gimp_preview_area_blend + gimp_preview_area_draw + gimp_preview_area_fill + gimp_preview_area_get_type + gimp_preview_area_mask + gimp_preview_area_menu_popup + gimp_preview_area_new + gimp_preview_area_set_color_config + gimp_preview_area_set_colormap + gimp_preview_area_set_max_size + gimp_preview_area_set_offsets + gimp_preview_draw + gimp_preview_draw_buffer + gimp_preview_get_area + gimp_preview_get_controls + gimp_preview_get_position + gimp_preview_get_size + gimp_preview_get_type + gimp_preview_get_update + gimp_preview_invalidate + gimp_preview_set_bounds + gimp_preview_set_default_cursor + gimp_preview_set_update + gimp_preview_transform + gimp_preview_untransform + gimp_prop_boolean_combo_box_new + gimp_prop_boolean_radio_frame_new + gimp_prop_check_button_new + gimp_prop_color_area_new + gimp_prop_coordinates_connect + gimp_prop_coordinates_new + gimp_prop_entry_new + gimp_prop_enum_check_button_new + gimp_prop_enum_combo_box_new + gimp_prop_enum_icon_box_new + gimp_prop_enum_label_new + gimp_prop_enum_radio_box_new + gimp_prop_enum_radio_frame_new + gimp_prop_enum_stock_box_new + gimp_prop_expander_new + gimp_prop_file_chooser_button_new + gimp_prop_file_chooser_button_new_with_dialog + gimp_prop_hscale_new + gimp_prop_icon_image_new + gimp_prop_int_combo_box_new + gimp_prop_label_new + gimp_prop_memsize_entry_new + gimp_prop_opacity_entry_new + gimp_prop_path_editor_new + gimp_prop_pointer_combo_box_new + gimp_prop_scale_entry_new + gimp_prop_size_entry_new + gimp_prop_spin_button_new + gimp_prop_stock_image_new + gimp_prop_string_combo_box_new + gimp_prop_text_buffer_new + gimp_prop_unit_combo_box_new + gimp_prop_unit_menu_new + gimp_query_boolean_box + gimp_query_double_box + gimp_query_int_box + gimp_query_size_box + gimp_query_string_box + gimp_radio_button_update + gimp_radio_group_new + gimp_radio_group_new2 + gimp_radio_group_set_active + gimp_random_seed_new + gimp_ruler_add_track_widget + gimp_ruler_get_position + gimp_ruler_get_range + gimp_ruler_get_type + gimp_ruler_get_unit + gimp_ruler_new + gimp_ruler_remove_track_widget + gimp_ruler_set_position + gimp_ruler_set_range + gimp_ruler_set_unit + gimp_scale_entry_get_logarithmic + gimp_scale_entry_new + gimp_scale_entry_set_logarithmic + gimp_scale_entry_set_sensitive + gimp_screen_get_color_profile + gimp_scrolled_preview_freeze + gimp_scrolled_preview_get_type + gimp_scrolled_preview_set_policy + gimp_scrolled_preview_set_position + gimp_scrolled_preview_thaw + gimp_size_entry_add_field + gimp_size_entry_attach_label + gimp_size_entry_get_help_widget + gimp_size_entry_get_refval + gimp_size_entry_get_type + gimp_size_entry_get_unit + gimp_size_entry_get_value + gimp_size_entry_grab_focus + gimp_size_entry_new + gimp_size_entry_set_activates_default + gimp_size_entry_set_pixel_digits + gimp_size_entry_set_refval + gimp_size_entry_set_refval_boundaries + gimp_size_entry_set_refval_digits + gimp_size_entry_set_resolution + gimp_size_entry_set_size + gimp_size_entry_set_unit + gimp_size_entry_set_value + gimp_size_entry_set_value_boundaries + gimp_size_entry_show_unit_menu + gimp_size_entry_update_policy_get_type + gimp_spin_button_get_type + gimp_spin_button_new + gimp_spin_button_new_ + gimp_spin_button_new_with_range + gimp_standard_help_func + gimp_stock_init + gimp_string_combo_box_get_active + gimp_string_combo_box_get_type + gimp_string_combo_box_new + gimp_string_combo_box_set_active + gimp_table_attach_aligned + gimp_toggle_button_sensitive_update + gimp_toggle_button_update + gimp_uint_adjustment_update + gimp_unit_combo_box_get_active + gimp_unit_combo_box_get_type + gimp_unit_combo_box_new + gimp_unit_combo_box_new_with_model + gimp_unit_combo_box_set_active + gimp_unit_menu_get_pixel_digits + gimp_unit_menu_get_type + gimp_unit_menu_get_unit + gimp_unit_menu_new + gimp_unit_menu_set_pixel_digits + gimp_unit_menu_set_unit + gimp_unit_menu_update + gimp_unit_store_get_has_percent + gimp_unit_store_get_has_pixels + gimp_unit_store_get_type + gimp_unit_store_get_value + gimp_unit_store_get_values + gimp_unit_store_new + gimp_unit_store_set_has_percent + gimp_unit_store_set_has_pixels + gimp_unit_store_set_pixel_value + gimp_unit_store_set_pixel_values + gimp_unit_store_set_resolution + gimp_unit_store_set_resolutions + gimp_widget_get_color_profile + gimp_widget_get_color_transform + gimp_widget_get_monitor + gimp_widget_track_monitor + gimp_widgets_error_quark + gimp_widgets_init + gimp_zoom_button_new + gimp_zoom_model_get_factor + gimp_zoom_model_get_fraction + gimp_zoom_model_get_type + gimp_zoom_model_new + gimp_zoom_model_set_range + gimp_zoom_model_zoom + gimp_zoom_model_zoom_step + gimp_zoom_type_get_type + gtk_box_new + gtk_button_box_new + gtk_label_get_xalign + gtk_label_get_yalign + gtk_label_set_xalign + gtk_label_set_yalign + gtk_paned_new + gtk_scale_new + gtk_scrollbar_new + gtk_separator_new + gtk_widget_get_modifier_mask diff --git a/libgimpwidgets/gimpwidgets.h b/libgimpwidgets/gimpwidgets.h new file mode 100644 index 0000000..f3d8b77 --- /dev/null +++ b/libgimpwidgets/gimpwidgets.h @@ -0,0 +1,256 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets.h + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_WIDGETS_H__ +#define __GIMP_WIDGETS_H__ + +#define __GIMP_WIDGETS_H_INSIDE__ + +#include <libgimpwidgets/gimpwidgetstypes.h> + +#include <libgimpwidgets/gimpbrowser.h> +#include <libgimpwidgets/gimpbusybox.h> +#include <libgimpwidgets/gimpbutton.h> +#include <libgimpwidgets/gimpcairo-utils.h> +#include <libgimpwidgets/gimpcellrenderercolor.h> +#include <libgimpwidgets/gimpcellrenderertoggle.h> +#include <libgimpwidgets/gimpchainbutton.h> +#include <libgimpwidgets/gimpcolorarea.h> +#include <libgimpwidgets/gimpcolorbutton.h> +#include <libgimpwidgets/gimpcolordisplay.h> +#include <libgimpwidgets/gimpcolordisplaystack.h> +#include <libgimpwidgets/gimpcolorhexentry.h> +#include <libgimpwidgets/gimpcolornotebook.h> +#include <libgimpwidgets/gimpcolorprofilechooserdialog.h> +#include <libgimpwidgets/gimpcolorprofilecombobox.h> +#include <libgimpwidgets/gimpcolorprofilestore.h> +#include <libgimpwidgets/gimpcolorprofileview.h> +#include <libgimpwidgets/gimpcolorscale.h> +#include <libgimpwidgets/gimpcolorscales.h> +#include <libgimpwidgets/gimpcolorselector.h> +#include <libgimpwidgets/gimpcolorselect.h> +#include <libgimpwidgets/gimpcolorselection.h> +#include <libgimpwidgets/gimpdialog.h> +#include <libgimpwidgets/gimpenumcombobox.h> +#include <libgimpwidgets/gimpenumlabel.h> +#include <libgimpwidgets/gimpenumstore.h> +#include <libgimpwidgets/gimpenumwidgets.h> +#include <libgimpwidgets/gimpfileentry.h> +#include <libgimpwidgets/gimpframe.h> +#include <libgimpwidgets/gimphelpui.h> +#include <libgimpwidgets/gimphintbox.h> +#include <libgimpwidgets/gimpicons.h> +#include <libgimpwidgets/gimpintcombobox.h> +#include <libgimpwidgets/gimpintstore.h> +#include <libgimpwidgets/gimpmemsizeentry.h> +#include <libgimpwidgets/gimpnumberpairentry.h> +#include <libgimpwidgets/gimpoffsetarea.h> +#include <libgimpwidgets/gimppageselector.h> +#include <libgimpwidgets/gimppatheditor.h> +#include <libgimpwidgets/gimppickbutton.h> +#include <libgimpwidgets/gimppixmap.h> +#include <libgimpwidgets/gimppreview.h> +#include <libgimpwidgets/gimppreviewarea.h> +#include <libgimpwidgets/gimppropwidgets.h> +#include <libgimpwidgets/gimpquerybox.h> +#include <libgimpwidgets/gimpruler.h> +#include <libgimpwidgets/gimpscaleentry.h> +#include <libgimpwidgets/gimpscrolledpreview.h> +#include <libgimpwidgets/gimpsizeentry.h> +#include <libgimpwidgets/gimpspinbutton.h> +#include <libgimpwidgets/gimpstringcombobox.h> +#include <libgimpwidgets/gimpunitcombobox.h> +#include <libgimpwidgets/gimpunitmenu.h> +#include <libgimpwidgets/gimpunitstore.h> +#include <libgimpwidgets/gimpwidgets-error.h> +#include <libgimpwidgets/gimpwidgetsutils.h> +#include <libgimpwidgets/gimpzoommodel.h> + +#include <libgimpwidgets/gimp3migration.h> +#include <libgimpwidgets/gimpoldwidgets.h> + +#undef __GIMP_WIDGETS_H_INSIDE__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +/* + * Widget Constructors + */ + +GtkWidget * gimp_int_radio_group_new (gboolean in_frame, + const gchar *frame_title, + GCallback radio_button_callback, + gpointer radio_button_callback_data, + gint initial, /* item_data */ + + /* specify radio buttons as va_list: + * const gchar *label, + * gint item_data, + * GtkWidget **widget_ptr, + */ + + ...) G_GNUC_NULL_TERMINATED; + +void gimp_int_radio_group_set_active (GtkRadioButton *radio_button, + gint item_data); + + +GtkWidget * gimp_radio_group_new (gboolean in_frame, + const gchar *frame_title, + + /* specify radio buttons as va_list: + * const gchar *label, + * GCallback callback, + * gpointer callback_data, + * gpointer item_data, + * GtkWidget **widget_ptr, + * gboolean active, + */ + + ...) G_GNUC_NULL_TERMINATED; +GtkWidget * gimp_radio_group_new2 (gboolean in_frame, + const gchar *frame_title, + GCallback radio_button_callback, + gpointer radio_button_callback_data, + gpointer initial, /* item_data */ + + /* specify radio buttons as va_list: + * const gchar *label, + * gpointer item_data, + * GtkWidget **widget_ptr, + */ + + ...) G_GNUC_NULL_TERMINATED; + +void gimp_radio_group_set_active (GtkRadioButton *radio_button, + gpointer item_data); + + +GIMP_DEPRECATED_FOR(gtk_spin_button_new) +GtkWidget * gimp_spin_button_new (/* return value: */ + GtkObject **adjustment, + + gdouble value, + gdouble lower, + gdouble upper, + gdouble step_increment, + gdouble page_increment, + gdouble page_size, + gdouble climb_rate, + guint digits); + +/** + * GIMP_RANDOM_SEED_SPINBUTTON: + * @hbox: The #GtkHBox returned by gimp_random_seed_new(). + * + * Returns: the random_seed's #GtkSpinButton. + **/ +#define GIMP_RANDOM_SEED_SPINBUTTON(hbox) \ + (g_object_get_data (G_OBJECT (hbox), "spinbutton")) + +/** + * GIMP_RANDOM_SEED_SPINBUTTON_ADJ: + * @hbox: The #GtkHBox returned by gimp_random_seed_new(). + * + * Returns: the #GtkAdjustment of the random_seed's #GtkSpinButton. + **/ +#define GIMP_RANDOM_SEED_SPINBUTTON_ADJ(hbox) \ + gtk_spin_button_get_adjustment \ + (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (hbox), "spinbutton"))) + +/** + * GIMP_RANDOM_SEED_TOGGLE: + * @hbox: The #GtkHBox returned by gimp_random_seed_new(). + * + * Returns: the random_seed's #GtkToggleButton. + **/ +#define GIMP_RANDOM_SEED_TOGGLE(hbox) \ + (g_object_get_data (G_OBJECT(hbox), "toggle")) + +GtkWidget * gimp_random_seed_new (guint32 *seed, + gboolean *random_seed); + +/** + * GIMP_COORDINATES_CHAINBUTTON: + * @sizeentry: The #GimpSizeEntry returned by gimp_coordinates_new(). + * + * Returns: the #GimpChainButton which is attached to the + * #GimpSizeEntry. + **/ +#define GIMP_COORDINATES_CHAINBUTTON(sizeentry) \ + (g_object_get_data (G_OBJECT (sizeentry), "chainbutton")) + +GtkWidget * gimp_coordinates_new (GimpUnit unit, + const gchar *unit_format, + gboolean menu_show_pixels, + gboolean menu_show_percent, + gint spinbutton_width, + GimpSizeEntryUpdatePolicy update_policy, + + gboolean chainbutton_active, + gboolean chain_constrains_ratio, + + const gchar *xlabel, + gdouble x, + gdouble xres, + gdouble lower_boundary_x, + gdouble upper_boundary_x, + gdouble xsize_0, /* % */ + gdouble xsize_100, /* % */ + + const gchar *ylabel, + gdouble y, + gdouble yres, + gdouble lower_boundary_y, + gdouble upper_boundary_y, + gdouble ysize_0, /* % */ + gdouble ysize_100 /* % */); + + +/* + * Standard Callbacks + */ + +void gimp_toggle_button_update (GtkWidget *widget, + gpointer data); + +void gimp_radio_button_update (GtkWidget *widget, + gpointer data); + +void gimp_int_adjustment_update (GtkAdjustment *adjustment, + gpointer data); + +void gimp_uint_adjustment_update (GtkAdjustment *adjustment, + gpointer data); + +void gimp_float_adjustment_update (GtkAdjustment *adjustment, + gpointer data); + +void gimp_double_adjustment_update (GtkAdjustment *adjustment, + gpointer data); + + +G_END_DECLS + +#endif /* __GIMP_WIDGETS_H__ */ diff --git a/libgimpwidgets/gimpwidgetsenums.c b/libgimpwidgets/gimpwidgetsenums.c new file mode 100644 index 0000000..82b16a1 --- /dev/null +++ b/libgimpwidgets/gimpwidgetsenums.c @@ -0,0 +1,313 @@ + +/* Generated data (by gimp-mkenums) */ + +#include "config.h" +#include <gio/gio.h> +#include "libgimpbase/gimpbase.h" +#include "gimpwidgetsenums.h" +#include "libgimp/libgimp-intl.h" + +/* enumerations from "gimpwidgetsenums.h" */ +GType +gimp_aspect_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_ASPECT_SQUARE, "GIMP_ASPECT_SQUARE", "square" }, + { GIMP_ASPECT_PORTRAIT, "GIMP_ASPECT_PORTRAIT", "portrait" }, + { GIMP_ASPECT_LANDSCAPE, "GIMP_ASPECT_LANDSCAPE", "landscape" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_ASPECT_SQUARE, NC_("aspect-type", "Square"), NULL }, + { GIMP_ASPECT_PORTRAIT, NC_("aspect-type", "Portrait"), NULL }, + { GIMP_ASPECT_LANDSCAPE, NC_("aspect-type", "Landscape"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpAspectType", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "aspect-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_chain_position_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_CHAIN_TOP, "GIMP_CHAIN_TOP", "top" }, + { GIMP_CHAIN_LEFT, "GIMP_CHAIN_LEFT", "left" }, + { GIMP_CHAIN_BOTTOM, "GIMP_CHAIN_BOTTOM", "bottom" }, + { GIMP_CHAIN_RIGHT, "GIMP_CHAIN_RIGHT", "right" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_CHAIN_TOP, "GIMP_CHAIN_TOP", NULL }, + { GIMP_CHAIN_LEFT, "GIMP_CHAIN_LEFT", NULL }, + { GIMP_CHAIN_BOTTOM, "GIMP_CHAIN_BOTTOM", NULL }, + { GIMP_CHAIN_RIGHT, "GIMP_CHAIN_RIGHT", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpChainPosition", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "chain-position"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_color_area_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COLOR_AREA_FLAT, "GIMP_COLOR_AREA_FLAT", "flat" }, + { GIMP_COLOR_AREA_SMALL_CHECKS, "GIMP_COLOR_AREA_SMALL_CHECKS", "small-checks" }, + { GIMP_COLOR_AREA_LARGE_CHECKS, "GIMP_COLOR_AREA_LARGE_CHECKS", "large-checks" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COLOR_AREA_FLAT, "GIMP_COLOR_AREA_FLAT", NULL }, + { GIMP_COLOR_AREA_SMALL_CHECKS, "GIMP_COLOR_AREA_SMALL_CHECKS", NULL }, + { GIMP_COLOR_AREA_LARGE_CHECKS, "GIMP_COLOR_AREA_LARGE_CHECKS", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpColorAreaType", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "color-area-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_color_selector_channel_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COLOR_SELECTOR_HUE, "GIMP_COLOR_SELECTOR_HUE", "hue" }, + { GIMP_COLOR_SELECTOR_SATURATION, "GIMP_COLOR_SELECTOR_SATURATION", "saturation" }, + { GIMP_COLOR_SELECTOR_VALUE, "GIMP_COLOR_SELECTOR_VALUE", "value" }, + { GIMP_COLOR_SELECTOR_RED, "GIMP_COLOR_SELECTOR_RED", "red" }, + { GIMP_COLOR_SELECTOR_GREEN, "GIMP_COLOR_SELECTOR_GREEN", "green" }, + { GIMP_COLOR_SELECTOR_BLUE, "GIMP_COLOR_SELECTOR_BLUE", "blue" }, + { GIMP_COLOR_SELECTOR_ALPHA, "GIMP_COLOR_SELECTOR_ALPHA", "alpha" }, + { GIMP_COLOR_SELECTOR_LCH_LIGHTNESS, "GIMP_COLOR_SELECTOR_LCH_LIGHTNESS", "lch-lightness" }, + { GIMP_COLOR_SELECTOR_LCH_CHROMA, "GIMP_COLOR_SELECTOR_LCH_CHROMA", "lch-chroma" }, + { GIMP_COLOR_SELECTOR_LCH_HUE, "GIMP_COLOR_SELECTOR_LCH_HUE", "lch-hue" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COLOR_SELECTOR_HUE, NC_("color-selector-channel", "_H"), N_("HSV Hue") }, + { GIMP_COLOR_SELECTOR_SATURATION, NC_("color-selector-channel", "_S"), N_("HSV Saturation") }, + { GIMP_COLOR_SELECTOR_VALUE, NC_("color-selector-channel", "_V"), N_("HSV Value") }, + { GIMP_COLOR_SELECTOR_RED, NC_("color-selector-channel", "_R"), N_("Red") }, + { GIMP_COLOR_SELECTOR_GREEN, NC_("color-selector-channel", "_G"), N_("Green") }, + { GIMP_COLOR_SELECTOR_BLUE, NC_("color-selector-channel", "_B"), N_("Blue") }, + { GIMP_COLOR_SELECTOR_ALPHA, NC_("color-selector-channel", "_A"), N_("Alpha") }, + { GIMP_COLOR_SELECTOR_LCH_LIGHTNESS, NC_("color-selector-channel", "_L"), N_("LCh Lightness") }, + { GIMP_COLOR_SELECTOR_LCH_CHROMA, NC_("color-selector-channel", "_C"), N_("LCh Chroma") }, + { GIMP_COLOR_SELECTOR_LCH_HUE, NC_("color-selector-channel", "_h"), N_("LCh Hue") }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpColorSelectorChannel", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "color-selector-channel"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_color_selector_model_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COLOR_SELECTOR_MODEL_RGB, "GIMP_COLOR_SELECTOR_MODEL_RGB", "rgb" }, + { GIMP_COLOR_SELECTOR_MODEL_LCH, "GIMP_COLOR_SELECTOR_MODEL_LCH", "lch" }, + { GIMP_COLOR_SELECTOR_MODEL_HSV, "GIMP_COLOR_SELECTOR_MODEL_HSV", "hsv" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COLOR_SELECTOR_MODEL_RGB, NC_("color-selector-model", "RGB"), N_("RGB color model") }, + { GIMP_COLOR_SELECTOR_MODEL_LCH, NC_("color-selector-model", "LCH"), N_("CIE LCh color model") }, + { GIMP_COLOR_SELECTOR_MODEL_HSV, NC_("color-selector-model", "HSV"), N_("HSV color model") }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpColorSelectorModel", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "color-selector-model"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_int_combo_box_layout_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY, "GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY", "icon-only" }, + { GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED, "GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED", "abbreviated" }, + { GIMP_INT_COMBO_BOX_LAYOUT_FULL, "GIMP_INT_COMBO_BOX_LAYOUT_FULL", "full" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY, "GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY", NULL }, + { GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED, "GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED", NULL }, + { GIMP_INT_COMBO_BOX_LAYOUT_FULL, "GIMP_INT_COMBO_BOX_LAYOUT_FULL", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpIntComboBoxLayout", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "int-combo-box-layout"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_page_selector_target_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_PAGE_SELECTOR_TARGET_LAYERS, "GIMP_PAGE_SELECTOR_TARGET_LAYERS", "layers" }, + { GIMP_PAGE_SELECTOR_TARGET_IMAGES, "GIMP_PAGE_SELECTOR_TARGET_IMAGES", "images" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_PAGE_SELECTOR_TARGET_LAYERS, NC_("page-selector-target", "Layers"), NULL }, + { GIMP_PAGE_SELECTOR_TARGET_IMAGES, NC_("page-selector-target", "Images"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpPageSelectorTarget", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "page-selector-target"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_size_entry_update_policy_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_SIZE_ENTRY_UPDATE_NONE, "GIMP_SIZE_ENTRY_UPDATE_NONE", "none" }, + { GIMP_SIZE_ENTRY_UPDATE_SIZE, "GIMP_SIZE_ENTRY_UPDATE_SIZE", "size" }, + { GIMP_SIZE_ENTRY_UPDATE_RESOLUTION, "GIMP_SIZE_ENTRY_UPDATE_RESOLUTION", "resolution" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_SIZE_ENTRY_UPDATE_NONE, "GIMP_SIZE_ENTRY_UPDATE_NONE", NULL }, + { GIMP_SIZE_ENTRY_UPDATE_SIZE, "GIMP_SIZE_ENTRY_UPDATE_SIZE", NULL }, + { GIMP_SIZE_ENTRY_UPDATE_RESOLUTION, "GIMP_SIZE_ENTRY_UPDATE_RESOLUTION", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpSizeEntryUpdatePolicy", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "size-entry-update-policy"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_zoom_type_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_ZOOM_IN, "GIMP_ZOOM_IN", "in" }, + { GIMP_ZOOM_OUT, "GIMP_ZOOM_OUT", "out" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_ZOOM_IN, NC_("zoom-type", "Zoom in"), NULL }, + { GIMP_ZOOM_OUT, NC_("zoom-type", "Zoom out"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpZoomType", values); + gimp_type_set_translation_domain (type, GETTEXT_PACKAGE "-libgimp"); + gimp_type_set_translation_context (type, "zoom-type"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + + +/* Generated data ends here */ + diff --git a/libgimpwidgets/gimpwidgetsenums.h b/libgimpwidgets/gimpwidgetsenums.h new file mode 100644 index 0000000..23f162e --- /dev/null +++ b/libgimpwidgets/gimpwidgetsenums.h @@ -0,0 +1,239 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_WIDGETS_ENUMS_H__ +#define __GIMP_WIDGETS_ENUMS_H__ + + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +/** + * GimpAspectType: + * @GIMP_ASPECT_SQUARE: it's a 1:1 square + * @GIMP_ASPECT_PORTRAIT: it's higher than it's wide + * @GIMP_ASPECT_LANDSCAPE: it's wider than it's high + * + * Aspect ratios. + **/ +#define GIMP_TYPE_ASPECT_TYPE (gimp_aspect_type_get_type ()) + +GType gimp_aspect_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_ASPECT_SQUARE, /*< desc="Square" >*/ + GIMP_ASPECT_PORTRAIT, /*< desc="Portrait" >*/ + GIMP_ASPECT_LANDSCAPE /*< desc="Landscape" >*/ +} GimpAspectType; + + +/** + * GimpChainPosition: + * @GIMP_CHAIN_TOP: the chain is on top + * @GIMP_CHAIN_LEFT: the chain is to the left + * @GIMP_CHAIN_BOTTOM: the chain is on bottom + * @GIMP_CHAIN_RIGHT: the chain is to the right + * + * Possible chain positions for #GimpChainButton. + **/ +#define GIMP_TYPE_CHAIN_POSITION (gimp_chain_position_get_type ()) + +GType gimp_chain_position_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_CHAIN_TOP, + GIMP_CHAIN_LEFT, + GIMP_CHAIN_BOTTOM, + GIMP_CHAIN_RIGHT +} GimpChainPosition; + + +/** + * GimpColorAreaType: + * @GIMP_COLOR_AREA_FLAT: don't display transparency + * @GIMP_COLOR_AREA_SMALL_CHECKS: display transparency using small checks + * @GIMP_COLOR_AREA_LARGE_CHECKS: display transparency using large checks + * + * The types of transparency display for #GimpColorArea. + **/ +#define GIMP_TYPE_COLOR_AREA_TYPE (gimp_color_area_type_get_type ()) + +GType gimp_color_area_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COLOR_AREA_FLAT = 0, + GIMP_COLOR_AREA_SMALL_CHECKS, + GIMP_COLOR_AREA_LARGE_CHECKS +} GimpColorAreaType; + + +/** + * GimpColorSelectorChannel: + * @GIMP_COLOR_SELECTOR_HUE: the hue channel + * @GIMP_COLOR_SELECTOR_SATURATION: the saturation channel + * @GIMP_COLOR_SELECTOR_VALUE: the value channel + * @GIMP_COLOR_SELECTOR_RED: the red channel + * @GIMP_COLOR_SELECTOR_GREEN: the green channel + * @GIMP_COLOR_SELECTOR_BLUE: the blue channel + * @GIMP_COLOR_SELECTOR_ALPHA: the alpha channel + * @GIMP_COLOR_SELECTOR_LCH_LIGHTNESS: the lightness channel + * @GIMP_COLOR_SELECTOR_LCH_CHROMA: the chroma channel + * @GIMP_COLOR_SELECTOR_LCH_HUE: the hue channel + * + * An enum to specify the types of color channels edited in + * #GimpColorSelector widgets. + **/ +#define GIMP_TYPE_COLOR_SELECTOR_CHANNEL (gimp_color_selector_channel_get_type ()) + +GType gimp_color_selector_channel_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COLOR_SELECTOR_HUE, /*< desc="_H", help="HSV Hue" >*/ + GIMP_COLOR_SELECTOR_SATURATION, /*< desc="_S", help="HSV Saturation" >*/ + GIMP_COLOR_SELECTOR_VALUE, /*< desc="_V", help="HSV Value" >*/ + GIMP_COLOR_SELECTOR_RED, /*< desc="_R", help="Red" >*/ + GIMP_COLOR_SELECTOR_GREEN, /*< desc="_G", help="Green" >*/ + GIMP_COLOR_SELECTOR_BLUE, /*< desc="_B", help="Blue" >*/ + GIMP_COLOR_SELECTOR_ALPHA, /*< desc="_A", help="Alpha" >*/ + GIMP_COLOR_SELECTOR_LCH_LIGHTNESS, /*< desc="_L", help="LCh Lightness" >*/ + GIMP_COLOR_SELECTOR_LCH_CHROMA, /*< desc="_C", help="LCh Chroma" >*/ + GIMP_COLOR_SELECTOR_LCH_HUE /*< desc="_h", help="LCh Hue" >*/ +} GimpColorSelectorChannel; + + +/** + * GimpColorSelectorModel: + * @GIMP_COLOR_SELECTOR_MODEL_RGB: RGB color model + * @GIMP_COLOR_SELECTOR_MODEL_LCH: CIE LCh color model + * @GIMP_COLOR_SELECTOR_MODEL_HSV: HSV color model + * + * An enum to specify the types of color spaces edited in + * #GimpColorSelector widgets. + * + * Since: 2.10 + **/ +#define GIMP_TYPE_COLOR_SELECTOR_MODEL (gimp_color_selector_model_get_type ()) + +GType gimp_color_selector_model_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COLOR_SELECTOR_MODEL_RGB, /*< desc="RGB", help="RGB color model" >*/ + GIMP_COLOR_SELECTOR_MODEL_LCH, /*< desc="LCH", help="CIE LCh color model" >*/ + GIMP_COLOR_SELECTOR_MODEL_HSV /*< desc="HSV", help="HSV color model" >*/ +} GimpColorSelectorModel; + + +/** + * GimpIntComboBoxLayout: + * @GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY: show icons only + * @GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED: show icons and abbreviated labels, + * when available + * @GIMP_INT_COMBO_BOX_LAYOUT_FULL: show icons and full labels + * + * Possible layouts for #GimpIntComboBox. + * + * Since: 2.10 + **/ +#define GIMP_TYPE_INT_COMBO_BOX_LAYOUT (gimp_int_combo_box_layout_get_type ()) + +GType gimp_int_combo_box_layout_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY, + GIMP_INT_COMBO_BOX_LAYOUT_ABBREVIATED, + GIMP_INT_COMBO_BOX_LAYOUT_FULL +} GimpIntComboBoxLayout; + + +/** + * GimpPageSelectorTarget: + * @GIMP_PAGE_SELECTOR_TARGET_LAYERS: import as layers of one image + * @GIMP_PAGE_SELECTOR_TARGET_IMAGES: import as separate images + * + * Import targets for #GimpPageSelector. + **/ +#define GIMP_TYPE_PAGE_SELECTOR_TARGET (gimp_page_selector_target_get_type ()) + +GType gimp_page_selector_target_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_PAGE_SELECTOR_TARGET_LAYERS, /*< desc="Layers" >*/ + GIMP_PAGE_SELECTOR_TARGET_IMAGES /*< desc="Images" >*/ +} GimpPageSelectorTarget; + + +/** + * GimpSizeEntryUpdatePolicy: + * @GIMP_SIZE_ENTRY_UPDATE_NONE: the size entry's meaning is up to the user + * @GIMP_SIZE_ENTRY_UPDATE_SIZE: the size entry displays values + * @GIMP_SIZE_ENTRY_UPDATE_RESOLUTION: the size entry displays resolutions + * + * Update policies for #GimpSizeEntry. + **/ +#define GIMP_TYPE_SIZE_ENTRY_UPDATE_POLICY (gimp_size_entry_update_policy_get_type ()) + +GType gimp_size_entry_update_policy_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_SIZE_ENTRY_UPDATE_NONE = 0, + GIMP_SIZE_ENTRY_UPDATE_SIZE = 1, + GIMP_SIZE_ENTRY_UPDATE_RESOLUTION = 2 +} GimpSizeEntryUpdatePolicy; + + +/** + * GimpZoomType: + * @GIMP_ZOOM_IN: zoom in + * @GIMP_ZOOM_OUT: zoom out + * @GIMP_ZOOM_IN_MORE: zoom in a lot + * @GIMP_ZOOM_OUT_MORE: zoom out a lot + * @GIMP_ZOOM_IN_MAX: zoom in as far as possible + * @GIMP_ZOOM_OUT_MAX: zoom out as far as possible + * @GIMP_ZOOM_TO: zoom to a specific zoom factor + * + * the zoom types for #GimpZoomModel. + **/ +#define GIMP_TYPE_ZOOM_TYPE (gimp_zoom_type_get_type ()) + +GType gimp_zoom_type_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_ZOOM_IN, /*< desc="Zoom in" >*/ + GIMP_ZOOM_OUT, /*< desc="Zoom out" >*/ + GIMP_ZOOM_IN_MORE, /*< skip >*/ + GIMP_ZOOM_OUT_MORE, /*< skip >*/ + GIMP_ZOOM_IN_MAX, /*< skip >*/ + GIMP_ZOOM_OUT_MAX, /*< skip >*/ + GIMP_ZOOM_TO /*< skip >*/ +} GimpZoomType; + + +G_END_DECLS + +#endif /* __GIMP_WIDGETS_ENUMS_H__ */ diff --git a/libgimpwidgets/gimpwidgetsmarshal.c b/libgimpwidgets/gimpwidgetsmarshal.c new file mode 100644 index 0000000..08cc8f4 --- /dev/null +++ b/libgimpwidgets/gimpwidgetsmarshal.c @@ -0,0 +1,433 @@ +/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ +#include <glib-object.h> + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + +/* VOID: ENUM (../../libgimpwidgets/gimpwidgetsmarshal.list:25) */ +#define _gimp_widgets_marshal_VOID__ENUM g_cclosure_marshal_VOID__ENUM + +/* VOID: ENUM, BOOLEAN (../../libgimpwidgets/gimpwidgetsmarshal.list:26) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__ENUM_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__ENUM_BOOLEAN (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__ENUM_BOOLEAN) (gpointer data1, + gint arg1, + gboolean arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__ENUM_BOOLEAN callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__ENUM_BOOLEAN) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_enum (param_values + 1), + g_marshal_value_peek_boolean (param_values + 2), + data2); +} + +/* VOID: INT, INT (../../libgimpwidgets/gimpwidgetsmarshal.list:27) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__INT_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__INT_INT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__INT_INT) (gpointer data1, + gint arg1, + gint arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__INT_INT callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__INT_INT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_int (param_values + 1), + g_marshal_value_peek_int (param_values + 2), + data2); +} + +/* VOID: OBJECT (../../libgimpwidgets/gimpwidgetsmarshal.list:28) */ +#define _gimp_widgets_marshal_VOID__OBJECT g_cclosure_marshal_VOID__OBJECT + +/* VOID: OBJECT, INT (../../libgimpwidgets/gimpwidgetsmarshal.list:29) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__OBJECT_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__OBJECT_INT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__OBJECT_INT) (gpointer data1, + gpointer arg1, + gint arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__OBJECT_INT callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__OBJECT_INT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_object (param_values + 1), + g_marshal_value_peek_int (param_values + 2), + data2); +} + +/* VOID: POINTER, POINTER (../../libgimpwidgets/gimpwidgetsmarshal.list:30) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__POINTER_POINTER (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_POINTER) (gpointer data1, + gpointer arg1, + gpointer arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__POINTER_POINTER callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_pointer (param_values + 2), + data2); +} + +/* VOID: STRING, FLAGS (../../libgimpwidgets/gimpwidgetsmarshal.list:31) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__STRING_FLAGS (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__STRING_FLAGS (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__STRING_FLAGS) (gpointer data1, + gpointer arg1, + guint arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__STRING_FLAGS callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__STRING_FLAGS) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_string (param_values + 1), + g_marshal_value_peek_flags (param_values + 2), + data2); +} + +/* VOID: STRING, INT (../../libgimpwidgets/gimpwidgetsmarshal.list:32) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__STRING_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__STRING_INT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__STRING_INT) (gpointer data1, + gpointer arg1, + gint arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__STRING_INT callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__STRING_INT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_string (param_values + 1), + g_marshal_value_peek_int (param_values + 2), + data2); +} + +/* VOID: DOUBLE, DOUBLE (../../libgimpwidgets/gimpwidgetsmarshal.list:33) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE) (gpointer data1, + gdouble arg1, + gdouble arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__DOUBLE_DOUBLE callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_double (param_values + 1), + g_marshal_value_peek_double (param_values + 2), + data2); +} + +/* BOOLEAN: POINTER (../../libgimpwidgets/gimpwidgetsmarshal.list:35) */ +/* Prototype for -Wmissing-prototypes */ +G_BEGIN_DECLS +extern +void _gimp_widgets_marshal_BOOLEAN__POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +G_END_DECLS +void +_gimp_widgets_marshal_BOOLEAN__POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER) (gpointer data1, + gpointer arg1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_BOOLEAN__POINTER callback; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__POINTER) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + data2); + + g_value_set_boolean (return_value, v_return); +} + diff --git a/libgimpwidgets/gimpwidgetsmarshal.h b/libgimpwidgets/gimpwidgetsmarshal.h new file mode 100644 index 0000000..e4010b6 --- /dev/null +++ b/libgimpwidgets/gimpwidgetsmarshal.h @@ -0,0 +1,90 @@ +/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ +#ifndef ___GIMP_WIDGETS_MARSHAL_MARSHAL_H__ +#define ___GIMP_WIDGETS_MARSHAL_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* VOID: ENUM (../../libgimpwidgets/gimpwidgetsmarshal.list:25) */ +#define _gimp_widgets_marshal_VOID__ENUM g_cclosure_marshal_VOID__ENUM + +/* VOID: ENUM, BOOLEAN (../../libgimpwidgets/gimpwidgetsmarshal.list:26) */ +extern +void _gimp_widgets_marshal_VOID__ENUM_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID: INT, INT (../../libgimpwidgets/gimpwidgetsmarshal.list:27) */ +extern +void _gimp_widgets_marshal_VOID__INT_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID: OBJECT (../../libgimpwidgets/gimpwidgetsmarshal.list:28) */ +#define _gimp_widgets_marshal_VOID__OBJECT g_cclosure_marshal_VOID__OBJECT + +/* VOID: OBJECT, INT (../../libgimpwidgets/gimpwidgetsmarshal.list:29) */ +extern +void _gimp_widgets_marshal_VOID__OBJECT_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID: POINTER, POINTER (../../libgimpwidgets/gimpwidgetsmarshal.list:30) */ +extern +void _gimp_widgets_marshal_VOID__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID: STRING, FLAGS (../../libgimpwidgets/gimpwidgetsmarshal.list:31) */ +extern +void _gimp_widgets_marshal_VOID__STRING_FLAGS (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID: STRING, INT (../../libgimpwidgets/gimpwidgetsmarshal.list:32) */ +extern +void _gimp_widgets_marshal_VOID__STRING_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID: DOUBLE, DOUBLE (../../libgimpwidgets/gimpwidgetsmarshal.list:33) */ +extern +void _gimp_widgets_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* BOOLEAN: POINTER (../../libgimpwidgets/gimpwidgetsmarshal.list:35) */ +extern +void _gimp_widgets_marshal_BOOLEAN__POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + + +G_END_DECLS + +#endif /* ___GIMP_WIDGETS_MARSHAL_MARSHAL_H__ */ diff --git a/libgimpwidgets/gimpwidgetsmarshal.list b/libgimpwidgets/gimpwidgetsmarshal.list new file mode 100644 index 0000000..8983fa8 --- /dev/null +++ b/libgimpwidgets/gimpwidgetsmarshal.list @@ -0,0 +1,35 @@ +# see glib-genmarshal(1) for a detailed description of the file format, +# possible parameter types are: +# VOID indicates no return type, or no extra +# parameters. if VOID is used as the parameter +# list, no additional parameters may be present. +# BOOLEAN for boolean types (gboolean) +# CHAR for signed char types (gchar) +# UCHAR for unsigned char types (guchar) +# INT for signed integer types (gint) +# UINT for unsigned integer types (guint) +# LONG for signed long integer types (glong) +# ULONG for unsigned long integer types (gulong) +# ENUM for enumeration types (gint) +# FLAGS for flag enumeration types (guint) +# FLOAT for single-precision float types (gfloat) +# DOUBLE for double-precision float types (gdouble) +# STRING for string types (gchar*) +# BOXED for boxed (anonymous but reference counted) types (GBoxed*) +# POINTER for anonymous pointer types (gpointer) +# PARAM for GParamSpec or derived types (GParamSpec*) +# OBJECT for GObject or derived types (GObject*) +# NONE deprecated alias for VOID +# BOOL deprecated alias for BOOLEAN + +VOID: ENUM +VOID: ENUM, BOOLEAN +VOID: INT, INT +VOID: OBJECT +VOID: OBJECT, INT +VOID: POINTER, POINTER +VOID: STRING, FLAGS +VOID: STRING, INT +VOID: DOUBLE, DOUBLE + +BOOLEAN: POINTER diff --git a/libgimpwidgets/gimpwidgetstypes.h b/libgimpwidgets/gimpwidgetstypes.h new file mode 100644 index 0000000..cf2ac14 --- /dev/null +++ b/libgimpwidgets/gimpwidgetstypes.h @@ -0,0 +1,110 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgetstypes.h + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#ifndef __GIMP_WIDGETS_TYPES_H__ +#define __GIMP_WIDGETS_TYPES_H__ + +#include <libgimpconfig/gimpconfigtypes.h> + +#include <libgimpwidgets/gimpwidgetsenums.h> + +G_BEGIN_DECLS + +/* For information look into the html documentation */ + + +typedef struct _GimpBrowser GimpBrowser; +typedef struct _GimpBusyBox GimpBusyBox; +typedef struct _GimpButton GimpButton; +typedef struct _GimpCellRendererColor GimpCellRendererColor; +typedef struct _GimpCellRendererToggle GimpCellRendererToggle; +typedef struct _GimpChainButton GimpChainButton; +typedef struct _GimpColorArea GimpColorArea; +typedef struct _GimpColorButton GimpColorButton; +typedef struct _GimpColorDisplay GimpColorDisplay; +typedef struct _GimpColorDisplayStack GimpColorDisplayStack; +typedef struct _GimpColorHexEntry GimpColorHexEntry; +typedef struct _GimpColorNotebook GimpColorNotebook; +typedef struct _GimpColorProfileChooserDialog GimpColorProfileChooserDialog; +typedef struct _GimpColorProfileComboBox GimpColorProfileComboBox; +typedef struct _GimpColorProfileStore GimpColorProfileStore; +typedef struct _GimpColorProfileView GimpColorProfileView; +typedef struct _GimpColorScale GimpColorScale; +typedef struct _GimpColorScales GimpColorScales; +typedef struct _GimpColorSelector GimpColorSelector; +typedef struct _GimpColorSelect GimpColorSelect; +typedef struct _GimpColorSelection GimpColorSelection; +typedef struct _GimpController GimpController; +typedef struct _GimpDialog GimpDialog; +typedef struct _GimpEnumStore GimpEnumStore; +typedef struct _GimpEnumComboBox GimpEnumComboBox; +typedef struct _GimpEnumLabel GimpEnumLabel; +typedef struct _GimpFileEntry GimpFileEntry; +typedef struct _GimpFrame GimpFrame; +typedef struct _GimpHintBox GimpHintBox; +typedef struct _GimpIntComboBox GimpIntComboBox; +typedef struct _GimpIntStore GimpIntStore; +typedef struct _GimpMemsizeEntry GimpMemsizeEntry; +typedef struct _GimpNumberPairEntry GimpNumberPairEntry; +typedef struct _GimpOffsetArea GimpOffsetArea; +typedef struct _GimpPageSelector GimpPageSelector; +typedef struct _GimpPathEditor GimpPathEditor; +typedef struct _GimpPickButton GimpPickButton; +typedef struct _GimpPreview GimpPreview; +typedef struct _GimpPreviewArea GimpPreviewArea; +typedef struct _GimpPixmap GimpPixmap; +typedef struct _GimpRuler GimpRuler; +typedef struct _GimpScrolledPreview GimpScrolledPreview; +typedef struct _GimpSizeEntry GimpSizeEntry; +typedef struct _GimpSpinButton GimpSpinButton; +typedef struct _GimpStringComboBox GimpStringComboBox; +typedef struct _GimpUnitComboBox GimpUnitComboBox; +typedef struct _GimpUnitMenu GimpUnitMenu; +typedef struct _GimpUnitStore GimpUnitStore; +typedef struct _GimpZoomModel GimpZoomModel; + + +/** + * GimpHelpFunc: + * @help_id: the help ID + * @help_data: the help user data + * + * This is the prototype for all functions you pass as @help_func to + * the various GIMP dialog constructors like gimp_dialog_new(), + * gimp_query_int_box() etc. + * + * Help IDs are textual identifiers the help system uses to figure + * which page to display. + * + * All these dialog constructors functions call gimp_help_connect(). + * + * In most cases it will be ok to use gimp_standard_help_func() which + * does nothing but passing the @help_id string to gimp_help(). If + * your plug-in needs some more sophisticated help handling you can + * provide your own @help_func which has to call gimp_help() to + * actually display the help. + **/ +typedef void (* GimpHelpFunc) (const gchar *help_id, + gpointer help_data); + + +G_END_DECLS + +#endif /* __GIMP_WIDGETS_TYPES_H__ */ diff --git a/libgimpwidgets/gimpwidgetsutils.c b/libgimpwidgets/gimpwidgetsutils.c new file mode 100644 index 0000000..9cc507c --- /dev/null +++ b/libgimpwidgets/gimpwidgetsutils.c @@ -0,0 +1,921 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgets.c + * Copyright (C) 2000 Michael Natterer <mitch@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <lcms2.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#ifdef G_OS_WIN32 +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 +#include <windows.h> +#include <icm.h> +#endif + +#ifdef GDK_WINDOWING_QUARTZ +#include <Carbon/Carbon.h> +#include <ApplicationServices/ApplicationServices.h> +#include <CoreServices/CoreServices.h> +#endif + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gimpwidgetstypes.h" + +#include "gimp3migration.h" +#include "gimpsizeentry.h" +#include "gimpwidgetsutils.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpwidgetsutils + * @title: GimpWidgetsUtils + * @short_description: A collection of helper functions. + * + * A collection of helper functions. + **/ + + +static GtkWidget * +find_mnemonic_widget (GtkWidget *widget, + gint level) +{ + gboolean can_focus; + + g_object_get (widget, "can-focus", &can_focus, NULL); + + if (GTK_WIDGET_GET_CLASS (widget)->activate_signal || + can_focus || + GTK_WIDGET_GET_CLASS (widget)->mnemonic_activate != + GTK_WIDGET_CLASS (g_type_class_peek (GTK_TYPE_WIDGET))->mnemonic_activate) + { + return widget; + } + + if (GIMP_IS_SIZE_ENTRY (widget)) + { + GimpSizeEntry *entry = GIMP_SIZE_ENTRY (widget); + + return gimp_size_entry_get_help_widget (entry, + entry->number_of_fields - 1); + } + else if (GTK_IS_CONTAINER (widget)) + { + GtkWidget *mnemonic_widget = NULL; + GList *children; + GList *list; + + children = gtk_container_get_children (GTK_CONTAINER (widget)); + + for (list = children; list; list = g_list_next (list)) + { + mnemonic_widget = find_mnemonic_widget (list->data, level + 1); + + if (mnemonic_widget) + break; + } + + g_list_free (children); + + return mnemonic_widget; + } + + return NULL; +} + +/** + * gimp_table_attach_aligned: + * @table: The #GtkTable the widgets will be attached to. + * @column: The column to start with. + * @row: The row to attach the widgets. + * @label_text: The text for the #GtkLabel which will be attached left of + * the widget. + * @xalign: The horizontal alignment of the #GtkLabel. + * @yalign: The vertical alignment of the #GtkLabel. + * @widget: The #GtkWidget to attach right of the label. + * @colspan: The number of columns the widget will use. + * @left_align: %TRUE if the widget should be left-aligned. + * + * Note that the @label_text can be %NULL and that the widget will be + * attached starting at (@column + 1) in this case, too. + * + * Returns: The created #GtkLabel. + **/ +GtkWidget * +gimp_table_attach_aligned (GtkTable *table, + gint column, + gint row, + const gchar *label_text, + gfloat xalign, + gfloat yalign, + GtkWidget *widget, + gint colspan, + gboolean left_align) +{ + GtkWidget *label = NULL; + + if (label_text) + { + GtkWidget *mnemonic_widget; + + label = gtk_label_new_with_mnemonic (label_text); + gtk_label_set_xalign (GTK_LABEL (label), xalign); + gtk_label_set_yalign (GTK_LABEL (label), yalign); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + gtk_table_attach (table, label, + column, column + 1, + row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + + mnemonic_widget = find_mnemonic_widget (widget, 0); + + if (mnemonic_widget) + gtk_label_set_mnemonic_widget (GTK_LABEL (label), mnemonic_widget); + } + + if (left_align) + { + GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); + + widget = hbox; + } + + gtk_table_attach (table, widget, + column + 1, column + 1 + colspan, + row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + + gtk_widget_show (widget); + + return label; +} + +/** + * gimp_label_set_attributes: + * @label: a #GtkLabel + * @...: a list of PangoAttrType and value pairs terminated by -1. + * + * Sets Pango attributes on a #GtkLabel in a more convenient way than + * gtk_label_set_attributes(). + * + * This function is useful if you want to change the font attributes + * of a #GtkLabel. This is an alternative to using PangoMarkup which + * is slow to parse and awkward to handle in an i18n-friendly way. + * + * The attributes are set on the complete label, from start to end. If + * you need to set attributes on part of the label, you will have to + * use the PangoAttributes API directly. + * + * Since: 2.2 + **/ +void +gimp_label_set_attributes (GtkLabel *label, + ...) +{ + PangoAttribute *attr = NULL; + PangoAttrList *attrs; + va_list args; + + g_return_if_fail (GTK_IS_LABEL (label)); + + attrs = pango_attr_list_new (); + + va_start (args, label); + + do + { + PangoAttrType attr_type = va_arg (args, PangoAttrType); + + if (attr_type == -1) + attr_type = PANGO_ATTR_INVALID; + + switch (attr_type) + { + case PANGO_ATTR_LANGUAGE: + attr = pango_attr_language_new (va_arg (args, PangoLanguage *)); + break; + + case PANGO_ATTR_FAMILY: + attr = pango_attr_family_new (va_arg (args, const gchar *)); + break; + + case PANGO_ATTR_STYLE: + attr = pango_attr_style_new (va_arg (args, PangoStyle)); + break; + + case PANGO_ATTR_WEIGHT: + attr = pango_attr_weight_new (va_arg (args, PangoWeight)); + break; + + case PANGO_ATTR_VARIANT: + attr = pango_attr_variant_new (va_arg (args, PangoVariant)); + break; + + case PANGO_ATTR_STRETCH: + attr = pango_attr_stretch_new (va_arg (args, PangoStretch)); + break; + + case PANGO_ATTR_SIZE: + attr = pango_attr_size_new (va_arg (args, gint)); + break; + + case PANGO_ATTR_FONT_DESC: + attr = pango_attr_font_desc_new (va_arg (args, + const PangoFontDescription *)); + break; + + case PANGO_ATTR_FOREGROUND: + { + const PangoColor *color = va_arg (args, const PangoColor *); + + attr = pango_attr_foreground_new (color->red, + color->green, + color->blue); + } + break; + + case PANGO_ATTR_BACKGROUND: + { + const PangoColor *color = va_arg (args, const PangoColor *); + + attr = pango_attr_background_new (color->red, + color->green, + color->blue); + } + break; + + case PANGO_ATTR_UNDERLINE: + attr = pango_attr_underline_new (va_arg (args, PangoUnderline)); + break; + + case PANGO_ATTR_STRIKETHROUGH: + attr = pango_attr_strikethrough_new (va_arg (args, gboolean)); + break; + + case PANGO_ATTR_RISE: + attr = pango_attr_rise_new (va_arg (args, gint)); + break; + + case PANGO_ATTR_SCALE: + attr = pango_attr_scale_new (va_arg (args, gdouble)); + break; + + default: + g_warning ("%s: invalid PangoAttribute type %d", + G_STRFUNC, attr_type); + case PANGO_ATTR_INVALID: + attr = NULL; + break; + } + + if (attr) + { + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attrs, attr); + } + } + while (attr); + + va_end (args); + + gtk_label_set_attributes (label, attrs); + pango_attr_list_unref (attrs); +} + +gint +gimp_widget_get_monitor (GtkWidget *widget) +{ + GdkWindow *window; + GdkScreen *screen; + GtkAllocation allocation; + gint x, y; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + + window = gtk_widget_get_window (widget); + + if (! window) + return gimp_get_monitor_at_pointer (&screen); + + screen = gtk_widget_get_screen (widget); + + gdk_window_get_origin (window, &x, &y); + gtk_widget_get_allocation (widget, &allocation); + + if (! gtk_widget_get_has_window (widget)) + { + x += allocation.x; + y += allocation.y; + } + + x += allocation.width / 2; + y += allocation.height / 2; + + return gdk_screen_get_monitor_at_point (screen, x, y); +} + +gint +gimp_get_monitor_at_pointer (GdkScreen **screen) +{ + gint x, y; + + g_return_val_if_fail (screen != NULL, 0); + + gdk_display_get_pointer (gdk_display_get_default (), + screen, &x, &y, NULL); + + return gdk_screen_get_monitor_at_point (*screen, x, y); +} + +typedef void (* MonitorChangedCallback) (GtkWidget *, gpointer); + +typedef struct +{ + GtkWidget *widget; + gint monitor; + + MonitorChangedCallback callback; + gpointer user_data; +} TrackMonitorData; + +static gboolean +track_monitor_configure_event (GtkWidget *toplevel, + GdkEvent *event, + TrackMonitorData *track_data) +{ + gint monitor = gimp_widget_get_monitor (toplevel); + + if (monitor != track_data->monitor) + { + track_data->monitor = monitor; + + track_data->callback (track_data->widget, track_data->user_data); + } + + return FALSE; +} + +static void +track_monitor_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel, + TrackMonitorData *track_data) +{ + GtkWidget *toplevel; + + if (previous_toplevel) + { + g_signal_handlers_disconnect_by_func (previous_toplevel, + track_monitor_configure_event, + track_data); + } + + toplevel = gtk_widget_get_toplevel (widget); + + if (GTK_IS_WINDOW (toplevel)) + { + GClosure *closure; + gint monitor; + + closure = g_cclosure_new (G_CALLBACK (track_monitor_configure_event), + track_data, NULL); + g_object_watch_closure (G_OBJECT (widget), closure); + g_signal_connect_closure (toplevel, "configure-event", closure, FALSE); + + monitor = gimp_widget_get_monitor (toplevel); + + if (monitor != track_data->monitor) + { + track_data->monitor = monitor; + + track_data->callback (track_data->widget, track_data->user_data); + } + } +} + +/** + * gimp_widget_track_monitor: + * @widget: a #GtkWidget + * @monitor_changed_callback: the callback when @widget's monitor changes + * @user_data: data passed to @monitor_changed_callback + * + * This function behaves as if #GtkWidget had a signal + * + * GtkWidget::monitor_changed(GtkWidget *widget, gpointer user_data) + * + * That is emitted whenever @widget's toplevel window is moved from + * one monitor to another. This function automatically connects to + * the right toplevel #GtkWindow, even across moving @widget between + * toplevel windows. + * + * Note that this function tracks the toplevel, not @widget itself, so + * all a window's widgets are always considered to be on the same + * monitor. This is because this function is mainly used for fetching + * the new monitor's color profile, and it makes little sense to use + * different profiles for the widgets of one window. + * + * Since: 2.10 + **/ +void +gimp_widget_track_monitor (GtkWidget *widget, + GCallback monitor_changed_callback, + gpointer user_data) +{ + TrackMonitorData *track_data; + GtkWidget *toplevel; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (monitor_changed_callback != NULL); + + track_data = g_new0 (TrackMonitorData, 1); + + track_data->widget = widget; + track_data->callback = (MonitorChangedCallback) monitor_changed_callback; + track_data->user_data = user_data; + + g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) g_free, track_data); + + g_signal_connect (widget, "hierarchy-changed", + G_CALLBACK (track_monitor_hierarchy_changed), + track_data); + + toplevel = gtk_widget_get_toplevel (widget); + + if (GTK_IS_WINDOW (toplevel)) + track_monitor_hierarchy_changed (widget, NULL, track_data); +} + +/** + * gimp_screen_get_color_profile: + * @screen: a #GdkScreen + * @monitor: the monitor number + * + * This function returns the #GimpColorProfile of monitor number @monitor + * of @screen, or %NULL if there is no profile configured. + * + * Since: 2.10 + * + * Return value: the monitor's #GimpColorProfile, or %NULL. + **/ +GimpColorProfile * +gimp_screen_get_color_profile (GdkScreen *screen, + gint monitor) +{ + GimpColorProfile *profile = NULL; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (monitor >= 0, NULL); + g_return_val_if_fail (monitor < gdk_screen_get_n_monitors (screen), NULL); + +#if defined GDK_WINDOWING_X11 + { + GdkAtom type = GDK_NONE; + gint format = 0; + gint nitems = 0; + gchar *atom_name; + guchar *data = NULL; + + if (monitor > 0) + atom_name = g_strdup_printf ("_ICC_PROFILE_%d", monitor); + else + atom_name = g_strdup ("_ICC_PROFILE"); + + if (gdk_property_get (gdk_screen_get_root_window (screen), + gdk_atom_intern (atom_name, FALSE), + GDK_NONE, + 0, 64 * 1024 * 1024, FALSE, + &type, &format, &nitems, &data) && nitems > 0) + { + profile = gimp_color_profile_new_from_icc_profile (data, nitems, + NULL); + g_free (data); + } + + g_free (atom_name); + } +#elif defined GDK_WINDOWING_QUARTZ + { + CGColorSpaceRef space = NULL; + + space = CGDisplayCopyColorSpace (monitor); + + if (space) + { + CFDataRef data; + + data = CGColorSpaceCopyICCProfile (space); + + if (data) + { + UInt8 *buffer = g_malloc (CFDataGetLength (data)); + + /* We cannot use CFDataGetBytesPtr(), because that returns + * a const pointer where cmsOpenProfileFromMem wants a + * non-const pointer. + */ + CFDataGetBytes (data, CFRangeMake (0, CFDataGetLength (data)), + buffer); + + profile = gimp_color_profile_new_from_icc_profile (buffer, + CFDataGetLength (data), + NULL); + + g_free (buffer); + CFRelease (data); + } + + CFRelease (space); + } + } +#elif defined G_OS_WIN32 + { + GdkRectangle monitor_geometry; + POINT point; + gint offsetx = GetSystemMetrics (SM_XVIRTUALSCREEN); + gint offsety = GetSystemMetrics (SM_YVIRTUALSCREEN); + HMONITOR monitor_handle; + MONITORINFOEX info; + DISPLAY_DEVICE display_device; + + info.cbSize = sizeof (MONITORINFOEX); + display_device.cb = sizeof (DISPLAY_DEVICE); + + /* If the first monitor is not set as the main monitor, + * the monitor variable may not match the index used in + * EnumDisplayDevices(devicename, index, displaydevice, flags). + */ + gdk_screen_get_monitor_geometry (screen, monitor, &monitor_geometry); + point.x = monitor_geometry.x + offsetx; + point.y = monitor_geometry.y + offsety; + monitor_handle = MonitorFromPoint (point, MONITOR_DEFAULTTONEAREST); + + if (GetMonitorInfo (monitor_handle, (LPMONITORINFO)&info)) + { + if (EnumDisplayDevices (info.szDevice, 0, &display_device, 0)) + { + gchar *device_key = g_convert (display_device.DeviceKey, -1, "UTF-16LE", "WINDOWS-1252", NULL, NULL, NULL); + gchar *filename = NULL; + gchar *dir = NULL; + gchar *fullpath = NULL; + GFile *file; + DWORD len = 0; + gboolean per_user; + WCS_PROFILE_MANAGEMENT_SCOPE scope; + + WcsGetUsePerUserProfiles ((LPWSTR)device_key, CLASS_MONITOR, &per_user); + scope = per_user ? WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE; + + if (WcsGetDefaultColorProfileSize (scope, + (LPWSTR)device_key, + CPT_ICC, + CPST_NONE, + 0, + &len)) + { + gchar *filename_utf16 = g_new (gchar, len); + + WcsGetDefaultColorProfile (scope, + (LPWSTR)device_key, + CPT_ICC, + CPST_NONE, + 0, + len, + (LPWSTR)filename_utf16); + + /* filename_utf16 must be native endian */ + filename = g_utf16_to_utf8 ((gunichar2 *)filename_utf16, -1, NULL, NULL, NULL); + g_free (filename_utf16); + } + else + { + /* Due to a bug in Windows, the meanings of LCS_sRGB and + * LCS_WINDOWS_COLOR_SPACE are swapped. + */ + GetStandardColorSpaceProfile (NULL, LCS_sRGB, NULL, &len); + filename = g_new (gchar, len); + GetStandardColorSpaceProfile (NULL, LCS_sRGB, filename, &len); + } + + GetColorDirectory (NULL, NULL, &len); + dir = g_new (gchar, len); + GetColorDirectory (NULL, dir, &len); + + fullpath = g_build_filename (dir, filename, NULL); + file = g_file_new_for_path (fullpath); + + profile = gimp_color_profile_new_from_file (file, NULL); + g_object_unref (file); + + g_free (fullpath); + g_free (dir); + g_free (filename); + g_free (device_key); + } + } + } +#endif + + return profile; +} + +GimpColorProfile * +gimp_widget_get_color_profile (GtkWidget *widget) +{ + GdkScreen *screen; + gint monitor; + + g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL); + + if (widget) + { + screen = gtk_widget_get_screen (widget); + monitor = gimp_widget_get_monitor (widget); + } + else + { + screen = gdk_screen_get_default (); + monitor = 0; + } + + return gimp_screen_get_color_profile (screen, monitor); +} + +static GimpColorProfile * +get_display_profile (GtkWidget *widget, + GimpColorConfig *config) +{ + GimpColorProfile *profile = NULL; + + if (gimp_color_config_get_display_profile_from_gdk (config)) + /* get the toplevel's profile so all a window's colors look the same */ + profile = gimp_widget_get_color_profile (gtk_widget_get_toplevel (widget)); + + if (! profile) + profile = gimp_color_config_get_display_color_profile (config, NULL); + + if (! profile) + profile = gimp_color_profile_new_rgb_srgb (); + + return profile; +} + +typedef struct _TransformCache TransformCache; + +struct _TransformCache +{ + GimpColorTransform *transform; + + GimpColorConfig *config; + GimpColorProfile *src_profile; + const Babl *src_format; + GimpColorProfile *dest_profile; + const Babl *dest_format; + GimpColorProfile *proof_profile; + + gulong notify_id; +}; + +static GList *transform_caches = NULL; +static gboolean debug_cache = FALSE; + +static gboolean +profiles_equal (GimpColorProfile *profile1, + GimpColorProfile *profile2) +{ + return ((profile1 == NULL && profile2 == NULL) || + (profile1 != NULL && profile2 != NULL && + gimp_color_profile_is_equal (profile1, profile2))); +} + +static TransformCache * +transform_cache_get (GimpColorConfig *config, + GimpColorProfile *src_profile, + const Babl *src_format, + GimpColorProfile *dest_profile, + const Babl *dest_format, + GimpColorProfile *proof_profile) +{ + GList *list; + + for (list = transform_caches; list; list = g_list_next (list)) + { + TransformCache *cache = list->data; + + if (config == cache->config && + src_format == cache->src_format && + dest_format == cache->dest_format && + profiles_equal (src_profile, cache->src_profile) && + profiles_equal (dest_profile, cache->dest_profile) && + profiles_equal (proof_profile, cache->proof_profile)) + { + if (debug_cache) + g_printerr ("found cache %p\n", cache); + + return cache; + } + } + + return NULL; +} + +static void +transform_cache_config_notify (GObject *config, + const GParamSpec *pspec, + TransformCache *cache) +{ + transform_caches = g_list_remove (transform_caches, cache); + + g_signal_handler_disconnect (config, cache->notify_id); + + if (cache->transform) + g_object_unref (cache->transform); + + g_object_unref (cache->src_profile); + g_object_unref (cache->dest_profile); + + if (cache->proof_profile) + g_object_unref (cache->proof_profile); + + g_free (cache); + + if (debug_cache) + g_printerr ("deleted cache %p\n", cache); +} + +GimpColorTransform * +gimp_widget_get_color_transform (GtkWidget *widget, + GimpColorConfig *config, + GimpColorProfile *src_profile, + const Babl *src_format, + const Babl *dest_format) +{ + static gboolean initialized = FALSE; + GimpColorProfile *dest_profile = NULL; + GimpColorProfile *proof_profile = NULL; + TransformCache *cache; + + g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), NULL); + g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (src_profile), NULL); + g_return_val_if_fail (src_format != NULL, NULL); + g_return_val_if_fail (dest_format != NULL, NULL); + + if (G_UNLIKELY (! initialized)) + { + initialized = TRUE; + + debug_cache = g_getenv ("GIMP_DEBUG_TRANSFORM_CACHE") != NULL; + } + + switch (gimp_color_config_get_mode (config)) + { + case GIMP_COLOR_MANAGEMENT_OFF: + return NULL; + + case GIMP_COLOR_MANAGEMENT_SOFTPROOF: + proof_profile = gimp_color_config_get_simulation_color_profile (config, + NULL); + /* fallthru */ + + case GIMP_COLOR_MANAGEMENT_DISPLAY: + dest_profile = get_display_profile (widget, config); + break; + } + + cache = transform_cache_get (config, + src_profile, + src_format, + dest_profile, + dest_format, + proof_profile); + + if (cache) + { + g_object_unref (dest_profile); + + if (proof_profile) + g_object_unref (proof_profile); + + if (cache->transform) + return g_object_ref (cache->transform); + + return NULL; + } + + if (! proof_profile && + gimp_color_profile_is_equal (src_profile, dest_profile)) + { + g_object_unref (dest_profile); + + return NULL; + } + + cache = g_new0 (TransformCache, 1); + + if (debug_cache) + g_printerr ("creating cache %p\n", cache); + + cache->config = g_object_ref (config); + cache->src_profile = g_object_ref (src_profile); + cache->src_format = src_format; + cache->dest_profile = dest_profile; + cache->dest_format = dest_format; + cache->proof_profile = proof_profile; + + cache->notify_id = + g_signal_connect (cache->config, "notify", + G_CALLBACK (transform_cache_config_notify), + cache); + + transform_caches = g_list_prepend (transform_caches, cache); + + if (cache->proof_profile) + { + GimpColorTransformFlags flags = 0; + + if (gimp_color_config_get_simulation_bpc (config)) + flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION; + + if (! gimp_color_config_get_simulation_optimize (config)) + flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE; + + if (gimp_color_config_get_simulation_gamut_check (config)) + { + cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = { 0, }; + guchar r, g, b; + + flags |= GIMP_COLOR_TRANSFORM_FLAGS_GAMUT_CHECK; + + gimp_rgb_get_uchar (&config->out_of_gamut_color, &r, &g, &b); + + alarmCodes[0] = (cmsUInt16Number) r * 256; + alarmCodes[1] = (cmsUInt16Number) g * 256; + alarmCodes[2] = (cmsUInt16Number) b * 256; + + cmsSetAlarmCodes (alarmCodes); + } + + cache->transform = + gimp_color_transform_new_proofing (cache->src_profile, + cache->src_format, + cache->dest_profile, + cache->dest_format, + cache->proof_profile, + gimp_color_config_get_simulation_intent (config), + gimp_color_config_get_display_intent (config), + flags); + } + else + { + GimpColorTransformFlags flags = 0; + + if (gimp_color_config_get_display_bpc (config)) + flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION; + + if (! gimp_color_config_get_display_optimize (config)) + flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE; + + cache->transform = + gimp_color_transform_new (cache->src_profile, + cache->src_format, + cache->dest_profile, + cache->dest_format, + gimp_color_config_get_display_intent (config), + flags); + } + + if (cache->transform) + return g_object_ref (cache->transform); + + return NULL; +} diff --git a/libgimpwidgets/gimpwidgetsutils.h b/libgimpwidgets/gimpwidgetsutils.h new file mode 100644 index 0000000..54925b4 --- /dev/null +++ b/libgimpwidgets/gimpwidgetsutils.h @@ -0,0 +1,66 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpwidgetsutils.h + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_WIDGETS_UTILS_H__ +#define __GIMP_WIDGETS_UTILS_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +GtkWidget * gimp_table_attach_aligned (GtkTable *table, + gint column, + gint row, + const gchar *label_text, + gfloat xalign, + gfloat yalign, + GtkWidget *widget, + gint colspan, + gboolean left_align); + +void gimp_label_set_attributes (GtkLabel *label, + ...); + +gint gimp_widget_get_monitor (GtkWidget *widget); +gint gimp_get_monitor_at_pointer (GdkScreen **screen); + +void gimp_widget_track_monitor (GtkWidget *widget, + GCallback monitor_changed_callback, + gpointer user_data); + +GimpColorProfile * gimp_screen_get_color_profile (GdkScreen *screen, + gint monitor); +GimpColorProfile * gimp_widget_get_color_profile (GtkWidget *widget); + +GimpColorTransform * gimp_widget_get_color_transform (GtkWidget *widget, + GimpColorConfig *config, + GimpColorProfile *src_profile, + const Babl *src_format, + const Babl *dest_format); + + +G_END_DECLS + +#endif /* __GIMP_WIDGETS_UTILS_H__ */ diff --git a/libgimpwidgets/gimpzoommodel.c b/libgimpwidgets/gimpzoommodel.c new file mode 100644 index 0000000..2860595 --- /dev/null +++ b/libgimpwidgets/gimpzoommodel.c @@ -0,0 +1,698 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpzoommodel.c + * Copyright (C) 2005 David Odin <dindinx@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "gimphelpui.h" +#include "gimpwidgetsmarshal.h" +#include "gimpzoommodel.h" + + +/** + * SECTION: gimpzoommodel + * @title: GimpZoomModel + * @short_description: A model for zoom values. + * + * A model for zoom values. + **/ + + +#define ZOOM_MIN (1.0 / 256.0) +#define ZOOM_MAX (256.0) + +enum +{ + ZOOMED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_VALUE, + PROP_MINIMUM, + PROP_MAXIMUM, + PROP_FRACTION, + PROP_PERCENTAGE +}; + + +typedef struct +{ + gdouble value; + gdouble minimum; + gdouble maximum; +} GimpZoomModelPrivate; + +#define GIMP_ZOOM_MODEL_GET_PRIVATE(obj) \ + ((GimpZoomModelPrivate *) ((GimpZoomModel *) (obj))->priv) + + +static void gimp_zoom_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_zoom_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +static guint zoom_model_signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE_WITH_PRIVATE (GimpZoomModel, gimp_zoom_model, G_TYPE_OBJECT) + +#define parent_class gimp_zoom_model_parent_class + + +static void +gimp_zoom_model_class_init (GimpZoomModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + /** + * GimpZoomModel::zoomed: + * @model: the object that received the signal + * @old_factor: the zoom factor before it changes + * @new_factor: the zoom factor after it has changed. + * + * Emitted when the zoom factor of the zoom model changes. + */ + zoom_model_signals[ZOOMED] = + g_signal_new ("zoomed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpZoomModelClass, + zoomed), + NULL, NULL, + _gimp_widgets_marshal_VOID__DOUBLE_DOUBLE, + G_TYPE_NONE, 2, + G_TYPE_DOUBLE, G_TYPE_DOUBLE); + + object_class->set_property = gimp_zoom_model_set_property; + object_class->get_property = gimp_zoom_model_get_property; + + /** + * GimpZoomModel:value: + * + * The zoom factor. + */ + g_object_class_install_property (object_class, PROP_VALUE, + g_param_spec_double ("value", + "Value", + "Zoom factor", + ZOOM_MIN, ZOOM_MAX, + 1.0, + GIMP_PARAM_READWRITE)); + /** + * GimpZoomModel:minimum: + * + * The minimum zoom factor. + */ + g_object_class_install_property (object_class, PROP_MINIMUM, + g_param_spec_double ("minimum", + "Minimum", + "Lower limit for the zoom factor", + ZOOM_MIN, ZOOM_MAX, + ZOOM_MIN, + GIMP_PARAM_READWRITE)); + /** + * GimpZoomModel:maximum: + * + * The maximum zoom factor. + */ + g_object_class_install_property (object_class, PROP_MAXIMUM, + g_param_spec_double ("maximum", + "Maximum", + "Upper limit for the zoom factor", + ZOOM_MIN, ZOOM_MAX, + ZOOM_MAX, + GIMP_PARAM_READWRITE)); + + /** + * GimpZoomModel:fraction: + * + * The zoom factor expressed as a fraction. + */ + g_object_class_install_property (object_class, PROP_FRACTION, + g_param_spec_string ("fraction", + "Fraction", + "The zoom factor expressed as a fraction", + "1:1", + GIMP_PARAM_READABLE)); + /** + * GimpZoomModel:percentage: + * + * The zoom factor expressed as percentage. + */ + g_object_class_install_property (object_class, PROP_PERCENTAGE, + g_param_spec_string ("percentage", + "Percentage", + "The zoom factor expressed as a percentage", + "100%", + GIMP_PARAM_READABLE)); +} + +static void +gimp_zoom_model_init (GimpZoomModel *model) +{ + GimpZoomModelPrivate *priv; + + model->priv = gimp_zoom_model_get_instance_private (model); + + priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + priv->value = 1.0; + priv->minimum = ZOOM_MIN; + priv->maximum = ZOOM_MAX; +} + +static void +gimp_zoom_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (object); + gdouble previous_value; + + previous_value = priv->value; + g_object_freeze_notify (object); + + switch (property_id) + { + case PROP_VALUE: + priv->value = g_value_get_double (value); + + g_object_notify (object, "value"); + g_object_notify (object, "fraction"); + g_object_notify (object, "percentage"); + break; + + case PROP_MINIMUM: + priv->minimum = MIN (g_value_get_double (value), priv->maximum); + break; + + case PROP_MAXIMUM: + priv->maximum = MAX (g_value_get_double (value), priv->minimum); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + if (priv->value > priv->maximum || priv->value < priv->minimum) + { + priv->value = CLAMP (priv->value, priv->minimum, priv->maximum); + + g_object_notify (object, "value"); + g_object_notify (object, "fraction"); + g_object_notify (object, "percentage"); + } + + g_object_thaw_notify (object); + + if (priv->value != previous_value) + { + g_signal_emit (object, zoom_model_signals[ZOOMED], + 0, previous_value, priv->value); + } +} + +static void +gimp_zoom_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (object); + gchar *tmp; + + switch (property_id) + { + case PROP_VALUE: + g_value_set_double (value, priv->value); + break; + + case PROP_MINIMUM: + g_value_set_double (value, priv->minimum); + break; + + case PROP_MAXIMUM: + g_value_set_double (value, priv->maximum); + break; + + case PROP_FRACTION: + { + gint numerator; + gint denominator; + + gimp_zoom_model_get_fraction (GIMP_ZOOM_MODEL (object), + &numerator, &denominator); + + tmp = g_strdup_printf ("%d:%d", numerator, denominator); + g_value_set_string (value, tmp); + g_free (tmp); + } + break; + + case PROP_PERCENTAGE: + tmp = g_strdup_printf (priv->value >= 0.15 ? "%.0f%%" : "%.2f%%", + priv->value * 100.0); + g_value_set_string (value, tmp); + g_free (tmp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_zoom_model_zoom_in (GimpZoomModel *model) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + if (priv->value < priv->maximum) + gimp_zoom_model_zoom (model, GIMP_ZOOM_IN, 0.0); +} + +static void +gimp_zoom_model_zoom_out (GimpZoomModel *model) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + if (priv->value > priv->minimum) + gimp_zoom_model_zoom (model, GIMP_ZOOM_OUT, 0.0); +} + +/** + * gimp_zoom_model_new: + * + * Creates a new #GimpZoomModel. + * + * Return value: a new #GimpZoomModel. + * + * Since GIMP 2.4 + **/ +GimpZoomModel * +gimp_zoom_model_new (void) +{ + return g_object_new (GIMP_TYPE_ZOOM_MODEL, NULL); +} + + +/** + * gimp_zoom_model_set_range: + * @model: a #GimpZoomModel + * @min: new lower limit for zoom factor + * @max: new upper limit for zoom factor + * + * Sets the allowed range of the @model. + * + * Since GIMP 2.4 + **/ +void +gimp_zoom_model_set_range (GimpZoomModel *model, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_ZOOM_MODEL (model)); + g_return_if_fail (min < max); + g_return_if_fail (min >= ZOOM_MIN); + g_return_if_fail (max <= ZOOM_MAX); + + g_object_set (model, + "minimum", min, + "maximum", max, + NULL); +} + +/** + * gimp_zoom_model_zoom: + * @model: a #GimpZoomModel + * @zoom_type: the #GimpZoomType + * @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO + * + * Since GIMP 2.4 + **/ +void +gimp_zoom_model_zoom (GimpZoomModel *model, + GimpZoomType zoom_type, + gdouble scale) +{ + g_return_if_fail (GIMP_IS_ZOOM_MODEL (model)); + + if (zoom_type != GIMP_ZOOM_TO) + scale = gimp_zoom_model_get_factor (model); + + g_object_set (model, + "value", gimp_zoom_model_zoom_step (zoom_type, scale), + NULL); +} + +/** + * gimp_zoom_model_get_factor: + * @model: a #GimpZoomModel + * + * Retrieves the current zoom factor of @model. + * + * Return value: the current scale factor + * + * Since GIMP 2.4 + **/ +gdouble +gimp_zoom_model_get_factor (GimpZoomModel *model) +{ + g_return_val_if_fail (GIMP_IS_ZOOM_MODEL (model), 1.0); + + return GIMP_ZOOM_MODEL_GET_PRIVATE (model)->value; +} + + +/** + * gimp_zoom_model_get_fraction + * @model: a #GimpZoomModel + * @numerator: return location for numerator + * @denominator: return location for denominator + * + * Retrieves the current zoom factor of @model as a fraction. + * + * Since GIMP 2.4 + **/ +void +gimp_zoom_model_get_fraction (GimpZoomModel *model, + gint *numerator, + gint *denominator) +{ + gint p0, p1, p2; + gint q0, q1, q2; + gdouble zoom_factor; + gdouble remainder, next_cf; + gboolean swapped = FALSE; + + g_return_if_fail (GIMP_IS_ZOOM_MODEL (model)); + g_return_if_fail (numerator != NULL && denominator != NULL); + + zoom_factor = gimp_zoom_model_get_factor (model); + + /* make sure that zooming behaves symmetrically */ + if (zoom_factor < 1.0) + { + zoom_factor = 1.0 / zoom_factor; + swapped = TRUE; + } + + /* calculate the continued fraction for the desired zoom factor */ + + p0 = 1; + q0 = 0; + p1 = floor (zoom_factor); + q1 = 1; + + remainder = zoom_factor - p1; + + while (fabs (remainder) >= 0.0001 && + fabs (((gdouble) p1 / q1) - zoom_factor) > 0.0001) + { + remainder = 1.0 / remainder; + + next_cf = floor (remainder); + + p2 = next_cf * p1 + p0; + q2 = next_cf * q1 + q0; + + /* Numerator and Denominator are limited by 256 */ + /* also absurd ratios like 170:171 are excluded */ + if (p2 > 256 || q2 > 256 || (p2 > 1 && q2 > 1 && p2 * q2 > 200)) + break; + + /* remember the last two fractions */ + p0 = p1; + p1 = p2; + q0 = q1; + q1 = q2; + + remainder = remainder - next_cf; + } + + zoom_factor = (gdouble) p1 / q1; + + /* hard upper and lower bounds for zoom ratio */ + + if (zoom_factor > 256.0) + { + p1 = 256; + q1 = 1; + } + else if (zoom_factor < 1.0 / 256.0) + { + p1 = 1; + q1 = 256; + } + + if (swapped) + { + *numerator = q1; + *denominator = p1; + } + else + { + *numerator = p1; + *denominator = q1; + } +} + +static GtkWidget * +zoom_button_new (const gchar *icon_name, + GtkIconSize icon_size) +{ + GtkWidget *button; + GtkWidget *image; + + image = gtk_image_new_from_icon_name (icon_name, + icon_size > 0 ? + icon_size : GTK_ICON_SIZE_BUTTON); + + button = gtk_button_new (); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + return button; +} + +static void +zoom_in_button_callback (GimpZoomModel *model, + gdouble old, + gdouble new, + GtkWidget *button) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + gtk_widget_set_sensitive (button, priv->value != priv->maximum); +} + +static void +zoom_out_button_callback (GimpZoomModel *model, + gdouble old, + gdouble new, + GtkWidget *button) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + gtk_widget_set_sensitive (button, priv->value != priv->minimum); +} + +/** + * gimp_zoom_button_new: + * @model: a #GimpZoomModel + * @zoom_type: + * @icon_size: use 0 for a button with text labels + * + * Return value: a newly created GtkButton + * + * Since GIMP 2.4 + **/ +GtkWidget * +gimp_zoom_button_new (GimpZoomModel *model, + GimpZoomType zoom_type, + GtkIconSize icon_size) +{ + GtkWidget *button = NULL; + + g_return_val_if_fail (GIMP_IS_ZOOM_MODEL (model), NULL); + + switch (zoom_type) + { + case GIMP_ZOOM_IN: + button = zoom_button_new ("zoom-in", icon_size); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_zoom_model_zoom_in), + model); + g_signal_connect_object (model, "zoomed", + G_CALLBACK (zoom_in_button_callback), + button, 0); + break; + + case GIMP_ZOOM_OUT: + button = zoom_button_new ("zoom-out", icon_size); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_zoom_model_zoom_out), + model); + g_signal_connect_object (model, "zoomed", + G_CALLBACK (zoom_out_button_callback), + button, 0); + break; + + default: + g_warning ("sorry, no button for this zoom type (%d)", zoom_type); + break; + } + + if (button) + { + gdouble zoom = gimp_zoom_model_get_factor (model); + + /* set initial button sensitivity */ + g_signal_emit (model, zoom_model_signals[ZOOMED], 0, zoom, zoom); + + if (icon_size > 0) + { + const gchar *desc; + + if (gimp_enum_get_value (GIMP_TYPE_ZOOM_TYPE, zoom_type, + NULL, NULL, &desc, NULL)) + { + gimp_help_set_help_data (button, desc, NULL); + } + } + } + + return button; +} + +/** + * gimp_zoom_model_zoom_step: + * @zoom_type: the zoom type + * @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO + * + * Utility function to calculate a new scale factor. + * + * Return value: the new scale factor + * + * Since GIMP 2.4 + **/ +gdouble +gimp_zoom_model_zoom_step (GimpZoomType zoom_type, + gdouble scale) +{ + gint i, n_presets; + gdouble new_scale = 1.0; + + /* This table is constructed to have fractions, that approximate + * sqrt(2)^k. This gives a smooth feeling regardless of the starting + * zoom level. + * + * Zooming in/out always jumps to a zoom step from the list below. + * However, we try to guarantee a certain size of the step, to + * avoid silly jumps from 101% to 100%. + * + * The factor 1.1 is chosen a bit arbitrary, but feels better + * than the geometric median of the zoom steps (2^(1/4)). + */ + +#define ZOOM_MIN_STEP 1.1 + + const gdouble presets[] = { + 1.0 / 256, 1.0 / 180, 1.0 / 128, 1.0 / 90, + 1.0 / 64, 1.0 / 45, 1.0 / 32, 1.0 / 23, + 1.0 / 16, 1.0 / 11, 1.0 / 8, 2.0 / 11, + 1.0 / 4, 1.0 / 3, 1.0 / 2, 2.0 / 3, + 1.0, + 3.0 / 2, 2.0, 3.0, + 4.0, 11.0 / 2, 8.0, 11.0, + 16.0, 23.0, 32.0, 45.0, + 64.0, 90.0, 128.0, 180.0, + 256.0, + }; + + n_presets = G_N_ELEMENTS (presets); + + switch (zoom_type) + { + case GIMP_ZOOM_IN: + scale *= ZOOM_MIN_STEP; + + new_scale = presets[n_presets - 1]; + for (i = n_presets - 1; i >= 0 && presets[i] > scale; i--) + new_scale = presets[i]; + + break; + + case GIMP_ZOOM_OUT: + scale /= ZOOM_MIN_STEP; + + new_scale = presets[0]; + for (i = 0; i < n_presets && presets[i] < scale; i++) + new_scale = presets[i]; + + break; + + case GIMP_ZOOM_IN_MORE: + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, scale); + new_scale = scale; + break; + + case GIMP_ZOOM_OUT_MORE: + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, scale); + new_scale = scale; + break; + + case GIMP_ZOOM_IN_MAX: + new_scale = ZOOM_MAX; + break; + + case GIMP_ZOOM_OUT_MAX: + new_scale = ZOOM_MIN; + break; + + case GIMP_ZOOM_TO: + new_scale = scale; + break; + } + + return CLAMP (new_scale, ZOOM_MIN, ZOOM_MAX); + +#undef ZOOM_MIN_STEP +} diff --git a/libgimpwidgets/gimpzoommodel.h b/libgimpwidgets/gimpzoommodel.h new file mode 100644 index 0000000..6e64035 --- /dev/null +++ b/libgimpwidgets/gimpzoommodel.h @@ -0,0 +1,89 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpzoommodel.h + * Copyright (C) 2005 David Odin <dindinx@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only <libgimpwidgets/gimpwidgets.h> can be included directly." +#endif + +#ifndef __GIMP_ZOOM_MODEL_H__ +#define __GIMP_ZOOM_MODEL_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_ZOOM_MODEL (gimp_zoom_model_get_type ()) +#define GIMP_ZOOM_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ZOOM_MODEL, GimpZoomModel)) +#define GIMP_ZOOM_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ZOOM_MODEL, GimpZoomModelClass)) +#define GIMP_IS_ZOOM_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ZOOM_MODEL)) +#define GIMP_IS_ZOOM_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ZOOM_MODEL)) +#define GIMP_ZOOM_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ZOOM_MODEL, GimpZoomModel)) + + +typedef struct _GimpZoomModelClass GimpZoomModelClass; + +struct _GimpZoomModel +{ + GObject parent_instance; + + /*< private >*/ + gpointer priv; +}; + +struct _GimpZoomModelClass +{ + GObjectClass parent_class; + + void (* zoomed) (GimpZoomModel *model, + gdouble old_factor, + gdouble new_factor); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_zoom_model_get_type (void) G_GNUC_CONST; + +GimpZoomModel * gimp_zoom_model_new (void); +void gimp_zoom_model_set_range (GimpZoomModel *model, + gdouble min, + gdouble max); +void gimp_zoom_model_zoom (GimpZoomModel *model, + GimpZoomType zoom_type, + gdouble scale); +gdouble gimp_zoom_model_get_factor (GimpZoomModel *model); +void gimp_zoom_model_get_fraction (GimpZoomModel *model, + gint *numerator, + gint *denominator); + +GtkWidget * gimp_zoom_button_new (GimpZoomModel *model, + GimpZoomType zoom_type, + GtkIconSize icon_size); + +gdouble gimp_zoom_model_zoom_step (GimpZoomType zoom_type, + gdouble scale); + +G_END_DECLS + +#endif /* __GIMP_ZOOM_MODEL_H__ */ diff --git a/libgimpwidgets/test-eevl.c b/libgimpwidgets/test-eevl.c new file mode 100644 index 0000000..7c5b9c0 --- /dev/null +++ b/libgimpwidgets/test-eevl.c @@ -0,0 +1,196 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * test-eevl.c + * Copyright (C) 2008 Fredrik Alstromer <roe@excu.se> + * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* A small regression test case for the evaluator */ + +#include "config.h" + +#include <string.h> + +#include <glib-object.h> + +#include "libgimpmath/gimpmath.h" + +#include "gimpeevl.h" + + +typedef struct +{ + const gchar *string; + GimpEevlQuantity result; + gboolean should_succeed; +} TestCase; + +static gboolean +test_units (const gchar *ident, + GimpEevlQuantity *factor, + gdouble *offset, + gpointer data) +{ + gboolean resolved = FALSE; + gboolean default_unit = (ident == NULL); + + *offset = 0.0; + + if (default_unit || + (ident && strcmp ("in", ident) == 0)) + { + factor->dimension = 1; + factor->value = 1.; + + resolved = TRUE; + } + else if (ident && strcmp ("mm", ident) == 0) + { + factor->dimension = 1; + factor->value = 25.4; + + resolved = TRUE; + } + + return resolved; +} + + +int +main(void) +{ + gint i; + gint failed = 0; + gint succeeded = 0; + + TestCase cases[] = + { + /* "Default" test case */ + { "2in + 3in", { 2 + 3, 1}, TRUE }, + + /* Whitespace variations */ + { "2in+3in", { 2 + 3, 1}, TRUE }, + { " 2in + 3in", { 2 + 3, 1}, TRUE }, + { "2in + 3in ", { 2 + 3, 1}, TRUE }, + { "2 in + 3 in", { 2 + 3, 1}, TRUE }, + { " 2 in + 3 in ", { 2 + 3, 1}, TRUE }, + + /* Make sure the default unit is applied as it should */ + { "2 + 3in", { 2 + 3, 1 }, TRUE }, + { "3", { 3, 1 }, TRUE }, + + /* Somewhat complicated input */ + { "(2 + 3)in", { 2 + 3, 1}, TRUE }, + // { "2 / 3 in", { 2 / 3., 1}, TRUE }, + { "(2 + 2/3)in", { 2 + 2 / 3., 1}, TRUE }, + { "1/2 + 1/2", { 1, 1}, TRUE }, + { "2 ^ 3 ^ 4", { pow (2, pow (3, 4)), 1}, TRUE }, + + /* Mixing of units */ + { "2mm + 3in", { 2 / 25.4 + 3, 1}, TRUE }, + + /* 'odd' behavior */ + { "2 ++ 1", { 3, 1}, TRUE }, + { "2 +- 1", { 1, 1}, TRUE }, + { "2 -- 1", { 3, 1}, TRUE }, + + /* End of test cases */ + { NULL, { 0, 0 }, TRUE } + }; + + g_print ("Testing Eevl Eva, the Evaluator\n\n"); + + for (i = 0; cases[i].string; i++) + { + const gchar *test = cases[i].string; + GimpEevlOptions options = GIMP_EEVL_OPTIONS_INIT; + GimpEevlQuantity should = cases[i].result; + GimpEevlQuantity result = { 0, -1 }; + gboolean should_succeed = cases[i].should_succeed; + GError *error = NULL; + const gchar *error_pos = 0; + + options.unit_resolver_proc = test_units; + + gimp_eevl_evaluate (test, + &options, + &result, + &error_pos, + &error); + + g_print ("%s = %lg (%d): ", test, result.value, result.dimension); + if (error || error_pos) + { + if (should_succeed) + { + failed++; + g_print ("evaluation failed "); + if (error) + { + g_print ("with: %s, ", error->message); + } + else + { + g_print ("without reason, "); + } + if (error_pos) + { + if (*error_pos) g_print ("'%s'.", error_pos); + else g_print ("at end of input."); + } + else + { + g_print ("but didn't say where."); + } + g_print ("\n"); + } + else + { + g_print ("OK (failure test case)\n"); + succeeded++; + } + } + else if (!should_succeed) + { + g_print ("evaluation should've failed, but didn't.\n"); + failed++; + } + else if (should.value != result.value || should.dimension != result.dimension) + { + g_print ("results don't match, should be: %lg (%d)\n", + should.value, should.dimension); + failed++; + } + else + { + g_print ("OK\n"); + succeeded++; + } + } + + g_print ("\n"); + if (!failed) + g_print ("All OK. "); + else + g_print ("Test failed! "); + + g_print ("(%d/%d) %lg%%\n\n", succeeded, succeeded+failed, + 100*succeeded/(gdouble)(succeeded+failed)); + + return failed; +} diff --git a/libgimpwidgets/test-preview-area.c b/libgimpwidgets/test-preview-area.c new file mode 100644 index 0000000..fc37966 --- /dev/null +++ b/libgimpwidgets/test-preview-area.c @@ -0,0 +1,240 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * Copyright (C) 2004 Sven Neumann <sven@gimp.org> + * test-preview-area.c + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +/* This code is based on testrgb.c from GTK+ - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + */ + +#include <config.h> + +#include <stdlib.h> + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "gimppreviewarea.h" + + +#define WIDTH 256 +#define HEIGHT 256 +#define NUM_ITERS 100 + + +static void +test_run (GtkWidget *area, + gboolean visible) +{ + guchar buf[WIDTH * HEIGHT * 8]; + gint i, j; + gint num_iters = NUM_ITERS; + guchar val; + gdouble start_time, total_time; + GTimer *timer; + GEnumClass *enum_class; + GEnumValue *enum_value; + + if (! visible) + num_iters *= 4; + + gtk_widget_realize (area); + + g_print ("\nPerformance tests for GimpPreviewArea " + "(%d x %d, %s, %d iterations):\n\n", + WIDTH, HEIGHT, visible ? "visible" : "hidden", num_iters); + + val = 0; + for (j = 0; j < WIDTH * HEIGHT * 8; j++) + { + val = (val + ((val + (rand () & 0xff)) >> 1)) >> 1; + buf[j] = val; + } + + gimp_preview_area_set_colormap (GIMP_PREVIEW_AREA (area), buf, 256); + + /* Let's warm up the cache, and also wait for the window manager + to settle. */ + for (i = 0; i < NUM_ITERS; i++) + { + gint offset = (rand () % (WIDTH * HEIGHT * 4)) & -4; + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (area), + 0, 0, WIDTH, HEIGHT, + GIMP_RGB_IMAGE, + buf + offset, + WIDTH * 4); + } + + gdk_window_process_all_updates (); + gdk_flush (); + + timer = g_timer_new (); + + enum_class = g_type_class_ref (GIMP_TYPE_IMAGE_TYPE); + + for (enum_value = enum_class->values; enum_value->value_name; enum_value++) + { + /* gimp_preview_area_draw */ + start_time = g_timer_elapsed (timer, NULL); + + for (i = 0; i < num_iters; i++) + { + gint offset = (rand () % (WIDTH * HEIGHT * 4)) & -4; + + gimp_preview_area_draw (GIMP_PREVIEW_AREA (area), + 0, 0, WIDTH, HEIGHT, + enum_value->value, + buf + offset, + WIDTH * 4); + + gdk_window_process_updates (area->window, FALSE); + } + + gdk_flush (); + total_time = g_timer_elapsed (timer, NULL) - start_time; + + g_print ("%-20s " + "draw : %5.2fs, %8.1f fps, %8.2f megapixels/s\n", + enum_value->value_name, + total_time, + num_iters / total_time, + num_iters * (WIDTH * HEIGHT * 1e-6) / total_time); + + /* gimp_preview_area_blend */ + start_time = g_timer_elapsed (timer, NULL); + + for (i = 0; i < num_iters; i++) + { + gint offset = (rand () % (WIDTH * HEIGHT * 4)) & -4; + gint offset2 = (rand () % (WIDTH * HEIGHT * 4)) & -4; + + gimp_preview_area_blend (GIMP_PREVIEW_AREA (area), + 0, 0, WIDTH, HEIGHT, + enum_value->value, + buf + offset, + WIDTH * 4, + buf + offset2, + WIDTH * 4, + rand () & 0xFF); + + gdk_window_process_updates (area->window, FALSE); + } + + gdk_flush (); + total_time = g_timer_elapsed (timer, NULL) - start_time; + + g_print ("%-20s " + "blend : %5.2fs, %8.1f fps, %8.2f megapixels/s\n", + enum_value->value_name, + total_time, + num_iters / total_time, + num_iters * (WIDTH * HEIGHT * 1e-6) / total_time); + + /* gimp_preview_area_mask */ + start_time = g_timer_elapsed (timer, NULL); + + for (i = 0; i < num_iters; i++) + { + gint offset = (rand () % (WIDTH * HEIGHT * 4)) & -4; + gint offset2 = (rand () % (WIDTH * HEIGHT * 4)) & -4; + gint offset3 = (rand () % (WIDTH * HEIGHT * 4)) & -4; + + gimp_preview_area_mask (GIMP_PREVIEW_AREA (area), + 0, 0, WIDTH, HEIGHT, + enum_value->value, + buf + offset, + WIDTH * 4, + buf + offset2, + WIDTH * 4, + buf + offset3, + WIDTH); + + gdk_window_process_updates (area->window, FALSE); + } + + gdk_flush (); + total_time = g_timer_elapsed (timer, NULL) - start_time; + + g_print ("%-20s " + "mask : %5.2fs, %8.1f fps, %8.2f megapixels/s\n", + enum_value->value_name, + total_time, + num_iters / total_time, + num_iters * (WIDTH * HEIGHT * 1e-6) / total_time); + g_print ("\n"); + } + + start_time = g_timer_elapsed (timer, NULL); + + for (i = 0; i < num_iters; i++) + { + guchar r = rand () % 0xFF; + guchar g = rand () % 0xFF; + guchar b = rand () % 0xFF; + + gimp_preview_area_fill (GIMP_PREVIEW_AREA (area), + 0, 0, WIDTH, HEIGHT, + r, g, b); + + gdk_window_process_updates (area->window, FALSE); + } + + gdk_flush (); + total_time = g_timer_elapsed (timer, NULL) - start_time; + g_print ("%-20s " + "fill : %5.2fs, %8.1f fps, %8.2f megapixels/s\n", + "Color fill", + total_time, + num_iters / total_time, + num_iters * (WIDTH * HEIGHT * 1e-6) / total_time); + g_print ("\n"); +} + +static void +test_preview_area (void) +{ + GtkWidget *window; + GtkWidget *area; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_accept_focus (GTK_WINDOW (window), FALSE); + + area = gimp_preview_area_new (); + gtk_container_add (GTK_CONTAINER (window), area); + gtk_widget_show (area); + + test_run (area, FALSE); + + gtk_widget_show (window); + + test_run (area, TRUE); +} + +int +main (int argc, char **argv) +{ + gtk_init (&argc, &argv); + + test_preview_area (); + + return EXIT_SUCCESS; +} |