summaryrefslogtreecommitdiffstats
path: root/libgimpwidgets
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
commit5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch)
treecbffb45144febf451e54061db2b21395faf94bfe /libgimpwidgets
parentInitial commit. (diff)
downloadgimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz
gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libgimpwidgets')
-rw-r--r--libgimpwidgets/Makefile.am418
-rw-r--r--libgimpwidgets/Makefile.in1988
-rw-r--r--libgimpwidgets/gimp3migration.c262
-rw-r--r--libgimpwidgets/gimp3migration.h77
-rw-r--r--libgimpwidgets/gimpbrowser.c396
-rw-r--r--libgimpwidgets/gimpbrowser.h95
-rw-r--r--libgimpwidgets/gimpbusybox.c236
-rw-r--r--libgimpwidgets/gimpbusybox.h76
-rw-r--r--libgimpwidgets/gimpbutton.c165
-rw-r--r--libgimpwidgets/gimpbutton.h77
-rw-r--r--libgimpwidgets/gimpcairo-utils.c202
-rw-r--r--libgimpwidgets/gimpcairo-utils.h36
-rw-r--r--libgimpwidgets/gimpcellrenderercolor.c332
-rw-r--r--libgimpwidgets/gimpcellrenderercolor.h71
-rw-r--r--libgimpwidgets/gimpcellrenderertoggle.c576
-rw-r--r--libgimpwidgets/gimpcellrenderertoggle.h78
-rw-r--r--libgimpwidgets/gimpchainbutton.c554
-rw-r--r--libgimpwidgets/gimpchainbutton.h92
-rw-r--r--libgimpwidgets/gimpcolorarea.c956
-rw-r--r--libgimpwidgets/gimpcolorarea.h100
-rw-r--r--libgimpwidgets/gimpcolorbutton.c1014
-rw-r--r--libgimpwidgets/gimpcolorbutton.h114
-rw-r--r--libgimpwidgets/gimpcolordisplay.c563
-rw-r--r--libgimpwidgets/gimpcolordisplay.h142
-rw-r--r--libgimpwidgets/gimpcolordisplaystack.c412
-rw-r--r--libgimpwidgets/gimpcolordisplaystack.h104
-rw-r--r--libgimpwidgets/gimpcolorhexentry.c324
-rw-r--r--libgimpwidgets/gimpcolorhexentry.h75
-rw-r--r--libgimpwidgets/gimpcolornotebook.c547
-rw-r--r--libgimpwidgets/gimpcolornotebook.h78
-rw-r--r--libgimpwidgets/gimpcolorprofilechooserdialog.c355
-rw-r--r--libgimpwidgets/gimpcolorprofilechooserdialog.h67
-rw-r--r--libgimpwidgets/gimpcolorprofilecombobox.c628
-rw-r--r--libgimpwidgets/gimpcolorprofilecombobox.h90
-rw-r--r--libgimpwidgets/gimpcolorprofilestore-private.h52
-rw-r--r--libgimpwidgets/gimpcolorprofilestore.c794
-rw-r--r--libgimpwidgets/gimpcolorprofilestore.h76
-rw-r--r--libgimpwidgets/gimpcolorprofileview.c209
-rw-r--r--libgimpwidgets/gimpcolorprofileview.h68
-rw-r--r--libgimpwidgets/gimpcolorscale.c1133
-rw-r--r--libgimpwidgets/gimpcolorscale.h88
-rw-r--r--libgimpwidgets/gimpcolorscales.c925
-rw-r--r--libgimpwidgets/gimpcolorscales.h49
-rw-r--r--libgimpwidgets/gimpcolorselect.c2006
-rw-r--r--libgimpwidgets/gimpcolorselect.h41
-rw-r--r--libgimpwidgets/gimpcolorselection.c697
-rw-r--r--libgimpwidgets/gimpcolorselection.h106
-rw-r--r--libgimpwidgets/gimpcolorselector.c638
-rw-r--r--libgimpwidgets/gimpcolorselector.h177
-rw-r--r--libgimpwidgets/gimpcontroller.c263
-rw-r--r--libgimpwidgets/gimpcontroller.h183
-rw-r--r--libgimpwidgets/gimpdialog.c689
-rw-r--r--libgimpwidgets/gimpdialog.h95
-rw-r--r--libgimpwidgets/gimpeevl.c666
-rw-r--r--libgimpwidgets/gimpeevl.h99
-rw-r--r--libgimpwidgets/gimpenumcombobox.c229
-rw-r--r--libgimpwidgets/gimpenumcombobox.h74
-rw-r--r--libgimpwidgets/gimpenumlabel.c218
-rw-r--r--libgimpwidgets/gimpenumlabel.h66
-rw-r--r--libgimpwidgets/gimpenumstore.c397
-rw-r--r--libgimpwidgets/gimpenumstore.h85
-rw-r--r--libgimpwidgets/gimpenumwidgets.c536
-rw-r--r--libgimpwidgets/gimpenumwidgets.h97
-rw-r--r--libgimpwidgets/gimpfileentry.c499
-rw-r--r--libgimpwidgets/gimpfileentry.h91
-rw-r--r--libgimpwidgets/gimpframe.c372
-rw-r--r--libgimpwidgets/gimpframe.h67
-rw-r--r--libgimpwidgets/gimphelpui.c563
-rw-r--r--libgimpwidgets/gimphelpui.h76
-rw-r--r--libgimpwidgets/gimphintbox.c246
-rw-r--r--libgimpwidgets/gimphintbox.h75
-rw-r--r--libgimpwidgets/gimpicons.c651
-rw-r--r--libgimpwidgets/gimpicons.h680
-rw-r--r--libgimpwidgets/gimpintcombobox.c978
-rw-r--r--libgimpwidgets/gimpintcombobox.h117
-rw-r--r--libgimpwidgets/gimpintstore.c351
-rw-r--r--libgimpwidgets/gimpintstore.h104
-rw-r--r--libgimpwidgets/gimpmemsizeentry.c320
-rw-r--r--libgimpwidgets/gimpmemsizeentry.h84
-rw-r--r--libgimpwidgets/gimpnumberpairentry.c1301
-rw-r--r--libgimpwidgets/gimpnumberpairentry.h104
-rw-r--r--libgimpwidgets/gimpoffsetarea.c506
-rw-r--r--libgimpwidgets/gimpoffsetarea.h91
-rw-r--r--libgimpwidgets/gimpoldwidgets.c650
-rw-r--r--libgimpwidgets/gimpoldwidgets.h122
-rw-r--r--libgimpwidgets/gimppageselector.c1327
-rw-r--r--libgimpwidgets/gimppageselector.h105
-rw-r--r--libgimpwidgets/gimppatheditor.c876
-rw-r--r--libgimpwidgets/gimppatheditor.h105
-rw-r--r--libgimpwidgets/gimppickbutton-default.c368
-rw-r--r--libgimpwidgets/gimppickbutton-default.h25
-rw-r--r--libgimpwidgets/gimppickbutton-kwin.c112
-rw-r--r--libgimpwidgets/gimppickbutton-kwin.h25
-rw-r--r--libgimpwidgets/gimppickbutton-quartz.c485
-rw-r--r--libgimpwidgets/gimppickbutton-quartz.h25
-rw-r--r--libgimpwidgets/gimppickbutton-win32.c1135
-rw-r--r--libgimpwidgets/gimppickbutton-win32.h23
-rw-r--r--libgimpwidgets/gimppickbutton-xdg.c152
-rw-r--r--libgimpwidgets/gimppickbutton-xdg.h25
-rw-r--r--libgimpwidgets/gimppickbutton.c188
-rw-r--r--libgimpwidgets/gimppickbutton.h69
-rw-r--r--libgimpwidgets/gimppixmap.c179
-rw-r--r--libgimpwidgets/gimppixmap.h78
-rw-r--r--libgimpwidgets/gimppreview.c851
-rw-r--r--libgimpwidgets/gimppreview.h149
-rw-r--r--libgimpwidgets/gimppreviewarea.c1956
-rw-r--r--libgimpwidgets/gimppreviewarea.h133
-rw-r--r--libgimpwidgets/gimppropwidgets.c4352
-rw-r--r--libgimpwidgets/gimppropwidgets.h243
-rw-r--r--libgimpwidgets/gimpquerybox.c699
-rw-r--r--libgimpwidgets/gimpquerybox.h180
-rw-r--r--libgimpwidgets/gimpruler.c1465
-rw-r--r--libgimpwidgets/gimpruler.h81
-rw-r--r--libgimpwidgets/gimpscaleentry.c479
-rw-r--r--libgimpwidgets/gimpscaleentry.h125
-rw-r--r--libgimpwidgets/gimpscrolledpreview.c917
-rw-r--r--libgimpwidgets/gimpscrolledpreview.h90
-rw-r--r--libgimpwidgets/gimpsizeentry.c1594
-rw-r--r--libgimpwidgets/gimpsizeentry.h155
-rw-r--r--libgimpwidgets/gimpspinbutton.c385
-rw-r--r--libgimpwidgets/gimpspinbutton.h90
-rw-r--r--libgimpwidgets/gimpstringcombobox.c363
-rw-r--r--libgimpwidgets/gimpstringcombobox.h74
-rw-r--r--libgimpwidgets/gimpunitcombobox.c218
-rw-r--r--libgimpwidgets/gimpunitcombobox.h71
-rw-r--r--libgimpwidgets/gimpunitmenu.c633
-rw-r--r--libgimpwidgets/gimpunitmenu.h105
-rw-r--r--libgimpwidgets/gimpunitstore.c937
-rw-r--r--libgimpwidgets/gimpunitstore.h113
-rw-r--r--libgimpwidgets/gimpwidgets-error.c40
-rw-r--r--libgimpwidgets/gimpwidgets-error.h58
-rw-r--r--libgimpwidgets/gimpwidgets-private.c106
-rw-r--r--libgimpwidgets/gimpwidgets-private.h47
-rw-r--r--libgimpwidgets/gimpwidgets.c997
-rw-r--r--libgimpwidgets/gimpwidgets.def470
-rw-r--r--libgimpwidgets/gimpwidgets.h256
-rw-r--r--libgimpwidgets/gimpwidgetsenums.c313
-rw-r--r--libgimpwidgets/gimpwidgetsenums.h239
-rw-r--r--libgimpwidgets/gimpwidgetsmarshal.c433
-rw-r--r--libgimpwidgets/gimpwidgetsmarshal.h90
-rw-r--r--libgimpwidgets/gimpwidgetsmarshal.list35
-rw-r--r--libgimpwidgets/gimpwidgetstypes.h110
-rw-r--r--libgimpwidgets/gimpwidgetsutils.c921
-rw-r--r--libgimpwidgets/gimpwidgetsutils.h66
-rw-r--r--libgimpwidgets/gimpzoommodel.c698
-rw-r--r--libgimpwidgets/gimpzoommodel.h89
-rw-r--r--libgimpwidgets/test-eevl.c196
-rw-r--r--libgimpwidgets/test-preview-area.c240
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=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ 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;
+}