summaryrefslogtreecommitdiffstats
path: root/tools
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 /tools
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 'tools')
-rw-r--r--tools/Makefile.am119
-rw-r--r--tools/Makefile.in1081
-rwxr-xr-xtools/defcheck.py126
-rw-r--r--tools/gimp-debug-resume.c113
-rwxr-xr-xtools/gimp-mkenums577
-rw-r--r--tools/gimp-test-clipboard.c443
-rwxr-xr-xtools/gimppath2svg.py117
-rw-r--r--tools/gimptool.c1142
-rw-r--r--tools/kernelgen.c128
-rwxr-xr-xtools/mnemonic-clashes96
-rwxr-xr-xtools/performance-log-close-tags.py49
-rwxr-xr-xtools/performance-log-coalesce.py62
-rwxr-xr-xtools/performance-log-deduce.py91
-rwxr-xr-xtools/performance-log-expand.py107
-rwxr-xr-xtools/performance-log-progressive-coalesce.py54
-rwxr-xr-xtools/performance-log-resolve.py55
-rwxr-xr-xtools/performance-log-viewer39
-rwxr-xr-xtools/performance-log-viewer.py3662
-rw-r--r--tools/svg-contrast.c117
19 files changed, 8178 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..420b9b1
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,119 @@
+## Process this file with automake to produce Makefile.in
+
+AUTOMAKE_OPTIONS = subdir-objects
+
+libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
+
+if PLATFORM_OSX
+xobjective_c = "-xobjective-c"
+xobjective_cxx = "-xobjective-c++"
+xnone = "-xnone"
+endif
+
+bin_PROGRAMS = \
+ gimptool-@GIMP_TOOL_VERSION@ \
+ gimp-test-clipboard-@GIMP_TOOL_VERSION@
+
+if OS_WIN32
+
+bin_PROGRAMS += gimp-debug-resume
+
+gimp_debug_resume_SOURCES = gimp-debug-resume.c
+
+else
+
+libm = -lm
+
+endif
+
+EXTRA_PROGRAMS = \
+ kernelgen
+
+
+gimptool_@GIMP_TOOL_VERSION@_SOURCES = gimptool.c
+
+gimptool_@GIMP_TOOL_VERSION@_LDADD = \
+ $(libgimpbase) \
+ $(GTK_LIBS)
+
+
+gimp_test_clipboard_@GIMP_TOOL_VERSION@_SOURCES = gimp-test-clipboard.c
+
+gimp_test_clipboard_@GIMP_TOOL_VERSION@_LDADD = \
+ $(libgimpbase) \
+ $(GTK_LIBS)
+
+
+kernelgen_SOURCES = kernelgen.c
+
+
+
+if ENABLE_VECTOR_ICONS
+svg-contrast$(BUILD_EXEEXT): svg-contrast.c
+ $(CC_FOR_BUILD) -fPIC -o $@ $< $(NATIVE_GLIB_LIBS) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) $(NATIVE_GLIB_CFLAGS) $(libm)
+
+
+# compute_svg_viewbox is not built or used because librsvg is just too buggy
+# right now. But we keep the code around. The goal of this build tool will be
+# to be able to extract SVG icons from a single SVG file at build time, rather
+# than having to export and commit them manually.
+#compute_svg_viewbox_SOURCES = compute-svg-viewbox.c
+
+#compute_svg_viewbox_CFLAGS = $(SVG_CFLAGS)
+
+#compute_svg_viewbox_LDADD = $(SVG_LIBS)
+
+# Build tools which must be built for the host platform.
+all-local: svg-contrast$(BUILD_EXEEXT)
+
+DISTCLEANFILES = svg-contrast$(BUILD_EXEEXT)
+endif
+
+AM_CPPFLAGS = \
+ -DGIMP_APP_VERSION=\"@GIMP_APP_VERSION@\" \
+ -DLOCALEDIR=\""$(gimplocaledir)"\" \
+ -DPREFIX=\""$(prefix)"\" \
+ -DEXEC_PREFIX=\""$(exec_prefix)"\" \
+ -DBINDIR=\""$(bindir)"\" \
+ -DSBINDIR=\""$(sbindir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ -DDATAROOTDIR=\""$(datarootdir)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DSHAREDSTATEDIR=\""$(sharedstatedir)"\" \
+ -DLOCALSTATEDIR=\""$(localstatedir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DINFODIR=\""$(infodir)"\" \
+ -DMANDIR=\""$(mandir)"\" \
+ -DGIMPPLUGINDIR=\""$(gimpplugindir)"\" \
+ -DGIMPDATADIR=\""$(gimpdatadir)"\" \
+ -DCC=\""$(CC)"\" \
+ -DGIMPDIR=\""$(gimpdir)"\" \
+ -DGIMP_PLUGIN_VERSION=\""$(GIMP_PLUGIN_VERSION)"\" \
+ -I$(top_srcdir) \
+ $(GTK_CFLAGS) \
+ -I$(includedir)
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(xnone)
+
+EXTRA_DIST = \
+ defcheck.py \
+ gimp-mkenums \
+ gimppath2svg.py \
+ svg-contrast.c \
+ mnemonic-clashes \
+ performance-log-close-tags.py \
+ performance-log-coalesce.py \
+ performance-log-deduce.py \
+ performance-log-expand.py \
+ performance-log-progressive-coalesce.py \
+ performance-log-resolve.py \
+ performance-log-viewer \
+ performance-log-viewer.py
diff --git a/tools/Makefile.in b/tools/Makefile.in
new file mode 100644
index 0000000..a2a4890
--- /dev/null
+++ b/tools/Makefile.in
@@ -0,0 +1,1081 @@
+# 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@
+bin_PROGRAMS = gimptool-@GIMP_TOOL_VERSION@$(EXEEXT) \
+ gimp-test-clipboard-@GIMP_TOOL_VERSION@$(EXEEXT) \
+ $(am__EXEEXT_1)
+@OS_WIN32_TRUE@am__append_1 = gimp-debug-resume
+EXTRA_PROGRAMS = kernelgen$(EXEEXT)
+subdir = tools
+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 $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@OS_WIN32_TRUE@am__EXEEXT_1 = gimp-debug-resume$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__gimp_debug_resume_SOURCES_DIST = gimp-debug-resume.c
+@OS_WIN32_TRUE@am_gimp_debug_resume_OBJECTS = \
+@OS_WIN32_TRUE@ gimp-debug-resume.$(OBJEXT)
+gimp_debug_resume_OBJECTS = $(am_gimp_debug_resume_OBJECTS)
+gimp_debug_resume_LDADD = $(LDADD)
+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 =
+am_gimp_test_clipboard_@GIMP_TOOL_VERSION@_OBJECTS = \
+ gimp-test-clipboard.$(OBJEXT)
+gimp_test_clipboard_@GIMP_TOOL_VERSION@_OBJECTS = \
+ $(am_gimp_test_clipboard_@GIMP_TOOL_VERSION@_OBJECTS)
+am__DEPENDENCIES_1 =
+gimp_test_clipboard_@GIMP_TOOL_VERSION@_DEPENDENCIES = $(libgimpbase) \
+ $(am__DEPENDENCIES_1)
+am_gimptool_@GIMP_TOOL_VERSION@_OBJECTS = gimptool.$(OBJEXT)
+gimptool_@GIMP_TOOL_VERSION@_OBJECTS = \
+ $(am_gimptool_@GIMP_TOOL_VERSION@_OBJECTS)
+gimptool_@GIMP_TOOL_VERSION@_DEPENDENCIES = $(libgimpbase) \
+ $(am__DEPENDENCIES_1)
+am_kernelgen_OBJECTS = kernelgen.$(OBJEXT)
+kernelgen_OBJECTS = $(am_kernelgen_OBJECTS)
+kernelgen_LDADD = $(LDADD)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/gimp-debug-resume.Po \
+ ./$(DEPDIR)/gimp-test-clipboard.Po ./$(DEPDIR)/gimptool.Po \
+ ./$(DEPDIR)/kernelgen.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 = $(gimp_debug_resume_SOURCES) \
+ $(gimp_test_clipboard_@GIMP_TOOL_VERSION@_SOURCES) \
+ $(gimptool_@GIMP_TOOL_VERSION@_SOURCES) $(kernelgen_SOURCES)
+DIST_SOURCES = $(am__gimp_debug_resume_SOURCES_DIST) \
+ $(gimp_test_clipboard_@GIMP_TOOL_VERSION@_SOURCES) \
+ $(gimptool_@GIMP_TOOL_VERSION@_SOURCES) $(kernelgen_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+AA_LIBS = @AA_LIBS@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALL_LINGUAS = @ALL_LINGUAS@
+ALSA_CFLAGS = @ALSA_CFLAGS@
+ALSA_LIBS = @ALSA_LIBS@
+ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPSTREAM_UTIL = @APPSTREAM_UTIL@
+AR = @AR@
+AS = @AS@
+ATK_CFLAGS = @ATK_CFLAGS@
+ATK_LIBS = @ATK_LIBS@
+ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BABL_CFLAGS = @BABL_CFLAGS@
+BABL_LIBS = @BABL_LIBS@
+BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@
+BUG_REPORT_URL = @BUG_REPORT_URL@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+BZIP2_LIBS = @BZIP2_LIBS@
+CAIRO_CFLAGS = @CAIRO_CFLAGS@
+CAIRO_LIBS = @CAIRO_LIBS@
+CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@
+CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@
+CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@
+CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCAS = @CCAS@
+CCASDEPMODE = @CCASDEPMODE@
+CCASFLAGS = @CCASFLAGS@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CC_VERSION = @CC_VERSION@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DESKTOP_DATADIR = @DESKTOP_DATADIR@
+DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@
+DLLTOOL = @DLLTOOL@
+DOC_SHOOTER = @DOC_SHOOTER@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILE_AA = @FILE_AA@
+FILE_EXR = @FILE_EXR@
+FILE_HEIF = @FILE_HEIF@
+FILE_JP2_LOAD = @FILE_JP2_LOAD@
+FILE_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@
+AUTOMAKE_OPTIONS = subdir-objects
+libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
+@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c"
+@PLATFORM_OSX_TRUE@xobjective_cxx = "-xobjective-c++"
+@PLATFORM_OSX_TRUE@xnone = "-xnone"
+@OS_WIN32_TRUE@gimp_debug_resume_SOURCES = gimp-debug-resume.c
+@OS_WIN32_FALSE@libm = -lm
+gimptool_@GIMP_TOOL_VERSION@_SOURCES = gimptool.c
+gimptool_@GIMP_TOOL_VERSION@_LDADD = \
+ $(libgimpbase) \
+ $(GTK_LIBS)
+
+gimp_test_clipboard_@GIMP_TOOL_VERSION@_SOURCES = gimp-test-clipboard.c
+gimp_test_clipboard_@GIMP_TOOL_VERSION@_LDADD = \
+ $(libgimpbase) \
+ $(GTK_LIBS)
+
+kernelgen_SOURCES = kernelgen.c
+@ENABLE_VECTOR_ICONS_TRUE@DISTCLEANFILES = svg-contrast$(BUILD_EXEEXT)
+AM_CPPFLAGS = \
+ -DGIMP_APP_VERSION=\"@GIMP_APP_VERSION@\" \
+ -DLOCALEDIR=\""$(gimplocaledir)"\" \
+ -DPREFIX=\""$(prefix)"\" \
+ -DEXEC_PREFIX=\""$(exec_prefix)"\" \
+ -DBINDIR=\""$(bindir)"\" \
+ -DSBINDIR=\""$(sbindir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ -DDATAROOTDIR=\""$(datarootdir)"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DSHAREDSTATEDIR=\""$(sharedstatedir)"\" \
+ -DLOCALSTATEDIR=\""$(localstatedir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DINFODIR=\""$(infodir)"\" \
+ -DMANDIR=\""$(mandir)"\" \
+ -DGIMPPLUGINDIR=\""$(gimpplugindir)"\" \
+ -DGIMPDATADIR=\""$(gimpdatadir)"\" \
+ -DCC=\""$(CC)"\" \
+ -DGIMPDIR=\""$(gimpdir)"\" \
+ -DGIMP_PLUGIN_VERSION=\""$(GIMP_PLUGIN_VERSION)"\" \
+ -I$(top_srcdir) \
+ $(GTK_CFLAGS) \
+ -I$(includedir)
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(xnone)
+
+EXTRA_DIST = \
+ defcheck.py \
+ gimp-mkenums \
+ gimppath2svg.py \
+ svg-contrast.c \
+ mnemonic-clashes \
+ performance-log-close-tags.py \
+ performance-log-coalesce.py \
+ performance-log-deduce.py \
+ performance-log-expand.py \
+ performance-log-progressive-coalesce.py \
+ performance-log-resolve.py \
+ performance-log-viewer \
+ performance-log-viewer.py
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tools/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu tools/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+gimp-debug-resume$(EXEEXT): $(gimp_debug_resume_OBJECTS) $(gimp_debug_resume_DEPENDENCIES) $(EXTRA_gimp_debug_resume_DEPENDENCIES)
+ @rm -f gimp-debug-resume$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(gimp_debug_resume_OBJECTS) $(gimp_debug_resume_LDADD) $(LIBS)
+
+gimp-test-clipboard-@GIMP_TOOL_VERSION@$(EXEEXT): $(gimp_test_clipboard_@GIMP_TOOL_VERSION@_OBJECTS) $(gimp_test_clipboard_@GIMP_TOOL_VERSION@_DEPENDENCIES) $(EXTRA_gimp_test_clipboard_@GIMP_TOOL_VERSION@_DEPENDENCIES)
+ @rm -f gimp-test-clipboard-@GIMP_TOOL_VERSION@$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(gimp_test_clipboard_@GIMP_TOOL_VERSION@_OBJECTS) $(gimp_test_clipboard_@GIMP_TOOL_VERSION@_LDADD) $(LIBS)
+
+gimptool-@GIMP_TOOL_VERSION@$(EXEEXT): $(gimptool_@GIMP_TOOL_VERSION@_OBJECTS) $(gimptool_@GIMP_TOOL_VERSION@_DEPENDENCIES) $(EXTRA_gimptool_@GIMP_TOOL_VERSION@_DEPENDENCIES)
+ @rm -f gimptool-@GIMP_TOOL_VERSION@$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(gimptool_@GIMP_TOOL_VERSION@_OBJECTS) $(gimptool_@GIMP_TOOL_VERSION@_LDADD) $(LIBS)
+
+kernelgen$(EXEEXT): $(kernelgen_OBJECTS) $(kernelgen_DEPENDENCIES) $(EXTRA_kernelgen_DEPENDENCIES)
+ @rm -f kernelgen$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(kernelgen_OBJECTS) $(kernelgen_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-debug-resume.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-test-clipboard.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernelgen.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+@ENABLE_VECTOR_ICONS_FALSE@all-local:
+all-am: Makefile $(PROGRAMS) all-local
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/gimp-debug-resume.Po
+ -rm -f ./$(DEPDIR)/gimp-test-clipboard.Po
+ -rm -f ./$(DEPDIR)/gimptool.Po
+ -rm -f ./$(DEPDIR)/kernelgen.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/gimp-debug-resume.Po
+ -rm -f ./$(DEPDIR)/gimp-test-clipboard.Po
+ -rm -f ./$(DEPDIR)/gimptool.Po
+ -rm -f ./$(DEPDIR)/kernelgen.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-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am all-local am--depfiles check \
+ check-am clean clean-binPROGRAMS clean-generic 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-binPROGRAMS install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+@ENABLE_VECTOR_ICONS_TRUE@svg-contrast$(BUILD_EXEEXT): svg-contrast.c
+@ENABLE_VECTOR_ICONS_TRUE@ $(CC_FOR_BUILD) -fPIC -o $@ $< $(NATIVE_GLIB_LIBS) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) $(NATIVE_GLIB_CFLAGS) $(libm)
+
+# compute_svg_viewbox is not built or used because librsvg is just too buggy
+# right now. But we keep the code around. The goal of this build tool will be
+# to be able to extract SVG icons from a single SVG file at build time, rather
+# than having to export and commit them manually.
+#compute_svg_viewbox_SOURCES = compute-svg-viewbox.c
+
+#compute_svg_viewbox_CFLAGS = $(SVG_CFLAGS)
+
+#compute_svg_viewbox_LDADD = $(SVG_LIBS)
+
+# Build tools which must be built for the host platform.
+@ENABLE_VECTOR_ICONS_TRUE@all-local: svg-contrast$(BUILD_EXEEXT)
+
+# 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/tools/defcheck.py b/tools/defcheck.py
new file mode 100755
index 0000000..36a0179
--- /dev/null
+++ b/tools/defcheck.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python2
+
+"""
+defcheck.py -- Consistency check for the .def files.
+Copyright (C) 2006 Simon Budig <simon@gimp.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+This is a hack to check the consistency of the .def files compared to
+the respective libraries.
+
+Invoke in the top level of the gimp source tree after compiling GIMP.
+If srcdir != builddir, run it in the build directory and pass the name
+of the source directory on the command-line.
+
+Needs the tool "nm" to work
+
+"""
+
+import sys, commands
+
+from os import path
+
+def_files = (
+ "libgimpbase/gimpbase.def",
+ "libgimpcolor/gimpcolor.def",
+ "libgimpconfig/gimpconfig.def",
+ "libgimp/gimp.def",
+ "libgimp/gimpui.def",
+ "libgimpmath/gimpmath.def",
+ "libgimpmodule/gimpmodule.def",
+ "libgimpthumb/gimpthumb.def",
+ "libgimpwidgets/gimpwidgets.def"
+)
+
+have_errors = 0
+
+srcdir = None
+if len(sys.argv) > 1:
+ srcdir = sys.argv[1]
+ if not path.exists(srcdir):
+ print "Directory '%s' does not exist" % srcdir
+ sys.exit (-1)
+
+for df in def_files:
+ directory, name = path.split (df)
+ basename, extension = name.split (".")
+ libname = path.join(directory, ".libs", "lib" + basename + "-*.so")
+
+ filename = df
+ if srcdir:
+ filename = path.join(srcdir, df)
+ try:
+ defsymbols = file (filename).read ().split ()[1:]
+ except IOError, message:
+ print message
+ if not srcdir:
+ print "You should run this script from the toplevel source directory."
+ sys.exit (-1)
+
+ doublesymbols = []
+ for i in range (len (defsymbols)-1, 0, -1):
+ if defsymbols[i] in defsymbols[:i]:
+ doublesymbols.append ((defsymbols[i], i+2))
+
+ unsortindex = -1
+ for i in range (len (defsymbols)-1):
+ if defsymbols[i] > defsymbols[i+1]:
+ unsortindex = i+1
+ break;
+
+ status, nm = commands.getstatusoutput ("nm --defined-only --extern-only " +
+ libname)
+ if status != 0:
+ print "trouble reading %s - has it been compiled?" % libname
+ continue
+
+ nmsymbols = nm.split()[2::3]
+ nmsymbols = [s for s in nmsymbols if s[0] != '_']
+
+ missing_defs = [s for s in nmsymbols if s not in defsymbols]
+ missing_nms = [s for s in defsymbols if s not in nmsymbols]
+
+ if unsortindex >= 0 or missing_defs or missing_nms or doublesymbols:
+ print
+ print "Problem found in", filename
+
+ if missing_defs:
+ print " the following symbols are in the library,"
+ print " but are not listed in the .def-file:"
+ for s in missing_defs:
+ print " +", s
+ print
+
+ if missing_nms:
+ print " the following symbols are listed in the .def-file,"
+ print " but are not exported by the library."
+ for s in missing_nms:
+ print " -", s
+ print
+
+ if doublesymbols:
+ print " the following symbols are listed multiple times in the .def-file,"
+ for s in doublesymbols:
+ print " : %s (line %d)" % s
+ print
+
+ if unsortindex >= 0:
+ print " the .def-file is not properly sorted (line %d)" % (unsortindex + 2)
+ print
+
+ have_errors = -1
+
+sys.exit (have_errors)
diff --git a/tools/gimp-debug-resume.c b/tools/gimp-debug-resume.c
new file mode 100644
index 0000000..2aac3b3
--- /dev/null
+++ b/tools/gimp-debug-resume.c
@@ -0,0 +1,113 @@
+/* based on pausep by Daniel Turini
+ */
+
+#define WIN32_LEAN_AND_MEAN
+#define _WIN32_WINNT 0x0502
+#include <windows.h>
+#include <tchar.h>
+#include <tlhelp32.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static BOOL
+resume_process (DWORD dwOwnerPID)
+{
+ HANDLE hThreadSnap = NULL;
+ BOOL bRet = FALSE;
+ THREADENTRY32 te32 = { 0 };
+
+ hThreadSnap = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ te32.dwSize = sizeof (THREADENTRY32);
+
+ if (Thread32First (hThreadSnap, &te32))
+ {
+ do
+ {
+ if (te32.th32OwnerProcessID == dwOwnerPID)
+ {
+ HANDLE hThread = OpenThread (THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
+ printf ("Resuming Thread: %u\n", (unsigned int) te32.th32ThreadID);
+ ResumeThread (hThread);
+ CloseHandle (hThread);
+ }
+ }
+ while (Thread32Next (hThreadSnap, &te32));
+ bRet = TRUE;
+ }
+ else
+ bRet = FALSE;
+
+ CloseHandle (hThreadSnap);
+
+ return bRet;
+}
+
+static BOOL
+process_list (void)
+{
+ HANDLE hProcessSnap = NULL;
+ BOOL bRet = FALSE;
+ PROCESSENTRY32 pe32 = {0};
+
+ hProcessSnap = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
+
+ if (hProcessSnap == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ pe32.dwSize = sizeof (PROCESSENTRY32);
+
+ if (Process32First (hProcessSnap, &pe32))
+ {
+ do
+ {
+ printf ("PID:\t%u\t%s\n",
+ (unsigned int) pe32.th32ProcessID,
+ pe32.szExeFile);
+ }
+ while (Process32Next (hProcessSnap, &pe32));
+ bRet = TRUE;
+ }
+ else
+ bRet = FALSE;
+
+ CloseHandle (hProcessSnap);
+
+ return bRet;
+}
+
+int
+main (int argc,
+ char* argv[])
+{
+ DWORD pid;
+
+ if (argc <= 1)
+ {
+ process_list ();
+ }
+ else if (argc == 2)
+ {
+ pid = atoi (argv[1]);
+ if (pid == 0)
+ {
+ printf ("invalid: %lu\n", pid);
+ return 1;
+ }
+ else
+ {
+ printf ("process: %lu\n", pid);
+ resume_process (pid);
+ }
+ }
+ else
+ {
+ printf ("Usage:\n"
+ "resume : show processlist\n"
+ "resume PID: resuming thread\n");
+ }
+
+ return 0;
+}
diff --git a/tools/gimp-mkenums b/tools/gimp-mkenums
new file mode 100755
index 0000000..f39103f
--- /dev/null
+++ b/tools/gimp-mkenums
@@ -0,0 +1,577 @@
+#!/usr/bin/perl -w
+
+# This is gimp-mkenums, a perl script based on glib-mkenums.
+# It can be used just like glib-mkenums but offers one extra
+# feature: The keyword "desc" is recognized in trigraphs and
+# allows to specify a literal description of the enum value.
+# This description is used to generate user interface elements
+# from the enumeration. To allow i18n of the description, the
+# value is by default put into the N_() macro.
+
+use Text::ParseWords;
+use File::Basename;
+
+# gimp-mkenums
+# Information about the current enumeration
+my $flags; # Is enumeration a bitmask?
+my $option_lowercase_name; # A lower case name to use as part of the *_get_type() function, instead of the one that we guess.
+ # For instance, when an enum uses abnormal capitalization and we can not guess where to put the underscores.
+my $seenbitshift; # Have we seen bitshift operators?
+my $enum_prefix; # Prefix for this enumeration
+my $enumname; # Name for this enumeration
+my $enumshort; # $enumname without prefix
+my $enumnick; # lower case version of $enumshort
+my $enumindex = 0; # Global enum counter
+my $firstenum = 1; # Is this the first enumeration per file?
+my @entries; # [ $name, $val ] for each entry
+
+sub parse_trigraph {
+ my $opts = shift;
+ my @opts;
+
+ for $opt (quotewords(",", "true", $opts)) {
+ $opt =~ s/^\s*//;
+ $opt =~ s/\s*$//;
+ my ($key,$val) = $opt =~ /(\w+)(?:=(.+))?/;
+ defined $val or $val = 1;
+ push @opts, $key, $val;
+ }
+ @opts;
+}
+
+
+sub parse_entries {
+ my $file = shift;
+ my $file_name = shift;
+ my $looking_for_name = 0;
+
+ while (<$file>) {
+ # read lines until we have no open comments
+ while (m@/\*([^*]|\*(?!/))*$@) {
+ my $new;
+ defined ($new = <$file>) || die "Unmatched comment in $ARGV";
+ $_ .= $new;
+ }
+ # strip comments w/o options
+ s@/\*(?!<)
+ ([^*]+|\*(?!/))*
+ \*/@@gx;
+
+ # strip newlines
+ s@\n@ @;
+
+ # skip empty lines
+ next if m@^\s*$@;
+
+ if ($looking_for_name) {
+ if (/^\s*(\w+)/) {
+ $enumname = $1;
+ return 1;
+ }
+ }
+
+ # Handle include files
+ if (/^\#include\s*<([^>]*)>/ ) {
+ my $file= "../$1";
+ open NEWFILE, $file or die "Cannot open include file $file: $!\n";
+
+ if (parse_entries (\*NEWFILE, $NEWFILE)) {
+ return 1;
+ } else {
+ next;
+ }
+ }
+
+ if (/^\s*\}\s*(\w+)/) {
+ $enumname = $1;
+ $enumindex++;
+ return 1;
+ }
+
+ if (/^\s*\}/) {
+ $enumindex++;
+ $looking_for_name = 1;
+ next;
+ }
+
+ if (m@^\s*
+ (\w+)\s* # name
+ (?:=( # value
+ \s*\w+\s*\(.*\)\s* # macro with multiple args
+ | # OR
+ (?:[^,/]|/(?!\*))* # anything but a comma or comment
+ ))?,?\s*
+ (?:/\*< # options
+ (([^*]|\*(?!/))*)
+ >\s*\*/)?,?
+ \s*$
+ @x) {
+ my ($name, $value, $options) = ($1,$2,$3);
+
+ if (!defined $flags && defined $value && $value =~ /<</) {
+ $seenbitshift = 1;
+ }
+
+ if (defined $options) {
+ my %options = parse_trigraph($options);
+ if (!defined $options{"skip"}) {
+ push @entries, [ $name, $options{nick}, $options{desc}, $options{help}, $options{abbrev} ];
+ }
+ } else {
+ push @entries, [ $name ];
+ }
+
+ # skip remaining lines of multiline values
+ do {
+ if (m/}/) {
+ redo;
+ } elsif (m@,\s*(/\*.*?\*/\s*)*$@) {
+ next;
+ }
+ } while (<$file>);
+
+ # failed to find end of value. bail
+ die "$0: $file_name:$.: Unterminated enum, while processing `$name'\n";
+ } elsif (m@^\s*\#@) {
+ # ignore preprocessor directives
+ } else {
+ print STDERR "$0: $file_name:$.: Failed to parse `$_'\n";
+ }
+ }
+
+ return 0;
+}
+
+sub version {
+ print STDERR "gimp-mkenums based on glib-mkenums version glib-2.0\n";
+ print STDERR "gimp-mkenums comes with ABSOLUTELY NO WARRANTY.\n";
+ print STDERR "You may redistribute copies of gimp-mkenums under the terms\n";
+ print STDERR "of the GNU General Public License which can be found in the\n";
+ print STDERR "GIMP source package.";
+ exit 0;
+}
+sub usage {
+ print STDERR "Usage: gimp-mkenums [options] [files...]\n";
+ print STDERR " --fhead <text> output file header\n";
+ print STDERR " --fprod <text> per input file production\n";
+ print STDERR " --ftail <text> output file trailer\n";
+ print STDERR " --eprod <text> per enum text (produced prior to value itarations)\n";
+ print STDERR " --vhead <text> value header, produced before iterating over enum values\n";
+ print STDERR " --vprod <text> value text, produced for each enum value\n";
+ print STDERR " --vtail <text> value tail, produced after iterating over enum values\n";
+ print STDERR " --dhead <text> description header, produced before iterating over enum value descriptions\n";
+ print STDERR " --dprod <text> description text, produced for each enum value description\n";
+ print STDERR " --dtail <text> description tail, produced after iterating over enum value descriptions\n";
+ print STDERR " --comments <text> comment structure\n";
+ print STDERR " -h, --help show this help message\n";
+ print STDERR " -v, --version print version information\n";
+ print STDERR "Production text substitutions:\n";
+ print STDERR " \@EnumName\@ PrefixTheXEnum\n";
+ print STDERR " \@enum_name\@ prefix_the_xenum\n";
+ print STDERR " \@ENUMNAME\@ PREFIX_THE_XENUM\n";
+ print STDERR " \@ENUMSHORT\@ THE_XENUM\n";
+ print STDERR " \@enumnick\@ the_xenum\n";
+ print STDERR " \@VALUENAME\@ PREFIX_THE_XVALUE\n";
+ print STDERR " \@valuenick\@ the-xvalue\n";
+ print STDERR " \@valuedesc\@ descriptions as defined in the header\n";
+ print STDERR " \@valuehelp\@ help texts as defined in the header\n";
+ print STDERR " \@valueabbrev\@ abbreviations as defined in the header\n";
+ print STDERR " \@valueudesc\@ untranslated descriptions as defined in the header\n";
+ print STDERR " \@valueuhelp\@ untranslated help texts as defined in the header\n";
+ print STDERR " \@valueuabbrev\@ untranslated abbreviations as defined in the header\n";
+ print STDERR " \@type\@ either enum or flags\n";
+ print STDERR " \@Type\@ either Enum or Flags\n";
+ print STDERR " \@TYPE\@ either ENUM or FLAGS\n";
+ print STDERR " \@filename\@ name of current input file\n";
+ print STDERR " \@basename\@ basename of current input file\n";
+ print STDERR " \@if (...)\@ ... \@endif\@ conditional inclusion\n";
+ exit 0;
+}
+
+# production variables:
+my $fhead = ""; # output file header
+my $fprod = ""; # per input file production
+my $ftail = ""; # output file trailer
+my $eprod = ""; # per enum text (produced prior to value itarations)
+my $vhead = ""; # value header, produced before iterating over enum values
+my $vprod = ""; # value text, produced for each enum value
+my $vtail = ""; # value tail, produced after iterating over enum values
+my $dhead = ""; # desc header, produced before iterating over enum values
+my $dprod = ""; # desc text, produced for each enum value
+my $dtail = ""; # desc tail, produced after iterating over enum values
+# other options
+my $comment_tmpl = "/* \@comment\@ */";
+
+if (!defined $ARGV[0]) {
+ usage;
+}
+while ($_ = $ARGV[0], /^-/) {
+ shift;
+ last if /^--$/;
+ if (/^--fhead$/) { $fhead = $fhead . shift }
+ elsif (/^--fprod$/) { $fprod = $fprod . shift }
+ elsif (/^--ftail$/) { $ftail = $ftail . shift }
+ elsif (/^--eprod$/) { $eprod = $eprod . shift }
+ elsif (/^--vhead$/) { $vhead = $vhead . shift }
+ elsif (/^--vprod$/) { $vprod = $vprod . shift }
+ elsif (/^--vtail$/) { $vtail = $vtail . shift }
+ elsif (/^--dhead$/) { $dhead = $dhead . shift }
+ elsif (/^--dprod$/) { $dprod = $dprod . shift }
+ elsif (/^--dtail$/) { $dtail = $dtail . shift }
+ elsif (/^--comments$/) { $comment_tmpl = shift }
+ elsif (/^--help$/ || /^-h$/) { usage; }
+ elsif (/^--version$/ || /^-v$/) { version; }
+ else { usage; }
+}
+
+# put auto-generation comment
+{
+ my $comment = $comment_tmpl;
+ $comment =~ s/\@comment\@/Generated data (by gimp-mkenums)/;
+ print "\n" . $comment . "\n\n";
+}
+
+if (length($fhead)) {
+ my $prod = $fhead;
+
+ $prod =~ s/\@filename\@/$ARGV[0]/g;
+ $prod =~ s/\@basename\@/basename($ARGV[0])/ge;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+}
+
+while (<>) {
+ if (eof) {
+ close (ARGV); # reset line numbering
+ $firstenum = 1; # Flag to print filename at next enum
+ }
+
+ # read lines until we have no open comments
+ while (m@/\*([^*]|\*(?!/))*$@) {
+ my $new;
+ defined ($new = <>) || die "Unmatched comment in $ARGV";
+ $_ .= $new;
+ }
+ # strip comments w/o options
+ s@/\*(?!<)
+ ([^*]+|\*(?!/))*
+ \*/@@gx;
+
+ # ignore forward declarations
+ next if /^\s*typedef\s+enum.*;/;
+
+ if (m@^\s*typedef\s+enum\s*
+ (\{)?\s*
+ (?:/\*<
+ (([^*]|\*(?!/))*)
+ >\s*\*/)?
+ @x) {
+ if (defined $2) {
+ my %options = parse_trigraph ($2);
+ next if defined $options{"skip"};
+ $enum_prefix = $options{prefix};
+ $flags = $options{flags};
+ $option_lowercase_name = $options{lowercase_name};
+ } else {
+ $enum_prefix = undef;
+ $flags = undef;
+ $option_lowercase_name = undef;
+ }
+ # Didn't have trailing '{' look on next lines
+ if (!defined $1) {
+ while (<>) {
+ if (eof) {
+ die "Hit end of file while parsing enum in $ARGV";
+ }
+ if (s/^\s*\{//) {
+ last;
+ }
+ }
+ }
+
+ $seenbitshift = 0;
+
+ @entries = ();
+
+ # Now parse the entries
+ parse_entries (\*ARGV, $ARGV);
+
+ # figure out if this was a flags or enums enumeration
+ if (!defined $flags) {
+ $flags = $seenbitshift;
+ }
+
+ # Autogenerate a prefix
+ if (!defined $enum_prefix) {
+ for (@entries) {
+ my $nick = $_->[1];
+ if (!defined $nick) {
+ my $name = $_->[0];
+ if (defined $enum_prefix) {
+ my $tmp = ~ ($name ^ $enum_prefix);
+ ($tmp) = $tmp =~ /(^\xff*)/;
+ $enum_prefix = $enum_prefix & $tmp;
+ } else {
+ $enum_prefix = $name;
+ }
+ }
+ }
+ if (!defined $enum_prefix) {
+ $enum_prefix = "";
+ } else {
+ # Trim so that it ends in an underscore
+ $enum_prefix =~ s/_[^_]*$/_/;
+ }
+ } else {
+ # canonicalize user defined prefixes
+ $enum_prefix = uc($enum_prefix);
+ $enum_prefix =~ s/-/_/g;
+ $enum_prefix =~ s/(.*)([^_])$/$1$2_/;
+ }
+
+
+ # enumname is e.g. GMatchType
+ $enspace = $enumname;
+ $enspace =~ s/^([A-Z][a-z]*).*$/$1/;
+
+ $enumshort = $enumname;
+ $enumshort =~ s/^[A-Z][a-z]*//;
+ $enumshort =~ s/([^A-Z])([A-Z])/$1_$2/g;
+ $enumshort =~ s/([A-Z][A-Z])([A-Z][0-9a-z])/$1_$2/g;
+ $enumshort = uc($enumshort);
+
+ $enumlong = uc($enspace) . "_" . $enumshort;
+ $enumsym = lc($enspace) . "_" . lc($enumshort);
+
+ $enumnick = lc($enumshort);
+ $enumnick =~ tr/_/-/;
+
+ for $entry (@entries) {
+ my ($name,$nick,$desc,$help,$abbrev) = @{$entry};
+ if (!defined $nick) {
+ ($nick = $name) =~ s/^$enum_prefix//;
+ $nick =~ tr/_/-/;
+ $nick = lc($nick);
+ }
+ if (!defined $desc) {
+ $udesc = "\"$name\"";
+ } else {
+ $udesc = $desc;
+ }
+ if (!defined $desc) {
+ $desc = "\"$name\"";
+ } else {
+ $desc = "NC_(\"$enumnick\", $desc)";
+ }
+ if (!defined $help) {
+ $uhelp = "NULL";
+ } else {
+ $uhelp = $help;
+ }
+ if (!defined $help) {
+ $help = "NULL";
+ } else {
+ $help = "N_($help)";
+ }
+ if (!defined $abbrev) {
+ $uabbrev = "NULL";
+ } else {
+ $uabbrev = $abbrev;
+ }
+ if (!defined $abbrev) {
+ $abbrev = "NULL";
+ } else {
+ $abbrev = "NC_(\"$enumnick\", $abbrev)";
+ }
+ @{$entry} = ($name, $nick, $desc, $help, $abbrev, $udesc, $uhelp, $uabbrev);
+ }
+
+
+ # Spit out the output
+
+ # The options might override the lower case name if it could
+ # not be generated correctly:
+ if (defined($option_lowercase_name)) {
+ $enumsym = $option_lowercase_name;
+ }
+
+ if ($firstenum) {
+ $firstenum = 0;
+
+ if (length($fprod)) {
+ my $prod = $fprod;
+
+ $prod =~ s/\@filename\@/$ARGV/g;
+ $prod =~ s/\@basename\@/basename($ARGV)/ge;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+ }
+ }
+
+ if (length($eprod)) {
+ my $prod = $eprod;
+
+ $prod =~ s/\@enum_name\@/$enumsym/g;
+ $prod =~ s/\@EnumName\@/$enumname/g;
+ $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
+ $prod =~ s/\@enumnick\@/$enumnick/g;
+ $prod =~ s/\@ENUMNAME\@/$enumlong/g;
+ if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
+ $prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+ }
+
+ if (length($vhead)) {
+ my $prod = $vhead;
+
+ $prod =~ s/\@enum_name\@/$enumsym/g;
+ $prod =~ s/\@EnumName\@/$enumname/g;
+ $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
+ $prod =~ s/\@enumnick\@/$enumnick/g;
+ $prod =~ s/\@ENUMNAME\@/$enumlong/g;
+ if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
+ $prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+ }
+
+ if (length($vprod)) {
+ my $prod = $vprod;
+
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+ for (@entries) {
+ my ($name,$nick,$desc,$help,$abbrev,$udesc,$uhelp,$uabbrev) = @{$_};
+ my $tmp_prod = $prod;
+
+ $tmp_prod =~ s/\@VALUENAME\@/$name/g;
+ $tmp_prod =~ s/\@valuenick\@/$nick/g;
+ $tmp_prod =~ s/\@valuedesc\@/$desc/g;
+ $tmp_prod =~ s/\@valuehelp\@/$help/g;
+ $tmp_prod =~ s/\@valueabbrev\@/$abbrev/g;
+ $tmp_prod =~ s/\@valueudesc\@/$udesc/g;
+ $tmp_prod =~ s/\@valueuhelp\@/$uhelp/g;
+ $tmp_prod =~ s/\@valueuabbrev\@/$uabbrev/g;
+ if ($flags) { $tmp_prod =~ s/\@type\@/flags/g; } else { $tmp_prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $tmp_prod =~ s/\@Type\@/Flags/g; } else { $tmp_prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $tmp_prod =~ s/\@TYPE\@/FLAGS/g; } else { $tmp_prod =~ s/\@TYPE\@/ENUM/g; }
+ $tmp_prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+
+ print "$tmp_prod\n";
+ }
+ }
+
+ if (length($vtail)) {
+ my $prod = $vtail;
+
+ $prod =~ s/\@enum_name\@/$enumsym/g;
+ $prod =~ s/\@EnumName\@/$enumname/g;
+ $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
+ $prod =~ s/\@enumnick\@/$enumnick/g;
+ $prod =~ s/\@ENUMNAME\@/$enumlong/g;
+ if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
+ $prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+ }
+
+ if (length($dhead)) {
+ my $prod = $dhead;
+
+ $prod =~ s/\@enum_name\@/$enumsym/g;
+ $prod =~ s/\@EnumName\@/$enumname/g;
+ $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
+ $prod =~ s/\@enumnick\@/$enumnick/g;
+ $prod =~ s/\@ENUMNAME\@/$enumlong/g;
+ if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
+ $prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+ }
+
+ if (length($dprod)) {
+ my $prod = $dprod;
+
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+ for (@entries) {
+ my ($name,$nick,$desc,$help,$abbrev,$udesc,$uhelp,$uabbrev) = @{$_};
+ my $tmp_prod = $prod;
+
+ $tmp_prod =~ s/\@VALUENAME\@/$name/g;
+ $tmp_prod =~ s/\@valuenick\@/$nick/g;
+ $tmp_prod =~ s/\@valuedesc\@/$desc/g;
+ $tmp_prod =~ s/\@valuehelp\@/$help/g;
+ $tmp_prod =~ s/\@valueabbrev\@/$abbrev/g;
+ $tmp_prod =~ s/\@valueudesc\@/$udesc/g;
+ $tmp_prod =~ s/\@valueuhelp\@/$uhelp/g;
+ $tmp_prod =~ s/\@valueuabbrev\@/$uabbrev/g;
+ if ($flags) { $tmp_prod =~ s/\@type\@/flags/g; } else { $tmp_prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $tmp_prod =~ s/\@Type\@/Flags/g; } else { $tmp_prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $tmp_prod =~ s/\@TYPE\@/FLAGS/g; } else { $tmp_prod =~ s/\@TYPE\@/ENUM/g; }
+ $tmp_prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+
+ print "$tmp_prod\n";
+ }
+ }
+
+ if (length($dtail)) {
+ my $prod = $dtail;
+
+ $prod =~ s/\@enum_name\@/$enumsym/g;
+ $prod =~ s/\@EnumName\@/$enumname/g;
+ $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
+ $prod =~ s/\@enumnick\@/$enumnick/g;
+ $prod =~ s/\@ENUMNAME\@/$enumlong/g;
+ if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
+ if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
+ if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
+ $prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+ }
+ }
+}
+
+if (length($ftail)) {
+ my $prod = $ftail;
+
+ $prod =~ s/\@filename\@/$ARGV/g;
+ $prod =~ s/\@basename\@/basename($ARGV)/ge;
+ $prod =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
+ $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
+
+ print "$prod\n";
+}
+
+# put auto-generation comment
+{
+ my $comment = $comment_tmpl;
+ $comment =~ s/\@comment\@/Generated data ends here/;
+ $comment =~ s/\@if (\((?>[^()]|(?1))*\))\@(.*?)\@endif\@/eval ($1) ? "$2" : ""/ges;
+ print "\n" . $comment . "\n\n";
+}
diff --git a/tools/gimp-test-clipboard.c b/tools/gimp-test-clipboard.c
new file mode 100644
index 0000000..04f02e3
--- /dev/null
+++ b/tools/gimp-test-clipboard.c
@@ -0,0 +1,443 @@
+/*
+ * gimp-test-clipboard.c -- do clipboard things
+ *
+ * Copyright (C) 2005 Michael Natterer <mitch@gimp.org>
+ *
+ * Use this code for whatever you like.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib/gstdio.h>
+#ifndef _O_BINARY
+#define _O_BINARY 0
+#endif
+
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+
+
+typedef struct _CopyData CopyData;
+
+struct _CopyData
+{
+ const gchar *filename;
+ gboolean file_copied;
+ GError *error;
+};
+
+
+static void test_clipboard_show_version (void) G_GNUC_NORETURN;
+static gboolean test_clipboard_parse_selection (const gchar *option_name,
+ const gchar *value,
+ gpointer data,
+ GError **error);
+static gboolean test_clipboard_list_targets (GtkClipboard *clipboard);
+static gboolean test_clipboard_copy (GtkClipboard *clipboard,
+ const gchar *target,
+ const gchar *filename);
+static gboolean test_clipboard_store (GtkClipboard *clipboard,
+ const gchar *target,
+ const gchar *filename);
+static gboolean test_clipboard_paste (GtkClipboard *clipboard,
+ const gchar *target,
+ const gchar *filename);
+static void test_clipboard_copy_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection,
+ guint info,
+ gpointer data);
+
+
+static GdkAtom option_selection_type = GDK_SELECTION_CLIPBOARD;
+static gboolean option_list_targets = FALSE;
+static gchar *option_target = NULL;
+static gchar *option_copy_filename = NULL;
+static gchar *option_store_filename = NULL;
+static gchar *option_paste_filename = NULL;
+
+static const GOptionEntry main_entries[] =
+{
+ {
+ "selection-type", 's', 0,
+ G_OPTION_ARG_CALLBACK, test_clipboard_parse_selection,
+ "Selection type (primary|secondary|clipboard)", "<type>"
+ },
+ {
+ "list-targets", 'l', 0,
+ G_OPTION_ARG_NONE, &option_list_targets,
+ "List the targets offered by the clipboard", NULL
+ },
+ {
+ "target", 't', 0,
+ G_OPTION_ARG_STRING, &option_target,
+ "The target format to copy or paste", "<target>"
+ },
+ {
+ "copy", 'c', 0,
+ G_OPTION_ARG_STRING, &option_copy_filename,
+ "Copy <file> to clipboard", "<file>"
+ },
+ {
+ "store", 'S', 0,
+ G_OPTION_ARG_STRING, &option_store_filename,
+ "Store <file> in the clipboard manager", "<file>"
+ },
+ {
+ "paste", 'p', 0,
+ G_OPTION_ARG_STRING, &option_paste_filename,
+ "Paste clipoard into <file> ('-' pastes to STDOUT)", "<file>"
+ },
+ {
+ "version", 'v', G_OPTION_FLAG_NO_ARG,
+ G_OPTION_ARG_CALLBACK, test_clipboard_show_version,
+ "Show version information and exit", NULL
+ },
+ { NULL }
+};
+
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ GOptionContext *context;
+ GtkClipboard *clipboard;
+ GError *error = NULL;
+
+ context = g_option_context_new (NULL);
+ g_option_context_add_main_entries (context, main_entries, NULL);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+
+ if (! g_option_context_parse (context, &argc, &argv, &error))
+ {
+ if (error)
+ {
+ g_printerr ("%s\n", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_print ("%s\n",
+ "Could not initialize the graphical user interface.\n"
+ "Make sure a proper setup for your display environment "
+ "exists.");
+ }
+
+ return EXIT_FAILURE;
+ }
+
+ gtk_init (&argc, &argv);
+
+ clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (),
+ option_selection_type);
+
+ if (! clipboard)
+ g_error ("gtk_clipboard_get_for_display");
+
+ if (option_list_targets)
+ {
+ if (! test_clipboard_list_targets (clipboard))
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+ }
+
+ if ((option_copy_filename && option_paste_filename) ||
+ (option_copy_filename && option_store_filename) ||
+ (option_paste_filename && option_store_filename))
+ {
+ g_printerr ("Can't perform two operations at the same time\n");
+ return EXIT_FAILURE;
+ }
+
+ if (option_copy_filename)
+ {
+ if (! option_target)
+ {
+ g_printerr ("Usage: %s -t <target> -c <file>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (! test_clipboard_copy (clipboard, option_target,
+ option_copy_filename))
+ return EXIT_FAILURE;
+ }
+
+ if (option_store_filename)
+ {
+ if (! option_target)
+ {
+ g_printerr ("Usage: %s -t <target> -S <file>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (! test_clipboard_store (clipboard, option_target,
+ option_store_filename))
+ return EXIT_FAILURE;
+ }
+
+ if (option_paste_filename)
+ {
+ if (! option_target)
+ {
+ g_printerr ("Usage: %s -t <target> -p <file>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (! test_clipboard_paste (clipboard, option_target,
+ option_paste_filename))
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static void
+test_clipboard_show_version (void)
+{
+ g_print ("gimp-test-clipboard (GIMP clipboard testbed) version %s\n",
+ GIMP_VERSION);
+
+ exit (EXIT_SUCCESS);
+}
+
+static gboolean
+test_clipboard_parse_selection (const gchar *option_name,
+ const gchar *value,
+ gpointer data,
+ GError **error)
+{
+ if (! strcmp (value, "primary"))
+ option_selection_type = GDK_SELECTION_PRIMARY;
+ else if (! strcmp (value, "secondary"))
+ option_selection_type = GDK_SELECTION_SECONDARY;
+ else if (! strcmp (value, "clipboard"))
+ option_selection_type = GDK_SELECTION_CLIPBOARD;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+test_clipboard_list_targets (GtkClipboard *clipboard)
+{
+ GtkSelectionData *data;
+
+ data = gtk_clipboard_wait_for_contents (clipboard,
+ gdk_atom_intern ("TARGETS",
+ FALSE));
+ if (data)
+ {
+ GdkAtom *targets;
+ gint n_targets;
+ gboolean success;
+
+ success = gtk_selection_data_get_targets (data, &targets, &n_targets);
+
+ gtk_selection_data_free (data);
+
+ if (success)
+ {
+ gint i;
+
+ for (i = 0; i < n_targets; i++)
+ g_print ("%s\n", gdk_atom_name (targets[i]));
+
+ g_free (targets);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+test_clipboard_copy (GtkClipboard *clipboard,
+ const gchar *target,
+ const gchar *filename)
+{
+ GtkTargetEntry entry;
+ CopyData data;
+
+ entry.target = g_strdup (target);
+ entry.flags = 0;
+ entry.info = 1;
+
+ data.filename = filename;
+ data.file_copied = FALSE;
+ data.error = NULL;
+
+ if (! gtk_clipboard_set_with_data (clipboard, &entry, 1,
+ test_clipboard_copy_callback,
+ NULL,
+ &data))
+ {
+ g_printerr ("%s: gtk_clipboard_set_with_data() failed\n",
+ g_get_prgname());
+ return FALSE;
+ }
+
+ gtk_main ();
+
+ if (! data.file_copied)
+ {
+ if (data.error)
+ {
+ g_printerr ("%s: copying failed: %s\n",
+ g_get_prgname (), data.error->message);
+ g_error_free (data.error);
+ }
+ else
+ {
+ g_printerr ("%s: copying failed\n",
+ g_get_prgname ());
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+test_clipboard_store (GtkClipboard *clipboard,
+ const gchar *target,
+ const gchar *filename)
+{
+ GtkTargetEntry entry;
+ CopyData data;
+
+ entry.target = g_strdup (target);
+ entry.flags = 0;
+ entry.info = 1;
+
+ data.filename = filename;
+ data.file_copied = FALSE;
+ data.error = NULL;
+
+ if (! gtk_clipboard_set_with_data (clipboard, &entry, 1,
+ test_clipboard_copy_callback,
+ NULL,
+ &data))
+ {
+ g_printerr ("%s: gtk_clipboard_set_with_data() failed\n",
+ g_get_prgname ());
+ return FALSE;
+ }
+
+ gtk_clipboard_set_can_store (clipboard, &entry, 1);
+ gtk_clipboard_store (clipboard);
+
+ if (! data.file_copied)
+ {
+ if (data.error)
+ {
+ g_printerr ("%s: storing failed: %s\n",
+ g_get_prgname (), data.error->message);
+ g_error_free (data.error);
+ }
+ else
+ {
+ g_printerr ("%s: could not contact clipboard manager\n",
+ g_get_prgname ());
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+test_clipboard_paste (GtkClipboard *clipboard,
+ const gchar *target,
+ const gchar *filename)
+{
+ GtkSelectionData *sel_data;
+
+ sel_data = gtk_clipboard_wait_for_contents (clipboard,
+ gdk_atom_intern (target,
+ FALSE));
+ if (sel_data)
+ {
+ const guchar *data;
+ gint length;
+ gint fd;
+
+ if (! strcmp (filename, "-"))
+ fd = 1;
+ else
+ fd = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | _O_BINARY, 0666);
+
+ if (fd < 0)
+ {
+ g_printerr ("%s: open() filed: %s",
+ g_get_prgname (), g_strerror (errno));
+ return FALSE;
+ }
+
+ data = gtk_selection_data_get_data (sel_data);
+ length = gtk_selection_data_get_length (sel_data);
+
+ if (write (fd, data, length) < length)
+ {
+ close (fd);
+ g_printerr ("%s: write() failed: %s",
+ g_get_prgname (), g_strerror (errno));
+ return FALSE;
+ }
+
+ if (close (fd) < 0)
+ {
+ g_printerr ("%s: close() failed: %s",
+ g_get_prgname (), g_strerror (errno));
+ return FALSE;
+ }
+
+ gtk_selection_data_free (sel_data);
+ }
+
+ return TRUE;
+}
+
+static void
+test_clipboard_copy_callback (GtkClipboard *clipboard,
+ GtkSelectionData *selection,
+ guint info,
+ gpointer data)
+{
+ CopyData *copy_data = data;
+ gchar *buf;
+ gsize buf_size;
+
+ if (! g_file_get_contents (copy_data->filename, &buf, &buf_size,
+ &copy_data->error))
+ {
+ if (! option_store_filename)
+ gtk_main_quit ();
+
+ return;
+ }
+
+ gtk_selection_data_set (selection,
+ gtk_selection_data_get_target (selection),
+ 8, (guchar *) buf, buf_size);
+
+ g_free (buf);
+
+ copy_data->file_copied = TRUE;
+
+ g_print ("%s: data transfer in progress, hit <ctrl>+c when pasted...",
+ G_STRFUNC);
+}
diff --git a/tools/gimppath2svg.py b/tools/gimppath2svg.py
new file mode 100755
index 0000000..21e5512
--- /dev/null
+++ b/tools/gimppath2svg.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python2
+
+import sys,re
+
+"""
+gimppath2svg.py -- Converts Gimp-Paths to a SVG-File.
+Copyright (C) 2000 Simon Budig <simon@gimp.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: gimppath2svg.py [infile [outfile]]
+"""
+
+
+svgtemplate = """<?xml version="1.0" standalone="yes"?>
+<svg width="%d" height="%d">
+<g>
+<path id="%s" transform="translate (%d,%d)"
+ style="stroke:#000000; stroke-width:1; fill:none"
+ d="%s"/>
+</g>
+</svg>
+"""
+
+emptysvgtemplate = """<?xml version="1.0" standalone="yes"?>
+<svg width="1" height="1">
+</svg>
+"""
+
+
+class Path:
+ def __init__(self):
+ self.name = ""
+ self.svgpath = ""
+ self.gimppoints = [[]]
+ self.bounds = None
+
+ def readgimpfile (self, filedesc):
+ text = filedesc.readlines()
+ for line in text:
+ namematch = re.match ("Name: (.*)$", line)
+ if namematch:
+ path.name = namematch.group(1)
+ pointmatch = re.match ("TYPE: (\d) X: (\d+) Y: (\d+)", line)
+ if pointmatch:
+ if pointmatch.group (1) == "3":
+ path.gimppoints.append ([])
+ (x, y) = map (int, pointmatch.groups()[1:])
+ path.gimppoints[-1].append (map (int, pointmatch.groups()))
+ if self.bounds:
+ if self.bounds[0] > x: self.bounds[0] = x
+ if self.bounds[1] > y: self.bounds[1] = y
+ if self.bounds[2] < x: self.bounds[2] = x
+ if self.bounds[3] < y: self.bounds[3] = y
+ else:
+ self.bounds = [x,y,x,y]
+
+ def makesvg (self):
+ for path in self.gimppoints:
+ if path:
+ start = path[0]
+ svg = "M %d %d " % tuple (start[1:])
+ path = path[1:]
+ while path:
+ curve = path [0:3]
+ path = path[3:]
+ if len (curve) == 2:
+ svg = svg + "C %d %d %d %d %d %d z " % tuple (
+ tuple (curve [0][1:]) +
+ tuple (curve [1][1:]) +
+ tuple (start [1:]))
+ if len (curve) == 3:
+ svg = svg + "C %d %d %d %d %d %d " % tuple (
+ tuple (curve [0][1:]) +
+ tuple (curve [1][1:]) +
+ tuple (curve [2][1:]))
+ self.svgpath = self.svgpath + svg
+
+ def writesvgfile (self, outfile):
+ if self.svgpath:
+ svg = svgtemplate % (self.bounds[2]-self.bounds[0],
+ self.bounds[3]-self.bounds[1],
+ self.name,
+ -self.bounds[0], -self.bounds[1],
+ self.svgpath)
+ else:
+ svg = emptysvgtemplate
+ outfile.write (svg)
+
+
+if len (sys.argv) > 1:
+ infile = open (sys.argv[1])
+else:
+ infile = sys.stdin
+
+if len (sys.argv) > 2:
+ outfile = open (sys.argv[2], "w")
+else:
+ outfile = sys.stdout
+
+
+path = Path()
+path.readgimpfile (infile)
+path.makesvg()
+path.writesvgfile (outfile)
diff --git a/tools/gimptool.c b/tools/gimptool.c
new file mode 100644
index 0000000..21fc043
--- /dev/null
+++ b/tools/gimptool.c
@@ -0,0 +1,1142 @@
+/* gimptool in C
+ * Copyright (C) 2001-2007 Tor Lillqvist
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Gimptool rewritten in C, originally for Win32, where end-users who
+ * might want to build and install a plug-in from source don't
+ * necessarily have any Bourne-compatible shell to run the gimptool
+ * script in. Later fixed up to replace the gimptool script on all
+ * platforms.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/stat.h>
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#ifdef G_OS_WIN32
+#include "libgimpbase/gimpwin32-io.h"
+#endif
+
+
+static gboolean silent = FALSE;
+static gboolean dry_run = FALSE;
+static const gchar *cli_prefix;
+static const gchar *cli_exec_prefix;
+
+static gboolean msvc_syntax = FALSE;
+static const gchar *env_cc;
+static const gchar *env_cflags;
+static const gchar *env_ldflags;
+static const gchar *env_libs;
+
+
+#ifdef G_OS_WIN32
+#define EXEEXT ".exe"
+#else
+#define EXEEXT ""
+#endif
+
+#ifdef G_OS_WIN32
+#define COPY win32_command ("copy")
+#define REMOVE win32_command ("del")
+#define REMOVE_DIR win32_command ("rd /s /q")
+#else
+#define COPY "cp"
+#define REMOVE "rm -f"
+#define REMOVE_DIR "rm -Rf"
+#endif
+
+static struct {
+ const gchar *option;
+ const gchar *value;
+} dirs[] = {
+ { "prefix", PREFIX },
+ { "exec-prefix", EXEC_PREFIX },
+ { "bindir", BINDIR },
+ { "sbindir", SBINDIR },
+ { "libexecdir", LIBEXECDIR },
+ { "datadir", DATADIR },
+ { "datarootdir", DATAROOTDIR },
+ { "sysconfdir", SYSCONFDIR },
+ { "sharedstatedir", SHAREDSTATEDIR },
+ { "localstatedir", LOCALSTATEDIR },
+ { "libdir", LIBDIR },
+ { "infodir", INFODIR },
+ { "mandir", MANDIR },
+#if 0
+ /* For --includedir we want the includedir of the developer package,
+ * not an includedir under the runtime installation prefix.
+ */
+ { "includedir", INCLUDEDIR },
+#endif
+ { "gimpplugindir", GIMPPLUGINDIR },
+ { "gimpdatadir", GIMPDATADIR }
+};
+
+
+static void usage (int exit_status) G_GNUC_NORETURN;
+
+
+#ifdef G_OS_WIN32
+
+static gchar *
+win32_command (const gchar *command)
+{
+ const gchar *comspec = getenv ("COMSPEC");
+
+ if (!comspec)
+ comspec = "cmd.exe";
+
+ return g_strdup_printf ("%s /c %s", comspec, command);
+}
+
+#endif
+
+static gboolean
+starts_with_dir (const gchar *string,
+ const gchar *dir)
+{
+ gchar *dirslash = g_strconcat (dir, "/", NULL);
+ gboolean retval;
+
+ retval = (g_str_has_prefix (string, dirslash) ||
+ g_strcmp0 (string, dir) == 0);
+ g_free (dirslash);
+
+ return retval;
+}
+
+static gchar *
+one_line_output (const gchar *program,
+ const gchar *args)
+{
+ gchar *command = g_strconcat (program, " ", args, NULL);
+ FILE *pipe = popen (command, "r");
+ gchar line[4096];
+
+ if (pipe == NULL)
+ {
+ g_printerr ("Cannot run '%s'\n", command);
+ g_free (command);
+ exit (EXIT_FAILURE);
+ }
+
+ if (fgets (line, sizeof (line), pipe) == NULL)
+ line[0] = '\0';
+
+ if (strlen (line) > 0 && line [strlen (line) - 1] == '\n')
+ line [strlen (line) - 1] = '\0';
+ if (strlen (line) > 0 && line [strlen (line) - 1] == '\r')
+ line [strlen (line) - 1] = '\0';
+
+ pclose (pipe);
+
+ if (strlen (line) == 0)
+ {
+ g_printerr ("No output from '%s'\n", command);
+ g_free (command);
+ exit (EXIT_FAILURE);
+ }
+ g_free (command);
+
+ return g_strdup (line);
+}
+
+static gchar *
+pkg_config (const gchar *args)
+{
+#ifdef G_OS_WIN32
+ if (msvc_syntax)
+ return one_line_output ("pkg-config --msvc-syntax", args);
+#endif
+
+ return one_line_output ("pkg-config", args);
+}
+
+static gchar *
+get_runtime_prefix (gchar slash)
+{
+#ifdef G_OS_WIN32
+
+ /* Don't use the developer package prefix, but deduce the
+ * installation-time prefix from where gimp-x.y.exe can be found.
+ */
+
+ gchar *path;
+ gchar *p, *r;
+
+ path = g_find_program_in_path ("gimp-" GIMP_APP_VERSION ".exe");
+
+ if (path == NULL)
+ path = g_find_program_in_path ("gimp.exe");
+
+ if (path != NULL)
+ {
+ r = strrchr (path, G_DIR_SEPARATOR);
+ if (r != NULL)
+ {
+ *r = '\0';
+ if (strlen (path) >= 4 &&
+ g_ascii_strcasecmp (r - 4, G_DIR_SEPARATOR_S "bin") == 0)
+ {
+ r[-4] = '\0';
+ if (slash == '/')
+ {
+ /* Use forward slashes, less quoting trouble in Makefiles */
+ while ((p = strchr (path, '\\')) != NULL)
+ *p = '/';
+ }
+ return path;
+ }
+ }
+ }
+
+ g_printerr ("Cannot determine GIMP " GIMP_APP_VERSION " installation location\n");
+
+ exit (EXIT_FAILURE);
+#else
+ /* On Unix assume the executable package is in the same prefix as the developer stuff */
+ return pkg_config ("--variable=prefix gimp-" GIMP_PKGCONFIG_VERSION);
+#endif
+}
+
+static gchar *
+get_exec_prefix (gchar slash)
+{
+#ifdef G_OS_WIN32
+ if (cli_exec_prefix != NULL)
+ return g_strdup (cli_exec_prefix);
+
+ /* On Win32, exec_prefix is always same as prefix. Or is it? Maybe not,
+ * but at least in tml's prebuilt stuff it is. If somebody else does
+ * it another way, feel free to hack this.
+ */
+ return get_runtime_prefix (slash);
+#else
+ return g_strdup (EXEC_PREFIX);
+#endif
+}
+
+static gchar *
+expand_and_munge (const gchar *value)
+{
+ gchar *retval;
+
+ if (starts_with_dir (value, "${prefix}"))
+ retval = g_strconcat (PREFIX, value + strlen ("${prefix}"), NULL);
+ else if (starts_with_dir (value, "${exec_prefix}"))
+ retval = g_strconcat (EXEC_PREFIX, value + strlen ("${exec_prefix}"), NULL);
+ else
+ retval = g_strdup (value);
+
+ if (starts_with_dir (retval, EXEC_PREFIX))
+ {
+ gchar *exec_prefix = get_exec_prefix ('/');
+
+ retval = g_strconcat (exec_prefix, retval + strlen (EXEC_PREFIX), NULL);
+ g_free (exec_prefix);
+ }
+
+ if (starts_with_dir (retval, PREFIX))
+ {
+ gchar *runtime_prefix = get_runtime_prefix ('/');
+
+ retval = g_strconcat (runtime_prefix, retval + strlen (PREFIX), NULL);
+ g_free (runtime_prefix);
+ }
+
+ return retval;
+}
+
+static void
+find_out_env_flags (void)
+{
+ gchar *p;
+
+ if ((p = getenv ("CC")) != NULL && *p != '\0')
+ env_cc = p;
+ else if (msvc_syntax)
+ env_cc = "cl -MD";
+ else
+ env_cc = CC;
+
+ if (g_ascii_strncasecmp (env_cc, "cl", 2) == 0 &&
+ g_ascii_strncasecmp (env_cc, "clang", 5) != 0)
+ msvc_syntax = TRUE;
+
+ if ((p = getenv ("CFLAGS")) != NULL)
+ env_cflags = p;
+ else
+ env_cflags = "";
+
+ if ((p = getenv ("LDFLAGS")) != NULL)
+ env_ldflags = p;
+ else
+ env_ldflags = "";
+
+ if ((p = getenv ("LIBS")) != NULL && *p != '\0')
+ env_libs = p;
+ else
+ env_libs = "";
+}
+
+static void
+usage (int exit_status)
+{
+ g_print ("\
+Usage: gimptool-" GIMP_TOOL_VERSION " [OPTION]...\n\
+\n\
+General options:\n\
+ --help print this message\n\
+ --quiet, --silent don't echo build commands\n\
+ --version print the version of GIMP associated with this script\n\
+ -n, --just-print, --dry-run, --recon\n\
+ don't actually run any commands; just print them\n\
+Developer options:\n\
+ --cflags print the compiler flags that are necessary to\n\
+ compile a plug-in\n\
+ --libs print the linker flags that are necessary to link a\n\
+ plug-in\n\
+ --prefix=PREFIX use PREFIX instead of the installation prefix that\n\
+ GIMP was built when computing the output for --cflags\n\
+ and --libs\n\
+ --exec-prefix=PREFIX use PREFIX instead of the installation exec prefix\n\
+ that GIMP was built when computing the output for\n\
+ --cflags and --libs\n\
+ --msvc-syntax print flags in MSVC syntax\n\
+\n\
+Installation directory options:\n\
+ --prefix --exec-prefix --bindir --sbindir --libexecdir --datadir --sysconfdir\n\
+ --sharedstatedir --localstatedir --libdir --infodir --mandir --includedir\n\
+ --gimpplugindir --gimpdatadir\n\
+\n\
+The --cflags and --libs options can be appended with -noui to get appropriate\n\
+settings for plug-ins which do not use GTK+.\n\
+\n\
+User options:\n\
+ --build plug-in.c build a plug-in from a source file\n\
+ --install plug-in.c same as --build, but installs the built\n\
+ plug-in as well\n\
+ --install-bin plug-in install a compiled plug-in\n\
+ --install-script script.scm install a script-fu script\n\
+\n\
+ --uninstall-bin plug-in remove a plug-in again\n\
+ --uninstall-script plug-in remove a script-fu script\n\
+\n\
+The --install and --uninstall options have \"admin\" counterparts (with\n\
+prefix --install-admin instead of --install) that can be used instead to\n\
+install/uninstall a plug-in or script in the machine directory instead of a\n\
+user directory.\n\
+\n\
+For plug-ins which do not use GTK+, the --build and --install options can be\n\
+appended with -noui for appropriate settings. For plug-ins that use GTK+ but\n\
+not libgimpui, append -nogimpui.\n");
+ exit (exit_status);
+}
+
+static gchar *
+get_includedir (void)
+{
+ return pkg_config ("--variable=includedir gimp-" GIMP_PKGCONFIG_VERSION);
+}
+
+static void
+do_includedir (void)
+{
+ gchar *includedir = get_includedir ();
+
+ g_print ("%s\n", includedir);
+ g_free (includedir);
+}
+
+static gchar *
+get_cflags (void)
+{
+ return pkg_config ("--cflags gimpui-" GIMP_PKGCONFIG_VERSION);
+}
+
+static void
+do_cflags (void)
+{
+ gchar *cflags = get_cflags ();
+
+ g_print ("%s\n", cflags);
+ g_free (cflags);
+}
+
+static gchar *
+get_cflags_noui (void)
+{
+ return pkg_config ("--cflags gimp-" GIMP_PKGCONFIG_VERSION);
+}
+
+static void
+do_cflags_noui (void)
+{
+ gchar *cflags = get_cflags_noui ();
+
+ g_print ("%s\n", cflags);
+ g_free (cflags);
+}
+
+static gchar *
+get_cflags_nogimpui (void)
+{
+ return pkg_config ("--cflags gimp-" GIMP_PKGCONFIG_VERSION " gtk+-2.0");
+}
+
+static void
+do_cflags_nogimpui (void)
+{
+ gchar *cflags = get_cflags_nogimpui ();
+
+ g_print ("%s\n", cflags);
+ g_free (cflags);
+}
+
+static gchar *
+get_libs (void)
+{
+ return pkg_config ("--libs gimpui-" GIMP_PKGCONFIG_VERSION);
+}
+
+static void
+do_libs (void)
+{
+ gchar *libs = get_libs ();
+
+ g_print ("%s\n", libs);
+ g_free (libs);
+}
+
+static gchar *
+get_libs_noui (void)
+{
+ return pkg_config ("--libs gimp-" GIMP_PKGCONFIG_VERSION);
+}
+
+static void
+do_libs_noui (void)
+{
+ gchar *libs = get_libs_noui ();
+
+ g_print ("%s\n", libs);
+ g_free (libs);
+}
+
+static gchar *
+get_libs_nogimpui (void)
+{
+ return pkg_config ("--libs gimp-" GIMP_PKGCONFIG_VERSION " gtk+-2.0");
+}
+
+static void
+do_libs_nogimpui (void)
+{
+ gchar *libs = get_libs_nogimpui ();
+
+ g_print ("%s\n", libs);
+ g_free (libs);
+}
+
+static void
+maybe_run (gchar *cmd)
+{
+ if (!silent)
+ g_print ("%s\n", cmd);
+
+ /* system() declared with attribute warn_unused_result.
+ * Trick to get rid of the compilation warning without using the result.
+ */
+ if (dry_run || system (cmd))
+ ;
+}
+
+static void
+do_build_2 (const gchar *cflags,
+ const gchar *libs,
+ const gchar *install_dir,
+ const gchar *what)
+{
+ const gchar *lang_flag = "";
+ const gchar *output_flag;
+ const gchar *here_comes_linker_flags = "";
+ const gchar *windows_subsystem_flag = "";
+ gchar *cmd;
+ gchar *dest_dir;
+ gchar *dest_exe;
+ gchar *source = g_shell_quote (what);
+
+ gchar *tmp;
+ gchar *p, *q;
+
+ if (install_dir != NULL)
+ dest_dir = g_strconcat (install_dir, "/", NULL);
+ else
+ dest_dir = g_strdup ("");
+
+ dest_exe = g_strdup (what);
+
+ p = strrchr (dest_exe, '.');
+ if (p == NULL ||
+ !(strcmp (p, ".c") == 0 ||
+ strcmp (p, ".cc") == 0 ||
+ strcmp (p, ".cpp") == 0))
+ {
+ /* If the file doesn't have a "standard" C/C++ suffix and:
+ * 1) if the compiler is known as a C++ compiler, then treat the file as a
+ * C++ file if possible.
+ * It's known that G++ and Clang++ treat a file as a C file if they are
+ * run with the "-x c++" option.
+ * 2) if the compiler is known as a C compiler or a multiple-language
+ * compiler, then treat the file as a C file if possible.
+ * It's known that GCC and Clang treat a file as a C file if they are
+ * run with the "-x c" option.
+ * TODO We may want to further support compilation with a source file
+ * without a standard suffix in more compilers as far as possible.
+ */
+ if (strcmp (env_cc, "g++") == 0 ||
+ strncmp (env_cc, "g++-", sizeof ("g++-") - 1) == 0 ||
+ strcmp (env_cc, "clang++") == 0 ||
+ strncmp (env_cc, "clang++-", sizeof ("clang++-") - 1) == 0)
+ lang_flag = "-x c++ ";
+ else if (strcmp (env_cc, "gcc") == 0 ||
+ strncmp (env_cc, "gcc-", sizeof ("gcc-") - 1) == 0)
+ {
+ /* It's known GCC recognizes .CPP and .cxx, so bypass these suffixes */
+ if (p != NULL && strcmp (p, ".CPP") != 0 && strcmp (p, ".cxx") != 0)
+ lang_flag = "-x c ";
+ }
+ else if (strcmp (env_cc, "clang") == 0 ||
+ strncmp (env_cc, "clang-", sizeof ("clang-") - 1) == 0)
+ {
+ /* It's known Clang recognizes .CC, .CPP, .cxx and .CXX,
+ * so bypass these suffixes
+ */
+ if (p != NULL && strcmp (p, ".CC") != 0 && strcmp (p, ".CPP") != 0 &&
+ strcmp (p, ".cxx") != 0 && strcmp (p, ".CXX") != 0)
+ lang_flag = "-x c ";
+ }
+ else
+ {
+ g_printerr ("The source file (%s) doesn't have a \"standard\" C or C++ suffix, "
+ "and the tool failed to confirm the language of the file.\n"
+ "Please be explicit about the language of the file "
+ "by renaming it with one of the suffixes: .c .cc .cpp\n",
+ what);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (p)
+ *p = '\0';
+ q = strrchr (dest_exe, G_DIR_SEPARATOR);
+#ifdef G_OS_WIN32
+ {
+ gchar *r = strrchr (dest_exe, '/');
+ if (r != NULL && (q == NULL || r > q))
+ q = r;
+ }
+#endif
+ if (q == NULL)
+ q = dest_exe;
+ else
+ q++;
+
+ if (install_dir)
+ {
+ tmp = dest_dir;
+ dest_dir = g_strconcat (dest_dir, q, G_DIR_SEPARATOR_S, NULL);
+ g_free (tmp);
+
+ g_mkdir_with_parents (dest_dir,
+ S_IRUSR | S_IXUSR | S_IWUSR |
+ S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH);
+ }
+
+ tmp = g_strconcat (dest_dir, q, NULL);
+ g_free (dest_dir);
+
+ dest_exe = g_shell_quote (tmp);
+ g_free (tmp);
+
+ if (msvc_syntax)
+ {
+ output_flag = "-Fe";
+ here_comes_linker_flags = " -link";
+ windows_subsystem_flag = " -subsystem:windows";
+ }
+ else
+ {
+ output_flag = "-o ";
+#ifdef G_OS_WIN32
+ windows_subsystem_flag = " -mwindows";
+#endif
+ }
+
+ cmd = g_strdup_printf ("%s %s%s %s %s%s %s%s %s%s %s %s",
+ env_cc,
+ lang_flag,
+ env_cflags,
+ cflags,
+ output_flag,
+ dest_exe,
+ source,
+ here_comes_linker_flags,
+ env_ldflags,
+ windows_subsystem_flag,
+ libs,
+ env_libs);
+
+ maybe_run (cmd);
+
+ g_free (dest_exe);
+ g_free (source);
+}
+
+static void
+do_build (const gchar *what)
+{
+ gchar *cflags = get_cflags ();
+ gchar *libs = get_libs ();
+
+ do_build_2 (cflags, libs, NULL, what);
+
+ g_free (cflags);
+ g_free (libs);
+}
+
+static void
+do_build_noui (const gchar *what)
+{
+ gchar *cflags = get_cflags_noui ();
+ gchar *libs = get_libs_noui ();
+
+ do_build_2 (cflags, libs, NULL, what);
+
+ g_free (cflags);
+ g_free (libs);
+}
+
+static void
+do_build_nogimpui (const gchar *what)
+{
+ do_build (what);
+}
+
+static gchar *
+get_user_plugin_dir (void)
+{
+ return g_build_filename (gimp_directory (),
+ "plug-ins",
+ NULL);
+}
+
+static gchar *
+get_plugin_dir (const gchar *base_dir,
+ const gchar *what)
+{
+ gchar *separator, *dot, *plugin_name, *plugin_dir;
+ gchar *tmp = g_strdup (what);
+
+ separator = strrchr (tmp, G_DIR_SEPARATOR);
+#ifdef G_OS_WIN32
+ {
+ gchar *alt_separator = strrchr (tmp, '/');
+
+ if (alt_separator != NULL &&
+ (separator == NULL || alt_separator > separator))
+ {
+ separator = alt_separator;
+ }
+ }
+#endif
+
+ dot = strrchr (tmp, '.');
+
+ if (separator)
+ plugin_name = separator + 1;
+ else
+ plugin_name = tmp;
+
+ if (dot)
+ *dot = '\0';
+
+ plugin_dir = g_strconcat (base_dir,
+ G_DIR_SEPARATOR_S,
+ plugin_name,
+ NULL);
+
+ g_free (tmp);
+
+ return plugin_dir;
+}
+
+static void
+do_install (const gchar *what)
+{
+ gchar *cflags = get_cflags ();
+ gchar *libs = get_libs ();
+ gchar *dir = get_user_plugin_dir ();
+
+ do_build_2 (cflags, libs, dir, what);
+
+ g_free (cflags);
+ g_free (libs);
+ g_free (dir);
+}
+
+static void
+do_install_noui (const gchar *what)
+{
+ gchar *cflags = get_cflags_noui ();
+ gchar *libs = get_libs_noui ();
+ gchar *dir = get_user_plugin_dir ();
+
+ do_build_2 (cflags, libs, dir, what);
+
+ g_free (cflags);
+ g_free (libs);
+ g_free (dir);
+}
+
+static void
+do_install_nogimpui (const gchar *what)
+{
+ do_install (what);
+}
+
+static gchar *
+get_sys_plugin_dir (gboolean forward_slashes)
+{
+ gchar *dir;
+
+#ifdef G_OS_WIN32
+ gchar *rprefix;
+
+ rprefix = get_runtime_prefix (forward_slashes ? '/' : G_DIR_SEPARATOR);
+
+ dir = g_build_path (forward_slashes ? "/" : G_DIR_SEPARATOR_S,
+ rprefix,
+ "lib", "gimp",
+ GIMP_PLUGIN_VERSION,
+ "plug-ins",
+ NULL);
+ g_free (rprefix);
+#else
+ dir = g_build_path (forward_slashes ? "/" : G_DIR_SEPARATOR_S,
+ LIBDIR,
+ "gimp",
+ GIMP_PLUGIN_VERSION,
+ "plug-ins",
+ NULL);
+#endif
+
+ return dir;
+}
+
+static void
+do_install_admin (const gchar *what)
+{
+ gchar *cflags = get_cflags ();
+ gchar *libs = get_libs ();
+ gchar *dir = get_sys_plugin_dir (TRUE);
+
+ do_build_2 (cflags, libs, dir, what);
+
+ g_free (cflags);
+ g_free (libs);
+ g_free (dir);
+}
+
+static void
+do_install_admin_noui (const gchar *what)
+{
+ gchar *cflags = get_cflags_noui ();
+ gchar *libs = get_libs_noui ();
+ gchar *dir = get_sys_plugin_dir (TRUE);
+
+ do_build_2 (cflags, libs, dir, what);
+
+ g_free (cflags);
+ g_free (libs);
+ g_free (dir);
+}
+
+static void
+do_install_admin_nogimpui (const gchar *what)
+{
+ gchar *cflags = get_cflags ();
+ gchar *libs = get_libs ();
+ gchar *dir = get_sys_plugin_dir (TRUE);
+
+ do_build_2 (cflags, libs, dir, what);
+
+ g_free (cflags);
+ g_free (libs);
+ g_free (dir);
+}
+
+static void
+do_install_bin_2 (const gchar *dir,
+ const gchar *what)
+{
+ gchar *cmd;
+ gchar *quoted_src;
+ gchar *quoted_dir;
+
+ gchar *dest_dir = g_strconcat (dir, G_DIR_SEPARATOR_S, NULL);
+
+ g_mkdir_with_parents (dest_dir,
+ S_IRUSR | S_IXUSR | S_IWUSR |
+ S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH);
+
+ quoted_src = g_shell_quote (what);
+ quoted_dir = g_shell_quote (dest_dir);
+ cmd = g_strconcat (COPY, " ", quoted_src, " ", quoted_dir, NULL);
+ maybe_run (cmd);
+
+ g_free (dest_dir);
+ g_free (cmd);
+ g_free (quoted_src);
+ g_free (quoted_dir);
+}
+
+static void
+do_install_bin (const gchar *what)
+{
+ gchar *dir = get_user_plugin_dir ();
+ gchar *plugin_dir = get_plugin_dir (dir, what);
+
+ do_install_bin_2 (plugin_dir, what);
+
+ g_free (plugin_dir);
+ g_free (dir);
+}
+
+static void
+do_install_admin_bin (const gchar *what)
+{
+ gchar *dir = get_sys_plugin_dir (FALSE);
+ gchar *plugin_dir = get_plugin_dir (dir, what);
+
+ do_install_bin_2 (dir, what);
+
+ g_free (plugin_dir);
+ g_free (dir);
+}
+
+static void
+do_uninstall (const gchar *dir)
+{
+ gchar *cmd;
+ gchar *quoted_src;
+
+ quoted_src = g_shell_quote (dir);
+
+ cmd = g_strconcat (REMOVE_DIR, " ", quoted_src, NULL);
+ maybe_run (cmd);
+
+ g_free (cmd);
+ g_free (quoted_src);
+}
+
+static void
+do_uninstall_script_2 (const gchar *dir,
+ const gchar *what)
+{
+ gchar *cmd;
+ gchar *quoted_src;
+ gchar *src;
+
+ src = g_strconcat (dir, G_DIR_SEPARATOR_S, what, NULL);
+ quoted_src = g_shell_quote (src);
+
+ cmd = g_strconcat (REMOVE, " ", quoted_src, NULL);
+ maybe_run (cmd);
+
+ g_free (cmd);
+ g_free (quoted_src);
+ g_free (src);
+}
+
+static gchar *
+maybe_append_exe (const gchar *what)
+{
+#ifdef G_OS_WIN32
+ gchar *p = strrchr (what, '.');
+
+ if (p == NULL || g_ascii_strcasecmp (p, ".exe") != 0)
+ return g_strconcat (what, ".exe", NULL);
+#endif
+
+ return g_strdup (what);
+}
+
+static void
+do_uninstall_bin (const gchar *what)
+{
+ gchar *dir = get_user_plugin_dir ();
+ gchar *exe = maybe_append_exe (what);
+ gchar *plugin_dir = get_plugin_dir (dir, what);
+
+ do_uninstall (plugin_dir);
+
+ g_free (plugin_dir);
+ g_free (dir);
+ g_free (exe);
+}
+
+static void
+do_uninstall_admin_bin (const gchar *what)
+{
+ gchar *dir = get_sys_plugin_dir (FALSE);
+ gchar *exe = maybe_append_exe (what);
+ gchar *plugin_dir = get_plugin_dir (dir, what);
+
+ do_uninstall (plugin_dir);
+
+ g_free (plugin_dir);
+ g_free (dir);
+ g_free (exe);
+}
+
+static gchar *
+get_user_script_dir (void)
+{
+ return g_build_filename (gimp_directory (),
+ "scripts",
+ NULL);
+}
+
+static void
+do_install_script (const gchar *what)
+{
+ gchar *dir = get_user_script_dir ();
+
+ do_install_bin_2 (dir, what);
+ g_free (dir);
+}
+
+static gchar *
+get_sys_script_dir (void)
+{
+ gchar *dir;
+ gchar *prefix = get_runtime_prefix (G_DIR_SEPARATOR);
+
+ dir = g_build_filename (prefix, "share", "gimp",
+ GIMP_PLUGIN_VERSION, "scripts",
+ NULL);
+ g_free (prefix);
+
+ return dir;
+}
+
+static void
+do_install_admin_script (const gchar *what)
+{
+ gchar *dir = get_sys_script_dir ();
+
+ do_install_bin_2 (dir, what);
+ g_free (dir);
+}
+
+static void
+do_uninstall_script (const gchar *what)
+{
+ gchar *dir = get_user_script_dir ();
+
+ do_uninstall_script_2 (dir, what);
+ g_free (dir);
+}
+
+static void
+do_uninstall_admin_script (const gchar *what)
+{
+ gchar *dir = get_sys_script_dir ();
+
+ do_uninstall_script_2 (dir, what);
+ g_free (dir);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ gint argi;
+ gint i;
+
+ if (argc == 1)
+ usage (EXIT_SUCCESS);
+
+ /* First scan for flags that affect our behaviour globally, but
+ * are still allowed late on the command line.
+ */
+ argi = 0;
+ while (++argi < argc)
+ {
+ if (strcmp (argv[argi], "-n") == 0 ||
+ strcmp (argv[argi], "--just-print") == 0 ||
+ strcmp (argv[argi], "--dry-run") == 0 ||
+ strcmp (argv[argi], "--recon") == 0)
+ {
+ dry_run = TRUE;
+ }
+ else if (strcmp (argv[argi], "--help") == 0)
+ {
+ usage (EXIT_SUCCESS);
+ }
+ else if (g_str_has_prefix (argv[argi], "--prefix="))
+ {
+ cli_prefix = argv[argi] + strlen ("--prefix=");
+ }
+ else if (g_str_has_prefix (argv[argi], "--exec-prefix="))
+ {
+ cli_exec_prefix = argv[argi] + strlen ("--exec_prefix=");
+ }
+ else if (strcmp (argv[argi], "--msvc-syntax") == 0)
+ {
+#ifdef G_OS_WIN32
+ msvc_syntax = TRUE;
+#else
+ g_printerr ("Ignoring --msvc-syntax\n");
+#endif
+ }
+ }
+
+ find_out_env_flags ();
+
+ /* Second pass, actually do something. */
+ argi = 0;
+ while (++argi < argc)
+ {
+ for (i = 0; i < G_N_ELEMENTS (dirs); i++)
+ {
+ gchar *test = g_strconcat ("--", dirs[i].option, NULL);
+
+ if (strcmp (argv[argi], test) == 0)
+ {
+ g_free (test);
+ break;
+ }
+ else
+ {
+ g_free (test);
+ }
+ }
+
+ if (i < G_N_ELEMENTS (dirs))
+ {
+ gchar *expanded = expand_and_munge (dirs[i].value);
+
+ g_print ("%s\n", expanded);
+ g_free (expanded);
+ }
+ else if (strcmp (argv[argi], "--quiet") == 0 ||
+ strcmp (argv[argi], "--silent") == 0)
+ {
+ silent = TRUE;
+ }
+ else if (strcmp (argv[argi], "--version") == 0)
+ {
+ g_print ("%d.%d.%d\n",
+ GIMP_MAJOR_VERSION, GIMP_MINOR_VERSION, GIMP_MICRO_VERSION);
+ exit (EXIT_SUCCESS);
+ }
+ else if (strcmp (argv[argi], "-n") == 0 ||
+ strcmp (argv[argi], "--just-print") == 0 ||
+ strcmp (argv[argi], "--dry-run") == 0 ||
+ strcmp (argv[argi], "--recon") == 0)
+ ; /* Already handled */
+ else if (strcmp (argv[argi], "--includedir") == 0)
+ do_includedir ();
+ else if (strcmp (argv[argi], "--cflags") == 0)
+ do_cflags ();
+ else if (strcmp (argv[argi], "--cflags-noui") == 0)
+ do_cflags_noui ();
+ else if (strcmp (argv[argi], "--cflags-nogimpui") == 0)
+ do_cflags_nogimpui ();
+ else if (strcmp (argv[argi], "--libs") == 0)
+ do_libs ();
+ else if (strcmp (argv[argi], "--libs-noui") == 0)
+ do_libs_noui ();
+ else if (strcmp (argv[argi], "--libs-nogimpui") == 0)
+ do_libs_nogimpui ();
+ else if (g_str_has_prefix (argv[argi], "--prefix="))
+ ;
+ else if (g_str_has_prefix (argv[argi], "--exec-prefix="))
+ ;
+ else if (strcmp (argv[argi], "--msvc-syntax") == 0)
+ ;
+ else if (strcmp (argv[argi], "--build") == 0)
+ do_build (argv[++argi]);
+ else if (strcmp (argv[argi], "--build-noui") == 0)
+ do_build_noui (argv[++argi]);
+ else if (strcmp (argv[argi], "--build-nogimpui") == 0)
+ do_build_nogimpui (argv[++argi]);
+ else if (strcmp (argv[argi], "--install") == 0)
+ do_install (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-noui") == 0)
+ do_install_noui (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-nogimpui") == 0)
+ do_install_nogimpui (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-admin") == 0)
+ do_install_admin (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-admin-noui") == 0)
+ do_install_admin_noui (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-admin-nogimpui") == 0)
+ do_install_admin_nogimpui (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-bin") == 0)
+ do_install_bin (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-admin-bin") == 0)
+ do_install_admin_bin (argv[++argi]);
+ else if (strcmp (argv[argi], "--uninstall-bin") == 0)
+ do_uninstall_bin (argv[++argi]);
+ else if (strcmp (argv[argi], "--uninstall-admin-bin") == 0)
+ do_uninstall_admin_bin (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-script") == 0)
+ do_install_script (argv[++argi]);
+ else if (strcmp (argv[argi], "--install-admin-script") == 0)
+ do_install_admin_script (argv[++argi]);
+ else if (strcmp (argv[argi], "--uninstall-script") == 0)
+ do_uninstall_script (argv[++argi]);
+ else if (strcmp (argv[argi], "--uninstall-admin-script") == 0)
+ do_uninstall_admin_script (argv[++argi]);
+ else
+ {
+ g_printerr ("Unrecognized switch %s\n", argv[argi]);
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ exit (EXIT_SUCCESS);
+}
+/*
+ * Local Variables:
+ * mode: c
+ * End:
+ */
diff --git a/tools/kernelgen.c b/tools/kernelgen.c
new file mode 100644
index 0000000..e2d8ef0
--- /dev/null
+++ b/tools/kernelgen.c
@@ -0,0 +1,128 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * kernelgen -- Copyright (C) 2000 Sven Neumann <sven@gimp.org>
+ *
+ * Simple hack to create brush subsampling kernels. If you want to
+ * play with it, change some of the #defines at the top and copy
+ * the output to app/paint/gimpbrushcore-kernels.h.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+
+#define STEPS 64
+#define KERNEL_WIDTH 3 /* changing these makes no sense */
+#define KERNEL_HEIGHT 3 /* changing these makes no sense */
+#define KERNEL_SUM 256
+#define SUBSAMPLE 4
+#define THRESHOLD 0.25 /* try to change this one */
+
+#define SQR(x) ((x) * (x))
+
+static void
+create_kernel (double x,
+ double y)
+{
+ double value[KERNEL_WIDTH][KERNEL_HEIGHT];
+ double dist_x;
+ double dist_y;
+ double sum;
+ double w;
+ int i, j;
+
+ memset (value, 0, KERNEL_WIDTH * KERNEL_HEIGHT * sizeof (double));
+ sum = 0.0;
+
+ x += 1.0;
+ y += 1.0;
+
+ for (j = 0; j < STEPS * KERNEL_HEIGHT; j++)
+ {
+ dist_y = y - (((double)j + 0.5) / (double)STEPS);
+
+ for (i = 0; i < STEPS * KERNEL_WIDTH; i++)
+ {
+ dist_x = x - (((double) i + 0.5) / (double) STEPS);
+
+ /* I've tried to use a gauss function here instead of a
+ threshold, but the result was not that impressive. */
+ w = (SQR (dist_x) + SQR (dist_y)) < THRESHOLD ? 1.0 : 0.0;
+
+ value[i / STEPS][j / STEPS] += w;
+ sum += w;
+ }
+ }
+
+ for (j = 0; j < KERNEL_HEIGHT; j++)
+ {
+ for (i = 0; i < KERNEL_WIDTH; i++)
+ {
+ w = (double) KERNEL_SUM * value[i][j] / sum;
+ printf (" %3d,", (int) (w + 0.5));
+ }
+ }
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ int i, j;
+ double x, y;
+
+ printf ("/* gimpbrushcore-kernels.h\n"
+ " *\n"
+ " * This file was generated using kernelgen as found in the tools dir.\n");
+ printf (" * (threshold = %g)\n", THRESHOLD);
+ printf (" */\n\n");
+ printf ("#ifndef __GIMP_BRUSH_CORE_KERNELS_H__\n");
+ printf ("#define __GIMP_BRUSH_CORE_KERNELS_H__\n\n");
+ printf ("#define KERNEL_WIDTH %d\n", KERNEL_WIDTH);
+ printf ("#define KERNEL_HEIGHT %d\n", KERNEL_HEIGHT);
+ printf ("#define KERNEL_SUBSAMPLE %d\n", SUBSAMPLE);
+ printf ("#define KERNEL_SUM %d\n", KERNEL_SUM);
+ printf ("\n\n");
+ printf ("/* Brush pixel subsampling kernels */\n");
+ printf ("static const int subsample[%d][%d][%d] =\n{\n",
+ SUBSAMPLE + 1, SUBSAMPLE + 1, KERNEL_WIDTH * KERNEL_HEIGHT);
+
+ for (j = 0; j <= SUBSAMPLE; j++)
+ {
+ y = (double) j / (double) SUBSAMPLE;
+
+ printf (" {\n");
+
+ for (i = 0; i <= SUBSAMPLE; i++)
+ {
+ x = (double) i / (double) SUBSAMPLE;
+
+ printf (" {");
+ create_kernel (x, y);
+ printf (" }%s", i < SUBSAMPLE ? ",\n" : "\n");
+ }
+
+ printf (" }%s", j < SUBSAMPLE ? ",\n" : "\n");
+ }
+
+ printf ("};\n\n");
+
+ printf ("#endif /* __GIMP_BRUSH_CORE_KERNELS_H__ */\n");
+
+ return 0;
+}
diff --git a/tools/mnemonic-clashes b/tools/mnemonic-clashes
new file mode 100755
index 0000000..28a1efc
--- /dev/null
+++ b/tools/mnemonic-clashes
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+srcdir="`dirname \"$0\"`/.."
+
+find_actions () {
+ for f in "$srcdir"/app/actions/*-actions.c; do
+ < $f \
+ tr '\n' ' ' | \
+ grep -Po '{ *".*?".*?(NC_\("/*?" *, *)?".*?".*?}' | \
+ perl -pe 's/{ *(".*?").*?(?:NC_\(".*?" *, *)?(".*?").*?}/\1: \2,/g'
+ done
+}
+
+python3 - $@ <<END
+from sys import argv
+from itertools import chain
+from glob import glob
+from xml.etree.ElementTree import ElementTree
+
+actions = {`find_actions`}
+
+found_clashes = False
+
+for file in glob ("$srcdir/menus/*.xml*"):
+ tree = ElementTree (file = file)
+
+ parents = {c: p for p in tree.iter () for c in p}
+
+ def menu_path (menu):
+ path = ""
+
+ while menu:
+ if menu.tag == "menu":
+ if path:
+ path = " -> " + path
+
+ path = menu.get ("name") + path
+
+ menu = parents.get (menu)
+
+ return path
+
+ for menu in chain (tree.iter ("menubar-and-popup"),
+ tree.iter ("menu"),
+ tree.iter ("popup")):
+ if len (argv) > 1 and menu.tag != argv[1]:
+ continue
+
+ mnemonics = {}
+
+ found_clashes_in_menu = False
+
+ for element in menu:
+ action = element.get ("action")
+
+ if action in actions:
+ label = actions[action]
+
+ if "_" in label:
+ mnemonic = label[label.find ("_") + 1].upper ()
+
+ if mnemonic not in mnemonics:
+ mnemonics[mnemonic] = []
+
+ mnemonics[mnemonic] += [action]
+
+ mnemonic_list = list (mnemonics.keys ())
+ mnemonic_list.sort ()
+
+ for mnemonic in mnemonic_list:
+ action_list = mnemonics[mnemonic]
+
+ if len (action_list) > 1:
+ if found_clashes:
+ print ()
+
+ if not found_clashes_in_menu:
+
+ if menu.tag == "menu":
+ print ("In %s (%s):" % (menu.get ("action"),
+ menu_path (menu)))
+ elif menu.tag == "popup":
+ print ("In %s:" % menu.get ("action"))
+ else:
+ print ("In top-level menu bar:")
+
+ found_clashes = True
+ found_clashes_in_menu = True
+
+ print (" Mnemonic clash for '%s':" % mnemonic)
+
+ for action in action_list:
+ print (" %s: %s" % (action, actions[action]))
+
+exit (found_clashes)
+END
diff --git a/tools/performance-log-close-tags.py b/tools/performance-log-close-tags.py
new file mode 100755
index 0000000..4bbf80c
--- /dev/null
+++ b/tools/performance-log-close-tags.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-close-tags.py -- Closes open tags in GIMP performance logs
+Copyright (C) 2020 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-close-tags.py < infile > outfile
+"""
+
+from xml.etree import ElementTree
+import sys
+
+class OpenTagsTracker:
+ open_tags = []
+
+ def start (self, tag, attrib):
+ self.open_tags.append (tag)
+
+ def end (self, tag):
+ self.open_tags.pop ()
+
+# Read performance log from STDIN
+text = sys.stdin.buffer.read ()
+
+# Write performance log to STDOUT
+sys.stdout.buffer.write (text)
+
+# Track open tags
+tracker = OpenTagsTracker ()
+
+ElementTree.XMLParser (target = tracker).feed (text)
+
+# Close remaining open tags
+for tag in reversed (tracker.open_tags):
+ print ("</%s>" % tag)
diff --git a/tools/performance-log-coalesce.py b/tools/performance-log-coalesce.py
new file mode 100755
index 0000000..fbbaca3
--- /dev/null
+++ b/tools/performance-log-coalesce.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-coalesce.py -- Coalesce GIMP performance log symbols, by
+ filling-in missing base addresses
+Copyright (C) 2018 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-coalesce.py < infile > outfile
+"""
+
+from xml.etree import ElementTree
+import sys
+
+empty_element = ElementTree.Element ("")
+
+# Read performance log from STDIN
+log = ElementTree.fromstring (sys.stdin.buffer.read ())
+
+address_map = log.find ("address-map")
+
+if address_map:
+ addresses = []
+
+ # Create base addresses dictionary
+ base_addresses = {}
+
+ for address in address_map.iterfind ("address"):
+ symbol = address.find ("symbol").text
+ source = address.find ("source").text
+ base = address.find ("base").text
+
+ if symbol and source and not base:
+ key = (symbol, source)
+ value = int (address.get ("value"), 0)
+
+ base_addresses[key] = min (value, base_addresses.get (key, value))
+
+ addresses.append (address)
+
+ # Fill-in missing base addresses
+ for address in addresses:
+ symbol = address.find ("symbol").text
+ source = address.find ("source").text
+
+ address.find ("base").text = hex (base_addresses[(symbol, source)])
+
+# Write performance log to STDOUT
+sys.stdout.buffer.write (ElementTree.tostring (log))
diff --git a/tools/performance-log-deduce.py b/tools/performance-log-deduce.py
new file mode 100755
index 0000000..1c6e059
--- /dev/null
+++ b/tools/performance-log-deduce.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-deduce.py -- Deduce GIMP performance log thread state
+Copyright (C) 2018 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-deduce.py < infile > outfile
+"""
+
+DEDUCE_MIN_N_OCCURRENCES = 10
+DEDUCE_MIN_PERCENTAGE = 0.75
+
+from xml.etree import ElementTree
+import sys
+
+empty_element = ElementTree.Element ("")
+
+# Read performance log from STDIN
+log = ElementTree.fromstring (sys.stdin.buffer.read ())
+
+# Construct state histogram
+address_states = {}
+
+for sample in (log.find ("samples") or empty_element).iterfind ("sample"):
+ threads = (sample.find ("backtrace") or empty_element).iterfind ("thread")
+
+ for thread in threads:
+ running = int (thread.get ("running"))
+
+ frame = thread.find ("frame")
+
+ if frame is not None:
+ address = frame.get ("address")
+
+ states = address_states.setdefault (address, [0, 0])
+
+ states[running] += 1
+
+# Find maximal states
+for address, states in list (address_states.items ()):
+ n = sum (states)
+
+ if n >= DEDUCE_MIN_N_OCCURRENCES:
+ state = 0
+ m = states[0]
+
+ for i in range (1, len (states)):
+ if states[i] > m:
+ state = i
+ m = states[i]
+
+ percentage = m / n
+
+ if percentage >= DEDUCE_MIN_PERCENTAGE:
+ address_states[address] = state
+ else:
+ del address_states[address]
+ else:
+ del address_states[address]
+
+# Replace thread states
+for sample in (log.find ("samples") or empty_element).iterfind ("sample"):
+ threads = (sample.find ("backtrace") or empty_element).iterfind ("thread")
+
+ for thread in threads:
+ frame = thread.find ("frame")
+
+ if frame is not None:
+ address = frame.get ("address")
+
+ running = address_states.get (address, None)
+
+ if running is not None:
+ thread.set ("running", str (running))
+
+# Write performance log to STDOUT
+sys.stdout.buffer.write (ElementTree.tostring (log))
diff --git a/tools/performance-log-expand.py b/tools/performance-log-expand.py
new file mode 100755
index 0000000..bc04d83
--- /dev/null
+++ b/tools/performance-log-expand.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-expand.py -- Delta-decodes GIMP performance logs
+Copyright (C) 2018 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-expand.py < infile > outfile
+"""
+
+from xml.etree import ElementTree
+import sys
+
+empty_element = ElementTree.Element ("")
+
+# Read performance log from STDIN
+log = ElementTree.fromstring (sys.stdin.buffer.read ())
+
+try:
+ has_backtrace = bool (int (log.find ("params").find ("backtrace").text))
+except:
+ has_backtrace = False
+
+def expand_simple (element, last_values):
+ last_values.update ({value.tag: value.text for value in element})
+
+ for value in list (element):
+ element.remove (value)
+
+ for tag, text in last_values.items ():
+ value = ElementTree.SubElement (element, tag)
+ value.text = text
+ value.tail = "\n"
+
+# Expand samples
+last_vars = {}
+last_backtrace = {}
+
+for sample in (log.find ("samples") or empty_element).iterfind ("sample"):
+ # Expand variables
+ vars = sample.find ("vars") or \
+ ElementTree.SubElement (sample, "vars")
+
+ expand_simple (vars, last_vars)
+
+ # Expand backtrace
+ if has_backtrace:
+ backtrace = sample.find ("backtrace") or \
+ ElementTree.SubElement (sample, "backtrace")
+
+ for thread in backtrace:
+ id = thread.get ("id")
+ head = thread.get ("head")
+ tail = thread.get ("tail")
+ attrib = dict (thread.attrib)
+ frames = list (thread)
+
+ last_thread = last_backtrace.setdefault (id, [{}, []])
+ last_frames = last_thread[1]
+
+ if head:
+ frames = last_frames[:int (head)] + frames
+
+ del attrib["head"]
+
+ if tail:
+ frames = frames + last_frames[-int (tail):]
+
+ del attrib["tail"]
+
+ last_thread[0] = attrib
+ last_thread[1] = frames
+
+ if not frames and thread.text is None:
+ del last_backtrace[id]
+
+ for thread in list (backtrace):
+ backtrace.remove (thread)
+
+ for id, (attrib, frames) in last_backtrace.items ():
+ thread = ElementTree.SubElement (backtrace, "thread", attrib)
+ thread.text = "\n"
+ thread.tail = "\n"
+
+ thread.extend (frames)
+
+# Expand address map
+last_address = {}
+
+for address in (log.find ("address-map") or empty_element).iterfind ("address"):
+ expand_simple (address, last_address)
+
+# Write performance log to STDOUT
+sys.stdout.buffer.write (ElementTree.tostring (log))
diff --git a/tools/performance-log-progressive-coalesce.py b/tools/performance-log-progressive-coalesce.py
new file mode 100755
index 0000000..1942e5f
--- /dev/null
+++ b/tools/performance-log-progressive-coalesce.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-progressive-coalesce.py -- Coalesce address-maps in progressive
+GIMP performance logs
+Copyright (C) 2020 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-progressive-coalesce.py < infile > outfile
+"""
+
+from xml.etree import ElementTree
+import sys
+
+empty_element = ElementTree.Element ("")
+
+# Read performance log from STDIN
+log = ElementTree.fromstring (sys.stdin.buffer.read ())
+
+samples = log.find ("samples") or empty_element
+
+address_map = log.find ("address-map")
+
+if not address_map:
+ address_map = ElementTree.Element ("address-map")
+
+# Coalesce partial address maps
+for partial_address_map in samples.iterfind ("address-map"):
+ for element in partial_address_map:
+ address_map.append (element)
+
+# Remove partial address maps
+for partial_address_map in samples.iterfind ("address-map"):
+ samples.remove (partial_address_map)
+
+# Add global address map
+if not log.find ("address-map") and len (address_map) > 0:
+ log.append (address_map)
+
+# Write performance log to STDOUT
+sys.stdout.buffer.write (ElementTree.tostring (log))
diff --git a/tools/performance-log-resolve.py b/tools/performance-log-resolve.py
new file mode 100755
index 0000000..272f0eb
--- /dev/null
+++ b/tools/performance-log-resolve.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-resolve.py -- Resolve GIMP performance log backtrace symbols
+Copyright (C) 2018 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-resolve.py < infile > outfile
+"""
+
+from xml.etree import ElementTree
+import sys
+
+empty_element = ElementTree.Element ("")
+
+# Read performance log from STDIN
+log = ElementTree.fromstring (sys.stdin.buffer.read ())
+
+address_map = log.find ("address-map")
+
+if address_map:
+ # Create address dictionary
+ addresses = {}
+
+ for address in address_map.iterfind ("address"):
+ addresses[address.get ("value")] = list (address)
+
+ # Resolve addresses in backtraces
+ for sample in (log.find ("samples") or empty_element).iterfind ("sample"):
+ for thread in sample.find ("backtrace") or ():
+ for frame in thread:
+ address = addresses.get (frame.get ("address"))
+
+ if address:
+ frame.text = "\n"
+ frame.extend (address)
+
+ # Remove address map
+ log.remove (address_map)
+
+# Write performance log to STDOUT
+sys.stdout.buffer.write (ElementTree.tostring (log))
diff --git a/tools/performance-log-viewer b/tools/performance-log-viewer
new file mode 100755
index 0000000..2ef7f38
--- /dev/null
+++ b/tools/performance-log-viewer
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+# performance-log-viewer -- GIMP performance log viewer driver
+# Copyright (C) 2018 Ell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+#
+# Usage: performance-log-viewer FILE
+
+if [ "$#" -ne 1 ]; then
+ echo "Usage: $0 FILE"
+
+ exit 1
+fi
+
+tools_dir="$(dirname "$(command -v -- "$0")")"
+file="$1"
+
+< "$file" || exit 1
+
+< "$file" \
+"$tools_dir/performance-log-close-tags.py" | \
+"$tools_dir/performance-log-progressive-coalesce.py" | \
+"$tools_dir/performance-log-expand.py" | \
+"$tools_dir/performance-log-coalesce.py" | \
+"$tools_dir/performance-log-deduce.py" | \
+"$tools_dir/performance-log-viewer.py"
diff --git a/tools/performance-log-viewer.py b/tools/performance-log-viewer.py
new file mode 100755
index 0000000..8e87eb8
--- /dev/null
+++ b/tools/performance-log-viewer.py
@@ -0,0 +1,3662 @@
+#!/usr/bin/env python3
+
+"""
+performance-log-viewer.py -- GIMP performance log viewer
+Copyright (C) 2018 Ell
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+Usage: performance-log-viewer.py < infile
+"""
+
+import builtins, sys, os, math, statistics, bisect, functools, enum, re, \
+ subprocess
+
+from collections import namedtuple
+from xml.etree import ElementTree
+
+import gi
+gi.require_version ("Gdk", "3.0")
+gi.require_version ("Gtk", "3.0")
+from gi.repository import GLib, GObject, Gio, Gdk, Gtk, Pango
+
+def compose (head = None, *tail):
+ return (
+ lambda *args, **kwargs: head (compose (*tail) (*args, **kwargs))
+ ) if tail else head or (lambda x: x)
+
+def div (x, y):
+ return x / y if y else \
+ +math.inf if x > 0 else \
+ -math.inf if x < 0 else \
+ None
+
+def format_float (x):
+ return "%g" % (round (100 * x) / 100)
+
+def format_percentage (x, digits = 0):
+ return "%%.%df%%%%" % digits % (100 * x)
+
+def format_size (size):
+ return GLib.format_size_full (size, GLib.FormatSizeFlags.IEC_UNITS)
+
+def format_duration (t):
+ return "%02d:%02d:%02d.%02d" % (int (t / 3600),
+ int (t / 60) % 60,
+ int (t % 60),
+ round (100 * t) % 100)
+
+def format_color (color):
+ return "#%02x%02x%02x" % tuple (
+ map (lambda x: min (max (round (255 * x), 0), 255), color)
+ )
+
+def is_bright_color (color):
+ return max (tuple (color)[0:3]) > 0.5
+
+def blend_colors (color1, color2, amount):
+ color1 = tuple (color1)
+ color2 = tuple (color2)
+
+ a1 = color1[-1]
+ a2 = color2[-1]
+
+ a = (1 - amount) * a1 + amount * a2
+
+ return tuple (a and ((1 - amount) * a1 * c1 + amount * a2 * c2) / a
+ for c1, c2 in zip (color1[:-1], color2[:-1])) + (a,)
+
+def rounded_rectangle (cr, x, y, width, height, radius):
+ radius = min (radius, width / 2, height / 2)
+
+ cr.arc (x + radius, y + radius, radius, -math.pi, -math.pi / 2)
+ cr.rel_line_to (width - 2 * radius, 0)
+
+ cr.arc (x + width - radius, y + radius, radius, -math.pi / 2, 0)
+ cr.rel_line_to (0, height - 2 * radius)
+
+ cr.arc (x + width - radius, y + height - radius, radius, 0, math.pi / 2)
+ cr.rel_line_to (-(width - 2 * radius), 0)
+
+ cr.arc (x + radius, y + height - radius, radius, math.pi / 2, math.pi)
+ cr.rel_line_to (0, -(height - 2 * radius))
+
+ cr.close_path ()
+
+def get_basename (path):
+ match = re.fullmatch (".*[\\\\/](.+?)[\\\\/]?", path)
+
+ return match[1] if match else path
+
+search_path = list (filter (
+ bool,
+ os.environ.get ("PERFORMANCE_LOG_VIEWER_PATH", ".").split (":")
+))
+
+editor_command = os.environ.get ("PERFORMANCE_LOG_VIEWER_EDITOR",
+ "xdg-open {file}")
+editor_command += " &"
+
+def find_file (filename):
+ def lookup (filename):
+ filename = re.sub ("[\\\\/]", GLib.DIR_SEPARATOR_S, filename)
+
+ if GLib.path_is_absolute (filename):
+ file = Gio.File.new_for_path (filename)
+
+ if file.query_exists ():
+ return file
+
+ for path in search_path:
+ rest = filename
+
+ while rest:
+ file = Gio.File.new_for_path (GLib.build_filenamev ((path, rest)))
+
+ if file.query_exists ():
+ return file
+
+ sep = rest.find (GLib.DIR_SEPARATOR_S)
+
+ rest = rest[sep + 1:] if sep >= 0 else ""
+
+ return None
+
+ if filename not in find_file.cache:
+ find_file.cache[filename] = lookup (filename)
+
+ return find_file.cache[filename]
+
+find_file.cache = {}
+
+def run_editor (file, line):
+ subprocess.call (editor_command.format (file = "\"%s\"" % file.get_path (),
+ line = line),
+ shell = True)
+
+VariableType = namedtuple ("VariableType",
+ ("parse", "format", "format_numeric"))
+
+var_types = {
+ "boolean": VariableType (
+ parse = int,
+ format = compose (str, bool),
+ format_numeric = format_float
+ ),
+
+ "integer": VariableType (
+ parse = int,
+ format = format_float,
+ format_numeric = None
+ ),
+
+ "size": VariableType (
+ parse = int,
+ format = format_size,
+ format_numeric = None
+ ),
+
+ "size-ratio": VariableType (
+ parse = lambda x: div (*map (int, x.split ("/"))),
+ format = format_percentage,
+ format_numeric = None
+ ),
+
+ "int-ratio": VariableType (
+ parse = lambda x: div (*map (int, x.split (":"))),
+ format = lambda x: "%g:%g" % (
+ (0, 0) if math.isnan (x) else
+ (1, 0) if x == math.inf else
+ (-1, 0) if x == -math.inf else
+ (0, 1) if x == 0 else
+ (round (100 * x) / 100, 1) if abs (x) > 1 else
+ (1, round (100 / x) / 100)
+ ),
+ format_numeric = None
+ ),
+
+ "percentage": VariableType (
+ parse = float,
+ format = format_percentage,
+ format_numeric = None
+ ),
+
+ "duration": VariableType (
+ parse = float,
+ format = format_duration,
+ format_numeric = None
+ ),
+
+ "rate-of-change": VariableType (
+ parse = float,
+ format = lambda x: "%s/s" % format_size (x),
+ format_numeric = None
+ )
+}
+var_types = {
+ type: VariableType (
+ parse = parse,
+ format = lambda x, f = format: \
+ f (x) if x is not None else "N/A",
+ format_numeric = lambda x, f = format_numeric or format:
+ f (x) if x is not None else "N/A"
+ )
+ for type, (parse, format, format_numeric) in var_types.items ()
+}
+
+# Read performance log from STDIN
+log = ElementTree.fromstring (sys.stdin.buffer.read ())
+
+Variable = namedtuple ("Variable", ("type", "desc", "color"))
+Value = namedtuple ("Value", ("value", "raw"))
+
+var_colors = [
+ (0.8, 0.4, 0.4),
+ (0.8, 0.6, 0.4),
+ (0.4, 0.8, 0.4),
+ (0.8, 0.8, 0.4),
+ (0.4, 0.4, 0.8),
+ (0.4, 0.8, 0.8),
+ (0.8, 0.4, 0.8),
+ (0.8, 0.8, 0.8)
+]
+
+var_defs = {}
+
+for var in log.find ("var-defs"):
+ color = var_colors[len (var_defs) % len (var_colors)]
+
+ var_defs[var.get ("name")] = Variable (var.get ("type"),
+ var.get ("desc"),
+ color)
+
+AddressInfo = namedtuple ("AddressInfo", ("id",
+ "name",
+ "object",
+ "symbol",
+ "offset",
+ "source",
+ "line"))
+
+address_map = {}
+
+if log.find ("address-map"):
+ for address in log.find ("address-map").iterfind ("address"):
+ value = int (address.get ("value"), 0)
+
+ object = address.find ("object").text
+ symbol = address.find ("symbol").text
+ base = address.find ("base").text
+ source = address.find ("source").text
+ line = address.find ("line").text
+
+ address_map[value] = AddressInfo (
+ id = int (base, 0) if base else value,
+ name = symbol or base or hex (value),
+ object = object,
+ symbol = symbol,
+ offset = value - int (base, 0) if base else None,
+ source = source,
+ line = int (line) if line else None
+ )
+
+class ThreadState (enum.Enum):
+ SUSPENDED = enum.auto ()
+ RUNNING = enum.auto ()
+
+ def __str__ (self):
+ return {
+ ThreadState.SUSPENDED: "S",
+ ThreadState.RUNNING: "R"
+ }[self]
+
+Thread = namedtuple ("Thread", ("id", "name", "state", "frames"))
+Frame = namedtuple ("Frame", ("id", "address", "info"))
+
+Sample = namedtuple ("Sample", ("t", "vars", "markers", "backtrace"))
+Marker = namedtuple ("Marker", ("id", "t", "description"))
+
+samples = []
+markers = []
+last_marker = 0
+
+for element in log.find ("samples"):
+ if element.tag == "sample":
+ sample = Sample (
+ t = int (element.get ("t")),
+ vars = {},
+ markers = markers[last_marker:],
+ backtrace = []
+ )
+
+ for var in element.find ("vars"):
+ sample.vars[var.tag] = Value (
+ value = var_types[var_defs[var.tag].type].parse (var.text) \
+ if var.text else None,
+ raw = var.text.strip () if var.text else None
+ )
+
+ if element.find ("backtrace"):
+ for thread in element.find ("backtrace").iterfind ("thread"):
+ id = thread.get ("id")
+ name = thread.get ("name")
+ running = thread.get ("running")
+
+ t = Thread (
+ id = int (id),
+ name = name,
+ state = ThreadState.RUNNING if running and int (running) \
+ else ThreadState.SUSPENDED,
+ frames = []
+ )
+
+ for frame in thread.iterfind ("frame"):
+ address = int (frame.get ("address"), 0)
+
+ info = address_map.get (address, None)
+
+ if not info:
+ info = AddressInfo (
+ id = address,
+ name = hex (address),
+ object = None,
+ symbol = None,
+ offset = None,
+ source = None,
+ line = None
+ )
+
+ t.frames.append (Frame (
+ id = len (t.frames),
+ address = address,
+ info = info
+ ))
+
+ sample.backtrace.append (t)
+
+ samples.append (sample)
+
+ last_marker = len (markers)
+ elif element.tag == "marker":
+ marker = Marker (
+ id = int (element.get ("id")),
+ t = int (element.get ("t")),
+ description = element.text.strip () if element.text else None
+ )
+
+ markers.append (marker)
+
+if samples:
+ samples[-1].markers.extend (markers[last_marker:])
+
+DELTA_SAME = __builtins__.object ()
+
+def delta_encode (dest, src):
+ if type (dest) == type (src):
+ if dest == src:
+ return DELTA_SAME
+ elif type (dest) == tuple:
+ return tuple (delta_encode (d, s) for d, s in zip (dest, src)) + \
+ dest[len (src):]
+
+ return dest
+
+def delta_decode (dest, src):
+ if dest == DELTA_SAME:
+ return src
+ elif type (dest) == type (src):
+ if type (dest) == tuple:
+ return tuple (delta_decode (d, s) for d, s in zip (dest, src)) + \
+ dest[len (src):]
+
+ return dest
+
+class History (GObject.GObject):
+ Source = namedtuple ("HistorySource", ("get", "set"))
+
+ def __init__ (self):
+ GObject.GObject.__init__ (self)
+
+ self.sources = []
+
+ self.state = None
+
+ self.undo_stack = []
+ self.redo_stack = []
+
+ self.blocked = 0
+ self.n_groups = 0
+ self.pending_record = False
+
+ @GObject.Property (type = bool, default = False)
+ def can_undo (self):
+ return bool (self.undo_stack)
+
+ @GObject.Property (type = bool, default = False)
+ def can_redo (self):
+ return bool (self.redo_stack)
+
+ def add_source (self, get, set):
+ self.sources.append (self.Source (get, set))
+
+ def block (self):
+ self.blocked += 1
+
+ def unblock (self):
+ self.blocked -= 1
+
+ def is_blocked (self):
+ return self.blocked > 0
+
+ def start_group (self):
+ self.n_groups += 1
+
+ def end_group (self):
+ self.n_groups -= 1
+
+ if self.n_groups == 0 and self.pending_record:
+ self.record ()
+
+ def record (self):
+ if self.is_blocked ():
+ return
+
+ if self.n_groups == 0:
+ state = tuple (source.get () for source in self.sources)
+
+ if self.state is None:
+ self.state = state
+ else:
+ self.pending_record = False
+
+ delta = delta_encode (self.state, state)
+
+ if delta == DELTA_SAME:
+ return
+
+ self.undo_stack.append (delta_encode (self.state, state))
+ self.redo_stack = []
+
+ self.state = state
+
+ self.notify ("can-undo")
+ self.notify ("can-redo")
+ else:
+ self.pending_record = True
+
+ def update (self):
+ if self.is_blocked ():
+ return
+
+ if self.n_groups == 0:
+ state = tuple (source.get () for source in self.sources)
+
+ for stack in self.undo_stack, self.redo_stack:
+ if stack:
+ stack[-1] = delta_encode (delta_decode (stack[-1],
+ self.state),
+ state)
+
+ self.state = state
+ else:
+ self.pending_record = True
+
+ def move (self, src, dest):
+ self.block ()
+
+ state = src.pop ()
+
+ for source, substate, prev_substate in \
+ zip (self.sources, self.state, state):
+ if prev_substate != DELTA_SAME:
+ source.set (delta_decode (prev_substate, substate))
+
+ state = delta_decode (state, self.state)
+
+ dest.append (delta_encode (self.state, state))
+
+ self.state = state
+
+ self.notify ("can-undo")
+ self.notify ("can-redo")
+
+ self.unblock ()
+
+ def undo (self):
+ self.move (self.undo_stack, self.redo_stack)
+
+ def redo (self):
+ self.move (self.redo_stack, self.undo_stack)
+
+history = History ()
+
+class SelectionOp (enum.Enum):
+ REPLACE = enum.auto ()
+ ADD = enum.auto ()
+ SUBTRACT = enum.auto ()
+ INTERSECT = enum.auto ()
+ XOR = enum.auto ()
+
+class Selection (GObject.GObject):
+ __gsignals__ = {
+ "changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
+ "change-complete": (GObject.SignalFlags.RUN_FIRST, None, ()),
+ "highlight-changed": (GObject.SignalFlags.RUN_FIRST, None, ())
+ }
+
+ def __init__ (self, iter = ()):
+ GObject.GObject.__init__ (self)
+
+ self.selection = set (iter)
+ self.highlight = None
+ self.cursor = None
+ self.cursor_dir = 0
+
+ self.pending_change_completion = False
+
+ def __eq__ (self, other):
+ return type (self) == type (other) and \
+ self.selection == other.selection and \
+ self.cursor == other.cursor and \
+ self.cursor_dir == other.cursor_dir
+
+ def __str__ (self):
+ n_sel = len (self.selection)
+
+ if n_sel == 0 or n_sel == len (samples):
+ return "All Samples"
+ elif n_sel == 1:
+ i, = self.selection
+
+ return "Sample %d" % i
+ else:
+ sel = list (self.selection)
+
+ sel.sort ()
+
+ if all (sel[i] + 1 == sel[i + 1] for i in range (n_sel - 1)):
+ return "Samples %d–%d" % (sel[0], sel[-1])
+ else:
+ return "%d Samples" % n_sel
+
+ def copy (self):
+ selection = Selection ()
+
+ selection.highlight = self.highlight
+ selection.cursor = self.cursor
+ selection.cursor_dir = self.cursor_dir
+ selection.selection = self.selection.copy ()
+
+ return selection
+
+ def get_effective_selection (self):
+ if self.selection:
+ return self.selection
+ else:
+ return set (range (len (samples)))
+
+ def select (self, selection, op = SelectionOp.REPLACE):
+ if op == SelectionOp.REPLACE:
+ self.selection = selection.copy ()
+ elif op == SelectionOp.ADD:
+ self.selection |= selection
+ elif op == SelectionOp.SUBTRACT:
+ self.selection -= selection
+ elif op == SelectionOp.INTERSECT:
+ self.selection &= selection
+ elif op == SelectionOp.XOR:
+ self.selection.symmetric_difference_update (selection)
+
+ if len (self.selection) == 1:
+ (self.cursor,) = self.selection
+ else:
+ self.cursor = None
+
+ self.cursor_dir = 0
+
+ self.pending_change_completion = True
+
+ self.emit ("changed")
+
+ def select_range (self, first, last, op = SelectionOp.REPLACE):
+ if first > last:
+ temp = first
+ first = last
+ last = temp
+
+ first = max (first, 0)
+ last = min (last, len (samples) - 1)
+
+ if first <= last:
+ self.select (set (range (first, last + 1)), op)
+ else:
+ self.select (set (), op)
+
+ def clear (self):
+ self.select (set ())
+
+ def invert (self):
+ self.select_range (0, len (samples), SelectionOp.XOR)
+
+ def change_complete (self):
+ if self.pending_change_completion:
+ self.pending_change_completion = False
+
+ history.start_group ()
+
+ history.record ()
+
+ self.emit ("change-complete")
+
+ history.end_group ()
+
+ def set_highlight (self, highlight):
+ self.highlight = highlight
+
+ self.emit ("highlight-changed")
+
+ def source_get (self):
+ return self.copy ()
+
+ def source_set (self, selection):
+ self.cursor = selection.cursor
+ self.cursor_dir = selection.cursor_dir
+ self.selection = selection.selection.copy ()
+
+ self.emit ("changed")
+ self.emit ("change-complete")
+
+ def add_history_source (self):
+ history.add_source (self.source_get, self.source_set)
+
+selection = Selection ()
+selection.add_history_source ()
+
+class FindSamplesPopover (Gtk.Popover):
+ def __init__ (self, *args, **kwargs):
+ Gtk.Popover.__init__ (self, *args, **kwargs)
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL,
+ border_width = 20,
+ spacing = 8)
+ self.add (vbox)
+ vbox.show ()
+
+ entry = Gtk.Entry (width_chars = 40,
+ placeholder_text = "Python expression")
+ self.entry = entry
+ vbox.pack_start (entry, False, False, 0)
+ entry.show ()
+
+ entry.connect ("activate", self.find_samples)
+
+ entry.get_buffer ().connect (
+ "notify::text",
+ lambda *args: self.entry.get_style_context ().remove_class ("error")
+ )
+
+ frame = Gtk.Frame (label = "Selection",
+ shadow_type = Gtk.ShadowType.NONE)
+ vbox.pack_start (frame, False, False, 8)
+ frame.get_label_widget ().get_style_context ().add_class ("dim-label")
+ frame.show ()
+
+ vbox2 = Gtk.Box (orientation = Gtk.Orientation.VERTICAL,
+ border_width = 8,
+ spacing = 8)
+ frame.add (vbox2)
+ vbox2.show ()
+
+ self.radios = []
+
+ radio = Gtk.RadioButton.new_with_mnemonic (None, "_Replace")
+ self.radios.append ((radio, SelectionOp.REPLACE))
+ vbox2.pack_start (radio, False, False, 0)
+ radio.show ()
+
+ radio = Gtk.RadioButton.new_with_mnemonic_from_widget (radio, "_Add")
+ self.radios.append ((radio, SelectionOp.ADD))
+ vbox2.pack_start (radio, False, False, 0)
+ radio.show ()
+
+ radio = Gtk.RadioButton.new_with_mnemonic_from_widget (radio, "_Subtract")
+ self.radios.append ((radio, SelectionOp.SUBTRACT))
+ vbox2.pack_start (radio, False, False, 0)
+ radio.show ()
+
+ radio = Gtk.RadioButton.new_with_mnemonic_from_widget (radio, "_Intersect")
+ self.radios.append ((radio, SelectionOp.INTERSECT))
+ vbox2.pack_start (radio, False, False, 0)
+ radio.show ()
+
+ button = Gtk.Button.new_with_mnemonic ("_Find")
+ vbox.pack_start (button, False, False, 0)
+ button.set_halign (Gtk.Align.CENTER)
+ button.show ()
+
+ button.connect ("clicked", self.find_samples)
+
+ def do_hide (self):
+ self.entry.set_text ("")
+ self.entry.get_style_context ().remove_class ("error")
+
+ Gtk.Popover.do_hide (self)
+
+ def find_samples (self, *args):
+ def var_name (var):
+ return var.replace ("-", "_")
+
+ try:
+ f = eval ("lambda thread, function, %s: %s" % (
+ ", ".join (map (var_name, var_defs)),
+ self.entry.get_text ()))
+ except:
+ self.entry.get_style_context ().add_class ("error")
+
+ return
+
+ sel = set ()
+
+ for i in range (len (samples)):
+ try:
+ def match_thread (thread, id, state = None):
+ return (id is None or \
+ (type (id) == int and \
+ id == thread.id) or \
+ (type (id) == str and \
+ thread.name and \
+ re.fullmatch (id, thread.name))) and \
+ (state is None or \
+ re.fullmatch (state, str (thread.state)))
+
+ def thread (id, state = None):
+ return any (match_thread (thread, id, state)
+ for thread in samples[i].backtrace or [])
+
+ def function (name, id = None, state = None):
+ for thread in samples[i].backtrace or []:
+ if match_thread (thread, id, state):
+ for frame in thread.frames:
+ if re.fullmatch (name, frame.info.name):
+ return True
+
+ return False
+
+ if f (thread, function, **{
+ var_name (var): value.value
+ for var, value in samples[i].vars.items ()
+ }):
+ sel.add (i)
+ except:
+ pass
+
+ op = [op for radio, op in self.radios if radio.get_active ()][0]
+
+ selection.select (sel, op)
+
+ selection.change_complete ()
+
+ self.hide ()
+
+class CellRendererColorToggle (Gtk.CellRendererToggle):
+ padding = 3
+
+ color = GObject.Property (type = Gdk.RGBA, default = Gdk.RGBA (0, 0, 0))
+
+ def do_render (self, cr, widget, background_area, cell_area, flags):
+ state = widget.get_state_flags ()
+ style = widget.get_style_context ()
+ fg_color = style.get_color (state)
+ active = self.get_property ("active")
+ size = max (min (cell_area.width, cell_area.height) -
+ 2 * self.padding,
+ 0)
+
+ (r, g, b, a) = self.color
+
+ if is_bright_color (fg_color):
+ bg = (0.75 * r, 0.75 * g, 0.75 * b)
+ fg = (r, g, b)
+ else:
+ bg = (r, g, b)
+ fg = (0.75 * r, 0.75 * g, 0.75 * b)
+
+ x = cell_area.x + (cell_area.width - size) // 2
+ y = cell_area.y + (cell_area.height - size) // 2
+
+ if active:
+ cr.rectangle (x, y, size, size)
+
+ cr.set_source_rgba (*bg)
+ cr.fill ()
+ else:
+ style.save ()
+
+ style.set_state (Gtk.StateFlags (state & ~Gtk.StateFlags.SELECTED))
+ Gtk.render_background (style, cr, x, y, size, size)
+
+ style.restore ()
+
+ cr.rectangle (x, y, size, size)
+
+ cr.set_source_rgb (*fg)
+ cr.set_line_width (2)
+ cr.stroke ()
+
+class VariableSet (Gtk.TreeView):
+ class Store (Gtk.ListStore):
+ NAME = 0
+ DESC = 1
+ COLOR = 2
+ ACTIVE = 3
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self, str, str, Gdk.RGBA, bool)
+
+ for var, var_def in var_defs.items ():
+ i = self.append ((var,
+ var_def.desc,
+ Gdk.RGBA (*var_def.color),
+ False))
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.TreeView.__init__ (self, *args, headers_visible = False, **kwargs)
+
+ store = self.Store ()
+ self.store = store
+ self.set_model (store)
+ self.set_tooltip_column (store.DESC)
+
+ col = Gtk.TreeViewColumn ()
+ self.append_column (col)
+
+ cell = CellRendererColorToggle ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "active", store.ACTIVE)
+ col.add_attribute (cell, "color", store.COLOR)
+
+ cell.connect ("toggled", self.var_toggled)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, True)
+ col.add_attribute (cell, "text", store.NAME)
+
+ def var_toggled (self, cell, path):
+ self.store[path][self.store.ACTIVE] = not cell.get_property ("active")
+
+class SampleGraph (Gtk.DrawingArea):
+ def __init__ (self, model = None, *args, **kwargs):
+ Gtk.DrawingArea.__init__ (self, *args, can_focus = True, **kwargs)
+
+ self.style_widget = Gtk.Entry ()
+
+ self.model = model
+
+ if model:
+ model.connect ("row-changed", lambda *args: self.update ())
+
+ self.update ()
+
+ self.selection = None
+ self.sel = None
+
+ selection.connect ("changed", self.selection_changed)
+ selection.connect ("highlight-changed",
+ lambda selection: self.queue_draw ())
+
+ self.add_events (Gdk.EventMask.BUTTON_PRESS_MASK |
+ Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.KEY_PRESS_MASK |
+ Gdk.EventMask.KEY_RELEASE_MASK)
+
+ self.selection_changed (selection)
+
+ def sample_to_x (self, i):
+ if not samples:
+ return None
+
+ width = self.get_allocated_width ()
+ n_samples = max (len (samples), 2)
+
+ return 1 + (width - 3) * i / (n_samples - 1)
+
+ def sample_to_range (self, i):
+ if not samples:
+ return None
+
+ width = self.get_allocated_width ()
+ n_samples = max (len (samples), 2)
+
+ return (1 + math.floor ((width - 3) * (i - 0.5) / (n_samples - 1)),
+ 1 + math.ceil ((width - 3) * (i + 0.5) / (n_samples - 1)))
+
+ def x_to_sample (self, x):
+ if not samples:
+ return None
+
+ width = max (self.get_allocated_width (), 4)
+ n_samples = len (samples)
+
+ return round ((n_samples - 1) * (x - 1) / (width - 3))
+
+ def update (self):
+ if not samples or not self.model:
+ return
+
+ self.max_value = 1
+
+ for row in self.model:
+ var_name = row[self.model.NAME]
+ var_active = row[self.model.ACTIVE]
+
+ if var_active:
+ values = (sample.vars[var_name].value for sample in samples)
+ values = filter (lambda x: x is not None, values)
+ values = filter (math.isfinite, values)
+
+ try:
+ self.max_value = max (self.max_value, max (values))
+ except:
+ pass
+
+ self.queue_draw ()
+
+ def selection_changed (self, selection):
+ if selection.selection:
+ self.sel = list (selection.selection)
+ self.sel.sort ()
+ else:
+ self.sel = None
+
+ self.queue_draw ()
+
+ def do_get_preferred_width (self):
+ return (300, 300)
+
+ def do_get_preferred_height (self):
+ if self.model:
+ return (32, 256)
+ else:
+ return (16, 16)
+
+ def update_selection (self):
+ sel = self.selection.copy ()
+
+ i0 = self.selection_i0
+ i1 = self.selection_i1
+
+ if self.selection_range:
+ swap = i0 > i1
+
+ if swap:
+ temp = i0
+ i0 = i1
+ i1 = temp
+
+ n_samples = len (samples)
+
+ while i0 > 0 and not samples[i0 - 1].markers: i0 -= 1
+ while i1 < n_samples - 1 and not samples[i1 + 1].markers: i1 += 1
+
+ if swap:
+ temp = i0
+ i0 = i1
+ i1 = temp
+
+ sel.select_range (i0, i1, self.selection_op)
+
+ selection.select (sel.selection)
+
+ selection.cursor = i1
+ selection.cursor_dir = i1 - i0
+
+ def do_button_press_event (self, event):
+ state = event.state & Gdk.ModifierType.MODIFIER_MASK
+
+ self.grab_focus ()
+
+ if event.button == 1:
+ i = self.x_to_sample (event.x)
+
+ if i is None:
+ return False
+
+ self.selection = selection.copy ()
+ self.selection_i0 = i
+ self.selection_i1 = i
+ self.selection_op = SelectionOp.REPLACE
+ self.selection_range = event.type != Gdk.EventType.BUTTON_PRESS
+
+ if state == Gdk.ModifierType.SHIFT_MASK:
+ self.selection_op = SelectionOp.ADD
+ elif state == Gdk.ModifierType.CONTROL_MASK:
+ self.selection_op = SelectionOp.SUBTRACT
+ elif state == (Gdk.ModifierType.SHIFT_MASK |
+ Gdk.ModifierType.CONTROL_MASK):
+ self.selection_op = SelectionOp.INTERSECT
+
+ self.update_selection ()
+
+ self.grab_add ()
+ elif event.button == 3:
+ if state == 0:
+ selection.clear ()
+ elif state == Gdk.ModifierType.CONTROL_MASK:
+ selection.invert ()
+
+ self.grab_add ()
+
+ return True
+
+ def do_button_release_event (self, event):
+ if event.button == 1 or event.button == 3:
+ self.selection = None
+
+ selection.change_complete ()
+
+ self.grab_remove ()
+
+ return True
+
+ return False
+
+ def do_motion_notify_event (self, event):
+ i = self.x_to_sample (event.x)
+
+ selection.set_highlight (i)
+
+ if self.selection and i is not None:
+ self.selection_i1 = i
+
+ self.update_selection ()
+
+ return True
+
+ return False
+
+ def do_leave_notify_event (self, event):
+ selection.set_highlight (None)
+
+ return False
+
+ def do_key_press_event (self, event):
+ if event.keyval == Gdk.KEY_Left or \
+ event.keyval == Gdk.KEY_Right or \
+ event.keyval == Gdk.KEY_Home or \
+ event.keyval == Gdk.KEY_KP_Home or \
+ event.keyval == Gdk.KEY_End or \
+ event.keyval == Gdk.KEY_KP_End:
+ n_samples = len (samples)
+
+ state = event.state & Gdk.ModifierType.MODIFIER_MASK
+
+ op = SelectionOp.REPLACE
+
+ if state == Gdk.ModifierType.SHIFT_MASK:
+ op = SelectionOp.XOR
+
+ cursor = selection.cursor
+ cursor_dir = selection.cursor_dir
+
+ if event.keyval == Gdk.KEY_Left or \
+ event.keyval == Gdk.KEY_Home or \
+ event.keyval == Gdk.KEY_KP_Home:
+ if selection.cursor is not None:
+ if cursor_dir <= 0 or op == SelectionOp.REPLACE:
+ cursor -= 1
+ else:
+ cursor = n_samples - 1
+
+ cursor_dir = -1
+ elif event.keyval == Gdk.KEY_Right or \
+ event.keyval == Gdk.KEY_End or \
+ event.keyval == Gdk.KEY_KP_End:
+ if cursor is not None:
+ if cursor_dir >= 0 or op == SelectionOp.REPLACE:
+ cursor += 1
+ else:
+ cursor = 0
+
+ cursor_dir = +1
+
+ if cursor < 0 or cursor >= n_samples:
+ cursor = min (max (cursor, 0), n_samples - 1)
+
+ selection.cursor = cursor
+ selection.cursor_dir = cursor_dir
+
+ if op != SelectionOp.REPLACE:
+ return True
+
+ i0 = cursor
+
+ if event.keyval == Gdk.KEY_Home or \
+ event.keyval == Gdk.KEY_KP_Home:
+ cursor = 0
+ elif event.keyval == Gdk.KEY_End or \
+ event.keyval == Gdk.KEY_KP_End:
+ cursor = n_samples - 1
+
+ if op == SelectionOp.REPLACE:
+ i0 = cursor
+
+ selection.select_range (i0, cursor, op)
+
+ if len (selection.selection) > 1:
+ selection.cursor = cursor
+ selection.cursor_dir = cursor_dir
+
+ return True
+ elif event.keyval == Gdk.KEY_Escape:
+ selection.select (set ())
+
+ return True
+
+ return False
+
+ def do_key_release_event (self, event):
+ selection.change_complete ()
+
+ return False
+
+ def do_draw (self, cr):
+ state = self.get_state_flags ()
+ style = (self.style_widget if self.model else
+ self).get_style_context ()
+ (width, height) = (self.get_allocated_width (),
+ self.get_allocated_height ())
+
+ fg_color = tuple (style.get_color (state))
+ grid_color = (*fg_color[:3], 0.25 * fg_color[3])
+ highlight_color = grid_color
+ selection_color = (*fg_color[:3], 0.15 * fg_color[3])
+
+ Gtk.render_background (style, cr, 0, 0, width, height)
+
+ if self.model:
+ max_value = self.max_value
+ vscale = (height - 4) / max_value
+
+ cr.save ()
+
+ cr.translate (0, height - 2)
+ cr.scale (1, -1)
+
+ first_sample = True
+ has_infinite = False
+
+ for row in self.model:
+ var_name = row[self.model.NAME]
+ var_active = row[self.model.ACTIVE]
+
+ if var_active:
+ is_boolean = var_defs[var_name].type == "boolean"
+ is_continuous = not is_boolean
+
+ for i in range (len (samples)):
+ value = samples[i].vars[var_name].value
+
+ if value is not None:
+ first_sample = False
+
+ if math.isinf (value):
+ first_sample = True
+ has_infinite = True
+
+ value = max_value
+ elif is_boolean:
+ value *= max_value
+
+ y = value * vscale
+
+ if is_continuous:
+ x = self.sample_to_x (i)
+
+ if first_sample:
+ cr.move_to (x, y)
+ else:
+ cr.line_to (x, y)
+ else:
+ (x0, x1) = self.sample_to_range (i)
+
+ if first_sample:
+ cr.move_to (x0, y)
+ else:
+ cr.line_to (x0, y)
+
+ cr.line_to (x1, y)
+ else:
+ first_sample = True
+
+ (r, g, b) = var_defs[var_name].color
+
+ cr.set_source_rgb (r, g, b)
+ cr.set_line_width (2)
+ cr.stroke ()
+
+ if has_infinite:
+ cr.save ()
+
+ for i in range (len (samples)):
+ value = samples[i].vars[var_name].value
+
+ if value is not None and math.isinf (value):
+ first_sample = False
+
+ y = max_value * vscale
+
+ if is_continuous:
+ x = self.sample_to_x (i)
+
+ if first_sample:
+ cr.move_to (x, y)
+ else:
+ cr.line_to (x, y)
+ else:
+ (x0, x1) = self.sample_to_range (i)
+
+ if first_sample:
+ cr.move_to (x0, y)
+ else:
+ cr.line_to (x0, y)
+
+ cr.line_to (x1, y)
+ else:
+ first_sample = True
+
+ cr.set_dash ([6, 6], 0)
+ cr.stroke ()
+
+ cr.restore ()
+
+ cr.restore ()
+
+ cr.set_line_width (1)
+
+ cr.set_source_rgba (*grid_color)
+
+ n_hgrid_lines = 4
+ n_vgrid_lines = 1
+
+ for i in range (n_hgrid_lines + 1):
+ cr.move_to (0, round (i * (height - 1) / n_hgrid_lines) + 0.5)
+ cr.rel_line_to (width, 0)
+
+ cr.stroke ()
+
+ for i in range (n_vgrid_lines + 1):
+ cr.move_to (round (i * (width - 1) / n_vgrid_lines) + 0.5, 0)
+ cr.rel_line_to (0, height)
+
+ cr.stroke ()
+ else:
+ for i in range (len (samples)):
+ if samples[i].markers:
+ (x0, x1) = self.sample_to_range (i)
+
+ cr.rectangle (x0, 0, x1 - x0, height)
+
+ cr.set_source_rgba (*fg_color)
+ cr.fill ()
+
+ if selection.highlight is not None:
+ (x0, x1) = self.sample_to_range (selection.highlight)
+
+ cr.rectangle (x0, 0, x1 - x0, height)
+
+ (r, g, b, a) = style.get_color (state)
+
+ cr.set_source_rgba (*highlight_color)
+ cr.fill ()
+
+ if self.sel:
+ def draw_selection ():
+ x0 = self.sample_to_range (i0)[0]
+ x1 = self.sample_to_range (i1)[1]
+
+ cr.rectangle (x0, 0, x1 - x0, height)
+
+ (r, g, b, a) = style.get_color (state)
+
+ cr.set_source_rgba (*selection_color)
+ cr.fill ()
+
+ i0 = None
+
+ for i in self.sel:
+ if i0 is None:
+ i0 = i
+ i1 = i
+ elif i == i1 + 1:
+ i1 = i
+ else:
+ draw_selection ()
+
+ i0 = i
+ i1 = i
+
+ if i0 is not None:
+ draw_selection ()
+
+class SampleGraphList (Gtk.Box):
+ Item = namedtuple (
+ "SampleGraphListGraph", ("widget",
+ "model",
+ "remove_button",
+ "move_up_button",
+ "move_down_button")
+ )
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.Box.__init__ (self,
+ *args,
+ orientation = Gtk.Orientation.VERTICAL,
+ **kwargs)
+
+ self.items = []
+
+ self.vset_size_group = Gtk.SizeGroup (
+ mode = Gtk.SizeGroupMode.HORIZONTAL
+ )
+
+ hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
+ self.pack_start (hbox, False, False, 0)
+ hbox.show ()
+
+ empty = Gtk.DrawingArea ()
+ hbox.pack_start (empty, False, True, 0)
+ self.vset_size_group.add_widget (empty)
+ empty.show ()
+
+ graph = SampleGraph (has_tooltip = True)
+ hbox.pack_start (graph, True, True, 0)
+ graph.show ()
+
+ graph.connect ("query-tooltip", self.graph_query_tooltip)
+
+ separator = Gtk.Separator (orientation = Gtk.Orientation.HORIZONTAL)
+ self.pack_start (separator, False, False, 0)
+ separator.show ()
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
+ self.items_vbox = vbox
+ self.pack_start (vbox, False, False, 0)
+ vbox.show ()
+
+ self.add_item (0)
+
+ def update_items (self):
+ for widget in self.items_vbox.get_children ():
+ self.items_vbox.remove (widget)
+
+ i = 0
+
+ for item in self.items:
+ if i > 0:
+ separator = Gtk.Separator (
+ orientation = Gtk.Orientation.HORIZONTAL
+ )
+ self.items_vbox.pack_start (separator, False, False, 0)
+ separator.show ()
+
+ self.items_vbox.pack_start (item.widget, False, False, 0)
+
+ item.remove_button.set_sensitive (len (self.items) > 1)
+ item.move_up_button.set_sensitive (i > 0)
+ item.move_down_button.set_sensitive (i < len (self.items) - 1)
+
+ i += 1
+
+ def add_item (self, i):
+ hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
+ hbox.show ()
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
+ hbox.pack_start (vbox, False, True, 0)
+ self.vset_size_group.add_widget (vbox)
+ vbox.show ()
+
+ scroll = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ )
+ vbox.pack_start (scroll, True, True, 0)
+ scroll.show ()
+
+ vset = VariableSet ()
+ scroll.add (vset)
+ vset.show ()
+
+ buttons = Gtk.ButtonBox (orientation = Gtk.Orientation.HORIZONTAL)
+ vbox.pack_start (buttons, False, False, 0)
+ buttons.set_layout (Gtk.ButtonBoxStyle.EXPAND)
+ buttons.show ()
+
+ button = Gtk.Button.new_from_icon_name ("list-add-symbolic",
+ Gtk.IconSize.BUTTON)
+ add_button = button
+ buttons.add (button)
+ button.show ()
+
+ button = Gtk.Button.new_from_icon_name ("list-remove-symbolic",
+ Gtk.IconSize.BUTTON)
+ remove_button = button
+ buttons.add (button)
+ button.show ()
+
+ button = Gtk.Button.new_from_icon_name ("go-up-symbolic",
+ Gtk.IconSize.BUTTON)
+ move_up_button = button
+ buttons.add (button)
+ button.show ()
+
+ button = Gtk.Button.new_from_icon_name ("go-down-symbolic",
+ Gtk.IconSize.BUTTON)
+ move_down_button = button
+ buttons.add (button)
+ button.show ()
+
+ graph = SampleGraph (vset.get_model (), has_tooltip = True)
+ hbox.pack_start (graph, True, True, 0)
+ graph.show ()
+
+ graph.connect ("query-tooltip", self.graph_query_tooltip)
+
+ item = self.Item (
+ widget = hbox,
+ model = vset.get_model (),
+ remove_button = remove_button,
+ move_up_button = move_up_button,
+ move_down_button = move_down_button
+ )
+ self.items.insert (i, item)
+
+ add_button.connect ("clicked",
+ lambda *args: self.add_item (
+ self.items.index (item) + 1
+ ))
+ remove_button.connect ("clicked",
+ lambda *args: self.remove_item (
+ self.items.index (item)
+ ))
+ move_up_button.connect ("clicked",
+ lambda *args: self.move_item (
+ self.items.index (item),
+ -1
+ ))
+ move_down_button.connect ("clicked",
+ lambda *args: self.move_item (
+ self.items.index (item),
+ +1
+ ))
+
+ self.update_items ()
+
+ def remove_item (self, i):
+ del self.items[i]
+
+ self.update_items ()
+
+ def move_item (self, i, offset):
+ item = self.items[i]
+ del self.items[i]
+ self.items.insert (i + offset, item)
+
+ self.update_items ()
+
+ def graph_query_tooltip (self, graph, x, y, keyboard_mode, tooltip):
+ if keyboard_mode:
+ return False
+
+ i = graph.x_to_sample (x)
+
+ if i is None or i < 0 or i >= len (samples):
+ return False
+
+ grid = Gtk.Grid (column_spacing = 4)
+ tooltip.set_custom (grid)
+ grid.show ()
+
+ row = 0
+
+ label = Gtk.Label ()
+ grid.attach (label, 0, row, 2, 1)
+ label.set_markup ("<b>Sample %d</b>" % i)
+ label.show ()
+
+ row += 1
+
+ label = Gtk.Label ()
+ grid.attach (label, 0, row, 2, 1)
+ label.set_markup ("<sub>%s</sub>" %
+ format_duration (samples[i].t / 1000000))
+ label.get_style_context ().add_class ("dim-label")
+ label.show ()
+
+ row += 1
+
+ for item in self.items:
+ model = item.model
+
+ vars = tuple (var[model.NAME] for var in model if var[model.ACTIVE])
+
+ if not vars:
+ continue
+
+ separator = Gtk.Separator (orientation = Gtk.Orientation.HORIZONTAL)
+ grid.attach (separator, 0, row, 2, 1)
+ separator.show ()
+
+ row += 1
+
+ for var in vars:
+ color = format_color (var_defs[var].color)
+
+ label = Gtk.Label (halign = Gtk.Align.START)
+ grid.attach (label, 0, row, 1, 1)
+ label.set_markup (
+ "<span color=\"%s\"><b>%s</b></span>" % (color, var)
+ )
+ label.show ()
+
+ value = samples[i].vars[var].value
+ text = var_types[var_defs[var].type].format (value) \
+ if value is not None else "N/A"
+
+ label = Gtk.Label (label = text, halign = Gtk.Align.END)
+ grid.attach (label, 1, row, 1, 1)
+ label.show ()
+
+ row += 1
+
+ markers = samples[i].markers
+
+ if markers:
+ separator = Gtk.Separator (orientation = Gtk.Orientation.HORIZONTAL)
+ grid.attach (separator, 0, row, 2, 1)
+ separator.show ()
+
+ row += 1
+
+ for marker in markers:
+ label = Gtk.Label (halign = Gtk.Align.START)
+ grid.attach (label, 0, row, 1, 1)
+ label.set_markup ("<b>Marker %d</b>" % (marker.id))
+ label.show ()
+
+ if marker.description:
+ label = Gtk.Label (marker.description,
+ halign = Gtk.Align.END)
+ grid.attach (label, 1, row, 1, 1)
+ label.show ()
+
+ row += 1
+
+ return True
+
+class InformationViewer (Gtk.ScrolledWindow):
+ class Store (Gtk.ListStore):
+ NAME = 0
+ VALUE = 1
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self, str, str)
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.ScrolledWindow.__init__ (
+ self,
+ *args,
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ **kwargs
+ )
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL,
+ border_width = 32,
+ margin_left = 64,
+ margin_right = 64,
+ spacing = 32)
+ self.add (vbox)
+ vbox.show ()
+
+ def add_element (element):
+ name = {
+ "params": "Log Parameters",
+ "gimp-version": "GIMP Version",
+ "env": "Environment",
+ "gegl-config": "GEGL Config"
+ }.get (element.tag, element.tag)
+ text = element.text.strip ()
+ n_items = len (element)
+
+ if not text and n_items == 0:
+ return
+
+ vbox2 = Gtk.Box (orientation = Gtk.Orientation.VERTICAL,
+ spacing = 16)
+ vbox.pack_start (vbox2, False, False, 0)
+ vbox2.show ()
+
+ label = Gtk.Label (xalign = 0)
+ vbox2.pack_start (label, False, False, 0)
+ label.set_markup ("<b>%s</b>" % name)
+ label.show ()
+
+ frame = Gtk.Frame (shadow_type = Gtk.ShadowType.IN)
+ vbox2.pack_start (frame, False, False, 0)
+ frame.show ()
+
+ if text:
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ height_request = 400
+ )
+ frame.add (scrolled)
+ scrolled.show ()
+
+ text = Gtk.TextView (editable = False,
+ monospace = True,
+ wrap_mode = Gtk.WrapMode.WORD,
+ left_margin = 16,
+ right_margin = 16,
+ top_margin = 16,
+ bottom_margin = 16)
+ scrolled.add (text)
+ text.get_buffer ().set_text (element.text.strip (), -1)
+ text.show ()
+ else:
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.NEVER
+ )
+ frame.add (scrolled)
+ scrolled.show ()
+
+ store = self.Store ()
+
+ for item in element:
+ store.append ((item.tag, item.text.strip ()))
+
+ tree = Gtk.TreeView (model = store)
+ scrolled.add (tree)
+ tree.show ()
+
+ col = Gtk.TreeViewColumn (title = "Name")
+ tree.append_column (col)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.NAME)
+
+ col = Gtk.TreeViewColumn (title = "Value")
+ tree.append_column (col)
+ col.set_alignment (0.5)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.VALUE)
+
+ params = log.find ("params")
+
+ if params:
+ add_element (params)
+
+ info = log.find ("info")
+
+ if info:
+ for element in info:
+ add_element (element)
+
+class MarkersViewer (Gtk.ScrolledWindow):
+ class Store (Gtk.ListStore):
+ ID = 0
+ TIME = 1
+ DESC = 2
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self, int, GObject.TYPE_INT64, str)
+
+ for marker in markers:
+ self.append ((marker.id, marker.t, marker.description))
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.Box.__init__ (self,
+ *args,
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ **kwargs)
+
+ self.needs_update = True
+
+ store = self.Store ()
+ self.store = store
+
+ tree = Gtk.TreeView (model = store)
+ self.tree = tree
+ self.add (tree)
+ tree.show ()
+
+ tree.get_selection ().set_mode (Gtk.SelectionMode.MULTIPLE)
+
+ self.tree_selection_changed_handler = tree.get_selection ().connect (
+ "changed", self.tree_selection_changed
+ )
+
+ col = Gtk.TreeViewColumn (title = "#")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.ID)
+
+ def format_time_col (tree_col, cell, model, iter, col):
+ time = model[iter][col]
+
+ cell.set_property ("text", format_duration (time / 1000000))
+
+ col = Gtk.TreeViewColumn (title = "Time")
+ tree.append_column (col)
+ col.set_resizable (True)
+ col.set_alignment (0.5)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.set_cell_data_func (cell, format_time_col, store.TIME)
+
+ col = Gtk.TreeViewColumn (title = "Description")
+ tree.append_column (col)
+ col.set_resizable (True)
+ col.set_alignment (0.5)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.DESC)
+
+ col = Gtk.TreeViewColumn ()
+ tree.append_column (col)
+
+ selection.connect ("change-complete", self.selection_change_complete)
+
+ def update (self):
+ markers = set ()
+
+ if not self.needs_update:
+ return
+
+ self.needs_update = False
+
+ for i in selection.selection:
+ markers.update (marker.id for marker in samples[i].markers)
+
+ tree_sel = self.tree.get_selection ()
+
+ GObject.signal_handler_block (tree_sel,
+ self.tree_selection_changed_handler)
+
+ tree_sel.unselect_all ()
+
+ for row in self.store:
+ if row[self.store.ID] in markers:
+ tree_sel.select_iter (row.iter)
+
+ GObject.signal_handler_unblock (tree_sel,
+ self.tree_selection_changed_handler)
+
+ def do_map (self):
+ self.update ()
+
+ Gtk.ScrolledWindow.do_map (self)
+
+ def selection_change_complete (self, selection):
+ self.needs_update = True
+
+ if self.get_mapped ():
+ self.update ()
+
+ def tree_selection_changed (self, tree_sel):
+ sel = set ()
+
+ for row in self.store:
+ if tree_sel.iter_is_selected (row.iter):
+ id = row[self.store.ID]
+
+ for i in range (len (samples)):
+ if any (marker.id == id for marker in samples[i].markers):
+ sel.add (i)
+
+ selection.select (sel)
+
+ selection.change_complete ()
+
+class VariablesViewer (Gtk.ScrolledWindow):
+ class Store (Gtk.ListStore):
+ NAME = 0
+ DESC = 1
+ COLOR = 2
+ VALUE = 3
+ RAW = 4
+ MIN = 5
+ MAX = 6
+ MEDIAN = 7
+ MEAN = 8
+ STDEV = 9
+ LAST_COLUMN = 10
+
+ def __init__ (self):
+ n_stats = self.LAST_COLUMN - self.COLOR
+
+ Gtk.ListStore.__init__ (self,
+ *((str, str, Gdk.RGBA) + n_stats * (str,)))
+
+ for var, var_def in var_defs.items ():
+ self.append (((var,
+ var_def.desc,
+ Gdk.RGBA (*var_def.color)) +
+ n_stats * ("",)))
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.Box.__init__ (self,
+ *args,
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ **kwargs)
+
+ self.needs_update = True
+
+ store = self.Store ()
+ self.store = store
+
+ tree = Gtk.TreeView (model = store)
+ self.add (tree)
+ tree.set_tooltip_column (store.DESC)
+ tree.show ()
+
+ self.single_sample_cols = []
+ self.multi_sample_cols = []
+
+ col = Gtk.TreeViewColumn (title = "Variable")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = CellRendererColorToggle (active = True)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "color", store.COLOR)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.NAME)
+
+ def add_value_column (title, column, single_sample):
+ col = Gtk.TreeViewColumn (title = title)
+ tree.append_column (col)
+ col.set_resizable (True)
+ col.set_alignment (0.5)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", column)
+
+ if single_sample:
+ self.single_sample_cols.append (col)
+ else:
+ self.multi_sample_cols.append (col)
+
+ add_value_column ("Value", store.VALUE, True)
+ add_value_column ("Raw", store.RAW, True)
+ add_value_column ("Min", store.MIN, False)
+ add_value_column ("Max", store.MAX, False)
+ add_value_column ("Median", store.MEDIAN, False)
+ add_value_column ("Mean", store.MEAN, False)
+ add_value_column ("Std. Dev.", store.STDEV, False)
+
+ col = Gtk.TreeViewColumn ()
+ tree.append_column (col)
+
+ selection.connect ("change-complete", self.selection_change_complete)
+
+ def update (self):
+ if not self.needs_update:
+ return
+
+ self.needs_update = False
+
+ sel = selection.get_effective_selection ()
+ n_sel = len (sel)
+
+ if n_sel == 1:
+ i, = sel
+
+ for row in self.store:
+ var_name = row[self.store.NAME]
+
+ var = samples[i].vars[var_name]
+ var_type = var_types[var_defs[var_name].type]
+
+ row[self.store.VALUE] = var_type.format (var.value)
+ row[self.store.RAW] = var.raw if var.raw is not None \
+ else "N/A"
+ else:
+ for row in self.store:
+ var_name = row[self.store.NAME]
+
+ var_type = var_types[var_defs[var_name].type]
+
+ vals = (samples[i].vars[var_name].value for i in sel)
+ vals = tuple (val for val in vals if val is not None)
+
+ if vals:
+ min_val = min (vals)
+ max_val = max (vals)
+ median = statistics.median (vals)
+ mean = statistics.mean (vals)
+ stdev = statistics.pstdev (vals, mean)
+
+ row[self.store.MIN] = var_type.format (min_val)
+ row[self.store.MAX] = var_type.format (max_val)
+ row[self.store.MEDIAN] = var_type.format (median)
+ row[self.store.MEAN] = var_type.format_numeric (mean)
+ row[self.store.STDEV] = var_type.format_numeric (stdev)
+ else:
+ row[self.store.MIN] = \
+ row[self.store.MAX] = \
+ row[self.store.MEDIAN] = \
+ row[self.store.MEAN] = \
+ row[self.store.STDEV] = var_type.format (None)
+
+ for col in self.single_sample_cols: col.set_visible (n_sel == 1)
+ for col in self.multi_sample_cols: col.set_visible (n_sel > 1)
+
+ def do_map (self):
+ self.update ()
+
+ Gtk.ScrolledWindow.do_map (self)
+
+ def selection_change_complete (self, selection):
+ self.needs_update = True
+
+ if self.get_mapped ():
+ self.update ()
+
+class BacktraceViewer (Gtk.Box):
+ class ThreadStore (Gtk.ListStore):
+ INDEX = 0
+ ID = 1
+ NAME = 2
+ STATE = 3
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self, int, int, str, str)
+
+ class FrameStore (Gtk.ListStore):
+ ID = 0
+ ADDRESS = 1
+ OBJECT = 2
+ FUNCTION = 3
+ OFFSET = 4
+ SOURCE = 5
+ LINE = 6
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self, int, str, str, str, str, str, str)
+
+ class CellRendererViewSource (Gtk.CellRendererPixbuf):
+ file = GObject.Property (type = Gio.File, default = None)
+ line = GObject.Property (type = int, default = 0)
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.CellRendererPixbuf.__init__ (
+ self,
+ *args,
+ icon_name = "text-x-generic-symbolic",
+ mode = Gtk.CellRendererMode.ACTIVATABLE,
+ **kwargs)
+
+ self.connect ("notify::file",
+ lambda *args:
+ self.set_property ("visible", bool (self.file)))
+
+ def do_activate (self, event, widget, path, *args):
+ if self.file:
+ run_editor (self.file, self.line)
+
+ return True
+
+ return False
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.Box.__init__ (self,
+ *args,
+ orientation = Gtk.Orientation.HORIZONTAL,
+ **kwargs)
+
+ self.needs_update = True
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
+ self.pack_start (vbox, False, False, 0)
+ vbox.show ()
+
+ header = Gtk.HeaderBar (title = "Threads", has_subtitle = False)
+ vbox.pack_start (header, False, False, 0)
+ header.show ()
+
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ )
+ vbox.pack_start (scrolled, True, True, 0)
+ scrolled.show ()
+
+ store = self.ThreadStore ()
+ self.thread_store = store
+ store.set_sort_column_id (store.ID, Gtk.SortType.ASCENDING)
+
+ tree = Gtk.TreeView (model = store)
+ self.thread_tree = tree
+ scrolled.add (tree)
+ tree.set_search_column (store.NAME)
+ tree.show ()
+
+ tree.connect ("row-activated", self.threads_row_activated)
+
+ tree.get_selection ().connect ("changed",
+ self.threads_selection_changed)
+
+ col = Gtk.TreeViewColumn (title = "ID")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.ThreadStore.ID)
+
+ col = Gtk.TreeViewColumn (title = "Name")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.ThreadStore.NAME)
+
+ col = Gtk.TreeViewColumn (title = "State")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.ThreadStore.STATE)
+
+ separator = Gtk.Separator (orientation = Gtk.Orientation.VERTICAL)
+ self.pack_start (separator, False, False, 0)
+ separator.show ()
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
+ self.pack_start (vbox, True, True, 0)
+ vbox.show ()
+
+ header = Gtk.HeaderBar (title = "Stack", has_subtitle = False)
+ vbox.pack_start (header, False, False, 0)
+ header.show ()
+
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ )
+ vbox.pack_start (scrolled, True, True, 0)
+ scrolled.show ()
+
+ store = self.FrameStore ()
+ self.frame_store = store
+
+ tree = Gtk.TreeView (model = store, has_tooltip = True)
+ scrolled.add (tree)
+ tree.set_search_column (store.FUNCTION)
+ tree.show ()
+
+ tree.connect ("row-activated", self.frames_row_activated)
+ tree.connect ("query-tooltip", self.frames_query_tooltip)
+
+ def format_filename_col (tree_col, cell, model, iter, col):
+ object = model[iter][col]
+
+ cell.set_property ("text", get_basename (object) if object else "")
+
+ self.tooltip_columns = {}
+
+ col = Gtk.TreeViewColumn (title = "#")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.FrameStore.ID)
+
+ col = Gtk.TreeViewColumn (title = "Address")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.FrameStore.ADDRESS)
+
+ col = Gtk.TreeViewColumn (title = "Object")
+ self.tooltip_columns[col] = store.OBJECT
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.set_cell_data_func (cell, format_filename_col, store.OBJECT)
+
+ col = Gtk.TreeViewColumn (title = "Function")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.FrameStore.FUNCTION)
+
+ col = Gtk.TreeViewColumn (title = "Offset")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.FrameStore.OFFSET)
+
+ col = Gtk.TreeViewColumn (title = "Source")
+ self.tooltip_columns[col] = store.SOURCE
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.set_cell_data_func (cell, format_filename_col, store.SOURCE)
+
+ col = Gtk.TreeViewColumn (title = "Line")
+ tree.append_column (col)
+ col.set_resizable (True)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", self.FrameStore.LINE)
+
+ def format_view_source_col (tree_col, cell, model, iter, cols):
+ filename = model[iter][cols[0]] or None
+ line = model[iter][cols[1]] or "0"
+
+ cell.set_property ("file", filename and find_file (filename))
+ cell.set_property ("line", int (line))
+
+ def format_view_source_tooltip (row):
+ filename = row[store.SOURCE]
+
+ if filename:
+ file = find_file (filename)
+
+ if file:
+ return file.get_path ()
+
+ return None
+
+ col = Gtk.TreeViewColumn ()
+ self.tooltip_columns[col] = format_view_source_tooltip
+ tree.append_column (col)
+
+ cell = self.CellRendererViewSource (xalign = 0)
+ col.pack_start (cell, False)
+ col.set_cell_data_func (cell, format_view_source_col, (store.SOURCE,
+ store.LINE))
+
+ selection.connect ("change-complete", self.selection_change_complete)
+
+ @GObject.Property (type = bool, default = False)
+ def available (self):
+ sel = selection.get_effective_selection ()
+
+ if len (sel) == 1:
+ i, = sel
+
+ return bool (samples[i].backtrace)
+
+ return False
+
+ def update (self):
+ if not self.needs_update or not self.available:
+ return
+
+ self.needs_update = False
+
+ tid = None
+
+ sel_rows = self.thread_tree.get_selection ().get_selected_rows ()[1]
+
+ if sel_rows:
+ tid = self.thread_store[sel_rows[0]][self.ThreadStore.ID]
+
+ i, = selection.get_effective_selection ()
+
+ self.thread_store.clear ()
+
+ for t in range (len (samples[i].backtrace)):
+ thread = samples[i].backtrace[t]
+
+ iter = self.thread_store.append (
+ (t, thread.id, thread.name, str (thread.state))
+ )
+
+ if thread.id == tid:
+ self.thread_tree.get_selection ().select_iter (iter)
+
+ def do_map (self):
+ self.update ()
+
+ Gtk.Box.do_map (self)
+
+ def selection_change_complete (self, selection):
+ self.needs_update = True
+
+ if self.get_mapped ():
+ self.update ()
+
+ self.notify ("available")
+
+ def threads_row_activated (self, tree, path, col):
+ iter = self.thread_store.get_iter (path)
+
+ tid = self.thread_store[iter][self.ThreadStore.ID]
+
+ sel = set ()
+
+ for i in range (len (samples)):
+ threads = filter (lambda thread:
+ thread.id == tid and
+ thread.state == ThreadState.RUNNING,
+ samples[i].backtrace or [])
+
+ if list (threads):
+ sel.add (i)
+
+ selection.select (sel)
+
+ selection.change_complete ()
+
+ def threads_selection_changed (self, tree_sel):
+ self.frame_store.clear ()
+
+ (store, rows) = tree_sel.get_selected_rows ()
+
+ if not rows:
+ return
+
+ i, = selection.get_effective_selection ()
+
+ try:
+ frames = samples[i].backtrace[store[rows[0]][store.INDEX]].frames
+
+ for frame in frames:
+ info = frame.info
+
+ self.frame_store.append ((
+ frame.id, hex (frame.address), info.object, info.symbol,
+ hex (info.offset) if info.offset is not None else None,
+ info.source, str (info.line) if info.line else None
+ ))
+ except:
+ pass
+
+ def frames_row_activated (self, tree, path, col):
+ iter = self.frame_store.get_iter (path)
+
+ address = int (self.frame_store[iter][self.FrameStore.ADDRESS], 0)
+ info = address_map.get (address, None)
+
+ if not info:
+ return
+
+ id = info.id
+
+ if not id:
+ return
+
+ sel = set ()
+
+ def has_frame (sample, id):
+ for thread in sample.backtrace or []:
+ for frame in thread.frames:
+ if frame.info.id == id:
+ return True
+
+ return False
+
+ for i in range (len (samples)):
+ if has_frame (samples[i], id):
+ sel.add (i)
+
+ selection.select (sel)
+
+ selection.change_complete ()
+
+ def frames_query_tooltip (self, tree, x, y, keyboard_mode, tooltip):
+ hit, x, y, model, path, iter = tree.get_tooltip_context (x, y,
+ keyboard_mode)
+
+ if hit:
+ column = None
+
+ if keyboard_mode:
+ cursor_path, cursor_col = tree.get_cursor ()
+
+ if path.compare (cursor_path) == 0:
+ column = self.tooltip_columns[cursor_col]
+ else:
+ for col in self.tooltip_columns:
+ area = tree.get_cell_area (path, col)
+
+ if x >= area.x and x < area.x + area.width and \
+ y >= area.y and y < area.y + area.height:
+ column = self.tooltip_columns[col]
+
+ break
+
+ if column is not None:
+ value = None
+
+ if type (column) == int:
+ value = model[iter][column]
+ else:
+ value = column (model[iter])
+
+ if value:
+ tooltip.set_text (str (value))
+
+ return True
+
+ return False
+
+class CellRendererPercentage (Gtk.CellRendererText):
+ padding = 0
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.CellRendererText.__init__ (self, *args, xalign = 1, **kwargs)
+
+ self.value = 0
+
+ @GObject.Property (type = float)
+ def value (self):
+ return self.value_property
+
+ @value.setter
+ def value (self, value):
+ self.value_property = value
+
+ self.set_property ("text", format_percentage (value, 2))
+
+ def do_render (self, cr, widget, background_area, cell_area, flags):
+ full_width = cell_area.width - 2 * self.padding
+ full_height = cell_area.height - 2 * self.padding
+
+ if full_width <= 0 or full_height <= 0:
+ return
+
+ state = widget.get_state_flags()
+ style = widget.get_style_context ()
+ fg_color = style.get_color (state)
+
+ rounded_rectangle (cr,
+ cell_area.x + self.padding,
+ cell_area.y + self.padding,
+ full_width,
+ full_height,
+ 1)
+
+ cr.clip ()
+
+ cr.set_source_rgba (*blend_colors ((0, 0, 0, 0), fg_color, 0.2))
+ cr.paint ()
+
+ Gtk.CellRendererText.do_render (self,
+ cr, widget,
+ background_area, cell_area,
+ flags)
+
+ value = min (max (self.value, 0), 1)
+ width = round (full_width * value)
+ height = full_height
+
+ if width > 0 and height > 0:
+ state = Gtk.StateFlags (state |
+ Gtk.StateFlags.SELECTED)
+ flags = Gtk.CellRendererState (flags |
+ Gtk.CellRendererState.SELECTED)
+
+ style.save ()
+ style.set_state (state)
+
+ x = round ((full_width - width) * self.get_property ("xalign"))
+
+ cr.rectangle (cell_area.x + self.padding + x,
+ cell_area.y + self.padding,
+ width,
+ height)
+
+ cr.clip ()
+
+ Gtk.render_background (style, cr,
+ cell_area.x, cell_area.y,
+ cell_area.width, cell_area.height)
+
+ cr.set_source_rgba (0, 0, 0, 0.25)
+ cr.paint ()
+
+ Gtk.CellRendererText.do_render (self,
+ cr, widget,
+ background_area, cell_area,
+ flags)
+
+ style.restore ()
+
+class ProfileViewer (Gtk.ScrolledWindow):
+ class ThreadFilter (Gtk.TreeView):
+ class Store (Gtk.ListStore):
+ VISIBLE = 0
+ ID = 1
+ NAME = 2
+ STATE = {list (ThreadState)[i]: 3 + i
+ for i in range (len (ThreadState))}
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self,
+ bool, int, str,
+ *(len (self.STATE) * (bool,)))
+
+ threads = list ({thread.id
+ for sample in samples
+ for thread in sample.backtrace or ()})
+ threads.sort ()
+
+ states = [state == ThreadState.RUNNING for state in self.STATE]
+
+ for id in threads:
+ self.append ((False, id, None, *states))
+
+ def get_filter (self):
+ return {row[self.ID]: {state
+ for state, column in self.STATE.items ()
+ if row[column]}
+ for row in self}
+
+ def set_filter (self, filter):
+ for row in self:
+ states = filter[row[self.ID]]
+
+ for state, column in self.STATE.items ():
+ row[column] = state in states
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.TreeView.__init__ (self, *args, **kwargs)
+
+ self.needs_update = True
+
+ store = self.Store ()
+ self.store = store
+
+ filter = Gtk.TreeModelFilter (child_model = store)
+ filter.set_visible_column (store.VISIBLE)
+ self.set_model (filter)
+ self.set_search_column (store.NAME)
+
+ col = Gtk.TreeViewColumn (title = "ID")
+ self.append_column (col)
+
+ cell = Gtk.CellRendererText (xalign = 1)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.ID)
+
+ col = Gtk.TreeViewColumn (title = "Name")
+ self.append_column (col)
+
+ cell = Gtk.CellRendererText ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.NAME)
+
+ for state in store.STATE:
+ col = Gtk.TreeViewColumn (title = str (state))
+ col.column = store.STATE[state]
+ self.append_column (col)
+ col.set_alignment (0.5)
+ col.set_clickable (True)
+
+ def col_clicked (col):
+ active = not all (row[col.column] for row in filter)
+
+ for row in filter:
+ row[col.column] = active
+
+ col.connect ("clicked", col_clicked)
+
+ cell = Gtk.CellRendererToggle ()
+ cell.column = store.STATE[state]
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "active", store.STATE[state])
+
+ def cell_toggled (cell, path):
+ filter[path][cell.column] = not cell.get_property ("active")
+
+ cell.connect ("toggled", cell_toggled)
+
+ selection.connect ("change-complete",
+ self.selection_change_complete)
+
+ def update (self):
+ if not self.needs_update:
+ return
+
+ self.needs_update = False
+
+ sel = selection.get_effective_selection ()
+
+ threads = {thread.id: thread.name
+ for i in sel
+ for thread in samples[i].backtrace or ()}
+
+ for row in self.store:
+ id = row[self.store.ID]
+
+ if id in threads:
+ row[self.store.VISIBLE] = True
+ row[self.store.NAME] = threads[id]
+ else:
+ row[self.store.VISIBLE] = False
+ row[self.store.NAME] = None
+
+ def do_map (self):
+ self.update ()
+
+ Gtk.TreeView.do_map (self)
+
+ def selection_change_complete (self, selection):
+ self.needs_update = True
+
+ if self.get_mapped ():
+ self.update ()
+
+ class ThreadPopover (Gtk.Popover):
+ def __init__ (self, *args, **kwargs):
+ Gtk.Popover.__init__ (self, *args, border_width = 4, **kwargs)
+
+ frame = Gtk.Frame (shadow_type = Gtk.ShadowType.IN)
+ self.add (frame)
+ frame.show ()
+
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ propagate_natural_height = True,
+ max_content_height = 400
+ )
+ frame.add (scrolled)
+ scrolled.show ()
+
+ thread_filter = ProfileViewer.ThreadFilter ()
+ self.thread_filter = thread_filter
+ scrolled.add (thread_filter)
+ thread_filter.show ()
+
+ class Profile (Gtk.Box):
+ ProfileFrame = namedtuple ("ProfileFrame", ("sample", "stack", "i"))
+
+ class Direction (enum.Enum):
+ CALLEES = enum.auto ()
+ CALLERS = enum.auto ()
+
+ class Store (Gtk.ListStore):
+ ID = 0
+ FUNCTION = 1
+ EXCLUSIVE = 2
+ INCLUSIVE = 3
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self,
+ GObject.TYPE_UINT64, str, float, float)
+
+ __gsignals__ = {
+ "needs-update": (GObject.SignalFlags.RUN_FIRST,
+ None, (bool,)),
+ "subprofile-added": (GObject.SignalFlags.RUN_FIRST,
+ None, (Gtk.Widget,)),
+ "subprofile-removed": (GObject.SignalFlags.RUN_FIRST,
+ None, (Gtk.Widget,)),
+ "path-changed": (GObject.SignalFlags.RUN_FIRST,
+ None, ())
+ }
+
+ def __init__ (self,
+ root = None,
+ id = None,
+ title = None,
+ frames = None,
+ direction = Direction.CALLEES,
+ sort = (Store.INCLUSIVE, Gtk.SortType.DESCENDING),
+ *args,
+ **kwargs):
+ Gtk.Box.__init__ (self,
+ *args,
+ orientation = Gtk.Orientation.HORIZONTAL,
+ **kwargs)
+
+ self.root = root or self
+ self.id = id
+ self.frames = frames
+ self.direction = direction
+
+ self.subprofile = None
+
+ vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
+ self.pack_start (vbox, False, False, 0)
+ vbox.show ()
+
+ header = Gtk.HeaderBar (title = title or "All Functions")
+ self.header = header
+ vbox.pack_start (header, False, False, 0)
+ header.show ()
+
+ if not id:
+ popover = ProfileViewer.ThreadPopover ()
+
+ thread_filter_store = popover.thread_filter.store
+
+ self.thread_filter_store = thread_filter_store
+ self.thread_filter = thread_filter_store.get_filter ()
+
+ history.add_source (self.thread_filter_source_get,
+ self.thread_filter_source_set)
+
+ button = Gtk.MenuButton (popover = popover)
+ header.pack_end (button)
+ button.show ()
+
+ button.connect ("toggled", self.thread_filter_button_toggled)
+
+ hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL,
+ spacing = 4)
+ button.add (hbox)
+ hbox.show ()
+
+ label = Gtk.Label (label = "Threads")
+ hbox.pack_start (label, False, False, 0)
+ label.show ()
+
+ image = Gtk.Image.new_from_icon_name ("pan-down-symbolic",
+ Gtk.IconSize.BUTTON)
+ hbox.pack_start (image, False, False, 0)
+ image.show ()
+
+ history.add_source (self.direction_source_get,
+ self.direction_source_set)
+
+ button = Gtk.Button (tooltip_text = "Call-graph direction")
+ header.pack_end (button)
+ button.show ()
+
+ button.connect ("clicked", self.direction_button_clicked)
+
+ image = Gtk.Image ()
+ self.direction_image = image
+ button.add (image)
+ image.show ()
+ else:
+ button = Gtk.Button.new_from_icon_name (
+ "edit-select-symbolic",
+ Gtk.IconSize.BUTTON
+ )
+ header.pack_end (button)
+ button.set_tooltip_text (
+ str (Selection (frame.sample for frame in frames))
+ )
+ button.show ()
+
+ button.connect ("clicked", self.select_samples_clicked)
+
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ )
+ vbox.pack_start (scrolled, True, True, 0)
+ scrolled.show ()
+
+ store = self.Store ()
+ self.store = store
+ store.set_sort_column_id (*sort)
+
+ tree = Gtk.TreeView (model = store)
+ self.tree = tree
+ scrolled.add (tree)
+ tree.set_search_column (store.FUNCTION)
+ tree.show ()
+
+ tree.get_selection ().connect ("changed",
+ self.tree_selection_changed)
+
+ tree.connect ("row-activated", self.tree_row_activated)
+ tree.connect ("key-press-event", self.tree_key_press_event)
+
+ col = Gtk.TreeViewColumn (title = "Function")
+ tree.append_column (col)
+ col.set_resizable (True)
+ col.set_sort_column_id (store.FUNCTION)
+
+ cell = Gtk.CellRendererText (ellipsize = Pango.EllipsizeMode.END)
+ col.pack_start (cell, True)
+ col.add_attribute (cell, "text", store.FUNCTION)
+ cell.set_property ("width-chars", 40)
+
+ col = Gtk.TreeViewColumn (title = "Self")
+ tree.append_column (col)
+ col.set_alignment (0.5)
+ col.set_sort_column_id (store.EXCLUSIVE)
+
+ cell = CellRendererPercentage ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "value", store.EXCLUSIVE)
+
+ col = Gtk.TreeViewColumn (title = "All")
+ tree.append_column (col)
+ col.set_alignment (0.5)
+ col.set_sort_column_id (store.INCLUSIVE)
+
+ cell = CellRendererPercentage ()
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "value", store.INCLUSIVE)
+
+ if id:
+ self.update ()
+
+ def update (self):
+ self.remove_subprofile ()
+
+ if not self.id:
+ self.update_frames ()
+
+ self.update_store ()
+
+ self.update_ui ()
+
+ def update_frames (self):
+ self.frames = []
+
+ for i in selection.get_effective_selection ():
+ for thread in samples[i].backtrace or []:
+ if thread.state in self.thread_filter[thread.id]:
+ thread_frames = thread.frames
+
+ if self.direction == self.Direction.CALLERS:
+ thread_frames = reversed (thread_frames)
+
+ stack = []
+ prev_id = 0
+
+ for frame in thread_frames:
+ id = frame.info.id
+
+ if id == prev_id:
+ continue
+
+ self.frames.append (self.ProfileFrame (
+ sample = i,
+ stack = stack,
+ i = len (stack)
+ ))
+
+ stack.append (frame)
+
+ prev_id = id
+
+ def update_store (self):
+ stacks = {}
+ symbols = {}
+
+ sort = self.store.get_sort_column_id ()
+
+ self.store = self.Store ()
+
+ for frame in self.frames:
+ info = frame.stack[frame.i].info
+ symbol_id = info.id
+ stack_id = builtins.id (frame.stack)
+
+ symbol = symbols.get (symbol_id, None)
+
+ if not symbol:
+ symbol = [info, 0, 0]
+ symbols[symbol_id] = symbol
+
+ stack = stacks.get (stack_id, None)
+
+ if not stack:
+ stack = set ()
+ stacks[stack_id] = stack
+
+ if frame.i == 0:
+ symbol[1] += 1
+
+ if symbol_id not in stack:
+ stack.add (symbol_id)
+
+ symbol[2] += 1
+
+ n_stacks = len (stacks)
+
+ for symbol in symbols.values ():
+ id = symbol[0].id
+ name = symbol[0].name if id != self.id else "[Self]"
+
+ self.store.append ((id,
+ name,
+ symbol[1] / n_stacks,
+ symbol[2] / n_stacks))
+
+ self.store.set_sort_column_id (*sort)
+
+ self.tree.set_model (self.store)
+ self.tree.set_search_column (self.store.FUNCTION)
+
+ def update_ui (self):
+ if not self.id:
+ if self.direction == self.Direction.CALLEES:
+ icon_name = "format-indent-more-symbolic"
+ else:
+ icon_name = "format-indent-less-symbolic"
+
+ self.direction_image.set_from_icon_name (icon_name,
+ Gtk.IconSize.BUTTON)
+ else:
+ if self.direction == self.Direction.CALLEES:
+ subtitle = "Callees"
+ else:
+ subtitle = "Callers"
+
+ self.header.set_subtitle (subtitle)
+
+ def select (self, id):
+ if id is not None:
+ for row in self.store:
+ if row[self.store.ID] == id:
+ iter = row.iter
+ path = self.store.get_path (iter)
+
+ self.tree.get_selection ().select_iter (iter)
+
+ self.tree.scroll_to_cell (path, None, True, 0.5, 0)
+
+ break
+ else:
+ self.tree.get_selection ().unselect_all ()
+
+ def add_subprofile (self, subprofile):
+ self.remove_subprofile ()
+
+ box = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
+ self.subprofile_box = box
+ self.pack_start (box, True, True, 0)
+ box.show ()
+
+ separator = Gtk.Separator (orientation = Gtk.Orientation.VERTICAL)
+ box.pack_start (separator, False, False, 0)
+ separator.show ()
+
+ self.subprofile = subprofile
+ box.pack_start (subprofile, True, True, 0)
+ subprofile.show ()
+
+ subprofile.connect ("subprofile-added",
+ lambda profile, subprofile:
+ self.emit ("subprofile-added",
+ subprofile))
+ subprofile.connect ("subprofile-removed",
+ lambda profile, subprofile:
+ self.emit ("subprofile-removed",
+ subprofile))
+ subprofile.connect ("path-changed",
+ lambda profile: self.emit ("path-changed"))
+
+ self.emit ("subprofile-added", subprofile)
+
+ def remove_subprofile (self):
+ if self.subprofile:
+ subprofile = self.subprofile
+
+ self.remove (self.subprofile_box)
+
+ self.subprofile = None
+ self.subprofile_box = None
+
+ self.emit ("subprofile-removed", subprofile)
+
+ def get_path (self):
+ tree_sel = self.tree.get_selection ()
+
+ sel_rows = tree_sel.get_selected_rows ()[1]
+
+ if not sel_rows:
+ return ()
+
+ id = self.store[sel_rows[0]][self.store.ID]
+
+ if self.subprofile:
+ return (id,) + self.subprofile.get_path ()
+ else:
+ return (id,)
+
+ def set_path (self, path):
+ self.select (path[0] if path else None)
+
+ if self.subprofile:
+ self.subprofile.set_path (path[1:])
+
+ def thread_filter_source_get (self):
+ return self.thread_filter_store.get_filter ()
+
+ def thread_filter_source_set (self, thread_filter):
+ self.thread_filter = thread_filter
+
+ self.thread_filter_store.set_filter (thread_filter)
+
+ self.emit ("needs-update", False)
+
+ def thread_filter_button_toggled (self, button):
+ if not button.get_active ():
+ thread_filter = self.thread_filter_store.get_filter ()
+
+ if thread_filter != self.thread_filter:
+ self.thread_filter = thread_filter
+
+ history.start_group ()
+
+ history.record ()
+
+ self.emit ("needs-update", True)
+
+ history.end_group ()
+
+ def direction_source_get (self):
+ return self.direction
+
+ def direction_source_set (self, direction):
+ self.direction = direction
+
+ self.emit ("needs-update", False)
+
+ def direction_button_clicked (self, button):
+ if self.direction == self.Direction.CALLEES:
+ self.direction = self.Direction.CALLERS
+ else:
+ self.direction = self.Direction.CALLEES
+
+ history.start_group ()
+
+ history.record ()
+
+ self.emit ("needs-update", True)
+
+ history.end_group ()
+
+ def select_samples_clicked (self, button):
+ selection.select ({frame.sample for frame in self.frames})
+
+ selection.change_complete ()
+
+ def tree_selection_changed (self, tree_sel):
+ self.remove_subprofile ()
+
+ sel_rows = tree_sel.get_selected_rows ()[1]
+
+ if not sel_rows:
+ self.emit ("path-changed")
+
+ return
+
+ id = self.store[sel_rows[0]][self.store.ID]
+ title = self.store[sel_rows[0]][self.store.FUNCTION]
+
+ frames = []
+
+ for frame in self.frames:
+ if frame.stack[frame.i].info.id == id:
+ frames.append (frame)
+
+ if frame.i > 0 and id != self.id:
+ frames.append (self.ProfileFrame (sample = frame.sample,
+ stack = frame.stack,
+ i = frame.i - 1))
+
+ if id != self.id:
+ self.add_subprofile (ProfileViewer.Profile (
+ self.root,
+ id,
+ title,
+ frames,
+ self.direction,
+ self.store.get_sort_column_id ()
+ ))
+ else:
+ filenames = {frame.stack[frame.i].info.source
+ for frame in frames}
+ filenames = list (filter (bool, filenames))
+
+ if len (filenames) == 1:
+ file = find_file (filenames[0])
+
+ if file:
+ self.add_subprofile (ProfileViewer.SourceProfile (
+ file,
+ frames[0].stack[frames[0].i].info.name,
+ frames
+ ))
+
+ self.emit ("path-changed")
+
+ def tree_row_activated (self, tree, path, col):
+ if self.root != self:
+ self.root.select (self.store[path][self.store.ID])
+
+ def tree_key_press_event (self, tree, event):
+ if event.keyval == Gdk.KEY_Escape:
+ self.select (None)
+
+ if self.root is not self:
+ self.get_parent ().get_ancestor (
+ ProfileViewer.Profile
+ ).tree.grab_focus ()
+
+ return True
+
+ return False
+
+ class SourceProfile (Gtk.Box):
+ class Store (Gtk.ListStore):
+ LINE = 0
+ HAS_FRAMES = 1
+ EXCLUSIVE = 2
+ INCLUSIVE = 3
+ TEXT = 4
+
+ def __init__ (self):
+ Gtk.ListStore.__init__ (self, int, bool, float, float, str)
+
+ __gsignals__ = {
+ "subprofile-added": (GObject.SignalFlags.RUN_FIRST,
+ None, (Gtk.Widget,)),
+ "subprofile-removed": (GObject.SignalFlags.RUN_FIRST,
+ None, (Gtk.Widget,)),
+ "path-changed": (GObject.SignalFlags.RUN_FIRST,
+ None, ())
+ }
+
+ def __init__ (self,
+ file,
+ function,
+ frames,
+ *args,
+ **kwargs):
+ Gtk.Box.__init__ (self,
+ *args,
+ orientation = Gtk.Orientation.VERTICAL,
+ **kwargs)
+
+ self.file = file
+ self.frames = frames
+
+ header = Gtk.HeaderBar (title = file.get_basename (),
+ subtitle = function)
+ self.header = header
+ self.pack_start (header, False, False, 0)
+ header.show ()
+
+ box = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
+ header.pack_start (box)
+ box.get_style_context ().add_class ("linked")
+ box.get_style_context ().add_class ("raised")
+ box.show ()
+
+ button = Gtk.Button.new_from_icon_name ("go-up-symbolic",
+ Gtk.IconSize.BUTTON)
+ self.prev_button = button
+ box.pack_start (button, False, True, 0)
+ button.show ()
+
+ button.connect ("clicked", lambda *args: self.move (-1))
+
+ button = Gtk.Button.new_from_icon_name ("go-down-symbolic",
+ Gtk.IconSize.BUTTON)
+ self.next_button = button
+ box.pack_end (button, False, True, 0)
+ button.show ()
+
+ button.connect ("clicked", lambda *args: self.move (+1))
+
+ button = Gtk.Button.new_from_icon_name ("edit-select-symbolic",
+ Gtk.IconSize.BUTTON)
+ self.select_samples_button = button
+ header.pack_end (button)
+ button.show ()
+
+ button.connect ("clicked", self.select_samples_clicked)
+
+ button = Gtk.Button.new_from_icon_name ("text-x-generic-symbolic",
+ Gtk.IconSize.BUTTON)
+ header.pack_end (button)
+ button.set_tooltip_text (file.get_path ())
+ button.show ()
+
+ button.connect ("clicked", self.view_source_clicked)
+
+ scrolled = Gtk.ScrolledWindow (
+ hscrollbar_policy = Gtk.PolicyType.NEVER,
+ vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ )
+ self.pack_start (scrolled, True, True, 0)
+ scrolled.show ()
+
+ store = self.Store ()
+ self.store = store
+
+ tree = Gtk.TreeView (model = store)
+ self.tree = tree
+ scrolled.add (tree)
+ tree.set_search_column (store.LINE)
+ tree.show ()
+
+ tree.get_selection ().connect ("changed",
+ self.tree_selection_changed)
+
+ scale = 0.85
+
+ col = Gtk.TreeViewColumn (title = "Self")
+ tree.append_column (col)
+ col.set_alignment (0.5)
+ col.set_sort_column_id (store.EXCLUSIVE)
+
+ cell = CellRendererPercentage (scale = scale)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "visible", store.HAS_FRAMES)
+ col.add_attribute (cell, "value", store.EXCLUSIVE)
+
+ col = Gtk.TreeViewColumn (title = "All")
+ tree.append_column (col)
+ col.set_alignment (0.5)
+ col.set_sort_column_id (store.INCLUSIVE)
+
+ cell = CellRendererPercentage (scale = scale)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "visible", store.HAS_FRAMES)
+ col.add_attribute (cell, "value", store.INCLUSIVE)
+
+ col = Gtk.TreeViewColumn ()
+ tree.append_column (col)
+
+ cell = Gtk.CellRendererText (xalign = 1,
+ xpad = 8,
+ family = "Monospace",
+ weight = Pango.Weight.BOLD,
+ scale = scale)
+ col.pack_start (cell, False)
+ col.add_attribute (cell, "text", store.LINE)
+
+ cell = Gtk.CellRendererText (family = "Monospace",
+ scale = scale)
+ col.pack_start (cell, True)
+ col.add_attribute (cell, "text", store.TEXT)
+
+ self.update ()
+
+ def get_samples (self):
+ sel_rows = self.tree.get_selection ().get_selected_rows ()[1]
+
+ if sel_rows:
+ line = self.store[sel_rows[0]][self.store.LINE]
+
+ sel = {frame.sample for frame in self.frames
+ if frame.stack[frame.i].info.line == line}
+
+ return sel
+ else:
+ return {}
+
+ def update (self):
+ self.update_store ()
+ self.update_ui ()
+
+ def update_store (self):
+ stacks = {}
+ lines = {}
+
+ for frame in self.frames:
+ info = frame.stack[frame.i].info
+ line_id = info.line
+ stack_id = builtins.id (frame.stack)
+
+ line = lines.get (line_id, None)
+
+ if not line:
+ line = [0, 0]
+ lines[line_id] = line
+
+ stack = stacks.get (stack_id, None)
+
+ if not stack:
+ stack = set ()
+ stacks[stack_id] = stack
+
+ if frame.i == 0:
+ line[0] += 1
+
+ if line_id not in stack:
+ stack.add (line_id)
+
+ line[1] += 1
+
+ self.lines = list (lines.keys ())
+ self.lines.sort ()
+
+ n_stacks = len (stacks)
+
+ self.store.clear ()
+
+ i = 1
+
+ for text in open (self.file.get_path (), "r"):
+ text = text.rstrip ("\n")
+
+ line = lines.get (i, None)
+
+ if line:
+ self.store.append ((i,
+ True,
+ line[0] / n_stacks,
+ line[1] / n_stacks,
+ text))
+ else:
+ self.store.append ((i,
+ False,
+ 0,
+ 0,
+ text))
+
+ i += 1
+
+ self.select (max (lines.items (), key = lambda line: line[1][1])[0])
+
+ def update_ui (self):
+ sel_rows = self.tree.get_selection ().get_selected_rows ()[1]
+
+ if sel_rows:
+ line = self.store[sel_rows[0]][self.store.LINE]
+
+ i = bisect.bisect_left (self.lines, line)
+
+ self.prev_button.set_sensitive (i > 0)
+
+ if i < len (self.lines) and self.lines[i] == line:
+ i += 1
+
+ self.next_button.set_sensitive (i < len (self.lines))
+ else:
+ self.prev_button.set_sensitive (False)
+ self.next_button.set_sensitive (False)
+
+ samples = self.get_samples ()
+
+ if samples:
+ self.select_samples_button.set_sensitive (True)
+ self.select_samples_button.set_tooltip_text (
+ str (Selection (samples))
+ )
+ else:
+ self.select_samples_button.set_sensitive (False)
+ self.select_samples_button.set_tooltip_text (None)
+
+ def select (self, line):
+ if line is not None:
+ for row in self.store:
+ if row[self.store.LINE] == line:
+ iter = row.iter
+ path = self.store.get_path (iter)
+
+ self.tree.get_selection ().select_iter (iter)
+
+ self.tree.scroll_to_cell (path, None, True, 0.5, 0)
+
+ break
+ else:
+ self.tree.get_selection ().unselect_all ()
+
+
+ def move (self, dir):
+ if dir == 0:
+ return
+
+ sel_rows = self.tree.get_selection ().get_selected_rows ()[1]
+
+ if sel_rows:
+ line = self.store[sel_rows[0]][self.store.LINE]
+
+ i = bisect.bisect_left (self.lines, line)
+
+ if dir < 0:
+ i -= 1
+ elif i < len (self.lines) and self.lines[i] == line:
+ i += 1
+
+ if i >= 0 and i < len (self.lines):
+ self.select (self.lines[i])
+ else:
+ self.select (None)
+
+ def select_samples_clicked (self, button):
+ selection.select (self.get_samples ())
+
+ selection.change_complete ()
+
+ def view_source_clicked (self, button):
+ line = 0
+
+ sel_rows = self.tree.get_selection ().get_selected_rows ()[1]
+
+ if sel_rows:
+ line = self.store[sel_rows[0]][self.store.LINE]
+
+ run_editor (self.file, line)
+
+ def get_path (self):
+ tree_sel = self.tree.get_selection ()
+
+ sel_rows = tree_sel.get_selected_rows ()[1]
+
+ if not sel_rows:
+ return ()
+
+ line = self.store[sel_rows[0]][self.store.LINE]
+
+ return (line,)
+
+ def set_path (self, path):
+ self.select (path[0] if path else None)
+
+ def tree_selection_changed (self, tree_sel):
+ self.update_ui ()
+
+ self.emit ("path-changed")
+
+ def __init__ (self, *args, **kwargs):
+ Gtk.ScrolledWindow.__init__ (
+ self,
+ *args,
+ hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
+ vscrollbar_policy = Gtk.PolicyType.NEVER,
+ **kwargs
+ )
+
+ self.adjustment_changed_handler = None
+ self.needs_update = True
+ self.path = ()
+
+ profile = self.Profile ()
+ self.root_profile = profile
+ self.add (profile)
+ profile.show ()
+
+ selection.connect ("change-complete", self.selection_change_complete)
+
+ profile.connect ("needs-update", self.profile_needs_update)
+ profile.connect ("subprofile-added", self.profile_subprofile_added)
+ profile.connect ("subprofile-removed", self.profile_subprofile_removed)
+ profile.connect ("path-changed", self.profile_path_changed)
+
+ history.add_source (self.source_get, self.source_set)
+
+ @GObject.Property (type = bool, default = False)
+ def available (self):
+ sel = selection.get_effective_selection ()
+
+ if len (sel) > 1:
+ return any (samples[i].backtrace for i in sel)
+
+ return False
+
+ def update (self):
+ if not self.available:
+ return
+
+ history.block ()
+
+ if self.needs_update:
+ self.root_profile.update ()
+
+ self.needs_update = False
+
+ self.root_profile.set_path (self.path)
+
+ history.unblock ()
+
+ def queue_update (self, now = False):
+ self.needs_update = True
+
+ if now or self.get_mapped ():
+ self.update ()
+
+ def do_map (self):
+ self.update ()
+
+ Gtk.ScrolledWindow.do_map (self)
+
+ def selection_change_complete (self, selection):
+ self.queue_update ()
+
+ self.notify ("available")
+
+ def profile_needs_update (self, profile, now):
+ self.queue_update (now)
+
+ def profile_subprofile_added (self, profile, subprofile):
+ if not history.is_blocked ():
+ self.path = profile.get_path ()
+
+ history.record ()
+
+ if not self.adjustment_changed_handler:
+ adjustment = self.get_hadjustment ()
+
+ def adjustment_changed (adjustment):
+ GObject.signal_handler_disconnect (
+ adjustment,
+ self.adjustment_changed_handler
+ )
+ self.adjustment_changed_handler = None
+
+ adjustment.set_value (adjustment.get_upper ())
+
+ self.adjustment_changed_handler = adjustment.connect (
+ "changed",
+ adjustment_changed
+ )
+
+ def profile_subprofile_removed (self, profile, subprofile):
+ if not history.is_blocked ():
+ self.path = profile.get_path ()
+
+ history.record ()
+
+
+ def profile_path_changed (self, profile):
+ if not history.is_blocked ():
+ self.path = profile.get_path ()
+
+ history.update ()
+
+ def source_get (self):
+ return self.path
+
+ def source_set (self, path):
+ self.path = path
+
+ if self.get_mapped ():
+ self.root_profile.set_path (path)
+
+class LogViewer (Gtk.Window):
+ def __init__ (self, *args, **kwargs):
+ Gtk.Window.__init__ (
+ self,
+ *args,
+ default_width = 1024,
+ default_height = 768,
+ window_position = Gtk.WindowPosition.CENTER,
+ **kwargs)
+
+ header = Gtk.HeaderBar (
+ title = "GIMP Performance Log Viewer",
+ show_close_button = True
+ )
+ self.header = header
+ self.set_titlebar (header)
+ header.show ()
+
+ box = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
+ header.pack_start (box)
+ box.get_style_context ().add_class ("linked")
+ box.get_style_context ().add_class ("raised")
+ box.show ()
+
+ button = Gtk.Button.new_from_icon_name ("go-previous-symbolic",
+ Gtk.IconSize.BUTTON)
+ box.pack_start (button, False, True, 0)
+ button.show ()
+
+ history.bind_property ("can-undo",
+ button, "sensitive",
+ GObject.BindingFlags.SYNC_CREATE)
+
+ button.connect ("clicked", lambda *args: history.undo ())
+
+ button = Gtk.Button.new_from_icon_name ("go-next-symbolic",
+ Gtk.IconSize.BUTTON)
+ box.pack_end (button, False, True, 0)
+ button.show ()
+
+ history.bind_property ("can-redo",
+ button, "sensitive",
+ GObject.BindingFlags.SYNC_CREATE)
+
+ button.connect ("clicked", lambda *args: history.redo ())
+
+ button = Gtk.MenuButton ()
+ header.pack_end (button)
+ button.set_tooltip_text ("Find samples")
+ button.show ()
+
+ image = Gtk.Image.new_from_icon_name ("edit-find-symbolic",
+ Gtk.IconSize.BUTTON)
+ button.add (image)
+ image.show ()
+
+ popover = FindSamplesPopover (relative_to = button)
+ self.find_popover = popover
+ button.set_popover (popover)
+
+ def selection_action (action):
+ def f (*args):
+ action (selection)
+ selection.change_complete ()
+
+ return f
+
+ button = Gtk.Button.new_from_icon_name (
+ "object-flip-horizontal-symbolic",
+ Gtk.IconSize.BUTTON
+ )
+ header.pack_end (button)
+ button.set_tooltip_text ("Invert selection")
+ button.show ()
+
+ button.connect ("clicked", selection_action (Selection.invert))
+
+ button = Gtk.Button.new_from_icon_name (
+ "edit-clear-symbolic",
+ Gtk.IconSize.BUTTON
+ )
+ self.clear_selection_button = button
+ header.pack_end (button)
+ button.set_tooltip_text ("Clear selection")
+ button.show ()
+
+ button.connect ("clicked", selection_action (Selection.clear))
+
+ paned = Gtk.Paned (orientation = Gtk.Orientation.VERTICAL)
+ self.paned = paned
+ self.add (paned)
+ paned.set_position (144)
+ paned.show ()
+
+ graphs = SampleGraphList ()
+ paned.add1 (graphs)
+ paned.child_set (graphs, shrink = False)
+ graphs.show ()
+
+ hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
+ paned.add2 (hbox)
+ hbox.show ()
+
+ sidebar = Gtk.StackSidebar ()
+ hbox.pack_start (sidebar, False, False, 0)
+ sidebar.show ()
+
+ stack = Gtk.Stack (transition_type = Gtk.StackTransitionType.CROSSFADE)
+ self.stack = stack
+ hbox.pack_start (stack, True, True, 0)
+ stack.show ()
+
+ sidebar.set_stack (stack)
+
+ info_viewer = InformationViewer ()
+ stack.add_titled (info_viewer, "information", "Information")
+ info_viewer.show ()
+
+ if markers:
+ markers_viewer = MarkersViewer ()
+ stack.add_titled (markers_viewer, "markers", "Markers")
+ markers_viewer.show ()
+
+ vars_viewer = VariablesViewer ()
+ stack.add_titled (vars_viewer, "variables", "Variables")
+ vars_viewer.show ()
+
+ box = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
+ self.cflow_box = box
+ stack.add_named (box, "cflow")
+
+ backtrace_viewer = BacktraceViewer ()
+ self.backtrace_viewer = backtrace_viewer
+ box.pack_start (backtrace_viewer, True, True, 0)
+
+ backtrace_viewer.bind_property ("available",
+ backtrace_viewer, "visible",
+ GObject.BindingFlags.SYNC_CREATE)
+
+ backtrace_viewer.connect ("notify::available",
+ self.cflow_notify_available)
+
+ profile_viewer = ProfileViewer ()
+ self.profile_viewer = profile_viewer
+ box.pack_start (profile_viewer, True, True, 0)
+
+ profile_viewer.bind_property ("available",
+ profile_viewer, "visible",
+ GObject.BindingFlags.SYNC_CREATE)
+
+ profile_viewer.connect ("notify::available",
+ self.cflow_notify_available)
+
+ self.cflow_notify_available (self)
+
+ selection.connect ("change-complete", self.selection_change_complete)
+
+ self.selection_change_complete (selection)
+
+ def selection_change_complete (self, selection):
+ self.header.set_subtitle (str (selection))
+ self.clear_selection_button.set_sensitive (selection.selection)
+
+ def cflow_notify_available (self, *args):
+ if self.backtrace_viewer.available:
+ self.stack.child_set (self.cflow_box, title = "Backtrace")
+ self.cflow_box.show ()
+ elif self.profile_viewer.available:
+ self.stack.child_set (self.cflow_box, title = "Profile")
+ self.cflow_box.show ()
+ else:
+ self.cflow_box.hide ()
+
+Gtk.Settings.get_default ().set_property ("gtk-application-prefer-dark-theme",
+ True)
+
+window = LogViewer ()
+window.show ()
+
+window.connect ("destroy", Gtk.main_quit)
+
+history.record ()
+
+Gtk.main ()
diff --git a/tools/svg-contrast.c b/tools/svg-contrast.c
new file mode 100644
index 0000000..11cf136
--- /dev/null
+++ b/tools/svg-contrast.c
@@ -0,0 +1,117 @@
+/* svg-contrast.c
+ * Copyright (C) 2016 Jehan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gio/gio.h>
+#include <glib/gprintf.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* This tool inverts grey colors in a SVG image.
+ * Non-grey colors are not touched, since they are considered
+ * exceptions that we would want to keep the same (for instance
+ * Red, Blue or Green channel representations).
+ *
+ * It is not based off XML knowledge since colors could appear in
+ * various fields. Instead we just assume that a color has the XML
+ * format "#RRGGBB" and we use regular expression to update these.
+ */
+static gboolean
+rgb_color_contrast (const GMatchInfo *info,
+ GString *res,
+ gpointer data)
+{
+ gchar *match;
+ gchar *adjusted;
+ gdouble contrast;
+ gdouble value;
+ gint value_u8;
+
+ contrast = *(const gdouble *) data;
+
+ /* We only adjust grey colors, so we just need the first channel. */
+ match = g_match_info_fetch (info, 1);
+ value_u8 = strtol (match, NULL, 16);
+ value = value_u8 / 255.0;
+ value = 0.5 + contrast * (value - 0.5);
+ value = CLAMP (value, 0.0, 1.0);
+ value_u8 = floor (255.0 * value + 0.5);
+ adjusted = g_strdup_printf ("#%02x%02x%02x",
+ value_u8, value_u8, value_u8);
+
+ g_string_append (res, adjusted);
+ g_free (adjusted);
+ g_free (match);
+
+ return FALSE;
+}
+
+int main (int argc, char **argv)
+{
+ gchar *input;
+ gchar *output;
+ gdouble contrast;
+ GFile *file;
+ gchar *contents;
+ gchar *replaced;
+ GRegex *regex;
+ gint retval = 0;
+
+ if (argc != 4)
+ {
+ g_fprintf (stderr, "Usage: svg-contrast input output [contrast]\n");
+ return 1;
+ }
+ input = argv[1];
+ output = argv[2];
+ contrast = atof (argv[3]);
+
+ file = g_file_new_for_path (input);
+ if (! g_file_load_contents (file, NULL, &contents, NULL, NULL, NULL))
+ {
+ g_fprintf (stderr,
+ "Error: svg-contrast could not load contents of file %s.\n",
+ input);
+ g_object_unref (file);
+ return 1;
+ }
+ g_object_unref (file);
+
+ /* Replace grey colors only. */
+ regex = g_regex_new ("#([0-9a-fA-F]{2}){3}\\b", 0, 0, NULL);
+ replaced = g_regex_replace_eval (regex, contents, -1, 0, 0,
+ rgb_color_contrast, &contrast, NULL);
+
+ file = g_file_new_for_path (output);
+ if (! g_file_replace_contents (file, replaced, strlen (replaced),
+ NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION,
+ NULL, NULL, NULL))
+ {
+ g_fprintf (stderr,
+ "Error: svg-contrast could not save file %s.\n",
+ output);
+ retval = 1;
+ }
+
+ g_object_unref (file);
+ g_free (contents);
+ g_free (replaced);
+ g_regex_unref (regex);
+
+ return retval;
+}