diff options
Diffstat (limited to '')
-rw-r--r-- | tools/Makefile.am | 119 | ||||
-rw-r--r-- | tools/Makefile.in | 1080 | ||||
-rwxr-xr-x | tools/defcheck.py | 126 | ||||
-rw-r--r-- | tools/gimp-debug-resume.c | 113 | ||||
-rwxr-xr-x | tools/gimp-mkenums | 577 | ||||
-rw-r--r-- | tools/gimp-test-clipboard.c | 443 | ||||
-rwxr-xr-x | tools/gimppath2svg.py | 117 | ||||
-rw-r--r-- | tools/gimptool.c | 1142 | ||||
-rw-r--r-- | tools/kernelgen.c | 128 | ||||
-rwxr-xr-x | tools/mnemonic-clashes | 96 | ||||
-rwxr-xr-x | tools/performance-log-close-tags.py | 49 | ||||
-rwxr-xr-x | tools/performance-log-coalesce.py | 62 | ||||
-rwxr-xr-x | tools/performance-log-deduce.py | 91 | ||||
-rwxr-xr-x | tools/performance-log-expand.py | 107 | ||||
-rwxr-xr-x | tools/performance-log-progressive-coalesce.py | 54 | ||||
-rwxr-xr-x | tools/performance-log-resolve.py | 55 | ||||
-rwxr-xr-x | tools/performance-log-viewer | 39 | ||||
-rwxr-xr-x | tools/performance-log-viewer.py | 3662 | ||||
-rw-r--r-- | tools/svg-contrast.c | 117 |
19 files changed, 8177 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..ac64b0a --- /dev/null +++ b/tools/Makefile.in @@ -0,0 +1,1080 @@ +# Makefile.in generated by automake 1.16.2 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +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)/m4macros/gtk-doc.m4 \ + $(top_srcdir)/m4macros/intltool.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4macros/alsa.m4 \ + $(top_srcdir)/m4macros/ax_compare_version.m4 \ + $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \ + $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \ + $(top_srcdir)/m4macros/detectcflags.m4 \ + $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@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_MNG = @FILE_MNG@ +FILE_PDF_SAVE = @FILE_PDF_SAVE@ +FILE_PS = @FILE_PS@ +FILE_WMF = @FILE_WMF@ +FILE_XMC = @FILE_XMC@ +FILE_XPM = @FILE_XPM@ +FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@ +FONTCONFIG_LIBS = @FONTCONFIG_LIBS@ +FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@ +FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@ +FREETYPE_CFLAGS = @FREETYPE_CFLAGS@ +FREETYPE_LIBS = @FREETYPE_LIBS@ +GDBUS_CODEGEN = @GDBUS_CODEGEN@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@ +GEGL = @GEGL@ +GEGL_CFLAGS = @GEGL_CFLAGS@ +GEGL_LIBS = @GEGL_LIBS@ +GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@ +GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GEXIV2_CFLAGS = @GEXIV2_CFLAGS@ +GEXIV2_LIBS = @GEXIV2_LIBS@ +GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@ +GIMP_API_VERSION = @GIMP_API_VERSION@ +GIMP_APP_VERSION = @GIMP_APP_VERSION@ +GIMP_BINARY_AGE = @GIMP_BINARY_AGE@ +GIMP_COMMAND = @GIMP_COMMAND@ +GIMP_DATA_VERSION = @GIMP_DATA_VERSION@ +GIMP_FULL_NAME = @GIMP_FULL_NAME@ +GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@ +GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@ +GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@ +GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@ +GIMP_MKENUMS = @GIMP_MKENUMS@ +GIMP_MODULES = @GIMP_MODULES@ +GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@ +GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@ +GIMP_PLUGINS = @GIMP_PLUGINS@ +GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@ +GIMP_REAL_VERSION = @GIMP_REAL_VERSION@ +GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@ +GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@ +GIMP_UNSTABLE = @GIMP_UNSTABLE@ +GIMP_USER_VERSION = @GIMP_USER_VERSION@ +GIMP_VERSION = @GIMP_VERSION@ +GIO_CFLAGS = @GIO_CFLAGS@ +GIO_LIBS = @GIO_LIBS@ +GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@ +GIO_UNIX_LIBS = @GIO_UNIX_LIBS@ +GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@ +GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@ +GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@ +GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GREP = @GREP@ +GS_LIBS = @GS_LIBS@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@ +GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@ +GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@ +GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@ +HARFBUZZ_LIBS = @HARFBUZZ_LIBS@ +HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@ +HAVE_CXX14 = @HAVE_CXX14@ +HAVE_FINITE = @HAVE_FINITE@ +HAVE_ISFINITE = @HAVE_ISFINITE@ +HAVE_VFORK = @HAVE_VFORK@ +HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@ +ISO_CODES_LOCATION = @ISO_CODES_LOCATION@ +JPEG_LIBS = @JPEG_LIBS@ +JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@ +JSON_GLIB_LIBS = @JSON_GLIB_LIBS@ +LCMS_CFLAGS = @LCMS_CFLAGS@ +LCMS_LIBS = @LCMS_LIBS@ +LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@ +LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@ +LIBHEIF_LIBS = @LIBHEIF_LIBS@ +LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@ +LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@ +LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@ +LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@ +LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@ +LIBOBJS = @LIBOBJS@ +LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LT_VERSION_INFO = @LT_VERSION_INFO@ +LZMA_CFLAGS = @LZMA_CFLAGS@ +LZMA_LIBS = @LZMA_LIBS@ +MAIL = @MAIL@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@ +MIME_INFO_LIBS = @MIME_INFO_LIBS@ +MIME_TYPES = @MIME_TYPES@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@ +MNG_CFLAGS = @MNG_CFLAGS@ +MNG_LIBS = @MNG_LIBS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@ +MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@ +NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@ +NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENEXR_CFLAGS = @OPENEXR_CFLAGS@ +OPENEXR_LIBS = @OPENEXR_LIBS@ +OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@ +OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@ +OPENJPEG_LIBS = @OPENJPEG_LIBS@ +OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@ +PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@ +PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@ +PATHSEP = @PATHSEP@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@ +PERL_VERSION = @PERL_VERSION@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PNG_CFLAGS = @PNG_CFLAGS@ +PNG_LIBS = @PNG_LIBS@ +POFILES = @POFILES@ +POPPLER_CFLAGS = @POPPLER_CFLAGS@ +POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@ +POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@ +POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@ +POPPLER_LIBS = @POPPLER_LIBS@ +POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PYBIN_PATH = @PYBIN_PATH@ +PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@ +PYCAIRO_LIBS = @PYCAIRO_LIBS@ +PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@ +PYGTK_CFLAGS = @PYGTK_CFLAGS@ +PYGTK_CODEGEN = @PYGTK_CODEGEN@ +PYGTK_DEFSDIR = @PYGTK_DEFSDIR@ +PYGTK_LIBS = @PYGTK_LIBS@ +PYLINK_LIBS = @PYLINK_LIBS@ +PYTHON = @PYTHON@ +PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_INCLUDES = @PYTHON_INCLUDES@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@ +RT_LIBS = @RT_LIBS@ +SCREENSHOT_LIBS = @SCREENSHOT_LIBS@ +SED = @SED@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SOCKET_LIBS = @SOCKET_LIBS@ +SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@ +SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@ +SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@ +STRIP = @STRIP@ +SVG_CFLAGS = @SVG_CFLAGS@ +SVG_LIBS = @SVG_LIBS@ +SYMPREFIX = @SYMPREFIX@ +TIFF_LIBS = @TIFF_LIBS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WEBKIT_CFLAGS = @WEBKIT_CFLAGS@ +WEBKIT_LIBS = @WEBKIT_LIBS@ +WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@ +WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@ +WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@ +WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@ +WEBPMUX_LIBS = @WEBPMUX_LIBS@ +WEBP_CFLAGS = @WEBP_CFLAGS@ +WEBP_LIBS = @WEBP_LIBS@ +WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@ +WEB_PAGE = @WEB_PAGE@ +WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@ +WINDRES = @WINDRES@ +WMF_CFLAGS = @WMF_CFLAGS@ +WMF_CONFIG = @WMF_CONFIG@ +WMF_LIBS = @WMF_LIBS@ +WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@ +XDG_EMAIL = @XDG_EMAIL@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@ +XMC_CFLAGS = @XMC_CFLAGS@ +XMC_LIBS = @XMC_LIBS@ +XMKMF = @XMKMF@ +XMLLINT = @XMLLINT@ +XMU_LIBS = @XMU_LIBS@ +XPM_LIBS = @XPM_LIBS@ +XSLTPROC = @XSLTPROC@ +XVFB_RUN = @XVFB_RUN@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +Z_LIBS = @Z_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gimpdatadir = @gimpdatadir@ +gimpdir = @gimpdir@ +gimplocaledir = @gimplocaledir@ +gimpplugindir = @gimpplugindir@ +gimpsysconfdir = @gimpsysconfdir@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +manpage_gimpdir = @manpage_gimpdir@ +mkdir_p = @mkdir_p@ +ms_librarian = @ms_librarian@ +mypaint_brushes_dir = @mypaint_brushes_dir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +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, + ©_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; +} |