drop gtk2 support

Change-Id: Ie838cabfecfef7e3225c1555536d5c9cf3b43f15
Reviewed-on: https://gerrit.libreoffice.org/77405
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <[email protected]>
Reviewed-by: Caolán McNamara <[email protected]>
Tested-by: Caolán McNamara <[email protected]>
diff --git a/Repository.mk b/Repository.mk
index 8068e52..9658891 100644
--- a/Repository.mk
+++ b/Repository.mk
@@ -224,14 +224,6 @@
	) \
))

ifeq ($(USING_X11), TRUE)
$(eval $(call gb_Helper_register_executables_for_install,OOO,gnome, \
	$(if $(ENABLE_GTK),\
		xid-fullscreen-on-all-monitors \
	) \
))
endif

ifneq ($(ENABLE_POPPLER),)
$(eval $(call gb_Helper_register_executables_for_install,OOO,pdfimport, \
	xpdfimport \
@@ -290,7 +282,6 @@

$(eval $(call gb_Helper_register_libraries_for_install,OOOLIBS,gnome, \
	$(if $(ENABLE_EVOAB2),evoab) \
	$(if $(ENABLE_GTK),vclplug_gtk) \
	$(if $(ENABLE_GTK3),vclplug_gtk3) \
	$(if $(ENABLE_GIO),losessioninstall) \
	$(if $(ENABLE_GIO),ucpgio1) \
diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk
index 57f66b8..4d99f79 100644
--- a/RepositoryExternal.mk
+++ b/RepositoryExternal.mk
@@ -2605,34 +2605,6 @@

endif # ENABLE_AVAHI


define gb_LinkTarget__use_gtk
$(call gb_LinkTarget_set_include,$(1),\
	$$(INCLUDE) \
	$(GTK_CFLAGS) \
)

$(call gb_LinkTarget_add_libs,$(1),$(GTK_LIBS))

ifeq ($(ENABLE_GTK_PRINT),TRUE)

$(call gb_LinkTarget_add_defs,$(1),-DENABLE_GTK_PRINT)

$(call gb_LinkTarget_set_include,$(1),\
	$$(INCLUDE) \
	$(GTK_PRINT_CFLAGS) \
)

$(call gb_LinkTarget_add_libs,$(1),$(GTK_PRINT_LIBS))

endif

endef

define gb_LinkTarget__use_gthread
$(call gb_LinkTarget_add_libs,$(1),$(GTHREAD_LIBS))
endef

ifeq ($(ENABLE_CUPS),TRUE)

define gb_LinkTarget__use_cups
diff --git a/bin/check-elf-dynamic-objects b/bin/check-elf-dynamic-objects
index 5492aaf..f64044d 100755
--- a/bin/check-elf-dynamic-objects
+++ b/bin/check-elf-dynamic-objects
@@ -93,7 +93,6 @@
openglwhitelist="libGL.so.1"
giowhitelist="libgio-2.0.so.0 libgobject-2.0.so.0 libgmodule-2.0.so.0 libgthread-2.0.so.0 libglib-2.0.so.0 libdbus-glib-1.so.2 libdbus-1.so.3"
gstreamerwhitelist="libgstaudio-1.0.so.0 libgstpbutils-1.0.so.0 libgstvideo-1.0.so.0 libgstbase-1.0.so.0 libgstreamer-1.0.so.0"
gtk2whitelist="libgtk-x11-2.0.so.0 libgdk-x11-2.0.so.0 libpangocairo-1.0.so.0 libfribidi.so.0 libatk-1.0.so.0 libcairo.so.2 libgio-2.0.so.0 libpangoft2-1.0.so.0 libpango-1.0.so.0 libfontconfig.so.1 libfreetype.so.6 libgdk_pixbuf-2.0.so.0 libgobject-2.0.so.0 libglib-2.0.so.0 libgmodule-2.0.so.0 libgthread-2.0.so.0 libdbus-glib-1.so.2 libdbus-1.so.3"
gtk3whitelist="libgtk-3.so.0 libgdk-3.so.0 libcairo-gobject.so.2 libpangocairo-1.0.so.0 libfribidi.so.0 libatk-1.0.so.0 libcairo.so.2 libgio-2.0.so.0 libpangoft2-1.0.so.0 libpango-1.0.so.0 libfontconfig.so.1 libfreetype.so.6 libgdk_pixbuf-2.0.so.0 libgobject-2.0.so.0 libglib-2.0.so.0 libgmodule-2.0.so.0 libgthread-2.0.so.0 libdbus-glib-1.so.2 libdbus-1.so.3"
qt5whitelist="libQt5Core.so.5 libQt5Gui.so.5 libQt5Network.so.5 libQt5Widgets.so.5 libQt5X11Extras.so.5 libcairo.so.2 libglib-2.0.so.0 libgobject-2.0.so.0 libxcb.so.1"
kf5whitelist="libKF5ConfigCore.so.5 libKF5CoreAddons.so.5 libKF5I18n.so.5 libKF5KIOCore.so.5 libKF5KIOFileWidgets.so.5 libKF5KIOWidgets.so.5 libKF5WindowSystem.so.5"
@@ -129,10 +128,7 @@
                whitelist="${whitelist} ${qt5whitelist} ${kf5whitelist}"
            fi
        ;;
        */libvclplug_gtklo.so|*/libqstart_gtklo.so|*/updater)
            whitelist="${whitelist} ${x11whitelist} ${gtk2whitelist}"
        ;;
        */libvclplug_gtk3lo.so)
        */libvclplug_gtk3lo.so|*/updater)
            whitelist="${whitelist} ${x11whitelist} ${gtk3whitelist}"
        ;;
        */libvclplug_qt5lo.so)
diff --git a/bin/distro-install-file-lists b/bin/distro-install-file-lists
index 4cbe0be..ef016e3 100755
--- a/bin/distro-install-file-lists
+++ b/bin/distro-install-file-lists
@@ -324,9 +324,6 @@
            mv_file_between_flists core_list.txt common_list.txt "$INSTALLDIR/$f"
        done

        # Ship ooqstart for gnome in gnome package
        mv_file_between_flists gnome_list.txt core_list.txt "$INSTALLDIR/program/libqstart_gtk680.*"

        # themes are included in other packages
        # don't use remove_file as we don't want them removed from the buildroot.
        mv_file_between_flists /dev/null common_list.txt $INSTALLDIR/share/config/images_crystal.zip
diff --git a/bin/moveglobalheaders.sh b/bin/moveglobalheaders.sh
index 6c8cff7..ca20283 100755
--- a/bin/moveglobalheaders.sh
+++ b/bin/moveglobalheaders.sh
@@ -36,8 +36,7 @@
# we like to be special ...
sed -ie 's/\/svtools\/inc\/svtools/\/include\/svtools\//' svtools/Library_svt.mk
sed -ie 's/\/sfx2\/inc\/sfx2/\/include\/sfx2\//' sfx2/Library_sfx.mk
sed -ie 's/\/sfx2\/inc\/sfx2/\/include\/sfx2\//' sfx2/Library_qstart_gtk.mk
git add svtools/Library_svt.mk sfx2/Library_sfx.mk sfx2/Library_qstart_gtk.mk
git add svtools/Library_svt.mk sfx2/Library_sfx.mk

# urgh
sed -ie 's/\.\.\/svx\//svx\//' svx/source/svdraw/svdoashp.cxx
diff --git a/config_host.mk.in b/config_host.mk.in
index 122c9de..29b8568 100644
--- a/config_host.mk.in
+++ b/config_host.mk.in
@@ -142,8 +142,6 @@
export ENABLE_GPGMEPP=@ENABLE_GPGMEPP@
export ENABLE_GSTREAMER_1_0=@ENABLE_GSTREAMER_1_0@
export ENABLE_GTK3=@ENABLE_GTK3@
export ENABLE_GTK=@ENABLE_GTK@
export ENABLE_GTK_PRINT=@ENABLE_GTK_PRINT@
export DISABLE_GUI=@DISABLE_GUI@
export ENABLE_HTMLHELP=@ENABLE_HTMLHELP@
export ENABLE_IOS_LIBREOFFICELIGHT_APP=@ENABLE_IOS_LIBREOFFICELIGHT_APP@
@@ -236,13 +234,8 @@
export GSTREAMER_1_0_CFLAGS=$(gb_SPACE)@GSTREAMER_1_0_CFLAGS@
export GSTREAMER_1_0_LIBS=$(gb_SPACE)@GSTREAMER_1_0_LIBS@
export GTHREAD_CFLAGS=$(gb_SPACE)@GTHREAD_CFLAGS@
export GTHREAD_LIBS=$(gb_SPACE)@GTHREAD_LIBS@
export GTK3_CFLAGS=$(gb_SPACE)@GTK3_CFLAGS@
export GTK3_LIBS=$(gb_SPACE)@GTK3_LIBS@
export GTK_CFLAGS=$(gb_SPACE)@GTK_CFLAGS@
export GTK_LIBS=$(gb_SPACE)@GTK_LIBS@
export GTK_PRINT_CFLAGS=$(gb_SPACE)@GTK_PRINT_CFLAGS@
export GTK_PRINT_LIBS=$(gb_SPACE)@GTK_PRINT_LIBS@
export USING_X11=@USING_X11@
export HAMCREST_JAR=@HAMCREST_JAR@
export HAVE_BROKEN_GCC_WMAYBE_UNINITIALIZED=@HAVE_BROKEN_GCC_WMAYBE_UNINITIALIZED@
diff --git a/config_host/config_vclplug.h.in b/config_host/config_vclplug.h.in
index 694334d..8ff5a9c 100644
--- a/config_host/config_vclplug.h.in
+++ b/config_host/config_vclplug.h.in
@@ -7,7 +7,6 @@
#ifndef CONFIG_VCLPLUG_H
#define CONFIG_VCLPLUG_H

#define ENABLE_GTK 0
#define ENABLE_GTK3 0
#define ENABLE_GTK3_KDE5 0
#define ENABLE_KF5 0
diff --git a/configure.ac b/configure.ac
index cf0f6e5..ce79ad5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -574,7 +574,6 @@
case "$host_os" in

solaris*)
    test_gtk=yes
    build_gstreamer_1_0=yes
    test_freetype=yes
    _os=SunOS
@@ -600,7 +599,6 @@
    ;;

linux-gnu*|k*bsd*-gnu*)
    test_gtk=yes
    build_gstreamer_1_0=yes
    test_kf5=yes
    test_gtk3_kde5=yes
@@ -649,7 +647,6 @@
    ;;

darwin*) # macOS or iOS
    test_gtk=yes
    test_randr=no
    test_xrender=no
    test_freetype=no
@@ -662,7 +659,6 @@
    if test "$host_cpu" = "arm64" -o "$enable_ios_simulator" = "yes"; then
        build_for_ios=YES
        _os=iOS
        test_gtk=no
        test_cups=no
        enable_mpl_subset=yes
        enable_lotuswordpro=no
@@ -694,7 +690,6 @@
;;

freebsd*)
    test_gtk=yes
    build_gstreamer_1_0=yes
    test_kf5=yes
    test_gtk3_kde5=yes
@@ -722,7 +717,6 @@
    ;;

*netbsd*)
    test_gtk=yes
    build_gstreamer_1_0=yes
    test_kf5=yes
    test_gtk3_kde5=yes
@@ -739,7 +733,6 @@
    ;;

openbsd*)
    test_gtk=yes
    test_freetype=yes
    PTHREAD_CFLAGS="-D_THREAD_SAFE"
    PTHREAD_LIBS="-pthread"
@@ -747,7 +740,6 @@
    ;;

dragonfly*)
    test_gtk=yes
    build_gstreamer_1_0=yes
    test_kf5=yes
    test_gtk3_kde5=yes
@@ -770,7 +762,6 @@
    test_dbus=no
    test_fontconfig=no
    test_freetype=no
    test_gtk=no
    test_kf5=no
    test_qt5=no
    test_gtk3_kde5=no
@@ -1204,11 +1195,6 @@
         'fully-internal' even forces the internal version for uses of Python
         during the build.]))

libo_FUZZ_ARG_ENABLE(gtk,
    AS_HELP_STRING([--disable-gtk],
        [Determines whether to use Gtk+ vclplug on platforms where Gtk+ is available.]),
,test "${enable_gtk+set}" = set || enable_gtk=yes)

libo_FUZZ_ARG_ENABLE(gtk3,
    AS_HELP_STRING([--disable-gtk3],
        [Determines whether to use Gtk+ 3.0 vclplug on platforms where Gtk+ 3.0 is available.]),
@@ -1652,12 +1638,12 @@
AC_ARG_WITH(system-cairo,
    AS_HELP_STRING([--with-system-cairo],
        [Use cairo libraries already on system.  Happens automatically for
         (implicit) --enable-gtk and for --enable-gtk3.]))
         (implicit) --enable-gtk3.]))

AC_ARG_WITH(system-epoxy,
    AS_HELP_STRING([--with-system-epoxy],
        [Use epoxy libraries already on system.  Happens automatically for
         --enable-gtk3.]),,
         (implicit) --enable-gtk3.]),,
       [with_system_epoxy="$with_system_libs"])

AC_ARG_WITH(myspell-dicts,
@@ -4716,7 +4702,6 @@
    ./configure \
        --disable-cups \
        --disable-gstreamer-1-0 \
        --disable-gtk \
        --disable-gtk3 \
        --disable-pdfimport \
        --disable-postgresql-sdbc \
@@ -4849,7 +4834,6 @@
    test_xrender=no
    test_cups=no
    test_dbus=no
    test_gtk=no
    build_gstreamer_1_0=no
    test_kf5=no
    test_qt5=no
@@ -10328,7 +10312,6 @@
dnl ===================================================================
R=""
if test "$USING_X11" != TRUE; then
    enable_gtk=no
    enable_gtk3=no
fi
GTK3_CFLAGS=""
@@ -10369,18 +10352,6 @@
    fi
fi

ENABLE_GTK=""
if test "x$enable_gtk" = "xyes"; then
    if test "$with_system_cairo" = no; then
        AC_MSG_ERROR([System cairo required for gtk support, do not use --without-system-cairo or use --disable-gtk])
    fi
    : ${with_system_cairo:=yes}
    ENABLE_GTK="TRUE"
    AC_DEFINE(ENABLE_GTK)
    R="gtk $R"
fi
AC_SUBST(ENABLE_GTK)

ENABLE_QT5=""
if test "x$enable_qt5" = "xyes"; then
    ENABLE_QT5="TRUE"
@@ -10531,61 +10502,27 @@
AC_SUBST(SYSTEM_BLUEZ)

dnl ===================================================================
dnl Check whether the gtk 2.0 libraries are available.
dnl Check whether to enable GIO support
dnl ===================================================================

GTK_CFLAGS=""
GTK_LIBS=""
if test  "$test_gtk" = "yes"; then

    if test "$ENABLE_GTK" = "TRUE"; then
        PKG_CHECK_MODULES(GTK, gtk+-2.0 >= 2.18.0 ,,AC_MSG_ERROR([requirements to build the gtk-plugin not met (gtk+-2.0 >= 2.18.0). Use --disable-gtk or install the missing packages]))
        PKG_CHECK_MODULES(GTK, gdk-pixbuf-2.0 >= 2.2 ,,AC_MSG_ERROR([requirements to build the gtk-plugin not met (gdk-pixbuf-2.0 >= 2.2). Use --disable-gtk or install the missing packages]))
        GTK_CFLAGS=$(printf '%s' "$GTK_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
        FilterLibs "${GTK_LIBS}"
        GTK_LIBS="${filteredlibs}"
        PKG_CHECK_MODULES(GTHREAD, gthread-2.0,,AC_MSG_ERROR([requirements to build the gtk-plugin not met (gthread-2.0). Use --disable-gtk or install the missing packages]))
        BUILD_TYPE="$BUILD_TYPE GTK"
        GTHREAD_CFLAGS=$(printf '%s' "$GTK_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
        FilterLibs "${GTHREAD_LIBS}"
        GTHREAD_LIBS="${filteredlibs}"

        AC_MSG_CHECKING([whether to enable Gtk print dialog support])
        PKG_CHECK_MODULES([GTK_PRINT], [gtk+-unix-print-2.0 >= 2.10.0],
                          [ENABLE_GTK_PRINT="TRUE"],
                          [ENABLE_GTK_PRINT=""])
        GTK_PRINT_CFLAGS=$(printf '%s' "$GTK_PRINT_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
        FilterLibs "${GTK_PRINT_LIBS}"
        GTK_PRINT_LIBS="${filteredlibs}"
    fi

    if test "$ENABLE_GTK" = "TRUE" || test "$ENABLE_GTK3" = "TRUE"; then
        AC_MSG_CHECKING([whether to enable GIO support])
        if test "$_os" != "WINNT" -a "$_os" != "Darwin" -a "$enable_gio" = "yes"; then
            dnl Need at least 2.26 for the dbus support.
            PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.26],
                              [ENABLE_GIO="TRUE"], [ENABLE_GIO=""])
            if test "$ENABLE_GIO" = "TRUE"; then
                AC_DEFINE(ENABLE_GIO)
                GIO_CFLAGS=$(printf '%s' "$GIO_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
                FilterLibs "${GIO_LIBS}"
                GIO_LIBS="${filteredlibs}"
            fi
        else
            AC_MSG_RESULT([no])
if test "$ENABLE_GTK3" = "TRUE"; then
    AC_MSG_CHECKING([whether to enable GIO support])
    if test "$_os" != "WINNT" -a "$_os" != "Darwin" -a "$enable_gio" = "yes"; then
        dnl Need at least 2.26 for the dbus support.
        PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.26],
                          [ENABLE_GIO="TRUE"], [ENABLE_GIO=""])
        if test "$ENABLE_GIO" = "TRUE"; then
            AC_DEFINE(ENABLE_GIO)
            GIO_CFLAGS=$(printf '%s' "$GIO_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
            FilterLibs "${GIO_LIBS}"
            GIO_LIBS="${filteredlibs}"
        fi
    else
        AC_MSG_RESULT([no])
    fi
fi
AC_SUBST(ENABLE_GIO)
AC_SUBST(GIO_CFLAGS)
AC_SUBST(GIO_LIBS)
AC_SUBST(GTK_CFLAGS)
AC_SUBST(GTK_LIBS)
AC_SUBST(GTHREAD_CFLAGS)
AC_SUBST(GTHREAD_LIBS)
AC_SUBST([ENABLE_GTK_PRINT])
AC_SUBST([GTK_PRINT_CFLAGS])
AC_SUBST([GTK_PRINT_LIBS])


dnl ===================================================================
diff --git a/cui/Library_cui.mk b/cui/Library_cui.mk
index 8a2bf42..54e3733 100644
--- a/cui/Library_cui.mk
+++ b/cui/Library_cui.mk
@@ -20,7 +20,6 @@
$(eval $(call gb_Library_set_precompiled_header,cui,cui/inc/pch/precompiled_cui))

$(eval $(call gb_Library_add_defs,cui,\
    $(if $(filter TRUE,$(ENABLE_GTK)),-DENABLE_GTK) \
    -DCUI_DLLIMPLEMENTATION \
))

diff --git a/cui/source/options/optgdlg.cxx b/cui/source/options/optgdlg.cxx
index bcf1d7a..c0b8403 100644
--- a/cui/source/options/optgdlg.cxx
+++ b/cui/source/options/optgdlg.cxx
@@ -18,6 +18,7 @@
 */

#include <config_features.h>
#include <config_vclplug.h>
#include <svl/zforlist.hxx>
#include <svl/currencytable.hxx>
#include <svtools/langhelp.hxx>
@@ -257,7 +258,7 @@
        m_xFileDlgCB->set_sensitive(false);
    }

#if ! ENABLE_GTK
#if !ENABLE_GTK3
    m_xPrintDlgFrame->hide();
#else
    if (!SvtMiscOptions().IsExperimentalMode())
@@ -269,7 +270,7 @@
    m_xQuickLaunchCB->show();

    //Only available in Win or if building the gtk systray
#if !defined(_WIN32) && ! ENABLE_GTK
#if !defined(_WIN32)
    m_xQuickStarterFrame->hide();
#endif

diff --git a/desktop/Pagein_common.mk b/desktop/Pagein_common.mk
index ca5873d..c1661a3 100644
--- a/desktop/Pagein_common.mk
+++ b/desktop/Pagein_common.mk
@@ -28,7 +28,7 @@
    ucb1 \
    configmgr \
    vclplug_gen \
    $(if $(findstring TRUE,$(ENABLE_GTK)),vclplug_gtk) \
    $(if $(findstring TRUE,$(ENABLE_GTK3)),vclplug_gtk3) \
    basegfx \
    sot \
    xmlscript \
diff --git a/distro-configs/LibreOfficeFlatpak.conf b/distro-configs/LibreOfficeFlatpak.conf
index 5bdba75..0d0f9ce 100644
--- a/distro-configs/LibreOfficeFlatpak.conf
+++ b/distro-configs/LibreOfficeFlatpak.conf
@@ -1,4 +1,3 @@
--disable-gtk
--disable-odk
--enable-release-build
--with-ant-home=/run/build/libreoffice/ant
diff --git a/distro-configs/LibreOfficeMacOSX.conf b/distro-configs/LibreOfficeMacOSX.conf
index a4f913c..fc7291c 100644
--- a/distro-configs/LibreOfficeMacOSX.conf
+++ b/distro-configs/LibreOfficeMacOSX.conf
@@ -5,7 +5,6 @@
--enable-extension-integration
--enable-online-update
--without-system-postgresql
--disable-gtk
--with-help=html
--with-myspell-dicts
--with-package-format=dmg
diff --git a/distro-configs/LibreOfficeOnline.conf b/distro-configs/LibreOfficeOnline.conf
index 75b885a..a007b85 100644
--- a/distro-configs/LibreOfficeOnline.conf
+++ b/distro-configs/LibreOfficeOnline.conf
@@ -8,7 +8,6 @@
--disable-firebird-sdbc
--disable-gio
--disable-gstreamer-1-0
--disable-gtk
--disable-gtk3
--disable-gui
--disable-qt5
diff --git a/distro-configs/LibreOfficeOssFuzz.conf b/distro-configs/LibreOfficeOssFuzz.conf
index 1cca285..ab9ec3b 100644
--- a/distro-configs/LibreOfficeOssFuzz.conf
+++ b/distro-configs/LibreOfficeOssFuzz.conf
@@ -4,7 +4,6 @@
--disable-runtime-optimizations
--disable-database-connectivity
--disable-gui
--disable-gtk
--disable-gtk3
--disable-pdfium
--disable-postgresql-sdbc
diff --git a/onlineupdate/Executable_test_updater_dialog.mk b/onlineupdate/Executable_test_updater_dialog.mk
index bd144c8..a2988d0 100644
--- a/onlineupdate/Executable_test_updater_dialog.mk
+++ b/onlineupdate/Executable_test_updater_dialog.mk
@@ -15,6 +15,7 @@
	-I$(SRCDIR)/onlineupdate/source/update/updater/xpcom/glue \
	-I$(SRCDIR)/onlineupdate/source/update/updater \
	$$(INCLUDE) \
	$(if $(filter-out WNT,$(OS)),$$(GTK3_CFLAGS) ) \
))

$(eval $(call gb_Executable_use_static_libraries,test_updater_dialog,\
@@ -48,15 +49,10 @@

$(eval $(call gb_Executable_use_externals,test_updater_dialog,\
	nss3 \
	gtk \
))

$(eval $(call gb_Executable_add_libs,test_updater_dialog,\
	-lX11 \
	-lXext \
	-lXrender \
	-lSM \
	-lICE \
	$(GTK3_LIBS) \
))
endif

diff --git a/onlineupdate/Executable_updater.mk b/onlineupdate/Executable_updater.mk
index 7c24b7b..ab15a94 100644
--- a/onlineupdate/Executable_updater.mk
+++ b/onlineupdate/Executable_updater.mk
@@ -14,6 +14,7 @@
	-I$(SRCDIR)/onlineupdate/source/update/common \
	-I$(SRCDIR)/onlineupdate/source/update/updater/xpcom/glue \
	$$(INCLUDE) \
	$(if $(filter-out WNT,$(OS)),$$(GTK3_CFLAGS) ) \
))

$(eval $(call gb_Executable_use_custom_headers,updater,onlineupdate/generated))
@@ -61,16 +62,12 @@

$(eval $(call gb_Executable_use_externals,updater,\
	nss3 \
	gtk \
))

$(eval $(call gb_Executable_add_libs,updater,\
	-lX11 \
	-lXext \
	-lXrender \
	-lSM \
	-lICE \
	$(GTK3_LIBS) \
))

endif

$(eval $(call gb_Executable_add_exception_objects,updater,\
diff --git a/onlineupdate/source/update/updater/progressui_gtk.cxx b/onlineupdate/source/update/updater/progressui_gtk.cxx
index 47d27ea..7c3bcfc 100644
--- a/onlineupdate/source/update/updater/progressui_gtk.cxx
+++ b/onlineupdate/source/update/updater/progressui_gtk.cxx
@@ -14,6 +14,11 @@
#include <string.h>
#include "progressui_gtk_icon.h"

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

#define TIMER_INTERVAL 100

static float    sProgressVal;  // between 0 and 100
@@ -133,4 +138,9 @@
{
    sProgressVal = progress;  // 32-bit writes are atomic
}

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

#endif  // defined(UNIX) || defined(MACOSX)
diff --git a/scp2/InstallScript_setup_osl.mk b/scp2/InstallScript_setup_osl.mk
index 0c3b97b..6f7b582 100644
--- a/scp2/InstallScript_setup_osl.mk
+++ b/scp2/InstallScript_setup_osl.mk
@@ -33,7 +33,7 @@
	$(if $(WITH_EXTENSION_INTEGRATION),\
		scp2/extensions \
	) \
	$(if $(filter TRUE,$(ENABLE_EVOAB2) $(ENABLE_GIO) $(ENABLE_GTK) $(ENABLE_GTK3)),\
	$(if $(filter TRUE,$(ENABLE_EVOAB2) $(ENABLE_GIO) $(ENABLE_GTK3)),\
		scp2/gnome \
	) \
	$(if $(filter TRUE,$(ENABLE_QT5) $(ENABLE_KF5) $(ENABLE_GTK3_KDE5)),\
diff --git a/scp2/Module_scp2.mk b/scp2/Module_scp2.mk
index aa06d63..25ae816 100644
--- a/scp2/Module_scp2.mk
+++ b/scp2/Module_scp2.mk
@@ -37,7 +37,7 @@
		InstallModule_windows \
		InstallModule_winexplorerext \
	) \
	$(if $(filter TRUE,$(ENABLE_EVOAB2) $(ENABLE_GIO) $(ENABLE_GTK) $(ENABLE_GTK3)),\
	$(if $(filter TRUE,$(ENABLE_EVOAB2) $(ENABLE_GIO) $(ENABLE_GTK3)),\
		InstallModule_gnome \
	) \
	$(if $(filter TRUE,$(ENABLE_QT5) $(ENABLE_KF5) $(ENABLE_GTK3_KDE5)),\
diff --git a/sd/CppunitTest_sd_uimpress.mk b/sd/CppunitTest_sd_uimpress.mk
index 4a36cc6..94229cc 100644
--- a/sd/CppunitTest_sd_uimpress.mk
+++ b/sd/CppunitTest_sd_uimpress.mk
@@ -71,7 +71,6 @@

$(eval $(call gb_CppunitTest_use_externals,sd_uimpress,\
    boost_headers \
    gtk \
    dbus \
	$(if $(ENABLE_AVAHI), \
	    avahi \
diff --git a/solenv/clang-format/blacklist b/solenv/clang-format/blacklist
index c89b686..d694f0b 100644
--- a/solenv/clang-format/blacklist
+++ b/solenv/clang-format/blacklist
@@ -18113,51 +18113,13 @@
vcl/unx/generic/window/salobj.cxx
vcl/unx/generic/window/screensaverinhibitor.cxx
vcl/unx/glxtest.cxx
vcl/unx/gtk/a11y/atkaction.cxx
vcl/unx/gtk/a11y/atkbridge.cxx
vcl/unx/gtk/a11y/atkcomponent.cxx
vcl/unx/gtk/a11y/atkeditabletext.cxx
vcl/unx/gtk/a11y/atkfactory.cxx
vcl/unx/gtk/a11y/atkfactory.hxx
vcl/unx/gtk/a11y/atkhypertext.cxx
vcl/unx/gtk/a11y/atkimage.cxx
vcl/unx/gtk/a11y/atklistener.cxx
vcl/unx/gtk/a11y/atklistener.hxx
vcl/unx/gtk/a11y/atkregistry.cxx
vcl/unx/gtk/a11y/atkregistry.hxx
vcl/unx/gtk/a11y/atkselection.cxx
vcl/unx/gtk/a11y/atktable.cxx
vcl/unx/gtk/a11y/atktext.cxx
vcl/unx/gtk/a11y/atktextattributes.cxx
vcl/unx/gtk/a11y/atktextattributes.hxx
vcl/unx/gtk/a11y/atkutil.cxx
vcl/unx/gtk/a11y/atkutil.hxx
vcl/unx/gtk/a11y/atkvalue.cxx
vcl/unx/gtk/a11y/atkwindow.cxx
vcl/unx/gtk/a11y/atkwindow.hxx
vcl/unx/gtk/a11y/atkwrapper.cxx
vcl/unx/gtk/a11y/atkwrapper.hxx
vcl/unx/gtk/fpicker/SalGtkFilePicker.cxx
vcl/unx/gtk/fpicker/SalGtkFilePicker.hxx
vcl/unx/gtk/fpicker/SalGtkFolderPicker.cxx
vcl/unx/gtk/fpicker/SalGtkFolderPicker.hxx
vcl/unx/gtk/fpicker/SalGtkPicker.cxx
vcl/unx/gtk/fpicker/SalGtkPicker.hxx
vcl/unx/gtk/fpicker/eventnotification.hxx
vcl/unx/gtk/fpicker/resourceprovider.cxx
vcl/unx/gtk/gloactiongroup.cxx
vcl/unx/gtk/glomenu.cxx
vcl/unx/gtk/gtkdata.cxx
vcl/unx/gtk/gtkinst.cxx
vcl/unx/gtk/gtkobject.cxx
vcl/unx/gtk/gtkprintwrapper.cxx
vcl/unx/gtk/gtksalframe.cxx
vcl/unx/gtk/gtksalmenu.cxx
vcl/unx/gtk/gtksys.cxx
vcl/unx/gtk/hudawareness.cxx
vcl/unx/gtk/salnativewidgets-gtk.cxx
vcl/unx/gtk/salprn-gtk.cxx
vcl/unx/gtk/xid_fullscreen_on_all_monitors.c
vcl/unx/gtk3/a11y/atkfactory.hxx
vcl/unx/gtk3/a11y/atklistener.hxx
vcl/unx/gtk3/a11y/atkregistry.hxx
vcl/unx/gtk3/a11y/atktextattributes.hxx
vcl/unx/gtk3/a11y/atkutil.hxx
vcl/unx/gtk3/a11y/atkwindow.hxx
vcl/unx/gtk3/a11y/atkwrapper.hxx
vcl/unx/gtk3/a11y/gtk3atkaction.cxx
vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
@@ -18175,9 +18137,16 @@
vcl/unx/gtk3/a11y/gtk3atkvalue.cxx
vcl/unx/gtk3/a11y/gtk3atkwindow.cxx
vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
vcl/unx/gtk3/fpicker/eventnotification.hxx
vcl/unx/gtk3/fpicker/resourceprovider.cxx
vcl/unx/gtk3/cairo_gtk3_cairo.cxx
vcl/unx/gtk3/cairo_gtk3_cairo.hxx
vcl/unx/gtk3/gtk3fpicker.cxx
vcl/unx/gtk3/gtk3gloactiongroup.cxx
vcl/unx/gtk3/gtk3glomenu.cxx
vcl/unx/gtk3/gtk3gtkdata.cxx
diff --git a/solenv/gbuild/CppunitTest.mk b/solenv/gbuild/CppunitTest.mk
index 7c5c251..0d40bd6 100644
--- a/solenv/gbuild/CppunitTest.mk
+++ b/solenv/gbuild/CppunitTest.mk
@@ -224,7 +224,6 @@
$(call gb_CppunitTest_get_target,$(1)) : $(call gb_Library_get_target,desktop_detector)
$(call gb_CppunitTest_get_target,$(1)) : $(if $(filter $(2),$(true)),, \
    $(call gb_Library_get_target,vclplug_gen) \
        $(if $(ENABLE_GTK),$(call gb_Library_get_target,vclplug_gtk)) \
        $(if $(ENABLE_GTK3),$(call gb_Library_get_target,vclplug_gtk3)) \
        $(if $(ENABLE_QT5),$(call gb_Library_get_target,vclplug_qt5)) \
	 )
diff --git a/vcl/Executable_xid_fullscreen_on_all_monitors.mk b/vcl/Executable_xid_fullscreen_on_all_monitors.mk
deleted file mode 100644
index 296436a..0000000
--- a/vcl/Executable_xid_fullscreen_on_all_monitors.mk
+++ /dev/null
@@ -1,22 +0,0 @@
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#

$(eval $(call gb_Executable_Executable,xid-fullscreen-on-all-monitors))

ifeq ($(filter ANDROID WNT DRAGONFLY FREEBSD NETBSD OPENBSD MACOSX,$(OS)),)
$(eval $(call gb_Executable_add_libs,xid-fullscreen-on-all-monitors,\
    -ldl \
))
endif

$(eval $(call gb_Executable_add_cobjects,xid-fullscreen-on-all-monitors,\
    vcl/unx/gtk/xid_fullscreen_on_all_monitors \
))

# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_gtk.mk b/vcl/Library_vclplug_gtk.mk
deleted file mode 100644
index 360eabf..0000000
--- a/vcl/Library_vclplug_gtk.mk
+++ /dev/null
@@ -1,132 +0,0 @@
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This file incorporates work covered by the following license notice:
#
#   Licensed to the Apache Software Foundation (ASF) under one or more
#   contributor license agreements. See the NOTICE file distributed
#   with this work for additional information regarding copyright
#   ownership. The ASF licenses this file to you under the Apache
#   License, Version 2.0 (the "License"); you may not use this file
#   except in compliance with the License. You may obtain a copy of
#   the License at http://www.apache.org/licenses/LICENSE-2.0 .
#

$(eval $(call gb_Library_Library,vclplug_gtk))

$(eval $(call gb_Library_set_include,vclplug_gtk,\
    $$(INCLUDE) \
    -I$(SRCDIR)/vcl/inc \
    -I$(SRCDIR)/vcl/unx \
    -I$(SRCDIR)/vcl/unx/gtk \
))

$(eval $(call gb_Library_add_defs,vclplug_gtk,\
    -DVCLPLUG_GTK_IMPLEMENTATION \
))

$(eval $(call gb_Library_use_custom_headers,vclplug_gtk,\
	officecfg/registry \
))

$(eval $(call gb_Library_use_sdk_api,vclplug_gtk))

$(eval $(call gb_Library_use_libraries,vclplug_gtk,\
    vclplug_gen \
    vcl \
    tl \
    utl \
    sot \
    ucbhelper \
    basegfx \
    comphelper \
    cppuhelper \
    i18nlangtag \
    i18nutil \
    $(if $(ENABLE_JAVA), \
        jvmaccess) \
    cppu \
    sal \
))

$(eval $(call gb_Library_use_externals,vclplug_gtk,\
	boost_headers \
	cairo \
	dbus \
	epoxy \
	gio \
	graphite \
	gthread \
	gtk \
	harfbuzz \
	icuuc \
))

$(eval $(call gb_Library_add_libs,vclplug_gtk,\
	-lX11 \
	-lXext \
	-lSM \
	-lICE \
))

$(eval $(call gb_Library_add_exception_objects,vclplug_gtk,\
    vcl/unx/gtk/a11y/atkaction \
    vcl/unx/gtk/a11y/atkbridge \
    vcl/unx/gtk/a11y/atkcomponent \
    vcl/unx/gtk/a11y/atkeditabletext \
    vcl/unx/gtk/a11y/atkfactory \
    vcl/unx/gtk/a11y/atkhypertext \
    vcl/unx/gtk/a11y/atkimage \
    vcl/unx/gtk/a11y/atklistener \
    vcl/unx/gtk/a11y/atkregistry \
    vcl/unx/gtk/a11y/atkselection \
    vcl/unx/gtk/a11y/atktable \
    vcl/unx/gtk/a11y/atktextattributes \
    vcl/unx/gtk/a11y/atktext \
    vcl/unx/gtk/a11y/atkutil \
    vcl/unx/gtk/a11y/atkvalue \
    vcl/unx/gtk/a11y/atkwindow \
    vcl/unx/gtk/a11y/atkwrapper \
    vcl/unx/gtk/gtkdata \
    vcl/unx/gtk/gtkinst \
    vcl/unx/gtk/gtksys \
    vcl/unx/gtk/salnativewidgets-gtk \
    vcl/unx/gtk/gtksalframe \
    vcl/unx/gtk/gtkobject \
    vcl/unx/gtk/fpicker/resourceprovider \
    vcl/unx/gtk/fpicker/SalGtkPicker \
    vcl/unx/gtk/fpicker/SalGtkFilePicker \
    vcl/unx/gtk/fpicker/SalGtkFolderPicker \
))

ifneq ($(ENABLE_DBUS),)
ifneq ($(ENABLE_GIO),)
$(eval $(call gb_Library_add_exception_objects,vclplug_gtk,\
    vcl/unx/gtk/gloactiongroup \
    vcl/unx/gtk/gtksalmenu \
    vcl/unx/gtk/glomenu \
    vcl/unx/gtk/hudawareness \
))
endif
endif

ifeq ($(ENABLE_GTK_PRINT),TRUE)
$(eval $(call gb_Library_add_exception_objects,vclplug_gtk,\
    vcl/unx/gtk/gtkprintwrapper \
    vcl/unx/gtk/salprn-gtk \
))
endif

ifeq ($(OS),LINUX)
$(eval $(call gb_Library_add_libs,vclplug_gtk,\
	-lm \
	-ldl \
))
endif

# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vclplug_gtk3.mk b/vcl/Library_vclplug_gtk3.mk
index c0ad6cd..9f6504c 100644
--- a/vcl/Library_vclplug_gtk3.mk
+++ b/vcl/Library_vclplug_gtk3.mk
@@ -46,7 +46,6 @@

$(eval $(call gb_Library_add_libs,vclplug_gtk3,\
	$(GTK3_LIBS) \
	$(GTHREAD_LIBS) \
	-lX11 \
	-lXext \
	-lSM \
@@ -97,10 +96,13 @@
    vcl/unx/gtk3/a11y/gtk3atkvalue \
    vcl/unx/gtk3/a11y/gtk3atkwindow \
    vcl/unx/gtk3/a11y/gtk3atkwrapper \
    vcl/unx/gtk3/fpicker/resourceprovider \
    vcl/unx/gtk3/fpicker/SalGtkFilePicker \
    vcl/unx/gtk3/fpicker/SalGtkFolderPicker \
    vcl/unx/gtk3/fpicker/SalGtkPicker \
    vcl/unx/gtk3/gtk3gtkdata \
    vcl/unx/gtk3/gtk3gtkinst \
    vcl/unx/gtk3/gtk3gtksys \
    vcl/unx/gtk3/gtk3fpicker \
    vcl/unx/gtk3/cairo_gtk3_cairo \
    vcl/unx/gtk3/gtk3gtkprintwrapper \
    vcl/unx/gtk3/gtk3salnativewidgets-gtk \
diff --git a/vcl/Library_vclplug_gtk3_kde5.mk b/vcl/Library_vclplug_gtk3_kde5.mk
index d67c53e..2317264 100644
--- a/vcl/Library_vclplug_gtk3_kde5.mk
+++ b/vcl/Library_vclplug_gtk3_kde5.mk
@@ -51,7 +51,6 @@

$(eval $(call gb_Library_add_libs,vclplug_gtk3_kde5,\
	$(GTK3_LIBS) \
	$(GTHREAD_LIBS) \
	-lX11 \
	-lXext \
	-lSM \
diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk
index 54e3fbd6..859702c 100644
--- a/vcl/Module_vcl.mk
+++ b/vcl/Module_vcl.mk
@@ -66,12 +66,6 @@
    Package_fontunxpsprint \
))

ifneq ($(ENABLE_GTK),)
$(eval $(call gb_Module_add_targets,vcl,\
    Executable_xid_fullscreen_on_all_monitors \
    Library_vclplug_gtk \
))
endif
ifneq ($(ENABLE_GTK3),)
$(eval $(call gb_Module_add_targets,vcl,\
    Library_vclplug_gtk3 \
diff --git a/vcl/inc/unx/gtk/gtkdata.hxx b/vcl/inc/unx/gtk/gtkdata.hxx
index f1750b7..84e2d19 100644
--- a/vcl/inc/unx/gtk/gtkdata.hxx
+++ b/vcl/inc/unx/gtk/gtkdata.hxx
@@ -42,44 +42,22 @@

inline GdkWindow * widget_get_window(GtkWidget *widget)
{
#if GTK_CHECK_VERSION(3,0,0)
    return gtk_widget_get_window(widget);
#else
    return widget->window;
#endif
}

inline ::Window widget_get_xid(GtkWidget *widget)
{
#if GTK_CHECK_VERSION(3,0,0)
    return GDK_WINDOW_XID(gtk_widget_get_window(widget));
#else
    return GDK_WINDOW_XWINDOW(widget->window);
#endif
}

inline void widget_set_can_focus(GtkWidget *widget, gboolean can_focus)
{
#if GTK_CHECK_VERSION(3,0,0)
    return gtk_widget_set_can_focus(widget, can_focus);
#else
    if (can_focus)
        GTK_WIDGET_SET_FLAGS( widget, GTK_CAN_FOCUS );
    else
        GTK_WIDGET_UNSET_FLAGS( widget, GTK_CAN_FOCUS );
#endif
}

inline void widget_set_can_default(GtkWidget *widget, gboolean can_default)
{
#if GTK_CHECK_VERSION(3,0,0)
    return gtk_widget_set_can_default(widget, can_default);
#else
    if (can_default)
        GTK_WIDGET_SET_FLAGS( widget, GTK_CAN_DEFAULT );
    else
        GTK_WIDGET_UNSET_FLAGS( widget, GTK_CAN_DEFAULT );
#endif
}

class GtkSalTimer final : public SalTimer
@@ -132,11 +110,7 @@

class GtkSalFrame;

#if GTK_CHECK_VERSION(3,0,0)
class GtkSalDisplay : public SalGenericDisplay
#else
class GtkSalDisplay : public SalDisplay
#endif
{
    GtkSalSystem*                   m_pSys;
    GdkDisplay* const               m_pGdkDisplay;
@@ -161,20 +135,11 @@

    virtual void deregisterFrame( SalFrame* pFrame ) override;
    GdkCursor *getCursor( PointerStyle ePointerStyle );
#if GTK_CHECK_VERSION(3,0,0)
    virtual int CaptureMouse( SalFrame* pFrame );
#else
    virtual int CaptureMouse( SalFrame* pFrame ) override;
#endif

    SalX11Screen GetDefaultXScreen() { return m_pSys->GetDisplayDefaultXScreen(); }
    Size         GetScreenSize( int nDisplayScreen );
    int          GetXScreenCount() { return m_pSys->GetDisplayXScreenCount(); }
#if GTK_CHECK_VERSION(3,0,0)
//    int          GetScreenCount() { return m_pSys->GetDisplayScreenCount(); }
#else
    virtual ScreenData *initScreen( SalX11Screen nXScreen ) const override;
#endif

    GdkFilterReturn filterGdkEvent( GdkXEvent* sys_event );
    void startupNotificationCompleted() { m_bStartupCompleted = true; }
@@ -185,12 +150,7 @@
    virtual void TriggerUserEventProcessing() override;
    virtual void TriggerAllUserEventsProcessed() override;

#if !GTK_CHECK_VERSION(3,0,0)
    virtual bool Dispatch( XEvent *pEvent ) override;
#endif
#if GTK_CHECK_VERSION(3,0,0)
    void RefreshMenusUnity();
#endif
};

inline GtkSalData* GetGtkSalData()
diff --git a/vcl/inc/unx/gtk/gtkframe.hxx b/vcl/inc/unx/gtk/gtkframe.hxx
index 084339f..f9a41c6 100644
--- a/vcl/inc/unx/gtk/gtkframe.hxx
+++ b/vcl/inc/unx/gtk/gtkframe.hxx
@@ -24,9 +24,7 @@
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#if GTK_CHECK_VERSION(3,0,0)
#  include <gtk/gtkx.h>
#endif
#include <gtk/gtkx.h>
#include <gdk/gdkkeysyms.h>

#include <salframe.hxx>
@@ -54,7 +52,6 @@
class GtkSalGraphics;
class GtkSalDisplay;

#if GTK_CHECK_VERSION(3,0,0)
typedef sal_uIntPtr GdkNativeWindow;
#define GDK_WINDOW_XWINDOW(o) GDK_WINDOW_XID(o)
#define gdk_set_sm_client_id(i) gdk_x11_set_sm_client_id(i)
@@ -62,11 +59,6 @@
class GtkDropTarget;
class GtkDragSource;
class GtkDnDTransferable;
#endif

#if !(GLIB_MAJOR_VERSION > 2 || GLIB_MINOR_VERSION >= 26)
    typedef void GDBusConnection;
#endif

class GtkSalMenu;

@@ -172,10 +164,8 @@

    SalX11Screen                    m_nXScreen;
    GtkWidget*                      m_pWindow;
#if GTK_CHECK_VERSION(3,0,0)
    GtkHeaderBar*                   m_pHeaderBar;
    GtkGrid*                        m_pTopLevelGrid;
#endif
    GtkEventBox*                    m_pEventBox;
    GtkFixed*                       m_pFixedContainer;
    GdkWindow*                      m_pForeignParent;
@@ -208,7 +198,6 @@
    Size                            m_aMinSize;
    tools::Rectangle                       m_aRestorePosSize;

#if GTK_CHECK_VERSION(3,0,0)
    OUString                        m_aTooltip;
    tools::Rectangle                m_aHelpArea;
    tools::Rectangle                m_aFloatRect;
@@ -220,10 +209,6 @@
    GtkDropTarget*                  m_pDropTarget;
    GtkDragSource*                  m_pDragSource;
    bool                            m_bGeometryIsProvisional;
#else
    GdkRegion*                      m_pRegion;
    bool                            m_bSetFocusOnMap;
#endif

    GtkSalMenu*                     m_pSalMenu;

@@ -242,12 +227,7 @@

    // signals
    static gboolean     signalButton( GtkWidget*, GdkEventButton*, gpointer );
#if GTK_CHECK_VERSION(3,0,0)
    static void         signalStyleUpdated(GtkWidget*, gpointer);
#else
    static void         signalStyleSet(GtkWidget*, GtkStyle* pPrevious, gpointer);
#endif
#if GTK_CHECK_VERSION(3,0,0)
    static gboolean     signalDraw( GtkWidget*, cairo_t *cr, gpointer );
    static void         signalRealize(GtkWidget*, gpointer frame);
    static void         sizeAllocated(GtkWidget*, GdkRectangle *pAllocation, gpointer frame);
@@ -270,11 +250,6 @@

    static void         gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame);
    static void         gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame);
#else
    static gboolean     signalExpose( GtkWidget*, GdkEventExpose*, gpointer );
    void askForXEmbedFocus( sal_Int32 nTimecode );
    void grabKeyboard(bool bGrab);
#endif
    static gboolean     signalFocus( GtkWidget*, GdkEventFocus*, gpointer );
    static gboolean     signalMap( GtkWidget*, GdkEvent*, gpointer );
    static gboolean     signalUnmap( GtkWidget*, GdkEvent*, gpointer );
@@ -345,7 +320,6 @@
    void SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize = nullptr );

public:
#if GTK_CHECK_VERSION(3,0,0)
    cairo_surface_t*                m_pSurface;
    basegfx::B2IVector              m_aFrameSize;
    DamageHandler                   m_aDamageHandler;
@@ -353,7 +327,6 @@
    Idle                            m_aSmoothScrollIdle;
    int                             m_nGrabLevel;
    bool                            m_bSalObjectSetPosSize;
#endif
    GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle );
    GtkSalFrame( SystemParentData* pSysData );

@@ -374,9 +347,7 @@
    GtkFixed*   getFixedContainer() const { return m_pFixedContainer; }
    GtkEventBox* getEventBox() const { return m_pEventBox; }
    GtkWidget*  getMouseEventWidget() const;
#if GTK_CHECK_VERSION(3,0,0)
    GtkGrid*    getTopLevelGridWidget() const { return m_pTopLevelGrid; }
#endif
    GdkWindow*  getForeignParent() const { return m_pForeignParent; }
    GdkNativeWindow getForeignParentWindow() const { return m_aForeignParentWindow; }
    GdkWindow*  getForeignTopLevel() const { return m_pForeignTopLevel; }
@@ -385,8 +356,6 @@
    int          GetDisplayScreen() const { return maGeometry.nDisplayScreenNumber; }
    void updateScreenNumber();

#if GTK_CHECK_VERSION(3,0,0)
    // only for gtk3 ...
    cairo_t* getCairoContext() const;
    void damaged(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
                 sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom) const;
@@ -428,7 +397,6 @@
    void LaunchAsyncScroll(GdkEvent const * pEvent);
    DECL_LINK(AsyncScroll, Timer *, void);

#endif
    virtual ~GtkSalFrame() override;

    // SalGraphics or NULL, but two Graphics for all SalFrames
@@ -525,7 +493,6 @@
    // done setting up the clipregion
    virtual void                EndSetClipRegion() override;

#if GTK_CHECK_VERSION(3,0,0)
    virtual void                PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) override;
    virtual void                SetModal(bool bModal) override;
    virtual bool                GetModal() const override;
@@ -535,7 +502,6 @@
    virtual bool                UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea) override;
    virtual bool                HidePopover(void* nId) override;
    virtual weld::Window*       GetFrameWeld() const override;
#endif

    static GtkSalFrame         *getFromWindow( GtkWindow *pWindow );

diff --git a/vcl/inc/unx/gtk/gtkgdi.hxx b/vcl/inc/unx/gtk/gtkgdi.hxx
index c9b7be4..e19d012 100644
--- a/vcl/inc/unx/gtk/gtkgdi.hxx
+++ b/vcl/inc/unx/gtk/gtkgdi.hxx
@@ -29,8 +29,6 @@
#include <unx/gtk/gtkframe.hxx>
#include <ControlCacheKey.hxx>

#if GTK_CHECK_VERSION(3,0,0)

#include <headless/svpgdi.hxx>
#include <textrender.hxx>

@@ -394,8 +392,6 @@
                            ControlState nState, const ImplControlValue& aValue );
};

#endif // !gtk3

#endif // INCLUDED_VCL_INC_UNX_GTK_GTKGDI_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkinst.hxx b/vcl/inc/unx/gtk/gtkinst.hxx
index 65caa38..78dd9bc 100644
--- a/vcl/inc/unx/gtk/gtkinst.hxx
+++ b/vcl/inc/unx/gtk/gtkinst.hxx
@@ -57,7 +57,6 @@
    void ThreadsLeave();
};

#if GTK_CHECK_VERSION(3,0,0)
class GtkSalFrame;

struct VclToGtkHelper
@@ -182,20 +181,10 @@
    css::uno::Reference<css::datatransfer::XTransferable> const & GetTransferrable() const { return m_xTrans; }
};

#endif

class GtkSalTimer;
#if GTK_CHECK_VERSION(3,0,0)
class GtkInstance : public SvpSalInstance
#else
class GtkInstance : public X11SalInstance
#endif
{
#if GTK_CHECK_VERSION(3,0,0)
    typedef SvpSalInstance Superclass_t;
#else
    typedef X11SalInstance Superclass_t;
#endif
public:
            GtkInstance( std::unique_ptr<SalYieldMutex> pMutex );
    virtual ~GtkInstance() override;
@@ -233,7 +222,6 @@
    virtual css::uno::Reference< css::ui::dialogs::XFolderPicker2 >
        createFolderPicker( const css::uno::Reference< css::uno::XComponentContext >& ) override;

#if GTK_CHECK_VERSION(3,0,0)
    virtual css::uno::Reference< css::uno::XInterface > CreateClipboard( const css::uno::Sequence< css::uno::Any >& i_rArguments ) override;
    virtual css::uno::Reference< css::uno::XInterface > CreateDragSource() override;
    virtual css::uno::Reference< css::uno::XInterface > CreateDropTarget() override;
@@ -241,7 +229,6 @@
    virtual weld::Builder* CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile) override;
    virtual weld::MessageDialog* CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonType, const OUString &rPrimaryMessage) override;
    virtual weld::Window* GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow) override;
#endif

    virtual const cairo_font_options_t* GetCairoFontOptions() override;
            const cairo_font_options_t* GetLastSeenCairoFontOptions() const;
@@ -251,15 +238,11 @@

    std::shared_ptr<vcl::unx::GtkPrintWrapper> const & getPrintWrapper() const;

#if GTK_CHECK_VERSION(3,0,0)
    void* CreateGStreamerSink(const SystemChildWindow*) override;
#endif

private:
    GtkSalTimer *m_pTimer;
#if GTK_CHECK_VERSION(3,0,0)
    std::unordered_map< GdkAtom, css::uno::Reference<css::uno::XInterface> > m_aClipboards;
#endif
    bool                        IsTimerExpired();
    bool                        bNeedsInit;
    cairo_font_options_t*       m_pLastCairoFontOptions;
diff --git a/vcl/inc/unx/gtk/gtkobject.hxx b/vcl/inc/unx/gtk/gtkobject.hxx
index 4523e99..f54d7af 100644
--- a/vcl/inc/unx/gtk/gtkobject.hxx
+++ b/vcl/inc/unx/gtk/gtkobject.hxx
@@ -29,12 +29,8 @@
{
    SystemEnvData     m_aSystemData;
    GtkWidget*          m_pSocket;
#if GTK_CHECK_VERSION(3,0,0)
    GtkSalFrame*        m_pParent;
    cairo_region_t*     m_pRegion;
#else
    GdkRegion*          m_pRegion;
#endif

    // signals
    static gboolean     signalButton( GtkWidget*, GdkEventButton*, gpointer );
diff --git a/vcl/inc/unx/gtk/gtkprintwrapper.hxx b/vcl/inc/unx/gtk/gtkprintwrapper.hxx
index 4862c8c..589c800 100644
--- a/vcl/inc/unx/gtk/gtkprintwrapper.hxx
+++ b/vcl/inc/unx/gtk/gtkprintwrapper.hxx
@@ -12,14 +12,11 @@

#include <gtk/gtk.h>

#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
#include <gtk/gtkunixprint.h>

#include <osl/module.hxx>
#include <sal/types.h>

#endif

namespace vcl
{
namespace unx
@@ -30,7 +27,6 @@
private:
    GtkPrintWrapper(const GtkPrintWrapper&) = delete;
    GtkPrintWrapper& operator=(const GtkPrintWrapper&) = delete;
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
public:
    GtkPrintWrapper();
    ~GtkPrintWrapper();
@@ -63,59 +59,6 @@
    // print selection support, since 2.17.4
    void print_unix_dialog_set_support_selection(GtkPrintUnixDialog* dialog, gboolean support_selection) const;
    void print_unix_dialog_set_has_selection(GtkPrintUnixDialog* dialog, gboolean has_selection) const;

#if !GTK_CHECK_VERSION(3,0,0)
private:
    void impl_load();

private:
    typedef GtkPageSetup* (* page_setup_new_t)();
    typedef GtkPrintJob* (* print_job_new_t)(const gchar*, GtkPrinter*, GtkPrintSettings*, GtkPageSetup*);
    typedef void (* print_job_send_t)(GtkPrintJob*, GtkPrintJobCompleteFunc, gpointer, GDestroyNotify);
    typedef gboolean (* print_job_set_source_file_t)(GtkPrintJob*, const gchar*, GError**);
    typedef const gchar* (* print_settings_get_t)(GtkPrintSettings*, const gchar*);
    typedef gboolean (* print_settings_get_collate_t)(GtkPrintSettings*);
    typedef void (* print_settings_set_collate_t)(GtkPrintSettings*, gboolean);
    typedef gint (* print_settings_get_n_copies_t)(GtkPrintSettings*);
    typedef void (* print_settings_set_n_copies_t)(GtkPrintSettings*, gint);
    typedef GtkPageRange* (* print_settings_get_page_ranges_t)(GtkPrintSettings*, gint*);
    typedef void (* print_settings_set_print_pages_t)(GtkPrintSettings*, GtkPrintPages);
    typedef GtkWidget* (* print_unix_dialog_new_t)(const gchar*, GtkWindow*);
    typedef void (* print_unix_dialog_add_custom_tab_t)(GtkPrintUnixDialog*, GtkWidget*, GtkWidget*);
    typedef GtkPrinter* (* print_unix_dialog_get_selected_printer_t)(GtkPrintUnixDialog*);
    typedef void (* print_unix_dialog_set_manual_capabilities_t)(GtkPrintUnixDialog*, GtkPrintCapabilities);
    typedef GtkPrintSettings* (* print_unix_dialog_get_settings_t)(GtkPrintUnixDialog*);
    typedef void (* print_unix_dialog_set_settings_t)(GtkPrintUnixDialog*, GtkPrintSettings*);
    typedef void (* print_unix_dialog_set_support_selection_t)(GtkPrintUnixDialog*, gboolean);
    typedef void (* print_unix_dialog_set_has_selection_t)(GtkPrintUnixDialog*, gboolean);

private:
    osl::Module m_aModule;

    // general printing support, since 2.10.0
    page_setup_new_t m_page_setup_new;
    print_job_new_t m_print_job_new;
    print_job_send_t m_print_job_send;
    print_job_set_source_file_t m_print_job_set_source_file;
    print_settings_get_t m_print_settings_get;
    print_settings_get_collate_t m_print_settings_get_collate;
    print_settings_set_collate_t m_print_settings_set_collate;
    print_settings_get_n_copies_t m_print_settings_get_n_copies;
    print_settings_set_n_copies_t m_print_settings_set_n_copies;
    print_settings_get_page_ranges_t m_print_settings_get_page_ranges;
    print_settings_set_print_pages_t m_print_settings_set_print_pages;
    print_unix_dialog_new_t m_print_unix_dialog_new;
    print_unix_dialog_add_custom_tab_t m_print_unix_dialog_add_custom_tab;
    print_unix_dialog_get_selected_printer_t m_print_unix_dialog_get_selected_printer;
    print_unix_dialog_set_manual_capabilities_t m_print_unix_dialog_set_manual_capabilities;
    print_unix_dialog_get_settings_t m_print_unix_dialog_get_settings;
    print_unix_dialog_set_settings_t m_print_unix_dialog_set_settings;

    // print selection support, since 2.17.4
    print_unix_dialog_set_support_selection_t m_print_unix_dialog_set_support_selection;
    print_unix_dialog_set_has_selection_t m_print_unix_dialog_set_has_selection;
#endif
#endif
};

}
diff --git a/vcl/inc/unx/gtk/gtksalmenu.hxx b/vcl/inc/unx/gtk/gtksalmenu.hxx
index 2d812ca..ddef9ef 100644
--- a/vcl/inc/unx/gtk/gtksalmenu.hxx
+++ b/vcl/inc/unx/gtk/gtksalmenu.hxx
@@ -23,27 +23,8 @@
#include <unotools/tempfile.hxx>
#include <vcl/idle.hxx>

#if GTK_CHECK_VERSION(3,0,0)
#  define ENABLE_GMENU_INTEGRATION
#  include <unx/gtk/glomenu.h>
#  include <unx/gtk/gloactiongroup.h>
#elif ENABLE_DBUS && ENABLE_GIO && \
    (GLIB_MAJOR_VERSION > 2 || GLIB_MINOR_VERSION >= 36)
#  define ENABLE_GMENU_INTEGRATION
#  include <unx/gtk/glomenu.h>
#  include <unx/gtk/gloactiongroup.h>
#else
#  if !(GLIB_MAJOR_VERSION > 2 || GLIB_MINOR_VERSION >= 32)
     typedef void GMenuModel;
#  endif
#  if !(GLIB_MAJOR_VERSION > 2 || GLIB_MINOR_VERSION >= 28)
     typedef void GActionGroup;
#  endif
#endif

#if !GTK_CHECK_VERSION(3,0,0)
typedef void GtkCssProvider;
#endif
#include <unx/gtk/glomenu.h>
#include <unx/gtk/gloactiongroup.h>

class MenuItemList;
class GtkSalMenuItem;
diff --git a/vcl/source/app/salplug.cxx b/vcl/source/app/salplug.cxx
index 20604ed..ce3fb55 100644
--- a/vcl/source/app/salplug.cxx
+++ b/vcl/source/app/salplug.cxx
@@ -95,7 +95,7 @@
                 * So make sure libgtk+ & co are still mapped into memory when
                 * atk-bridge's atexit handler gets called.
                 */
                if( aUsedModuleBase == "gtk" || aUsedModuleBase == "gtk3" || aUsedModuleBase == "gtk3_kde5" || aUsedModuleBase == "win" )
                if( aUsedModuleBase == "gtk3" || aUsedModuleBase == "gtk3_kde5" || aUsedModuleBase == "win" )
                {
                    pCloseModule = nullptr;
                }
@@ -154,12 +154,12 @@
#if ENABLE_GTK3_KDE5
        "gtk3_kde5",
#endif
        "gtk3", "gtk", "gen", nullptr
        "gtk3", "gen", nullptr
    };

    static const char* const pStandardFallbackList[] =
    {
        "gtk3", "gtk", "gen", nullptr
        "gtk3", "gen", nullptr
    };

#ifdef HEADLESS_VCLPLUG
@@ -259,7 +259,7 @@
#ifdef MACOSX
        "osx"
#else
        "gtk3", "gtk", "kf5", "gen"
        "gtk3", "kf5", "gen"
#endif
#endif
     };
diff --git a/vcl/unx/gtk/a11y/atkaction.cxx b/vcl/unx/gtk/a11y/atkaction.cxx
deleted file mode 100644
index 14a172f..0000000
--- a/vcl/unx/gtk/a11y/atkaction.cxx
+++ /dev/null
@@ -1,275 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleAction.hpp>
#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>

#include <com/sun/star/awt/Key.hpp>
#include <com/sun/star/awt/KeyModifier.hpp>

#include <rtl/strbuf.hxx>
#include <algorithm>
#include <map>

using namespace ::com::sun::star;

// FIXME
static const gchar *
getAsConst( const OString& rString )
{
    static const int nMax = 10;
    static OString aUgly[nMax];
    static int nIdx = 0;
    nIdx = (nIdx + 1) % nMax;
    aUgly[nIdx] = rString;
    return aUgly[ nIdx ].getStr();
}

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleAction>
        getAction( AtkAction *action )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( action );

    if( pWrap )
    {
        if( !pWrap->mpAction.is() )
        {
            pWrap->mpAction.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpAction;
    }

    return css::uno::Reference<css::accessibility::XAccessibleAction>();
}

extern "C" {

static gboolean
action_wrapper_do_action (AtkAction *action,
                          gint       i)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
            return pAction->doAccessibleAction( i );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in doAccessibleAction()" );
    }

    return FALSE;
}

static gint
action_wrapper_get_n_actions (AtkAction *action)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
            return pAction->getAccessibleActionCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleActionCount()" );
    }

    return 0;
}

static const gchar *
action_wrapper_get_description (AtkAction *, gint)
{
    // GAIL implement this only for cells
    g_warning( "Not implemented: get_description()" );
    return "";
}

static const gchar *
action_wrapper_get_localized_name (AtkAction *, gint)
{
    // GAIL doesn't implement this as well
    g_warning( "Not implemented: get_localized_name()" );
    return "";
}

#define ACTION_NAME_PAIR( OOoName, AtkName ) \
    std::pair< const OUString, const gchar * > ( OUString( OOoName ), AtkName )

static const gchar *
action_wrapper_get_name (AtkAction *action,
                         gint       i)
{
    static std::map< OUString, const gchar * > aNameMap;

    if( aNameMap.empty() )
    {
        aNameMap.insert( ACTION_NAME_PAIR( "click", "click" ) );
        aNameMap.insert( ACTION_NAME_PAIR( "select", "click" ) );
        aNameMap.insert( ACTION_NAME_PAIR( "togglePopup", "push" ) );
    }

    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
        {
            std::map< OUString, const gchar * >::iterator iter;

            OUString aDesc( pAction->getAccessibleActionDescription( i ) );

            iter = aNameMap.find( aDesc );
            if( iter != aNameMap.end() )
                return iter->second;

            std::pair< const OUString, const gchar * > aNewVal( aDesc,
                g_strdup( OUStringToConstGChar(aDesc) ) );

            if( aNameMap.insert( aNewVal ).second )
                return aNewVal.second;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleActionDescription()" );
    }

    return "";
}

/*
*  GNOME Expects a string in the format:
*
*  <mnemonic>;<full-path>;<accelerator>
*
*  The keybindings in <full-path> should be separated by ":"
*/

static void
appendKeyStrokes(OStringBuffer& rBuffer, const uno::Sequence< awt::KeyStroke >& rKeyStrokes)
{
    for( const auto& rKeyStroke : rKeyStrokes )
    {
        if( rKeyStroke.Modifiers &  awt::KeyModifier::SHIFT )
            rBuffer.append("<Shift>");
        if( rKeyStroke.Modifiers &  awt::KeyModifier::MOD1 )
            rBuffer.append("<Control>");
        if( rKeyStroke.Modifiers &  awt::KeyModifier::MOD2 )
            rBuffer.append("<Alt>");

        if( ( rKeyStroke.KeyCode >= awt::Key::A ) && ( rKeyStroke.KeyCode <= awt::Key::Z ) )
            rBuffer.append( static_cast<sal_Char>( 'a' + ( rKeyStroke.KeyCode - awt::Key::A ) ) );
        else
        {
            sal_Char c = '\0';

            switch( rKeyStroke.KeyCode )
            {
                case awt::Key::TAB:      c = '\t'; break;
                case awt::Key::SPACE:    c = ' '; break;
                case awt::Key::ADD:      c = '+'; break;
                case awt::Key::SUBTRACT: c = '-'; break;
                case awt::Key::MULTIPLY: c = '*'; break;
                case awt::Key::DIVIDE:   c = '/'; break;
                case awt::Key::POINT:    c = '.'; break;
                case awt::Key::COMMA:    c = ','; break;
                case awt::Key::LESS:     c = '<'; break;
                case awt::Key::GREATER:  c = '>'; break;
                case awt::Key::EQUAL:    c = '='; break;
                case 0:
                    break;
                default:
                    g_warning( "Unmapped KeyCode: %d", rKeyStroke.KeyCode );
                    break;
            }

            if( c != '\0' )
                rBuffer.append( c );
            else
            {
                // The KeyCode approach did not work, probably a non ascii character
                // let's hope that there is a character given in KeyChar.
                rBuffer.append( OUStringToGChar( OUString( rKeyStroke.KeyChar ) ) );
            }
        }
    }
}

static const gchar *
action_wrapper_get_keybinding (AtkAction *action,
                               gint       i)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
        {
            uno::Reference< accessibility::XAccessibleKeyBinding > xBinding( pAction->getAccessibleActionKeyBinding( i ));

            if( xBinding.is() )
            {
                OStringBuffer aRet;

                sal_Int32 nmax = std::min( xBinding->getAccessibleKeyBindingCount(), sal_Int32(3) );
                for( sal_Int32 n = 0; n < nmax; n++ )
                {
                    appendKeyStrokes( aRet,  xBinding->getAccessibleKeyBinding( n ) );

                    if( n < 2 )
                        aRet.append( ';' );
                }

                // !! FIXME !! remember keystroke in wrapper object ?
                return getAsConst( aRet.makeStringAndClear() );
            }
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in get_keybinding()" );
    }

    return "";
}

static gboolean
action_wrapper_set_description (AtkAction *, gint, const gchar *)
{
    return FALSE;
}

} // extern "C"

void
actionIfaceInit (AtkActionIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->do_action = action_wrapper_do_action;
  iface->get_n_actions = action_wrapper_get_n_actions;
  iface->get_description = action_wrapper_get_description;
  iface->get_keybinding = action_wrapper_get_keybinding;
  iface->get_name = action_wrapper_get_name;
  iface->get_localized_name = action_wrapper_get_localized_name;
  iface->set_description = action_wrapper_set_description;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkbridge.cxx b/vcl/unx/gtk/a11y/atkbridge.cxx
deleted file mode 100644
index a74f1a9..0000000
--- a/vcl/unx/gtk/a11y/atkbridge.cxx
+++ /dev/null
@@ -1,72 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <unx/gtk/atkbridge.hxx>
#include <unx/gtk/gtkframe.hxx>

#include "atkfactory.hxx"
#include "atkutil.hxx"
#include "atkwindow.hxx"

bool InitAtkBridge()
{
#if GTK_CHECK_VERSION(3,0,0)
    ooo_atk_util_ensure_event_listener();
#else
    const char* pVersion = atk_get_toolkit_version();
    if( ! pVersion )
        return false;

    unsigned int major, minor, micro;

    /* check gail minimum version requirements */
    if( sscanf( pVersion, "%u.%u.%u", &major, &minor, &micro) < 3 )
    {
        // g_warning( "unable to parse gail version number" );
        return false;
    }

    if( ( (major << 16) | (minor << 8) | micro ) < ( (1 << 16) | 8 << 8 | 6 ) )
    {
        g_warning( "libgail >= 1.8.6 required for accessibility support" );
        return false;
    }

    /* Initialize the AtkUtilityWrapper class */
    g_type_class_unref( g_type_class_ref( ooo_atk_util_get_type() ) );

    /* Initialize the GailWindow wrapper class */
    g_type_class_unref( g_type_class_ref( ooo_window_wrapper_get_type() ) );

    /* Register AtkObject wrapper factory */
    AtkRegistry * registry = atk_get_default_registry();
    if( registry )
        atk_registry_set_factory_type( registry, OOO_TYPE_FIXED, wrapper_factory_get_type() );
#endif
    return true;
}

void DeInitAtkBridge()
{
#if !GTK_CHECK_VERSION(3,0,0)
    restore_gail_window_vtable();
#endif
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkcomponent.cxx b/vcl/unx/gtk/a11y/atkcomponent.cxx
deleted file mode 100644
index da5a48e..0000000
--- a/vcl/unx/gtk/a11y/atkcomponent.cxx
+++ /dev/null
@@ -1,387 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"
#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
#include <gtk/gtk.h>

using namespace ::com::sun::star;

static AtkObjectWrapper* getObjectWrapper(AtkComponent *pComponent)
{
    AtkObjectWrapper *pWrap = nullptr;
    if (ATK_IS_OBJECT_WRAPPER(pComponent))
        pWrap = ATK_OBJECT_WRAPPER(pComponent);
    else if (GTK_IS_DRAWING_AREA(pComponent)) //when using a GtkDrawingArea as a custom widget in welded gtk3
    {
        GtkWidget* pDrawingArea = GTK_WIDGET(pComponent);
        AtkObject* pAtkObject = gtk_widget_get_accessible(pDrawingArea);
        pWrap = ATK_IS_OBJECT_WRAPPER(pAtkObject) ? ATK_OBJECT_WRAPPER(pAtkObject) : nullptr;
    }
    return pWrap;
}

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleComponent>
    getComponent(AtkObjectWrapper *pWrap)
{
    if (pWrap)
    {
        if (!pWrap->mpComponent.is())
            pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
        return pWrap->mpComponent;
    }

    return css::uno::Reference<css::accessibility::XAccessibleComponent>();
}

/*****************************************************************************/

static awt::Point
translatePoint( css::uno::Reference<accessibility::XAccessibleComponent> const & pComponent,
                gint x, gint y, AtkCoordType t)
{
    awt::Point aOrigin( 0, 0 );
    if( t == ATK_XY_SCREEN )
        aOrigin = pComponent->getLocationOnScreen();
    return awt::Point( x - aOrigin.X, y - aOrigin.Y );
}

/*****************************************************************************/

extern "C" {

static gboolean
component_wrapper_grab_focus (AtkComponent *component)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
        return atk_component_grab_focus(ATK_COMPONENT(obj->mpOrig));

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
        {
            pComponent->grabFocus();
            return TRUE;
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in grabFocus()" );
    }

    return FALSE;
}

/*****************************************************************************/

static gboolean
component_wrapper_contains (AtkComponent *component,
                            gint          x,
                            gint          y,
                            AtkCoordType  coord_type)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
        return atk_component_contains(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
            return pComponent->containsPoint( translatePoint( pComponent, x, y, coord_type ) );
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in containsPoint()" );
    }

    return FALSE;
}

/*****************************************************************************/

static AtkObject *
component_wrapper_ref_accessible_at_point (AtkComponent *component,
                                           gint          x,
                                           gint          y,
                                           AtkCoordType  coord_type)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
        return atk_component_ref_accessible_at_point(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);

        if( pComponent.is() )
        {
            uno::Reference< accessibility::XAccessible > xAccessible = pComponent->getAccessibleAtPoint(
                translatePoint( pComponent, x, y, coord_type ) );
            return atk_object_wrapper_ref( xAccessible );
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in getAccessibleAtPoint()" );
    }

    return nullptr;
}

/*****************************************************************************/

static void
component_wrapper_get_position (AtkComponent   *component,
                                gint           *x,
                                gint           *y,
                                AtkCoordType   coord_type)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
    {
        atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), x, y, nullptr, nullptr, coord_type);
        return;
    }

    *x = *y = -1;

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
        {
            awt::Point aPos;

            if( coord_type == ATK_XY_SCREEN )
                aPos = pComponent->getLocationOnScreen();
            else
                aPos = pComponent->getLocation();

            *x = aPos.X;
            *y = aPos.Y;
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in getLocation[OnScreen]()" );
    }
}

/*****************************************************************************/

static void
component_wrapper_get_size (AtkComponent   *component,
                            gint           *width,
                            gint           *height)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
    {
        atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), nullptr, nullptr, width, height, ATK_XY_WINDOW);
        return;
    }

    *width = *height = -1;

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
        {
            awt::Size aSize = pComponent->getSize();
            *width = aSize.Width;
            *height = aSize.Height;
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in getSize()" );
    }
}

/*****************************************************************************/

static void
component_wrapper_get_extents (AtkComponent *component,
                               gint         *x,
                               gint         *y,
                               gint         *width,
                               gint         *height,
                               AtkCoordType  coord_type)
{
    component_wrapper_get_position( component, x, y, coord_type );
    component_wrapper_get_size( component, width, height );
}

/*****************************************************************************/

static gboolean
component_wrapper_set_extents (AtkComponent *, gint, gint, gint, gint, AtkCoordType)
{
    g_warning( "AtkComponent::set_extents unimplementable" );
    return FALSE;
}

/*****************************************************************************/

static gboolean
component_wrapper_set_position (AtkComponent *, gint, gint, AtkCoordType)
{
    g_warning( "AtkComponent::set_position unimplementable" );
    return FALSE;
}

/*****************************************************************************/

static gboolean
component_wrapper_set_size (AtkComponent *, gint, gint)
{
    g_warning( "AtkComponent::set_size unimplementable" );
    return FALSE;
}

/*****************************************************************************/

static AtkLayer
component_wrapper_get_layer (AtkComponent   *component)
{
    AtkRole role = atk_object_get_role( ATK_OBJECT( component ) );
    AtkLayer layer = ATK_LAYER_WIDGET;

    switch (role)
    {
        case ATK_ROLE_POPUP_MENU:
        case ATK_ROLE_MENU_ITEM:
        case ATK_ROLE_CHECK_MENU_ITEM:
        case ATK_ROLE_SEPARATOR:
        case ATK_ROLE_LIST_ITEM:
            layer = ATK_LAYER_POPUP;
            break;
        case ATK_ROLE_MENU:
            {
                AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
                if( atk_object_get_role( parent ) != ATK_ROLE_MENU_BAR )
                    layer = ATK_LAYER_POPUP;
            }
            break;

        case ATK_ROLE_LIST:
            {
                AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
                if( atk_object_get_role( parent ) == ATK_ROLE_COMBO_BOX )
                    layer = ATK_LAYER_POPUP;
            }
            break;

        default:
            ;
    }

    return layer;
}

/*****************************************************************************/

static gint
component_wrapper_get_mdi_zorder (AtkComponent   *)
{
    // only needed for ATK_LAYER_MDI (not used) or ATK_LAYER_WINDOW (inherited from GAIL)
    return G_MININT;
}

/*****************************************************************************/

// This code is mostly stolen from libgail ..

static guint
component_wrapper_add_focus_handler (AtkComponent    *component,
                                     AtkFocusHandler  handler)
{
    GSignalMatchType match_type;
    gulong ret;
    guint signal_id;

    match_type = GSignalMatchType(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC);
    signal_id = g_signal_lookup( "focus-event", ATK_TYPE_OBJECT );

    ret = g_signal_handler_find( component, match_type, signal_id, 0, nullptr,
                                 static_cast<gpointer>(&handler), nullptr);
    if (!ret)
    {
        return g_signal_connect_closure_by_id (component,
                                               signal_id, 0,
                                               g_cclosure_new (
                                               G_CALLBACK (handler), nullptr,
                                               nullptr),
                                               FALSE);
    }
    else
    {
        return 0;
    }
}

/*****************************************************************************/

static void
component_wrapper_remove_focus_handler (AtkComponent  *component,
                                        guint         handler_id)
{
    g_signal_handler_disconnect (component, handler_id);
}

/*****************************************************************************/

} // extern "C"

void
componentIfaceInit (AtkComponentIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->add_focus_handler = component_wrapper_add_focus_handler;
  iface->contains = component_wrapper_contains;
  iface->get_extents = component_wrapper_get_extents;
  iface->get_layer = component_wrapper_get_layer;
  iface->get_mdi_zorder = component_wrapper_get_mdi_zorder;
  iface->get_position = component_wrapper_get_position;
  iface->get_size = component_wrapper_get_size;
  iface->grab_focus = component_wrapper_grab_focus;
  iface->ref_accessible_at_point = component_wrapper_ref_accessible_at_point;
  iface->remove_focus_handler = component_wrapper_remove_focus_handler;
  iface->set_extents = component_wrapper_set_extents;
  iface->set_position = component_wrapper_set_position;
  iface->set_size = component_wrapper_set_size;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkeditabletext.cxx b/vcl/unx/gtk/a11y/atkeditabletext.cxx
deleted file mode 100644
index 49d3eb9..0000000
--- a/vcl/unx/gtk/a11y/atkeditabletext.cxx
+++ /dev/null
@@ -1,194 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"
#include "atktextattributes.hxx"

#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
#include <com/sun/star/accessibility/TextSegment.hpp>

#include <string.h>

using namespace ::com::sun::star;

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleEditableText>
    getEditableText( AtkEditableText *pEditableText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pEditableText );
    if( pWrap )
    {
        if( !pWrap->mpEditableText.is() )
        {
            pWrap->mpEditableText.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpEditableText;
    }

    return css::uno::Reference<css::accessibility::XAccessibleEditableText>();
}

/*****************************************************************************/

extern "C" {

static gboolean
editable_text_wrapper_set_run_attributes( AtkEditableText  *text,
                                          AtkAttributeSet  *attribute_set,
                                          gint              nStartOffset,
                                          gint              nEndOffset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
        {
            uno::Sequence< beans::PropertyValue > aAttributeList;

            if( attribute_set_map_to_property_values( attribute_set, aAttributeList ) )
                return pEditableText->setAttributes(nStartOffset, nEndOffset, aAttributeList);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setAttributes()" );
    }

    return FALSE;
}

static void
editable_text_wrapper_set_text_contents( AtkEditableText  *text,
                                         const gchar      *string )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
        {
            OUString aString ( string, strlen(string), RTL_TEXTENCODING_UTF8 );
            pEditableText->setText( aString );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setText()" );
    }
}

static void
editable_text_wrapper_insert_text( AtkEditableText  *text,
                                   const gchar      *string,
                                   gint             length,
                                   gint             *pos )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
        {
            OUString aString ( string, length, RTL_TEXTENCODING_UTF8 );
            if( pEditableText->insertText( aString, *pos ) )
                *pos += length;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in insertText()" );
    }
}

static void
editable_text_wrapper_cut_text( AtkEditableText  *text,
                                gint             start,
                                gint             end )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->cutText( start, end );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in cutText()" );
    }
}

static void
editable_text_wrapper_delete_text( AtkEditableText  *text,
                                   gint             start,
                                   gint             end )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->deleteText( start, end );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in deleteText()" );
    }
}

static void
editable_text_wrapper_paste_text( AtkEditableText  *text,
                                  gint             pos )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->pasteText( pos );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in pasteText()" );
    }
}

static void
editable_text_wrapper_copy_text( AtkEditableText  *text,
                                 gint             start,
                                 gint             end )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->copyText( start, end );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in copyText()" );
    }
}

} // extern "C"

void
editableTextIfaceInit (AtkEditableTextIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->set_text_contents = editable_text_wrapper_set_text_contents;
  iface->insert_text = editable_text_wrapper_insert_text;
  iface->copy_text = editable_text_wrapper_copy_text;
  iface->cut_text = editable_text_wrapper_cut_text;
  iface->delete_text = editable_text_wrapper_delete_text;
  iface->paste_text = editable_text_wrapper_paste_text;
  iface->set_run_attributes = editable_text_wrapper_set_run_attributes;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkfactory.cxx b/vcl/unx/gtk/a11y/atkfactory.cxx
deleted file mode 100644
index 3a776b2..0000000
--- a/vcl/unx/gtk/a11y/atkfactory.cxx
+++ /dev/null
@@ -1,195 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <unx/gtk/gtkframe.hxx>
#include <vcl/window.hxx>
#include "atkwrapper.hxx"
#include "atkfactory.hxx"
#include "atkregistry.hxx"

using namespace ::com::sun::star;

extern "C" {

/*
 *  Instances of this dummy object class are returned whenever we have to
 *  create an AtkObject, but can't touch the OOo object anymore since it
 *  is already disposed.
 */

static AtkStateSet *
noop_wrapper_ref_state_set( AtkObject * )
{
    AtkStateSet *state_set = atk_state_set_new();
    atk_state_set_add_state( state_set, ATK_STATE_DEFUNCT );
    return state_set;
}

static void
atk_noop_object_wrapper_class_init(AtkNoOpObjectClass *klass)
{
    AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );
    atk_class->ref_state_set = noop_wrapper_ref_state_set;
}

static GType
atk_noop_object_wrapper_get_type()
{
    static GType type = 0;

    if (!type)
    {
        static const GTypeInfo typeInfo =
        {
            sizeof (AtkNoOpObjectClass),
            nullptr,
            nullptr,
            reinterpret_cast<GClassInitFunc>(atk_noop_object_wrapper_class_init),
            nullptr,
            nullptr,
            sizeof (AtkObjectWrapper),
            0,
            nullptr,
            nullptr
        } ;

        type = g_type_register_static (ATK_TYPE_OBJECT, "OOoAtkNoOpObj", &typeInfo, GTypeFlags(0)) ;
    }
    return type;
}

static AtkObject*
atk_noop_object_wrapper_new()
{
  AtkObject *accessible;

  accessible = static_cast<AtkObject *>(g_object_new (atk_noop_object_wrapper_get_type(), nullptr));
  g_return_val_if_fail (accessible != nullptr, nullptr);

  accessible->role = ATK_ROLE_INVALID;
  accessible->layer = ATK_LAYER_INVALID;

  return accessible;
}

/*
 * The wrapper factory
 */

static GType
wrapper_factory_get_accessible_type()
{
  return atk_object_wrapper_get_type();
}

static AtkObject*
wrapper_factory_create_accessible( GObject *obj )
{
#if GTK_CHECK_VERSION(3,0,0)
    GtkWidget* pEventBox = gtk_widget_get_parent(GTK_WIDGET(obj));

    // gail_container_real_remove_gtk tries to re-instantiate an accessible
    // for a widget that is about to vanish ..
    if (!pEventBox)
        return atk_noop_object_wrapper_new();

    GtkWidget* pTopLevelGrid = gtk_widget_get_parent(pEventBox);
    if (!pTopLevelGrid)
        return atk_noop_object_wrapper_new();

    GtkWidget* pTopLevel = gtk_widget_get_parent(pTopLevelGrid);
    if (!pTopLevel)
        return atk_noop_object_wrapper_new();
#else
    GtkWidget* pTopLevel = gtk_widget_get_parent(GTK_WIDGET(obj));

    // gail_container_real_remove_gtk tries to re-instantiate an accessible
    // for a widget that is about to vanish ..
    if (!pTopLevel)
        return atk_noop_object_wrapper_new();
#endif

    GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(GTK_WINDOW(pTopLevel));
    g_return_val_if_fail( pFrame != nullptr, nullptr );

    vcl::Window* pFrameWindow = pFrame->GetWindow();
    if( pFrameWindow )
    {
        vcl::Window* pWindow = pFrameWindow;

        // skip accessible objects already exposed by the frame objects
        if( WindowType::BORDERWINDOW == pWindow->GetType() )
            pWindow = pFrameWindow->GetAccessibleChildWindow(0);

        if( pWindow )
        {
            uno::Reference< accessibility::XAccessible > xAccessible = pWindow->GetAccessible();
            if( xAccessible.is() )
            {
                AtkObject *accessible = ooo_wrapper_registry_get( xAccessible );

                if( accessible )
                    g_object_ref( G_OBJECT(accessible) );
                else
                    accessible = atk_object_wrapper_new( xAccessible, gtk_widget_get_accessible(pTopLevel) );

                return accessible;
            }
        }
    }

    return nullptr;
}

AtkObject* ooo_fixed_get_accessible(GtkWidget *obj)
{
    return wrapper_factory_create_accessible(G_OBJECT(obj));
}

static void
wrapper_factory_class_init( AtkObjectFactoryClass *klass )
{
  klass->create_accessible   = wrapper_factory_create_accessible;
  klass->get_accessible_type = wrapper_factory_get_accessible_type;
}

GType
wrapper_factory_get_type()
{
  static GType t = 0;

  if (!t) {
    static const GTypeInfo tinfo =
    {
      sizeof (AtkObjectFactoryClass),
      nullptr, nullptr, reinterpret_cast<GClassInitFunc>(wrapper_factory_class_init),
      nullptr, nullptr, sizeof (AtkObjectFactory), 0, nullptr, nullptr
    };

    t = g_type_register_static (
        ATK_TYPE_OBJECT_FACTORY, "OOoAtkObjectWrapperFactory",
        &tinfo, GTypeFlags(0));
  }

  return t;
}

} // extern C

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkhypertext.cxx b/vcl/unx/gtk/a11y/atkhypertext.cxx
deleted file mode 100644
index ab94126..0000000
--- a/vcl/unx/gtk/a11y/atkhypertext.cxx
+++ /dev/null
@@ -1,273 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>

using namespace ::com::sun::star;

// ---------------------- AtkHyperlink ----------------------

struct HyperLink {
    AtkHyperlink const atk_hyper_link;

    uno::Reference< accessibility::XAccessibleHyperlink > xLink;
};

static uno::Reference< accessibility::XAccessibleHyperlink > const &
    getHyperlink( AtkHyperlink *pHyperlink )
{
    HyperLink *pLink = reinterpret_cast<HyperLink *>(pHyperlink);
    return pLink->xLink;
}

static GObjectClass *hyper_parent_class = nullptr;

extern "C" {

static void
hyper_link_finalize (GObject *obj)
{
    HyperLink *hl = reinterpret_cast<HyperLink *>(obj);
    hl->xLink.clear();
    hyper_parent_class->finalize (obj);
}

static gchar *
hyper_link_get_uri( AtkHyperlink *pLink,
                    gint          i )
{
    try {
        uno::Any aAny = getHyperlink( pLink )->getAccessibleActionObject( i );
        OUString aUri = aAny.get< OUString > ();
        return OUStringToGChar(aUri);
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in hyper_link_get_uri" );
    }
    return nullptr;
}

static AtkObject *
hyper_link_get_object( AtkHyperlink *,
                       gint          )
{
    g_warning( "FIXME: hyper_link_get_object unimplemented" );
    return nullptr;
}
static gint
hyper_link_get_end_index( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->getEndIndex();
    }
    catch(const uno::Exception&) {
    }
    return -1;
}
static gint
hyper_link_get_start_index( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->getStartIndex();
    }
    catch(const uno::Exception&) {
    }
    return -1;
}
static gboolean
hyper_link_is_valid( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->isValid();
    }
    catch(const uno::Exception&) {
    }
    return FALSE;
}
static gint
hyper_link_get_n_anchors( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->getAccessibleActionCount();
    }
    catch(const uno::Exception&) {
    }
    return 0;
}

static guint
hyper_link_link_state( AtkHyperlink * )
{
    g_warning( "FIXME: hyper_link_link_state unimplemented" );
    return 0;
}
static gboolean
hyper_link_is_selected_link( AtkHyperlink * )
{
    g_warning( "FIXME: hyper_link_is_selected_link unimplemented" );
    return FALSE;
}

static void
hyper_link_class_init (AtkHyperlinkClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = hyper_link_finalize;

    hyper_parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));

    klass->get_uri = hyper_link_get_uri;
    klass->get_object = hyper_link_get_object;
    klass->get_end_index = hyper_link_get_end_index;
    klass->get_start_index = hyper_link_get_start_index;
    klass->is_valid = hyper_link_is_valid;
    klass->get_n_anchors = hyper_link_get_n_anchors;
    klass->link_state = hyper_link_link_state;
    klass->is_selected_link = hyper_link_is_selected_link;
}

static GType
hyper_link_get_type()
{
    static GType type = 0;

    if (!type) {
        static const GTypeInfo tinfo = {
            sizeof (AtkHyperlinkClass),
            nullptr,               /* base init */
            nullptr,               /* base finalize */
            reinterpret_cast<GClassInitFunc>(hyper_link_class_init),
            nullptr,               /* class finalize */
            nullptr,               /* class data */
            sizeof (HyperLink), /* instance size */
            0,                  /* nb preallocs */
            nullptr,               /* instance init */
            nullptr                /* value table */
        };

        static const GInterfaceInfo atk_action_info = {
            reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
            nullptr,
            nullptr
        };

        type = g_type_register_static (ATK_TYPE_HYPERLINK,
                                       "OOoAtkObjHyperLink", &tinfo,
                                       GTypeFlags(0));
        g_type_add_interface_static (type, ATK_TYPE_ACTION,
                                     &atk_action_info);
    }

    return type;
}

// ---------------------- AtkHyperText ----------------------

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleHypertext>
    getHypertext( AtkHypertext *pHypertext )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pHypertext );
    if( pWrap )
    {
        if( !pWrap->mpHypertext.is() )
        {
            pWrap->mpHypertext.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpHypertext;
    }

    return css::uno::Reference<css::accessibility::XAccessibleHypertext>();
}

static AtkHyperlink *
hypertext_get_link( AtkHypertext *hypertext,
                    gint          link_index)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
            = getHypertext( hypertext );
        if( pHypertext.is() )
        {
            HyperLink *pLink = static_cast<HyperLink *>(g_object_new( hyper_link_get_type(), nullptr ));
            pLink->xLink = pHypertext->getHyperLink( link_index );
            if( !pLink->xLink.is() ) {
                g_object_unref( G_OBJECT( pLink ) );
                pLink = nullptr;
            }
            return ATK_HYPERLINK( pLink );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getHyperLink()" );
    }

    return nullptr;
}

static gint
hypertext_get_n_links( AtkHypertext *hypertext )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
            = getHypertext( hypertext );
        if( pHypertext.is() )
            return pHypertext->getHyperLinkCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getHyperLinkCount()" );
    }

    return 0;
}

static gint
hypertext_get_link_index( AtkHypertext *hypertext,
                          gint          index)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
            = getHypertext( hypertext );
        if( pHypertext.is() )
            return pHypertext->getHyperLinkIndex( index );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getHyperLinkIndex()" );
    }

    return 0;
}

} // extern "C"

void
hypertextIfaceInit (AtkHypertextIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->get_link = hypertext_get_link;
  iface->get_n_links = hypertext_get_n_links;
  iface->get_link_index = hypertext_get_link_index;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkimage.cxx b/vcl/unx/gtk/a11y/atkimage.cxx
deleted file mode 100644
index acd43f4..0000000
--- a/vcl/unx/gtk/a11y/atkimage.cxx
+++ /dev/null
@@ -1,131 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleImage.hpp>

using namespace ::com::sun::star;

// FIXME
static const gchar *
getAsConst( const OUString& rString )
{
    static const int nMax = 10;
    static OString aUgly[nMax];
    static int nIdx = 0;
    nIdx = (nIdx + 1) % nMax;
    aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
    return aUgly[ nIdx ].getStr();
}

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleImage>
    getImage( AtkImage *pImage )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pImage );
    if( pWrap )
    {
        if( !pWrap->mpImage.is() )
        {
            pWrap->mpImage.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpImage;
    }

    return css::uno::Reference<css::accessibility::XAccessibleImage>();
}

extern "C" {

static const gchar *
image_get_image_description( AtkImage *image )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleImage> pImage
            = getImage( image );
        if( pImage.is() )
            return getAsConst( pImage->getAccessibleImageDescription() );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleImageDescription()" );
    }

    return nullptr;
}

static void
image_get_image_position( AtkImage     *image,
                          gint         *x,
                          gint         *y,
                          AtkCoordType  coord_type )
{
    *x = *y = -1;
    if( ATK_IS_COMPONENT( image ) )
    {
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
        atk_component_get_position( ATK_COMPONENT( image ), x, y, coord_type );
        SAL_WNODEPRECATED_DECLARATIONS_POP
    }
    else
        g_warning( "FIXME: no image position information" );
}

static void
image_get_image_size( AtkImage *image,
                      gint     *width,
                      gint     *height )
{
    *width = *height = -1;
    try {
        css::uno::Reference<css::accessibility::XAccessibleImage> pImage
            = getImage( image );
        if( pImage.is() )
        {
            *width = pImage->getAccessibleImageWidth();
            *height = pImage->getAccessibleImageHeight();
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleImageHeight() or Width" );
    }
}

static gboolean
image_set_image_description( AtkImage *, const gchar * )
{
    g_warning ("FIXME: no set image description");
    return FALSE;
}

} // extern "C"

void
imageIfaceInit (AtkImageIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->set_image_description = image_set_image_description;
  iface->get_image_description = image_get_image_description;
  iface->get_image_position = image_get_image_position;
  iface->get_image_size = image_get_image_size;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atklistener.cxx b/vcl/unx/gtk/a11y/atklistener.cxx
deleted file mode 100644
index c30bc63..0000000
--- a/vcl/unx/gtk/a11y/atklistener.cxx
+++ /dev/null
@@ -1,739 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#ifdef AIX
#define _LINUX_SOURCE_COMPAT
#include <sys/timer.h>
#undef _LINUX_SOURCE_COMPAT
#endif

#include <com/sun/star/accessibility/TextSegment.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>

#include "atklistener.hxx"
#include "atkwrapper.hxx"
#include <vcl/svapp.hxx>

#include <rtl/ref.hxx>
#include <sal/log.hxx>

#define DEBUG_ATK_LISTENER 0

#if DEBUG_ATK_LISTENER
#include <iostream>
#include <sstream>
#endif

using namespace com::sun::star;

AtkListener::AtkListener( AtkObjectWrapper* pWrapper ) : mpWrapper( pWrapper )
{
    if( mpWrapper )
    {
        g_object_ref( mpWrapper );
        updateChildList( mpWrapper->mpContext );
    }
}

AtkListener::~AtkListener()
{
    if( mpWrapper )
        g_object_unref( mpWrapper );
}

/*****************************************************************************/

static AtkStateType mapState( const uno::Any &rAny )
{
    sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
    rAny >>= nState;
    return mapAtkState( nState );
}

/*****************************************************************************/

extern "C" {
    // rhbz#1001768 - down to horrific problems releasing the solar mutex
    // while destroying a Window - which occurs inside these notifications.
    static gboolean
    idle_defunc_state_change( AtkObject *atk_obj )
    {
        SolarMutexGuard aGuard;

        // This is an equivalent to a state change to DEFUNC(T).
        atk_object_notify_state_change( atk_obj, ATK_STATE_DEFUNCT, TRUE );
        if( atk_get_focus_object() == atk_obj )
        {
            SAL_WNODEPRECATED_DECLARATIONS_PUSH
            atk_focus_tracker_notify( nullptr );
            SAL_WNODEPRECATED_DECLARATIONS_POP
        }
        g_object_unref( G_OBJECT( atk_obj ) );
        return false;
    }
}

// XEventListener implementation
void AtkListener::disposing( const lang::EventObject& )
{
    if( mpWrapper )
    {
        AtkObject *atk_obj = ATK_OBJECT( mpWrapper );

        // Release all interface references to avoid shutdown problems with
        // global mutex
        atk_object_wrapper_dispose( mpWrapper );

        g_idle_add( reinterpret_cast<GSourceFunc>(idle_defunc_state_change),
                    g_object_ref( G_OBJECT( atk_obj ) ) );

        // Release the wrapper object so that it can vanish ..
        g_object_unref( mpWrapper );
        mpWrapper = nullptr;
    }
}

/*****************************************************************************/

static AtkObject *getObjFromAny( const uno::Any &rAny )
{
    uno::Reference< accessibility::XAccessible > xAccessible;
    rAny >>= xAccessible;
    return xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
}

/*****************************************************************************/

// Updates the child list held to provide the old IndexInParent on children_changed::remove
void AtkListener::updateChildList(
    css::uno::Reference<css::accessibility::XAccessibleContext> const &
        pContext)
{
     m_aChildList.clear();

     uno::Reference< accessibility::XAccessibleStateSet > xStateSet = pContext->getAccessibleStateSet();
     if( xStateSet.is()
         && !xStateSet->contains(accessibility::AccessibleStateType::DEFUNC)
         && !xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
     {
         sal_Int32 nChildren = pContext->getAccessibleChildCount();
         m_aChildList.resize(nChildren);
         for(sal_Int32 n = 0; n < nChildren; n++)
         {
             try
             {
                 m_aChildList[n] = pContext->getAccessibleChild(n);
             }
             catch (lang::IndexOutOfBoundsException const&)
             {
                 sal_Int32 nChildren2 = pContext->getAccessibleChildCount();
                 assert(nChildren2 <= n && "consistency?");
                 m_aChildList.resize(std::min(nChildren2, n));
                 break;
             }
         }
     }
}

/*****************************************************************************/

void AtkListener::handleChildAdded(
    const uno::Reference< accessibility::XAccessibleContext >& rxParent,
    const uno::Reference< accessibility::XAccessible>& rxAccessible)
{
    AtkObject * pChild = rxAccessible.is() ? atk_object_wrapper_ref( rxAccessible ) : nullptr;

    if( pChild )
    {
        updateChildList(rxParent);

        atk_object_wrapper_add_child( mpWrapper, pChild,
            atk_object_get_index_in_parent( pChild ));

        g_object_unref( pChild );
    }
}

/*****************************************************************************/

void AtkListener::handleChildRemoved(
    const uno::Reference< accessibility::XAccessibleContext >& rxParent,
    const uno::Reference< accessibility::XAccessible>& rxChild)
{
    sal_Int32 nIndex = -1;

    // Locate the child in the children list
    size_t n, nmax = m_aChildList.size();
    for( n = 0; n < nmax; ++n )
    {
        if( rxChild == m_aChildList[n] )
        {
            nIndex = n;
            break;
        }
    }

    // FIXME: two problems here:
    // a) we get child-removed events for objects that are no real children
    //    in the accessibility hierarchy or have been removed before due to
    //    some child removing batch.
    // b) spi_atk_bridge_signal_listener ignores the given parameters
    //    for children_changed events and always asks the parent for the
    //    0. child, which breaks somehow on vanishing list boxes.
    // Ignoring "remove" events for objects not in the m_aChildList
    // for now.
    if( nIndex >= 0 )
    {
        uno::Reference<accessibility::XAccessibleEventBroadcaster> xBroadcaster(
            rxChild->getAccessibleContext(), uno::UNO_QUERY);

        if (xBroadcaster.is())
        {
            uno::Reference<accessibility::XAccessibleEventListener> xListener(this);
            xBroadcaster->removeAccessibleEventListener(xListener);
        }

        updateChildList(rxParent);

        AtkObject * pChild = atk_object_wrapper_ref( rxChild, false );
        if( pChild )
        {
            atk_object_wrapper_remove_child( mpWrapper, pChild, nIndex );
            g_object_unref( pChild );
        }
    }
}

/*****************************************************************************/

void AtkListener::handleInvalidateChildren(
    const uno::Reference< accessibility::XAccessibleContext >& rxParent)
{
    // Send notifications for all previous children
    size_t n = m_aChildList.size();
    while( n-- > 0 )
    {
        if( m_aChildList[n].is() )
        {
            AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n], false );
            if( pChild )
            {
                atk_object_wrapper_remove_child( mpWrapper, pChild, n );
                g_object_unref( pChild );
            }
        }
    }

    updateChildList(rxParent);

    // Send notifications for all new children
    size_t nmax = m_aChildList.size();
    for( n = 0; n < nmax; ++n )
    {
        if( m_aChildList[n].is() )
        {
            AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n] );

            if( pChild )
            {
                atk_object_wrapper_add_child( mpWrapper, pChild, n );
                g_object_unref( pChild );
            }
        }
    }
}

/*****************************************************************************/

static uno::Reference< accessibility::XAccessibleContext >
getAccessibleContextFromSource( const uno::Reference< uno::XInterface >& rxSource )
{
    uno::Reference< accessibility::XAccessibleContext > xContext(rxSource, uno::UNO_QUERY);
    if( ! xContext.is() )
    {
         g_warning( "ERROR: Event source does not implement XAccessibleContext" );

         // Second try - query for XAccessible, which should give us access to
         // XAccessibleContext.
         uno::Reference< accessibility::XAccessible > xAccessible(rxSource, uno::UNO_QUERY);
         if( xAccessible.is() )
             xContext = xAccessible->getAccessibleContext();
    }

    return xContext;
}

#if DEBUG_ATK_LISTENER

namespace {

void printNotifyEvent( const accessibility::AccessibleEventObject& rEvent )
{
    static std::vector<const char*> aLabels = {
        0,
        "NAME_CHANGED",                                       // 01
        "DESCRIPTION_CHANGED",                                // 02
        "ACTION_CHANGED",                                     // 03
        "STATE_CHANGED",                                      // 04
        "ACTIVE_DESCENDANT_CHANGED",                          // 05
        "BOUNDRECT_CHANGED",                                  // 06
        "CHILD",                                              // 07
        "INVALIDATE_ALL_CHILDREN",                            // 08
        "SELECTION_CHANGED",                                  // 09
        "VISIBLE_DATA_CHANGED",                               // 10
        "VALUE_CHANGED",                                      // 11
        "CONTENT_FLOWS_FROM_RELATION_CHANGED",                // 12
        "CONTENT_FLOWS_TO_RELATION_CHANGED",                  // 13
        "CONTROLLED_BY_RELATION_CHANGED",                     // 14
        "CONTROLLER_FOR_RELATION_CHANGED",                    // 15
        "LABEL_FOR_RELATION_CHANGED",                         // 16
        "LABELED_BY_RELATION_CHANGED",                        // 17
        "MEMBER_OF_RELATION_CHANGED",                         // 18
        "SUB_WINDOW_OF_RELATION_CHANGED",                     // 19
        "CARET_CHANGED",                                      // 20
        "TEXT_SELECTION_CHANGED",                             // 21
        "TEXT_CHANGED",                                       // 22
        "TEXT_ATTRIBUTE_CHANGED",                             // 23
        "HYPERTEXT_CHANGED",                                  // 24
        "TABLE_CAPTION_CHANGED",                              // 25
        "TABLE_COLUMN_DESCRIPTION_CHANGED",                   // 26
        "TABLE_COLUMN_HEADER_CHANGED",                        // 27
        "TABLE_MODEL_CHANGED",                                // 28
        "TABLE_ROW_DESCRIPTION_CHANGED",                      // 29
        "TABLE_ROW_HEADER_CHANGED",                           // 30
        "TABLE_SUMMARY_CHANGED",                              // 31
        "LISTBOX_ENTRY_EXPANDED",                             // 32
        "LISTBOX_ENTRY_COLLAPSED",                            // 33
        "ACTIVE_DESCENDANT_CHANGED_NOFOCUS",                  // 34
        "SELECTION_CHANGED_ADD",                              // 35
        "SELECTION_CHANGED_REMOVE",                           // 36
        "SELECTION_CHANGED_WITHIN",                           // 37
        "PAGE_CHANGED",                                       // 38
        "SECTION_CHANGED",                                    // 39
        "COLUMN_CHANGED",                                     // 40
        "ROLE_CHANGED",                                       // 41
    };

    static std::vector<const char*> aStates = {
        "INVALID",             // 00
        "ACTIVE",              // 01
        "ARMED",               // 02
        "BUSY",                // 03
        "CHECKED",             // 04
        "DEFUNC",              // 05
        "EDITABLE",            // 06
        "ENABLED",             // 07
        "EXPANDABLE",          // 08
        "EXPANDED",            // 09
        "FOCUSABLE",           // 10
        "FOCUSED",             // 11
        "HORIZONTAL",          // 12
        "ICONIFIED",           // 13
        "INDETERMINATE",       // 14
        "MANAGES_DESCENDANTS", // 15
        "MODAL",               // 16
        "MULTI_LINE",          // 17
        "MULTI_SELECTABLE",    // 18
        "OPAQUE",              // 19
        "PRESSED",             // 20
        "RESIZABLE",           // 21
        "SELECTABLE",          // 22
        "SELECTED",            // 23
        "SENSITIVE",           // 24
        "SHOWING",             // 25
        "SINGLE_LINE",         // 26
        "STALE",               // 27
        "TRANSIENT",           // 28
        "VERTICAL",            // 29
        "VISIBLE",             // 30
        "MOVEABLE",            // 31
        "DEFAULT",             // 32
        "OFFSCREEN",           // 33
        "COLLAPSE",            // 34
    };

    auto getOrUnknown = [](const std::vector<const char*>& rCont, size_t nIndex) -> std::string
    {
        return (nIndex < rCont.size()) ? rCont[nIndex] : "<unknown>";
    };

    std::ostringstream os;
    os << "--" << std::endl;
    os << "* event = " << getOrUnknown(aLabels, rEvent.EventId) << std::endl;

    switch (rEvent.EventId)
    {
        case accessibility::AccessibleEventId::STATE_CHANGED:
        {
            sal_Int16 nState;
            if (rEvent.OldValue >>= nState)
                os << "  * old state = " << getOrUnknown(aStates, nState);
            if (rEvent.NewValue >>= nState)
                os << "  * new state = " << getOrUnknown(aStates, nState);

            os << std::endl;
            break;
        }
        default:
            ;
    }

    std::cout << os.str();
}

}

#endif

void AtkListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
{
    if( !mpWrapper )
        return;

    AtkObject *atk_obj = ATK_OBJECT( mpWrapper );

    switch( aEvent.EventId )
    {
    // AtkObject signals:
        // Hierarchy signals
        case accessibility::AccessibleEventId::CHILD:
        {
            uno::Reference< accessibility::XAccessibleContext > xParent;
            uno::Reference< accessibility::XAccessible > xChild;

            xParent = getAccessibleContextFromSource(aEvent.Source);
            g_return_if_fail( xParent.is() );

            if( aEvent.OldValue >>= xChild )
                handleChildRemoved(xParent, xChild);

            if( aEvent.NewValue >>= xChild )
                handleChildAdded(xParent, xChild);
            break;
        }

        case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
        {
            uno::Reference< accessibility::XAccessibleContext > xParent = getAccessibleContextFromSource(aEvent.Source);
            g_return_if_fail( xParent.is() );

            handleInvalidateChildren(xParent);
            break;
        }

        case accessibility::AccessibleEventId::NAME_CHANGED:
        {
            OUString aName;
            if( aEvent.NewValue >>= aName )
            {
                atk_object_set_name(atk_obj,
                    OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr());
            }
            break;
        }

        case accessibility::AccessibleEventId::DESCRIPTION_CHANGED:
        {
            OUString aDescription;
            if( aEvent.NewValue >>= aDescription )
            {
                atk_object_set_description(atk_obj,
                    OUStringToOString(aDescription, RTL_TEXTENCODING_UTF8).getStr());
            }
            break;
        }

        case accessibility::AccessibleEventId::STATE_CHANGED:
        {
            AtkStateType eOldState = mapState( aEvent.OldValue );
            AtkStateType eNewState = mapState( aEvent.NewValue );

            gboolean bState = eNewState != ATK_STATE_INVALID;
            AtkStateType eRealState = bState ? eNewState : eOldState;

            atk_object_notify_state_change( atk_obj, eRealState, bState );
            break;
        }

        case accessibility::AccessibleEventId::BOUNDRECT_CHANGED:

#ifdef HAS_ATKRECTANGLE
            if( ATK_IS_COMPONENT( atk_obj ) )
            {
                AtkRectangle rect;

                atk_component_get_extents( ATK_COMPONENT( atk_obj ),
                                           &rect.x,
                                           &rect.y,
                                           &rect.width,
                                           &rect.height,
                                           ATK_XY_SCREEN );

                g_signal_emit_by_name( atk_obj, "bounds_changed", &rect );
            }
            else
                g_warning( "bounds_changed event for object not implementing AtkComponent\n");
#endif

            break;

        case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED:
            g_signal_emit_by_name( atk_obj, "visible-data-changed" );
            break;

        case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
        {
            AtkObject *pChild = getObjFromAny( aEvent.NewValue );
            if( pChild )
            {
                g_signal_emit_by_name( atk_obj, "active-descendant-changed", pChild );
                g_object_unref( pChild );
            }
            break;
        }

        //ACTIVE_DESCENDANT_CHANGED_NOFOCUS (sic) appears to have been added
        //as a workaround or an aid for the ia2 winaccessibility implementation
        //so ignore it silently without warning here
        case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
            break;

        // #i92103#
        case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
        {
            AtkObject *pChild = getObjFromAny( aEvent.NewValue );
            if( pChild )
            {
                atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, true );
                g_object_unref( pChild );
            }
            break;
        }

        case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
        {
            AtkObject *pChild = getObjFromAny( aEvent.NewValue );
            if( pChild )
            {
                atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, false );
                g_object_unref( pChild );
            }
            break;
        }

        // AtkAction signals ...
        case accessibility::AccessibleEventId::ACTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-actions");
            break;

        // AtkText
        case accessibility::AccessibleEventId::CARET_CHANGED:
        {
            sal_Int32 nPos=0;
            aEvent.NewValue >>= nPos;
            g_signal_emit_by_name( atk_obj, "text_caret_moved", nPos );
            break;
        }
        case accessibility::AccessibleEventId::TEXT_CHANGED:
        {
            // TESTME: and remove this comment:
            // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
            accessibility::TextSegment aDeletedText;
            accessibility::TextSegment aInsertedText;

            // TODO: when GNOME starts to send "update" kind of events, change
            // we need to re-think this implementation as well
            if( aEvent.OldValue >>= aDeletedText )
            {
                /* Remember the text segment here to be able to return removed text in get_text().
                 * This is clearly a hack to be used until appropriate API exists in atk to pass
                 * the string value directly or we find a compelling reason to start caching the
                 * UTF-8 converted strings in the atk wrapper object.
                 */

                g_object_set_data( G_OBJECT(atk_obj), "ooo::text_changed::delete", &aDeletedText);

                g_signal_emit_by_name( atk_obj, "text_changed::delete",
                                       static_cast<gint>(aDeletedText.SegmentStart),
                                       static_cast<gint>( aDeletedText.SegmentEnd - aDeletedText.SegmentStart ) );

                g_object_steal_data( G_OBJECT(atk_obj), "ooo::text_changed::delete" );
            }

            if( aEvent.NewValue >>= aInsertedText )
                g_signal_emit_by_name( atk_obj, "text_changed::insert",
                                       static_cast<gint>(aInsertedText.SegmentStart),
                                       static_cast<gint>( aInsertedText.SegmentEnd - aInsertedText.SegmentStart ) );
            break;
        }

        case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED:
        {
            g_signal_emit_by_name( atk_obj, "text-selection-changed" );
            break;
        }

        case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
            g_signal_emit_by_name( atk_obj, "text-attributes-changed" );
            break;

        // AtkValue
        case accessibility::AccessibleEventId::VALUE_CHANGED:
            g_object_notify( G_OBJECT( atk_obj ), "accessible-value" );
            break;

        case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
        case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
        case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
        case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
        case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
        case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED:
        case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
        case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
            // FIXME: ask Bill how Atk copes with this little lot ...
            break;

        // AtkTable
        case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED:
        {
            accessibility::AccessibleTableModelChange aChange;
            aEvent.NewValue >>= aChange;

            sal_Int32 nRowsChanged = aChange.LastRow - aChange.FirstRow + 1;
            sal_Int32 nColumnsChanged = aChange.LastColumn - aChange.FirstColumn + 1;

            static const struct {
                    const char *row;
                    const char *col;
            } aSignalNames[] =
            {
                { nullptr, nullptr }, // dummy
                { "row_inserted", "column_inserted" }, // INSERT = 1
                { "row_deleted", "column_deleted" } // DELETE = 2
            };
            switch( aChange.Type )
            {
                case accessibility::AccessibleTableModelChangeType::INSERT:
                case accessibility::AccessibleTableModelChangeType::DELETE:
                    if( nRowsChanged > 0 )
                        g_signal_emit_by_name( G_OBJECT( atk_obj ),
                                               aSignalNames[aChange.Type].row,
                                               aChange.FirstRow, nRowsChanged );
                    if( nColumnsChanged > 0 )
                        g_signal_emit_by_name( G_OBJECT( atk_obj ),
                                               aSignalNames[aChange.Type].col,
                                               aChange.FirstColumn, nColumnsChanged );
                    break;

                case accessibility::AccessibleTableModelChangeType::UPDATE:
                    // This is not really a model change, is it ?
                    break;
                default:
                    g_warning( "TESTME: unusual table model change %d\n", aChange.Type );
                    break;
            }
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "model-changed" );
            break;
        }

        case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
        {
            accessibility::AccessibleTableModelChange aChange;
            aEvent.NewValue >>= aChange;

            AtkPropertyValues values;
            memset(&values,  0, sizeof(AtkPropertyValues));
            g_value_init (&values.new_value, G_TYPE_INT);
            values.property_name = "accessible-table-column-header";

            for (sal_Int32 nChangedColumn = aChange.FirstColumn; nChangedColumn <= aChange.LastColumn; ++nChangedColumn)
            {
                g_value_set_int (&values.new_value, nChangedColumn);
                g_signal_emit_by_name(G_OBJECT(atk_obj), "property_change::accessible-table-column-header", &values, nullptr);
            }
            break;
        }

        case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-caption");
            break;

        case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-description");
            break;

        case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-description");
            break;

        case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-header");
            break;

        case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-summary");
            break;

        case accessibility::AccessibleEventId::SELECTION_CHANGED:
        case accessibility::AccessibleEventId::SELECTION_CHANGED_ADD:
        case accessibility::AccessibleEventId::SELECTION_CHANGED_REMOVE:
        case accessibility::AccessibleEventId::SELECTION_CHANGED_WITHIN:
            if (ATK_IS_SELECTION(atk_obj))
                g_signal_emit_by_name(G_OBJECT(atk_obj), "selection_changed");
            else
            {
                // e.g. tdf#122353, when such dialogs become native the problem will go away anyway
                SAL_INFO("vcl.gtk", "selection change from obj which doesn't support XAccessibleSelection");
            }
            break;

        case accessibility::AccessibleEventId::HYPERTEXT_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-hypertext-offset");
            break;

        case accessibility::AccessibleEventId::ROLE_CHANGED:
        {
            uno::Reference< accessibility::XAccessibleContext > xContext = getAccessibleContextFromSource( aEvent.Source );
            atk_object_wrapper_set_role( mpWrapper, xContext->getAccessibleRole() );
            break;
        }

        case accessibility::AccessibleEventId::PAGE_CHANGED:
        {
            /* // If we implemented AtkDocument then I imagine this is what this
               // handler should look like
               sal_Int32 nPos=0;
               aEvent.NewValue >>= nPos;
               g_signal_emit_by_name( G_OBJECT( atk_obj ), "page_changed", nPos );
            */
            break;
        }

        default:
            SAL_WARN("vcl.gtk", "Unknown event notification: " << aEvent.EventId);
            break;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkregistry.cxx b/vcl/unx/gtk/a11y/atkregistry.cxx
deleted file mode 100644
index ff96378..0000000
--- a/vcl/unx/gtk/a11y/atkregistry.cxx
+++ /dev/null
@@ -1,66 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkregistry.hxx"

using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;

static GHashTable *uno_to_gobject = nullptr;

/*****************************************************************************/

AtkObject *
ooo_wrapper_registry_get(const Reference< XAccessible >& rxAccessible)
{
    if( uno_to_gobject )
    {
        gpointer cached =
            g_hash_table_lookup(uno_to_gobject, static_cast<gpointer>(rxAccessible.get()));

        if( cached )
            return ATK_OBJECT( cached );
    }

    return nullptr;
}

/*****************************************************************************/

void
ooo_wrapper_registry_add(const Reference< XAccessible >& rxAccessible, AtkObject *obj)
{
   if( !uno_to_gobject )
        uno_to_gobject = g_hash_table_new (nullptr, nullptr);

   g_hash_table_insert( uno_to_gobject, static_cast<gpointer>(rxAccessible.get()), obj );
}

/*****************************************************************************/

void
ooo_wrapper_registry_remove(
    css::uno::Reference<css::accessibility::XAccessible> const & pAccessible)
{
    if( uno_to_gobject )
        g_hash_table_remove(
            uno_to_gobject, static_cast<gpointer>(pAccessible.get()) );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkselection.cxx b/vcl/unx/gtk/a11y/atkselection.cxx
deleted file mode 100644
index 1d9772b..0000000
--- a/vcl/unx/gtk/a11y/atkselection.cxx
+++ /dev/null
@@ -1,190 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleSelection.hpp>

using namespace ::com::sun::star;

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleSelection>
    getSelection( AtkSelection *pSelection )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pSelection );
    if( pWrap )
    {
        if( !pWrap->mpSelection.is() )
        {
            pWrap->mpSelection.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpSelection;
    }

    return css::uno::Reference<css::accessibility::XAccessibleSelection>();
}

extern "C" {

static gboolean
selection_add_selection( AtkSelection *selection,
                         gint          i )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->selectAccessibleChild( i );
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in selectAccessibleChild()" );
    }

    return FALSE;
}

static gboolean
selection_clear_selection( AtkSelection *selection )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->clearAccessibleSelection();
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in selectAccessibleChild()" );
    }

    return FALSE;
}

static AtkObject*
selection_ref_selection( AtkSelection *selection,
                         gint          i )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
            return atk_object_wrapper_ref( pSelection->getSelectedAccessibleChild( i ) );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChild()" );
    }

    return nullptr;
}

static gint
selection_get_selection_count( AtkSelection   *selection)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
            return pSelection->getSelectedAccessibleChildCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return -1;
}

static gboolean
selection_is_child_selected( AtkSelection   *selection,
                              gint           i)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
            return pSelection->isAccessibleChildSelected( i );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return FALSE;
}

static gboolean
selection_remove_selection( AtkSelection *selection,
                            gint           i )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->deselectAccessibleChild( i );
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return FALSE;
}

static gboolean
selection_select_all_selection( AtkSelection   *selection)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->selectAllAccessibleChildren();
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return FALSE;
}

} // extern "C"

void
selectionIfaceInit( AtkSelectionIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->add_selection = selection_add_selection;
  iface->clear_selection = selection_clear_selection;
  iface->ref_selection = selection_ref_selection;
  iface->get_selection_count = selection_get_selection_count;
  iface->is_child_selected = selection_is_child_selected;
  iface->remove_selection = selection_remove_selection;
  iface->select_all_selection = selection_select_all_selection;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atktable.cxx b/vcl/unx/gtk/a11y/atktable.cxx
deleted file mode 100644
index 221e55d..0000000
--- a/vcl/unx/gtk/a11y/atktable.cxx
+++ /dev/null
@@ -1,580 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleTable.hpp>
#include <comphelper/sequence.hxx>

using namespace ::com::sun::star;

static AtkObject *
atk_object_wrapper_conditional_ref( const uno::Reference< accessibility::XAccessible >& rxAccessible )
{
    if( rxAccessible.is() )
        return atk_object_wrapper_ref( rxAccessible );

    return nullptr;
}

/*****************************************************************************/

// FIXME
static const gchar *
getAsConst( const OUString& rString )
{
    static const int nMax = 10;
    static OString aUgly[nMax];
    static int nIdx = 0;
    nIdx = (nIdx + 1) % nMax;
    aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
    return aUgly[ nIdx ].getStr();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleTable>
    getTable( AtkTable *pTable )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pTable );
    if( pWrap )
    {
        if( !pWrap->mpTable.is() )
        {
            pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpTable;
    }

    return css::uno::Reference<css::accessibility::XAccessibleTable>();
}

/*****************************************************************************/

extern "C" {

static AtkObject*
table_wrapper_ref_at (AtkTable *table,
                      gint      row,
                      gint      column)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable = getTable( table );
        if( pTable.is() )
            return atk_object_wrapper_conditional_ref( pTable->getAccessibleCellAt( row, column ) );
    }

    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleCellAt()" );
    }

    return nullptr;
}

/*****************************************************************************/

static gint
table_wrapper_get_index_at (AtkTable      *table,
                            gint          row,
                            gint          column)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleIndex( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleIndex()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_column_at_index (AtkTable      *table,
                                   gint          nIndex)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleColumn( nIndex );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumn()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_row_at_index( AtkTable *table,
                                gint      nIndex )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleRow( nIndex );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRow()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_n_columns( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleColumnCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnCount()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_n_rows( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleRowCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowCount()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_column_extent_at( AtkTable *table,
                                    gint      row,
                                    gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleColumnExtentAt( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnExtentAt()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_row_extent_at( AtkTable *table,
                                 gint      row,
                                 gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleRowExtentAt( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowExtentAt()" );
    }

    return -1;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_caption( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return atk_object_wrapper_conditional_ref( pTable->getAccessibleCaption() );
    }

    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleCaption()" );
    }

    return nullptr;
}

/*****************************************************************************/

static const gchar *
table_wrapper_get_row_description( AtkTable *table,
                                   gint      row )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return getAsConst( pTable->getAccessibleRowDescription( row ) );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowDescription()" );
    }

    return nullptr;
}

/*****************************************************************************/

static const gchar *
table_wrapper_get_column_description( AtkTable *table,
                                      gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return getAsConst( pTable->getAccessibleColumnDescription( column ) );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnDescription()" );
    }

    return nullptr;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_row_header( AtkTable *table,
                              gint      row )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
        {
            uno::Reference< accessibility::XAccessibleTable > xRowHeaders( pTable->getAccessibleRowHeaders() );
            if( xRowHeaders.is() )
                return atk_object_wrapper_conditional_ref( xRowHeaders->getAccessibleCellAt( row, 0 ) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowHeaders()" );
    }

    return nullptr;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_column_header( AtkTable *table,
                                 gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
        {
            uno::Reference< accessibility::XAccessibleTable > xColumnHeaders( pTable->getAccessibleColumnHeaders() );
            if( xColumnHeaders.is() )
                return atk_object_wrapper_conditional_ref( xColumnHeaders->getAccessibleCellAt( 0, column ) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnHeaders()" );
    }

    return nullptr;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_summary( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
        {
            return atk_object_wrapper_conditional_ref( pTable->getAccessibleSummary() );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleSummary()" );
    }

    return nullptr;
}

/*****************************************************************************/

static gint
convertToGIntArray( const uno::Sequence< ::sal_Int32 >& aSequence, gint **pSelected )
{
    if( aSequence.hasElements() )
    {
        *pSelected = g_new( gint, aSequence.getLength() );

        *pSelected = comphelper::sequenceToArray(*pSelected, aSequence);
    }

    return aSequence.getLength();
}

/*****************************************************************************/

static gint
table_wrapper_get_selected_columns( AtkTable      *table,
                                    gint          **pSelected )
{
    *pSelected = nullptr;
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return convertToGIntArray( pTable->getSelectedAccessibleColumns(), pSelected );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleColumns()" );
    }

    return 0;
}

/*****************************************************************************/

static gint
table_wrapper_get_selected_rows( AtkTable      *table,
                                 gint          **pSelected )
{
    *pSelected = nullptr;
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return convertToGIntArray( pTable->getSelectedAccessibleRows(), pSelected );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleRows()" );
    }

    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_is_column_selected( AtkTable      *table,
                                  gint          column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->isAccessibleColumnSelected( column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in isAccessibleColumnSelected()" );
    }

    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_is_row_selected( AtkTable      *table,
                               gint          row )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->isAccessibleRowSelected( row );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in isAccessibleRowSelected()" );
    }

    return FALSE;
}

/*****************************************************************************/

static gboolean
table_wrapper_is_selected( AtkTable      *table,
                           gint          row,
                           gint          column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->isAccessibleSelected( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in isAccessibleSelected()" );
    }

    return FALSE;
}

/*****************************************************************************/

static gboolean
table_wrapper_add_row_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for add_row_selection" );
    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_remove_row_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for remove_row_selection" );
    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_add_column_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for add_column_selection" );
    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_remove_column_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for remove_column_selection" );
    return 0;
}

/*****************************************************************************/

static void
table_wrapper_set_caption( AtkTable *, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_column_description( AtkTable *, gint, const gchar * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_column_header( AtkTable *, gint, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_row_description( AtkTable *, gint, const gchar * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_row_header( AtkTable *, gint, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_summary( AtkTable *, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

} // extern "C"

void
tableIfaceInit (AtkTableIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->ref_at = table_wrapper_ref_at;
  iface->get_n_rows = table_wrapper_get_n_rows;
  iface->get_n_columns = table_wrapper_get_n_columns;
  iface->get_index_at = table_wrapper_get_index_at;
  iface->get_column_at_index = table_wrapper_get_column_at_index;
  iface->get_row_at_index = table_wrapper_get_row_at_index;
  iface->is_row_selected = table_wrapper_is_row_selected;
  iface->is_selected = table_wrapper_is_selected;
  iface->get_selected_rows = table_wrapper_get_selected_rows;
  iface->add_row_selection = table_wrapper_add_row_selection;
  iface->remove_row_selection = table_wrapper_remove_row_selection;
  iface->add_column_selection = table_wrapper_add_column_selection;
  iface->remove_column_selection = table_wrapper_remove_column_selection;
  iface->get_selected_columns = table_wrapper_get_selected_columns;
  iface->is_column_selected = table_wrapper_is_column_selected;
  iface->get_column_extent_at = table_wrapper_get_column_extent_at;
  iface->get_row_extent_at = table_wrapper_get_row_extent_at;
  iface->get_row_header = table_wrapper_get_row_header;
  iface->set_row_header = table_wrapper_set_row_header;
  iface->get_column_header = table_wrapper_get_column_header;
  iface->set_column_header = table_wrapper_set_column_header;
  iface->get_caption = table_wrapper_get_caption;
  iface->set_caption = table_wrapper_set_caption;
  iface->get_summary = table_wrapper_get_summary;
  iface->set_summary = table_wrapper_set_summary;
  iface->get_row_description = table_wrapper_get_row_description;
  iface->set_row_description = table_wrapper_set_row_description;
  iface->get_column_description = table_wrapper_get_column_description;
  iface->set_column_description = table_wrapper_set_column_description;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atktext.cxx b/vcl/unx/gtk/a11y/atktext.cxx
deleted file mode 100644
index 1406cee..0000000
--- a/vcl/unx/gtk/a11y/atktext.cxx
+++ /dev/null
@@ -1,838 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"
#include "atktextattributes.hxx"
#include <algorithm>

#include <osl/diagnose.h>

#include <com/sun/star/accessibility/AccessibleTextType.hpp>
#include <com/sun/star/accessibility/TextSegment.hpp>
#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
#include <com/sun/star/text/TextMarkupType.hpp>

using namespace ::com::sun::star;

static sal_Int16
text_type_from_boundary(AtkTextBoundary boundary_type)
{
    switch(boundary_type)
    {
        case ATK_TEXT_BOUNDARY_CHAR:
            return accessibility::AccessibleTextType::CHARACTER;
        case ATK_TEXT_BOUNDARY_WORD_START:
        case ATK_TEXT_BOUNDARY_WORD_END:
            return accessibility::AccessibleTextType::WORD;
        case ATK_TEXT_BOUNDARY_SENTENCE_START:
        case ATK_TEXT_BOUNDARY_SENTENCE_END:
            return accessibility::AccessibleTextType::SENTENCE;
        case ATK_TEXT_BOUNDARY_LINE_START:
        case ATK_TEXT_BOUNDARY_LINE_END:
            return accessibility::AccessibleTextType::LINE;
        default:
            return -1;
    }
}

/*****************************************************************************/

static gchar *
adjust_boundaries( css::uno::Reference<css::accessibility::XAccessibleText> const & pText,
                   accessibility::TextSegment const & rTextSegment,
                   AtkTextBoundary  boundary_type,
                   gint * start_offset, gint * end_offset )
{
    accessibility::TextSegment aTextSegment;
    OUString aString;
    gint start = 0, end = 0;

    if( !rTextSegment.SegmentText.isEmpty() )
    {
        switch(boundary_type)
        {
        case ATK_TEXT_BOUNDARY_CHAR:
        case ATK_TEXT_BOUNDARY_LINE_START:
        case ATK_TEXT_BOUNDARY_LINE_END:
        case ATK_TEXT_BOUNDARY_SENTENCE_START:
            start = rTextSegment.SegmentStart;
            end = rTextSegment.SegmentEnd;
            aString = rTextSegment.SegmentText;
            break;

        // the OOo break iterator behaves as SENTENCE_START
        case ATK_TEXT_BOUNDARY_SENTENCE_END:
            start = rTextSegment.SegmentStart;
            end = rTextSegment.SegmentEnd;

            if( start > 0 )
                --start;
            if( end > 0 && end < pText->getCharacterCount() - 1 )
                --end;

            aString = pText->getTextRange(start, end);
            break;

        case ATK_TEXT_BOUNDARY_WORD_START:
            start = rTextSegment.SegmentStart;

            // Determine the start index of the next segment
            aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd,
                                                     text_type_from_boundary(boundary_type));
            if( !aTextSegment.SegmentText.isEmpty() )
                end = aTextSegment.SegmentStart;
            else
                end = pText->getCharacterCount();

            aString = pText->getTextRange(start, end);
            break;

        case ATK_TEXT_BOUNDARY_WORD_END:
            end = rTextSegment.SegmentEnd;

            // Determine the end index of the previous segment
            aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart,
                                                     text_type_from_boundary(boundary_type));
            if( !aTextSegment.SegmentText.isEmpty() )
                start = aTextSegment.SegmentEnd;
            else
                start = 0;

            aString = pText->getTextRange(start, end);
            break;

        default:
            return nullptr;
        }
    }

    *start_offset = start;
    *end_offset   = end;

    return OUStringToGChar(aString);
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleText>
    getText( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpText.is() )
        {
            pWrap->mpText.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpText;
    }

    return css::uno::Reference<css::accessibility::XAccessibleText>();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
    getTextMarkup( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpTextMarkup.is() )
        {
            pWrap->mpTextMarkup.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpTextMarkup;
    }

    return css::uno::Reference<css::accessibility::XAccessibleTextMarkup>();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
    getTextAttributes( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpTextAttributes.is() )
        {
            pWrap->mpTextAttributes.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpTextAttributes;
    }

    return css::uno::Reference<css::accessibility::XAccessibleTextAttributes>();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
    getMultiLineText( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpMultiLineText.is() )
        {
            pWrap->mpMultiLineText.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpMultiLineText;
    }

    return css::uno::Reference<css::accessibility::XAccessibleMultiLineText>();
}

/*****************************************************************************/

extern "C" {

static gchar *
text_wrapper_get_text (AtkText *text,
                       gint     start_offset,
                       gint     end_offset)
{
    gchar * ret = nullptr;

    g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), nullptr );

    /* at-spi expects the delete event to be send before the deletion happened
     * so we save the deleted string object in the UNO event notification and
     * fool libatk-bridge.so here ..
     */
    void * pData = g_object_get_data( G_OBJECT(text), "ooo::text_changed::delete" );
    if( pData != nullptr )
    {
        accessibility::TextSegment * pTextSegment =
            static_cast <accessibility::TextSegment *> (pData);

        if( pTextSegment->SegmentStart == start_offset &&
            pTextSegment->SegmentEnd == end_offset )
        {
            OString aUtf8 = OUStringToOString( pTextSegment->SegmentText, RTL_TEXTENCODING_UTF8 );
            return g_strdup( aUtf8.getStr() );
        }
    }

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            OUString aText;
            sal_Int32 n = pText->getCharacterCount();

            if( -1 == end_offset )
                aText = pText->getText();
            else if( start_offset < n )
                aText = pText->getTextRange(start_offset, end_offset);

            ret = g_strdup( OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getText()" );
    }

    return ret;
}

static gchar *
text_wrapper_get_text_after_offset (AtkText          *text,
                                    gint             offset,
                                    AtkTextBoundary  boundary_type,
                                    gint             *start_offset,
                                    gint             *end_offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type));
            return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in get_text_after_offset()" );
    }

    return nullptr;
}

static gchar *
text_wrapper_get_text_at_offset (AtkText          *text,
                                 gint             offset,
                                 AtkTextBoundary  boundary_type,
                                 gint             *start_offset,
                                 gint             *end_offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            /* If the user presses the 'End' key, the caret will be placed behind the last character,
             * which is the same index as the first character of the next line. In atk the magic offset
             * '-2' is used to cover this special case.
             */
            if (
                 -2 == offset &&
                     (ATK_TEXT_BOUNDARY_LINE_START == boundary_type ||
                      ATK_TEXT_BOUNDARY_LINE_END == boundary_type)
               )
            {
                css::uno::Reference<
                    css::accessibility::XAccessibleMultiLineText> pMultiLineText
                        = getMultiLineText( text );
                if( pMultiLineText.is() )
                {
                    accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret();
                    return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
                }
            }

            accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type));
            return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in get_text_at_offset()" );
    }

    return nullptr;
}

static gunichar
text_wrapper_get_character_at_offset (AtkText          *text,
                                      gint             offset)
{
    gint start, end;
    gunichar uc = 0;

    gchar * char_as_string =
        text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR,
                                        &start, &end);
    if( char_as_string )
    {
        uc = g_utf8_get_char( char_as_string );
        g_free( char_as_string );
    }

    return uc;
}

static gchar *
text_wrapper_get_text_before_offset (AtkText          *text,
                                     gint             offset,
                                     AtkTextBoundary  boundary_type,
                                     gint             *start_offset,
                                     gint             *end_offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type));
            return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in text_before_offset()" );
    }

    return nullptr;
}

static gint
text_wrapper_get_caret_offset (AtkText          *text)
{
    gint offset = -1;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            offset = pText->getCaretPosition();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCaretPosition()" );
    }

    return offset;
}

static gboolean
text_wrapper_set_caret_offset (AtkText *text,
                               gint     offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setCaretPosition( offset );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setCaretPosition()" );
    }

    return FALSE;
}

// #i92232#
static AtkAttributeSet*
handle_text_markup_as_run_attribute( css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup,
                                     const gint nTextMarkupType,
                                     const gint offset,
                                     AtkAttributeSet* pSet,
                                     gint *start_offset,
                                     gint *end_offset )
{
    const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) );
    if ( nTextMarkupCount > 0 )
    {
        for ( gint nTextMarkupIndex = 0;
              nTextMarkupIndex < nTextMarkupCount;
              ++nTextMarkupIndex )
        {
            accessibility::TextSegment aTextSegment =
                pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
            const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart;
            const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
            if ( nStartOffsetTextMarkup <= offset )
            {
                if ( offset < nEndOffsetTextMarkup )
                {
                    // text markup at <offset>
                    *start_offset = ::std::max( *start_offset,
                                                nStartOffsetTextMarkup );
                    *end_offset = ::std::min( *end_offset,
                                              nEndOffsetTextMarkup );
                    switch ( nTextMarkupType )
                    {
                        case css::text::TextMarkupType::SPELLCHECK:
                        {
                            pSet = attribute_set_prepend_misspelled( pSet );
                        }
                        break;
                        case css::text::TextMarkupType::TRACK_CHANGE_INSERTION:
                        {
                            pSet = attribute_set_prepend_tracked_change_insertion( pSet );
                        }
                        break;
                        case css::text::TextMarkupType::TRACK_CHANGE_DELETION:
                        {
                            pSet = attribute_set_prepend_tracked_change_deletion( pSet );
                        }
                        break;
                        case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
                        {
                            pSet = attribute_set_prepend_tracked_change_formatchange( pSet );
                        }
                        break;
                        default:
                        {
                            OSL_ASSERT( false );
                        }
                    }
                    break; // no further iteration needed.
                }
                else
                {
                    *start_offset = ::std::max( *start_offset,
                                                nEndOffsetTextMarkup );
                    // continue iteration.
                }
            }
            else
            {
                *end_offset = ::std::min( *end_offset,
                                          nStartOffsetTextMarkup );
                break; // no further iteration.
            }
        } // eof iteration over text markups
    }

    return pSet;
}

static AtkAttributeSet *
text_wrapper_get_run_attributes( AtkText        *text,
                                 gint           offset,
                                 gint           *start_offset,
                                 gint           *end_offset)
{
    AtkAttributeSet *pSet = nullptr;

    try {
        bool bOffsetsAreValid = false;

        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is())
        {
            uno::Sequence< beans::PropertyValue > aAttributeList;

            css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
                pTextAttributes = getTextAttributes( text );
            if(pTextAttributes.is()) // Text attributes are available for paragraphs only
            {
                aAttributeList = pTextAttributes->getRunAttributes( offset, uno::Sequence< OUString > () );
            }
            else // For other text objects use character attributes
            {
                aAttributeList = pText->getCharacterAttributes( offset, uno::Sequence< OUString > () );
            }

            pSet = attribute_set_new_from_property_values( aAttributeList, true, text );
            //  #i100938#
            // - always provide start_offset and end_offset
            {
                accessibility::TextSegment aTextSegment =
                    pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);

                *start_offset = aTextSegment.SegmentStart;
                // #i100938#
                // Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance
                *end_offset = aTextSegment.SegmentEnd;
                bOffsetsAreValid = true;
            }
        }

        // Special handling for misspelled text
        // #i92232#
        // - add special handling for tracked changes and refactor the
        //   corresponding code for handling misspelled text.
        css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
            pTextMarkup = getTextMarkup( text );
        if( pTextMarkup.is() )
        {
            // Get attribute run here if it hasn't been done before
            if (!bOffsetsAreValid && pText.is())
            {
                accessibility::TextSegment aAttributeTextSegment =
                    pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
                *start_offset = aAttributeTextSegment.SegmentStart;
                *end_offset = aAttributeTextSegment.SegmentEnd;
            }
            // handle misspelled text
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::SPELLCHECK,
                    offset, pSet, start_offset, end_offset );
            // handle tracked changes
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::TRACK_CHANGE_INSERTION,
                    offset, pSet, start_offset, end_offset );
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::TRACK_CHANGE_DELETION,
                    offset, pSet, start_offset, end_offset );
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE,
                    offset, pSet, start_offset, end_offset );
        }
    }
    catch(const uno::Exception&){

        g_warning( "Exception in get_run_attributes()" );

        if( pSet )
        {
            atk_attribute_set_free( pSet );
            pSet = nullptr;
        }
    }

    return pSet;
}

/*****************************************************************************/

static AtkAttributeSet *
text_wrapper_get_default_attributes( AtkText *text )
{
    AtkAttributeSet *pSet = nullptr;

    try {
        css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
            pTextAttributes = getTextAttributes( text );
        if( pTextAttributes.is() )
        {
            uno::Sequence< beans::PropertyValue > aAttributeList =
                pTextAttributes->getDefaultAttributes( uno::Sequence< OUString > () );

            pSet = attribute_set_new_from_property_values( aAttributeList, false, text );
        }
    }
    catch(const uno::Exception&) {

        g_warning( "Exception in get_default_attributes()" );

        if( pSet )
        {
            atk_attribute_set_free( pSet );
            pSet = nullptr;
        }
    }

    return pSet;
}

/*****************************************************************************/

static void
text_wrapper_get_character_extents( AtkText          *text,
                                    gint             offset,
                                    gint             *x,
                                    gint             *y,
                                    gint             *width,
                                    gint             *height,
                                    AtkCoordType      coords )
{
    *x = *y = *width = *height = -1;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            awt::Rectangle aRect = pText->getCharacterBounds( offset );

            gint origin_x = 0;
            gint origin_y = 0;

            if( coords == ATK_XY_SCREEN )
            {
                g_return_if_fail( ATK_IS_COMPONENT( text ) );
                SAL_WNODEPRECATED_DECLARATIONS_PUSH
                atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
                SAL_WNODEPRECATED_DECLARATIONS_POP
            }

            *x = aRect.X + origin_x;
            *y = aRect.Y + origin_y;
            *width = aRect.Width;
            *height = aRect.Height;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCharacterBounds" );
    }
}

static gint
text_wrapper_get_character_count (AtkText *text)
{
    gint rv = 0;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            rv = pText->getCharacterCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCharacterCount" );
    }

    return rv;
}

static gint
text_wrapper_get_offset_at_point (AtkText     *text,
                                  gint         x,
                                  gint         y,
                                  AtkCoordType coords)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            gint origin_x = 0;
            gint origin_y = 0;

            if( coords == ATK_XY_SCREEN )
            {
                g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
                SAL_WNODEPRECATED_DECLARATIONS_PUSH
                atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
                SAL_WNODEPRECATED_DECLARATIONS_POP
            }

            return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getIndexAtPoint" );
    }

    return -1;
}

// FIXME: the whole series of selections API is problematic ...

static gint
text_wrapper_get_n_selections (AtkText *text)
{
    gint rv = 0;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0;
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectionEnd() or getSelectionStart()" );
    }

    return rv;
}

static gchar *
text_wrapper_get_selection (AtkText *text,
                            gint     selection_num,
                            gint    *start_offset,
                            gint    *end_offset)
{
    g_return_val_if_fail( selection_num == 0, FALSE );

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            *start_offset = pText->getSelectionStart();
            *end_offset   = pText->getSelectionEnd();

            return OUStringToGChar( pText->getSelectedText() );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" );
    }

    return nullptr;
}

static gboolean
text_wrapper_add_selection (AtkText *text,
                            gint     start_offset,
                            gint     end_offset)
{
    // FIXME: can we try to be more compatible by expanding an
    //        existing adjacent selection ?

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setSelection( start_offset, end_offset ); // ?
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setSelection()" );
    }

    return FALSE;
}

static gboolean
text_wrapper_remove_selection (AtkText *text,
                               gint     selection_num)
{
    g_return_val_if_fail( selection_num == 0, FALSE );

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setSelection( 0, 0 ); // ?
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setSelection()" );
    }

    return FALSE;
}

static gboolean
text_wrapper_set_selection (AtkText *text,
                            gint     selection_num,
                            gint     start_offset,
                            gint     end_offset)
{
    g_return_val_if_fail( selection_num == 0, FALSE );

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setSelection( start_offset, end_offset );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setSelection()" );
    }

    return FALSE;
}

} // extern "C"

void
textIfaceInit (AtkTextIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->get_text = text_wrapper_get_text;
  iface->get_character_at_offset = text_wrapper_get_character_at_offset;
  iface->get_text_before_offset = text_wrapper_get_text_before_offset;
  iface->get_text_at_offset = text_wrapper_get_text_at_offset;
  iface->get_text_after_offset = text_wrapper_get_text_after_offset;
  iface->get_caret_offset = text_wrapper_get_caret_offset;
  iface->set_caret_offset = text_wrapper_set_caret_offset;
  iface->get_character_count = text_wrapper_get_character_count;
  iface->get_n_selections = text_wrapper_get_n_selections;
  iface->get_selection = text_wrapper_get_selection;
  iface->add_selection = text_wrapper_add_selection;
  iface->remove_selection = text_wrapper_remove_selection;
  iface->set_selection = text_wrapper_set_selection;
  iface->get_run_attributes = text_wrapper_get_run_attributes;
  iface->get_default_attributes = text_wrapper_get_default_attributes;
  iface->get_character_extents = text_wrapper_get_character_extents;
  iface->get_offset_at_point = text_wrapper_get_offset_at_point;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atktextattributes.cxx b/vcl/unx/gtk/a11y/atktextattributes.cxx
deleted file mode 100644
index 0ba7fd5..0000000
--- a/vcl/unx/gtk/a11y/atktextattributes.cxx
+++ /dev/null
@@ -1,1383 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atktextattributes.hxx"

#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontUnderline.hpp>

#include <com/sun/star/style/CaseMap.hpp>
#include <com/sun/star/style/LineSpacing.hpp>
#include <com/sun/star/style/LineSpacingMode.hpp>
#include <com/sun/star/style/ParagraphAdjust.hpp>
#include <com/sun/star/style/TabAlign.hpp>
#include <com/sun/star/style/TabStop.hpp>

#include <com/sun/star/text/WritingMode2.hpp>

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleComponent.hpp>

#include <i18nlangtag/languagetag.hxx>
#include <vcl/svapp.hxx>
#include <vcl/outdev.hxx>

#include <stdio.h>
#include <string.h>

using namespace ::com::sun::star;

typedef gchar* (* AtkTextAttrFunc)       ( const uno::Any& rAny );
typedef bool   (* TextPropertyValueFunc) ( uno::Any& rAny, const gchar * value );

#define STRNCMP_PARAM( s )  s,sizeof( s )-1

/*****************************************************************************/

static AtkTextAttribute atk_text_attribute_paragraph_style = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_font_effect = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_decoration = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_line_height = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_rotation = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_shadow = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_tab_interval = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_tab_stops = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_writing_mode = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_vertical_align = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_misspelled = ATK_TEXT_ATTR_INVALID;
// #i92232#
static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID;
// #i92233#
static AtkTextAttribute atk_text_attribute_mm_to_pixel_ratio = ATK_TEXT_ATTR_INVALID;

/*****************************************************************************/

/**
  * !! IMPORTANT NOTE !! : when adding items to this list, KEEP THE LIST SORTED
  *                        and re-arrange the enum values accordingly.
  */

enum ExportedAttribute
{
    TEXT_ATTRIBUTE_BACKGROUND_COLOR = 0,
    TEXT_ATTRIBUTE_CASEMAP,
    TEXT_ATTRIBUTE_FOREGROUND_COLOR,
    TEXT_ATTRIBUTE_CONTOURED,
    TEXT_ATTRIBUTE_CHAR_ESCAPEMENT,
    TEXT_ATTRIBUTE_BLINKING,
    TEXT_ATTRIBUTE_FONT_NAME,
    TEXT_ATTRIBUTE_HEIGHT,
    TEXT_ATTRIBUTE_HIDDEN,
    TEXT_ATTRIBUTE_KERNING,
    TEXT_ATTRIBUTE_LOCALE,
    TEXT_ATTRIBUTE_POSTURE,
    TEXT_ATTRIBUTE_RELIEF,
    TEXT_ATTRIBUTE_ROTATION,
    TEXT_ATTRIBUTE_SCALE,
    TEXT_ATTRIBUTE_SHADOWED,
    TEXT_ATTRIBUTE_STRIKETHROUGH,
    TEXT_ATTRIBUTE_UNDERLINE,
    TEXT_ATTRIBUTE_WEIGHT,
    // #i92233#
    TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO,
    TEXT_ATTRIBUTE_JUSTIFICATION,
    TEXT_ATTRIBUTE_BOTTOM_MARGIN,
    TEXT_ATTRIBUTE_FIRST_LINE_INDENT,
    TEXT_ATTRIBUTE_LEFT_MARGIN,
    TEXT_ATTRIBUTE_LINE_SPACING,
    TEXT_ATTRIBUTE_RIGHT_MARGIN,
    TEXT_ATTRIBUTE_STYLE_NAME,
    TEXT_ATTRIBUTE_TAB_STOPS,
    TEXT_ATTRIBUTE_TOP_MARGIN,
    TEXT_ATTRIBUTE_WRITING_MODE,
    TEXT_ATTRIBUTE_LAST
};

static const char * ExportedTextAttributes[TEXT_ATTRIBUTE_LAST] =
{
    "CharBackColor",        // TEXT_ATTRIBUTE_BACKGROUND_COLOR
    "CharCaseMap",          // TEXT_ATTRIBUTE_CASEMAP
    "CharColor",            // TEXT_ATTRIBUTE_FOREGROUND_COLOR
    "CharContoured",        // TEXT_ATTRIBUTE_CONTOURED
    "CharEscapement",       // TEXT_ATTRIBUTE_CHAR_ESCAPEMENT
    "CharFlash",            // TEXT_ATTRIBUTE_BLINKING
    "CharFontName",         // TEXT_ATTRIBUTE_FONT_NAME
    "CharHeight",           // TEXT_ATTRIBUTE_HEIGHT
    "CharHidden",           // TEXT_ATTRIBUTE_HIDDEN
    "CharKerning",          // TEXT_ATTRIBUTE_KERNING
    "CharLocale",           // TEXT_ATTRIBUTE_LOCALE
    "CharPosture",          // TEXT_ATTRIBUTE_POSTURE
    "CharRelief",           // TEXT_ATTRIBUTE_RELIEF
    "CharRotation",         // TEXT_ATTRIBUTE_ROTATION
    "CharScaleWidth",       // TEXT_ATTRIBUTE_SCALE
    "CharShadowed",         // TEXT_ATTRIBUTE_SHADOWED
    "CharStrikeout",        // TEXT_ATTRIBUTE_STRIKETHROUGH
    "CharUnderline",        // TEXT_ATTRIBUTE_UNDERLINE
    "CharWeight",           // TEXT_ATTRIBUTE_WEIGHT
    // #i92233#
    "MMToPixelRatio",       // TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO
    "ParaAdjust",           // TEXT_ATTRIBUTE_JUSTIFICATION
    "ParaBottomMargin",     // TEXT_ATTRIBUTE_BOTTOM_MARGIN
    "ParaFirstLineIndent",  // TEXT_ATTRIBUTE_FIRST_LINE_INDENT
    "ParaLeftMargin",       // TEXT_ATTRIBUTE_LEFT_MARGIN
    "ParaLineSpacing",      // TEXT_ATTRIBUTE_LINE_SPACING
    "ParaRightMargin",      // TEXT_ATTRIBUTE_RIGHT_MARGIN
    "ParaStyleName",        // TEXT_ATTRIBUTE_STYLE_NAME
    "ParaTabStops",         // TEXT_ATTRIBUTE_TAB_STOPS
    "ParaTopMargin",        // TEXT_ATTRIBUTE_TOP_MARGIN
    "WritingMode"           // TEXT_ATTRIBUTE_WRITING_MODE
};

/*****************************************************************************/

static gchar*
get_value( const uno::Sequence< beans::PropertyValue >& rAttributeList,
           sal_Int32 nIndex, AtkTextAttrFunc func )
{
    if( nIndex != -1 )
        return func(rAttributeList[nIndex].Value);

    return nullptr;
}

#define get_bool_value( list, index ) get_value( list, index, Bool2String )
#define get_height_value( list, index ) get_value( list, index, Float2String )
#define get_justification_value( list, index ) get_value( list, index, Adjust2Justification )
#define get_cmm_value( list, index ) get_value( list, index, CMM2UnitString )
#define get_scale_width( list, index ) get_value( list, index, Scale2String )
#define get_strikethrough_value( list, index ) get_value( list, index, Strikeout2String )
#define get_string_value( list, index ) get_value( list, index, GetString )
#define get_style_value( list, index ) get_value( list, index, FontSlant2Style )
#define get_underline_value( list, index ) get_value( list, index, Underline2String )
#define get_variant_value( list, index ) get_value( list, index, CaseMap2String )
#define get_weight_value( list, index ) get_value( list, index, Weight2String )
#define get_language_string( list, index ) get_value( list, index, Locale2String )

static double toPoint(sal_Int16 n)
{
    // 100th mm -> pt
    return static_cast<double>(n * 72) / 2540;
}

/*****************************************************************************/

static bool
InvalidValue( uno::Any&, const gchar * )
{
    return false;
}

/*****************************************************************************/

static gchar*
Float2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%g", rAny.get<float>() );
}

static bool
String2Float( uno::Any& rAny, const gchar * value )
{
    float fval;

    if( 1 != sscanf( value, "%g", &fval ) )
        return false;

    rAny <<= fval;
    return true;
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleComponent>
    getComponent( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpComponent.is() )
        {
            pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpComponent;
    }

    return css::uno::Reference<css::accessibility::XAccessibleComponent>();
}

static gchar*
get_color_value(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                const sal_Int32 * pIndexArray,
                ExportedAttribute attr,
                AtkText * text)
{
    sal_Int32 nColor = -1; // AUTOMATIC
    sal_Int32 nIndex = pIndexArray[attr];

    if( nIndex != -1 )
        nColor = rAttributeList[nIndex].Value.get<sal_Int32>();

    /*
     * Check for color value for 100% alpha white, which means
     * "automatic". Grab the RGB value from XAccessibleComponent
     * in this case.
     */

    if( (nColor == -1) && text )
    {
        try
        {
            css::uno::Reference<css::accessibility::XAccessibleComponent>
                pComponent = getComponent( text );
            if( pComponent.is() )
            {
                switch( attr )
                {
                    case TEXT_ATTRIBUTE_BACKGROUND_COLOR:
                        nColor = pComponent->getBackground();
                        break;
                    case TEXT_ATTRIBUTE_FOREGROUND_COLOR:
                        nColor = pComponent->getForeground();
                        break;
                    default:
                        break;
                }
            }
        }

        catch(const uno::Exception&) {
            g_warning( "Exception in get[Fore|Back]groundColor()" );
        }
    }

    if( nColor != -1 )
    {
        sal_uInt8 blue  = nColor & 0xFF;
        sal_uInt8 green = (nColor >> 8) & 0xFF;
        sal_uInt8 red   = (nColor >> 16) & 0xFF;

        return g_strdup_printf( "%u,%u,%u", red, green, blue );
    }

    return nullptr;
}

static bool
String2Color( uno::Any& rAny, const gchar * value )
{
    int red, green, blue;

    if( 3 != sscanf( value, "%d,%d,%d", &red, &green, &blue ) )
        return false;

    sal_Int32 nColor = static_cast<sal_Int32>(blue) | ( static_cast<sal_Int32>(green) << 8 ) | ( static_cast<sal_Int32>(red) << 16 );
    rAny <<= nColor;
    return true;
}

/*****************************************************************************/

static gchar*
FontSlant2Style(const uno::Any& rAny)
{
    const gchar * value = nullptr;

    awt::FontSlant aFontSlant;
    if(!(rAny >>= aFontSlant))
        return nullptr;

    switch( aFontSlant )
    {
        case awt::FontSlant_NONE:
            value = "normal";
            break;

        case awt::FontSlant_OBLIQUE:
            value = "oblique";
            break;

        case awt::FontSlant_ITALIC:
            value = "italic";
            break;

        case awt::FontSlant_REVERSE_OBLIQUE:
            value = "reverse oblique";
            break;

        case awt::FontSlant_REVERSE_ITALIC:
            value = "reverse italic";
            break;

        default:
            break;
    }

    if( value )
         return g_strdup( value );

    return nullptr;
}

static bool
Style2FontSlant( uno::Any& rAny, const gchar * value )
{
    awt::FontSlant aFontSlant;

    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
        aFontSlant = awt::FontSlant_NONE;
    else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) == 0 )
        aFontSlant = awt::FontSlant_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) == 0 )
        aFontSlant = awt::FontSlant_ITALIC;
    else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) == 0 )
        aFontSlant = awt::FontSlant_REVERSE_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) == 0 )
        aFontSlant = awt::FontSlant_REVERSE_ITALIC;
    else
        return false;

    rAny <<= aFontSlant;
    return true;
}

/*****************************************************************************/

static gchar*
Weight2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%g", rAny.get<float>() * 4 );
}

static bool
String2Weight( uno::Any& rAny, const gchar * value )
{
    float weight;

    if( 1 != sscanf( value, "%g", &weight ) )
        return false;

    rAny <<= weight / 4;
    return true;
}

/*****************************************************************************/

static gchar*
Adjust2Justification(const uno::Any& rAny)
{
    const gchar * value = nullptr;

    switch( static_cast<style::ParagraphAdjust>(rAny.get<short>()) )
    {
        case style::ParagraphAdjust_LEFT:
            value = "left";
            break;

        case style::ParagraphAdjust_RIGHT:
            value = "right";
            break;

        case style::ParagraphAdjust_BLOCK:
        case style::ParagraphAdjust_STRETCH:
            value = "fill";
            break;

        case style::ParagraphAdjust_CENTER:
            value = "center";
            break;

        default:
            break;
    }

    if( value )
        return g_strdup( value );

    return nullptr;
}

static bool
Justification2Adjust( uno::Any& rAny, const gchar * value )
{
    style::ParagraphAdjust nParagraphAdjust;

    if( strncmp( value, STRNCMP_PARAM( "left" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_LEFT;
    else if( strncmp( value, STRNCMP_PARAM( "right" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_RIGHT;
    else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_BLOCK;
    else if( strncmp( value, STRNCMP_PARAM( "center" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_CENTER;
    else
        return false;

    rAny <<= static_cast<short>(nParagraphAdjust);
    return true;
}

/*****************************************************************************/

const gchar * const font_strikethrough[] = {
    "none",   // FontStrikeout::NONE
    "single", // FontStrikeout::SINGLE
    "double", // FontStrikeout::DOUBLE
    nullptr,     // FontStrikeout::DONTKNOW
    "bold",   // FontStrikeout::BOLD
    "with /", // FontStrikeout::SLASH
    "with X"  // FontStrikeout::X
};

static gchar*
Strikeout2String(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( n >= 0 && n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)) )
        return g_strdup( font_strikethrough[n] );

    return nullptr;
}

static bool
String2Strikeout( uno::Any& rAny, const gchar * value )
{
    for( sal_Int16 n=0; n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)); ++n )
    {
        if( ( nullptr != font_strikethrough[n] ) &&
            0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) )
        {
            rAny <<= n;
            return true;
        }
    }

    return false;
}

/*****************************************************************************/

static gchar*
Underline2String(const uno::Any& rAny)
{
    const gchar * value = nullptr;

    switch( rAny.get<sal_Int16>() )
    {
        case awt::FontUnderline::NONE:
            value = "none";
            break;

        case awt::FontUnderline::SINGLE:
            value = "single";
            break;

        case awt::FontUnderline::DOUBLE:
            value = "double";
            break;

        default:
            break;
    }

    if( value )
        return g_strdup( value );

    return nullptr;
}

static bool
String2Underline( uno::Any& rAny, const gchar * value )
{
    short nUnderline;

    if( strncmp( value, STRNCMP_PARAM( "none" ) ) == 0 )
        nUnderline = awt::FontUnderline::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "single" ) ) == 0 )
        nUnderline = awt::FontUnderline::SINGLE;
    else if( strncmp( value, STRNCMP_PARAM( "double" ) ) == 0 )
        nUnderline = awt::FontUnderline::DOUBLE;
    else
        return false;

    rAny <<= nUnderline;
    return true;
}

/*****************************************************************************/

static gchar*
GetString(const uno::Any& rAny)
{
    OString aFontName = OUStringToOString( rAny.get< OUString > (), RTL_TEXTENCODING_UTF8 );

    if( !aFontName.isEmpty() )
        return g_strdup( aFontName.getStr() );

    return nullptr;
}

static bool
SetString( uno::Any& rAny, const gchar * value )
{
    OString aFontName( value );

    if( !aFontName.isEmpty() )
    {
        rAny <<= OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 );
        return true;
    }

    return false;
}

/*****************************************************************************/

// @see http://developer.gnome.org/doc/API/2.0/atk/AtkText.html#AtkTextAttribute

// CMM = 100th of mm
static gchar*
CMM2UnitString(const uno::Any& rAny)
{
    double fValue = rAny.get<sal_Int32>();
    fValue = fValue * 0.01;

    return g_strdup_printf( "%gmm", fValue );
}

static bool
UnitString2CMM( uno::Any& rAny, const gchar * value )
{
    float fValue = 0.0; // pb: don't use double here because of warning on linux

    if( 1 != sscanf( value, "%gmm", &fValue ) )
        return false;

    fValue = fValue * 100;

    rAny <<= static_cast<sal_Int32>(fValue);
    return true;
}

/*****************************************************************************/

static const gchar * bool_values[] = { "true", "false" };

static gchar *
Bool2String( const uno::Any& rAny )
{
    int n = 1;

    if( rAny.get<bool>() )
        n = 0;

    return g_strdup( bool_values[n] );
}

static bool
String2Bool( uno::Any& rAny, const gchar * value )
{
    bool bValue;

    if( strncmp( value, STRNCMP_PARAM( "true" ) ) == 0 )
        bValue = true;
    else if( strncmp( value, STRNCMP_PARAM( "false" ) ) == 0 )
        bValue = false;
    else
        return false;

    rAny <<= bValue;
    return true;
}

/*****************************************************************************/

static gchar*
Scale2String( const uno::Any& rAny )
{
    return g_strdup_printf( "%g", static_cast<double>(rAny.get< sal_Int16 > ()) / 100 );
}

static bool
String2Scale( uno::Any& rAny, const gchar * value )
{
    double dval;

    if( 1 != sscanf( value, "%lg", &dval ) )
        return false;

    rAny <<= static_cast<sal_Int16>(dval * 100);
    return true;
}

/*****************************************************************************/

static gchar *
CaseMap2String( const uno::Any& rAny )
{
    const gchar * value;

    switch( rAny.get<short>() )
    {
        case style::CaseMap::SMALLCAPS:
            value = "small_caps";
            break;

        default:
            value = "normal";
            break;
    }

    return g_strdup(value);
}

static bool
String2CaseMap( uno::Any& rAny, const gchar * value )
{
    short nCaseMap;

    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
        nCaseMap = style::CaseMap::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) == 0 )
        nCaseMap = style::CaseMap::SMALLCAPS;
    else
        return false;

    rAny <<= nCaseMap;
    return true;
}

/*****************************************************************************/

const gchar * const font_stretch[] = {
    "ultra_condensed",
    "extra_condensed",
    "condensed",
    "semi_condensed",
    "normal",
    "semi_expanded",
    "expanded",
    "extra_expanded",
    "ultra_expanded"
};

static gchar*
Kerning2Stretch(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();
    int i = 4;

    // No good idea for a mapping - just return the basic info
    if( n < 0 )
        i=2;
    else if( n > 0 )
        i=6;

    return g_strdup(font_stretch[i]);
}

/*****************************************************************************/

static gchar*
Locale2String(const uno::Any& rAny)
{
    /* FIXME-BCP47: support language tags? And why is country lowercase? */
    lang::Locale aLocale = rAny.get<lang::Locale> ();
    LanguageTag aLanguageTag( aLocale);
    return g_strdup_printf( "%s-%s",
        OUStringToOString( aLanguageTag.getLanguage(), RTL_TEXTENCODING_ASCII_US).getStr(),
        OUStringToOString( aLanguageTag.getCountry(), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() );
}

static bool
String2Locale( uno::Any& rAny, const gchar * value )
{
    /* FIXME-BCP47: support language tags? */
    bool ret = false;

    gchar ** str_array = g_strsplit_set( value, "-.@", -1 );
    if( str_array[0] != nullptr )
    {
        ret = true;

        lang::Locale aLocale;

        aLocale.Language = OUString::createFromAscii(str_array[0]);
        if( str_array[1] != nullptr )
        {
            gchar * country = g_ascii_strup(str_array[1], -1);
            aLocale.Country = OUString::createFromAscii(country);
            g_free(country);
        }

        rAny <<= aLocale;
    }

    g_strfreev(str_array);
    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-effect-prop
static const gchar * relief[] = { "none", "emboss", "engrave" };
static const gchar * const outline  = "outline";

static gchar *
get_font_effect(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                sal_Int32 nContourIndex, sal_Int32 nReliefIndex)
{
    if( nContourIndex != -1 )
    {
        if( rAttributeList[nContourIndex].Value.get<bool>() )
            return g_strdup(outline);
    }

    if( nReliefIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nReliefIndex].Value.get<sal_Int16>();
        if( n <  3)
            return g_strdup(relief[n]);
    }

    return nullptr;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/text.html#lining-striking-props

enum
{
    DECORATION_NONE = 0,
    DECORATION_BLINK,
    DECORATION_UNDERLINE,
    DECORATION_LINE_THROUGH
};

static const gchar * decorations[] = { "none", "blink", "underline", "line-through" };

static gchar *
get_text_decoration(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                    sal_Int32 nBlinkIndex, sal_Int32 nUnderlineIndex,
                    sal_Int16 nStrikeoutIndex)
{
    gchar * value_list[4] = { nullptr, nullptr, nullptr, nullptr };
    gint count = 0;

    // no property value found
    if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1))
        return nullptr;

    if( nBlinkIndex != -1 )
    {
        if( rAttributeList[nBlinkIndex].Value.get<bool>() )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_BLINK]);
    }
    if( nUnderlineIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get<sal_Int16> ();
        if( n != awt::FontUnderline::NONE )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_UNDERLINE]);
    }
    if( nStrikeoutIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get<sal_Int16> ();
        if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_LINE_THROUGH]);
    }

    if( count == 0 )
        value_list[count++] = const_cast <gchar *> (decorations[DECORATION_NONE]);

    return g_strjoinv(" ", value_list);
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/text.html#propdef-text-shadow

static const gchar * shadow_values[] = { "none", "black" };

static gchar *
Bool2Shadow( const uno::Any& rAny )
{
    int n = 0;

    if( rAny.get<bool>() )
        n = 1;

    return g_strdup( shadow_values[n] );
}

/*****************************************************************************/

static gchar *
Short2Degree( const uno::Any& rAny )
{
    float f = rAny.get<sal_Int16>() / 10.0;
    return g_strdup_printf( "%g", f );
}

/*****************************************************************************/

const gchar * const directions[] = { "ltr", "rtl", "rtl", "ltr", "none" };

static gchar *
WritingMode2Direction( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( 0 <= n && n <= text::WritingMode2::PAGE )
        return g_strdup(directions[n]);

    return nullptr;
}

// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection

const gchar * const writing_modes[] = { "lr-tb", "rl-tb", "tb-rl", "tb-lr", "none" };
static gchar *
WritingMode2String( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( 0 <= n && n <= text::WritingMode2::PAGE )
        return g_strdup(writing_modes[n]);

    return nullptr;
}

/*****************************************************************************/

const char * const baseline_values[] = { "baseline", "sub", "super" };

// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-vertical-align
static gchar *
Escapement2VerticalAlign( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();
    gchar * ret = nullptr;

    // Values are in %, 101% means "automatic"
    if( n == 0 )
        ret = g_strdup(baseline_values[0]);
    else if( n == 101 )
        ret = g_strdup(baseline_values[2]);
    else if( n == -101 )
        ret = g_strdup(baseline_values[1]);
    else
        ret = g_strdup_printf( "%d%%", n );

    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-line-height
static gchar *
LineSpacing2LineHeight( const uno::Any& rAny )
{
    style::LineSpacing ls;
    gchar * ret = nullptr;

    if( rAny >>= ls )
    {
        if( ls.Mode == style::LineSpacingMode::PROP )
            ret = g_strdup_printf( "%d%%", ls.Height );
        else if( ls.Mode == style::LineSpacingMode::FIX )
            ret = g_strdup_printf( "%.3gpt", toPoint(ls.Height) );
    }

    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/People/howcome/t/970224HTMLERB-CSS/WD-tabs-970117.html
static gchar *
TabStopList2String( const uno::Any& rAny, bool default_tabs )
{
    uno::Sequence< style::TabStop > theTabStops;
    gchar * ret = nullptr;

    if( rAny >>= theTabStops)
    {
        sal_Unicode lastFillChar = ' ';

        for( const auto& rTabStop : std::as_const(theTabStops) )
        {
            bool is_default_tab = (style::TabAlign_DEFAULT == rTabStop.Alignment);

            if( is_default_tab != default_tabs )
                continue;

            double fValue = rTabStop.Position;
            fValue = fValue * 0.01;

            const gchar * tab_align = "";
            switch( rTabStop.Alignment )
            {
                case style::TabAlign_LEFT :
                    tab_align = "left ";
                    break;
                case style::TabAlign_CENTER :
                    tab_align = "center ";
                    break;
                case style::TabAlign_RIGHT :
                    tab_align = "right ";
                    break;
                case style::TabAlign_DECIMAL :
                    tab_align = "decimal ";
                    break;
                default:
                    break;
            }

            const gchar * lead_char = "";

            if( rTabStop.FillChar != lastFillChar )
            {
                lastFillChar = rTabStop.FillChar;
                switch (lastFillChar)
                {
                    case ' ':
                        lead_char = "blank ";
                        break;

                    case '.':
                        lead_char = "dotted ";
                        break;

                    case '-':
                        lead_char = "dashed ";
                        break;

                    case '_':
                        lead_char = "lined ";
                        break;

                    default:
                        lead_char = "custom ";
                        break;
                }
            }

            gchar * tab_str = g_strdup_printf( "%s%s%gmm", lead_char, tab_align, fValue );

            if( ret )
            {
                gchar * old_tab_str = ret;
                ret = g_strconcat(old_tab_str, " ", tab_str, nullptr);
                g_free( old_tab_str );
            }
            else
                ret = tab_str;
        }
    }

    return ret;
}

static gchar *
TabStops2String( const uno::Any& rAny )
{
    return TabStopList2String(rAny, false);
}

static gchar *
DefaultTabStops2String( const uno::Any& rAny )
{
    return TabStopList2String(rAny, true);
}

/*****************************************************************************/

extern "C" {

static int
attr_compare(const void *p1,const void *p2)
{
    const rtl_uString * pustr = static_cast<const rtl_uString *>(p1);
    const char * pc = *static_cast<const char * const *>(p2);

    return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc);
}

}

static void
find_exported_attributes( sal_Int32 *pArray,
    const css::uno::Sequence< css::beans::PropertyValue >& rAttributeList )
{
    for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ )
    {
        const char ** pAttr = static_cast<const char **>(bsearch(rAttributeList[i].Name.pData,
            ExportedTextAttributes, TEXT_ATTRIBUTE_LAST, sizeof(const char *),
            attr_compare));

        if( pAttr )
        {
            sal_Int32 nIndex = pAttr - ExportedTextAttributes;
            pArray[nIndex] = i;
        }
    }
}

/*****************************************************************************/

static AtkAttributeSet*
attribute_set_prepend( AtkAttributeSet* attribute_set,
                       AtkTextAttribute attribute,
                       gchar * value )
{
    if( value )
    {
        AtkAttribute *at = static_cast<AtkAttribute *>(g_malloc( sizeof (AtkAttribute) ));
        at->name  = g_strdup( atk_text_attribute_get_name( attribute ) );
        at->value = value;

        return g_slist_prepend(attribute_set, at);
    }

    return attribute_set;
}

/*****************************************************************************/

AtkAttributeSet*
attribute_set_new_from_property_values(
    const uno::Sequence< beans::PropertyValue >& rAttributeList,
    bool run_attributes_only,
    AtkText *text)
{
    AtkAttributeSet* attribute_set = nullptr;

    sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 };

    // Initialize index array with -1
    for(sal_Int32 & rn : aIndexList)
        rn = -1;

    find_exported_attributes(aIndexList, rAttributeList);

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_BG_COLOR,
        get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_BACKGROUND_COLOR, run_attributes_only ? nullptr : text ) );

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FG_COLOR,
        get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_FOREGROUND_COLOR, run_attributes_only ? nullptr : text) );

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INVISIBLE,
        get_bool_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HIDDEN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_UNDERLINE,
        get_underline_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_UNDERLINE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRIKETHROUGH,
        get_strikethrough_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SIZE,
        get_height_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HEIGHT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_WEIGHT,
        get_weight_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WEIGHT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FAMILY_NAME,
        get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FONT_NAME]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_VARIANT,
        get_variant_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CASEMAP]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STYLE,
        get_style_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_POSTURE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SCALE,
        get_scale_width(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SCALE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LANGUAGE,
        get_language_string(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LOCALE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_DIRECTION,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2Direction));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRETCH,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_KERNING], Kerning2Stretch));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_font_effect )
        atk_text_attribute_font_effect = atk_text_attribute_register("font-effect");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_font_effect,
        get_font_effect(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CONTOURED], aIndexList[TEXT_ATTRIBUTE_RELIEF]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_decoration )
        atk_text_attribute_decoration = atk_text_attribute_register("text-decoration");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_decoration,
        get_text_decoration(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BLINKING],
            aIndexList[TEXT_ATTRIBUTE_UNDERLINE], aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_rotation )
        atk_text_attribute_rotation = atk_text_attribute_register("text-rotation");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_rotation,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_ROTATION], Short2Degree));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_shadow )
        atk_text_attribute_shadow = atk_text_attribute_register("text-shadow");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_shadow,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SHADOWED], Bool2Shadow));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_writing_mode )
        atk_text_attribute_writing_mode = atk_text_attribute_register("writing-mode");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_writing_mode,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2String));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_vertical_align )
        atk_text_attribute_vertical_align = atk_text_attribute_register("vertical-align");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_vertical_align,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CHAR_ESCAPEMENT], Escapement2VerticalAlign));

    if( run_attributes_only )
        return attribute_set;

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LEFT_MARGIN,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LEFT_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_RIGHT_MARGIN,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_RIGHT_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INDENT,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FIRST_LINE_INDENT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TOP_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BOTTOM_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_JUSTIFICATION,
        get_justification_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_JUSTIFICATION]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_paragraph_style )
        atk_text_attribute_paragraph_style = atk_text_attribute_register("paragraph-style");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_paragraph_style,
        get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STYLE_NAME]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_line_height )
        atk_text_attribute_line_height = atk_text_attribute_register("line-height");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_line_height,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LINE_SPACING], LineSpacing2LineHeight));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_interval )
        atk_text_attribute_tab_interval = atk_text_attribute_register("tab-interval");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_interval,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], DefaultTabStops2String));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_stops )
        atk_text_attribute_tab_stops = atk_text_attribute_register("tab-stops");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_stops,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], TabStops2String));

    // #i92233#
    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_mm_to_pixel_ratio )
        atk_text_attribute_mm_to_pixel_ratio = atk_text_attribute_register("mm-to-pixel-ratio");

    attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_mm_to_pixel_ratio,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO], Float2String));

    return attribute_set;
}

AtkAttributeSet*
attribute_set_new_from_extended_attributes(
    const css::uno::Reference< css::accessibility::XAccessibleExtendedAttributes >& rExtendedAttributes )
{
    AtkAttributeSet *pSet = nullptr;

    // extended attributes is a string of colon-separated pairs of property and value,
    // with pairs separated by semicolons. Example: "heading-level:2;weight:bold;"
    uno::Any anyVal = rExtendedAttributes->getExtendedAttributes();
    OUString sExtendedAttrs;
    anyVal >>= sExtendedAttrs;
    sal_Int32 nIndex = 0;
    do
    {
        OUString sProperty = sExtendedAttrs.getToken( 0, ';', nIndex );

        sal_Int32 nColonPos = 0;
        OString sPropertyName = OUStringToOString( sProperty.getToken( 0, ':', nColonPos ),
                                                   RTL_TEXTENCODING_UTF8 );
        OString sPropertyValue = OUStringToOString( sProperty.getToken( 0, ':', nColonPos ),
                                                    RTL_TEXTENCODING_UTF8 );

        pSet = attribute_set_prepend( pSet,
                                      atk_text_attribute_register( sPropertyName.getStr() ),
                                      g_strdup_printf( "%s", sPropertyValue.getStr() ) );
    }
    while ( nIndex >= 0 && nIndex < sExtendedAttrs.getLength() );

    return pSet;
}

AtkAttributeSet* attribute_set_prepend_misspelled( AtkAttributeSet* attribute_set )
{
    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_misspelled )
        atk_text_attribute_misspelled = atk_text_attribute_register( "text-spelling" );

    attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_misspelled,
        g_strdup_printf( "misspelled" ) );

    return attribute_set;
}

// #i92232#
AtkAttributeSet* attribute_set_prepend_tracked_change_insertion( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "insertion" ) );

    return attribute_set;
}

AtkAttributeSet* attribute_set_prepend_tracked_change_deletion( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "deletion" ) );

    return attribute_set;
}

AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "attribute-change" ) );

    return attribute_set;
}

/*****************************************************************************/

struct AtkTextAttrMapping
{
    const char *          name;
    TextPropertyValueFunc const toPropertyValue;
};

const AtkTextAttrMapping g_TextAttrMap[] =
{
    { "", InvalidValue },                       // ATK_TEXT_ATTR_INVALID = 0
    { "ParaLeftMargin", UnitString2CMM },       // ATK_TEXT_ATTR_LEFT_MARGIN
    { "ParaRightMargin", UnitString2CMM },      // ATK_TEXT_ATTR_RIGHT_MARGIN
    { "ParaFirstLineIndent", UnitString2CMM },  // ATK_TEXT_ATTR_INDENT
    { "CharHidden", String2Bool },              // ATK_TEXT_ATTR_INVISIBLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_EDITABLE
    { "ParaTopMargin", UnitString2CMM },        // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES
    { "ParaBottomMargin", UnitString2CMM },     // ATK_TEXT_ATTR_PIXELS_BELOW_LINES
    { "", InvalidValue },                       // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP
    { "", InvalidValue },                       // ATK_TEXT_ATTR_BG_FULL_HEIGHT
    { "", InvalidValue },                       // ATK_TEXT_ATTR_RISE
    { "CharUnderline", String2Underline },      // ATK_TEXT_ATTR_UNDERLINE
    { "CharStrikeout", String2Strikeout },      // ATK_TEXT_ATTR_STRIKETHROUGH
    { "CharHeight", String2Float },             // ATK_TEXT_ATTR_SIZE
    { "CharScaleWidth", String2Scale },         // ATK_TEXT_ATTR_SCALE
    { "CharWeight", String2Weight },            // ATK_TEXT_ATTR_WEIGHT
    { "CharLocale", String2Locale },            // ATK_TEXT_ATTR_LANGUAGE
    { "CharFontName",  SetString },             // ATK_TEXT_ATTR_FAMILY_NAME
    { "CharBackColor", String2Color },          // ATK_TEXT_ATTR_BG_COLOR
    { "CharColor", String2Color },              // ATK_TEXT_ATTR_FG_COLOR
    { "", InvalidValue },                       // ATK_TEXT_ATTR_BG_STIPPLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_FG_STIPPLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_WRAP_MODE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_DIRECTION
    { "ParaAdjust", Justification2Adjust },     // ATK_TEXT_ATTR_JUSTIFICATION
    { "", InvalidValue },                       // ATK_TEXT_ATTR_STRETCH
    { "CharCaseMap", String2CaseMap },          // ATK_TEXT_ATTR_VARIANT
    { "CharPosture", Style2FontSlant }          // ATK_TEXT_ATTR_STYLE
};

/*****************************************************************************/

bool
attribute_set_map_to_property_values(
    AtkAttributeSet* attribute_set,
    uno::Sequence< beans::PropertyValue >& rValueList )
{
    // Ensure enough space ..
    uno::Sequence< beans::PropertyValue > aAttributeList (SAL_N_ELEMENTS(g_TextAttrMap));

    sal_Int32 nIndex = 0;
    for( GSList * item = attribute_set; item != nullptr; item = g_slist_next( item ) )
    {
        AtkAttribute* attribute = reinterpret_cast<AtkAttribute *>(item);

        AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name );
        if( text_attr < SAL_N_ELEMENTS(g_TextAttrMap) )
        {
            if( g_TextAttrMap[text_attr].name[0] != '\0' )
            {
                if( ! g_TextAttrMap[text_attr].toPropertyValue( aAttributeList[nIndex].Value, attribute->value) )
                    return false;

                aAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name );
                aAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE;
                ++nIndex;
            }
        }
        else
        {
            // Unsupported text attribute
            return false;
        }
    }

    aAttributeList.realloc( nIndex );
    rValueList = aAttributeList;
    return true;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkutil.cxx b/vcl/unx/gtk/a11y/atkutil.cxx
deleted file mode 100644
index 50ed279..0000000
--- a/vcl/unx/gtk/a11y/atkutil.cxx
+++ /dev/null
@@ -1,789 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#ifdef AIX
#define _LINUX_SOURCE_COMPAT
#include <sys/timer.h>
#undef _LINUX_SOURCE_COMPAT
#endif

#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/weakref.hxx>
#include <rtl/ref.hxx>
#include <sal/log.hxx>

#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/menu.hxx>
#include <vcl/toolbox.hxx>

#include <unx/gtk/gtkdata.hxx>
#include "atkwrapper.hxx"
#include "atkutil.hxx"

#include <gtk/gtk.h>
#include <config_version.h>

#include <cassert>
#include <set>

using namespace ::com::sun::star;

namespace
{
    struct theNextFocusObject :
        public rtl::Static< uno::WeakReference< accessibility::XAccessible >, theNextFocusObject>
    {
    };
}

static guint focus_notify_handler = 0;

/*****************************************************************************/

extern "C" {

static gboolean
atk_wrapper_focus_idle_handler (gpointer data)
{
    SolarMutexGuard aGuard;

    focus_notify_handler = 0;

    uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject::get();
    if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) )
    {
        AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
        // Gail does not notify focus changes to NULL, so do we ..
        if( atk_obj )
        {
            SAL_WNODEPRECATED_DECLARATIONS_PUSH
            atk_focus_tracker_notify(atk_obj);
            SAL_WNODEPRECATED_DECLARATIONS_POP
            // #i93269#
            // emit text_caret_moved event for <XAccessibleText> object,
            // if cursor is inside the <XAccessibleText> object.
            // also emit state-changed:focused event under the same condition.
            {
                AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj);
                if( wrapper_obj && !wrapper_obj->mpText.is() )
                {
                    wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY);
                    if ( wrapper_obj->mpText.is() )
                    {
                        gint caretPos = -1;

                        try {
                            caretPos = wrapper_obj->mpText->getCaretPosition();
                        }
                        catch(const uno::Exception&) {
                            g_warning( "Exception in getCaretPosition()" );
                        }

                        if ( caretPos != -1 )
                        {
                            atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, TRUE );
                            g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos );
                        }
                    }
                }
            }
            g_object_unref(atk_obj);
        }
    }

    return false;
}

} // extern "C"

/*****************************************************************************/

static void
atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible )
{
    if( focus_notify_handler )
        g_source_remove(focus_notify_handler);

    theNextFocusObject::get() = xAccessible;

    focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
}

/*****************************************************************************/

class DocumentFocusListener :
    public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
{

    std::set< uno::Reference< uno::XInterface > > m_aRefList;

public:
    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void attachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void attachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible,
        const uno::Reference< accessibility::XAccessibleContext >& xContext
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void attachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible,
        const uno::Reference< accessibility::XAccessibleContext >& xContext,
        const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void detachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void detachRecursive(
        const uno::Reference< accessibility::XAccessibleContext >& xContext
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void detachRecursive(
        const uno::Reference< accessibility::XAccessibleContext >& xContext,
        const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );

    // XEventListener
    virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;

    // XAccessibleEventListener
    virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override;
};

/*****************************************************************************/

void DocumentFocusListener::disposing( const lang::EventObject& aEvent )
{

    // Unref the object here, but do not remove as listener since the object
    // might no longer be in a state that safely allows this.
    if( aEvent.Source.is() )
        m_aRefList.erase(aEvent.Source);

}

/*****************************************************************************/

void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
{
    try {
        switch( aEvent.EventId )
        {
            case accessibility::AccessibleEventId::STATE_CHANGED:
            {
                sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
                aEvent.NewValue >>= nState;

                if( accessibility::AccessibleStateType::FOCUSED == nState )
                    atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) );

                break;
            }

            case accessibility::AccessibleEventId::CHILD:
            {
                uno::Reference< accessibility::XAccessible > xChild;
                if( (aEvent.OldValue >>= xChild) && xChild.is() )
                    detachRecursive(xChild);

                if( (aEvent.NewValue >>= xChild) && xChild.is() )
                    attachRecursive(xChild);

                break;
            }

            case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
                SAL_INFO("vcl.a11y", "Invalidate all children called");
                break;

            default:
                break;
        }
    }
    catch( const lang::IndexOutOfBoundsException& e )
    {
        g_warning("Focused object has invalid index in parent");
    }
}

/*****************************************************************************/

uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
{
    uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);

    if( xAccessible.is() )
        return xAccessible;

    uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);

    if( xContext.is() )
    {
        uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
        if( xParent.is() )
        {
            uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
            if( xParentContext.is() )
            {
                return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
            }
        }
    }

    return uno::Reference< accessibility::XAccessible >();
}

/*****************************************************************************/

void DocumentFocusListener::attachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible
)
{
    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( xContext.is() )
        attachRecursive(xAccessible, xContext);
}

/*****************************************************************************/

void DocumentFocusListener::attachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible,
    const uno::Reference< accessibility::XAccessibleContext >& xContext
)
{
    uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
        xContext->getAccessibleStateSet();

    if( xStateSet.is() )
        attachRecursive(xAccessible, xContext, xStateSet);
}

/*****************************************************************************/

void DocumentFocusListener::attachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible,
    const uno::Reference< accessibility::XAccessibleContext >& xContext,
    const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
)
{
    if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED ) )
        atk_wrapper_focus_tracker_notify_when_idle( xAccessible );

    uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);

    if (!xBroadcaster.is())
        return;

    // If not already done, add the broadcaster to the list and attach as listener.
    const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
    if( m_aRefList.insert(xInterface).second )
    {
        xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));

        if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
        {
            sal_Int32 n, nmax = xContext->getAccessibleChildCount();
            for( n = 0; n < nmax; n++ )
            {
                uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );

                if( xChild.is() )
                    attachRecursive(xChild);
            }
        }
    }
}

/*****************************************************************************/

void DocumentFocusListener::detachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible
)
{
    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( xContext.is() )
        detachRecursive(xContext);
}

/*****************************************************************************/

void DocumentFocusListener::detachRecursive(
    const uno::Reference< accessibility::XAccessibleContext >& xContext
)
{
    uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
        xContext->getAccessibleStateSet();

    if( xStateSet.is() )
        detachRecursive(xContext, xStateSet);
}

/*****************************************************************************/

void DocumentFocusListener::detachRecursive(
    const uno::Reference< accessibility::XAccessibleContext >& xContext,
    const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
)
{
    uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);

    if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
    {
        xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));

        if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
        {
            sal_Int32 n, nmax = xContext->getAccessibleChildCount();
            for( n = 0; n < nmax; n++ )
            {
                uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );

                if( xChild.is() )
                    detachRecursive(xChild);
            }
        }
    }
}

/*****************************************************************************/

/*
 * page tabs in gtk are widgets, so we need to simulate focus events for those
 */

static void handle_tabpage_activated(vcl::Window *pWindow)
{
    uno::Reference< accessibility::XAccessible > xAccessible =
        pWindow->GetAccessible();

    if( ! xAccessible.is() )
        return;

    uno::Reference< accessibility::XAccessibleSelection > xSelection(
        xAccessible->getAccessibleContext(), uno::UNO_QUERY);

    if( xSelection.is() )
        atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) );
}

/*****************************************************************************/

/*
 * toolbar items in gtk are widgets, so we need to simulate focus events for those
 */

static void notify_toolbox_item_focus(ToolBox *pToolBox)
{
    uno::Reference< accessibility::XAccessible > xAccessible =
        pToolBox->GetAccessible();

    if( ! xAccessible.is() )
        return;

    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( ! xContext.is() )
        return;

    ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
    if( nPos != ToolBox::ITEM_NOTFOUND )
        atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
            //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32
}

static void handle_toolbox_highlight(vcl::Window *pWindow)
{
    ToolBox *pToolBox = static_cast <ToolBox *> (pWindow);

    // Make sure either the toolbox or its parent toolbox has the focus
    if ( ! pToolBox->HasFocus() )
    {
        ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() );
        if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
            return;
    }

    notify_toolbox_item_focus(pToolBox);
}

static void handle_toolbox_highlightoff(vcl::Window const *pWindow)
{
    ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() );

    // Notify when leaving sub toolboxes
    if( pToolBoxParent && pToolBoxParent->HasFocus() )
        notify_toolbox_item_focus( pToolBoxParent );
}

/*****************************************************************************/

static void create_wrapper_for_child(
    const uno::Reference< accessibility::XAccessibleContext >& xContext,
    sal_Int32 index)
{
    if( xContext.is() )
    {
        uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index));
        if( xChild.is() )
        {
            // create the wrapper object - it will survive the unref unless it is a transient object
            g_object_unref( atk_object_wrapper_ref( xChild ) );
        }
    }
}

/*****************************************************************************/

static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent)
{
    vcl::Window* pWindow = pEvent->GetWindow();
    sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData()));

    if( pWindow && pWindow->IsReallyVisible() )
    {
        uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible());
        if( xAccessible.is() )
        {
            create_wrapper_for_child(xAccessible->getAccessibleContext(), index);
        }
    }
}

/*****************************************************************************/

namespace {

struct WindowList {
    ~WindowList() { assert(list.empty()); };
        // needs to be empty already on DeInitVCL, but at least check it's empty
        // on exit

    std::set< VclPtr<vcl::Window> > list;
};

WindowList g_aWindowList;

}

DocumentFocusListener & GtkSalData::GetDocumentFocusListener()
{
    if (!m_pDocumentFocusListener)
    {
        m_pDocumentFocusListener = new DocumentFocusListener;
        m_xDocumentFocusListener.set(m_pDocumentFocusListener);
    }
    return *m_pDocumentFocusListener;
}

static void handle_get_focus(::VclWindowEvent const * pEvent)
{
    GtkSalData *const pSalData(GetGtkSalData());
    assert(pSalData);

    DocumentFocusListener & rDocumentFocusListener(pSalData->GetDocumentFocusListener());

    vcl::Window *pWindow = pEvent->GetWindow();

    // The menu bar is handled through VclEventId::MenuHighlightED
    if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
        return;

    // ToolBoxes are handled through VclEventId::ToolboxHighlight
    if( pWindow->GetType() == WindowType::TOOLBOX )
        return;

    if( pWindow->GetType() == WindowType::TABCONTROL )
    {
        handle_tabpage_activated( pWindow );
        return;
    }

    uno::Reference< accessibility::XAccessible > xAccessible =
        pWindow->GetAccessible();

    if( ! xAccessible.is() )
        return;

    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( ! xContext.is() )
        return;

    uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
        xContext->getAccessibleStateSet();

    if( ! xStateSet.is() )
        return;

/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
 * need to add listeners to the children instead of re-using the tabpage stuff
 */
    if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED) &&
        ( pWindow->GetType() != WindowType::TREELISTBOX ) )
    {
        atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
    }
    else
    {
        if( g_aWindowList.list.insert(pWindow).second )
        {
            try
            {
                rDocumentFocusListener.attachRecursive(xAccessible, xContext, xStateSet);
            }
            catch (const uno::Exception&)
            {
                g_warning( "Exception caught processing focus events" );
            }
        }
    }
}

/*****************************************************************************/

static void handle_menu_highlighted(::VclMenuEvent const * pEvent)
{
    try
    {
        Menu* pMenu = pEvent->GetMenu();
        sal_uInt16 nPos = pEvent->GetItemPos();

        if( pMenu &&  nPos != 0xFFFF)
        {
            uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() );

            if( xAccessible.is() )
            {
                uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() );

                if( xContext.is() )
                    atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
            }
        }
    }
    catch (const uno::Exception&)
    {
        g_warning( "Exception caught processing menu highlight events" );
    }
}

/*****************************************************************************/

static void WindowEventHandler(void *, VclSimpleEvent& rEvent)
{
    try
    {
        switch (rEvent.GetId())
        {
        case VclEventId::WindowShow:
            break;
        case VclEventId::WindowHide:
            break;
        case VclEventId::WindowClose:
            break;
        case VclEventId::WindowGetFocus:
            handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent));
            break;
        case VclEventId::WindowLoseFocus:
            break;
        case VclEventId::WindowMinimize:
            break;
        case VclEventId::WindowNormalize:
            break;
        case VclEventId::WindowKeyInput:
        case VclEventId::WindowKeyUp:
        case VclEventId::WindowCommand:
        case VclEventId::WindowMouseMove:
            break;

        case VclEventId::MenuHighlight:
            if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent))
            {
                handle_menu_highlighted(pMenuEvent);
            }
            else if (const VclAccessibleEvent* pAccEvent = dynamic_cast<const VclAccessibleEvent*>(&rEvent))
            {
                const uno::Reference< accessibility::XAccessible >& xAccessible = pAccEvent->GetAccessible();
                if (xAccessible.is())
                    atk_wrapper_focus_tracker_notify_when_idle(xAccessible);
            }
            break;

        case VclEventId::ToolboxHighlight:
            handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
            break;

        case VclEventId::ToolboxButtonStateChanged:
            handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent));
            break;

        case VclEventId::ObjectDying:
            g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() );
            [[fallthrough]];
        case VclEventId::ToolboxHighlightOff:
            handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
            break;

        case VclEventId::TabpageActivate:
            handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
            break;

        case VclEventId::ComboboxSetText:
            // This looks quite strange to me. Stumbled over this when fixing #i104290#.
            // This kicked in when leaving the combobox in the toolbar, after that the events worked.
            // I guess this was a try to work around missing combobox events, which didn't do the full job, and shouldn't be necessary anymore.
            // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general.
            // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow());
            break;

        default:
            break;
        }
    }
    catch (const lang::IndexOutOfBoundsException&)
    {
        g_warning("Focused object has invalid index in parent");
    }
}

static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler );

/*****************************************************************************/

extern "C" {

static const gchar *
ooo_atk_util_get_toolkit_name()
{
    return "VCL";
}

/*****************************************************************************/

static const gchar *
ooo_atk_util_get_toolkit_version()
{
    return LIBO_VERSION_DOTTED;
}

/*****************************************************************************/

/*
 * GObject inheritance
 */

static void
ooo_atk_util_class_init (AtkUtilClass *)
{
    AtkUtilClass *atk_class;
    gpointer data;

    data = g_type_class_peek (ATK_TYPE_UTIL);
    atk_class = ATK_UTIL_CLASS (data);

    atk_class->get_toolkit_name = ooo_atk_util_get_toolkit_name;
    atk_class->get_toolkit_version = ooo_atk_util_get_toolkit_version;

    ooo_atk_util_ensure_event_listener();
}

} // extern "C"

void ooo_atk_util_ensure_event_listener()
{
    static bool bInited;
    if (!bInited)
    {
        Application::AddEventListener( g_aEventListenerLink );
        bInited = true;
    }
}

GType
ooo_atk_util_get_type()
{
    static GType type = 0;

    if (!type)
    {
        GType parent_type = g_type_from_name( "GailUtil" );

        if( ! parent_type )
        {
            g_warning( "Unknown type: GailUtil" );
            parent_type = ATK_TYPE_UTIL;
        }

        GTypeQuery type_query;
        g_type_query( parent_type, &type_query );

        static const GTypeInfo typeInfo =
        {
            static_cast<guint16>(type_query.class_size),
            nullptr,
            nullptr,
            reinterpret_cast<GClassInitFunc>(ooo_atk_util_class_init),
            nullptr,
            nullptr,
            static_cast<guint16>(type_query.instance_size),
            0,
            nullptr,
            nullptr
        } ;

        type = g_type_register_static (parent_type, "OOoUtil", &typeInfo, GTypeFlags(0)) ;
    }

    return type;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkvalue.cxx b/vcl/unx/gtk/a11y/atkvalue.cxx
deleted file mode 100644
index f5e45d3..0000000
--- a/vcl/unx/gtk/a11y/atkvalue.cxx
+++ /dev/null
@@ -1,138 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleValue.hpp>

#include <string.h>

using namespace ::com::sun::star;

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleValue>
    getValue( AtkValue *pValue )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pValue );
    if( pWrap )
    {
        if( !pWrap->mpValue.is() )
        {
            pWrap->mpValue.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpValue;
    }

    return css::uno::Reference<css::accessibility::XAccessibleValue>();
}

static void anyToGValue( const uno::Any& aAny, GValue *pValue )
{
    // FIXME: expand to lots of types etc.
    double aDouble=0;
    aAny >>= aDouble;

    memset( pValue,  0, sizeof( GValue ) );
    g_value_init( pValue, G_TYPE_DOUBLE );
    g_value_set_double( pValue, aDouble );
}

extern "C" {

static void
value_wrapper_get_current_value( AtkValue *value,
                                 GValue   *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
            anyToGValue( pValue->getCurrentValue(), gval );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }
}

static void
value_wrapper_get_maximum_value( AtkValue *value,
                                 GValue   *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
            anyToGValue( pValue->getMaximumValue(), gval );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }
}

static void
value_wrapper_get_minimum_value( AtkValue *value,
                                 GValue   *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
            anyToGValue( pValue->getMinimumValue(), gval );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }
}

static gboolean
value_wrapper_set_current_value( AtkValue     *value,
                                 const GValue *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
        {
            // FIXME - this needs expanding
            double aDouble = g_value_get_double( gval );
            return pValue->setCurrentValue( uno::Any(aDouble) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }

    return FALSE;
}

} // extern "C"

void
valueIfaceInit (AtkValueIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->get_current_value = value_wrapper_get_current_value;
  iface->get_maximum_value = value_wrapper_get_maximum_value;
  iface->get_minimum_value = value_wrapper_get_minimum_value;
  iface->set_current_value = value_wrapper_set_current_value;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkwindow.cxx b/vcl/unx/gtk/a11y/atkwindow.cxx
deleted file mode 100644
index eb72edf..0000000
--- a/vcl/unx/gtk/a11y/atkwindow.cxx
+++ /dev/null
@@ -1,330 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <unx/gtk/gtkframe.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/popupmenuwindow.hxx>
#include <sal/log.hxx>

#include "atkwindow.hxx"
#include "atkwrapper.hxx"
#include "atkregistry.hxx"

#include <com/sun/star/accessibility/AccessibleRole.hpp>

using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;

extern "C" {

static void (* window_real_initialize) (AtkObject *obj, gpointer data) = nullptr;
static void (* window_real_finalize) (GObject *obj) = nullptr;

static void
init_from_window( AtkObject *accessible, vcl::Window const *pWindow )
{
    static AtkRole aDefaultRole = ATK_ROLE_INVALID;

    // Special role for sub-menu and combo-box popups that are exposed directly
    // by their parents already.
    if( aDefaultRole == ATK_ROLE_INVALID )
    {
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
        aDefaultRole = atk_role_register( "redundant object" );
        SAL_WNODEPRECATED_DECLARATIONS_POP
    }

    AtkRole role = aDefaultRole;

    // Determine the appropriate role for the GtkWindow
    switch( pWindow->GetAccessibleRole() )
    {
        case AccessibleRole::ALERT:
            role = ATK_ROLE_ALERT;
            break;

        case AccessibleRole::DIALOG:
            role = ATK_ROLE_DIALOG;
            break;

        case AccessibleRole::FRAME:
            role = ATK_ROLE_FRAME;
            break;

        /* Ignore window objects for sub-menus, combo- and list boxes,
         *  which are exposed as children of their parents.
         */
        case AccessibleRole::WINDOW:
        {
            WindowType type = WindowType::WINDOW;
            bool parentIsMenuFloatingWindow = false;

            vcl::Window *pParent = pWindow->GetParent();
            if( pParent ) {
                type = pParent->GetType();
                parentIsMenuFloatingWindow = pParent->IsMenuFloatingWindow();
            }

            if( (WindowType::LISTBOX != type) && (WindowType::COMBOBOX != type) &&
                (WindowType::MENUBARWINDOW != type) && ! parentIsMenuFloatingWindow )
            {
                role = ATK_ROLE_WINDOW;
            }
        }
        break;

        default:
        {
            vcl::Window *pChild = pWindow->GetWindow(GetWindowType::FirstChild);
            if( pChild )
            {
                if( WindowType::HELPTEXTWINDOW == pChild->GetType() )
                {
                    role = ATK_ROLE_TOOL_TIP;
                    pChild->SetAccessibleRole( AccessibleRole::LABEL );
                    accessible->name = g_strdup( OUStringToOString( pChild->GetText(), RTL_TEXTENCODING_UTF8 ).getStr() );
                }
                else if ( pWindow->GetType() == WindowType::BORDERWINDOW && pChild->GetType() == WindowType::FLOATINGWINDOW )
                {
                    PopupMenuFloatingWindow* p = dynamic_cast<PopupMenuFloatingWindow*>(pChild);
                    if (p && p->IsPopupMenu() && p->GetMenuStackLevel() == 0)
                    {
                        // This is a top-level menu popup.  Register it.
                        role = ATK_ROLE_POPUP_MENU;
                        pChild->SetAccessibleRole( AccessibleRole::POPUP_MENU );
                        accessible->name = g_strdup( OUStringToOString( pChild->GetText(), RTL_TEXTENCODING_UTF8 ).getStr() );
                    }
                }
            }
            break;
        }
    }

    accessible->role = role;
}

/*****************************************************************************/

static gboolean
ooo_window_wrapper_clear_focus(gpointer)
{
    SolarMutexGuard aGuard;
    SAL_WNODEPRECATED_DECLARATIONS_PUSH
    atk_focus_tracker_notify( nullptr );
    SAL_WNODEPRECATED_DECLARATIONS_POP
    return false;
}

/*****************************************************************************/

static gboolean
ooo_window_wrapper_real_focus_gtk (GtkWidget *, GdkEventFocus *)
{
    g_idle_add( ooo_window_wrapper_clear_focus, nullptr );
    return false;
}

static gboolean ooo_tooltip_map( GtkWidget* pToolTip, gpointer )
{
    AtkObject* pAccessible = gtk_widget_get_accessible( pToolTip );
    if( pAccessible )
        atk_object_notify_state_change( pAccessible, ATK_STATE_SHOWING, TRUE );
    return FALSE;
}

static gboolean ooo_tooltip_unmap( GtkWidget* pToolTip, gpointer )
{
    AtkObject* pAccessible = gtk_widget_get_accessible( pToolTip );
    if( pAccessible )
        atk_object_notify_state_change( pAccessible, ATK_STATE_SHOWING, FALSE );
    return FALSE;
}

/*****************************************************************************/

static bool
isChildPopupMenu(vcl::Window* pWindow)
{
    vcl::Window* pChild = pWindow->GetAccessibleChildWindow(0);
    if (!pChild)
        return false;

    if (WindowType::FLOATINGWINDOW != pChild->GetType())
        return false;

    PopupMenuFloatingWindow* p = dynamic_cast<PopupMenuFloatingWindow*>(pChild);
    if (!p)
        return false;

    return p->IsPopupMenu();
}

static void
ooo_window_wrapper_real_initialize(AtkObject *obj, gpointer data)
{
    window_real_initialize(obj, data);

    GtkSalFrame *pFrame = GtkSalFrame::getFromWindow( GTK_WINDOW( data ) );
    if( pFrame )
    {
        vcl::Window *pWindow = pFrame->GetWindow();
        if( pWindow )
        {
            init_from_window( obj, pWindow );

            Reference< XAccessible > xAccessible( pWindow->GetAccessible() );

            /* We need the wrapper object for the top-level XAccessible to be
             * in the wrapper registry when atk traverses the hierarchy up on
             * focus events
             */
            if( WindowType::BORDERWINDOW == pWindow->GetType() )
            {
                if ( isChildPopupMenu(pWindow) )
                {
                    AtkObject *child = atk_object_wrapper_new( xAccessible, obj );
                    ooo_wrapper_registry_add( xAccessible, child );
                }
                else
                {
                    ooo_wrapper_registry_add( xAccessible, obj );
                    g_object_set_data( G_OBJECT(obj), "ooo:atk-wrapper-key", xAccessible.get() );
                }
            }
            else
            {
                AtkObject *child = atk_object_wrapper_new( xAccessible, obj );
                child->role = ATK_ROLE_FILLER;
                if( (ATK_ROLE_DIALOG == obj->role) || (ATK_ROLE_ALERT == obj->role) )
                    child->role = ATK_ROLE_OPTION_PANE;
                ooo_wrapper_registry_add( xAccessible, child );
            }
        }
    }

    g_signal_connect_after( GTK_WIDGET( data ), "focus-out-event",
                            G_CALLBACK (ooo_window_wrapper_real_focus_gtk),
                            nullptr);

    if( obj->role == ATK_ROLE_TOOL_TIP )
    {
        g_signal_connect_after( GTK_WIDGET( data ), "map-event",
                                G_CALLBACK (ooo_tooltip_map),
                                nullptr);
        g_signal_connect_after( GTK_WIDGET( data ), "unmap-event",
                                G_CALLBACK (ooo_tooltip_unmap),
                                nullptr);
    }
}

/*****************************************************************************/

static void
ooo_window_wrapper_real_finalize (GObject *obj)
{
    ooo_wrapper_registry_remove( static_cast<XAccessible *>(g_object_get_data( obj, "ooo:atk-wrapper-key" )));
    window_real_finalize( obj );
}

/*****************************************************************************/

static void
ooo_window_wrapper_class_init (AtkObjectClass *klass, gpointer)
{
    AtkObjectClass *atk_class;
    GObjectClass *gobject_class;
    gpointer data;

    /*
     * Patch the gobject vtable of GailWindow to refer to our instance of
     * "initialize".
     */

    data = g_type_class_peek_parent( klass );
    atk_class = ATK_OBJECT_CLASS (data);

    window_real_initialize = atk_class->initialize;
    atk_class->initialize = ooo_window_wrapper_real_initialize;

    gobject_class = G_OBJECT_CLASS (data);

    window_real_finalize = gobject_class->finalize;
    gobject_class->finalize = ooo_window_wrapper_real_finalize;
}

} // extern "C"

/*****************************************************************************/

GType
ooo_window_wrapper_get_type()
{
    static GType type = 0;

    if (!type)
    {
        GType parent_type = g_type_from_name( "GailWindow" );

        if( ! parent_type )
        {
            SAL_INFO("vcl.a11y", "Unknown type: GailWindow");
            parent_type = ATK_TYPE_OBJECT;
        }

        GTypeQuery type_query;
        g_type_query( parent_type, &type_query );

        static const GTypeInfo typeInfo =
        {
            static_cast<guint16>(type_query.class_size),
            nullptr,
            nullptr,
            reinterpret_cast<GClassInitFunc>(ooo_window_wrapper_class_init),
            nullptr,
            nullptr,
            static_cast<guint16>(type_query.instance_size),
            0,
            nullptr,
            nullptr
        } ;

        type = g_type_register_static (parent_type, "OOoWindowAtkObject", &typeInfo, GTypeFlags(0)) ;
    }

    return type;
}

void restore_gail_window_vtable()
{
    AtkObjectClass *atk_class;
    gpointer data;

    GType type = g_type_from_name( "GailWindow" );

    if( type == G_TYPE_INVALID )
        return;

    data = g_type_class_peek( type );
    atk_class = ATK_OBJECT_CLASS (data);

    atk_class->initialize = window_real_initialize;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/a11y/atkwrapper.cxx b/vcl/unx/gtk/a11y/atkwrapper.cxx
deleted file mode 100644
index cd43902..0000000
--- a/vcl/unx/gtk/a11y/atkwrapper.cxx
+++ /dev/null
@@ -1,959 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Type.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/accessibility/AccessibleRelation.hpp>
#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleValue.hpp>
#include <com/sun/star/accessibility/XAccessibleAction.hpp>
#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <com/sun/star/accessibility/XAccessibleContext2.hpp>
#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
#include <com/sun/star/accessibility/XAccessibleStateSet.hpp>
#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
#include <com/sun/star/accessibility/XAccessibleTable.hpp>
#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleImage.hpp>
#include <com/sun/star/accessibility/XAccessibleHyperlink.hpp>
#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>
#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
#include <com/sun/star/awt/XExtendedToolkit.hpp>
#include <com/sun/star/awt/XTopWindow.hpp>
#include <com/sun/star/awt/XTopWindowListener.hpp>
#include <com/sun/star/awt/XWindow.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/beans/Property.hpp>

#include <rtl/ref.hxx>
#include <rtl/strbuf.hxx>
#include <osl/diagnose.h>
#include <cppuhelper/factory.hxx>
#include <cppuhelper/queryinterface.hxx>

#include "atkwrapper.hxx"
#include "atkregistry.hxx"
#include "atklistener.hxx"
#include "atktextattributes.hxx"

#include <string.h>
#include <vector>

using namespace ::com::sun::star;

static GObjectClass *parent_class = nullptr;

static AtkRelationType mapRelationType( sal_Int16 nRelation )
{
    AtkRelationType type = ATK_RELATION_NULL;

    switch( nRelation )
    {
        case accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM:
            type = ATK_RELATION_FLOWS_FROM;
            break;

        case accessibility::AccessibleRelationType::CONTENT_FLOWS_TO:
            type = ATK_RELATION_FLOWS_TO;
            break;

        case accessibility::AccessibleRelationType::CONTROLLED_BY:
            type = ATK_RELATION_CONTROLLED_BY;
            break;

        case accessibility::AccessibleRelationType::CONTROLLER_FOR:
            type = ATK_RELATION_CONTROLLER_FOR;
            break;

        case accessibility::AccessibleRelationType::LABEL_FOR:
            type = ATK_RELATION_LABEL_FOR;
            break;

        case accessibility::AccessibleRelationType::LABELED_BY:
            type = ATK_RELATION_LABELLED_BY;
            break;

        case accessibility::AccessibleRelationType::MEMBER_OF:
            type = ATK_RELATION_MEMBER_OF;
            break;

        case accessibility::AccessibleRelationType::SUB_WINDOW_OF:
            type = ATK_RELATION_SUBWINDOW_OF;
            break;

        case accessibility::AccessibleRelationType::NODE_CHILD_OF:
            type = ATK_RELATION_NODE_CHILD_OF;
            break;

        default:
            break;
    }

    return type;
}

AtkStateType mapAtkState( sal_Int16 nState )
{
    AtkStateType type = ATK_STATE_INVALID;

    // A perfect / complete mapping ...
    switch( nState )
    {
#define MAP_DIRECT( a ) \
        case accessibility::AccessibleStateType::a: \
            type = ATK_STATE_##a; break

        MAP_DIRECT( INVALID );
        MAP_DIRECT( ACTIVE );
        MAP_DIRECT( ARMED );
        MAP_DIRECT( BUSY );
        MAP_DIRECT( CHECKED );
        MAP_DIRECT( EDITABLE );
        MAP_DIRECT( ENABLED );
        MAP_DIRECT( EXPANDABLE );
        MAP_DIRECT( EXPANDED );
        MAP_DIRECT( FOCUSABLE );
        MAP_DIRECT( FOCUSED );
        MAP_DIRECT( HORIZONTAL );
        MAP_DIRECT( ICONIFIED );
        MAP_DIRECT( INDETERMINATE );
        MAP_DIRECT( MANAGES_DESCENDANTS );
        MAP_DIRECT( MODAL );
        MAP_DIRECT( MULTI_LINE );
        MAP_DIRECT( OPAQUE );
        MAP_DIRECT( PRESSED );
        MAP_DIRECT( RESIZABLE );
        MAP_DIRECT( SELECTABLE );
        MAP_DIRECT( SELECTED );
        MAP_DIRECT( SENSITIVE );
        MAP_DIRECT( SHOWING );
        MAP_DIRECT( SINGLE_LINE );
        MAP_DIRECT( STALE );
        MAP_DIRECT( TRANSIENT );
        MAP_DIRECT( VERTICAL );
        MAP_DIRECT( VISIBLE );
        MAP_DIRECT( DEFAULT );
        // a spelling error ...
        case accessibility::AccessibleStateType::DEFUNC:
            type = ATK_STATE_DEFUNCT; break;
        case accessibility::AccessibleStateType::MULTI_SELECTABLE:
            type = ATK_STATE_MULTISELECTABLE; break;
    default:
        //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped
        //NOTE! Do not report it
        type = ATK_STATE_LAST_DEFINED;
        break;
    }

    return type;
}

static AtkRole getRoleForName( const gchar * name )
{
    AtkRole ret = atk_role_for_name( name );
    if( ATK_ROLE_INVALID == ret )
    {
        // this should only happen in old ATK versions
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
        ret = atk_role_register( name );
        SAL_WNODEPRECATED_DECLARATIONS_POP
    }

    return ret;
}

static AtkRole mapToAtkRole( sal_Int16 nRole )
{
    AtkRole role = ATK_ROLE_UNKNOWN;

    static AtkRole roleMap[] = {
        ATK_ROLE_UNKNOWN,
        ATK_ROLE_ALERT,
        ATK_ROLE_COLUMN_HEADER,
        ATK_ROLE_CANVAS,
        ATK_ROLE_CHECK_BOX,
        ATK_ROLE_CHECK_MENU_ITEM,
        ATK_ROLE_COLOR_CHOOSER,
        ATK_ROLE_COMBO_BOX,
        ATK_ROLE_DATE_EDITOR,
        ATK_ROLE_DESKTOP_ICON,
        ATK_ROLE_DESKTOP_FRAME,   // ? pane
        ATK_ROLE_DIRECTORY_PANE,
        ATK_ROLE_DIALOG,
        ATK_ROLE_UNKNOWN,         // DOCUMENT - registered below
        ATK_ROLE_UNKNOWN,         // EMBEDDED_OBJECT - registered below
        ATK_ROLE_UNKNOWN,         // END_NOTE - registered below
        ATK_ROLE_FILE_CHOOSER,
        ATK_ROLE_FILLER,
        ATK_ROLE_FONT_CHOOSER,
        ATK_ROLE_FOOTER,
        ATK_ROLE_UNKNOWN,         // FOOTNOTE - registered below
        ATK_ROLE_FRAME,
        ATK_ROLE_GLASS_PANE,
        ATK_ROLE_IMAGE,           // GRAPHIC
        ATK_ROLE_UNKNOWN,         // GROUP_BOX - registered below
        ATK_ROLE_HEADER,
        ATK_ROLE_HEADING,
        ATK_ROLE_TEXT,            // HYPER_LINK - registered below
        ATK_ROLE_ICON,
        ATK_ROLE_INTERNAL_FRAME,
        ATK_ROLE_LABEL,
        ATK_ROLE_LAYERED_PANE,
        ATK_ROLE_LIST,
        ATK_ROLE_LIST_ITEM,
        ATK_ROLE_MENU,
        ATK_ROLE_MENU_BAR,
        ATK_ROLE_MENU_ITEM,
        ATK_ROLE_OPTION_PANE,
        ATK_ROLE_PAGE_TAB,
        ATK_ROLE_PAGE_TAB_LIST,
        ATK_ROLE_PANEL,
        ATK_ROLE_PARAGRAPH,
        ATK_ROLE_PASSWORD_TEXT,
        ATK_ROLE_POPUP_MENU,
        ATK_ROLE_PUSH_BUTTON,
        ATK_ROLE_PROGRESS_BAR,
        ATK_ROLE_RADIO_BUTTON,
        ATK_ROLE_RADIO_MENU_ITEM,
        ATK_ROLE_ROW_HEADER,
        ATK_ROLE_ROOT_PANE,
        ATK_ROLE_SCROLL_BAR,
        ATK_ROLE_SCROLL_PANE,
        ATK_ROLE_PANEL,         // SHAPE
        ATK_ROLE_SEPARATOR,
        ATK_ROLE_SLIDER,
        ATK_ROLE_SPIN_BUTTON,   // SPIN_BOX ?
        ATK_ROLE_SPLIT_PANE,
        ATK_ROLE_STATUSBAR,
        ATK_ROLE_TABLE,
        ATK_ROLE_TABLE_CELL,
        ATK_ROLE_TEXT,
        ATK_ROLE_PANEL,         // TEXT_FRAME
        ATK_ROLE_TOGGLE_BUTTON,
        ATK_ROLE_TOOL_BAR,
        ATK_ROLE_TOOL_TIP,
        ATK_ROLE_TREE,
        ATK_ROLE_VIEWPORT,
        ATK_ROLE_WINDOW,
        ATK_ROLE_PUSH_BUTTON,   // BUTTON_DROPDOWN
        ATK_ROLE_PUSH_BUTTON,   // BUTTON_MENU
        ATK_ROLE_UNKNOWN,       // CAPTION - registered below
        ATK_ROLE_UNKNOWN,       // CHART - registered below
        ATK_ROLE_UNKNOWN,       // EDIT_BAR - registered below
        ATK_ROLE_UNKNOWN,       // FORM - registered below
        ATK_ROLE_UNKNOWN,       // IMAGE_MAP - registered below
        ATK_ROLE_UNKNOWN,       // NOTE - registered below
        ATK_ROLE_UNKNOWN,       // PAGE - registered below
        ATK_ROLE_RULER,
        ATK_ROLE_UNKNOWN,       // SECTION - registered below
        ATK_ROLE_UNKNOWN,       // TREE_ITEM - registered below
        ATK_ROLE_TREE_TABLE,
        ATK_ROLE_SCROLL_PANE,   // COMMENT - mapped to atk_role_scroll_pane
        ATK_ROLE_UNKNOWN        // COMMENT_END - mapped to atk_role_unknown
#if defined(ATK_CHECK_VERSION)
        //older ver that doesn't define ATK_CHECK_VERSION doesn't have the following
        , ATK_ROLE_DOCUMENT_PRESENTATION
        , ATK_ROLE_DOCUMENT_SPREADSHEET
        , ATK_ROLE_DOCUMENT_TEXT
#if ATK_CHECK_VERSION(2,15,2)
        , ATK_ROLE_STATIC
#else
        , ATK_ROLE_LABEL
#endif
#else
        //older version should fallback to DOCUMENT_FRAME role
        , ATK_ROLE_DOCUMENT_FRAME
        , ATK_ROLE_DOCUMENT_FRAME
        , ATK_ROLE_DOCUMENT_FRAME
        , ATK_ROLE_LABEL
#endif
    };

    static bool initialized = false;

    if( ! initialized )
    {
        // the accessible roles below were added to ATK in later versions,
        // with role_for_name we will know if they exist in runtime.
        roleMap[accessibility::AccessibleRole::EDIT_BAR] = getRoleForName("edit bar");
        roleMap[accessibility::AccessibleRole::EMBEDDED_OBJECT] = getRoleForName("embedded");
        roleMap[accessibility::AccessibleRole::CHART] = getRoleForName("chart");
        roleMap[accessibility::AccessibleRole::CAPTION] = getRoleForName("caption");
        roleMap[accessibility::AccessibleRole::DOCUMENT] = getRoleForName("document frame");
        roleMap[accessibility::AccessibleRole::PAGE] = getRoleForName("page");
        roleMap[accessibility::AccessibleRole::SECTION] = getRoleForName("section");
        roleMap[accessibility::AccessibleRole::FORM] = getRoleForName("form");
        roleMap[accessibility::AccessibleRole::GROUP_BOX] = getRoleForName("grouping");
        roleMap[accessibility::AccessibleRole::COMMENT] = getRoleForName("comment");
        roleMap[accessibility::AccessibleRole::IMAGE_MAP] = getRoleForName("image map");
        roleMap[accessibility::AccessibleRole::TREE_ITEM] = getRoleForName("tree item");
        roleMap[accessibility::AccessibleRole::HYPER_LINK] = getRoleForName("link");
        roleMap[accessibility::AccessibleRole::END_NOTE] = getRoleForName("footnote");
        roleMap[accessibility::AccessibleRole::FOOTNOTE] = getRoleForName("footnote");
        roleMap[accessibility::AccessibleRole::NOTE] = getRoleForName("comment");

        initialized = true;
    }

    static const sal_Int32 nMapSize = SAL_N_ELEMENTS(roleMap);
    if( 0 <= nRole &&  nMapSize > nRole )
        role = roleMap[nRole];

    return role;
}

/*****************************************************************************/

extern "C" {

/*****************************************************************************/

static const gchar*
wrapper_get_name( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    if( obj->mpContext.is() )
    {
        try {
            OString aName =
                OUStringToOString(
                    obj->mpContext->getAccessibleName(),
                    RTL_TEXTENCODING_UTF8);

            int nCmp = atk_obj->name ? rtl_str_compare( atk_obj->name, aName.getStr() ) : -1;
            if( nCmp != 0 )
            {
                if( atk_obj->name )
                    g_free(atk_obj->name);
                atk_obj->name = g_strdup(aName.getStr());
            }
        }
        catch(const uno::Exception&) {
            g_warning( "Exception in getAccessibleName()" );
        }
    }

    return ATK_OBJECT_CLASS (parent_class)->get_name(atk_obj);
}

/*****************************************************************************/

static const gchar*
wrapper_get_description( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    if( obj->mpContext.is() )
    {
        try {
            OString aDescription =
                OUStringToOString(
                    obj->mpContext->getAccessibleDescription(),
                    RTL_TEXTENCODING_UTF8);

            g_free(atk_obj->description);
            atk_obj->description = g_strdup(aDescription.getStr());
        }
        catch(const uno::Exception&) {
            g_warning( "Exception in getAccessibleDescription()" );
        }
    }

    return ATK_OBJECT_CLASS (parent_class)->get_description(atk_obj);

}

/*****************************************************************************/

static AtkAttributeSet *
wrapper_get_attributes( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER( atk_obj );
    AtkAttributeSet *pSet = nullptr;

    try
    {
        uno::Reference< accessibility::XAccessibleExtendedAttributes >
            xExtendedAttrs( obj->mpContext, uno::UNO_QUERY );
        if( xExtendedAttrs.is() )
            pSet = attribute_set_new_from_extended_attributes( xExtendedAttrs );
    }
    catch(const uno::Exception&)
    {
        g_warning( "Exception in getAccessibleAttributes()" );
    }

    return pSet;
}

/*****************************************************************************/

static gint
wrapper_get_n_children( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
    gint n = 0;

    if( obj->mpContext.is() )
    {
        try {
            n = obj->mpContext->getAccessibleChildCount();
        }
        catch(const uno::Exception&) {
            OSL_FAIL("Exception in getAccessibleChildCount()" );
        }
    }

    return n;
}

/*****************************************************************************/

static AtkObject *
wrapper_ref_child( AtkObject *atk_obj,
                   gint       i )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
    AtkObject* child = nullptr;

    // see comments above atk_object_wrapper_remove_child
    if( -1 < i && obj->index_of_child_about_to_be_removed == i )
    {
        g_object_ref( obj->child_about_to_be_removed );
        return obj->child_about_to_be_removed;
    }

    if( obj->mpContext.is() )
    {
        try {
            uno::Reference< accessibility::XAccessible > xAccessible =
                obj->mpContext->getAccessibleChild( i );

            child = atk_object_wrapper_ref( xAccessible );
        }
        catch(const uno::Exception&) {
            OSL_FAIL("Exception in getAccessibleChild");
        }
    }

    return child;
}

/*****************************************************************************/

static gint
wrapper_get_index_in_parent( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj->mpOrig)
        return atk_object_get_index_in_parent(obj->mpOrig);

    gint i = -1;

    if( obj->mpContext.is() )
    {
        try {
            i = obj->mpContext->getAccessibleIndexInParent();
        }
        catch(const uno::Exception&) {
            g_warning( "Exception in getAccessibleIndexInParent()" );
        }
    }
    return i;
}

/*****************************************************************************/

AtkRelation*
atk_object_wrapper_relation_new(const accessibility::AccessibleRelation& rRelation)
{
    sal_uInt32 nTargetCount = rRelation.TargetSet.getLength();

    std::vector<AtkObject*> aTargets;

    for (const auto& rTarget : rRelation.TargetSet)
    {
        uno::Reference< accessibility::XAccessible > xAccessible( rTarget, uno::UNO_QUERY );
        aTargets.push_back(atk_object_wrapper_ref(xAccessible));
    }

    AtkRelation *pRel =
        atk_relation_new(
            aTargets.data(), nTargetCount,
            mapRelationType( rRelation.RelationType )
        );

    return pRel;
}

static AtkRelationSet *
wrapper_ref_relation_set( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit relation set impl
    if (obj->mpOrig)
        return atk_object_ref_relation_set(obj->mpOrig);

    AtkRelationSet *pSet = atk_relation_set_new();

    if( obj->mpContext.is() )
    {
        try {
            uno::Reference< accessibility::XAccessibleRelationSet > xRelationSet(
                    obj->mpContext->getAccessibleRelationSet()
            );

            sal_Int32 nRelations = xRelationSet.is() ? xRelationSet->getRelationCount() : 0;
            for( sal_Int32 n = 0; n < nRelations; n++ )
            {
                AtkRelation *pRel = atk_object_wrapper_relation_new(xRelationSet->getRelation(n));
                atk_relation_set_add(pSet, pRel);
                g_object_unref(pRel);
            }
        }
        catch(const uno::Exception &) {
            g_object_unref( G_OBJECT( pSet ) );
            pSet = nullptr;
        }
    }

    return pSet;
}

static AtkStateSet *
wrapper_ref_state_set( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
    AtkStateSet *pSet = atk_state_set_new();

    if( obj->mpContext.is() )
    {
        try {
            uno::Reference< accessibility::XAccessibleStateSet > xStateSet(
                obj->mpContext->getAccessibleStateSet());

            if( xStateSet.is() )
            {
                uno::Sequence< sal_Int16 > aStates = xStateSet->getStates();

                for( const auto nState : aStates )
                {
                    // ATK_STATE_LAST_DEFINED is used to check if the state
                    // is unmapped, do not report it to Atk
                    if ( mapAtkState( nState ) != ATK_STATE_LAST_DEFINED )
                        atk_state_set_add_state( pSet, mapAtkState( nState ) );
                }

                // We need to emulate FOCUS state for menus, menu-items etc.
                if( atk_obj == atk_get_focus_object() )
                    atk_state_set_add_state( pSet, ATK_STATE_FOCUSED );
/* FIXME - should we do this ?
                else
                    atk_state_set_remove_state( pSet, ATK_STATE_FOCUSED );
*/
            }
        }

        catch(const uno::Exception &) {
            g_warning( "Exception in wrapper_ref_state_set" );
            atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );
        }
    }
    else
        atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );

    return pSet;
}

/*****************************************************************************/

static void
atk_object_wrapper_finalize (GObject *obj)
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER (obj);

    if( pWrap->mpAccessible.is() )
    {
        ooo_wrapper_registry_remove( pWrap->mpAccessible );
        pWrap->mpAccessible.clear();
    }

    atk_object_wrapper_dispose( pWrap );

    parent_class->finalize( obj );
}

static void
atk_object_wrapper_class_init (AtkObjectWrapperClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS( klass );
  AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );

  parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));

  // GObject methods
  gobject_class->finalize = atk_object_wrapper_finalize;

  // AtkObject methods
  atk_class->get_name = wrapper_get_name;
  atk_class->get_description = wrapper_get_description;
  atk_class->get_attributes = wrapper_get_attributes;
  atk_class->get_n_children = wrapper_get_n_children;
  atk_class->ref_child = wrapper_ref_child;
  atk_class->get_index_in_parent = wrapper_get_index_in_parent;
  atk_class->ref_relation_set = wrapper_ref_relation_set;
  atk_class->ref_state_set = wrapper_ref_state_set;
}

static void
atk_object_wrapper_init (AtkObjectWrapper      *wrapper,
                         AtkObjectWrapperClass*)
{
   wrapper->mpAction = nullptr;
   wrapper->mpComponent = nullptr;
   wrapper->mpEditableText = nullptr;
   wrapper->mpHypertext = nullptr;
   wrapper->mpImage = nullptr;
   wrapper->mpSelection = nullptr;
   wrapper->mpTable = nullptr;
   wrapper->mpText = nullptr;
   wrapper->mpValue = nullptr;
}

} // extern "C"

GType
atk_object_wrapper_get_type()
{
  static GType type = 0;

  if (!type)
    {
      static const GTypeInfo typeInfo =
      {
        sizeof (AtkObjectWrapperClass),
        nullptr,
        nullptr,
        reinterpret_cast<GClassInitFunc>(atk_object_wrapper_class_init),
        nullptr,
        nullptr,
        sizeof (AtkObjectWrapper),
        0,
        reinterpret_cast<GInstanceInitFunc>(atk_object_wrapper_init),
        nullptr
      } ;
      type = g_type_register_static (ATK_TYPE_OBJECT,
                                     "OOoAtkObj",
                                     &typeInfo, GTypeFlags(0)) ;
    }
  return type;
}

static bool
isOfType( uno::XInterface *pInterface, const uno::Type & rType )
{
    g_return_val_if_fail( pInterface != nullptr, false );

    bool bIs = false;
    try {
        uno::Any aRet = pInterface->queryInterface( rType );

        bIs = ( ( typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass ) &&
                ( aRet.pReserved != nullptr ) );
    } catch( const uno::Exception &) { }

    return bIs;
}

extern "C" {
typedef  GType (* GetGIfaceType ) ();
}
const struct {
        const char          *name;
        GInterfaceInitFunc const   aInit;
        GetGIfaceType const        aGetGIfaceType;
        const uno::Type &  (*aGetUnoType) ();
} aTypeTable[] = {
// re-location heaven:
    {
        "Comp", reinterpret_cast<GInterfaceInitFunc>(componentIfaceInit),
        atk_component_get_type,
        cppu::UnoType<accessibility::XAccessibleComponent>::get
    },
    {
        "Act",  reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
        atk_action_get_type,
        cppu::UnoType<accessibility::XAccessibleAction>::get
    },
    {
        "Txt",  reinterpret_cast<GInterfaceInitFunc>(textIfaceInit),
        atk_text_get_type,
        cppu::UnoType<accessibility::XAccessibleText>::get
    },
    {
        "Val",  reinterpret_cast<GInterfaceInitFunc>(valueIfaceInit),
        atk_value_get_type,
        cppu::UnoType<accessibility::XAccessibleValue>::get
    },
    {
        "Tab",  reinterpret_cast<GInterfaceInitFunc>(tableIfaceInit),
        atk_table_get_type,
        cppu::UnoType<accessibility::XAccessibleTable>::get
    },
    {
        "Edt",  reinterpret_cast<GInterfaceInitFunc>(editableTextIfaceInit),
        atk_editable_text_get_type,
        cppu::UnoType<accessibility::XAccessibleEditableText>::get
    },
    {
        "Img",  reinterpret_cast<GInterfaceInitFunc>(imageIfaceInit),
        atk_image_get_type,
        cppu::UnoType<accessibility::XAccessibleImage>::get
    },
    {
        "Hyp",  reinterpret_cast<GInterfaceInitFunc>(hypertextIfaceInit),
        atk_hypertext_get_type,
        cppu::UnoType<accessibility::XAccessibleHypertext>::get
    },
    {
        "Sel",  reinterpret_cast<GInterfaceInitFunc>(selectionIfaceInit),
        atk_selection_get_type,
        cppu::UnoType<accessibility::XAccessibleSelection>::get
    }
    // AtkDocument is a nastily broken interface (so far)
    //  we could impl. get_document_type perhaps though.
};

const int aTypeTableSize = G_N_ELEMENTS( aTypeTable );

static GType
ensureTypeFor( uno::XInterface *pAccessible )
{
    int i;
    bool bTypes[ aTypeTableSize ] = { false, };
    OStringBuffer aTypeNameBuf( "OOoAtkObj" );

    for( i = 0; i < aTypeTableSize; i++ )
    {
        if( isOfType( pAccessible, aTypeTable[i].aGetUnoType() ) )
        {
            aTypeNameBuf.append(aTypeTable[i].name);
            bTypes[i] = true;
        }
    }

    OString aTypeName = aTypeNameBuf.makeStringAndClear();
    GType nType = g_type_from_name( aTypeName.getStr() );
    if( nType == G_TYPE_INVALID )
    {
        GTypeInfo aTypeInfo = {
            sizeof( AtkObjectWrapperClass ),
            nullptr, nullptr, nullptr, nullptr, nullptr,
            sizeof( AtkObjectWrapper ),
            0, nullptr, nullptr
        } ;
        nType = g_type_register_static( ATK_TYPE_OBJECT_WRAPPER,
                                        aTypeName.getStr(), &aTypeInfo,
                                        GTypeFlags(0) ) ;

        for( int j = 0; j < aTypeTableSize; j++ )
            if( bTypes[j] )
            {
                GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr };
                aIfaceInfo.interface_init = aTypeTable[j].aInit;
                g_type_add_interface_static (nType, aTypeTable[j].aGetGIfaceType(),
                                             &aIfaceInfo);
            }
    }
    return nType;
}

AtkObject *
atk_object_wrapper_ref( const uno::Reference< accessibility::XAccessible > &rxAccessible, bool create )
{
    g_return_val_if_fail( rxAccessible.get() != nullptr, nullptr );

    AtkObject *obj = ooo_wrapper_registry_get(rxAccessible);
    if( obj )
    {
        g_object_ref( obj );
        return obj;
    }

    if( create )
        return atk_object_wrapper_new( rxAccessible );

    return nullptr;
}

AtkObject *
atk_object_wrapper_new( const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
                        AtkObject* parent, AtkObject* orig )
{
    g_return_val_if_fail( rxAccessible.get() != nullptr, nullptr );

    AtkObjectWrapper *pWrap = nullptr;

    try {
        uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext());

        g_return_val_if_fail( xContext.get() != nullptr, nullptr );

        GType nType = ensureTypeFor( xContext.get() );
        gpointer obj = g_object_new( nType, nullptr);

        pWrap = ATK_OBJECT_WRAPPER( obj );
        pWrap->mpAccessible = rxAccessible;

        pWrap->index_of_child_about_to_be_removed = -1;
        pWrap->child_about_to_be_removed = nullptr;

        pWrap->mpContext = xContext;
        pWrap->mpOrig = orig;

        AtkObject* atk_obj = ATK_OBJECT(pWrap);
        atk_obj->role = mapToAtkRole( xContext->getAccessibleRole() );
        atk_obj->accessible_parent = parent;

        ooo_wrapper_registry_add( rxAccessible, atk_obj );

        if( parent )
            g_object_ref( atk_obj->accessible_parent );
        else
        {
            /* gail_focus_tracker remembers the focused object at the first
             * parent in the hierarchy that is a Gtk+ widget, but at the time the
             * event gets processed (at idle), it may be too late to create the
             * hierarchy, so doing it now ..
             */
            uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );

            if( xParent.is() )
                atk_obj->accessible_parent = atk_object_wrapper_ref( xParent );
        }

        // Attach a listener to the UNO object if it's not TRANSIENT
        uno::Reference< accessibility::XAccessibleStateSet > xStateSet( xContext->getAccessibleStateSet() );
        if( xStateSet.is() && ! xStateSet->contains( accessibility::AccessibleStateType::TRANSIENT ) )
        {
            uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
            if( xBroadcaster.is() )
            {
                uno::Reference<accessibility::XAccessibleEventListener> xListener(new AtkListener(pWrap));
                xBroadcaster->addAccessibleEventListener(xListener);
            }
            else
                OSL_ASSERT( false );
        }

#if ATK_CHECK_VERSION(2,33,1)
        {
            css::uno::Reference<css::accessibility::XAccessibleContext2> xContext2(xContext, css::uno::UNO_QUERY);
            if( xContext2.is() )
            {
                OString aId = OUStringToOString( xContext2->getAccessibleId(), RTL_TEXTENCODING_UTF8);
                atk_object_set_accessible_id(atk_obj, aId.getStr());
            }
        }
#endif

        return ATK_OBJECT( pWrap );
    }
    catch (const uno::Exception &)
    {
        if( pWrap )
            g_object_unref( pWrap );

        return nullptr;
    }
}

/*****************************************************************************/

void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
{
    AtkObject *atk_obj = ATK_OBJECT( wrapper );

    atk_object_set_parent( child, atk_obj );
    g_signal_emit_by_name( atk_obj, "children_changed::add", index, child, nullptr );
}

/*****************************************************************************/

void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
{
    /*
     * the atk-bridge GTK+ module gets back to the event source to ref the child just
     * vanishing, so we keep this reference because the semantic on OOo side is different.
     */
    wrapper->child_about_to_be_removed = child;
    wrapper->index_of_child_about_to_be_removed = index;

    g_signal_emit_by_name( ATK_OBJECT( wrapper ), "children_changed::remove", index, child, nullptr );

    wrapper->index_of_child_about_to_be_removed = -1;
    wrapper->child_about_to_be_removed = nullptr;
}

/*****************************************************************************/

void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role)
{
    AtkObject *atk_obj = ATK_OBJECT( wrapper );
    atk_object_set_role( atk_obj, mapToAtkRole( role ) );
}

/*****************************************************************************/

void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper)
{
    wrapper->mpContext.clear();
    wrapper->mpAction.clear();
    wrapper->mpComponent.clear();
    wrapper->mpEditableText.clear();
    wrapper->mpHypertext.clear();
    wrapper->mpImage.clear();
    wrapper->mpSelection.clear();
    wrapper->mpMultiLineText.clear();
    wrapper->mpTable.clear();
    wrapper->mpText.clear();
    wrapper->mpTextMarkup.clear();
    wrapper->mpTextAttributes.clear();
    wrapper->mpValue.clear();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gloactiongroup.cxx b/vcl/unx/gtk/gloactiongroup.cxx
deleted file mode 100644
index 56782e2..0000000
--- a/vcl/unx/gtk/gloactiongroup.cxx
+++ /dev/null
@@ -1,398 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <unx/gtk/gtksalmenu.hxx>

#ifdef ENABLE_GMENU_INTEGRATION

#include <unx/gtk/gloactiongroup.h>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkframe.hxx>

#include <sal/log.hxx>

/*
 * GLOAction
 */

#define G_TYPE_LO_ACTION                                (g_lo_action_get_type ())
#define G_LO_ACTION(inst)                               (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
                                                         G_TYPE_LO_ACTION, GLOAction))

struct GLOAction
{
    GObject         parent_instance;

    gint            item_id;            // Menu item ID.
    gboolean        submenu;            // TRUE if action is a submenu action.
    gboolean        enabled;            // TRUE if action is enabled.
    GVariantType*   parameter_type;     // A GVariantType with the action parameter type.
    GVariantType*   state_type;         // A GVariantType with item state type
    GVariant*       state_hint;         // A GVariant with state hints.
    GVariant*       state;              // A GVariant with current item state
};

typedef GObjectClass GLOActionClass;

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
G_DEFINE_TYPE (GLOAction, g_lo_action, G_TYPE_OBJECT);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static GLOAction*
g_lo_action_new()
{
    return G_LO_ACTION (g_object_new (G_TYPE_LO_ACTION, nullptr));
}

static void
g_lo_action_init (GLOAction *action)
{
    action->item_id = -1;
    action->submenu = FALSE;
    action->enabled = TRUE;
    action->parameter_type = nullptr;
    action->state_type = nullptr;
    action->state_hint = nullptr;
    action->state = nullptr;
}

static void
g_lo_action_finalize (GObject *object)
{
    GLOAction* action = G_LO_ACTION(object);

    if (action->parameter_type)
        g_variant_type_free (action->parameter_type);

    if (action->state_type)
        g_variant_type_free (action->state_type);

    if (action->state_hint)
        g_variant_unref (action->state_hint);

    if (action->state)
        g_variant_unref (action->state);

    G_OBJECT_CLASS (g_lo_action_parent_class)->finalize (object);
}

static void
g_lo_action_class_init (GLOActionClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->finalize = g_lo_action_finalize;
}

/*
 * GLOActionGroup
 */

struct GLOActionGroupPrivate
{
    GHashTable  *table;    /* string -> GLOAction */
};

static void g_lo_action_group_iface_init (GActionGroupInterface *);

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
G_DEFINE_TYPE_WITH_CODE (GLOActionGroup,
    g_lo_action_group, G_TYPE_OBJECT,
    G_ADD_PRIVATE(GLOActionGroup)
    G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
                           g_lo_action_group_iface_init));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static gchar **
g_lo_action_group_list_actions (GActionGroup *group)
{
    GLOActionGroup *loGroup = G_LO_ACTION_GROUP (group);
    GHashTableIter iter;
    gint n, i = 0;
    gchar **keys;
    gpointer key;

    n = g_hash_table_size (loGroup->priv->table);
    keys = g_new (gchar *, n + 1);

    g_hash_table_iter_init (&iter, loGroup->priv->table);
    while (g_hash_table_iter_next (&iter, &key, nullptr))
        keys[i++] = g_strdup (static_cast<gchar*>(key));
    g_assert_cmpint (i, ==, n);
    keys[n] = nullptr;

    return keys;
}

static gboolean
g_lo_action_group_query_action (GActionGroup        *group,
                                const gchar         *action_name,
                                gboolean            *enabled,
                                const GVariantType **parameter_type,
                                const GVariantType **state_type,
                                GVariant           **state_hint,
                                GVariant           **state)
{
    //SAL_INFO("vcl.unity", "g_lo_action_group_query_action on " << group);
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group);
    GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));

    if (action == nullptr)
        return FALSE;

    if (enabled)
    {
        *enabled = action->enabled;
    }

    if (parameter_type)
        *parameter_type = action->parameter_type;

    if (state_type)
        *state_type = action->state_type;

    if (state_hint)
        *state_hint = (action->state_hint) ? g_variant_ref (action->state_hint) : nullptr;

    if (state)
        *state = (action->state) ? g_variant_ref (action->state) : nullptr;

    return TRUE;
}

static void
g_lo_action_group_perform_submenu_action (GLOActionGroup *group,
                                          const gchar    *action_name,
                                          GVariant       *state)
{
    gboolean bState = g_variant_get_boolean (state);
    SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);

    if (bState)
        GtkSalMenu::Activate(action_name);
    else
        GtkSalMenu::Deactivate(action_name);
}

static void
g_lo_action_group_change_state (GActionGroup *group,
                                const gchar  *action_name,
                                GVariant     *value)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_change_state on " << group );
    g_return_if_fail (value != nullptr);

    g_variant_ref_sink (value);

    if (action_name != nullptr)
    {
        GLOActionGroup* lo_group = G_LO_ACTION_GROUP (group);
        GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));

        if (action != nullptr)
        {
            if (action->submenu)
                g_lo_action_group_perform_submenu_action (lo_group, action_name, value);
            else
            {
                gboolean is_new = FALSE;

                /* If action already exists but has no state, it should be removed and added again. */
                if (action->state_type == nullptr)
                {
                    g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
                    action->state_type = g_variant_type_copy (g_variant_get_type(value));
                    is_new = TRUE;
                }

                if (g_variant_is_of_type (value, action->state_type))
                {
                    if (action->state)
                        g_variant_unref(action->state);

                    action->state = g_variant_ref (value);

                    if (is_new)
                        g_action_group_action_added (G_ACTION_GROUP (group), action_name);
                    else
                        g_action_group_action_state_changed (group, action_name, value);
                }
            }
        }
    }

    g_variant_unref (value);
}

static void
g_lo_action_group_activate (GActionGroup *group,
                            const gchar  *action_name,
                            GVariant     *parameter)
{
    if (parameter != nullptr)
        g_action_group_change_action_state(group, action_name, parameter);
    GtkSalMenu::DispatchCommand(action_name);
}

void
g_lo_action_group_insert (GLOActionGroup *group,
                          const gchar    *action_name,
                          gint            item_id,
                          gboolean        submenu)
{
    g_lo_action_group_insert_stateful (group, action_name, item_id, submenu, nullptr, nullptr, nullptr, nullptr);
}

void
g_lo_action_group_insert_stateful (GLOActionGroup     *group,
                                   const gchar        *action_name,
                                   gint                item_id,
                                   gboolean            submenu,
                                   const GVariantType *parameter_type,
                                   const GVariantType *state_type,
                                   GVariant           *state_hint,
                                   GVariant           *state)
{
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    GLOAction* old_action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));

    if (old_action == nullptr || old_action->item_id != item_id)
    {
        if (old_action != nullptr)
            g_lo_action_group_remove (group, action_name);

        GLOAction* action = g_lo_action_new();

        g_hash_table_insert (group->priv->table, g_strdup (action_name), action);

        action->item_id = item_id;
        action->submenu = submenu;

        if (parameter_type)
            action->parameter_type = const_cast<GVariantType*>(parameter_type);

        if (state_type)
            action->state_type = const_cast<GVariantType*>(state_type);

        if (state_hint)
            action->state_hint = g_variant_ref_sink (state_hint);

        if (state)
            action->state = g_variant_ref_sink (state);

        g_action_group_action_added (G_ACTION_GROUP (group), action_name);
    }
}

static void
g_lo_action_group_finalize (GObject *object)
{
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (object);

    g_hash_table_unref (lo_group->priv->table);

    G_OBJECT_CLASS (g_lo_action_group_parent_class)->finalize (object);
}

static void
g_lo_action_group_init (GLOActionGroup *group)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_init on " << group);
    group->priv = static_cast<GLOActionGroupPrivate *>(g_lo_action_group_get_instance_private (group));
    group->priv->table = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  g_free, g_object_unref);
}

static void
g_lo_action_group_class_init (GLOActionGroupClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = g_lo_action_group_finalize;
}

static void
g_lo_action_group_iface_init (GActionGroupInterface *iface)
{
    iface->list_actions = g_lo_action_group_list_actions;
    iface->query_action = g_lo_action_group_query_action;
    iface->change_action_state = g_lo_action_group_change_state;
    iface->activate_action = g_lo_action_group_activate;
}

GLOActionGroup *
g_lo_action_group_new()
{
    GLOActionGroup* group = G_LO_ACTION_GROUP (g_object_new (G_TYPE_LO_ACTION_GROUP, nullptr));
    return group;
}

void
g_lo_action_group_set_action_enabled (GLOActionGroup *group,
                                      const gchar    *action_name,
                                      gboolean        enabled)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_set_action_enabled on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
    g_return_if_fail (action_name != nullptr);

    GLOAction* action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));

    if (action == nullptr)
        return;

    action->enabled = enabled;

    g_action_group_action_enabled_changed (G_ACTION_GROUP (group), action_name, enabled);
}

void
g_lo_action_group_remove (GLOActionGroup *group,
                          const gchar    *action_name)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_remove on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    if (action_name != nullptr)
    {
        g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
        g_hash_table_remove (group->priv->table, action_name);
    }
}

void
g_lo_action_group_clear (GLOActionGroup  *group)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_clear on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    GList* keys = g_hash_table_get_keys (group->priv->table);

    for (GList* element = g_list_first (keys); element != nullptr; element = g_list_next (element))
    {
        g_lo_action_group_remove (group, static_cast<gchar*>(element->data));
    }

    g_list_free (keys);
}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/glomenu.cxx b/vcl/unx/gtk/glomenu.cxx
deleted file mode 100644
index 5457ac6..0000000
--- a/vcl/unx/gtk/glomenu.cxx
+++ /dev/null
@@ -1,691 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <string.h>

#include <unx/gtk/gtksalmenu.hxx>

#ifdef ENABLE_GMENU_INTEGRATION

#include <unx/gtk/glomenu.h>

struct GLOMenu
{
    GMenuModel const  parent_instance;

    GArray      *items;
};

typedef GMenuModelClass GLOMenuClass;

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
G_DEFINE_TYPE (GLOMenu, g_lo_menu, G_TYPE_MENU_MODEL);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

struct item
{
    GHashTable*     attributes;     // Item attributes.
    GHashTable*     links;          // Item links.
};

static void
g_lo_menu_struct_item_init (struct item *menu_item)
{
    menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, reinterpret_cast<GDestroyNotify>(g_variant_unref));
    menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}

/* We treat attribute names the same as GSettings keys:
 * - only lowercase ascii, digits and '-'
 * - must start with lowercase
 * - must not end with '-'
 * - no consecutive '-'
 * - not longer than 1024 chars
 */
static gboolean
valid_attribute_name (const gchar *name)
{
    gint i;

    if (!g_ascii_islower (name[0]))
        return FALSE;

    for (i = 1; name[i]; i++)
    {
        if (name[i] != '-' &&
                !g_ascii_islower (name[i]) &&
                !g_ascii_isdigit (name[i]))
            return FALSE;

        if (name[i] == '-' && name[i + 1] == '-')
            return FALSE;
    }

    if (name[i - 1] == '-')
        return FALSE;

    if (i > 1024)
        return FALSE;

    return TRUE;
}

/*
 * GLOMenu
 */

static gboolean
g_lo_menu_is_mutable (GMenuModel*)
{
    // Menu is always mutable.
    return TRUE;
}

static gint
g_lo_menu_get_n_items (GMenuModel *model)
{
    g_return_val_if_fail (model != nullptr, 0);
    GLOMenu *menu = G_LO_MENU (model);
    g_return_val_if_fail (menu->items != nullptr, 0);

    return menu->items->len;
}

gint
g_lo_menu_get_n_items_from_section (GLOMenu *menu,
                                    gint     section)
{
    g_return_val_if_fail (0 <= section && section < static_cast<gint>(menu->items->len), 0);

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_val_if_fail (model != nullptr, 0);

    gint length = model->items->len;

    g_object_unref (model);

    return length;
}

static void
g_lo_menu_get_item_attributes (GMenuModel  *model,
                               gint         position,
                               GHashTable **table)
{
    GLOMenu *menu = G_LO_MENU (model);
    *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes);
}

static void
g_lo_menu_get_item_links (GMenuModel  *model,
                          gint         position,
                          GHashTable **table)
{
    GLOMenu *menu = G_LO_MENU (model);
    *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links);
}

void
g_lo_menu_insert (GLOMenu     *menu,
                  gint         position,
                  const gchar *label)
{
    g_lo_menu_insert_section (menu, position, label, nullptr);
}

void
g_lo_menu_insert_in_section (GLOMenu     *menu,
                             gint         section,
                             gint         position,
                             const gchar *label)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= section && section < static_cast<gint>(menu->items->len));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_insert (model, position, label);

    g_object_unref (model);
}

GLOMenu *
g_lo_menu_new()
{
    return G_LO_MENU( g_object_new (G_TYPE_LO_MENU, nullptr) );
}

static void
g_lo_menu_set_attribute_value (GLOMenu     *menu,
                               gint         position,
                               const gchar *attribute,
                               GVariant    *value)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (attribute != nullptr);
    g_return_if_fail (valid_attribute_name (attribute));

    if (position >= static_cast<gint>(menu->items->len))
        return;

    struct item menu_item = g_array_index (menu->items, struct item, position);

    if (value != nullptr)
        g_hash_table_insert (menu_item.attributes, g_strdup (attribute), g_variant_ref_sink (value));
    else
        g_hash_table_remove (menu_item.attributes, attribute);
}

static GVariant*
g_lo_menu_get_attribute_value_from_item_in_section (GLOMenu            *menu,
                                                    gint                section,
                                                    gint                position,
                                                    const gchar        *attribute,
                                                    const GVariantType *type)
{
    GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));

    g_return_val_if_fail (model != nullptr, nullptr);

    GVariant *value = g_menu_model_get_item_attribute_value (model,
                                                             position,
                                                             attribute,
                                                             type);

    g_object_unref (model);

    return value;
}

void
g_lo_menu_set_label (GLOMenu     *menu,
                     gint         position,
                     const gchar *label)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GVariant *value;

    if (label != nullptr)
        value = g_variant_new_string (label);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_LABEL, value);
}

void
g_lo_menu_set_icon (GLOMenu     *menu,
                    gint         position,
                    const GIcon *icon)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GVariant *value;

    if (icon != nullptr)
    {
#if GLIB_CHECK_VERSION(2,38,0)
        value = g_icon_serialize (const_cast<GIcon*>(icon));
#else
        value = nullptr;
#endif
    }
    else
        value = nullptr;

#ifndef G_MENU_ATTRIBUTE_ICON
#    define G_MENU_ATTRIBUTE_ICON "icon"
#endif

    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ICON, value);
    if (value)
      g_variant_unref (value);
}

void
g_lo_menu_set_label_to_item_in_section (GLOMenu     *menu,
                                        gint         section,
                                        gint         position,
                                        const gchar *label)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_set_label (model, position, label);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

void
g_lo_menu_set_icon_to_item_in_section (GLOMenu     *menu,
                                       gint         section,
                                       gint         position,
                                       const GIcon *icon)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_set_icon (model, position, icon);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

gchar *
g_lo_menu_get_label_from_item_in_section (GLOMenu *menu,
                                          gint     section,
                                          gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    GVariant *label_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
                                                                                section,
                                                                                position,
                                                                                G_MENU_ATTRIBUTE_LABEL,
                                                                                G_VARIANT_TYPE_STRING);

    gchar *label = nullptr;

    if (label_value)
    {
        label = g_variant_dup_string (label_value, nullptr);
        g_variant_unref (label_value);
    }

    return label;
}

void
g_lo_menu_set_action_and_target_value (GLOMenu     *menu,
                                       gint         position,
                                       const gchar *action,
                                       GVariant    *target_value)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GVariant *action_value;

    if (action != nullptr)
    {
        action_value = g_variant_new_string (action);
    }
    else
    {
        action_value = nullptr;
        target_value = nullptr;
    }

    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ACTION, action_value);
    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_TARGET, target_value);
    g_lo_menu_set_attribute_value (menu, position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, nullptr);

    g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 1);
}

void
g_lo_menu_set_action_and_target_value_to_item_in_section (GLOMenu     *menu,
                                                          gint         section,
                                                          gint         position,
                                                          const gchar *command,
                                                          GVariant    *target_value)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_set_action_and_target_value (model, position, command, target_value);

    g_object_unref (model);
}

void
g_lo_menu_set_accelerator_to_item_in_section (GLOMenu     *menu,
                                              gint         section,
                                              gint         position,
                                              const gchar *accelerator)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    GVariant *value;

    if (accelerator != nullptr)
        value = g_variant_new_string (accelerator);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_ACCELERATOR, value);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

gchar *
g_lo_menu_get_accelerator_from_item_in_section (GLOMenu *menu,
                                                gint     section,
                                                gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    GVariant *accel_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
                                                                                section,
                                                                                position,
                                                                                G_LO_MENU_ATTRIBUTE_ACCELERATOR,
                                                                                G_VARIANT_TYPE_STRING);

    gchar *accel = nullptr;

    if (accel_value != nullptr)
    {
        accel = g_variant_dup_string (accel_value, nullptr);
        g_variant_unref (accel_value);
    }

    return accel;
}

void
g_lo_menu_set_command_to_item_in_section (GLOMenu     *menu,
                                          gint         section,
                                          gint         position,
                                          const gchar *command)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    GVariant *value;

    if (command != nullptr)
        value = g_variant_new_string (command);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_COMMAND, value);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

gchar *
g_lo_menu_get_command_from_item_in_section (GLOMenu *menu,
                                            gint     section,
                                            gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    GVariant *command_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
                                                                                  section,
                                                                                  position,
                                                                                  G_LO_MENU_ATTRIBUTE_COMMAND,
                                                                                  G_VARIANT_TYPE_STRING);

    gchar *command = nullptr;

    if (command_value != nullptr)
    {
        command = g_variant_dup_string (command_value, nullptr);
        g_variant_unref (command_value);
    }

    return command;
}

static void
g_lo_menu_set_link (GLOMenu     *menu,
                    gint         position,
                    const gchar *link,
                    GMenuModel  *model)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (link != nullptr);
    g_return_if_fail (valid_attribute_name (link));

    if (position < 0 || position >= static_cast<gint>(menu->items->len))
        position = menu->items->len - 1;

    struct item menu_item = g_array_index (menu->items, struct item, position);

    if (model != nullptr)
        g_hash_table_insert (menu_item.links, g_strdup (link), g_object_ref (model));
    else
        g_hash_table_remove (menu_item.links, link);
}

void
g_lo_menu_insert_section (GLOMenu     *menu,
                          gint         position,
                          const gchar *label,
                          GMenuModel  *section)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    if (position < 0 || position > static_cast<gint>(menu->items->len))
        position = menu->items->len;

    struct item menu_item;

    g_lo_menu_struct_item_init(&menu_item);

    g_array_insert_val (menu->items, position, menu_item);

    g_lo_menu_set_label (menu, position, label);
    g_lo_menu_set_link (menu, position, G_MENU_LINK_SECTION, section);

    g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1);
}

void
g_lo_menu_new_section (GLOMenu     *menu,
                       gint         position,
                       const gchar *label)
{
    GMenuModel *section = G_MENU_MODEL (g_lo_menu_new());

    g_lo_menu_insert_section (menu, position, label, section);

    g_object_unref (section);
}

GLOMenu *
g_lo_menu_get_section (GLOMenu *menu,
                       gint section)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    return G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
                      ->get_item_link (G_MENU_MODEL (menu), section, G_MENU_LINK_SECTION));
}

void
g_lo_menu_new_submenu_in_item_in_section (GLOMenu *menu,
                                          gint     section,
                                          gint     position)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= section && section < static_cast<gint>(menu->items->len));

    GLOMenu* model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    if (0 <= position && position < static_cast<gint>(model->items->len)) {
        GMenuModel* submenu = G_MENU_MODEL (g_lo_menu_new());

        g_lo_menu_set_link (model, position, G_MENU_LINK_SUBMENU, submenu);

        g_object_unref (submenu);

        g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

        g_object_unref (model);
    }
}

GLOMenu *
g_lo_menu_get_submenu_from_item_in_section (GLOMenu *menu,
                                            gint     section,
                                            gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
    g_return_val_if_fail (0 <= section && section < static_cast<gint>(menu->items->len), nullptr);

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_val_if_fail (model != nullptr, nullptr);

    GLOMenu *submenu = nullptr;

    if (0 <= position && position < static_cast<gint>(model->items->len))
        submenu = G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
                ->get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU));
        //submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU);

    g_object_unref (model);

    return submenu;
}

void
g_lo_menu_set_submenu_action_to_item_in_section (GLOMenu     *menu,
                                                 gint         section,
                                                 gint         position,
                                                 const gchar *action)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));

    g_return_if_fail (model != nullptr);

    GVariant *value;

    if (action != nullptr)
        value = g_variant_new_string (action);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (G_LO_MENU (model), position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, value);

    // Notify the update.
    g_menu_model_items_changed (model, position, 1, 1);

    g_object_unref (model);
}

static void
g_lo_menu_clear_item (struct item *menu_item)
{
    if (menu_item->attributes != nullptr)
        g_hash_table_unref (menu_item->attributes);
    if (menu_item->links != nullptr)
        g_hash_table_unref (menu_item->links);
}

void
g_lo_menu_remove (GLOMenu *menu,
                  gint     position)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= position && position < static_cast<gint>(menu->items->len));

    g_lo_menu_clear_item (&g_array_index (menu->items, struct item, position));
    g_array_remove_index (menu->items, position);
    g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0);
}

void
g_lo_menu_remove_from_section (GLOMenu *menu,
                               gint     section,
                               gint     position)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= section && section < static_cast<gint>(menu->items->len));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_remove (model, position);

    g_object_unref (model);
}

static void
g_lo_menu_finalize (GObject *object)
{
    GLOMenu *menu = G_LO_MENU (object);
    struct item *items;
    gint n_items;
    gint i;

    n_items = menu->items->len;
    items = reinterpret_cast<struct item *>(g_array_free (menu->items, FALSE));
    for (i = 0; i < n_items; i++)
        g_lo_menu_clear_item (&items[i]);
    g_free (items);

    G_OBJECT_CLASS (g_lo_menu_parent_class)
            ->finalize (object);
}

static void
g_lo_menu_init (GLOMenu *menu)
{
    menu->items = g_array_new (FALSE, FALSE, sizeof (struct item));
}

static void
g_lo_menu_class_init (GLOMenuClass *klass)
{
    GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass);
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = g_lo_menu_finalize;

    model_class->is_mutable = g_lo_menu_is_mutable;
    model_class->get_n_items = g_lo_menu_get_n_items;
    model_class->get_item_attributes = g_lo_menu_get_item_attributes;
    model_class->get_item_links = g_lo_menu_get_item_links;
}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtkdata.cxx b/vcl/unx/gtk/gtkdata.cxx
deleted file mode 100644
index ca7a567..0000000
--- a/vcl/unx/gtk/gtkdata.cxx
+++ /dev/null
@@ -1,890 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <unistd.h>
#include <fcntl.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <poll.h>
#if defined(FREEBSD) || defined(NETBSD)
#include <sys/types.h>
#include <sys/time.h>
#endif
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <unx/salobj.h>
#include <unx/geninst.h>
#include <osl/thread.h>
#include <osl/process.h>
#include <sal/log.hxx>

#include <unx/i18n_im.hxx>
#include <unx/i18n_xkb.hxx>
#include <unx/wmadaptor.hxx>

#include <unx/x11_cursors/salcursors.h>

#include <vcl/svapp.hxx>
#include <chrono>

using namespace vcl_sal;

/***************************************************************
 * class GtkSalDisplay                                         *
 ***************************************************************/
extern "C" {
static GdkFilterReturn call_filterGdkEvent( GdkXEvent* sys_event,
                                     GdkEvent* /*event*/,
                                     gpointer data )
{
    GtkSalDisplay *pDisplay = static_cast<GtkSalDisplay *>(data);
    return pDisplay->filterGdkEvent( sys_event );
}
}

GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) :
            SalDisplay( gdk_x11_display_get_xdisplay( pDisplay ) ),
            m_pSys( GtkSalSystem::GetSingleton() ),
            m_pGdkDisplay( pDisplay ),
            m_bStartupCompleted( false )
{
    for(GdkCursor* & rpCsr : m_aCursors)
        rpCsr = nullptr;
    m_bUseRandRWrapper = false; // use gdk signal instead
    Init ();

    // FIXME: unify this with SalInst's filter too ?
    gdk_window_add_filter( nullptr, call_filterGdkEvent, this );

    if ( getenv( "SAL_IGNOREXERRORS" ) )
        GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap

    m_bX11Display = true;

    gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
}

GtkSalDisplay::~GtkSalDisplay()
{
    gdk_window_remove_filter( nullptr, call_filterGdkEvent, this );

    if( !m_bStartupCompleted )
        gdk_notify_startup_complete();

    doDestruct();
    pDisp_ = nullptr;

    for(GdkCursor* & rpCsr : m_aCursors)
        if( rpCsr )
            gdk_cursor_unref( rpCsr );
}

extern "C" {

static void signalScreenSizeChanged( GdkScreen* pScreen, gpointer data )
{
    GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
    pDisp->screenSizeChanged( pScreen );
}

static void signalMonitorsChanged( GdkScreen* pScreen, gpointer data )
{
    GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
    pDisp->monitorsChanged( pScreen );
}

}

GdkFilterReturn GtkSalDisplay::filterGdkEvent( GdkXEvent* sys_event )
{
    GdkFilterReturn aFilterReturn = GDK_FILTER_CONTINUE;
    XEvent *pEvent = static_cast<XEvent *>(sys_event);

    // dispatch all XEvents to event callback
    if( GetSalData()->m_pInstance->
        CallEventCallback( pEvent, sizeof( XEvent ) ) )
        aFilterReturn = GDK_FILTER_REMOVE;

    if (GetDisplay() == pEvent->xany.display )
    {
        // #i53471# gtk has no callback mechanism that lets us be notified
        // when settings (as in XSETTING and opposed to styles) are changed.
        // so we need to listen for corresponding property notifications here
        // these should be rare enough so that we can assume that the settings
        // actually change when a corresponding PropertyNotify occurs
        SalFrame *pAnyFrame = anyFrame();
        if( pAnyFrame && pEvent->type == PropertyNotify &&
            pEvent->xproperty.atom == getWMAdaptor()->getAtom( WMAdaptor::XSETTINGS ) )
        {
            PostEvent( pAnyFrame, nullptr, SalEvent::SettingsChanged );
        }
        // let's see if one of our frames wants to swallow these events
        // get the frame
        for (auto pSalFrame : m_aFrames )
        {
            GtkSalFrame* pFrame = static_cast<GtkSalFrame*>( pSalFrame );
            if( pFrame->GetSystemData()->aWindow == pEvent->xany.window ||
                ( pFrame->getForeignParent() && pFrame->getForeignParentWindow() == pEvent->xany.window ) ||
                ( pFrame->getForeignTopLevel() && pFrame->getForeignTopLevelWindow() == pEvent->xany.window )
                )
            {
                if( ! pFrame->Dispatch( pEvent ) )
                    aFilterReturn = GDK_FILTER_REMOVE;
                break;
            }
        }
        X11SalObject::Dispatch( pEvent );
    }

    return aFilterReturn;
}

void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen )
{
    m_pSys->countScreenMonitors();
    if (pScreen)
        emitDisplayChanged();
}

void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen )
{
    m_pSys->countScreenMonitors();
    if (pScreen)
        emitDisplayChanged();
}

SalDisplay::ScreenData *
GtkSalDisplay::initScreen( SalX11Screen nXScreen ) const
{
    // choose visual for screen
    ScreenData *pSD;
    if (!(pSD = SalDisplay::initScreen( nXScreen )))
        return nullptr;

    // now set a gdk default colormap matching the chosen visual to the screen
    GdkScreen* pScreen = gdk_display_get_screen( m_pGdkDisplay, nXScreen.getXScreen() );
//  should really use this:
//  GdkVisual* pVis = gdk_x11_screen_lookup_visual_get( screen, pSD->m_aVisual.visualid );
//  and not this:
    GdkVisual* pVis = gdkx_visual_get( pSD->m_aVisual.visualid );
    if( pVis )
    {
        GdkColormap* pDefCol = gdk_screen_get_default_colormap( pScreen );
        GdkVisual* pDefVis = gdk_colormap_get_visual( pDefCol );
        if( pDefVis != pVis )
        {
           pDefCol = gdk_x11_colormap_foreign_new( pVis, pSD->m_aColormap.GetXColormap() );
           gdk_screen_set_default_colormap( pScreen, pDefCol );
           SAL_INFO( "vcl.gtk", "set new gdk color map for screen " << nXScreen.getXScreen() );
        }
    }
    else
        SAL_INFO( "vcl.gtk", "not GdkVisual for visual id " << pSD->m_aVisual.visualid );

    return pSD;
}

bool GtkSalDisplay::Dispatch( XEvent* pEvent )
{
    if( GetDisplay() == pEvent->xany.display )
    {
        // let's see if one of our frames wants to swallow these events
        // get the child frame
        for (auto pSalFrame : m_aFrames )
        {
            if (pSalFrame->GetSystemData()->aWindow == pEvent->xany.window)
                return static_cast<GtkSalFrame*>( pSalFrame )->Dispatch( pEvent );
        }
    }

    return false;
}

GdkCursor* GtkSalDisplay::getFromXBM( const unsigned char *pBitmap,
                                      const unsigned char *pMask,
                                      int nWidth, int nHeight,
                                      int nXHot, int nYHot )
{
    GdkScreen *pScreen = gdk_display_get_default_screen( m_pGdkDisplay );
    GdkDrawable *pDrawable = GDK_DRAWABLE( gdk_screen_get_root_window (pScreen) );
    GdkBitmap *pBitmapPix = gdk_bitmap_create_from_data
            ( pDrawable, reinterpret_cast<const char*>(pBitmap), nWidth, nHeight );
    GdkBitmap *pMaskPix = gdk_bitmap_create_from_data
            ( pDrawable, reinterpret_cast<const char*>(pMask), nWidth, nHeight );
    GdkColormap *pColormap = gdk_drawable_get_colormap( pDrawable );

    GdkColor aWhite = { 0, 0xffff, 0xffff, 0xffff };
    GdkColor aBlack = { 0, 0, 0, 0 };

    gdk_colormap_alloc_color( pColormap, &aBlack, FALSE, TRUE);
    gdk_colormap_alloc_color( pColormap, &aWhite, FALSE, TRUE);

    return gdk_cursor_new_from_pixmap
            ( pBitmapPix, pMaskPix,
              &aBlack, &aWhite, nXHot, nYHot);
}

static unsigned char nullmask_bits[] = { 0x00, 0x00, 0x00, 0x00 };
static unsigned char nullcurs_bits[] = { 0x00, 0x00, 0x00, 0x00 };

#define MAKE_CURSOR( vcl_name, name ) \
    case vcl_name: \
        pCursor = getFromXBM( name##curs##_bits, name##mask##_bits, \
                              name##curs_width, name##curs_height, \
                              name##curs_x_hot, name##curs_y_hot ); \
        break
#define MAP_BUILTIN( vcl_name, gdk_name ) \
        case vcl_name: \
            pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk_name ); \
            break

GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
{
    if ( !m_aCursors[ ePointerStyle ] )
    {
        GdkCursor *pCursor = nullptr;

        switch( ePointerStyle )
        {
            MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR );
            MAP_BUILTIN( PointerStyle::Text, GDK_XTERM );
            MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW );
            MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR );
            MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH );

            MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW );
            MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW );
            MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW );
            MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW );

            MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER );
            MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER );
            MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER );
            MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER );

            MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE );
            MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE );
            MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE );
            MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE );

            MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER );
            MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER );
            MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER );
            MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER );

            MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW );
            MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW );

            MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2 );
            MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2 );
            MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL );

            MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW );
            MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW );

            MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR );

            MAKE_CURSOR( PointerStyle::Null, null );
            MAKE_CURSOR( PointerStyle::Magnify, magnify_ );
            MAKE_CURSOR( PointerStyle::Fill, fill_ );
            MAKE_CURSOR( PointerStyle::MoveData, movedata_ );
            MAKE_CURSOR( PointerStyle::CopyData, copydata_ );
            MAKE_CURSOR( PointerStyle::MoveFile, movefile_ );
            MAKE_CURSOR( PointerStyle::CopyFile, copyfile_ );
            MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_ );
            MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_ );
            MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_ );
            MAKE_CURSOR( PointerStyle::Rotate, rotate_ );
            MAKE_CURSOR( PointerStyle::HShear, hshear_ );
            MAKE_CURSOR( PointerStyle::VShear, vshear_ );
            MAKE_CURSOR( PointerStyle::DrawLine, drawline_ );
            MAKE_CURSOR( PointerStyle::DrawRect, drawrect_ );
            MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_ );
            MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_ );
            MAKE_CURSOR( PointerStyle::DrawArc, drawarc_ );
            MAKE_CURSOR( PointerStyle::DrawPie, drawpie_ );
            MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_ );
            MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_ );
            MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_ );
            MAKE_CURSOR( PointerStyle::DrawText, drawtext_ );
            MAKE_CURSOR( PointerStyle::Mirror, mirror_ );
            MAKE_CURSOR( PointerStyle::Crook, crook_ );
            MAKE_CURSOR( PointerStyle::Crop, crop_ );
            MAKE_CURSOR( PointerStyle::MovePoint, movepoint_ );
            MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_ );
            MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_ );
            MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_ );
            MAKE_CURSOR( PointerStyle::LinkData, linkdata_ );
            MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_ );
            MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_ );
            MAKE_CURSOR( PointerStyle::LinkFile, linkfile_ );
            MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_ );
            MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_ );
            MAKE_CURSOR( PointerStyle::Chart, chart_ );
            MAKE_CURSOR( PointerStyle::Detective, detective_ );
            MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_ );
            MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_ );
            MAKE_CURSOR( PointerStyle::PivotField, pivotfld_ );
            MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_ );
            MAKE_CURSOR( PointerStyle::Chain, chain_ );
            MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_ );
            MAKE_CURSOR( PointerStyle::AutoScrollN, asn_ );
            MAKE_CURSOR( PointerStyle::AutoScrollS, ass_ );
            MAKE_CURSOR( PointerStyle::AutoScrollW, asw_ );
            MAKE_CURSOR( PointerStyle::AutoScrollE, ase_ );
            MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_ );
            MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_ );
            MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_ );
            MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_ );
            MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_ );
            MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_ );
            MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_ );
            MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_ );

            // #i32329#
            MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_ );
            MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_ );
            MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_ );
            MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_ );
            MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_ );

            MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_ );
            MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_ );

        default:
            SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" );
            break;
        }
        if( !pCursor )
            pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );

        m_aCursors[ ePointerStyle ] = pCursor;
    }

    return m_aCursors[ ePointerStyle ];
}

int GtkSalDisplay::CaptureMouse( SalFrame* pSFrame )
{
    GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pSFrame);

    if( !pFrame )
    {
        if( m_pCapture )
            static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( FALSE );
        m_pCapture = nullptr;
        return 0;
    }

    if( m_pCapture )
    {
        if( pFrame == m_pCapture )
            return 1;
        static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( FALSE );
    }

    m_pCapture = pFrame;
    pFrame->grabPointer( TRUE );
    return 1;
}

/**********************************************************************
 * class GtkSalData                                                   *
 **********************************************************************/

GtkSalData::GtkSalData( SalInstance *pInstance )
    : GenericUnixSalData( SAL_DATA_GTK, pInstance )
    , m_aDispatchMutex()
    , m_aDispatchCondition()
    , m_pDocumentFocusListener(nullptr)
{
    m_pUserEvent = nullptr;
}

static XIOErrorHandler aOrigXIOErrorHandler = nullptr;

extern "C" {

static int XIOErrorHdl(Display *)
{
    fprintf(stderr, "X IO Error\n");
    _exit(1);
        // avoid crashes in unrelated threads that still run while atexit
        // handlers are in progress
}

}

GtkSalData::~GtkSalData()
{
    Yield( true, true );
    g_warning ("TESTME: We used to have a stop-timer here, but the central code should do this");

     // sanity check: at this point nobody should be yielding, but wake them
     // up anyway before the condition they're waiting on gets destroyed.
    m_aDispatchCondition.set();

    osl::MutexGuard g( m_aDispatchMutex );
    if (m_pUserEvent)
    {
        g_source_destroy (m_pUserEvent);
        g_source_unref (m_pUserEvent);
        m_pUserEvent = nullptr;
    }
    XSetIOErrorHandler(aOrigXIOErrorHandler);
}

void GtkSalData::Dispose()
{
    deInitNWF();
}

/// Allows events to be processed, returns true if we processed an event.
bool GtkSalData::Yield( bool bWait, bool bHandleAllCurrentEvents )
{
    /* #i33212# only enter g_main_context_iteration in one thread at any one
     * time, else one of them potentially will never end as long as there is
     * another thread in there. Having only one yielding thread actually dispatch
     * fits the vcl event model (see e.g. the generic plugin).
     */
    bool bDispatchThread = false;
    bool bWasEvent = false;
    {
        // release YieldMutex (and re-acquire at block end)
        SolarMutexReleaser aReleaser;
        if( m_aDispatchMutex.tryToAcquire() )
            bDispatchThread = true;
        else if( ! bWait )
        {
            return false; // someone else is waiting already, return
        }

        if( bDispatchThread )
        {
            int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
            gboolean wasOneEvent = TRUE;
            while( nMaxEvents-- && wasOneEvent )
            {
                wasOneEvent = g_main_context_iteration( nullptr, bWait && !bWasEvent );
                if( wasOneEvent )
                    bWasEvent = true;
            }
        }
        else if( bWait )
        {
            /* #i41693# in case the dispatch thread hangs in join
             * for this thread the condition will never be set
             * workaround: timeout of 1 second an emergency exit
             */
            // we are the dispatch thread
            m_aDispatchCondition.reset();
            m_aDispatchCondition.wait( std::chrono::seconds(1) );
        }
    }

    if( bDispatchThread )
    {
        m_aDispatchMutex.release();
        if( bWasEvent )
            m_aDispatchCondition.set(); // trigger non dispatch thread yields
    }

    return bWasEvent;
}

void GtkSalData::Init()
{
    int i;
    SAL_INFO( "vcl.gtk", "GtkMainloop::Init()" );
    XrmInitialize();

    gtk_set_locale();

    /*
     * open connection to X11 Display
     * try in this order:
     *  o  -display command line parameter,
     *  o  $DISPLAY environment variable
     *  o  default display
     */

    GdkDisplay *pGdkDisp = nullptr;

    // is there a -display command line parameter?
    rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
    int nParams = osl_getCommandArgCount();
    OString aDisplay;
    OUString aParam, aBin;
    char** pCmdLineAry = new char*[ nParams+1 ];
    osl_getExecutableFile( &aParam.pData );
    osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData );
    pCmdLineAry[0] = g_strdup( OUStringToOString( aBin, aEnc ).getStr() );
    for (i=0; i<nParams; i++)
    {
        osl_getCommandArg(i, &aParam.pData );
        OString aBParam( OUStringToOString( aParam, aEnc ) );

        if( aParam == "-display" || aParam == "--display" )
        {
            pCmdLineAry[i+1] = g_strdup( "--display" );
            osl_getCommandArg(i+1, &aParam.pData );
            aDisplay = OUStringToOString( aParam, aEnc );
        }
        else
            pCmdLineAry[i+1] = g_strdup( aBParam.getStr() );
    }
    // add executable
    nParams++;

    g_set_application_name(SalGenericSystem::getFrameClassName());

    // Set consistent name of the root accessible
    OUString aAppName = Application::GetAppName();
    if( !aAppName.isEmpty() )
    {
        OString aPrgName = OUStringToOString(aAppName, aEnc);
        g_set_prgname(aPrgName.getStr());
    }

    // init gtk/gdk
    gtk_init_check( &nParams, &pCmdLineAry );
    gdk_error_trap_push();

    for (i = 0; i < nParams; i++ )
        g_free( pCmdLineAry[i] );
    delete [] pCmdLineAry;

#if OSL_DEBUG_LEVEL > 1
    if (g_getenv ("SAL_DEBUG_UPDATES"))
        gdk_window_set_debug_updates (TRUE);
#endif

    pGdkDisp = gdk_display_get_default();
    if ( !pGdkDisp )
    {
        OUString aProgramFileURL;
        osl_getExecutableFile( &aProgramFileURL.pData );
        OUString aProgramSystemPath;
        osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
        OString  aProgramName = OUStringToOString(
                                            aProgramSystemPath,
                                            osl_getThreadTextEncoding() );
        fprintf( stderr, "%s X11 error: Can't open display: %s\n",
                aProgramName.getStr(), aDisplay.getStr());
        fprintf( stderr, "   Set DISPLAY environment variable, use -display option\n");
        fprintf( stderr, "   or check permissions of your X-Server\n");
        fprintf( stderr, "   (See \"man X\" resp. \"man xhost\" for details)\n");
        fflush( stderr );
        exit(0);
    }

    aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl);

    /*
     * if a -display switch was used, we need
     * to set the environment accordingly since
     * the clipboard build another connection
     * to the xserver using $DISPLAY
     */
    OUString envVar("DISPLAY");
    const gchar *name = gdk_display_get_name( pGdkDisp );
    OUString envValue(name, strlen(name), aEnc);
    osl_setEnvironment(envVar.pData, envValue.pData);

    GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp );
    SetDisplay( pDisplay );

    Display *pDisp = gdk_x11_display_get_xdisplay( pGdkDisp );

    gdk_error_trap_push();
    SalI18N_KeyboardExtension *pKbdExtension = new SalI18N_KeyboardExtension( pDisp );
    bool bErrorOccured = gdk_error_trap_pop() != 0;
    gdk_error_trap_push();
    pKbdExtension->UseExtension( bErrorOccured );
    gdk_error_trap_pop();
    GetGtkDisplay()->SetKbdExtension( pKbdExtension );

    // add signal handler to notify screen size changes
    int nScreens = gdk_display_get_n_screens( pGdkDisp );
    for( int n = 0; n < nScreens; n++ )
    {
        GdkScreen *pScreen = gdk_display_get_screen( pGdkDisp, n );
        if( pScreen )
        {
            pDisplay->screenSizeChanged( pScreen );
            pDisplay->monitorsChanged( pScreen );
            g_signal_connect( G_OBJECT(pScreen), "size-changed",
                              G_CALLBACK(signalScreenSizeChanged), pDisplay );
            g_signal_connect( G_OBJECT(pScreen), "monitors-changed",
                              G_CALLBACK(signalMonitorsChanged), GetGtkDisplay() );
        }
    }
}

void GtkSalData::ErrorTrapPush()
{
    gdk_error_trap_push ();
}

bool GtkSalData::ErrorTrapPop( bool )
{
    return gdk_error_trap_pop () != 0;
}

#if !GLIB_CHECK_VERSION(2,32,0)
#define G_SOURCE_REMOVE FALSE
#endif

extern "C" {

    struct SalGtkTimeoutSource {
        GSource      aParent;
        GTimeVal     aFireTime;
        GtkSalTimer *pInstance;
    };

    static void sal_gtk_timeout_defer( SalGtkTimeoutSource *pTSource )
    {
        g_get_current_time( &pTSource->aFireTime );
        g_time_val_add( &pTSource->aFireTime, pTSource->pInstance->m_nTimeoutMS * 1000 );
    }

    static gboolean sal_gtk_timeout_expired( SalGtkTimeoutSource *pTSource,
                                             gint *nTimeoutMS, GTimeVal const *pTimeNow )
    {
        glong nDeltaSec = pTSource->aFireTime.tv_sec - pTimeNow->tv_sec;
        glong nDeltaUSec = pTSource->aFireTime.tv_usec - pTimeNow->tv_usec;
        if( nDeltaSec < 0 || ( nDeltaSec == 0 && nDeltaUSec < 0) )
        {
            *nTimeoutMS = 0;
            return TRUE;
        }
        if( nDeltaUSec < 0 )
        {
            nDeltaUSec += 1000000;
            nDeltaSec -= 1;
        }
        // if the clock changes backwards we need to cope ...
        if( static_cast<unsigned long>(nDeltaSec) > 1 + ( pTSource->pInstance->m_nTimeoutMS / 1000 ) )
        {
            sal_gtk_timeout_defer( pTSource );
            return TRUE;
        }

        *nTimeoutMS = MIN( G_MAXINT, ( nDeltaSec * 1000 + (nDeltaUSec + 999) / 1000 ) );

        return *nTimeoutMS == 0;
    }

    static gboolean sal_gtk_timeout_prepare( GSource *pSource, gint *nTimeoutMS )
    {
        SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);

        GTimeVal aTimeNow;
        g_get_current_time( &aTimeNow );

        return sal_gtk_timeout_expired( pTSource, nTimeoutMS, &aTimeNow );
    }

    static gboolean sal_gtk_timeout_check( GSource *pSource )
    {
        SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);

        GTimeVal aTimeNow;
        g_get_current_time( &aTimeNow );

        return ( pTSource->aFireTime.tv_sec < aTimeNow.tv_sec ||
                 ( pTSource->aFireTime.tv_sec == aTimeNow.tv_sec &&
                   pTSource->aFireTime.tv_usec < aTimeNow.tv_usec ) );
    }

    static gboolean sal_gtk_timeout_dispatch( GSource *pSource, GSourceFunc, gpointer )
    {
        SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);

        if( !pTSource->pInstance )
            return FALSE;

        SolarMutexGuard aGuard;

        sal_gtk_timeout_defer( pTSource );

        ImplSVData* pSVData = ImplGetSVData();
        if( pSVData->maSchedCtx.mpSalTimer )
            pSVData->maSchedCtx.mpSalTimer->CallCallback();

        return G_SOURCE_REMOVE;
    }

    static GSourceFuncs sal_gtk_timeout_funcs =
    {
        sal_gtk_timeout_prepare,
        sal_gtk_timeout_check,
        sal_gtk_timeout_dispatch,
        nullptr, nullptr, nullptr
    };
}

static SalGtkTimeoutSource *
create_sal_gtk_timeout( GtkSalTimer *pTimer )
{
  GSource *pSource = g_source_new( &sal_gtk_timeout_funcs, sizeof( SalGtkTimeoutSource ) );
  SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
  pTSource->pInstance = pTimer;

  // #i36226# timers should be executed with lower priority
  // than XEvents like in generic plugin
  g_source_set_priority( pSource, G_PRIORITY_LOW );
  g_source_set_can_recurse( pSource, TRUE );
  g_source_set_callback( pSource,
                         /* unused dummy */ g_idle_remove_by_data,
                         nullptr, nullptr );
  g_source_attach( pSource, g_main_context_default() );
#ifdef DBG_UTIL
  g_source_set_name( pSource, "VCL timeout source" );
#endif

  sal_gtk_timeout_defer( pTSource );

  return pTSource;
}

GtkSalTimer::GtkSalTimer()
    : m_pTimeout(nullptr)
    , m_nTimeoutMS(0)
{
}

GtkSalTimer::~GtkSalTimer()
{
    GtkInstance *pInstance = static_cast<GtkInstance *>(GetSalData()->m_pInstance);
    pInstance->RemoveTimer();
    Stop();
}

bool GtkSalTimer::Expired()
{
    if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) )
        return false;

    gint nDummy = 0;
    GTimeVal aTimeNow;
    g_get_current_time( &aTimeNow );
    return !!sal_gtk_timeout_expired( m_pTimeout, &nDummy, &aTimeNow);
}

void GtkSalTimer::Start( sal_uInt64 nMS )
{
    // glib is not 64bit safe in this regard.
    assert( nMS <= G_MAXINT );
    if ( nMS > G_MAXINT )
        nMS = G_MAXINT;
    m_nTimeoutMS = nMS; // for restarting
    Stop(); // FIXME: ideally re-use an existing m_pTimeout
    m_pTimeout = create_sal_gtk_timeout( this );
}

void GtkSalTimer::Stop()
{
    if( m_pTimeout )
    {
        g_source_destroy( &m_pTimeout->aParent );
        g_source_unref( &m_pTimeout->aParent );
        m_pTimeout = nullptr;
    }
}

extern "C" {
    static gboolean call_userEventFn( void *data )
    {
        SolarMutexGuard aGuard;
        const SalGenericDisplay *pDisplay = GetGenericUnixSalData()->GetDisplay();
        if ( pDisplay )
        {
            GtkSalDisplay *pThisDisplay = static_cast<GtkSalData *>(data)->GetGtkDisplay();
            assert(static_cast<const SalGenericDisplay *>(pThisDisplay) == pDisplay);
            pThisDisplay->DispatchInternalEvent();
        }
        return TRUE;
    }
}

void GtkSalData::TriggerUserEventProcessing()
{
    if (m_pUserEvent)
        g_main_context_wakeup (nullptr); // really needed ?
    else // nothing pending anyway
    {
        m_pUserEvent = g_idle_source_new();
        // tdf#112890 some dialog don't appear under gtk2, use the same
        // priority for user-events as gtk3 does since tdf#110737
        g_source_set_priority (m_pUserEvent,  G_PRIORITY_HIGH_IDLE + 30);
        g_source_set_can_recurse (m_pUserEvent, TRUE);
        g_source_set_callback (m_pUserEvent, call_userEventFn,
                               static_cast<gpointer>(this), nullptr);
        g_source_attach (m_pUserEvent, g_main_context_default ());
    }
}

void GtkSalData::TriggerAllUserEventsProcessed()
{
    assert( m_pUserEvent );
    g_source_destroy( m_pUserEvent );
    g_source_unref( m_pUserEvent );
    m_pUserEvent = nullptr;
}

void GtkSalDisplay::TriggerUserEventProcessing()
{
    GetGtkSalData()->TriggerUserEventProcessing();
}

void GtkSalDisplay::TriggerAllUserEventsProcessed()
{
    GetGtkSalData()->TriggerAllUserEventsProcessed();
}

GtkWidget* GtkSalDisplay::findGtkWidgetForNativeHandle(sal_uIntPtr hWindow) const
{
    for (auto pFrame : m_aFrames)
    {
        const SystemEnvData* pEnvData = pFrame->GetSystemData();
        if (pEnvData->aWindow == hWindow)
            return GTK_WIDGET(pEnvData->pWidget);
    }
    return nullptr;
}

void GtkSalDisplay::deregisterFrame( SalFrame* pFrame )
{
    if( m_pCapture == pFrame )
    {
        static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( FALSE );
        m_pCapture = nullptr;
    }
    SalGenericDisplay::deregisterFrame( pFrame );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtkinst.cxx b/vcl/unx/gtk/gtkinst.cxx
deleted file mode 100644
index 2a31b87..0000000
--- a/vcl/unx/gtk/gtkinst.cxx
+++ /dev/null
@@ -1,478 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <stack>
#include <string.h>
#include <osl/process.h>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/salobj.h>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/atkbridge.hxx>
#include <unx/gtk/gtkprn.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <headless/svpvd.hxx>
#include <headless/svpbmp.hxx>
#include <vcl/inputtypes.hxx>
#include <unx/genpspgraphics.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <rtl/uri.hxx>

#include <vcl/settings.hxx>

#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>

#include <unx/gtk/gtkprintwrapper.hxx>

extern "C"
{
    #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
    static void GdkThreadsEnter()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsEnter();
    }
    static void GdkThreadsLeave()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsLeave();
    }

    VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
    {
        SAL_INFO(
            "vcl.gtk",
            "create vcl plugin instance with gtk version " << gtk_major_version
                << " " << gtk_minor_version << " " << gtk_micro_version);

#if !GTK_CHECK_VERSION(3,0,0)
        if( gtk_major_version < 2 || // very unlikely sanity check
            ( gtk_major_version == 2 && gtk_minor_version < 4 ) )
        {
            g_warning("require a newer gtk than %d.%d for gdk_threads_set_lock_functions", static_cast<int>(gtk_major_version), gtk_minor_version);
            return nullptr;
        }
#else
        if (gtk_major_version == 3 && gtk_minor_version < 18)
        {
            g_warning("require gtk >= 3.18 for theme expectations");
            return nullptr;
        }
#endif

        // for gtk2 it is always built with X support, so this is always called
        // for gtk3 it is normally built with X and Wayland support, if
        // X is supported GDK_WINDOWING_X11 is defined and this is always
        // called, regardless of if we're running under X or Wayland.
        // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
        // X, because we need to do it earlier than we have a display
#if !GTK_CHECK_VERSION(3,0,0) || defined(GDK_WINDOWING_X11)
        /* #i92121# workaround deadlocks in the X11 implementation
        */
        static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
        /* #i90094#
           from now on we know that an X connection will be
           established, so protect X against itself
        */
        if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
            XInitThreads();
#endif

        // init gdk thread protection
        bool const sup = g_thread_supported();
            // extracted from the 'if' to avoid Clang -Wunreachable-code
        if ( !sup )
            g_thread_init( nullptr );

        gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
        SAL_INFO("vcl.gtk", "Hooked gdk threads locks");

        auto pYieldMutex = std::make_unique<GtkYieldMutex>();

        gdk_threads_init();

        GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
        SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);

        // Create SalData, this does not leak
        new GtkSalData( pInstance );

        return pInstance;
    }
}

#if GTK_CHECK_VERSION(3,0,0)
static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
{
    VclInputFlags nType = VclInputFlags::NONE;
    switch( pEvent->type )
    {
    case GDK_MOTION_NOTIFY:
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
    case GDK_SCROLL:
        nType = VclInputFlags::MOUSE;
        break;
    case GDK_KEY_PRESS:
    // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
        nType = VclInputFlags::KEYBOARD;
        break;
    case GDK_EXPOSE:
        nType = VclInputFlags::PAINT;
        break;
    default:
        nType = VclInputFlags::OTHER;
        break;
    }
    return nType;
}
#endif

GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
#if GTK_CHECK_VERSION(3,0,0)
    : SvpSalInstance( std::move(pMutex) )
#else
    : X11SalInstance( std::move(pMutex) )
#endif
    , m_pTimer(nullptr)
    , bNeedsInit(true)
    , m_pLastCairoFontOptions(nullptr)
{
}

//We want to defer initializing gtk until we are after uno has been
//bootstrapped so we can ask the config what the UI language is so that we can
//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
//UI in a LTR locale
void GtkInstance::AfterAppInit()
{
    EnsureInit();
}

void GtkInstance::EnsureInit()
{
    if (!bNeedsInit)
        return;
    // initialize SalData
    GtkSalData *pSalData = GetGtkSalData();
    pSalData->Init();
    GtkSalData::initNWF();

    InitAtkBridge();

    ImplSVData* pSVData = ImplGetSVData();
#ifdef GTK_TOOLKIT_NAME
    pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
#elif GTK_CHECK_VERSION(3,0,0)
    pSVData->maAppData.mxToolkitName = OUString("gtk3");
#else
    pSVData->maAppData.mxToolkitName = OUString("gtk2");
#endif

    bNeedsInit = false;
}

GtkInstance::~GtkInstance()
{
    assert( nullptr == m_pTimer );
    DeInitAtkBridge();
    ResetLastSeenCairoFontOptions(nullptr);
}

SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
{
    EnsureInit();
    return new GtkSalFrame( pParent, nStyle );
}

SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
{
    EnsureInit();
    return new GtkSalFrame( pParentData );
}

SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
{
    EnsureInit();
#if !GTK_CHECK_VERSION(3,0,0)
    // there is no method to set a visual for a GtkWidget
    // so we need the X11SalObject in that case
    if( pWindowData )
        return X11SalObject::CreateObject( pParent, pWindowData, bShow );
#else
    (void)pWindowData;
    //FIXME: Missing CreateObject functionality ...
#endif

    return new GtkSalObject( static_cast<GtkSalFrame*>(pParent), bShow );
}

extern "C"
{
    typedef void*(* getDefaultFnc)();
    typedef void(* addItemFnc)(void *, const char *);
}

void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
{
    EnsureInit();
    OString sGtkURL;
    rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
    if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
        sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
    else
    {
        //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
        //Decode %XX components
        OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
        //Convert back to system locale encoding
        OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
        //Encode to an escaped ASCII-encoded URI
        gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
        sGtkURL = OString(g_uri);
        g_free(g_uri);
    }
    GtkRecentManager *manager = gtk_recent_manager_get_default ();
    gtk_recent_manager_add_item (manager, sGtkURL.getStr());
}

SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
    ImplJobSetup* pSetupData )
{
    EnsureInit();
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
    mbPrinterInit = true;
    // create and initialize SalInfoPrinter
    PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
    configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
    return pPrinter;
#else
    return Superclass_t::CreateInfoPrinter( pQueueInfo, pSetupData );
#endif
}

std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
    EnsureInit();
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
    mbPrinterInit = true;
    return std::unique_ptr<SalPrinter>(new GtkSalPrinter( pInfoPrinter ));
#else
    return Superclass_t::CreatePrinter( pInfoPrinter );
#endif
}

/*
 * These methods always occur in pairs
 * A ThreadsEnter is followed by a ThreadsLeave
 * We need to queue up the recursive lock count
 * for each pair, so we can accurately restore
 * it later.
 */
thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;

void GtkYieldMutex::ThreadsEnter()
{
    acquire();
    if (!yieldCounts.empty()) {
        auto n = yieldCounts.top();
        yieldCounts.pop();
        assert(n > 0);
        n--;
        if (n > 0)
            acquire(n);
    }
}

void GtkYieldMutex::ThreadsLeave()
{
    assert(m_nCount != 0);
    yieldCounts.push(m_nCount);
    release(true);
}

std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
                                                    long &nDX, long &nDY,
                                                    DeviceFormat eFormat,
                                                    const SystemGraphicsData *pGd )
{
    EnsureInit();
#if GTK_CHECK_VERSION(3,0,0)
    (void) pGd;
    SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
    assert(pSvpSalGraphics);
    std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface()));
    pNew->SetSize( nDX, nDY );
    return pNew;
#else
    GtkSalGraphics *pGtkSalGraphics = dynamic_cast<GtkSalGraphics*>(pG);
    assert(pGtkSalGraphics);
    return CreateX11VirtualDevice(pG, nDX, nDY, eFormat, pGd,
            std::make_unique<GtkSalGraphics>(pGtkSalGraphics->GetGtkFrame(),
                               pGtkSalGraphics->GetGtkWidget(),
                               pGtkSalGraphics->GetScreenNumber()));
#endif
}

std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
{
    EnsureInit();
#if GTK_CHECK_VERSION(3,0,0)
    return SvpSalInstance::CreateSalBitmap();//new SvpSalBitmap();
#else
    return X11SalInstance::CreateSalBitmap();
#endif
}

#ifdef ENABLE_GMENU_INTEGRATION

std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
{
    EnsureInit();
    GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
    pSalMenu->SetMenu( pVCLMenu );
    return std::unique_ptr<SalMenu>(pSalMenu);
}

std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
{
    EnsureInit();
    return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
}

#else // not ENABLE_GMENU_INTEGRATION

std::unique_ptr<SalMenu>     GtkInstance::CreateMenu( bool, Menu* )          { return nullptr; }
std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & ) { return nullptr; }

#endif

SalTimer* GtkInstance::CreateSalTimer()
{
    EnsureInit();
    assert( nullptr == m_pTimer );
    if ( nullptr == m_pTimer )
        m_pTimer = new GtkSalTimer();
    return m_pTimer;
}

void GtkInstance::RemoveTimer ()
{
    EnsureInit();
    m_pTimer = nullptr;
}

bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{
    EnsureInit();
    return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
}

bool GtkInstance::IsTimerExpired()
{
    EnsureInit();
    return (m_pTimer && m_pTimer->Expired());
}

bool GtkInstance::AnyInput( VclInputFlags nType )
{
    EnsureInit();
    if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
        return true;
#if !GTK_CHECK_VERSION(3,0,0)
    bool bRet = X11SalInstance::AnyInput(nType);
#else
    if (!gdk_events_pending())
        return false;

    if (nType == VCL_INPUT_ANY)
        return true;

    bool bRet = false;
    std::stack<GdkEvent*> aEvents;
    GdkEvent *pEvent = nullptr;
    while ((pEvent = gdk_event_get()))
    {
        aEvents.push(pEvent);
        VclInputFlags nEventType = categorizeEvent(pEvent);
        if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
        {
            bRet = true;
            break;
        }
    }

    while (!aEvents.empty())
    {
        pEvent = aEvents.top();
        gdk_event_put(pEvent);
        gdk_event_free(pEvent);
        aEvents.pop();
    }
#endif
    return bRet;
}

std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
{
    EnsureInit();
    return std::make_unique<GenPspGraphics>();
}

std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
GtkInstance::getPrintWrapper() const
{
    if (!m_xPrintWrapper)
        m_xPrintWrapper.reset(new vcl::unx::GtkPrintWrapper);
    return m_xPrintWrapper;
}

const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
{
    const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
    if (!m_pLastCairoFontOptions && pCairoFontOptions)
        m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
    return pCairoFontOptions;
}

const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
{
    return m_pLastCairoFontOptions;
}

void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
{
    if (m_pLastCairoFontOptions)
        cairo_font_options_destroy(m_pLastCairoFontOptions);
    if (pCairoFontOptions)
        m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
    else
        m_pLastCairoFontOptions = nullptr;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtkobject.cxx b/vcl/unx/gtk/gtkobject.cxx
deleted file mode 100644
index 38302c6..0000000
--- a/vcl/unx/gtk/gtkobject.cxx
+++ /dev/null
@@ -1,192 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#ifdef AIX
#define _LINUX_SOURCE_COMPAT
#include <sys/timer.h>
#undef _LINUX_SOURCE_COMPAT
#endif

#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkgdi.hxx>

GtkSalObject::GtkSalObject( GtkSalFrame* pParent, bool bShow )
        : m_pSocket( nullptr ),
          m_pRegion( nullptr )
{
    if( !pParent )
        return;

    // our plug window
    m_pSocket = gtk_drawing_area_new();
    Show( bShow );
    // insert into container
    gtk_fixed_put( pParent->getFixedContainer(),
                   m_pSocket,
                   0, 0 );
    // realize so we can get a window id
    gtk_widget_realize( m_pSocket );

    // make it transparent; some plugins may not insert
    // their own window here but use the socket window itself
    gtk_widget_set_app_paintable( m_pSocket, TRUE );

    // system data
    m_aSystemData.nSize         = sizeof( SystemEnvData );
    SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
    m_aSystemData.pDisplay      = pDisp->GetDisplay();
    m_aSystemData.pVisual       = pDisp->GetVisual(pParent->getXScreenNumber()).GetVisual();
    m_aSystemData.aWindow       = GDK_WINDOW_XWINDOW(widget_get_window(m_pSocket));
    m_aSystemData.aShellWindow  = GDK_WINDOW_XWINDOW(widget_get_window(GTK_WIDGET(pParent->getWindow())));
    m_aSystemData.pSalFrame     = nullptr;
    m_aSystemData.pWidget       = m_pSocket;
    m_aSystemData.nScreen       = pParent->getXScreenNumber().getXScreen();
    m_aSystemData.pToolkit      = "gtk2";
    m_aSystemData.pPlatformName = "xcb";

    g_signal_connect( G_OBJECT(m_pSocket), "button-press-event", G_CALLBACK(signalButton), this );
    g_signal_connect( G_OBJECT(m_pSocket), "button-release-event", G_CALLBACK(signalButton), this );
    g_signal_connect( G_OBJECT(m_pSocket), "focus-in-event", G_CALLBACK(signalFocus), this );
    g_signal_connect( G_OBJECT(m_pSocket), "focus-out-event", G_CALLBACK(signalFocus), this );
    g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );

    // #i59255# necessary due to sync effects with java child windows
    pParent->Flush();

}

GtkSalObject::~GtkSalObject()
{
    if( m_pRegion )
    {
        gdk_region_destroy( m_pRegion );
    }
    if( m_pSocket )
    {
        // remove socket from parent frame's fixed container
        gtk_container_remove( GTK_CONTAINER(gtk_widget_get_parent(m_pSocket)),
                              m_pSocket );
        // get rid of the socket
        // actually the gtk_container_remove should let the ref count
        // of the socket sink to 0 and destroy it (see signalDestroy)
        // this is just a sanity check
        if( m_pSocket )
            gtk_widget_destroy( m_pSocket );
    }
}

void GtkSalObject::ResetClipRegion()
{
    if( m_pSocket )
        gdk_window_shape_combine_region( widget_get_window(m_pSocket), nullptr, 0, 0 );
}

void GtkSalObject::BeginSetClipRegion( sal_uInt32 )
{
    if( m_pRegion )
        gdk_region_destroy( m_pRegion );
    m_pRegion = gdk_region_new();
}

void GtkSalObject::UnionClipRegion( long nX, long nY, long nWidth, long nHeight )
{
    GdkRectangle aRect;
    aRect.x         = nX;
    aRect.y         = nY;
    aRect.width     = nWidth;
    aRect.height    = nHeight;

    gdk_region_union_with_rect( m_pRegion, &aRect );
}

void GtkSalObject::EndSetClipRegion()
{
    if( m_pSocket )
        gdk_window_shape_combine_region( widget_get_window(m_pSocket), m_pRegion, 0, 0 );
}

void GtkSalObject::SetPosSize( long nX, long nY, long nWidth, long nHeight )
{
    if( m_pSocket )
    {
        GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
        gtk_fixed_move( pContainer, m_pSocket, nX, nY );
        gtk_widget_set_size_request( m_pSocket, nWidth, nHeight );
        gtk_container_resize_children( GTK_CONTAINER(pContainer) );
    }
}

void GtkSalObject::Show( bool bVisible )
{
    if( m_pSocket )
    {
        if( bVisible )
            gtk_widget_show( m_pSocket );
        else
            gtk_widget_hide( m_pSocket );
    }
}

const SystemEnvData* GtkSalObject::GetSystemData() const
{
    return &m_aSystemData;
}

gboolean GtkSalObject::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer object )
{
    GtkSalObject* pThis = static_cast<GtkSalObject*>(object);

    if( pEvent->type == GDK_BUTTON_PRESS )
    {
        pThis->CallCallback( SalObjEvent::ToTop );
    }

    return FALSE;
}

gboolean GtkSalObject::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer object )
{
    GtkSalObject* pThis = static_cast<GtkSalObject*>(object);

    pThis->CallCallback( pEvent->in ? SalObjEvent::GetFocus : SalObjEvent::LoseFocus );

    return FALSE;
}

void GtkSalObject::signalDestroy( GtkWidget* pObj, gpointer object )
{
    GtkSalObject* pThis = static_cast<GtkSalObject*>(object);
    if( pObj == pThis->m_pSocket )
    {
        pThis->m_pSocket = nullptr;
    }
}

void GtkSalObject::SetForwardKey( bool bEnable )
{
    if( bEnable )
        gtk_widget_add_events( GTK_WIDGET( m_pSocket ), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE );
    else
        gtk_widget_set_events( GTK_WIDGET( m_pSocket ), ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE) & gtk_widget_get_events( GTK_WIDGET( m_pSocket ) ) );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtkprintwrapper.cxx b/vcl/unx/gtk/gtkprintwrapper.cxx
deleted file mode 100644
index 7e22413..0000000
--- a/vcl/unx/gtk/gtkprintwrapper.cxx
+++ /dev/null
@@ -1,349 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <cassert>

#include <rtl/ustring.hxx>

#include <unx/gtk/gtkprintwrapper.hxx>

namespace vcl
{
namespace unx
{

#if !GTK_CHECK_VERSION(3,0,0)

GtkPrintWrapper::GtkPrintWrapper()
    : m_page_setup_new(nullptr)
    , m_print_job_new(nullptr)
    , m_print_job_send(nullptr)
    , m_print_job_set_source_file(nullptr)
    , m_print_settings_get(nullptr)
    , m_print_settings_get_collate(nullptr)
    , m_print_settings_set_collate(nullptr)
    , m_print_settings_get_n_copies(nullptr)
    , m_print_settings_set_n_copies(nullptr)
    , m_print_settings_get_page_ranges(nullptr)
    , m_print_settings_set_print_pages(nullptr)
    , m_print_unix_dialog_new(nullptr)
    , m_print_unix_dialog_add_custom_tab(nullptr)
    , m_print_unix_dialog_get_selected_printer(nullptr)
    , m_print_unix_dialog_set_manual_capabilities(nullptr)
    , m_print_unix_dialog_get_settings(nullptr)
    , m_print_unix_dialog_set_settings(nullptr)
    , m_print_unix_dialog_set_support_selection(nullptr)
    , m_print_unix_dialog_set_has_selection(nullptr)
{
    impl_load();
}

#else

GtkPrintWrapper::GtkPrintWrapper()
{
}

#endif

GtkPrintWrapper::~GtkPrintWrapper()
{
}

#if !GTK_CHECK_VERSION(3,0,0)

void GtkPrintWrapper::impl_load()
{
    m_aModule.load("libgtk-x11-2.0.so.0");
    if (!m_aModule.is())
        m_aModule.load("libgtk-x11-2.0.so");
    if (!m_aModule.is())
        return;

    m_page_setup_new = reinterpret_cast<page_setup_new_t>(m_aModule.getFunctionSymbol("gtk_page_setup_new"));
    m_print_job_new = reinterpret_cast<print_job_new_t>(m_aModule.getFunctionSymbol("gtk_print_job_new"));
    m_print_job_send = reinterpret_cast<print_job_send_t>(m_aModule.getFunctionSymbol("gtk_print_job_send"));
    m_print_job_set_source_file = reinterpret_cast<print_job_set_source_file_t>(m_aModule.getFunctionSymbol("gtk_print_job_set_source_file"));
    m_print_settings_get = reinterpret_cast<print_settings_get_t>(m_aModule.getFunctionSymbol("gtk_print_settings_get"));
    m_print_settings_get_collate = reinterpret_cast<print_settings_get_collate_t>(m_aModule.getFunctionSymbol("gtk_print_settings_get_collate"));
    m_print_settings_set_collate = reinterpret_cast<print_settings_set_collate_t>(m_aModule.getFunctionSymbol("gtk_print_settings_set_collate"));
    m_print_settings_get_n_copies = reinterpret_cast<print_settings_get_n_copies_t>(m_aModule.getFunctionSymbol("gtk_print_settings_get_n_copies"));
    m_print_settings_set_n_copies = reinterpret_cast<print_settings_set_n_copies_t>(m_aModule.getFunctionSymbol("gtk_print_settings_set_n_copies"));
    m_print_settings_get_page_ranges = reinterpret_cast<print_settings_get_page_ranges_t>(m_aModule.getFunctionSymbol("gtk_print_settings_get_page_ranges"));
    m_print_settings_set_print_pages = reinterpret_cast<print_settings_set_print_pages_t>(m_aModule.getFunctionSymbol("gtk_print_settings_set_print_pages"));
    m_print_unix_dialog_new = reinterpret_cast<print_unix_dialog_new_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_new"));
    m_print_unix_dialog_add_custom_tab = reinterpret_cast<print_unix_dialog_add_custom_tab_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_add_custom_tab"));
    m_print_unix_dialog_get_selected_printer = reinterpret_cast<print_unix_dialog_get_selected_printer_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_get_selected_printer"));
    m_print_unix_dialog_set_manual_capabilities = reinterpret_cast<print_unix_dialog_set_manual_capabilities_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_set_manual_capabilities"));
    m_print_unix_dialog_get_settings = reinterpret_cast<print_unix_dialog_get_settings_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_get_settings"));
    m_print_unix_dialog_set_settings = reinterpret_cast<print_unix_dialog_set_settings_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_set_settings"));
    m_print_unix_dialog_set_support_selection = reinterpret_cast<print_unix_dialog_set_support_selection_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_set_support_selection"));
    m_print_unix_dialog_set_has_selection = reinterpret_cast<print_unix_dialog_set_has_selection_t>(m_aModule.getFunctionSymbol("gtk_print_unix_dialog_set_has_selection"));
}

#endif

bool GtkPrintWrapper::supportsPrinting() const
{
#if !GTK_CHECK_VERSION(3,0,0)
    return
        m_page_setup_new
        && m_print_job_new
        && m_print_job_send
        && m_print_job_set_source_file
        && m_print_settings_get
        && m_print_settings_get_collate
        && m_print_settings_set_collate
        && m_print_settings_get_n_copies
        && m_print_settings_set_n_copies
        && m_print_settings_get_page_ranges
        && m_print_settings_set_print_pages
        && m_print_unix_dialog_new
        && m_print_unix_dialog_add_custom_tab
        && m_print_unix_dialog_get_selected_printer
        && m_print_unix_dialog_set_manual_capabilities
        && m_print_unix_dialog_get_settings
        && m_print_unix_dialog_set_settings
        ;
#else
    (void) this; // loplugin:staticmethods
    return true;
#endif
}

bool GtkPrintWrapper::supportsPrintSelection() const
{
#if !GTK_CHECK_VERSION(3,0,0)
    return
        supportsPrinting()
        && m_print_unix_dialog_set_support_selection
        && m_print_unix_dialog_set_has_selection
        ;
#else
    (void) this; // loplugin:staticmethods
    return true;
#endif
}

GtkPageSetup* GtkPrintWrapper::page_setup_new() const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_page_setup_new);
    return (*m_page_setup_new)();
#else
    (void) this; // loplugin:staticmethods
    return gtk_page_setup_new();
#endif
}

GtkPrintJob* GtkPrintWrapper::print_job_new(const gchar* title, GtkPrinter* printer, GtkPrintSettings* settings, GtkPageSetup* page_setup) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_job_new);
    return (*m_print_job_new)(title, printer, settings, page_setup);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_job_new(title, printer, settings, page_setup);
#endif
}

void GtkPrintWrapper::print_job_send(GtkPrintJob* job, GtkPrintJobCompleteFunc callback, gpointer user_data, GDestroyNotify dnotify) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_job_send);
    (*m_print_job_send)(job, callback, user_data, dnotify);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_job_send(job, callback, user_data, dnotify);
#endif
}

gboolean GtkPrintWrapper::print_job_set_source_file(GtkPrintJob* job, const gchar* filename, GError** error) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_job_set_source_file);
    return (*m_print_job_set_source_file)(job, filename, error);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_job_set_source_file(job, filename, error);
#endif
}

const gchar* GtkPrintWrapper::print_settings_get(GtkPrintSettings* settings, const gchar* key) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_get);
    return (*m_print_settings_get)(settings, key);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get(settings, key);
#endif
}

gboolean GtkPrintWrapper::print_settings_get_collate(GtkPrintSettings* settings) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_get_collate);
    return (*m_print_settings_get_collate)(settings);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get_collate(settings);
#endif
}

void GtkPrintWrapper::print_settings_set_collate(GtkPrintSettings* settings, gboolean collate) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_set_collate);
    (*m_print_settings_set_collate)(settings, collate);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_settings_set_collate(settings, collate);
#endif
}

gint GtkPrintWrapper::print_settings_get_n_copies(GtkPrintSettings* settings) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_get_n_copies);
    return (*m_print_settings_get_n_copies)(settings);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get_n_copies(settings);
#endif
}

void GtkPrintWrapper::print_settings_set_n_copies(GtkPrintSettings* settings, gint num_copies) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_set_n_copies);
    (*m_print_settings_set_n_copies)(settings, num_copies);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_settings_set_n_copies(settings, num_copies);
#endif
}

GtkPageRange* GtkPrintWrapper::print_settings_get_page_ranges(GtkPrintSettings* settings, gint* num_ranges) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_get_page_ranges);
    return (*m_print_settings_get_page_ranges)(settings, num_ranges);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get_page_ranges(settings, num_ranges);
#endif
}

void GtkPrintWrapper::print_settings_set_print_pages(GtkPrintSettings* settings, GtkPrintPages pages) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_settings_set_print_pages);
    (*m_print_settings_set_print_pages)(settings, pages);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_settings_set_print_pages(settings, pages);
#endif
}

GtkWidget* GtkPrintWrapper::print_unix_dialog_new() const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_new);
    return (*m_print_unix_dialog_new)(nullptr, nullptr);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_unix_dialog_new(nullptr, nullptr);
#endif
}

void GtkPrintWrapper::print_unix_dialog_add_custom_tab(GtkPrintUnixDialog* dialog, GtkWidget* child, GtkWidget* tab_label) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_add_custom_tab);
    (*m_print_unix_dialog_add_custom_tab)(dialog, child, tab_label);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_add_custom_tab(dialog, child, tab_label);
#endif
}

GtkPrinter* GtkPrintWrapper::print_unix_dialog_get_selected_printer(GtkPrintUnixDialog* dialog) const
{
    GtkPrinter* pRet = nullptr;
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_get_selected_printer);
    pRet = (*m_print_unix_dialog_get_selected_printer)(dialog);
#else
    (void) this; // loplugin:staticmethods
    pRet = gtk_print_unix_dialog_get_selected_printer(dialog);
#endif
    g_object_ref(G_OBJECT(pRet));
    return pRet;
}

void GtkPrintWrapper::print_unix_dialog_set_manual_capabilities(GtkPrintUnixDialog* dialog, GtkPrintCapabilities capabilities) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_set_manual_capabilities);
    (*m_print_unix_dialog_set_manual_capabilities)(dialog, capabilities);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_manual_capabilities(dialog, capabilities);
#endif
}

GtkPrintSettings* GtkPrintWrapper::print_unix_dialog_get_settings(GtkPrintUnixDialog* dialog) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_get_settings);
    return (*m_print_unix_dialog_get_settings)(dialog);
#else
    (void) this; // loplugin:staticmethods
    return gtk_print_unix_dialog_get_settings(dialog);
#endif
}

void GtkPrintWrapper::print_unix_dialog_set_settings(GtkPrintUnixDialog* dialog, GtkPrintSettings* settings) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_set_settings);
    (*m_print_unix_dialog_set_settings)(dialog, settings);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_settings(dialog, settings);
#endif
}

void GtkPrintWrapper::print_unix_dialog_set_support_selection(GtkPrintUnixDialog* dialog, gboolean support_selection) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_set_support_selection);
    (*m_print_unix_dialog_set_support_selection)(dialog, support_selection);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_support_selection(dialog, support_selection);
#endif
}

void GtkPrintWrapper::print_unix_dialog_set_has_selection(GtkPrintUnixDialog* dialog, gboolean has_selection) const
{
#if !GTK_CHECK_VERSION(3,0,0)
    assert(m_print_unix_dialog_set_has_selection);
    (*m_print_unix_dialog_set_has_selection)(dialog, has_selection);
#else
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_has_selection(dialog, has_selection);
#endif
}

}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtksalframe.cxx b/vcl/unx/gtk/gtksalframe.cxx
deleted file mode 100644
index 2601d7d..0000000
--- a/vcl/unx/gtk/gtksalframe.cxx
+++ /dev/null
@@ -1,3606 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <vcl/keycodes.hxx>
#include <vcl/layout.hxx>
#include <unx/wmadaptor.hxx>
#include <unx/sm.hxx>
#include <unx/salbmp.h>
#include <unx/genprn.h>
#include <unx/geninst.h>
#include <headless/svpgdi.hxx>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <rtl/process.h>
#include <sal/log.hxx>
#include <tools/diagnose_ex.h>
#include <vcl/floatwin.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>
#include <vcl/opengl/OpenGLHelper.hxx>

#include <config_gio.h>

#include <unx/x11/xlimits.hxx>
#if defined ENABLE_GMENU_INTEGRATION // defined in gtksalmenu.hxx above
#  include <unx/gtk/hudawareness.h>
#endif

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xatom.h>

#include <dlfcn.h>
#include <window.h>
#include <strings.hrc>
#include <bitmaps.hlst>
#include <sal/macros.h>

#include <basegfx/range/b2ibox.hxx>
#include <basegfx/vector/b2ivector.hxx>

#include <algorithm>
#include <glib/gprintf.h>

#if OSL_DEBUG_LEVEL > 1
#  include <cstdio>
#endif

#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>

#include <config_folders.h>

#define IS_WIDGET_REALIZED GTK_WIDGET_REALIZED
#define IS_WIDGET_MAPPED   GTK_WIDGET_MAPPED

using namespace com::sun::star;

int GtkSalFrame::m_nFloats = 0;

#if defined ENABLE_GMENU_INTEGRATION
static GDBusConnection* pSessionBus = nullptr;
#endif

sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
{
    sal_uInt16 nCode = 0;
    if( state & GDK_SHIFT_MASK )
        nCode |= KEY_SHIFT;
    if( state & GDK_CONTROL_MASK )
        nCode |= KEY_MOD1;
    if( state & GDK_MOD1_MASK )
        nCode |= KEY_MOD2;

    // Map Meta/Super keys to MOD3 modifier on all Unix systems
    // except macOS
    if ( (state & GDK_META_MASK ) || ( state & GDK_SUPER_MASK ) )
        nCode |= KEY_MOD3;
    return nCode;
}

sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
{
    sal_uInt16 nCode = GetKeyModCode( state );
    if( state & GDK_BUTTON1_MASK )
        nCode |= MOUSE_LEFT;
    if( state & GDK_BUTTON2_MASK )
        nCode |= MOUSE_MIDDLE;
    if( state & GDK_BUTTON3_MASK )
        nCode |= MOUSE_RIGHT;

    return nCode;
}

sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
{
    sal_uInt16 nCode = 0;
    if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
        nCode = KEY_0 + (keyval-GDK_KEY_0);
    else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
        nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
    else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
        nCode = KEY_A + (keyval-GDK_KEY_A );
    else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
        nCode = KEY_A + (keyval-GDK_KEY_a );
    else if( keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26 )
    // KEY_F26 is the last function key known to keycodes.hxx
    {
        if( GetGtkSalData()->GetGtkDisplay()->IsNumLockFromXS() )
        {
            nCode = KEY_F1 + (keyval-GDK_KEY_F1);
        }
        else
        {
            switch( keyval )
            {
                // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
                // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
                // gdk/gdkkeysyms.h, they are unlikely to be generated
                // except possibly by Solaris systems
                // this whole section needs review
                case GDK_KEY_L2:
                    if( GetGtkSalData()->GetGtkDisplay()->GetServerVendor() == vendor_sun )
                        nCode = KEY_REPEAT;
                    else
                        nCode = KEY_F12;
                    break;
                case GDK_KEY_L3:            nCode = KEY_PROPERTIES; break;
                case GDK_KEY_L4:            nCode = KEY_UNDO;       break;
                case GDK_KEY_L6:            nCode = KEY_COPY;       break; // KEY_F16
                case GDK_KEY_L8:            nCode = KEY_PASTE;      break; // KEY_F18
                case GDK_KEY_L10:           nCode = KEY_CUT;        break; // KEY_F20
                default:
                    nCode = KEY_F1 + (keyval-GDK_KEY_F1);           break;
            }
        }
    }
    else
    {
        switch( keyval )
        {
            case GDK_KEY_KP_Down:
            case GDK_KEY_Down:          nCode = KEY_DOWN;       break;
            case GDK_KEY_KP_Up:
            case GDK_KEY_Up:            nCode = KEY_UP;         break;
            case GDK_KEY_KP_Left:
            case GDK_KEY_Left:          nCode = KEY_LEFT;       break;
            case GDK_KEY_KP_Right:
            case GDK_KEY_Right:         nCode = KEY_RIGHT;      break;
            case GDK_KEY_KP_Begin:
            case GDK_KEY_KP_Home:
            case GDK_KEY_Begin:
            case GDK_KEY_Home:          nCode = KEY_HOME;       break;
            case GDK_KEY_KP_End:
            case GDK_KEY_End:           nCode = KEY_END;        break;
            case GDK_KEY_KP_Page_Up:
            case GDK_KEY_Page_Up:       nCode = KEY_PAGEUP;     break;
            case GDK_KEY_KP_Page_Down:
            case GDK_KEY_Page_Down:     nCode = KEY_PAGEDOWN;   break;
            case GDK_KEY_KP_Enter:
            case GDK_KEY_Return:        nCode = KEY_RETURN;     break;
            case GDK_KEY_Escape:        nCode = KEY_ESCAPE;     break;
            case GDK_KEY_ISO_Left_Tab:
            case GDK_KEY_KP_Tab:
            case GDK_KEY_Tab:           nCode = KEY_TAB;        break;
            case GDK_KEY_BackSpace:     nCode = KEY_BACKSPACE;  break;
            case GDK_KEY_KP_Space:
            case GDK_KEY_space:         nCode = KEY_SPACE;      break;
            case GDK_KEY_KP_Insert:
            case GDK_KEY_Insert:        nCode = KEY_INSERT;     break;
            case GDK_KEY_KP_Delete:
            case GDK_KEY_Delete:        nCode = KEY_DELETE;     break;
            case GDK_KEY_plus:
            case GDK_KEY_KP_Add:        nCode = KEY_ADD;        break;
            case GDK_KEY_minus:
            case GDK_KEY_KP_Subtract:   nCode = KEY_SUBTRACT;   break;
            case GDK_KEY_asterisk:
            case GDK_KEY_KP_Multiply:   nCode = KEY_MULTIPLY;   break;
            case GDK_KEY_slash:
            case GDK_KEY_KP_Divide:     nCode = KEY_DIVIDE;     break;
            case GDK_KEY_period:        nCode = KEY_POINT;      break;
            case GDK_KEY_decimalpoint:  nCode = KEY_POINT;      break;
            case GDK_KEY_comma:         nCode = KEY_COMMA;      break;
            case GDK_KEY_less:          nCode = KEY_LESS;       break;
            case GDK_KEY_greater:       nCode = KEY_GREATER;    break;
            case GDK_KEY_KP_Equal:
            case GDK_KEY_equal:         nCode = KEY_EQUAL;      break;
            case GDK_KEY_Find:          nCode = KEY_FIND;       break;
            case GDK_KEY_Menu:          nCode = KEY_CONTEXTMENU;break;
            case GDK_KEY_Help:          nCode = KEY_HELP;       break;
            case GDK_KEY_Undo:          nCode = KEY_UNDO;       break;
            case GDK_KEY_Redo:          nCode = KEY_REPEAT;     break;
            // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
            // but VCL doesn't have a key definition for that
            case GDK_KEY_Cancel:        nCode = KEY_F11;        break;
            case GDK_KEY_KP_Decimal:
            case GDK_KEY_KP_Separator:  nCode = KEY_DECIMAL;    break;
            case GDK_KEY_asciitilde:    nCode = KEY_TILDE;      break;
            case GDK_KEY_leftsinglequotemark:
            case GDK_KEY_quoteleft:    nCode = KEY_QUOTELEFT;    break;
            case GDK_KEY_bracketleft:  nCode = KEY_BRACKETLEFT;  break;
            case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
            case GDK_KEY_semicolon:    nCode = KEY_SEMICOLON;    break;
            case GDK_KEY_quoteright:   nCode = KEY_QUOTERIGHT;   break;
            // some special cases, also see saldisp.cxx
            // - - - - - - - - - - - - -  Apollo - - - - - - - - - - - - - 0x1000
            // These can be found in ap_keysym.h
            case 0x1000FF02: // apXK_Copy
                nCode = KEY_COPY;
                break;
            case 0x1000FF03: // apXK_Cut
                nCode = KEY_CUT;
                break;
            case 0x1000FF04: // apXK_Paste
                nCode = KEY_PASTE;
                break;
            case 0x1000FF14: // apXK_Repeat
                nCode = KEY_REPEAT;
                break;
            // Exit, Save
            // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
            // These can be found in DECkeysym.h
            case 0x1000FF00:
                nCode = KEY_DELETE;
                break;
            // - - - - - - - - - - - - - -  H P  - - - - - - - - - - - - - 0x1000
            // These can be found in HPkeysym.h
            case 0x1000FF73: // hpXK_DeleteChar
                nCode = KEY_DELETE;
                break;
            case 0x1000FF74: // hpXK_BackTab
            case 0x1000FF75: // hpXK_KP_BackTab
                nCode = KEY_TAB;
                break;
            // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
            // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
            // These also can be found in HPkeysym.h
            case 0x1004FF02: // osfXK_Copy
                nCode = KEY_COPY;
                break;
            case 0x1004FF03: // osfXK_Cut
                nCode = KEY_CUT;
                break;
            case 0x1004FF04: // osfXK_Paste
                nCode = KEY_PASTE;
                break;
            case 0x1004FF07: // osfXK_BackTab
                nCode = KEY_TAB;
                break;
            case 0x1004FF08: // osfXK_BackSpace
                nCode = KEY_BACKSPACE;
                break;
            case 0x1004FF1B: // osfXK_Escape
                nCode = KEY_ESCAPE;
                break;
            // Up, Down, Left, Right, PageUp, PageDown
            // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
            // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
            // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
            // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
            // These can be found in Sunkeysym.h
            case 0x1005FF10: // SunXK_F36
                nCode = KEY_F11;
                break;
            case 0x1005FF11: // SunXK_F37
                nCode = KEY_F12;
                break;
            case 0x1005FF70: // SunXK_Props
                nCode = KEY_PROPERTIES;
                break;
            case 0x1005FF71: // SunXK_Front
                nCode = KEY_FRONT;
                break;
            case 0x1005FF72: // SunXK_Copy
                nCode = KEY_COPY;
                break;
            case 0x1005FF73: // SunXK_Open
                nCode = KEY_OPEN;
                break;
            case 0x1005FF74: // SunXK_Paste
                nCode = KEY_PASTE;
                break;
            case 0x1005FF75: // SunXK_Cut
                nCode = KEY_CUT;
                break;
            // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
            // These can be found in XF86keysym.h
            // but more importantly they are also available GTK/Gdk version 3
            // and hence are already provided in gdk/gdkkeysyms.h, and hence
            // in gdk/gdk.h
            case GDK_KEY_Copy:          nCode = KEY_COPY;  break; // 0x1008ff57
            case GDK_KEY_Cut:           nCode = KEY_CUT;   break; // 0x1008ff58
            case GDK_KEY_Open:          nCode = KEY_OPEN;  break; // 0x1008ff6b
            case GDK_KEY_Paste:         nCode = KEY_PASTE; break; // 0x1008ff6d
        }
    }

    return nCode;
}

guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
{
    guint updated_keyval = 0;
    gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
        GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
    return updated_keyval;
}

// F10 means either KEY_F10 or KEY_MENU, which has to be decided
// in the independent part.
struct KeyAlternate
{
    sal_uInt16          nKeyCode;
    sal_Unicode     nCharCode;
    KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
    KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
};

static KeyAlternate
GetAlternateKeyCode( const sal_uInt16 nKeyCode )
{
    KeyAlternate aAlternate;

    switch( nKeyCode )
    {
        case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
        case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
    }

    return aAlternate;
}

bool GtkSalFrame::doKeyCallback( guint state,
                                 guint keyval,
                                 guint16 hardware_keycode,
                                 guint8 group,
                                 sal_Unicode aOrigCode,
                                 bool bDown,
                                 bool bSendRelease
                                 )
{
    SalKeyEvent aEvent;

    aEvent.mnCharCode       = aOrigCode;
    aEvent.mnRepeat         = 0;

    vcl::DeletionListener aDel( this );

    /*
     *  #i42122# translate all keys with Ctrl and/or Alt to group 0 else
     *  shortcuts (e.g. Ctrl-o) will not work but be inserted by the
     *  application
     *
     *  #i52338# do this for all keys that the independent part has no key code
     *  for
     *
     *  fdo#41169 rather than use group 0, detect if there is a group which can
     *  be used to input Latin text and use that if possible
     */
    aEvent.mnCode = GetKeyCode( keyval );
    if( aEvent.mnCode == 0 )
    {
        gint best_group = SAL_MAX_INT32;

        // Try and find Latin layout
        GdkKeymap* keymap = gdk_keymap_get_default();
        GdkKeymapKey *keys;
        gint n_keys;
        if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
        {
            // Find the lowest group that supports Latin layout
            for (gint i = 0; i < n_keys; ++i)
            {
                if (keys[i].level != 0 && keys[i].level != 1)
                    continue;
                best_group = std::min(best_group, keys[i].group);
                if (best_group == 0)
                    break;
            }
            g_free(keys);
        }

        //Unavailable, go with original group then I suppose
        if (best_group == SAL_MAX_INT32)
            best_group = group;

        guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
        aEvent.mnCode = GetKeyCode(updated_keyval);
    }

    aEvent.mnCode   |= GetKeyModCode( state );

    if( bDown )
    {
        bool bHandled = CallCallback( SalEvent::KeyInput, &aEvent );
        // #i46889# copy AlternateKeyCode handling from generic plugin
        if( ! bHandled )
        {
            KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
            if( aAlternate.nKeyCode )
            {
                aEvent.mnCode = aAlternate.nKeyCode;
                if( aAlternate.nCharCode )
                    aEvent.mnCharCode = aAlternate.nCharCode;
                CallCallback( SalEvent::KeyInput, &aEvent );
            }
        }
        if( bSendRelease && ! aDel.isDeleted() )
        {
            CallCallback( SalEvent::KeyUp, &aEvent );
        }
    }
    else
        CallCallback( SalEvent::KeyUp, &aEvent );

    return false;
}

GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
    : m_nXScreen( getDisplay()->GetDefaultXScreen() )
    , m_bGraphics(false)
{
    getDisplay()->registerFrame( this );
    m_bDefaultPos       = true;
    m_bDefaultSize      = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
    m_bWindowIsGtkPlug  = false;
    Init( pParent, nStyle );
}

GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
    : m_nXScreen( getDisplay()->GetDefaultXScreen() )
    , m_bGraphics(false)
{
    getDisplay()->registerFrame( this );
    // permanently ignore errors from our unruly children ...
    GetGenericUnixSalData()->ErrorTrapPush();
    m_bDefaultPos       = true;
    m_bDefaultSize      = true;
    Init( pSysData );
}

#ifdef ENABLE_GMENU_INTEGRATION

static void
gdk_x11_window_set_utf8_property  (GdkWindow *window,
                                   const gchar *name,
                                   const gchar *value)
{
  GdkDisplay* display = gdk_window_get_display (window);

  if (value != nullptr)
    {
      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
                       GDK_WINDOW_XID (window),
                       gdk_x11_get_xatom_by_name_for_display (display, name),
                       gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8,
                       PropModeReplace, reinterpret_cast<guchar const *>(value), strlen (value));
    }
  else
    {
      XDeleteProperty (GDK_DISPLAY_XDISPLAY (display),
                       GDK_WINDOW_XID (window),
                       gdk_x11_get_xatom_by_name_for_display (display, name));
    }
}

// AppMenu watch functions.

static void ObjectDestroyedNotify( gpointer data )
{
    if ( data ) {
        g_object_unref( data );
    }
}

static void hud_activated( gboolean hud_active, gpointer user_data )
{
    if ( hud_active )
    {
        SolarMutexGuard aGuard;
        GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
        GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );

        if ( pSalMenu )
            pSalMenu->UpdateFull();
    }
}

static gboolean ensure_dbus_setup( gpointer data )
{
    GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( data );
    GdkWindow* gdkWindow = widget_get_window( pSalFrame->getWindow() );

    if ( gdkWindow != nullptr && g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) == nullptr )
    {
        // Get a DBus session connection.
        if(!pSessionBus)
            pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
        if( !pSessionBus )
        {
            return FALSE;
        }

        // Create menu model and action group attached to this frame.
        GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
        GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());

        // Generate menu paths.
        ::Window windowId = GDK_WINDOW_XID( gdkWindow );
        gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
        gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );

        // Set window properties.
        g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
        g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );

        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );

        // Publish the menu model and the action group.
        SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
        pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
        SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
        pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
        pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );

        g_free( aDBusWindowPath );
        g_free( aDBusMenubarPath );
    }

    return FALSE;
}

void on_registrar_available( GDBusConnection * /*connection*/,
                             const gchar     * /*name*/,
                             const gchar     * /*name_owner*/,
                             gpointer         user_data )
{
    SolarMutexGuard aGuard;

    GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );

    SalMenu* pSalMenu = pSalFrame->GetMenu();

    if ( pSalMenu != nullptr )
    {
        GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
        pGtkSalMenu->EnableUnity(true);
    }
}

// This is called when the registrar becomes unavailable. It shows the menubar.
void on_registrar_unavailable( GDBusConnection * /*connection*/,
                               const gchar     * /*name*/,
                               gpointer         user_data )
{
    SolarMutexGuard aGuard;

    SAL_INFO("vcl.unity", "on_registrar_unavailable");

    //pSessionBus = NULL;
    GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );

    SalMenu* pSalMenu = pSalFrame->GetMenu();

    if ( pSalMenu ) {
        GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
        pGtkSalMenu->EnableUnity(false);
    }
}
#endif

void GtkSalFrame::EnsureAppMenuWatch()
{
#ifdef ENABLE_GMENU_INTEGRATION
    if ( !m_nWatcherId )
    {
        // Get a DBus session connection.
        if ( pSessionBus == nullptr )
        {
            pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );

            if ( pSessionBus == nullptr )
                return;
        }

        // Publish the menu only if AppMenu registrar is available.
        m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
                                                       "com.canonical.AppMenu.Registrar",
                                                       G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                       on_registrar_available,
                                                       on_registrar_unavailable,
                                                       this,
                                                       nullptr );
    }
#else
    (void) this; // loplugin:staticmethods
#endif
}

void GtkSalFrame::InvalidateGraphics()
{
    if( m_pGraphics )
    {
        m_pGraphics->DeInit();
        m_pGraphics->SetWindow(nullptr);
        m_pGraphics.reset();
    }
}

GtkSalFrame::~GtkSalFrame()
{
    InvalidateGraphics();

    if( m_pParent )
        m_pParent->m_aChildren.remove( this );

    getDisplay()->deregisterFrame( this );

    if( m_pRegion )
    {
        gdk_region_destroy( m_pRegion );
    }

    m_pIMHandler.reset();

    GtkWidget *pEventWidget = getMouseEventWidget();
    for (auto handler_id : m_aMouseSignalIds)
        g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
    if( m_pFixedContainer )
        gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
    if( m_pEventBox )
        gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
    {
        SolarMutexGuard aGuard;
#if defined ENABLE_GMENU_INTEGRATION
        if(m_nWatcherId)
            g_bus_unwatch_name(m_nWatcherId);
#endif
        if( m_pWindow )
        {
            g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );

#if defined ENABLE_GMENU_INTEGRATION
            if ( pSessionBus )
            {
                if ( m_nHudAwarenessId )
                    hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
                if ( m_nMenuExportId )
                    g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
                if ( m_nActionGroupExportId )
                    g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
            }
#endif
            gtk_widget_destroy( m_pWindow );
        }
    }
    if( m_pForeignParent )
        g_object_unref( G_OBJECT( m_pForeignParent ) );
    if( m_pForeignTopLevel )
        g_object_unref( G_OBJECT( m_pForeignTopLevel) );
}

void GtkSalFrame::moveWindow( long nX, long nY )
{
    if( isChild( false ) )
    {
        if( m_pParent )
            gtk_fixed_move( m_pParent->getFixedContainer(),
                            m_pWindow,
                            nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY );
    }
    else
        gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
}

void GtkSalFrame::widget_set_size_request(long nWidth, long nHeight)
{
    gtk_widget_set_size_request(m_pWindow, nWidth, nHeight );
}

void GtkSalFrame::window_resize(long nWidth, long nHeight)
{
    gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
}

void GtkSalFrame::resizeWindow( long nWidth, long nHeight )
{
    if( isChild( false ) )
    {
        widget_set_size_request(nWidth, nHeight);
    }
    else if( ! isChild( true, false ) )
        window_resize(nWidth, nHeight);
}

// tdf#124694 GtkFixed takes the max size of all its children as its
// preferred size, causing it to not clip its child, but grow instead.

static void
ooo_fixed_size_request(GtkWidget*, GtkRequisition* req)
{
    req->width = 0;
    req->height = 0;
}

static void
ooo_fixed_class_init(GtkFixedClass *klass)
{
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    widget_class->size_request = ooo_fixed_size_request;
}

/*
 * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
 * utilize GAIL for the toplevel window and toolkit implementation incl.
 * key event listener support ..
 */

GType
ooo_fixed_get_type()
{
    static GType type = 0;

    if (!type) {
        static const GTypeInfo tinfo =
        {
            sizeof (GtkFixedClass),
            nullptr,      /* base init */
            nullptr,  /* base finalize */
            reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
            nullptr, /* class finalize */
            nullptr,                      /* class data */
            sizeof (GtkFixed),         /* instance size */
            0,                         /* nb preallocs */
            nullptr,  /* instance init */
            nullptr                       /* value table */
        };

        type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
                                       &tinfo, GTypeFlags(0));
    }

    return type;
}

void GtkSalFrame::updateScreenNumber()
{
    int nScreen = 0;
    GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
    if( pScreen )
        nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
    maGeometry.nDisplayScreenNumber = nScreen;
}

GtkWidget *GtkSalFrame::getMouseEventWidget() const
{
    return m_pWindow;
}

void GtkSalFrame::InitCommon()
{
    m_pEventBox = nullptr;
    // add the fixed container child,
    // fixed is needed since we have to position plugin windows
    m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
    gtk_container_add( GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pFixedContainer) );

    GtkWidget *pEventWidget = getMouseEventWidget();

    gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
    gtk_widget_set_double_buffered(GTK_WIDGET(m_pFixedContainer), false);
    gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);


    // connect signals
    g_signal_connect( G_OBJECT(m_pWindow), "style-set", G_CALLBACK(signalStyleSet), this );
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
    g_signal_connect( G_OBJECT(m_pFixedContainer), "expose-event", G_CALLBACK(signalExpose), this );
    g_signal_connect( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
    g_signal_connect( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
    g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
    g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
    g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
    g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
    g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
    g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
    g_signal_connect( G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this );
    g_signal_connect( G_OBJECT(m_pWindow), "scroll-event", G_CALLBACK(signalScroll), this );
    g_signal_connect( G_OBJECT(m_pWindow), "leave-notify-event", G_CALLBACK(signalCrossing), this );
    g_signal_connect( G_OBJECT(m_pWindow), "enter-notify-event", G_CALLBACK(signalCrossing), this );
    g_signal_connect( G_OBJECT(m_pWindow), "visibility-notify-event", G_CALLBACK(signalVisibility), this );
    g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );

    // init members
    m_pCurrentCursor    = nullptr;
    m_nKeyModifiers     = ModKeyFlags::NONE;
    m_bFullscreen       = false;
    m_bSpanMonitorsWhenFullscreen = false;
    m_nState            = GDK_WINDOW_STATE_WITHDRAWN;
    m_pIMHandler        = nullptr;
    m_pRegion           = nullptr;
    m_ePointerStyle     = static_cast<PointerStyle>(0xffff);
    m_bSetFocusOnMap    = false;
    m_pSalMenu          = nullptr;
    m_nWatcherId        = 0;
    m_nMenuExportId     = 0;
    m_nActionGroupExportId = 0;
    m_nHudAwarenessId   = 0;

    gtk_widget_add_events( m_pWindow,
                           GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
                           GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
                           GDK_VISIBILITY_NOTIFY_MASK | GDK_SCROLL_MASK
                           );

    // show the widgets
    gtk_widget_show_all( GTK_WIDGET(m_pFixedContainer) );

    // realize the window, we need an XWindow id
    gtk_widget_realize( m_pWindow );

    //system data
    m_aSystemData.nSize         = sizeof( SystemEnvData );
    GtkSalDisplay* pDisp = GetGtkSalData()->GetGtkDisplay();
    m_aSystemData.pDisplay      = pDisp->GetDisplay();
    m_aSystemData.pVisual       = pDisp->GetVisual( m_nXScreen ).GetVisual();
    m_aSystemData.aWindow       = widget_get_xid(m_pWindow);
    m_aSystemData.aShellWindow  = m_aSystemData.aWindow;
    m_aSystemData.pSalFrame     = this;
    m_aSystemData.pWidget       = m_pWindow;
    m_aSystemData.nScreen       = m_nXScreen.getXScreen();
    m_aSystemData.pToolkit      = "gtk2";
    m_aSystemData.pPlatformName = "xcb";

    m_bGraphics = false;
    m_pGraphics = nullptr;

    // fake an initial geometry, gets updated via configure event or SetPosSize
    if( m_bDefaultPos || m_bDefaultSize )
    {
        Size aDefSize = calcDefaultSize();
        maGeometry.nX                   = -1;
        maGeometry.nY                   = -1;
        maGeometry.nWidth               = aDefSize.Width();
        maGeometry.nHeight              = aDefSize.Height();
        if( m_pParent )
        {
            // approximation
            maGeometry.nTopDecoration       = m_pParent->maGeometry.nTopDecoration;
            maGeometry.nBottomDecoration    = m_pParent->maGeometry.nBottomDecoration;
            maGeometry.nLeftDecoration      = m_pParent->maGeometry.nLeftDecoration;
            maGeometry.nRightDecoration     = m_pParent->maGeometry.nRightDecoration;
        }
        else
        {
            maGeometry.nTopDecoration       = 0;
            maGeometry.nBottomDecoration    = 0;
            maGeometry.nLeftDecoration      = 0;
            maGeometry.nRightDecoration     = 0;
        }
    }
    else
    {
        resizeWindow( maGeometry.nWidth, maGeometry.nHeight );
        moveWindow( maGeometry.nX, maGeometry.nY );
    }
    updateScreenNumber();

    SetIcon(SV_ICON_ID_OFFICE);

    m_nWorkArea = pDisp->getWMAdaptor()->getCurrentWorkArea();
    /* #i64117# gtk sets a nice background pixmap
    *  but we actually don't really want that, so save
    *  some time on the Xserver as well as prevent
    *  some paint issues
    */
    XSetWindowBackgroundPixmap( getDisplay()->GetDisplay(),
                                widget_get_xid(m_pWindow),
                                None );
}

static void lcl_set_accept_focus( GtkWindow* pWindow )
{
    if (GetGtkSalData()->GetGtkDisplay()->getWMAdaptor()->getWindowManagerName().startsWith("Metacity"))
    {
       /*  Metacity considers a toolbar type window as should not
        *  have focus on mapping, yet it believes it should unfocus
        *  the parent window... So convince Metacity to not do so,
        *  by disabling the focus until the window is mapped. We
        *  will restore the focus later in the map signal.
        */
        gtk_window_set_accept_focus( pWindow, false );

        /*  remove WM_TAKE_FOCUS protocol; this would usually be the
         *  right thing, but gtk handles it internally whereas we
         *  want to handle it ourselves (as to sometimes not get
         *  the focus)
         */
        Display* pDisplay = GetGtkSalData()->GetGtkDisplay()->GetDisplay();
        ::Window aWindow = widget_get_xid(GTK_WIDGET(pWindow));
        Atom* pProtocols = nullptr;
        int nProtocols = 0;
        XGetWMProtocols( pDisplay,
                         aWindow,
                         &pProtocols, &nProtocols );
        if( pProtocols )
        {
            bool bSet = false;
            Atom nTakeFocus = XInternAtom( pDisplay, "WM_TAKE_FOCUS", True );
            if( nTakeFocus )
            {
                for( int i = 0; i < nProtocols; i++ )
                {
                    if( pProtocols[i] == nTakeFocus )
                    {
                        for( int n = i; n < nProtocols-1; n++ )
                            pProtocols[n] = pProtocols[n+1];
                        nProtocols--;
                        i--;
                        bSet = true;
                    }
                }
            }
            if( bSet )
                XSetWMProtocols( pDisplay, aWindow, pProtocols, nProtocols );
            XFree( pProtocols );
        }
    }
    else
    {
        // Only needed for Compiz. The toolbar type hint seems to be enough for other WMs.
        gtk_window_set_focus_on_map( pWindow, false );
    }
}

GtkSalFrame *GtkSalFrame::getFromWindow( GtkWindow *pWindow )
{
    return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
}

void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
{
    if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
    {
        nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
        nStyle &= ~SalFrameStyleFlags::FLOAT;
    }

    m_pParent = static_cast<GtkSalFrame*>(pParent);
    m_pForeignParent = nullptr;
    m_aForeignParentWindow = None;
    m_pForeignTopLevel = nullptr;
    m_aForeignTopLevelWindow = None;
    m_nStyle = nStyle;

    GtkWindowType eWinType = (  (nStyle & SalFrameStyleFlags::FLOAT) &&
                              ! (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
                              )
        ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL;

    if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
    {
        m_pWindow = gtk_event_box_new();
        if( m_pParent )
        {
            // insert into container
            gtk_fixed_put( m_pParent->getFixedContainer(),
                           m_pWindow, 0, 0 );

        }
    }
    else
    {
        m_pWindow = gtk_widget_new( GTK_TYPE_WINDOW, "type", eWinType,
                                    "visible", FALSE, nullptr );
    }
    g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
    g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));

    // force wm class hint
    if (!isChild())
    {
        if (m_pParent)
            m_sWMClass = m_pParent->m_sWMClass;
        updateWMClass();
    }

    if( m_pParent && m_pParent->m_pWindow && ! isChild() )
        gtk_window_set_screen( GTK_WINDOW(m_pWindow), gtk_window_get_screen( GTK_WINDOW(m_pParent->m_pWindow) ) );

    if (m_pParent)
    {
        if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
            gtk_window_set_transient_for( GTK_WINDOW(m_pWindow), GTK_WINDOW(m_pParent->m_pWindow) );
        m_pParent->m_aChildren.push_back( this );
    }

    InitCommon();

    // set window type
    bool bDecoHandling =
        ! isChild() &&
        ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
          (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );

    if( bDecoHandling )
    {
        GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
        if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
            eType = GDK_WINDOW_TYPE_HINT_DIALOG;
        if( nStyle & SalFrameStyleFlags::INTRO )
        {
            gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
            eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
        }
        else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
        {
            eType = GDK_WINDOW_TYPE_HINT_UTILITY;
            gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
        }
        else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
        {
            eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
            lcl_set_accept_focus( GTK_WINDOW(m_pWindow) );
            gtk_window_set_decorated( GTK_WINDOW(m_pWindow), false );
        }
        if( (nStyle & SalFrameStyleFlags::PARTIAL_FULLSCREEN )
            && getDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
        {
            eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
            gtk_window_set_keep_above( GTK_WINDOW(m_pWindow), true );
        }
        gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
        gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
        gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
    }
    else if( nStyle & SalFrameStyleFlags::FLOAT )
        gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );

#ifdef ENABLE_GMENU_INTEGRATION
    if( eWinType == GTK_WINDOW_TOPLEVEL )
    {
        // Enable DBus native menu if available.
        ensure_dbus_setup( this );
    }
#endif
}

GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow(GdkNativeWindow aWindow)
{
    ::Window aRoot, aParent;
    ::Window* pChildren;
    unsigned int nChildren;
    bool bBreak = false;
    do
    {
        pChildren = nullptr;
        nChildren = 0;
        aParent = aRoot = None;
        XQueryTree( getDisplay()->GetDisplay(), aWindow,
                    &aRoot, &aParent, &pChildren, &nChildren );
        XFree( pChildren );
        if( aParent != aRoot )
            aWindow = aParent;
        int nCount = 0;
        Atom* pProps = XListProperties( getDisplay()->GetDisplay(),
                                        aWindow,
                                        &nCount );
        for( int i = 0; i < nCount && ! bBreak; ++i )
            bBreak = (pProps[i] == XA_WM_HINTS);
        if( pProps )
            XFree( pProps );
    } while( aParent != aRoot && ! bBreak );

    return aWindow;
}

void GtkSalFrame::Init( SystemParentData* pSysData )
{
    m_pParent = nullptr;
    m_aForeignParentWindow = pSysData->aWindow;
    m_pForeignParent = nullptr;
    m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
    m_pForeignTopLevel = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
    gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );

    if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
    {
        m_pWindow = gtk_plug_new( pSysData->aWindow );
        m_bWindowIsGtkPlug  = true;
        widget_set_can_default( m_pWindow, true );
        widget_set_can_focus( m_pWindow, true );
        gtk_widget_set_sensitive( m_pWindow, true );
    }
    else
    {
        m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
        m_bWindowIsGtkPlug  = false;
    }
    m_nStyle = SalFrameStyleFlags::PLUG;
    InitCommon();

    m_pForeignParent = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
    gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );

    int x_ret, y_ret;
    unsigned int w, h, bw, d;
    ::Window aRoot;
    XGetGeometry( getDisplay()->GetDisplay(), pSysData->aWindow,
                  &aRoot, &x_ret, &y_ret, &w, &h, &bw, &d );
    maGeometry.nWidth   = w;
    maGeometry.nHeight  = h;
    window_resize(w, h);
    gtk_window_move( GTK_WINDOW(m_pWindow), 0, 0 );
    if( ! m_bWindowIsGtkPlug )
    {
        XReparentWindow( getDisplay()->GetDisplay(),
                         widget_get_xid(m_pWindow),
                         static_cast<::Window>(pSysData->aWindow),
                         0, 0 );
    }
}

void GtkSalFrame::askForXEmbedFocus( sal_Int32 i_nTimeCode )
{
    XEvent aEvent;

    memset( &aEvent, 0, sizeof(aEvent) );
    aEvent.xclient.window = m_aForeignParentWindow;
    aEvent.xclient.type = ClientMessage;
    aEvent.xclient.message_type = getDisplay()->getWMAdaptor()->getAtom( vcl_sal::WMAdaptor::XEMBED );
    aEvent.xclient.format = 32;
    aEvent.xclient.data.l[0] = i_nTimeCode ? i_nTimeCode : CurrentTime;
    aEvent.xclient.data.l[1] = 3; // XEMBED_REQUEST_FOCUS
    aEvent.xclient.data.l[2] = 0;
    aEvent.xclient.data.l[3] = 0;
    aEvent.xclient.data.l[4] = 0;

    GetGenericUnixSalData()->ErrorTrapPush();
    XSendEvent( getDisplay()->GetDisplay(),
                m_aForeignParentWindow,
                False, NoEventMask, &aEvent );
    GetGenericUnixSalData()->ErrorTrapPop();
}

void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
{
}

SalGraphics* GtkSalFrame::AcquireGraphics()
{
    if( m_bGraphics )
        return nullptr;

    if( !m_pGraphics )
    {
        m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow, m_nXScreen ) );
    }
    m_bGraphics = true;
    return m_pGraphics.get();
}

void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
{
    (void) pGraphics;
    assert( pGraphics == m_pGraphics.get() );
    m_bGraphics = false;
}

bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
{
    getDisplay()->SendInternalEvent( this, pData.release() );
    return true;
}

void GtkSalFrame::SetTitle( const OUString& rTitle )
{
    m_aTitle = rTitle;
    if( m_pWindow && ! isChild() )
        gtk_window_set_title( GTK_WINDOW(m_pWindow), OUStringToOString( rTitle, RTL_TEXTENCODING_UTF8 ).getStr() );
}

void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
{
    if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
        || ! m_pWindow )
        return;

    gchar* appicon;

    if (nIcon == SV_ICON_ID_TEXT)
        appicon = g_strdup ("libreoffice-writer");
    else if (nIcon == SV_ICON_ID_SPREADSHEET)
        appicon = g_strdup ("libreoffice-calc");
    else if (nIcon == SV_ICON_ID_DRAWING)
        appicon = g_strdup ("libreoffice-draw");
    else if (nIcon == SV_ICON_ID_PRESENTATION)
        appicon = g_strdup ("libreoffice-impress");
    else if (nIcon == SV_ICON_ID_DATABASE)
        appicon = g_strdup ("libreoffice-base");
    else if (nIcon == SV_ICON_ID_FORMULA)
        appicon = g_strdup ("libreoffice-math");
    else
        appicon = g_strdup ("libreoffice-startcenter");

    gtk_window_set_icon_name (GTK_WINDOW (m_pWindow), appicon);
}

void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
{
    m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
}

SalMenu* GtkSalFrame::GetMenu()
{
    return m_pSalMenu;
}

void GtkSalFrame::DrawMenuBar()
{
}

void GtkSalFrame::Center()
{
    long nX, nY;

    if( m_pParent )
    {
        nX = (static_cast<long>(m_pParent->maGeometry.nWidth) - static_cast<long>(maGeometry.nWidth))/2;
        nY = (static_cast<long>(m_pParent->maGeometry.nHeight) - static_cast<long>(maGeometry.nHeight))/2;
    }
    else
    {
        GdkScreen *pScreen = nullptr;
        gint px, py;
        GdkModifierType nMask;
        gdk_display_get_pointer( getGdkDisplay(), &pScreen, &px, &py, &nMask );
        if( !pScreen )
            pScreen = gtk_widget_get_screen( m_pWindow );

        gint nMonitor;
        nMonitor = gdk_screen_get_monitor_at_point( pScreen, px, py );

        GdkRectangle aMonitor;
        gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aMonitor );

        nX = aMonitor.x + (aMonitor.width - static_cast<long>(maGeometry.nWidth))/2;
        nY = aMonitor.y + (aMonitor.height - static_cast<long>(maGeometry.nHeight))/2;
    }
    SetPosSize( nX, nY, 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
}

Size GtkSalFrame::calcDefaultSize()
{
    return bestmaxFrameSizeForScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
}

void GtkSalFrame::SetDefaultSize()
{
    Size aDefSize = calcDefaultSize();

    SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
                SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );

    if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
        gtk_window_maximize( GTK_WINDOW(m_pWindow) );
}

static void initClientId()
{
    static bool bOnce = false;
    if (!bOnce)
    {
        bOnce = true;
        const OString& rID = SessionManagerClient::getSessionID();
        if (!rID.isEmpty())
            gdk_set_sm_client_id(rID.getStr());
    }
}

void GtkSalFrame::Show( bool bVisible, bool bNoActivate )
{
    if( m_pWindow )
    {
        if( m_pParent && (m_pParent->m_nStyle & SalFrameStyleFlags::PARTIAL_FULLSCREEN)
            && getDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
            gtk_window_set_keep_above( GTK_WINDOW(m_pWindow), bVisible );
        if( bVisible )
        {
            initClientId();
            getDisplay()->startupNotificationCompleted();

            if( m_bDefaultPos )
                Center();
            if( m_bDefaultSize )
                SetDefaultSize();
            setMinMaxSize();

            // #i45160# switch to desktop where a dialog with parent will appear
            if( m_pParent && m_pParent->m_nWorkArea != m_nWorkArea && IS_WIDGET_MAPPED(m_pParent->m_pWindow) )
                getDisplay()->getWMAdaptor()->switchToWorkArea( m_pParent->m_nWorkArea );

            if( isFloatGrabWindow() &&
                m_pParent &&
                m_nFloats == 0 &&
                ! getDisplay()->GetCaptureFrame() )
            {
                /* #i63086#
                 * outsmart Metacity's "focus:mouse" mode
                 * which insists on taking the focus from the document
                 * to the new float. Grab focus to parent frame BEFORE
                 * showing the float (cannot grab it to the float
                 * before show).
                 */
                 m_pParent->grabPointer( true, true );
            }

            if( ! bNoActivate && (m_nStyle & SalFrameStyleFlags::TOOLWINDOW) )
                m_bSetFocusOnMap = true;

            gtk_widget_show( m_pWindow );

            if( isFloatGrabWindow() )
            {
                m_nFloats++;
                if( ! getDisplay()->GetCaptureFrame() && m_nFloats == 1 )
                {
                    grabPointer(true, true);
                    GtkSalFrame *pKeyboardFrame = m_pParent ? m_pParent : this;
                    pKeyboardFrame->grabKeyboard(true);
                }
                // #i44068# reset parent's IM context
                if( m_pParent )
                    m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
            }
            if( m_bWindowIsGtkPlug )
                askForXEmbedFocus( 0 );
        }
        else
        {
            if( isFloatGrabWindow() )
            {
                m_nFloats--;
                if( ! getDisplay()->GetCaptureFrame() && m_nFloats == 0)
                {
                    GtkSalFrame *pKeyboardFrame = m_pParent ? m_pParent : this;
                    pKeyboardFrame->grabKeyboard(false);
                    grabPointer(false);
                }
            }
            gtk_widget_hide( m_pWindow );
            if( m_pIMHandler )
                m_pIMHandler->focusChanged( false );
            // flush here; there may be a very seldom race between
            // the display connection used for clipboard and our connection
            Flush();
        }
        CallCallback( SalEvent::Resize, nullptr );
        TriggerPaintEvent();
    }
}

void GtkSalFrame::setMinMaxSize()
{
    /*  #i34504# metacity (and possibly others) do not treat
     *  _NET_WM_STATE_FULLSCREEN and max_width/height independently;
     *  whether they should is undefined. So don't set the max size hint
     *  for a full screen window.
    */
    if( m_pWindow && ! isChild() )
    {
        GdkGeometry aGeo;
        int aHints = 0;
        if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
        {
            if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
            {
                aGeo.min_width  = m_aMinSize.Width();
                aGeo.min_height = m_aMinSize.Height();
                aHints |= GDK_HINT_MIN_SIZE;
            }
            if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
            {
                aGeo.max_width  = m_aMaxSize.Width();
                aGeo.max_height = m_aMaxSize.Height();
                aHints |= GDK_HINT_MAX_SIZE;
            }
        }
        else
        {
            if( ! m_bFullscreen )
            {
                aGeo.min_width = maGeometry.nWidth;
                aGeo.min_height = maGeometry.nHeight;
                aHints |= GDK_HINT_MIN_SIZE;

                aGeo.max_width = maGeometry.nWidth;
                aGeo.max_height = maGeometry.nHeight;
                aHints |= GDK_HINT_MAX_SIZE;
            }
        }
        if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
        {
            aGeo.max_width = m_aMaxSize.Width();
            aGeo.max_height = m_aMaxSize.Height();
            aHints |= GDK_HINT_MAX_SIZE;
        }
        if( aHints )
        {
            gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
                                           nullptr,
                                           &aGeo,
                                           GdkWindowHints( aHints ) );
        }
    }
}

void GtkSalFrame::SetMaxClientSize( long nWidth, long nHeight )
{
    if( ! isChild() )
    {
        m_aMaxSize = Size( nWidth, nHeight );
        // Show does a setMinMaxSize
        if( IS_WIDGET_MAPPED( m_pWindow ) )
            setMinMaxSize();
    }
}
void GtkSalFrame::SetMinClientSize( long nWidth, long nHeight )
{
    if( ! isChild() )
    {
        m_aMinSize = Size( nWidth, nHeight );
        if( m_pWindow )
        {
            widget_set_size_request(nWidth, nHeight );
            // Show does a setMinMaxSize
            if( IS_WIDGET_MAPPED( m_pWindow ) )
                setMinMaxSize();
        }
    }
}

void GtkSalFrame::AllocateFrame()
{
}

void GtkSalFrame::SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags )
{
    if( !m_pWindow || isChild( true, false ) )
        return;

    bool bSized = false, bMoved = false;

    if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
        (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
            )
    {
        m_bDefaultSize = false;

        if( static_cast<unsigned long>(nWidth) != maGeometry.nWidth || static_cast<unsigned long>(nHeight) != maGeometry.nHeight )
            bSized = true;
        maGeometry.nWidth   = nWidth;
        maGeometry.nHeight  = nHeight;

        if( isChild( false ) )
            widget_set_size_request(nWidth, nHeight);
        else if( ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) )
            window_resize(nWidth, nHeight);
        setMinMaxSize();
    }
    else if( m_bDefaultSize )
        SetDefaultSize();

    m_bDefaultSize = false;

    if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
    {
        if( m_pParent )
        {
            if( AllSettings::GetLayoutRTL() )
                nX = m_pParent->maGeometry.nWidth-maGeometry.nWidth-1-nX;
            nX += m_pParent->maGeometry.nX;
            nY += m_pParent->maGeometry.nY;
        }

        if( nX != maGeometry.nX || nY != maGeometry.nY )
            bMoved = true;
        maGeometry.nX = nX;
        maGeometry.nY = nY;

        m_bDefaultPos = false;

        moveWindow( maGeometry.nX, maGeometry.nY );

        updateScreenNumber();
    }
    else if( m_bDefaultPos )
        Center();

    m_bDefaultPos = false;

    if( bSized )
        AllocateFrame();

    if( bSized && ! bMoved )
        CallCallback( SalEvent::Resize, nullptr );
    else if( bMoved && ! bSized )
        CallCallback( SalEvent::Move, nullptr );
    else if( bMoved && bSized )
        CallCallback( SalEvent::MoveResize, nullptr );

    if (bSized)
        TriggerPaintEvent();
}

void GtkSalFrame::GetClientSize( long& rWidth, long& rHeight )
{
    if( m_pWindow && !(m_nState & GDK_WINDOW_STATE_ICONIFIED) )
    {
        rWidth = maGeometry.nWidth;
        rHeight = maGeometry.nHeight;
    }
    else
        rWidth = rHeight = 0;
}

void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
{
    rRect = GetGtkSalData()->GetGtkDisplay()->getWMAdaptor()->getWorkArea( 0 );
}

SalFrame* GtkSalFrame::GetParent() const
{
    return m_pParent;
}

void GtkSalFrame::SetWindowState( const SalFrameState* pState )
{
    if( ! m_pWindow || ! pState || isChild( true, false ) )
        return;

    const WindowStateMask nMaxGeometryMask =
        WindowStateMask::X | WindowStateMask::Y |
        WindowStateMask::Width | WindowStateMask::Height |
        WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
        WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;

    if( (pState->mnMask & WindowStateMask::State) &&
        ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) &&
        (pState->mnState & WindowStateState::Maximized) &&
        (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
    {
        resizeWindow( pState->mnWidth, pState->mnHeight );
        moveWindow( pState->mnX, pState->mnY );
        m_bDefaultPos = m_bDefaultSize = false;

        maGeometry.nX       = pState->mnMaximizedX;
        maGeometry.nY       = pState->mnMaximizedY;
        maGeometry.nWidth   = pState->mnMaximizedWidth;
        maGeometry.nHeight  = pState->mnMaximizedHeight;
        updateScreenNumber();

        m_nState = GdkWindowState( m_nState | GDK_WINDOW_STATE_MAXIMIZED );
        m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ),
                                       Size( pState->mnWidth, pState->mnHeight ) );
        CallCallback( SalEvent::Resize, nullptr );
    }
    else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
                               WindowStateMask::Width | WindowStateMask::Height ) )
    {
        sal_uInt16 nPosSizeFlags = 0;
        long nX         = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
        long nY         = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
        if( pState->mnMask & WindowStateMask::X )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
        else
            nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
        if( pState->mnMask & WindowStateMask::Y )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
        else
            nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
        if( pState->mnMask & WindowStateMask::Width )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
        if( pState->mnMask & WindowStateMask::Height )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
        SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
    }
    if( pState->mnMask & WindowStateMask::State && ! isChild() )
    {
        if( pState->mnState & WindowStateState::Maximized )
            gtk_window_maximize( GTK_WINDOW(m_pWindow) );
        else
            gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
        /* #i42379# there is no rollup state in GDK; and rolled up windows are
        *  (probably depending on the WM) reported as iconified. If we iconify a
        *  window here that was e.g. a dialog, then it will be unmapped but still
        *  not be displayed in the task list, so it's an iconified window that
        *  the user cannot get out of this state. So do not set the iconified state
        *  on windows with a parent (that is transient frames) since these tend
        *  to not be represented in an icon task list.
        */
        if( (pState->mnState & WindowStateState::Minimized)
            && ! m_pParent )
            gtk_window_iconify( GTK_WINDOW(m_pWindow) );
        else
            gtk_window_deiconify( GTK_WINDOW(m_pWindow) );
    }
    TriggerPaintEvent();
}

bool GtkSalFrame::GetWindowState( SalFrameState* pState )
{
    pState->mnState = WindowStateState::Normal;
    pState->mnMask  = WindowStateMask::State;
    // rollup ? gtk 2.2 does not seem to support the shaded state
    if( m_nState & GDK_WINDOW_STATE_ICONIFIED )
        pState->mnState |= WindowStateState::Minimized;
    if( m_nState & GDK_WINDOW_STATE_MAXIMIZED )
    {
        pState->mnState |= WindowStateState::Maximized;
        pState->mnX                 = m_aRestorePosSize.Left();
        pState->mnY                 = m_aRestorePosSize.Top();
        pState->mnWidth             = m_aRestorePosSize.GetWidth();
        pState->mnHeight            = m_aRestorePosSize.GetHeight();
        pState->mnMaximizedX        = maGeometry.nX;
        pState->mnMaximizedY        = maGeometry.nY;
        pState->mnMaximizedWidth    = maGeometry.nWidth;
        pState->mnMaximizedHeight   = maGeometry.nHeight;
        pState->mnMask  |= WindowStateMask::MaximizedX          |
                           WindowStateMask::MaximizedY          |
                           WindowStateMask::MaximizedWidth      |
                           WindowStateMask::MaximizedHeight;
    }
    else
    {
        pState->mnX         = maGeometry.nX;
        pState->mnY         = maGeometry.nY;
        pState->mnWidth     = maGeometry.nWidth;
        pState->mnHeight    = maGeometry.nHeight;
    }
    pState->mnMask  |= WindowStateMask::X            |
                       WindowStateMask::Y            |
                       WindowStateMask::Width        |
                       WindowStateMask::Height;

    return true;
}

void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
{
    if( !m_pWindow )
        return;

    if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
        return;

    GdkScreen *pScreen = nullptr;
    GdkRectangle aNewMonitor;

    bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
    m_bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;

    if (m_bSpanMonitorsWhenFullscreen)   //span all screens
    {
        pScreen = gtk_widget_get_screen( m_pWindow );
        aNewMonitor.x = 0;
        aNewMonitor.y = 0;
        aNewMonitor.width = gdk_screen_get_width(pScreen);
        aNewMonitor.height = gdk_screen_get_height(pScreen);
    }
    else
    {
        gint nMonitor;
        bool bSameMonitor = false;

        if (!bSpanAllScreens)
        {
            pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
            if (!pScreen)
            {
                g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
                           "fallback to current\n", nNewScreen);
            }
        }

        if (!pScreen)
        {
            pScreen = gtk_widget_get_screen( m_pWindow );
            bSameMonitor = true;
        }

        // Heavy lifting, need to move screen ...
        if( pScreen != gtk_widget_get_screen( m_pWindow ))
            gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );

        gint nOldMonitor = gdk_screen_get_monitor_at_window(
                                pScreen, widget_get_window( m_pWindow ) );
        if (bSameMonitor)
            nMonitor = nOldMonitor;

    #if OSL_DEBUG_LEVEL > 1
        if( nMonitor == nOldMonitor )
            g_warning( "An apparently pointless SetScreen - should we elide it ?" );
    #endif

        GdkRectangle aOldMonitor;
        gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
        gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );

        maGeometry.nX = aNewMonitor.x + maGeometry.nX - aOldMonitor.x;
        maGeometry.nY = aNewMonitor.y + maGeometry.nY - aOldMonitor.y;
    }

    bool bResize = false;
    bool bVisible = IS_WIDGET_MAPPED( m_pWindow );
    if( bVisible )
        Show( false );

    if( eType == SetType::Fullscreen )
    {
        maGeometry.nX = aNewMonitor.x;
        maGeometry.nY = aNewMonitor.y;
        maGeometry.nWidth = aNewMonitor.width;
        maGeometry.nHeight = aNewMonitor.height;
        m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
        bResize = true;

        // #i110881# for the benefit of compiz set a max size here
        // else setting to fullscreen fails for unknown reasons
        m_aMaxSize.setWidth( aNewMonitor.width );
        m_aMaxSize.setHeight( aNewMonitor.height );
    }

    if( pSize && eType == SetType::UnFullscreen )
    {
        maGeometry.nX = pSize->Left();
        maGeometry.nY = pSize->Top();
        maGeometry.nWidth = pSize->GetWidth();
        maGeometry.nHeight = pSize->GetHeight();
        m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
        bResize = true;
    }

    if (bResize)
    {
        // temporarily re-sizeable
        if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
            gtk_window_set_resizable( GTK_WINDOW(m_pWindow), TRUE );
        window_resize(maGeometry.nWidth, maGeometry.nHeight);
        //I wonder if we should instead leave maGeometry alone and rely on
        //configure-event to trigger signalConfigure and set it there
        AllocateFrame();
        TriggerPaintEvent();
    }

    gtk_window_move( GTK_WINDOW( m_pWindow ), maGeometry.nX, maGeometry.nY );

    // _NET_WM_STATE_FULLSCREEN (Metacity <-> KWin)
    if( ! getDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
    {
        if( eType == SetType::Fullscreen )
            gtk_window_fullscreen( GTK_WINDOW( m_pWindow ) );
        else if( eType == SetType::UnFullscreen )
            gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
    }

    if( eType == SetType::UnFullscreen &&
        !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
        gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );

    // FIXME: we should really let gtk+ handle our widget hierarchy ...
    if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
        SetParent( nullptr );
    std::list< GtkSalFrame* > aChildren = m_aChildren;
    for (auto const& child : aChildren)
        child->SetScreen( nNewScreen, SetType::RetainSize );

    m_bDefaultPos = m_bDefaultSize = false;
    updateScreenNumber();
    CallCallback( SalEvent::MoveResize, nullptr );

    if( bVisible )
        Show( true );
}

void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
{
    SetScreen( nNewScreen, SetType::RetainSize );
}

void GtkSalFrame::updateWMClass()
{
    OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
    const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
                                                    SalGenericSystem::getFrameClassName();
    Display *display;

    if (!getDisplay()->IsX11Display())
        return;

    display = getDisplay()->GetDisplay();

    if( IS_WIDGET_REALIZED( m_pWindow ) )
    {
        XClassHint* pClass = XAllocClassHint();
        OString aResName = SalGenericSystem::getFrameResName();
        pClass->res_name  = const_cast<char*>(aResName.getStr());
        pClass->res_class = const_cast<char*>(pResClass);
        XSetClassHint( display,
                       widget_get_xid(m_pWindow),
                       pClass );
        XFree( pClass );
    }
}

void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
{
    if( rWMClass != m_sWMClass && ! isChild() )
    {
        m_sWMClass = rWMClass;
        updateWMClass();

        for (auto const& child : m_aChildren)
            child->SetApplicationID(rWMClass);
    }
}

void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
{
    m_bFullscreen = bFullScreen;

    if( !m_pWindow || isChild() )
        return;

    if( bFullScreen )
    {
        m_aRestorePosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ),
                                       Size( maGeometry.nWidth, maGeometry.nHeight ) );
        SetScreen( nScreen, SetType::Fullscreen );
    }
    else
    {
        SetScreen( nScreen, SetType::UnFullscreen,
                   !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
        m_aRestorePosSize = tools::Rectangle();
    }
}

void GtkSalFrame::StartPresentation( bool bStart )
{
    boost::optional<guint> aWindow;
    boost::optional<Display*> aDisplay;
    if( getDisplay()->IsX11Display() )
    {
        aWindow = widget_get_xid(m_pWindow);
        aDisplay = GDK_DISPLAY_XDISPLAY( getGdkDisplay() );
    }

    m_ScreenSaverInhibitor.inhibit( bStart,
                                    "presentation",
                                    getDisplay()->IsX11Display(),
                                    aWindow,
                                    aDisplay );
}

void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
{
    if( m_pWindow )
        gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
}

void GtkSalFrame::ToTop( SalFrameToTop nFlags )
{
    if( m_pWindow )
    {
        if( isChild( false ) )
            gtk_widget_grab_focus( m_pWindow );
        else if( IS_WIDGET_MAPPED( m_pWindow ) )
        {
            if( ! (nFlags & SalFrameToTop::GrabFocusOnly) )
                gtk_window_present( GTK_WINDOW(m_pWindow) );
            else
            {
                guint32 nUserTime = gdk_x11_get_server_time(GTK_WIDGET (m_pWindow)->window);
                gdk_window_focus( widget_get_window(m_pWindow), nUserTime );
            }
        }
        else
        {
            if( nFlags & SalFrameToTop::RestoreWhenMin )
                gtk_window_present( GTK_WINDOW(m_pWindow) );
        }
    }
}

void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
{
    if( m_pWindow && ePointerStyle != m_ePointerStyle )
    {
        m_ePointerStyle = ePointerStyle;
        GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
        gdk_window_set_cursor( widget_get_window(m_pWindow), pCursor );
        m_pCurrentCursor = pCursor;

        // #i80791# use grabPointer the same way as CaptureMouse, respective float grab
        if( getDisplay()->MouseCaptured( this ) )
            grabPointer( true );
        else if( m_nFloats > 0 )
            grabPointer( true, true );
    }
}

void GtkSalFrame::grabPointer( bool bGrab, bool bOwnerEvents )
{
    static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
    if (pEnv && *pEnv)
        return;

    if (!m_pWindow)
        return;

    const int nMask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);

    if( bGrab )
    {
        bool bUseGdkGrab = true;
        for (auto pSalFrame : getDisplay()->getFrames() )
        {
            const GtkSalFrame* pFrame = static_cast< const GtkSalFrame* >( pSalFrame );
            if( pFrame->m_bWindowIsGtkPlug )
            {
                bUseGdkGrab = false;
                break;
            }
        }
        if( bUseGdkGrab )
        {
            gdk_pointer_grab( widget_get_window( m_pWindow ), bOwnerEvents,
                              GdkEventMask(nMask), nullptr, m_pCurrentCursor,
                              GDK_CURRENT_TIME );
        }
        else
        {
            // FIXME: for some unknown reason gdk_pointer_grab does not
            // really produce owner events for GtkPlug windows
            // the cause is yet unknown

            // this is of course a bad hack, especially as we cannot
            // set the right cursor this way
            XGrabPointer( getDisplay()->GetDisplay(),
                          widget_get_xid( m_pWindow ),
                          bOwnerEvents,
                          PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
                          GrabModeAsync,
                          GrabModeAsync,
                          None,
                          None,
                          CurrentTime
                );
        }
    }
    else
    {
        // Two GdkDisplays may be open
        gdk_display_pointer_ungrab( getGdkDisplay(), GDK_CURRENT_TIME);
    }
}

void GtkSalFrame::grabKeyboard( bool bGrab )
{
    static const char* pEnv = getenv("SAL_NO_MOUSEGRABS"); // let's not introduce a special var for this
    if (pEnv && *pEnv)
        return;

    if (!m_pWindow)
        return;

    if( bGrab )
    {
        gdk_keyboard_grab(widget_get_window(m_pWindow), true,
                          GDK_CURRENT_TIME);
    }
    else
    {
        gdk_keyboard_ungrab(GDK_CURRENT_TIME);
    }
}

void GtkSalFrame::CaptureMouse( bool bCapture )
{
    getDisplay()->CaptureMouse( bCapture ? this : nullptr );
}

void GtkSalFrame::SetPointerPos( long nX, long nY )
{
    GtkSalFrame* pFrame = this;
    while( pFrame && pFrame->isChild( false ) )
        pFrame = pFrame->m_pParent;
    if( ! pFrame )
        return;

    GdkScreen *pScreen = gtk_window_get_screen( GTK_WINDOW(pFrame->m_pWindow) );
    GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );

    /* when the application tries to center the mouse in the dialog the
     * window isn't mapped already. So use coordinates relative to the root window.
     */
    unsigned int nWindowLeft = maGeometry.nX + nX;
    unsigned int nWindowTop  = maGeometry.nY + nY;

    XWarpPointer( GDK_DISPLAY_XDISPLAY (pDisplay), None,
                  GDK_WINDOW_XID (gdk_screen_get_root_window( pScreen ) ),
                  0, 0, 0, 0, nWindowLeft, nWindowTop);
    // #i38648# ask for the next motion hint
    gint x, y;
    GdkModifierType mask;
    gdk_window_get_pointer( widget_get_window(pFrame->m_pWindow) , &x, &y, &mask );
}

void GtkSalFrame::Flush()
{
    XFlush (GDK_DISPLAY_XDISPLAY (getGdkDisplay()));
}

void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
    guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
{
    if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
        return;

    // Get GDK key modifiers
    GdkModifierType nModifiers = GdkModifierType(0);

    if ( rKeyCode.IsShift() )
        nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );

    if ( rKeyCode.IsMod1() )
        nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );

    if ( rKeyCode.IsMod2() )
        nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_MOD1_MASK );

    *pGdkModifiers = nModifiers;

    // Get GDK keycode.
    guint nKeyCode = 0;

    guint nCode = rKeyCode.GetCode();

    if ( nCode >= KEY_0 && nCode <= KEY_9 )
        nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
    else if ( nCode >= KEY_A && nCode <= KEY_Z )
        nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
    else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
        nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
    else
    {
        switch (nCode)
        {
            case KEY_DOWN:          nKeyCode = GDK_KEY_Down;            break;
            case KEY_UP:            nKeyCode = GDK_KEY_Up;              break;
            case KEY_LEFT:          nKeyCode = GDK_KEY_Left;            break;
            case KEY_RIGHT:         nKeyCode = GDK_KEY_Right;           break;
            case KEY_HOME:          nKeyCode = GDK_KEY_Home;            break;
            case KEY_END:           nKeyCode = GDK_KEY_End;             break;
            case KEY_PAGEUP:        nKeyCode = GDK_KEY_Page_Up;         break;
            case KEY_PAGEDOWN:      nKeyCode = GDK_KEY_Page_Down;       break;
            case KEY_RETURN:        nKeyCode = GDK_KEY_Return;          break;
            case KEY_ESCAPE:        nKeyCode = GDK_KEY_Escape;          break;
            case KEY_TAB:           nKeyCode = GDK_KEY_Tab;             break;
            case KEY_BACKSPACE:     nKeyCode = GDK_KEY_BackSpace;       break;
            case KEY_SPACE:         nKeyCode = GDK_KEY_space;           break;
            case KEY_INSERT:        nKeyCode = GDK_KEY_Insert;          break;
            case KEY_DELETE:        nKeyCode = GDK_KEY_Delete;          break;
            case KEY_ADD:           nKeyCode = GDK_KEY_plus;            break;
            case KEY_SUBTRACT:      nKeyCode = GDK_KEY_minus;           break;
            case KEY_MULTIPLY:      nKeyCode = GDK_KEY_asterisk;        break;
            case KEY_DIVIDE:        nKeyCode = GDK_KEY_slash;           break;
            case KEY_POINT:         nKeyCode = GDK_KEY_period;          break;
            case KEY_COMMA:         nKeyCode = GDK_KEY_comma;           break;
            case KEY_LESS:          nKeyCode = GDK_KEY_less;            break;
            case KEY_GREATER:       nKeyCode = GDK_KEY_greater;         break;
            case KEY_EQUAL:         nKeyCode = GDK_KEY_equal;           break;
            case KEY_FIND:          nKeyCode = GDK_KEY_Find;            break;
            case KEY_CONTEXTMENU:   nKeyCode = GDK_KEY_Menu;            break;
            case KEY_HELP:          nKeyCode = GDK_KEY_Help;            break;
            case KEY_UNDO:          nKeyCode = GDK_KEY_Undo;            break;
            case KEY_REPEAT:        nKeyCode = GDK_KEY_Redo;            break;
            case KEY_DECIMAL:       nKeyCode = GDK_KEY_KP_Decimal;      break;
            case KEY_TILDE:         nKeyCode = GDK_KEY_asciitilde;      break;
            case KEY_QUOTELEFT:     nKeyCode = GDK_KEY_quoteleft;       break;
            case KEY_BRACKETLEFT:   nKeyCode = GDK_KEY_bracketleft;     break;
            case KEY_BRACKETRIGHT:  nKeyCode = GDK_KEY_bracketright;    break;
            case KEY_SEMICOLON:     nKeyCode = GDK_KEY_semicolon;       break;
            case KEY_QUOTERIGHT:    nKeyCode = GDK_KEY_quoteright;      break;

            // Special cases
            case KEY_COPY:          nKeyCode = GDK_KEY_Copy;            break;
            case KEY_CUT:           nKeyCode = GDK_KEY_Cut;             break;
            case KEY_PASTE:         nKeyCode = GDK_KEY_Paste;           break;
            case KEY_OPEN:          nKeyCode = GDK_KEY_Open;            break;
        }
    }

    *pGdkKeyCode = nKeyCode;
}

OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
{
    return getDisplay()->GetKeyName( nKeyCode );
}

GdkDisplay *GtkSalFrame::getGdkDisplay()
{
    return GetGtkSalData()->GetGdkDisplay();
}

GtkSalDisplay *GtkSalFrame::getDisplay()
{
    return GetGtkSalData()->GetGtkDisplay();
}

SalFrame::SalPointerState GtkSalFrame::GetPointerState()
{
    SalPointerState aState;
    GdkScreen* pScreen;
    gint x, y;
    GdkModifierType aMask;
    gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
    aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
    aState.mnState = GetMouseModCode( aMask );
    return aState;
}

KeyIndicatorState GtkSalFrame::GetIndicatorState()
{
    return GetGtkSalData()->GetGtkDisplay()->GetIndicatorState();
}

void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
{
    GetGtkSalData()->GetGtkDisplay()->SimulateKeyPress(nKeyCode);
}

void GtkSalFrame::SetInputContext( SalInputContext* pContext )
{
    if( ! pContext )
        return;

    if( ! (pContext->mnOptions & InputContextFlags::Text) )
        return;

    // create a new im context
    if( ! m_pIMHandler )
        m_pIMHandler.reset( new IMHandler( this ) );
}

void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
{
    if( m_pIMHandler )
        m_pIMHandler->endExtTextInput( nFlags );
}

bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
{
    // not supported yet
    return false;
}

LanguageType GtkSalFrame::GetInputLanguage()
{
    return LANGUAGE_DONTKNOW;
}

void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
{
    if( ! m_pWindow )
        return;

    GtkSalGraphics* pGraphics = m_pGraphics.get();
    bool bFreeGraphics = false;
    if( ! pGraphics )
    {
        pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
        if ( !pGraphics )
        {
            SAL_WARN("vcl.gtk", "Could not get graphics - unable to update settings");
            return;
        }
        bFreeGraphics = true;
    }

    pGraphics->UpdateSettings( rSettings );

    if( bFreeGraphics )
        ReleaseGraphics( pGraphics );
}

void GtkSalFrame::Beep()
{
    gdk_display_beep( getGdkDisplay() );
}

const SystemEnvData* GtkSalFrame::GetSystemData() const
{
    return &m_aSystemData;
}

void GtkSalFrame::SetParent( SalFrame* pNewParent )
{
    if( m_pParent )
        m_pParent->m_aChildren.remove( this );
    m_pParent = static_cast<GtkSalFrame*>(pNewParent);
    if( m_pParent )
        m_pParent->m_aChildren.push_back( this );
    if( ! isChild() )
        gtk_window_set_transient_for( GTK_WINDOW(m_pWindow),
                                      (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
                                     );
}

void GtkSalFrame::createNewWindow( ::Window aNewParent, bool bXEmbed, SalX11Screen nXScreen )
{
    bool bWasVisible = m_pWindow && IS_WIDGET_MAPPED(m_pWindow);
    if( bWasVisible )
        Show( false );

    if( static_cast<int>(nXScreen.getXScreen()) >= getDisplay()->GetXScreenCount() )
        nXScreen = m_nXScreen;

    SystemParentData aParentData;
    aParentData.nSize = sizeof(SystemParentData);
    aParentData.aWindow = aNewParent;
    aParentData.bXEmbedSupport = bXEmbed;
    if( aNewParent == None )
    {
        aParentData.aWindow = None;
        aParentData.bXEmbedSupport = false;
    }
    else
    {
        // is new parent a root window ?
        Display* pDisp = getDisplay()->GetDisplay();
        int nScreens = getDisplay()->GetXScreenCount();
        for( int i = 0; i < nScreens; i++ )
        {
            if( aNewParent == RootWindow( pDisp, i ) )
            {
                nXScreen = SalX11Screen( i );
                aParentData.aWindow = None;
                aParentData.bXEmbedSupport = false;
                break;
            }
        }
    }

    // free xrender resources
    if( m_pGraphics )
        m_pGraphics->SetDrawable( None, m_nXScreen );

    // first deinit frame
    m_pIMHandler.reset();
    if( m_pRegion )
    {
        gdk_region_destroy( m_pRegion );
    }

    GtkWidget *pEventWidget = getMouseEventWidget();
    for (auto handler_id : m_aMouseSignalIds)
        g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
    if( m_pFixedContainer )
        gtk_widget_destroy( GTK_WIDGET(m_pFixedContainer) );
    if( m_pEventBox )
        gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
    if( m_pWindow )
        gtk_widget_destroy( m_pWindow );
    if( m_pForeignParent )
        g_object_unref( G_OBJECT( m_pForeignParent ) );
    if( m_pForeignTopLevel )
        g_object_unref( G_OBJECT( m_pForeignTopLevel ) );

    // init new window
    m_bDefaultPos = m_bDefaultSize = false;
    if( aParentData.aWindow != None )
    {
        m_nStyle |= SalFrameStyleFlags::PLUG;
        Init( &aParentData );
    }
    else
    {
        m_nStyle &= ~SalFrameStyleFlags::PLUG;
        Init( (m_pParent && m_pParent->m_nXScreen == m_nXScreen) ? m_pParent : nullptr, m_nStyle );
    }

    // update graphics
    if( m_pGraphics )
    {
        m_pGraphics->SetDrawable( widget_get_xid(m_pWindow), m_nXScreen );
        m_pGraphics->SetWindow( m_pWindow );
    }

    if( ! m_aTitle.isEmpty() )
        SetTitle( m_aTitle );

    if( bWasVisible )
        Show( true );

    std::list< GtkSalFrame* > aChildren;
    aChildren.swap(m_aChildren);
    for (auto const& child : aChildren)
        child->createNewWindow( None, false, m_nXScreen );

    // FIXME: SalObjects
}

bool GtkSalFrame::SetPluginParent( SystemParentData* pSysParent )
{
    GetGenericUnixSalData()->ErrorTrapPush(); // permanently ignore unruly children's errors
    createNewWindow( pSysParent->aWindow, (pSysParent->nSize > sizeof(long)) && pSysParent->bXEmbedSupport, m_nXScreen );
    return true;
}

void GtkSalFrame::ResetClipRegion()
{
    if( m_pWindow )
        gdk_window_shape_combine_region( widget_get_window( m_pWindow ), nullptr, 0, 0 );
}

void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
{
    if( m_pRegion )
        gdk_region_destroy( m_pRegion );
    m_pRegion = gdk_region_new();
}

void GtkSalFrame::UnionClipRegion( long nX, long nY, long nWidth, long nHeight )
{
    if( m_pRegion )
    {
        GdkRectangle aRect;
        aRect.x         = nX;
        aRect.y         = nY;
        aRect.width     = nWidth;
        aRect.height    = nHeight;
        gdk_region_union_with_rect( m_pRegion, &aRect );
    }
}

void GtkSalFrame::EndSetClipRegion()
{
    if( m_pWindow && m_pRegion )
        gdk_window_shape_combine_region( widget_get_window(m_pWindow), m_pRegion, 0, 0 );
}

bool GtkSalFrame::Dispatch( const XEvent* pEvent )
{
    bool bContinueDispatch = true;

    if( pEvent->type == PropertyNotify )
    {
        vcl_sal::WMAdaptor* pAdaptor = getDisplay()->getWMAdaptor();
        Atom nDesktopAtom = pAdaptor->getAtom( vcl_sal::WMAdaptor::NET_WM_DESKTOP );
        if( pEvent->xproperty.atom == nDesktopAtom &&
            pEvent->xproperty.state == PropertyNewValue )
        {
            m_nWorkArea = pAdaptor->getWindowWorkArea( widget_get_xid(m_pWindow) );
        }
    }
    else if( pEvent->type == ConfigureNotify )
    {
        if( m_pForeignParent && pEvent->xconfigure.window == m_aForeignParentWindow )
        {
            bContinueDispatch = false;
            gtk_window_resize( GTK_WINDOW(m_pWindow), pEvent->xconfigure.width, pEvent->xconfigure.height );
            if( ( sal::static_int_cast< int >(maGeometry.nWidth) !=
                  pEvent->xconfigure.width ) ||
                ( sal::static_int_cast< int >(maGeometry.nHeight) !=
                  pEvent->xconfigure.height ) )
            {
                maGeometry.nWidth  = pEvent->xconfigure.width;
                maGeometry.nHeight = pEvent->xconfigure.height;
                setMinMaxSize();
                getDisplay()->SendInternalEvent( this, nullptr, SalEvent::Resize );
            }
        }
        else if( m_pForeignTopLevel && pEvent->xconfigure.window == m_aForeignTopLevelWindow )
        {
            bContinueDispatch = false;
            // update position
            int x = 0, y = 0;
            ::Window aChild;
            XTranslateCoordinates( getDisplay()->GetDisplay(),
                                   widget_get_xid(m_pWindow),
                                   getDisplay()->GetRootWindow( getDisplay()->GetDefaultXScreen() ),
                                   0, 0,
                                   &x, &y,
                                   &aChild );
            if( x != maGeometry.nX || y != maGeometry.nY )
            {
                maGeometry.nX = x;
                maGeometry.nY = y;
                getDisplay()->SendInternalEvent( this, nullptr, SalEvent::Move );
            }
        }
    }
    else if( pEvent->type == ClientMessage &&
             pEvent->xclient.message_type == getDisplay()->getWMAdaptor()->getAtom( vcl_sal::WMAdaptor::XEMBED ) &&
             pEvent->xclient.window == widget_get_xid(m_pWindow) &&
             m_bWindowIsGtkPlug
             )
    {
        // FIXME: this should not be necessary, GtkPlug should do this
        // transparently for us
        if( pEvent->xclient.data.l[1] == 1 || // XEMBED_WINDOW_ACTIVATE
            pEvent->xclient.data.l[1] == 2    // XEMBED_WINDOW_DEACTIVATE
        )
        {
            GdkEventFocus aEvent;
            aEvent.type = GDK_FOCUS_CHANGE;
            aEvent.window = widget_get_window( m_pWindow );
            aEvent.send_event = gint8(TRUE);
            aEvent.in = gint16(pEvent->xclient.data.l[1] == 1);
            signalFocus( m_pWindow, &aEvent, this );
        }
    }

    return bContinueDispatch;
}

gboolean GtkSalFrame::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    SalMouseEvent aEvent;
    SalEvent nEventType = SalEvent::NONE;
    switch( pEvent->type )
    {
        case GDK_BUTTON_PRESS:
            nEventType = SalEvent::MouseButtonDown;
            break;
        case GDK_BUTTON_RELEASE:
            nEventType = SalEvent::MouseButtonUp;
            break;
        default:
            return false;
    }
    switch( pEvent->button )
    {
        case 1: aEvent.mnButton = MOUSE_LEFT;   break;
        case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
        case 3: aEvent.mnButton = MOUSE_RIGHT;  break;
        default: return false;
    }
    aEvent.mnTime   = pEvent->time;
    aEvent.mnX      = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
    aEvent.mnY      = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
    aEvent.mnCode   = GetMouseModCode( pEvent->state );

    bool bClosePopups = false;
    if( pEvent->type == GDK_BUTTON_PRESS &&
        !(pThis->m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
        )
    {
        if( m_nFloats > 0 )
        {
            // close popups if user clicks outside our application
            gint x, y;
            bClosePopups = (gdk_display_get_window_at_pointer( GtkSalFrame::getGdkDisplay(), &x, &y ) == nullptr);
        }
        /*  #i30306# release implicit pointer grab if no popups are open; else
         *  Drag cannot grab the pointer and will fail.
         */
        if( m_nFloats < 1 || bClosePopups )
            gdk_display_pointer_ungrab( GtkSalFrame::getGdkDisplay(), GDK_CURRENT_TIME );
    }

    if( pThis->m_bWindowIsGtkPlug &&
        pEvent->type == GDK_BUTTON_PRESS &&
        pEvent->button == 1 )
    {
        pThis->askForXEmbedFocus( pEvent->time );
    }

    if( AllSettings::GetLayoutRTL() )
        aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;

    vcl::DeletionListener aDel( pThis );

    pThis->CallCallback( nEventType, &aEvent );

    if( ! aDel.isDeleted() )
    {
        if( bClosePopups )
        {
            ImplSVData* pSVData = ImplGetSVData();
            if ( pSVData->maWinData.mpFirstFloat )
            {
                if (!(pSVData->maWinData.mpFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose))
                    pSVData->maWinData.mpFirstFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
            }
        }

        if( ! aDel.isDeleted() )
        {
            int frame_x = static_cast<int>(pEvent->x_root - pEvent->x);
            int frame_y = static_cast<int>(pEvent->y_root - pEvent->y);
            if( frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY )
            {
                pThis->maGeometry.nX = frame_x;
                pThis->maGeometry.nY = frame_y;
                pThis->CallCallback( SalEvent::Move, nullptr );
            }
        }
    }

    return true;
}

gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
{
    GdkEventScroll& rEvent = pInEvent->scroll;
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    static sal_uLong        nLines = 0;
    if( ! nLines )
    {
        char* pEnv = getenv( "SAL_WHEELLINES" );
        nLines = pEnv ? atoi( pEnv ) : 3;
        if( nLines > 10 )
            nLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
    }

    bool bNeg = (rEvent.direction == GDK_SCROLL_DOWN || rEvent.direction == GDK_SCROLL_RIGHT );
    SalWheelMouseEvent aEvent;
    aEvent.mnTime           = rEvent.time;
    aEvent.mnX              = static_cast<sal_uLong>(rEvent.x);
    aEvent.mnY              = static_cast<sal_uLong>(rEvent.y);
    aEvent.mnDelta          = bNeg ? -120 : 120;
    aEvent.mnNotchDelta     = bNeg ? -1 : 1;
    aEvent.mnScrollLines    = nLines;
    aEvent.mnCode           = GetMouseModCode( rEvent.state );
    aEvent.mbHorz           = (rEvent.direction == GDK_SCROLL_LEFT || rEvent.direction == GDK_SCROLL_RIGHT);

    if( AllSettings::GetLayoutRTL() )
        aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;

    pThis->CallCallback( SalEvent::WheelMouse, &aEvent );

    return true;
}

gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    SalMouseEvent aEvent;
    aEvent.mnTime   = pEvent->time;
    aEvent.mnX      = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
    aEvent.mnY      = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
    aEvent.mnCode   = GetMouseModCode( pEvent->state );
    aEvent.mnButton = 0;

    if( AllSettings::GetLayoutRTL() )
        aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;

    vcl::DeletionListener aDel( pThis );

    pThis->CallCallback( SalEvent::MouseMove, &aEvent );

    if( ! aDel.isDeleted() )
    {
        int frame_x = static_cast<int>(pEvent->x_root - pEvent->x);
        int frame_y = static_cast<int>(pEvent->y_root - pEvent->y);
        if( frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY )
        {
            pThis->maGeometry.nX = frame_x;
            pThis->maGeometry.nY = frame_y;
            pThis->CallCallback( SalEvent::Move, nullptr );
        }

        if( ! aDel.isDeleted() )
        {
            // ask for the next hint
            gint x, y;
            GdkModifierType mask;
            gdk_window_get_pointer( widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
        }
    }

    return true;
}

gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    SalMouseEvent aEvent;
    aEvent.mnTime   = pEvent->time;
    aEvent.mnX      = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
    aEvent.mnY      = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
    aEvent.mnCode   = GetMouseModCode( pEvent->state );
    aEvent.mnButton = 0;

    if (AllSettings::GetLayoutRTL())
        aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;

    pThis->CallCallback( (pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, &aEvent );

    return true;
}

gboolean GtkSalFrame::signalExpose( GtkWidget*, GdkEventExpose* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    struct SalPaintEvent aEvent( pEvent->area.x, pEvent->area.y, pEvent->area.width, pEvent->area.height, OpenGLHelper::isVCLOpenGLEnabled() );

    pThis->CallCallback( SalEvent::Paint, &aEvent );

    return false;
}

gboolean GtkSalFrame::signalConfigure( GtkWidget*, GdkEventConfigure* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    bool bMoved = false, bSized = false;
    int x = pEvent->x, y = pEvent->y;

    /*  HACK: during sizing/moving a toolbar pThis->maGeometry is actually
     *  already exact; even worse: due to the asynchronicity of configure
     *  events the borderwindow which would evaluate this event
     *  would size/move based on wrong data if we would actually evaluate
     *  this event. So let's swallow it.
     */
    if( (pThis->m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) &&
        GtkSalFrame::getDisplay()->GetCaptureFrame() == pThis )
        return false;

    /* #i31785# claims we cannot trust the x,y members of the event;
     * they are e.g. not set correctly on maximize/demaximize;
     * yet the gdkdisplay-x11.c code handling configure_events has
     * done this XTranslateCoordinates work since the day ~zero.
     */
    if( x != pThis->maGeometry.nX || y != pThis->maGeometry.nY )
    {
        bMoved = true;
        pThis->maGeometry.nX        = x;
        pThis->maGeometry.nY        = y;
    }
    /* #i86302#
     * for non sizeable windows we set the min and max hint for the window manager to
     * achieve correct sizing. However this is asynchronous and e.g. on Compiz
     * it sometimes happens that the window gets resized to another size (some default)
     * if we update the size here, subsequent setMinMaxSize will use this wrong size
     * - which is not good since the window manager will now size the window back to this
     * wrong size at some point.
     */
    if( pThis->m_bFullscreen || (pThis->m_nStyle & (SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::PLUG)) == SalFrameStyleFlags::SIZEABLE )
    {
        if( pEvent->width != static_cast<int>(pThis->maGeometry.nWidth) || pEvent->height != static_cast<int>(pThis->maGeometry.nHeight) )
        {
            bSized = true;
            pThis->maGeometry.nWidth    = pEvent->width;
            pThis->maGeometry.nHeight   = pEvent->height;
        }
    }

    // update decoration hints
    if( ! (pThis->m_nStyle & SalFrameStyleFlags::PLUG) )
    {
        GdkRectangle aRect;
        gdk_window_get_frame_extents( widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &aRect );
        pThis->maGeometry.nTopDecoration    = y - aRect.y;
        pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
        pThis->maGeometry.nLeftDecoration   = x - aRect.x;
        pThis->maGeometry.nRightDecoration  = aRect.x + aRect.width - x - pEvent->width;
    }
    else
    {
        pThis->maGeometry.nTopDecoration =
            pThis->maGeometry.nBottomDecoration =
            pThis->maGeometry.nLeftDecoration =
            pThis->maGeometry.nRightDecoration = 0;
    }

    pThis->updateScreenNumber();
    if( bSized )
        pThis->AllocateFrame();

    if( bMoved && bSized )
        pThis->CallCallback( SalEvent::MoveResize, nullptr );
    else if( bMoved )
        pThis->CallCallback( SalEvent::Move, nullptr );
    else if( bSized )
        pThis->CallCallback( SalEvent::Resize, nullptr );

    if (bSized)
        pThis->TriggerPaintEvent();
    return false;
}

void GtkSalFrame::TriggerPaintEvent()
{
}

gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    SalGenericInstance *pSalInstance =
        static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);

    // check if printers have changed (analogous to salframe focus handler)
    pSalInstance->updatePrinterUpdate();

    if( !pEvent->in )
        pThis->m_nKeyModifiers = ModKeyFlags::NONE;

    if( pThis->m_pIMHandler )
        pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );

    // ask for changed printers like generic implementation
    if( pEvent->in && pSalInstance->isPrinterInit() )
        pSalInstance->updatePrinterUpdate();

    // FIXME: find out who the hell steals the focus from our frame
    // while we have the pointer grabbed, this should not come from
    // the window manager. Is this an event that was still queued ?
    // The focus does not seem to get set inside our process

    // in the meantime do not propagate focus get/lose if floats are open
    if( m_nFloats == 0 )
        pThis->CallCallback( pEvent->in ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr );

    return false;
}

static OString getDisplayString()
{
    int nParams = rtl_getAppCommandArgCount();
    OUString aParam;
    for( int i = 0; i < nParams; i++ )
    {
        rtl_getAppCommandArg( i, &aParam.pData );
        if( i < nParams-1 && (aParam == "-display" || aParam == "--display" ) )
        {
            rtl_getAppCommandArg( i+1, &aParam.pData );
            return OUStringToOString( aParam, osl_getThreadTextEncoding() );
        }
    }
    return OString();
}

gboolean GtkSalFrame::signalMap( GtkWidget *pWidget, GdkEvent*, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    //Spawn off a helper program that will attempt to set this fullscreen
    //window either to span all displays or the current monitor
    if (pThis->m_bFullscreen)
    {
        GdkWindow* gdkwin = widget_get_window(pThis->m_pWindow);
        if (gdkwin)
        {
            OUString sProgramURL( "$BRAND_BASE_DIR/" LIBO_LIBEXEC_FOLDER "/xid-fullscreen-on-all-monitors");
            rtl::Bootstrap::expandMacros(sProgramURL);
            OUString sProgram;
            if (osl::FileBase::getSystemPathFromFileURL(sProgramURL, sProgram) == osl::File::E_None)
            {
                OString sFinalProgram(OUStringToOString(sProgram, osl_getThreadTextEncoding())
                    + " " + OString::number(static_cast<int>(GDK_WINDOW_XID(gdkwin)))
                    + " " + OString::number(static_cast<int>(pThis->m_bSpanMonitorsWhenFullscreen)));
                OString sDisplay(getDisplayString());
                if (!sDisplay.isEmpty())
                {
                    sFinalProgram += "--display " + sDisplay;
                }
                (void) system(sFinalProgram.getStr());
            }
        }
    }

    if ( pThis->m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
        gtk_window_set_accept_focus( GTK_WINDOW(pWidget), true );

    bool bSetFocus = pThis->m_bSetFocusOnMap;
    pThis->m_bSetFocusOnMap = false;

    if( bSetFocus )
        pThis->ToTop( SalFrameToTop::GrabFocus );

    pThis->CallCallback( SalEvent::Resize, nullptr );
    pThis->TriggerPaintEvent();

    return false;
}

gboolean GtkSalFrame::signalUnmap( GtkWidget*, GdkEvent*, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    pThis->CallCallback( SalEvent::Resize, nullptr );

    return false;
}

gboolean GtkSalFrame::signalKey( GtkWidget*, GdkEventKey* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    vcl::DeletionListener aDel( pThis );

    if( pThis->m_pIMHandler )
    {
        if( pThis->m_pIMHandler->handleKeyEvent( pEvent ) )
            return true;
    }

    // handle modifiers
    if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
        pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
        pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
        pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
        pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
    {
        sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
        ModKeyFlags nExtModMask = ModKeyFlags::NONE;
        sal_uInt16 nModMask = 0;
        // pressing just the ctrl key leads to a keysym of XK_Control but
        // the event state does not contain ControlMask. In the release
        // event it's the other way round: it does contain the Control mask.
        // The modifier mode therefore has to be adapted manually.
        switch( pEvent->keyval )
        {
            case GDK_KEY_Control_L:
                nExtModMask = ModKeyFlags::LeftMod1;
                nModMask = KEY_MOD1;
                break;
            case GDK_KEY_Control_R:
                nExtModMask = ModKeyFlags::RightMod1;
                nModMask = KEY_MOD1;
                break;
            case GDK_KEY_Alt_L:
                nExtModMask = ModKeyFlags::LeftMod2;
                nModMask = KEY_MOD2;
                break;
            case GDK_KEY_Alt_R:
                nExtModMask = ModKeyFlags::RightMod2;
                nModMask = KEY_MOD2;
                break;
            case GDK_KEY_Shift_L:
                nExtModMask = ModKeyFlags::LeftShift;
                nModMask = KEY_SHIFT;
                break;
            case GDK_KEY_Shift_R:
                nExtModMask = ModKeyFlags::RightShift;
                nModMask = KEY_SHIFT;
                break;
            // Map Meta/Super to MOD3 modifier on all Unix systems
            // except macOS
            case GDK_KEY_Meta_L:
            case GDK_KEY_Super_L:
                nExtModMask = ModKeyFlags::LeftMod3;
                nModMask = KEY_MOD3;
                break;
            case GDK_KEY_Meta_R:
            case GDK_KEY_Super_R:
                nExtModMask = ModKeyFlags::RightMod3;
                nModMask = KEY_MOD3;
                break;
        }

        SalKeyModEvent aModEvt;
        aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
        aModEvt.mnCode = nModCode;

        if( pEvent->type == GDK_KEY_RELEASE )
        {
            aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
            nModCode &= ~nModMask;
            pThis->m_nKeyModifiers &= ~nExtModMask;
        }
        else
        {
            nModCode |= nModMask;
            pThis->m_nKeyModifiers |= nExtModMask;
            aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
        }

        pThis->CallCallback( SalEvent::KeyModChange, &aModEvt );
    }
    else
    {
        pThis->doKeyCallback( pEvent->state,
                              pEvent->keyval,
                              pEvent->hardware_keycode,
                              pEvent->group,
                              sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
                              (pEvent->type == GDK_KEY_PRESS),
                              false );
        if( ! aDel.isDeleted() )
            pThis->m_nKeyModifiers = ModKeyFlags::NONE;
    }

    if( !aDel.isDeleted() && pThis->m_pIMHandler )
        pThis->m_pIMHandler->updateIMSpotLocation();

    return true;
}

gboolean GtkSalFrame::signalDelete( GtkWidget*, GdkEvent*, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    pThis->CallCallback( SalEvent::Close, nullptr );

    return true;
}

void GtkSalFrame::signalStyleSet( GtkWidget*, GtkStyle* pPrevious, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    // every frame gets an initial style set on creation
    // do not post these as the whole application tends to
    // redraw itself to adjust to the new style
    // where there IS no new style resulting in tremendous unnecessary flickering
    if( pPrevious != nullptr )
    {
        // signalStyleSet does NOT usually have the gdk lock
        // so post user event to safely dispatch the SalEvent::SettingsChanged
        // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
        GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );

        // fire off font-changed when the system cairo font hints change
        GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
        const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
        const cairo_font_options_t* pCurrentCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
        bool bFontSettingsChanged = true;
        if (pLastCairoFontOptions && pCurrentCairoFontOptions)
            bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
        else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
            bFontSettingsChanged = false;
        if (bFontSettingsChanged)
        {
            pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
            GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
        }
    }

    /* #i64117# gtk sets a nice background pixmap
    *  but we actually don't really want that, so save
    *  some time on the Xserver as well as prevent
    *  some paint issues
    */
    GdkWindow* pWin = widget_get_window(GTK_WIDGET(pThis->getWindow()));
    if( pWin )
    {
        ::Window aWin = GDK_WINDOW_XWINDOW(pWin);
        if( aWin != None )
            XSetWindowBackgroundPixmap( GtkSalFrame::getDisplay()->GetDisplay(),
                                        aWin,
                                        None );
    }
    if( ! pThis->m_pParent )
    {
        // signalize theme changed for NWF caches
        // FIXME: should be called only once for a style change
        GtkSalGraphics::bThemeChanged = true;
    }
}

gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if( (pThis->m_nState & GDK_WINDOW_STATE_ICONIFIED) != (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED ) )
    {
        GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
        pThis->TriggerPaintEvent();
    }

    if(   (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_MAXIMIZED) &&
        ! (pThis->m_nState & GDK_WINDOW_STATE_MAXIMIZED) )
    {
        pThis->m_aRestorePosSize =
            tools::Rectangle( Point( pThis->maGeometry.nX, pThis->maGeometry.nY ),
                       Size( pThis->maGeometry.nWidth, pThis->maGeometry.nHeight ) );
    }
    pThis->m_nState = pEvent->window_state.new_window_state;

    #if OSL_DEBUG_LEVEL > 1
    if( (pEvent->window_state.changed_mask & GDK_WINDOW_STATE_FULLSCREEN) )
    {
        fprintf( stderr, "window %p %s full screen state\n",
            pThis,
            (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_FULLSCREEN) ? "enters" : "leaves");
    }
    #endif

    return false;
}

gboolean GtkSalFrame::signalVisibility( GtkWidget*, GdkEventVisibility* /*pEvent*/, gpointer /*frame*/ )
{
    return true;
}

void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if( pObj == pThis->m_pWindow )
    {
        pThis->m_pFixedContainer = nullptr;
        pThis->m_pEventBox = nullptr;
        pThis->m_pWindow = nullptr;
        pThis->InvalidateGraphics();
    }
}

// GtkSalFrame::IMHandler

GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
: m_pFrame(pFrame),
  m_nPrevKeyPresses( 0 ),
  m_pIMContext( nullptr ),
  m_bFocused( true ),
  m_bPreeditJustChanged( false )
{
    m_aInputEvent.mpTextAttr = nullptr;
    createIMContext();
}

GtkSalFrame::IMHandler::~IMHandler()
{
    // cancel an eventual event posted to begin preedit again
    GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
    deleteIMContext();
}

void GtkSalFrame::IMHandler::createIMContext()
{
    if(  m_pIMContext )
        return;

    m_pIMContext = gtk_im_multicontext_new ();
    g_signal_connect( m_pIMContext, "commit",
                      G_CALLBACK (signalIMCommit), this );
    g_signal_connect( m_pIMContext, "preedit_changed",
                      G_CALLBACK (signalIMPreeditChanged), this );
    g_signal_connect( m_pIMContext, "retrieve_surrounding",
                      G_CALLBACK (signalIMRetrieveSurrounding), this );
    g_signal_connect( m_pIMContext, "delete_surrounding",
                      G_CALLBACK (signalIMDeleteSurrounding), this );
    g_signal_connect( m_pIMContext, "preedit_start",
                      G_CALLBACK (signalIMPreeditStart), this );
    g_signal_connect( m_pIMContext, "preedit_end",
                      G_CALLBACK (signalIMPreeditEnd), this );

    GetGenericUnixSalData()->ErrorTrapPush();
    gtk_im_context_set_client_window( m_pIMContext, widget_get_window(GTK_WIDGET(m_pFrame->m_pWindow)) );
    gtk_im_context_focus_in( m_pIMContext );
    GetGenericUnixSalData()->ErrorTrapPop();
    m_bFocused = true;

}

void GtkSalFrame::IMHandler::deleteIMContext()
{
    if( m_pIMContext )
    {
        // first give IC a chance to deinitialize
        GetGenericUnixSalData()->ErrorTrapPush();
        gtk_im_context_set_client_window( m_pIMContext, nullptr );
        GetGenericUnixSalData()->ErrorTrapPop();
        // destroy old IC
        g_object_unref( m_pIMContext );
        m_pIMContext = nullptr;
    }
}

void GtkSalFrame::IMHandler::doCallEndExtTextInput()
{
    m_aInputEvent.mpTextAttr = nullptr;
    m_pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
}

void GtkSalFrame::IMHandler::updateIMSpotLocation()
{
    SalExtTextInputPosEvent aPosEvent;
    m_pFrame->CallCallback( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
    GdkRectangle aArea;
    aArea.x = aPosEvent.mnX;
    aArea.y = aPosEvent.mnY;
    aArea.width = aPosEvent.mnWidth;
    aArea.height = aPosEvent.mnHeight;
    GetGenericUnixSalData()->ErrorTrapPush();
    gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
    GetGenericUnixSalData()->ErrorTrapPop();
}

void GtkSalFrame::IMHandler::sendEmptyCommit()
{
    vcl::DeletionListener aDel( m_pFrame );

    SalExtTextInputEvent aEmptyEv;
    aEmptyEv.mpTextAttr         = nullptr;
    aEmptyEv.maText.clear();
    aEmptyEv.mnCursorPos        = 0;
    aEmptyEv.mnCursorFlags      = 0;
    m_pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
    if( ! aDel.isDeleted() )
        m_pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
}

void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
{
    gtk_im_context_reset ( m_pIMContext );

    if( m_aInputEvent.mpTextAttr )
    {
        vcl::DeletionListener aDel( m_pFrame );
        // delete preedit in sal (commit an empty string)
        sendEmptyCommit();
        if( ! aDel.isDeleted() )
        {
            // mark previous preedit state again (will e.g. be sent at focus gain)
            m_aInputEvent.mpTextAttr = m_aInputFlags.data();
            if( m_bFocused )
            {
                // begin preedit again
                GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
            }
        }
    }
}

void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
{
    m_bFocused = bFocusIn;
    if( bFocusIn )
    {
        GetGenericUnixSalData()->ErrorTrapPush();
        gtk_im_context_focus_in( m_pIMContext );
        GetGenericUnixSalData()->ErrorTrapPop();
        if( m_aInputEvent.mpTextAttr )
        {
            sendEmptyCommit();
            // begin preedit again
            GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
        }
    }
    else
    {
        GetGenericUnixSalData()->ErrorTrapPush();
        gtk_im_context_focus_out( m_pIMContext );
        GetGenericUnixSalData()->ErrorTrapPop();
        // cancel an eventual event posted to begin preedit again
        GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
    }
}

bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
{
    vcl::DeletionListener aDel( m_pFrame );

    if( pEvent->type == GDK_KEY_PRESS )
    {
        // Add this key press event to the list of previous key presses
        // to which we compare key release events.  If a later key release
        // event has a matching key press event in this list, we swallow
        // the key release because some GTK Input Methods don't swallow it
        // for us.
        m_aPrevKeyPresses.emplace_back(pEvent );
        m_nPrevKeyPresses++;

        // Also pop off the earliest key press event if there are more than 10
        // already.
        while (m_nPrevKeyPresses > 10)
        {
            m_aPrevKeyPresses.pop_front();
            m_nPrevKeyPresses--;
        }

        GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );

        // #i51353# update spot location on every key input since we cannot
        // know which key may activate a preedit choice window
        updateIMSpotLocation();
        if( aDel.isDeleted() )
            return true;

        gboolean bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
        g_object_unref( pRef );

        if( aDel.isDeleted() )
            return true;

        m_bPreeditJustChanged = false;

        if( bResult )
            return true;
        else
        {
            SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk", "key press has vanished !" );
            if( ! m_aPrevKeyPresses.empty() ) // sanity check
            {
                // event was not swallowed, do not filter a following
                // key release event
                // note: this relies on gtk_im_context_filter_keypress
                // returning without calling a handler (in the "not swallowed"
                // case ) which might change the previous key press list so
                // we would pop the wrong event here
                m_aPrevKeyPresses.pop_back();
                m_nPrevKeyPresses--;
            }
        }
    }

    // Determine if we got an earlier key press event corresponding to this key release
    if (pEvent->type == GDK_KEY_RELEASE)
    {
        GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
        gboolean bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
        g_object_unref( pRef );

        if( aDel.isDeleted() )
            return true;

        m_bPreeditJustChanged = false;

        auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
        // If we found a corresponding previous key press event, swallow the release
        // and remove the earlier key press from our list
        if (iter != m_aPrevKeyPresses.end())
        {
            m_aPrevKeyPresses.erase(iter);
            m_nPrevKeyPresses--;
            return true;
        }

        if( bResult )
            return true;
    }

    return false;
}

/* FIXME:
* #122282# still more hacking: some IMEs never start a preedit but simply commit
* in this case we cannot commit a single character. Workaround: do not do the
* single key hack for enter or space if the unicode committed does not match
*/

static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
{
    bool bRet = true;
    switch( keyval )
    {
        case GDK_KEY_KP_Enter:
        case GDK_KEY_Return:
            if( cCode != '\n' && cCode != '\r' )
                bRet = false;
            break;
        case GDK_KEY_space:
        case GDK_KEY_KP_Space:
            if( cCode != ' ' )
                bRet = false;
            break;
        default:
            break;
    }
    return bRet;
}

void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* pContext, gchar* pText, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );
    {
        const bool bWasPreedit =
            (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
            pThis->m_bPreeditJustChanged;

        pThis->m_aInputEvent.mpTextAttr         = nullptr;
        pThis->m_aInputEvent.maText             = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
        pThis->m_aInputEvent.mnCursorPos        = pThis->m_aInputEvent.maText.getLength();
        pThis->m_aInputEvent.mnCursorFlags      = 0;

        pThis->m_aInputFlags.clear();

        /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
         *  which is logical and consequent. But since even simple input like
         *  <space> comes through the commit signal instead of signalKey
         *  and all kinds of windows only implement KeyInput (e.g. PushButtons,
         *  RadioButtons and a lot of other Controls), will send a single
         *  KeyInput/KeyUp sequence instead of an ExtText event if there
         *  never was a preedit and the text is only one character.
         *
         *  In this case there the last ExtText event must have been
         *  SalEvent::EndExtTextInput, either because of a regular commit
         *  or because there never was a preedit.
         */
        bool bSingleCommit = false;
        if( ! bWasPreedit
            && pThis->m_aInputEvent.maText.getLength() == 1
            && ! pThis->m_aPrevKeyPresses.empty()
            )
        {
            const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
            sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];

            if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
            {
                pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
                bSingleCommit = true;
            }
        }
        if( ! bSingleCommit )
        {
            pThis->m_pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
            if( ! aDel.isDeleted() )
                pThis->doCallEndExtTextInput();
        }
        if( ! aDel.isDeleted() )
        {
            // reset input event
            pThis->m_aInputEvent.maText.clear();
            pThis->m_aInputEvent.mnCursorPos = 0;
            pThis->updateIMSpotLocation();
        }
    }
#ifdef __sun
    // #i51356# workaround a solaris IIIMP bug
    // in case of partial commits the preedit changed signal
    // and commit signal come in wrong order
    if( ! aDel.isDeleted() )
        signalIMPreeditChanged( pContext, im_handler );
#else
    (void) pContext;
#endif
}

void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext*, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    char*           pText           = nullptr;
    PangoAttrList*  pAttrs          = nullptr;
    gint            nCursorPos      = 0;

    gtk_im_context_get_preedit_string( pThis->m_pIMContext,
                                       &pText,
                                       &pAttrs,
                                       &nCursorPos );
    if( pText && ! *pText ) // empty string
    {
        // change from nothing to nothing -> do not start preedit
        // e.g. this will activate input into a calc cell without
        // user input
        if( pThis->m_aInputEvent.maText.getLength() == 0 )
        {
            g_free( pText );
            pango_attr_list_unref( pAttrs );
            return;
        }
    }

    pThis->m_bPreeditJustChanged = true;

    bool bEndPreedit = (!pText || !*pText) && pThis->m_aInputEvent.mpTextAttr != nullptr;
    pThis->m_aInputEvent.maText             = pText ? OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 ) : OUString();
    pThis->m_aInputEvent.mnCursorPos        = nCursorPos;
    pThis->m_aInputEvent.mnCursorFlags      = 0;

    pThis->m_aInputFlags = std::vector<ExtTextInputAttr>( std::max( 1, static_cast<int>(pThis->m_aInputEvent.maText.getLength()) ), ExtTextInputAttr::NONE );

    PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
    do
    {
        GSList *attr_list = nullptr;
        GSList *tmp_list = nullptr;
        gint start, end;
        ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;

        pango_attr_iterator_range (iter, &start, &end);
        if (start == G_MAXINT || end == G_MAXINT)
        {
            auto len = pText ? g_utf8_strlen(pText, -1) : 0;
            if (end == G_MAXINT)
                end = len;
            if (start == G_MAXINT)
                start = len;
        }
        if (end == start)
            continue;

        start = g_utf8_pointer_to_offset (pText, pText + start);
        end = g_utf8_pointer_to_offset (pText, pText + end);

        tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
        while (tmp_list)
        {
            PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);

            switch (pango_attr->klass->type)
            {
                case PANGO_ATTR_BACKGROUND:
                    sal_attr |= ExtTextInputAttr::Highlight;
                    pThis->m_aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
                    break;
                case PANGO_ATTR_UNDERLINE:
                    sal_attr |= ExtTextInputAttr::Underline;
                    break;
                case PANGO_ATTR_STRIKETHROUGH:
                    sal_attr |= ExtTextInputAttr::RedText;
                    break;
                default:
                    break;
            }
            pango_attribute_destroy (pango_attr);
            tmp_list = tmp_list->next;
        }
        if (sal_attr == ExtTextInputAttr::NONE)
            sal_attr |= ExtTextInputAttr::Underline;
        g_slist_free (attr_list);

        // Set the sal attributes on our text
        for (int i = start; i < end; ++i)
        {
            SAL_WARN_IF(i >= static_cast<int>(pThis->m_aInputFlags.size()),
                "vcl.gtk", "pango attrib out of range. Broken range: "
                << start << "," << end << " Legal range: 0,"
                << pThis->m_aInputFlags.size());
            if (i >= static_cast<int>(pThis->m_aInputFlags.size()))
                continue;
            pThis->m_aInputFlags[i] |= sal_attr;
        }
    } while (pango_attr_iterator_next (iter));
    pango_attr_iterator_destroy(iter);

    pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();

    g_free( pText );
    pango_attr_list_unref( pAttrs );

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );

    pThis->m_pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
    if( bEndPreedit && ! aDel.isDeleted() )
        pThis->doCallEndExtTextInput();
    if( ! aDel.isDeleted() )
        pThis->updateIMSpotLocation();
}

void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
{
}

void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    pThis->m_bPreeditJustChanged = true;

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );
    pThis->doCallEndExtTextInput();
    if( ! aDel.isDeleted() )
        pThis->updateIMSpotLocation();
}

static uno::Reference<accessibility::XAccessibleEditableText> lcl_GetxText(vcl::Window *pFocusWin)
{
    uno::Reference<accessibility::XAccessibleEditableText> xText;
    try
    {
        uno::Reference< accessibility::XAccessible > xAccessible( pFocusWin->GetAccessible() );
        if (xAccessible.is())
            xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
    }
    catch(const uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION( "vcl.gtk", "Exception in getting input method surrounding text");
    }
    return xText;
}

gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer /*im_handler*/ )
{
    vcl::Window *pFocusWin = Application::GetFocusWindow();
    if (!pFocusWin)
        return true;

    uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
    if (xText.is())
    {
        sal_Int32 nPosition = xText->getCaretPosition();
        OUString sAllText = xText->getText();
        OString sUTF = OUStringToOString(sAllText, RTL_TEXTENCODING_UTF8);
        OUString sCursorText(sAllText.copy(0, nPosition));
        gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
            OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
        return true;
    }

    return false;
}

gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
    gpointer /*im_handler*/ )
{
    vcl::Window *pFocusWin = Application::GetFocusWindow();
    if (!pFocusWin)
        return true;

    uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
    if (xText.is())
    {
        sal_Int32 nPosition = xText->getCaretPosition();
        // #i111768# range checking
        sal_Int32 nDeletePos = nPosition + offset;
        sal_Int32 nDeleteEnd = nDeletePos + nchars;
        if (nDeletePos < 0)
            nDeletePos = 0;
        if (nDeleteEnd < 0)
            nDeleteEnd = 0;
        if (nDeleteEnd > xText->getCharacterCount())
            nDeleteEnd = xText->getCharacterCount();

        xText->deleteText(nDeletePos, nDeleteEnd);
        //tdf91641 adjust cursor if deleted chars shift it forward (normal case)
        if (nDeletePos < nPosition)
        {
            if (nDeleteEnd <= nPosition)
                nPosition = nPosition - (nDeleteEnd - nDeletePos);
            else
                nPosition = nDeletePos;

            if (xText->getCharacterCount() >= nPosition)
                xText->setCaretPosition( nPosition );
        }
        return true;
    }

    return false;
}

Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
{
    tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
    return Size( aRect.GetWidth(), aRect.GetHeight() );
}

sal_uIntPtr GtkSalFrame::GetNativeWindowHandle()
{
    return widget_get_xid(m_pWindow);
}

GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
{
    GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
    event->key.window = GDK_WINDOW(g_object_ref(gtk_widget_get_window(pWidget)));
    event->key.send_event = 1 /* TRUE */;
    event->key.time = gtk_get_current_event_time();
    event->key.state = 0;
    event->key.keyval = 0;
    event->key.length = 0;
    event->key.string = nullptr;
    event->key.hardware_keycode = 0;
    event->key.group = 0;
    event->key.is_modifier = false;
    return event;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx
deleted file mode 100644
index d050351..0000000
--- a/vcl/unx/gtk/gtksalmenu.cxx
+++ /dev/null
@@ -1,1444 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <unx/gtk/gtksalmenu.hxx>

#ifdef ENABLE_GMENU_INTEGRATION

#include <unx/gendata.hxx>
#include <unx/saldisp.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/glomenu.h>
#include <unx/gtk/gloactiongroup.h>
#include <vcl/floatwin.hxx>
#include <vcl/menu.hxx>
#include <vcl/pngwrite.hxx>
#include <unx/gtk/gtkinst.hxx>

#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <window.h>
#include <strings.hrc>

static bool bUnityMode = false;

/*
 * This function generates a unique command name for each menu item
 */
static gchar* GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
{
    OString aCommand("window-");
    aCommand = aCommand + OString::number(reinterpret_cast<unsigned long>(pParentMenu));
    aCommand = aCommand + "-" + OString::number(nItemId);
    return g_strdup(aCommand.getStr());
}

static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
{
    return GetCommandForItem(pSalMenuItem->mpParentMenu,
                             pSalMenuItem->mnId);
}

bool GtkSalMenu::PrepUpdate()
{
#if GTK_CHECK_VERSION(3,0,0)
    return mpMenuModel && mpActionGroup;
#else
    return bUnityMode && mpMenuModel && mpActionGroup;
#endif
}

/*
 * Menu updating methods
 */

static void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nSection, unsigned nValidItems )
{
    sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );

    while ( nSectionItems > static_cast<sal_Int32>(nValidItems) )
    {
        gchar* aCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, --nSectionItems );

        if ( aCommand != nullptr && pOldCommandList != nullptr )
            *pOldCommandList = g_list_append( *pOldCommandList, g_strdup( aCommand ) );

        g_free( aCommand );

        g_lo_menu_remove_from_section( pMenu, nSection, nSectionItems );
    }
}

typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;

namespace
{
    MenuAndId decode_command(const gchar *action_name)
    {
        OString sCommand(action_name);

        sal_Int32 nIndex = 0;
        OString sWindow = sCommand.getToken(0, '-', nIndex);
        OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
        OString sItemId = sCommand.getToken(0, '-', nIndex);

        GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());

        assert(sWindow == "window" && pSalSubMenu);
        (void) sWindow;

        return MenuAndId(pSalSubMenu, sItemId.toInt32());
    }
}

static void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList,
                                       sal_Int32 nSection, GActionGroup* pActionGroup)
{
    while (nSection >= 0)
    {
        sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
        while (nSectionItems--)
        {
            gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems);
            // remove disabled entries
            bool bRemove = !g_action_group_get_action_enabled(pActionGroup, pCommand);
            if (!bRemove)
            {
                //also remove any empty submenus
                GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems);
                if (pSubMenuModel)
                {
                    gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel));
                    if (nSubMenuSections == 0)
                        bRemove = true;
                    else if (nSubMenuSections == 1)
                    {
                        gint nItems = g_lo_menu_get_n_items_from_section(pSubMenuModel, 0);
                        if (nItems == 0)
                            bRemove = true;
                        else if (nItems == 1)
                        {
                            //If the only entry is the "No Selection Possible" entry, then we are allowed
                            //to removed it
                            gchar* pSubCommand = g_lo_menu_get_command_from_item_in_section(pSubMenuModel, 0, 0);
                            MenuAndId aMenuAndId(decode_command(pSubCommand));
                            bRemove = aMenuAndId.second == 0xFFFF;
                            g_free(pSubCommand);
                        }
                    }
                }
            }

            if (bRemove)
            {
                //but tdf#86850 Always display clipboard functions
                bRemove = g_strcmp0(pCommand, ".uno:Cut") &&
                          g_strcmp0(pCommand, ".uno:Copy") &&
                          g_strcmp0(pCommand, ".uno:Paste");
            }

            if (bRemove)
            {
                if (pCommand != nullptr && pOldCommandList != nullptr)
                    *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand));
                g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems);
            }

            g_free(pCommand);
        }
        --nSection;
    }
}

static void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection )
{
    if ( pMenu == nullptr || pOldCommandList == nullptr )
        return;

    sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1;

    for ( ; n > nLastSection; n--)
    {
        RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 );
        g_lo_menu_remove( pMenu, n );
    }
}

static gint CompareStr( gpointer str1, gpointer str2 )
{
    return g_strcmp0( static_cast<const gchar*>(str1), static_cast<const gchar*>(str2) );
}

static void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, GList* pNewCommandList )
{
    if ( pActionGroup == nullptr || pOldCommandList == nullptr )
    {
        g_list_free_full( pOldCommandList, g_free );
        g_list_free_full( pNewCommandList, g_free );
        return;
    }

    while ( pNewCommandList != nullptr )
    {
        GList* pNewCommand = g_list_first( pNewCommandList );
        pNewCommandList = g_list_remove_link( pNewCommandList, pNewCommand );

        gpointer aCommand = g_list_nth_data( pNewCommand, 0 );

        GList* pOldCommand = g_list_find_custom( pOldCommandList, aCommand, reinterpret_cast<GCompareFunc>(CompareStr) );

        if ( pOldCommand != nullptr )
        {
            pOldCommandList = g_list_remove_link( pOldCommandList, pOldCommand );
            g_list_free_full( pOldCommand, g_free );
        }

        g_list_free_full( pNewCommand, g_free );
    }

    while ( pOldCommandList != nullptr )
    {
        GList* pCommand = g_list_first( pOldCommandList );
        pOldCommandList = g_list_remove_link( pOldCommandList, pCommand );

        gchar* aCommand = static_cast<gchar*>(g_list_nth_data( pCommand, 0 ));

        g_lo_action_group_remove( pActionGroup, aCommand );

        g_list_free_full( pCommand, g_free );
    }
}

void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
{
    SolarMutexGuard aGuard;

    SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate");
    if( !PrepUpdate() )
        return;

    if (mbNeedsUpdate)
    {
        mbNeedsUpdate = false;
        if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
        {
            maUpdateMenuBarIdle.Stop();
            maUpdateMenuBarIdle.Invoke();
            return;
        }
    }

    Menu* pVCLMenu = mpVCLMenu;
    GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
    SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
    GList *pOldCommandList = nullptr;
    GList *pNewCommandList = nullptr;

    sal_uInt16 nLOMenuSize = g_menu_model_get_n_items( G_MENU_MODEL( pLOMenu ) );

    if ( nLOMenuSize == 0 )
        g_lo_menu_new_section( pLOMenu, 0, nullptr );

    sal_Int32 nSection = 0;
    sal_Int32 nItemPos = 0;
    sal_Int32 validItems = 0;
    sal_Int32 nItem;

    for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) {
        if ( !IsItemVisible( nItem ) )
            continue;

        GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
        sal_uInt16 nId = pSalMenuItem->mnId;

        // PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level
        // popup menu, but we have our own implementation below, so skip that one.
        if ( nId == 0xFFFF )
            continue;

        if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
        {
            // Delete extra items from current section.
            RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );

            nSection++;
            nItemPos = 0;
            validItems = 0;

            if ( nLOMenuSize <= nSection )
            {
                g_lo_menu_new_section( pLOMenu, nSection, nullptr );
                nLOMenuSize++;
            }

            continue;
        }

        if ( nItemPos >= g_lo_menu_get_n_items_from_section( pLOMenu, nSection ) )
            g_lo_menu_insert_in_section( pLOMenu, nSection, nItemPos, "EMPTY STRING" );

        // Get internal menu item values.
        OUString aText = pVCLMenu->GetItemText( nId );
        Image aImage = pVCLMenu->GetItemImage( nId );
        bool bEnabled = pVCLMenu->IsItemEnabled( nId );
        vcl::KeyCode nAccelKey = pVCLMenu->GetAccelKey( nId );
        bool bChecked = pVCLMenu->IsItemChecked( nId );
        MenuItemBits itemBits = pVCLMenu->GetItemBits( nId );

        // Store current item command in command list.
        gchar *aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pLOMenu, nSection, nItemPos );

        if ( aCurrentCommand != nullptr )
            pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );

        // Get the new command for the item.
        gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);

        // Force updating of native menu labels.
        NativeSetItemText( nSection, nItemPos, aText );
        NativeSetItemIcon( nSection, nItemPos, aImage );
        NativeSetAccelerator( nSection, nItemPos, nAccelKey, nAccelKey.GetName( GetFrame()->GetWindow() ) );

        if ( g_strcmp0( aNativeCommand, "" ) != 0 && pSalMenuItem->mpSubMenu == nullptr )
        {
            NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, bChecked, false );
            NativeCheckItem( nSection, nItemPos, itemBits, bChecked );
            NativeSetEnableItem( aNativeCommand, bEnabled );

            pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
        }

        GtkSalMenu* pSubmenu = pSalMenuItem->mpSubMenu;

        if ( pSubmenu && pSubmenu->GetMenu() )
        {
            bool bNonMenuChangedToMenu = NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, false, true );
            pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );

            GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );

            if ( pSubMenuModel == nullptr )
            {
                g_lo_menu_new_submenu_in_item_in_section( pLOMenu, nSection, nItemPos );
                pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
            }

            g_object_unref( pSubMenuModel );

            if (bRecurse || bNonMenuChangedToMenu)
            {
                SAL_INFO("vcl.unity", "preparing submenu  " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
                pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
                pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
                pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
            }
        }

        g_free( aNativeCommand );

        ++nItemPos;
        ++validItems;
    }

    if (bRemoveDisabledEntries)
    {
        // Delete disabled items in last section.
        RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
    }

    // Delete extra items in last section.
    RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );

    // Delete extra sections.
    RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );

    // Delete unused commands.
    RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );

    // Resolves: tdf#103166 if the menu is empty, add a disabled
    // <No Selection Possible> placeholder.
    sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
    gint nItemsCount = 0;
    for (nSection = 0; nSection < nSectionsCount; ++nSection)
    {
        nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
        if (nItemsCount)
            break;
    }
    if (!nItemsCount)
    {
        gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
        OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
        g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
                                    OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
        NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
        NativeSetEnableItem(aNativeCommand, false);
        g_free(aNativeCommand);
    }
}

void GtkSalMenu::Update()
{
    //find out if top level is a menubar or not, if not, then it's a popup menu
    //hierarchy and in those we hide (most) disabled entries
    const GtkSalMenu* pMenu = this;
    while (pMenu->mpParentSalMenu)
        pMenu = pMenu->mpParentSalMenu;
    ImplUpdate(false, !pMenu->mbMenuBar);
}

#if GTK_CHECK_VERSION(3,0,0)
static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
{
    Point *pPos = static_cast<Point*>(user_data);
    *x = pPos->X();
    if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
    {
        GtkRequisition natural_size;
        gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
        *x -= natural_size.width;
    }
    *y = pPos->Y();
    *push_in = false;
}
#endif

bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
                                     FloatWinPopupFlags nFlags)
{
#if GTK_CHECK_VERSION(3,0,0)
    VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
    mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());

    GLOActionGroup* pActionGroup = g_lo_action_group_new();
    mpActionGroup = G_ACTION_GROUP(pActionGroup);
    mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
    // Generate the main menu structure, populates mpMenuModel
    UpdateFull();

    GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
    gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
    gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);

    //run in a sub main loop because we need to keep vcl PopupMenu alive to use
    //it during DispatchCommand, returning now to the outer loop causes the
    //launching PopupMenu to be destroyed, instead run the subloop here
    //until the gtk menu is destroyed
    GMainLoop* pLoop = g_main_loop_new(nullptr, true);
    g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);

#if GTK_CHECK_VERSION(3,22,0)
    if (gtk_check_version(3, 22, 0) == nullptr)
    {
        GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;

        if (nFlags & FloatWinPopupFlags::Left)
        {
            rect_anchor = GDK_GRAVITY_NORTH_WEST;
            menu_anchor = GDK_GRAVITY_NORTH_EAST;
        }
        else if (nFlags & FloatWinPopupFlags::Up)
        {
            rect_anchor = GDK_GRAVITY_NORTH_WEST;
            menu_anchor = GDK_GRAVITY_SOUTH_WEST;
        }
        else if (nFlags & FloatWinPopupFlags::Right)
        {
            rect_anchor = GDK_GRAVITY_NORTH_EAST;
        }

        tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
        aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
        GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
                           static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};

        GdkWindow* gdkWindow = widget_get_window(mpFrame->getMouseEventWidget());
        gtk_menu_popup_at_rect(GTK_MENU(pWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
    }
    else
#endif
    {
        guint nButton;
        guint32 nTime;

        //typically there is an event, and we can then distinguish if this was
        //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
        //doesn't)
        GdkEvent *pEvent = gtk_get_current_event();
        if (pEvent)
        {
            gdk_event_get_button(pEvent, &nButton);
            nTime = gdk_event_get_time(pEvent);
        }
        else
        {
            nButton = 0;
            nTime = GtkSalFrame::GetLastInputEventTime();
        }

        // do the same strange semantics as vcl popup windows to arrive at a frame geometry
        // in mirrored UI case; best done by actually executing the same code
        sal_uInt16 nArrangeIndex;
        Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
        aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);

        gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
                       &aPos, nButton, nTime);
    }

    if (g_main_loop_is_running(pLoop))
    {
        gdk_threads_leave();
        g_main_loop_run(pLoop);
        gdk_threads_enter();
    }
    g_main_loop_unref(pLoop);

    mpVCLMenu->Deactivate();

    gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);

    gtk_widget_destroy(pWidget);

    g_object_unref(mpActionGroup);
    ClearActionGroupAndMenuModel();

    mpFrame = nullptr;

    return true;
#else
    (void)pWin;
    (void)rRect;
    (void)nFlags;
    return false;
#endif
}

/*
 * GtkSalMenu
 */

GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
    mbInActivateCallback( false ),
    mbMenuBar( bMenuBar ),
    mbNeedsUpdate( false ),
    mbReturnFocusToDocument( false ),
    mbAddedGrab( false ),
    mpMenuBarContainerWidget( nullptr ),
    mpMenuAllowShrinkWidget( nullptr ),
    mpMenuBarWidget( nullptr ),
    mpMenuBarContainerProvider( nullptr ),
    mpMenuBarProvider( nullptr ),
    mpCloseButton( nullptr ),
    mpVCLMenu( nullptr ),
    mpParentSalMenu( nullptr ),
    mpFrame( nullptr ),
    mpMenuModel( nullptr ),
    mpActionGroup( nullptr )
{
    //typically this only gets called after the menu has been customized on the
    //next idle slot, in the normal case of a new menubar SetFrame is called
    //directly long before this idle would get called.
    maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
    maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
    maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
}

IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
{
    SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
    if (!mpFrame)
        return;
    SetFrame(mpFrame);
}

void GtkSalMenu::SetNeedsUpdate()
{
    GtkSalMenu* pMenu = this;
    // start that the menu and its parents are in need of an update
    // on the next activation
    while (pMenu && !pMenu->mbNeedsUpdate)
    {
        pMenu->mbNeedsUpdate = true;
        pMenu = pMenu->mpParentSalMenu;
    }
    // only if a menubar is directly updated do we force in a full
    // structure update
    if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
        maUpdateMenuBarIdle.Start();
}

void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
{
    if (mpMenuModel)
        g_object_unref(mpMenuModel);
    mpMenuModel = pMenuModel;
    if (mpMenuModel)
        g_object_ref(mpMenuModel);
}

GtkSalMenu::~GtkSalMenu()
{
    SolarMutexGuard aGuard;

    DestroyMenuBarWidget();

    if (mpMenuModel)
        g_object_unref(mpMenuModel);

    maItems.clear();

    if (mpFrame)
        mpFrame->SetMenu(nullptr);
}

bool GtkSalMenu::VisibleMenuBar()
{
    return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
}

void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
{
    SolarMutexGuard aGuard;
    GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );

    if ( nPos == MENU_APPEND )
        maItems.push_back( pItem );
    else
        maItems.insert( maItems.begin() + nPos, pItem );

    pItem->mpParentMenu = this;

    SetNeedsUpdate();
}

void GtkSalMenu::RemoveItem( unsigned nPos )
{
    SolarMutexGuard aGuard;
    maItems.erase( maItems.begin() + nPos );
    SetNeedsUpdate();
}

void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
{
    SolarMutexGuard aGuard;
    GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
    GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );

    if ( pGtkSubMenu == nullptr )
        return;

    pGtkSubMenu->mpParentSalMenu = this;
    pItem->mpSubMenu = pGtkSubMenu;

    SetNeedsUpdate();
}

#if GTK_CHECK_VERSION(3,0,0)
static void CloseMenuBar(GtkWidget *, gpointer pMenu)
{
    Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
}
#endif

void GtkSalMenu::ShowCloseButton(bool bShow)
{
#if GTK_CHECK_VERSION(3,0,0)
    assert(mbMenuBar);
    if (!mpMenuBarContainerWidget)
        return;

    if (!bShow)
    {
        if (mpCloseButton)
            gtk_widget_destroy(mpCloseButton);
        return;
    }

    MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
    mpCloseButton = gtk_button_new();
    g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);

    gtk_button_set_relief(GTK_BUTTON(mpCloseButton), GTK_RELIEF_NONE);
    gtk_button_set_focus_on_click(GTK_BUTTON(mpCloseButton), false);
    gtk_widget_set_can_focus(mpCloseButton, false);

    GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(mpCloseButton));

    GtkCssProvider *pProvider = gtk_css_provider_new();
    static const gchar data[] = "* { "
      "padding: 0;"
      "margin-left: 8px;"
      "margin-right: 8px;"
      "min-width: 18px;"
      "min-height: 18px;"
      "}";
    const gchar olddata[] = "* { "
      "padding: 0;"
      "margin-left: 8px;"
      "margin-right: 8px;"
      "}";
    gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
    gtk_style_context_add_provider(pButtonContext,
                                   GTK_STYLE_PROVIDER(pProvider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    gtk_style_context_add_class(pButtonContext, "flat");
    gtk_style_context_add_class(pButtonContext, "small-button");

    GIcon* icon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
    GtkWidget* image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
    gtk_widget_show(image);
    g_object_unref(icon);

    OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
    gtk_widget_set_tooltip_text(mpCloseButton,
        OUStringToOString(sToolTip, RTL_TEXTENCODING_UTF8).getStr());

    gtk_widget_set_valign(mpCloseButton, GTK_ALIGN_CENTER);

    gtk_container_add(GTK_CONTAINER(mpCloseButton), image);
    gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), GTK_WIDGET(mpCloseButton), 1, 0, 1, 1);
    gtk_widget_show_all(mpCloseButton);
#else
    (void)bShow;
    (void)mpMenuBarContainerWidget;
    (void)mpCloseButton;
#endif
}

//Typically when the menubar is deactivated we want the focus to return
//to where it came from. If the menubar was activated because of F6
//moving focus into the associated VCL menubar then on pressing ESC
//or any other normal reason for deactivation we want focus to return
//to the document, definitely not still stuck in the associated
//VCL menubar. But if F6 is pressed while the menubar is activated
//we want to pass that F6 back to the VCL menubar which will move
//focus to the next pane by itself.
void GtkSalMenu::ReturnFocus()
{
    if (mbAddedGrab)
    {
        gtk_grab_remove(mpMenuBarWidget);
        mbAddedGrab = false;
    }
    if (!mbReturnFocusToDocument)
        gtk_widget_grab_focus(GTK_WIDGET(mpFrame->getEventBox()));
    else
        mpFrame->GetWindow()->GrabFocusToDocument();
    mbReturnFocusToDocument = false;
}

gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
{
    if (pEvent->keyval == GDK_KEY_F6)
    {
        mbReturnFocusToDocument = false;
        gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
        //because we return false here, the keypress will continue
        //to propagate and in the case that vcl focus is in
        //the vcl menubar then that will also process F6 and move
        //to the next pane
    }
    return false;
}

//The GtkSalMenu is owner by a Vcl Menu/MenuBar. In the menubar
//case the vcl menubar is present and "visible", but with a 0 height
//so it not apparent. Normally it acts as though it is not there when
//a Native menubar is active. If we return true here, then for keyboard
//activation and traversal with F6 through panes then the vcl menubar
//acts as though it *is* present and we translate its take focus and F6
//traversal key events into the gtk menubar equivalents.
bool GtkSalMenu::CanGetFocus() const
{
    return mpMenuBarWidget != nullptr;
}

bool GtkSalMenu::TakeFocus()
{
    if (!mpMenuBarWidget)
        return false;

    //Send a keyboard event to the gtk menubar to let it know it has been
    //activated via the keyboard. Doesn't do anything except cause the gtk
    //menubar "keyboard_mode" member to get set to true, so typically mnemonics
    //are shown which will serve as indication that the menubar has focus
    //(given that we want to show it with no menus popped down)
    GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
    gtk_widget_event(mpMenuBarWidget, event);
    gdk_event_free(event);

    //this pairing results in a menubar with keyboard focus with no menus
    //auto-popped down
    gtk_grab_add(mpMenuBarWidget);
    mbAddedGrab = true;
    gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
    gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
    mbReturnFocusToDocument = true;
    return true;
}

#if GTK_CHECK_VERSION(3,0,0)

static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
{
    GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
    GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
    pMenu->ReturnFocus();
}

static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
{
    GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
    return pMenu->SignalKey(pEvent);
}

#endif

void GtkSalMenu::CreateMenuBarWidget()
{
#if GTK_CHECK_VERSION(3,0,0)
    if (mpMenuBarContainerWidget)
        return;

    GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
    mpMenuBarContainerWidget = gtk_grid_new();

    gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
    gtk_grid_insert_row(pGrid, 0);
    gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);

    mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
    // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
    // but still allow scrolled window to not be sized to the child content.
    // So the menubar can be shrunk past its nominal smallest width.
    // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
    gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);

    mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);

    gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
    gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
    gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
    gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);

    g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
    g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);

    gtk_widget_show_all(mpMenuBarContainerWidget);

    ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );

    ApplyPersona();
#else
    (void)mpMenuAllowShrinkWidget;
    (void)mpMenuBarContainerWidget;
#endif
}

void GtkSalMenu::ApplyPersona()
{
#if GTK_CHECK_VERSION(3,0,0)
    if (!mpMenuBarContainerWidget)
        return;
    assert(mbMenuBar);
    // I'm dubious about the persona theming feature, but as it exists, lets try and support
    // it, apply the image to the mpMenuBarContainerWidget
    const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();

    GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
    if (mpMenuBarContainerProvider)
    {
        gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
        mpMenuBarContainerProvider = nullptr;
    }
    GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
    if (mpMenuBarProvider)
    {
        gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
        mpMenuBarProvider = nullptr;
    }

    if (!rPersonaBitmap.IsEmpty())
    {
        if (maPersonaBitmap != rPersonaBitmap)
        {
            vcl::PNGWriter aPNGWriter(rPersonaBitmap);
            mxPersonaImage.reset(new utl::TempFile);
            mxPersonaImage->EnableKillingFile(true);
            SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
            aPNGWriter.Write(*pStream);
            mxPersonaImage->CloseStream();
        }

        mpMenuBarContainerProvider = gtk_css_provider_new();
        OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
        OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
        gtk_css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength(), nullptr);
        gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);


        // force the menubar to be transparent when persona is active otherwise for
        // me the menubar becomes gray when its in the backdrop
        mpMenuBarProvider = gtk_css_provider_new();
        static const gchar data[] = "* { "
          "background-image: none;"
          "background-color: transparent;"
          "}";
        gtk_css_provider_load_from_data(mpMenuBarProvider, data, -1, nullptr);
        gtk_style_context_add_provider(pMenuBarContext,
                                       GTK_STYLE_PROVIDER(mpMenuBarProvider),
                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    }
    maPersonaBitmap = rPersonaBitmap;
#else
    (void)maPersonaBitmap;
    (void)mpMenuBarContainerProvider;
    (void)mpMenuBarProvider;
#endif
}

void GtkSalMenu::DestroyMenuBarWidget()
{
#if GTK_CHECK_VERSION(3,0,0)
    if (mpMenuBarContainerWidget)
    {
        gtk_widget_destroy(mpMenuBarContainerWidget);
        mpMenuBarContainerWidget = nullptr;
        mpCloseButton = nullptr;
    }
#else
    (void)mpMenuBarContainerWidget;
#endif
}

void GtkSalMenu::SetFrame(const SalFrame* pFrame)
{
    SolarMutexGuard aGuard;
    assert(mbMenuBar);
    SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
    mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));

    // if we had a menu on the GtkSalMenu we have to free it as we generate a
    // full menu anyway and we might need to reuse an existing model and
    // actiongroup
    mpFrame->SetMenu( this );
    mpFrame->EnsureAppMenuWatch();

    // Clean menu model and action group if needed.
    GtkWidget* pWidget = mpFrame->getWindow();
    GdkWindow* gdkWindow = gtk_widget_get_window( pWidget );

    GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
    SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);

    if ( pMenuModel )
    {
        if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
            g_lo_menu_remove( pMenuModel, 0 );

        mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
    }

    if ( pActionGroup )
    {
        g_lo_action_group_clear( pActionGroup );
        mpActionGroup = G_ACTION_GROUP( pActionGroup );
    }

    // Generate the main menu structure.
    if ( PrepUpdate() )
        UpdateFull();

    g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );

    if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
    {
        DestroyMenuBarWidget();
        CreateMenuBarWidget();
    }
}

const GtkSalFrame* GtkSalMenu::GetFrame() const
{
    SolarMutexGuard aGuard;
    const GtkSalMenu* pMenu = this;
    while( pMenu && ! pMenu->mpFrame )
        pMenu = pMenu->mpParentSalMenu;
    return pMenu ? pMenu->mpFrame : nullptr;
}

void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
{
    SolarMutexGuard aGuard;

    if ( mpActionGroup == nullptr )
        return;

    gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );

    if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
    {
        GVariant *pCheckValue = nullptr;
        GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );

        if ( bits & MenuItemBits::RADIOCHECK )
            pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
        else
        {
            // By default, all checked items are checkmark buttons.
            if (bCheck || pCurrentState != nullptr)
                pCheckValue = g_variant_new_boolean( bCheck );
        }

        if ( pCheckValue != nullptr )
        {
            if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
            {
                g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
            }
            else
            {
                g_variant_unref (pCheckValue);
            }
        }

        if ( pCurrentState != nullptr )
            g_variant_unref( pCurrentState );
    }

    if ( aCommand )
        g_free( aCommand );
}

void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
{
    SolarMutexGuard aGuard;
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );

    if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
        g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
}

void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
{
    SolarMutexGuard aGuard;
    // Escape all underscores so that they don't get interpreted as hotkeys
    OUString aText = rText.replaceAll( "_", "__" );
    // Replace the LibreOffice hotkey identifier with an underscore
    aText = aText.replace( '~', '_' );
    OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );

    // Update item text only when necessary.
    gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );

    if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
        g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );

    if ( aLabel )
        g_free( aLabel );
}

namespace
{
    void DestroyMemoryStream(gpointer data)
    {
        SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
        delete pMemStm;
    }
}

void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
{
#if GLIB_CHECK_VERSION(2,38,0)
    if (!rImage && mbHasNullItemIcon)
        return;

    SolarMutexGuard aGuard;

    if (!!rImage)
    {
        SvMemoryStream* pMemStm = new SvMemoryStream;
        vcl::PNGWriter aWriter(rImage.GetBitmapEx());
        aWriter.Write(*pMemStm);

        GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
                                                    pMemStm->TellEnd(),
                                                    DestroyMemoryStream,
                                                    pMemStm);

        GIcon *pIcon = g_bytes_icon_new(pBytes);

        g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
        g_object_unref(pIcon);
        g_bytes_unref(pBytes);
        mbHasNullItemIcon = false;
    }
    else
    {
        g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
        mbHasNullItemIcon = true;
    }
#else
    (void)nSection;
    (void)nItemPos;
    (void)rImage;
#endif
}

void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, const OUString& rKeyName )
{
    SolarMutexGuard aGuard;

    if ( rKeyName.isEmpty() )
        return;

    guint nKeyCode;
    GdkModifierType nModifiers;
    GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);

    gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );

    gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );

    if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
        g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );

    g_free( aAccelerator );
    g_free( aCurrentAccel );
}

bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
                                       unsigned nItemPos,
                                       sal_uInt16 nId,
                                       const gchar* aCommand,
                                       MenuItemBits nBits,
                                       bool bChecked,
                                       bool bIsSubmenu )
{
    bool bSubMenuAddedOrRemoved = false;

    SolarMutexGuard aGuard;
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );

    GVariant *pTarget = nullptr;

    if (g_action_group_has_action(mpActionGroup, aCommand))
        g_lo_action_group_remove(pActionGroup, aCommand);

    if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
    {
        // Item is a checkmark button.
        GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
        GVariant* pState = g_variant_new_boolean( bChecked );

        g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
    }
    else if ( nBits & MenuItemBits::RADIOCHECK )
    {
        // Item is a radio button.
        GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
        GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
        GVariant* pState = g_variant_new_string( "" );
        pTarget = g_variant_new_string( aCommand );

        g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
    }
    else
    {
        // Item is not special, so insert a stateless action.
        g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
    }

    GLOMenu* pMenu = G_LO_MENU( mpMenuModel );

    // Menu item is not updated unless it's necessary.
    gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );

    if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
    {
        bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
        bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
        if (bSubMenuAddedOrRemoved)
        {
            //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
            //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
            //support achieving that
            gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
            g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
            g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
            g_free(pLabel);
        }

        g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );

        gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );

        if ( bIsSubmenu )
            g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
        else
        {
            g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
            pTarget = nullptr;
        }

        g_free( aItemCommand );
    }

    if ( aCurrentCommand )
        g_free( aCurrentCommand );

    if (pTarget)
        g_variant_unref(pTarget);

    return bSubMenuAddedOrRemoved;
}

GtkSalMenu* GtkSalMenu::GetTopLevel()
{
    GtkSalMenu *pMenu = this;
    while (pMenu->mpParentSalMenu)
        pMenu = pMenu->mpParentSalMenu;
    return pMenu;
}

void GtkSalMenu::DispatchCommand(const gchar *pCommand)
{
    SolarMutexGuard aGuard;
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalSubMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
    if (pTopLevel->mpMenuBarWidget)
    {
        // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
        // closing the menu. To handle this properly I imagine we need to set groups for the
        // radiobuttons so the others visually untoggle when the active one is toggled and
        // we would further need to teach vcl that the state can change more than once.
        //
        // or we could unconditionally deactivate the menus if regardless of what particular
        // type of menu item got activated
        gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
    }
    pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
}

void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
{
    for (GtkSalMenuItem* pSalItem : maItems)
    {
        if ( pSalItem->mpSubMenu != nullptr )
        {
            // We can re-enter this method via the new event loop that gets created
            // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
            // flag to detect that and skip some startup work.
            if (!pSalItem->mpSubMenu->mbInActivateCallback)
            {
                pSalItem->mpSubMenu->mbInActivateCallback = true;
                pMenuBar->HandleMenuActivateEvent(pSalItem->mpSubMenu->GetMenu());
                pSalItem->mpSubMenu->mbInActivateCallback = false;
                pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
                pSalItem->mpSubMenu->Update();
                pMenuBar->HandleMenuDeActivateEvent(pSalItem->mpSubMenu->GetMenu());
            }
        }
    }
}

void GtkSalMenu::ClearActionGroupAndMenuModel()
{
    SetMenuModel(nullptr);
    mpActionGroup = nullptr;
    for (GtkSalMenuItem* pSalItem : maItems)
    {
        if ( pSalItem->mpSubMenu != nullptr )
        {
            pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
        }
    }
}

void GtkSalMenu::Activate(const gchar* pCommand)
{
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
    Menu* pVclMenu = pSalMenu->GetMenu();
    Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
    GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;

    pSubMenu->mbInActivateCallback = true;
    pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
    pSubMenu->mbInActivateCallback = false;
    pVclSubMenu->UpdateNativeMenu();
}

void GtkSalMenu::Deactivate(const gchar* pCommand)
{
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
    Menu* pVclMenu = pSalMenu->GetMenu();
    Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
    pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
}

void GtkSalMenu::EnableUnity(bool bEnable)
{
    bUnityMode = bEnable;

    MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
    bool bDisplayable(pMenuBar->IsDisplayable());

    if (bEnable)
    {
        DestroyMenuBarWidget();
        UpdateFull();
        if (!bDisplayable)
            ShowMenuBar(false);
    }
    else
    {
        Update();
        ShowMenuBar(bDisplayable);
    }

    pMenuBar->LayoutChanged();
}

void GtkSalMenu::ShowMenuBar( bool bVisible )
{
    // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
    if (bUnityMode)
    {
        if (bVisible)
            Update();
        else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
            g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
    }
    else if (bVisible)
        CreateMenuBarWidget();
    else
        DestroyMenuBarWidget();
}

bool GtkSalMenu::IsItemVisible( unsigned nPos )
{
    SolarMutexGuard aGuard;
    bool bVisible = false;

    if ( nPos < maItems.size() )
        bVisible = maItems[ nPos ]->mbVisible;

    return bVisible;
}

void GtkSalMenu::CheckItem( unsigned, bool )
{
}

void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
{
    SolarMutexGuard aGuard;
    if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
    {
        gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
        NativeSetEnableItem( pCommand, bEnable );
        g_free( pCommand );
    }
}

void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
{
    SolarMutexGuard aGuard;
    if ( nPos < maItems.size() )
    {
        maItems[ nPos ]->mbVisible = bShow;
        if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
            Update();
    }
}

void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
{
    SolarMutexGuard aGuard;
    if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
    {
        gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );

        gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
        for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
        {
            gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
            for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
            {
                gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );

                if ( !g_strcmp0( pCommandFromModel, pCommand ) )
                {
                    NativeSetItemText( nSection, nItem, rText );
                    g_free( pCommandFromModel );
                    g_free( pCommand );
                    return;
                }

                g_free( pCommandFromModel );
            }
        }

        g_free( pCommand );
    }
}

void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
{
}

void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
{
}

void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
{
}

int GtkSalMenu::GetMenuBarHeight() const
{
#if GTK_CHECK_VERSION(3,0,0)
    return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
#else
    return 0;
#endif
}

/*
 * GtkSalMenuItem
 */

GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
    mpParentMenu( nullptr ),
    mpSubMenu( nullptr ),
    mnType( pItemData->eType ),
    mnId( pItemData->nId ),
    mbVisible( true )
{
}

GtkSalMenuItem::~GtkSalMenuItem()
{
}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/gtksys.cxx b/vcl/unx/gtk/gtksys.cxx
deleted file mode 100644
index 3734595..0000000
--- a/vcl/unx/gtk/gtksys.cxx
+++ /dev/null
@@ -1,296 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <string.h>
#include <gmodule.h>
#include <gtk/gtk.h>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtksys.hxx>
#include <osl/module.h>

GtkSalSystem *GtkSalSystem::GetSingleton()
{
    static GtkSalSystem *pSingleton = new GtkSalSystem();
    return pSingleton;
}

SalSystem *GtkInstance::CreateSalSystem()
{
    return GtkSalSystem::GetSingleton();
}

GtkSalSystem::GtkSalSystem() : SalGenericSystem()
{
    mpDisplay = gdk_display_get_default();
    countScreenMonitors();
#if GTK_CHECK_VERSION(3,0,0)
    // rhbz#1285356, native look will be gtk2, which crashes
    // when gtk3 is already loaded. Until there is a solution
    // java-side force look and feel to something that doesn't
    // crash when we are using gtk3
    setenv("STOC_FORCE_SYSTEM_LAF", "true", 1);
#endif
}

GtkSalSystem::~GtkSalSystem()
{
}

int
GtkSalSystem::GetDisplayXScreenCount()
{
    return gdk_display_get_n_screens (mpDisplay);
}

namespace
{

struct GdkRectangleCoincidentLess
{
    // fdo#78799 - detect and elide overlaying monitors of different sizes
    bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
    {
        return
            rLeft.x < rRight.x
            || rLeft.y < rRight.y
            ;
    }
};
struct GdkRectangleCoincident
{
    // fdo#78799 - detect and elide overlaying monitors of different sizes
    bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
    {
        return
            rLeft.x == rRight.x
            && rLeft.y == rRight.y
            ;
    }
};

}

/**
 * GtkSalSystem::countScreenMonitors()
 *
 * This method builds the vector which allows us to map from VCL's
 * idea of linear integer ScreenNumber to gtk+'s rather more
 * complicated screen + monitor concept.
 */
void
GtkSalSystem::countScreenMonitors()
{
    maScreenMonitors.clear();
    for (gint i = 0; i < gdk_display_get_n_screens(mpDisplay); i++)
    {
        GdkScreen* const pScreen(gdk_display_get_screen(mpDisplay, i));
        gint nMonitors(pScreen ? gdk_screen_get_n_monitors(pScreen) : 0);
        if (nMonitors > 1)
        {
            std::vector<GdkRectangle> aGeometries;
            aGeometries.reserve(nMonitors);
            for (gint j(0); j != nMonitors; ++j)
            {
                GdkRectangle aGeometry;
                gdk_screen_get_monitor_geometry(pScreen, j, &aGeometry);
                aGeometries.push_back(aGeometry);
            }
            std::sort(aGeometries.begin(), aGeometries.end(),
                    GdkRectangleCoincidentLess());
            const std::vector<GdkRectangle>::iterator aUniqueEnd(
                    std::unique(aGeometries.begin(), aGeometries.end(),
                    GdkRectangleCoincident()));
            nMonitors = std::distance(aGeometries.begin(), aUniqueEnd);
        }
        maScreenMonitors.emplace_back(pScreen, nMonitors);
    }
}

// Including gdkx.h kills us with the Window / XWindow conflict
extern "C" {
#if GTK_CHECK_VERSION(3,0,0)
    GType gdk_x11_display_get_type();
#endif
    int   gdk_x11_screen_get_screen_number (GdkScreen *screen);
}

SalX11Screen
GtkSalSystem::getXScreenFromDisplayScreen(unsigned int nScreen)
{
    gint nMonitor;

    GdkScreen *pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
    if (!pScreen)
        return SalX11Screen (0);
#if GTK_CHECK_VERSION(3,0,0)
    if (!G_TYPE_CHECK_INSTANCE_TYPE (mpDisplay, gdk_x11_display_get_type ()))
        return SalX11Screen (0);
#endif
    return SalX11Screen (gdk_x11_screen_get_screen_number (pScreen));
}

GdkScreen *
GtkSalSystem::getScreenMonitorFromIdx (int nIdx, gint &nMonitor)
{
    GdkScreen *pScreen = nullptr;
    for (auto const& screenMonitor : maScreenMonitors)
    {
        pScreen = screenMonitor.first;
        if (!pScreen)
            break;
        if (nIdx >= screenMonitor.second)
            nIdx -= screenMonitor.second;
        else
            break;
    }
    nMonitor = nIdx;

    // handle invalid monitor indexes as non-existent screens
    if (nMonitor < 0 || (pScreen && nMonitor >= gdk_screen_get_n_monitors (pScreen)))
        pScreen = nullptr;

    return pScreen;
}

int
GtkSalSystem::getScreenIdxFromPtr (GdkScreen *pScreen)
{
    int nIdx = 0;
    for (auto const& screenMonitor : maScreenMonitors)
    {
        if (screenMonitor.first == pScreen)
            return nIdx;
        nIdx += screenMonitor.second;
    }
    g_warning ("failed to find screen %p", pScreen);
    return 0;
}

int GtkSalSystem::getScreenMonitorIdx (GdkScreen *pScreen,
                                       int nX, int nY)
{
    // TODO: this will fail horribly for exotic combinations like two
    // monitors in mirror mode and one extra. Hopefully such
    // abominations are not used (or, even better, not possible) in
    // practice .-)
    return getScreenIdxFromPtr (pScreen) +
        gdk_screen_get_monitor_at_point (pScreen, nX, nY);
}

unsigned int GtkSalSystem::GetDisplayScreenCount()
{
    gint nMonitor;
    (void)getScreenMonitorFromIdx (G_MAXINT, nMonitor);
    return G_MAXINT - nMonitor;
}

bool GtkSalSystem::IsUnifiedDisplay()
{
    return gdk_display_get_n_screens (mpDisplay) == 1;
}

namespace {
int _fallback_get_primary_monitor (GdkScreen *pScreen)
{
    // Use monitor name as primacy heuristic
    int max = gdk_screen_get_n_monitors (pScreen);
    for (int i = 0; i < max; ++i)
    {
        char *name = gdk_screen_get_monitor_plug_name (pScreen, i);
        bool bLaptop = (name && !g_ascii_strncasecmp (name, "LVDS", 4));
        g_free (name);
        if (bLaptop)
            return i;
    }
    return 0;
}

int _get_primary_monitor (GdkScreen *pScreen)
{
    static int (*get_fn) (GdkScreen *) = nullptr;
#if GTK_CHECK_VERSION(3,0,0)
    get_fn = gdk_screen_get_primary_monitor;
#endif
    // Perhaps we have a newer gtk+ with this symbol:
    if (!get_fn)
    {
        get_fn = reinterpret_cast<int(*)(GdkScreen*)>(osl_getAsciiFunctionSymbol(nullptr,
            "gdk_screen_get_primary_monitor"));
    }
    if (!get_fn)
        get_fn = _fallback_get_primary_monitor;
    if (get_fn)
        return get_fn (pScreen);
    else
        return 0;
}
} // end anonymous namespace

unsigned int GtkSalSystem::GetDisplayBuiltInScreen()
{
    GdkScreen *pDefault = gdk_display_get_default_screen (mpDisplay);
    int idx = getScreenIdxFromPtr (pDefault);
    return idx + _get_primary_monitor (pDefault);
}

tools::Rectangle GtkSalSystem::GetDisplayScreenPosSizePixel (unsigned int nScreen)
{
    gint nMonitor;
    GdkScreen *pScreen;
    GdkRectangle aRect;
    pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
    if (!pScreen)
        return tools::Rectangle();
    gdk_screen_get_monitor_geometry (pScreen, nMonitor, &aRect);
    return tools::Rectangle (Point(aRect.x, aRect.y), Size(aRect.width, aRect.height));
}

// convert ~ to indicate mnemonic to '_'
static OString MapToGtkAccelerator(const OUString &rStr)
{
    return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
}

int GtkSalSystem::ShowNativeDialog (const OUString& rTitle, const OUString& rMessage,
                                    const std::vector< OUString >& rButtonNames)
{
    OString aTitle (OUStringToOString (rTitle, RTL_TEXTENCODING_UTF8));
    OString aMessage (OUStringToOString (rMessage, RTL_TEXTENCODING_UTF8));

    GtkDialog *pDialog = GTK_DIALOG (
        g_object_new (GTK_TYPE_MESSAGE_DIALOG,
                      "title", aTitle.getStr(),
                      "message-type", int(GTK_MESSAGE_WARNING),
                      "text", aMessage.getStr(),
                      nullptr));
    int nButton = 0;
    for (auto const& buttonName : rButtonNames)
        gtk_dialog_add_button (pDialog, MapToGtkAccelerator(buttonName).getStr(), nButton++);
    gtk_dialog_set_default_response (pDialog, 0/*nDefaultButton*/);

    nButton = gtk_dialog_run (pDialog);
    if (nButton < 0)
        nButton = -1;

    gtk_widget_destroy (GTK_WIDGET (pDialog));

    return nButton;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/hudawareness.cxx b/vcl/unx/gtk/hudawareness.cxx
deleted file mode 100644
index b7985fd..0000000
--- a/vcl/unx/gtk/hudawareness.cxx
+++ /dev/null
@@ -1,112 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <string.h>

#include <unx/gtk/gtksalmenu.hxx>

#ifdef ENABLE_GMENU_INTEGRATION

#include <unx/gtk/hudawareness.h>

struct HudAwarenessHandle
{
  GDBusConnection *connection;
  HudAwarenessCallback callback;
  gpointer user_data;
  GDestroyNotify notify;
};

static void
hud_awareness_method_call (GDBusConnection       * /* connection */,
                           const gchar           * /* sender */,
                           const gchar           * /* object_path */,
                           const gchar           * /* interface_name */,
                           const gchar           *method_name,
                           GVariant              *parameters,
                           GDBusMethodInvocation *invocation,
                           gpointer               user_data)
{
  HudAwarenessHandle *handle = static_cast<HudAwarenessHandle*>(user_data);

  if (g_str_equal (method_name, "HudActiveChanged"))
    {
      gboolean active;

      g_variant_get (parameters, "(b)", &active);

      (* handle->callback) (active, handle->user_data);
    }

  g_dbus_method_invocation_return_value (invocation, nullptr);
}

guint
hud_awareness_register (GDBusConnection       *connection,
                        const gchar           *object_path,
                        HudAwarenessCallback   callback,
                        gpointer               user_data,
                        GDestroyNotify         notify,
                        GError               **error)
{
  static GDBusInterfaceInfo *iface;
  static GDBusNodeInfo *info;
  GDBusInterfaceVTable vtable;
  HudAwarenessHandle *handle;
  guint object_id;

  memset (static_cast<void *>(&vtable), 0, sizeof (vtable));
  vtable.method_call = hud_awareness_method_call;

  if G_UNLIKELY (iface == nullptr)
    {
      GError *local_error = nullptr;

      info = g_dbus_node_info_new_for_xml ("<node>"
                                             "<interface name='com.canonical.hud.Awareness'>"
                                               "<method name='CheckAwareness'/>"
                                               "<method name='HudActiveChanged'>"
                                                 "<arg type='b'/>"
                                               "</method>"
                                             "</interface>"
                                           "</node>",
                                           &local_error);
      g_assert_no_error (local_error);
      iface = g_dbus_node_info_lookup_interface (info, "com.canonical.hud.Awareness");
      g_assert (iface != nullptr);
    }

  handle = static_cast<HudAwarenessHandle*>(g_malloc (sizeof (HudAwarenessHandle)));

  object_id = g_dbus_connection_register_object (connection, object_path, iface, &vtable, handle, &g_free, error);

  if (object_id == 0)
    {
      g_free (handle);
      return 0;
    }

  handle->connection = static_cast<GDBusConnection*>(g_object_ref (connection));
  handle->callback = callback;
  handle->user_data = user_data;
  handle->notify = notify;

  return object_id;
}

void
hud_awareness_unregister (GDBusConnection *connection,
                          guint            subscription_id)
{
  g_dbus_connection_unregister_object (connection, subscription_id);
}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/salnativewidgets-gtk.cxx b/vcl/unx/gtk/salnativewidgets-gtk.cxx
deleted file mode 100644
index eb2f594..0000000
--- a/vcl/unx/gtk/salnativewidgets-gtk.cxx
+++ /dev/null
@@ -1,4480 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <config_version.h>

#include <vcl/svapp.hxx>

#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkgdi.hxx>

#include <unx/pixmap.hxx>
#include <saldatabasic.hxx>
#include <unx/saldisp.hxx>

#include <cstdio>
#include <cmath>
#include <memory>
#include <vector>
#include <algorithm>
#include <unordered_map>

#include <boost/optional.hpp>

#include <vcl/vclenum.hxx>
#include <vcl/settings.hxx>
#include <unx/fontmanager.hxx>
#include <vcl/decoview.hxx>

#include <vcl/opengl/OpenGLHelper.hxx>
#include <ControlCacheKey.hxx>

typedef struct _cairo_font_options cairo_font_options_t;
const char* const tabPrelitDataName="libreoffice-tab-is-prelit";

// initialize statics
bool GtkSalGraphics::bThemeChanged = true;
bool GtkSalGraphics::bNeedPixmapPaint = false;
bool GtkSalGraphics::bNeedTwoPasses = false;

enum
{
    BG_NONE = 0,
    BG_FILL,
    BG_WHITE,
    BG_BLACK
};

GtkSalGraphics::GtkSalGraphics( GtkSalFrame *pFrame, GtkWidget *pWindow,
                                SalX11Screen nXScreen )
    : X11SalGraphics(),
      m_pWindow( pWindow ),
      m_aClipRegion(true)
{
    Init( pFrame, GDK_WINDOW_XID( widget_get_window( pWindow ) ), nXScreen );

    initWidgetDrawBackends();
}

GtkSalGraphics::~GtkSalGraphics()
{
}

/*************************************
 * Cached native widget objects
 *************************************/
class NWPixmapCacheList;
class NWPixmapCache;
struct NWFWidgetData
{
    GtkWidget *  gCacheWindow;
    GtkWidget *  gDumbContainer;

    GtkWidget *  gBtnWidget;
    GtkWidget *  gRadioWidget;
    GtkWidget *  gRadioWidgetSibling;
    GtkWidget *  gCheckWidget;
    GtkWidget *  gScrollHorizWidget;
    GtkWidget *  gScrollVertWidget;
    GtkWidget *  gArrowWidget;
    GtkWidget *  gDropdownWidget;
    GtkWidget *  gEditBoxWidget;
    GtkWidget *  gSpinButtonWidget;
    GtkWidget *  gNotebookWidget;
    GtkWidget *  gOptionMenuWidget;
    GtkWidget *  gComboWidget;
    GtkWidget *  gScrolledWindowWidget;
    GtkWidget *  gToolbarWidget;
    GtkWidget *  gToolbarButtonWidget;
    GtkWidget *  gHandleBoxWidget;
    GtkWidget *  gMenubarWidget;
    GtkWidget *  gMenuItemMenubarWidget;
    GtkWidget *  gMenuWidget;
    GtkWidget *  gMenuItemMenuWidget;
    GtkWidget *  gMenuItemCheckMenuWidget;
    GtkWidget *  gMenuItemRadioMenuWidget;
    GtkWidget *  gMenuItemSeparatorMenuWidget;
    GtkWidget *  gImageMenuItem;
    GtkWidget *  gTooltipPopup;
    GtkWidget *  gProgressBar;
    GtkWidget *  gTreeView;
    GtkWidget *  gHScale;
    GtkWidget *  gVScale;
    GtkWidget *  gSeparator;
    GtkWidget *  gDialog;
    GtkWidget *  gFrame;

    NWPixmapCacheList* gNWPixmapCacheList;
    NWPixmapCache* gCacheTabItems;
    NWPixmapCache* gCacheTabPages;

    NWFWidgetData() :
        gCacheWindow( nullptr ),
        gDumbContainer( nullptr ),
        gBtnWidget( nullptr ),
        gRadioWidget( nullptr ),
        gRadioWidgetSibling( nullptr ),
        gCheckWidget( nullptr ),
        gScrollHorizWidget( nullptr ),
        gScrollVertWidget( nullptr ),
        gArrowWidget( nullptr ),
        gDropdownWidget( nullptr ),
        gEditBoxWidget( nullptr ),
        gSpinButtonWidget( nullptr ),
        gNotebookWidget( nullptr ),
        gOptionMenuWidget( nullptr ),
        gComboWidget( nullptr ),
        gScrolledWindowWidget( nullptr ),
        gToolbarWidget( nullptr ),
        gToolbarButtonWidget( nullptr ),
        gHandleBoxWidget( nullptr ),
        gMenubarWidget( nullptr ),
        gMenuItemMenubarWidget( nullptr ),
        gMenuWidget( nullptr ),
        gMenuItemMenuWidget( nullptr ),
        gMenuItemCheckMenuWidget( nullptr ),
        gMenuItemRadioMenuWidget( nullptr ),
        gMenuItemSeparatorMenuWidget( nullptr ),
        gImageMenuItem( nullptr ),
        gTooltipPopup( nullptr ),
        gProgressBar( nullptr ),
        gTreeView( nullptr ),
        gHScale( nullptr ),
        gVScale( nullptr ),
        gSeparator( nullptr ),
        gDialog( nullptr ),
        gFrame( nullptr ),
        gNWPixmapCacheList( nullptr ),
        gCacheTabItems( nullptr ),
        gCacheTabPages( nullptr )
    {}
};

// Keep a hash table of Widgets->default flags so that we can
// easily and quickly reset each to a default state before using
// them
static std::unordered_map<long, guint>    gWidgetDefaultFlags;
class WidgetDataVector
{
private:
    std::vector<NWFWidgetData> mData;

public:
    explicit WidgetDataVector(size_t nElems = 0) : mData( nElems ) {}
    size_t size() const { return mData.size(); }
    NWFWidgetData &operator [](size_t i) { return mData.at(i); }
    NWFWidgetData &operator [](const SalX11Screen &s) { return mData.at(s.getXScreen()); }
};
static WidgetDataVector gWidgetData;

static const GtkBorder aDefDefBorder        = { 1, 1, 1, 1 };

// Some GTK defaults
#define MIN_ARROW_SIZE                    11
#define BTN_CHILD_SPACING                1
#define MIN_SPIN_ARROW_WIDTH                6

static void NWEnsureGTKRadio             ( SalX11Screen nScreen );
static void NWEnsureGTKButton            ( SalX11Screen nScreen );
static void NWEnsureGTKCheck             ( SalX11Screen nScreen );
static void NWEnsureGTKScrollbars        ( SalX11Screen nScreen );
static void NWEnsureGTKArrow             ( SalX11Screen nScreen );
static void NWEnsureGTKEditBox           ( SalX11Screen nScreen );
static void NWEnsureGTKSpinButton        ( SalX11Screen nScreen );
static void NWEnsureGTKNotebook          ( SalX11Screen nScreen );
static void NWEnsureGTKOptionMenu        ( SalX11Screen nScreen );
static void NWEnsureGTKCombo             ( SalX11Screen nScreen );
static void NWEnsureGTKScrolledWindow    ( SalX11Screen nScreen );
static void NWEnsureGTKToolbar           ( SalX11Screen nScreen );
static void NWEnsureGTKMenubar           ( SalX11Screen nScreen );
static void NWEnsureGTKMenu              ( SalX11Screen nScreen );
static void NWEnsureGTKTooltip           ( SalX11Screen nScreen );
static void NWEnsureGTKDialog            ( SalX11Screen nScreen );
static void NWEnsureGTKFrame             ( SalX11Screen nScreen );
static void NWEnsureGTKProgressBar       ( SalX11Screen nScreen );
static void NWEnsureGTKTreeView          ( SalX11Screen nScreen );
static void NWEnsureGTKSlider            ( SalX11Screen nScreen );

static void NWConvertVCLStateToGTKState( ControlState nVCLState, GtkStateType* nGTKState, GtkShadowType* nGTKShadow );
static void NWAddWidgetToCacheWindow( GtkWidget* widget, SalX11Screen nScreen );
static void NWSetWidgetState( GtkWidget* widget, ControlState nState, GtkStateType nGtkState );

static void NWCalcArrowRect( const tools::Rectangle& rButton, tools::Rectangle& rArrow );

/*
 * Individual helper functions
 *
 */

static tools::Rectangle NWGetButtonArea( SalX11Screen nScreen, tools::Rectangle aAreaRect, ControlState nState);

static tools::Rectangle NWGetTabItemRect( SalX11Screen nScreen, tools::Rectangle aAreaRect );

static tools::Rectangle NWGetEditBoxPixmapRect( SalX11Screen nScreen, tools::Rectangle aAreaRect );

static void NWPaintOneEditBox( SalX11Screen nScreen, GdkDrawable * gdkDrawable, GdkRectangle const *gdkRect,
                               ControlType nType, tools::Rectangle aEditBoxRect,
                               ControlState nState );

static tools::Rectangle NWGetSpinButtonRect( SalX11Screen nScreen, ControlPart nPart, tools::Rectangle aAreaRect );

static void NWPaintOneSpinButton( SalX11Screen nScreen, GdkPixmap * pixmap, ControlPart nPart, tools::Rectangle aAreaRect,
                            ControlState nState );

static tools::Rectangle NWGetComboBoxButtonRect( SalX11Screen nScreen, ControlPart nPart, tools::Rectangle aAreaRect );

static tools::Rectangle NWGetListBoxButtonRect( SalX11Screen nScreen, ControlPart nPart, tools::Rectangle aAreaRect);

static tools::Rectangle NWGetListBoxIndicatorRect( SalX11Screen nScreen, tools::Rectangle aAreaRect);

static tools::Rectangle NWGetToolbarRect( SalX11Screen nScreen,
                                   ControlPart nPart,
                                   tools::Rectangle aAreaRect );

static int getFrameWidth(GtkWidget const * widget);

static tools::Rectangle NWGetScrollButtonRect(    SalX11Screen nScreen, ControlPart nPart, tools::Rectangle aAreaRect );


/************************************************************************
 * GDK implementation of X11Pixmap
 ************************************************************************/

class GdkX11Pixmap : public X11Pixmap
{
public:
    GdkX11Pixmap( int nWidth, int nHeight, int nDepth );
    virtual ~GdkX11Pixmap() override;

    virtual int          GetDepth() const override;
    virtual SalX11Screen GetScreen() const override;
    virtual Pixmap       GetPixmap() const override;
    GdkPixmap*           GetGdkPixmap() const;
    GdkDrawable*         GetGdkDrawable() const;

protected:
    GdkPixmap* mpGdkPixmap;
    int        mnDepth;
};

GdkX11Pixmap::GdkX11Pixmap( int nWidth, int nHeight, int nDepth )
: X11Pixmap( nWidth, nHeight )
{
    mpGdkPixmap = gdk_pixmap_new( nullptr, nWidth, nHeight, nDepth );
    mnDepth = gdk_drawable_get_depth( GDK_DRAWABLE( mpGdkPixmap ) );

    GdkScreen *pScreen = gdk_drawable_get_screen( GDK_DRAWABLE( mpGdkPixmap ) );
    gdk_drawable_set_colormap( GDK_DRAWABLE( mpGdkPixmap ), gdk_screen_get_default_colormap( pScreen ) );
}

GdkX11Pixmap::~GdkX11Pixmap()
{
    g_object_unref( mpGdkPixmap );
}

int GdkX11Pixmap::GetDepth() const
{
    return mnDepth;
}

SalX11Screen GdkX11Pixmap::GetScreen() const
{
    return SalX11Screen( gdk_screen_get_number( gdk_drawable_get_screen( GDK_DRAWABLE(mpGdkPixmap) ) ) );
}

Pixmap GdkX11Pixmap::GetPixmap() const
{
    return GDK_PIXMAP_XID( mpGdkPixmap );
}

GdkPixmap* GdkX11Pixmap::GetGdkPixmap() const
{
    return mpGdkPixmap;
}

GdkDrawable* GdkX11Pixmap::GetGdkDrawable() const
{
    return GDK_DRAWABLE( mpGdkPixmap );
}


/*********************************************************
 * PixmapCache
 *********************************************************/

// as some native widget drawing operations are pretty slow
// with certain themes (eg tabpages)
// this cache can be used to cache the corresponding pixmap
// see NWPaintGTKTabItem

class NWPixmapCacheData
{
public:
    ControlType    m_nType;
    ControlState   m_nState;
    tools::Rectangle      m_pixmapRect;
    std::unique_ptr<GdkX11Pixmap> m_pixmap;
    std::unique_ptr<GdkX11Pixmap> m_mask;

    NWPixmapCacheData() : m_nType(ControlType::Generic), m_nState(ControlState::NONE) {}
    void SetPixmap( std::unique_ptr<GdkX11Pixmap> pPixmap, std::unique_ptr<GdkX11Pixmap> pMask );
};

class NWPixmapCache
{
    int m_size;
    int m_idx;
    int m_screen;
    std::unique_ptr<NWPixmapCacheData[]> pData;
public:
    explicit NWPixmapCache( SalX11Screen nScreen );
    ~NWPixmapCache();

    void SetSize( int n)
        { m_idx = 0; m_size = n; pData.reset(new NWPixmapCacheData[m_size]); }
    int GetSize() const { return m_size; }

    bool Find( ControlType aType, ControlState aState, const tools::Rectangle& r_pixmapRect, GdkX11Pixmap** pPixmap, GdkX11Pixmap** pMask );
    void Fill( ControlType aType, ControlState aState, const tools::Rectangle& r_pixmapRect, std::unique_ptr<GdkX11Pixmap> pPixmap, std::unique_ptr<GdkX11Pixmap> pMask );

    void ThemeChanged();
};

class NWPixmapCacheList
{
public:
    ::std::vector< NWPixmapCache* > mCaches;

    void AddCache( NWPixmapCache *pCache );
    void RemoveCache( NWPixmapCache *pCache );
    void ThemeChanged();
};

// --- implementation ---

void NWPixmapCacheData::SetPixmap( std::unique_ptr<GdkX11Pixmap> pPixmap, std::unique_ptr<GdkX11Pixmap> pMask )
{
    m_pixmap = std::move(pPixmap);
    m_mask = std::move(pMask);
}

NWPixmapCache::NWPixmapCache( SalX11Screen nScreen )
{
    m_idx = 0;
    m_size = 0;
    m_screen = nScreen.getXScreen();
    pData = nullptr;
    if( gWidgetData[m_screen].gNWPixmapCacheList )
        gWidgetData[m_screen].gNWPixmapCacheList->AddCache(this);
}
NWPixmapCache::~NWPixmapCache()
{
    if( gWidgetData[m_screen].gNWPixmapCacheList )
        gWidgetData[m_screen].gNWPixmapCacheList->RemoveCache(this);
}
void NWPixmapCache::ThemeChanged()
{
    // throw away cached pixmaps
    for(int i=0; i<m_size; i++)
        pData[i].SetPixmap( nullptr, nullptr );
}

bool  NWPixmapCache::Find( ControlType aType, ControlState aState, const tools::Rectangle& r_pixmapRect, GdkX11Pixmap** pPixmap, GdkX11Pixmap** pMask )
{
    aState &= ~ControlState::CACHING_ALLOWED; // mask clipping flag
    int i;
    for(i=0; i<m_size; i++)
    {
        if( pData[i].m_nType == aType &&
            pData[i].m_nState == aState &&
            pData[i].m_pixmapRect.GetWidth() == r_pixmapRect.GetWidth() &&
            pData[i].m_pixmapRect.GetHeight() == r_pixmapRect.GetHeight() &&
            pData[i].m_pixmap != nullptr )
        {
            *pPixmap = pData[i].m_pixmap.get();
            *pMask = pData[i].m_mask.get();
            return true;
        }
    }
    return false;
}

void NWPixmapCache::Fill( ControlType aType, ControlState aState, const tools::Rectangle& r_pixmapRect,
                         std::unique_ptr<GdkX11Pixmap> pPixmap,
                         std::unique_ptr<GdkX11Pixmap> pMask )
{
    if( !(aState & ControlState::CACHING_ALLOWED) )
        return;

    aState &= ~ControlState::CACHING_ALLOWED; // mask clipping flag
    m_idx = (m_idx+1) % m_size; // just wrap
    pData[m_idx].m_nType = aType;
    pData[m_idx].m_nState = aState;
    pData[m_idx].m_pixmapRect = r_pixmapRect;
    pData[m_idx].SetPixmap( std::move(pPixmap), std::move(pMask) );
}

void NWPixmapCacheList::AddCache( NWPixmapCache* pCache )
{
    mCaches.push_back( pCache );
}
void NWPixmapCacheList::RemoveCache( NWPixmapCache* pCache )
{
    auto p = ::std::find( mCaches.begin(), mCaches.end(), pCache );
    if( p != mCaches.end() )
        mCaches.erase( p );
}
void NWPixmapCacheList::ThemeChanged( )
{
    for (auto const& cache : mCaches)
        cache->ThemeChanged();
}

/*********************************************************
 * Make border manipulation easier
 *********************************************************/
static void NW_gtk_border_set_from_border( GtkBorder& aDst, const GtkBorder * pSrc )
{
    aDst.left        = pSrc->left;
    aDst.top        = pSrc->top;
    aDst.right    = pSrc->right;
    aDst.bottom    = pSrc->bottom;
}

/*********************************************************
 * Initialize GTK and local stuff
 *********************************************************/
void GtkSalData::initNWF()
{
    ImplSVData* pSVData = ImplGetSVData();

    // draw no border for popup menus (NWF draws its own)
    pSVData->maNWFData.mbFlatMenu = true;

    // draw separate buttons for toolbox dropdown items
    pSVData->maNWFData.mbToolboxDropDownSeparate = true;

    // draw toolbars in separate lines
    pSVData->maNWFData.mbDockingAreaSeparateTB = true;

    // open first menu on F10
    pSVData->maNWFData.mbOpenMenuOnF10 = true;

    // omit GetNativeControl while painting (see brdwin.cxx)
    pSVData->maNWFData.mbCanDrawWidgetAnySize = true;

    pSVData->maNWFData.mbDDListBoxNoTextArea = true;

    // use offscreen rendering when using OpenGL backend
    if( OpenGLHelper::isVCLOpenGLEnabled() )
    {
        GtkSalGraphics::bNeedPixmapPaint = true;
        GtkSalGraphics::bNeedTwoPasses = true;
    }

    int nScreens = GetGtkSalData()->GetGtkDisplay()->GetXScreenCount();
    gWidgetData = WidgetDataVector( nScreens );
    for( int i = 0; i < nScreens; i++ )
        gWidgetData[i].gNWPixmapCacheList = new NWPixmapCacheList;

    // small extra border around menu items
    NWEnsureGTKMenu( SalX11Screen( 0 ) );
    gint horizontal_padding = 1;
    gint vertical_padding = 1;
    gint separator_padding = 1;
    gtk_widget_style_get( gWidgetData[0].gMenuWidget,
            "horizontal-padding", &horizontal_padding,
            nullptr);
    gtk_widget_style_get( gWidgetData[0].gMenuWidget,
            "vertical-padding", &vertical_padding,
            nullptr);
    gtk_widget_style_get( gWidgetData[0].gMenuItemSeparatorMenuWidget,
            "horizontal-padding", &separator_padding,
            nullptr);
    gint xthickness = gWidgetData[0].gMenuWidget->style->xthickness;
    gint ythickness = gWidgetData[0].gMenuWidget->style->ythickness;
    pSVData->maNWFData.mnMenuFormatBorderX = xthickness + horizontal_padding;
    pSVData->maNWFData.mnMenuFormatBorderY = ythickness + vertical_padding;
    pSVData->maNWFData.mnMenuSeparatorBorderX = separator_padding;

    static const char* pEnv = getenv( "SAL_GTK_USE_PIXMAPPAINT" );
    if( pEnv && *pEnv )
        GtkSalGraphics::bNeedPixmapPaint = true;

    #if OSL_DEBUG_LEVEL > 1
    std::fprintf( stderr, "GtkPlugin: using %s NWF\n",
             GtkSalGraphics::bNeedPixmapPaint ? "offscreen" : "direct" );
    #endif

    GtkSettings *gtks = gtk_settings_get_default ();
    gint val;
    g_object_get (gtks, "gtk-auto-mnemonics", &val, nullptr);
    if (val) pSVData->maNWFData.mbAutoAccel = true;
    else pSVData->maNWFData.mbAutoAccel = false;
    g_object_get (gtks, "gtk-enable-mnemonics", &val, nullptr);
    if (val) pSVData->maNWFData.mbEnableAccel = true;
    else pSVData->maNWFData.mbEnableAccel = false;
}

/*********************************************************
 * Release GTK and local stuff
 *********************************************************/
void GtkSalData::deInitNWF()
{
    for( size_t i = 0; i < gWidgetData.size(); i++ )
    {
        // free up global widgets
        // gtk_widget_destroy will in turn destroy the child hierarchy
        // so only destroy disjunct hierarchies
        if( gWidgetData[i].gCacheWindow )
            gtk_widget_destroy( gWidgetData[i].gCacheWindow );
        if( gWidgetData[i].gMenuWidget )
            g_object_unref (gWidgetData[i].gMenuWidget);
        if( gWidgetData[i].gTooltipPopup )
            gtk_widget_destroy( gWidgetData[i].gTooltipPopup );
        if( gWidgetData[i].gDialog )
            gtk_widget_destroy( gWidgetData[i].gDialog );
        delete gWidgetData[i].gCacheTabPages;
        gWidgetData[i].gCacheTabPages = nullptr;
        delete gWidgetData[i].gCacheTabItems;
        gWidgetData[i].gCacheTabItems = nullptr;
        delete gWidgetData[i].gNWPixmapCacheList;
        gWidgetData[i].gNWPixmapCacheList = nullptr;
    }
}

/**********************************************************
 * track clip region
 **********************************************************/
void GtkSalGraphics::ResetClipRegion()
{
    m_aClipRegion.SetNull();
    X11SalGraphics::ResetClipRegion();
}

bool GtkSalGraphics::setClipRegion( const vcl::Region& i_rClip )
{
    m_aClipRegion = i_rClip;
    bool bRet = X11SalGraphics::setClipRegion( m_aClipRegion );
    if( m_aClipRegion.IsEmpty() )
        m_aClipRegion.SetNull();
    return bRet;
}

void GtkSalGraphics::copyBits( const SalTwoRect& rPosAry,
                               SalGraphics* pSrcGraphics )
{
    GtkSalFrame* pFrame = GetGtkFrame();
    ::Window aWin = None;
    if( pFrame && m_pWindow )
    {
        /* #i64117# some themes set the background pixmap VERY frequently */
        GdkWindow* pWin = GTK_WIDGET(m_pWindow)->window;
        if( pWin )
        {
            aWin = GDK_WINDOW_XWINDOW(pWin);
            if( aWin != None )
                XSetWindowBackgroundPixmap( GtkSalFrame::getDisplay()->GetDisplay(),
                                            aWin,
                                            None );
        }
    }
    X11SalGraphics::copyBits( rPosAry, pSrcGraphics );
}

bool GtkSalGraphics::isNativeControlSupported( ControlType nType, ControlPart nPart )
{
    switch(nType)
    {
        case ControlType::Pushbutton:
        case ControlType::Radiobutton:
        case ControlType::Checkbox:
        case ControlType::Tooltip:
        case ControlType::Progress:
        case ControlType::ListNode:
        case ControlType::ListNet:
            if(nPart==ControlPart::Entire)
                return true;
            break;

        case ControlType::Scrollbar:
            if(nPart==ControlPart::DrawBackgroundHorz || nPart==ControlPart::DrawBackgroundVert ||
               nPart==ControlPart::Entire       || nPart==ControlPart::HasThreeButtons)
                return true;
            break;

        case ControlType::Editbox:
        case ControlType::MultilineEditbox:
        case ControlType::Combobox:
            if(nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture)
                return true;
            break;

        case ControlType::Spinbox:
            if(nPart==ControlPart::Entire || nPart==ControlPart::AllButtons || nPart==ControlPart::HasBackgroundTexture)
                return true;
            break;

        case ControlType::SpinButtons:
            if(nPart==ControlPart::Entire || nPart==ControlPart::AllButtons)
                return true;
            break;

        case ControlType::Frame:
        case ControlType::WindowBackground:
            return true;

        case ControlType::TabItem:
        case ControlType::TabPane:
        case ControlType::TabBody:
            if(nPart==ControlPart::Entire || nPart==ControlPart::TabsDrawRtl)
                return true;
            break;

        case ControlType::Listbox:
            if(nPart==ControlPart::Entire || nPart==ControlPart::ListboxWindow || nPart==ControlPart::HasBackgroundTexture)
                return true;
            break;

        case ControlType::Toolbar:
            if( nPart==ControlPart::Entire
                ||  nPart==ControlPart::DrawBackgroundHorz
                ||  nPart==ControlPart::DrawBackgroundVert
                ||  nPart==ControlPart::ThumbHorz
                ||  nPart==ControlPart::ThumbVert
                ||  nPart==ControlPart::Button
                ||  nPart==ControlPart::SeparatorHorz
                ||  nPart==ControlPart::SeparatorVert
                )
                return true;
            break;

        case ControlType::Menubar:
            if(nPart==ControlPart::Entire || nPart==ControlPart::MenuItem)
                return true;
            break;

        case ControlType::MenuPopup:
            if (nPart==ControlPart::Entire
                ||  nPart==ControlPart::MenuItem
                ||  nPart==ControlPart::MenuItemCheckMark
                ||  nPart==ControlPart::MenuItemRadioMark
                ||  nPart==ControlPart::Separator
                ||  nPart==ControlPart::SubmenuArrow
            )
                return true;
            break;

        case ControlType::Slider:
            if(nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
                return true;
            break;

        case ControlType::Fixedline:
            if(nPart == ControlPart::SeparatorVert || nPart == ControlPart::SeparatorHorz)
                return true;
            break;

        case ControlType::ListHeader:
            if(nPart == ControlPart::Button || nPart == ControlPart::Arrow)
                return true;
            break;
        default: break;
    }

    return false;
}

bool GtkSalGraphics::hitTestNativeControl( ControlType        nType,
                                ControlPart        nPart,
                                const tools::Rectangle&        rControlRegion,
                                const Point&        aPos,
                                bool&            rIsInside )
{
    if ( ( nType == ControlType::Scrollbar ) &&
         ( ( nPart == ControlPart::ButtonUp ) ||
           ( nPart == ControlPart::ButtonDown ) ||
           ( nPart == ControlPart::ButtonLeft ) ||
           ( nPart == ControlPart::ButtonRight ) ) )
    {
        NWEnsureGTKScrollbars( m_nXScreen );

        // Grab some button style attributes
        gboolean has_forward;
        gboolean has_forward2;
        gboolean has_backward;
        gboolean has_backward2;

        gtk_widget_style_get( gWidgetData[m_nXScreen].gScrollHorizWidget,
                              "has-forward-stepper", &has_forward,
                              "has-secondary-forward-stepper", &has_forward2,
                              "has-backward-stepper", &has_backward,
                              "has-secondary-backward-stepper", &has_backward2,
                              nullptr );
        tools::Rectangle aForward;
        tools::Rectangle aBackward;

        rIsInside = false;

        ControlPart nCounterPart = ControlPart::NONE;
        if ( nPart == ControlPart::ButtonUp )
            nCounterPart = ControlPart::ButtonDown;
        else if ( nPart == ControlPart::ButtonDown )
            nCounterPart = ControlPart::ButtonUp;
        else if ( nPart == ControlPart::ButtonLeft )
            nCounterPart = ControlPart::ButtonRight;
        else if ( nPart == ControlPart::ButtonRight )
            nCounterPart = ControlPart::ButtonLeft;

        aBackward = NWGetScrollButtonRect( m_nXScreen, nPart, rControlRegion );
        aForward = NWGetScrollButtonRect( m_nXScreen, nCounterPart, rControlRegion );

        if ( has_backward && has_forward2 )
        {
            Size aSize( aBackward.GetSize() );
            if ( ( nPart == ControlPart::ButtonUp ) || ( nPart == ControlPart::ButtonDown ) )
                aSize.setHeight( aBackward.GetHeight() / 2 );
            else
                aSize.setWidth( aBackward.GetWidth() / 2 );
            aBackward.SetSize( aSize );

            if ( nPart == ControlPart::ButtonDown )
                aBackward.Move( 0, aBackward.GetHeight() / 2 );
            else if ( nPart == ControlPart::ButtonRight )
                aBackward.Move( aBackward.GetWidth() / 2, 0 );
        }

        if ( has_backward2 && has_forward )
        {
            Size aSize( aForward.GetSize() );
            if ( ( nPart == ControlPart::ButtonUp ) || ( nPart == ControlPart::ButtonDown ) )
                aSize.setHeight( aForward.GetHeight() / 2 );
            else
                aSize.setWidth( aForward.GetWidth() / 2 );
            aForward.SetSize( aSize );

            if ( nPart == ControlPart::ButtonDown )
                aForward.Move( 0, aForward.GetHeight() / 2 );
            else if ( nPart == ControlPart::ButtonRight )
                aForward.Move( aForward.GetWidth() / 2, 0 );
        }

        if ( ( nPart == ControlPart::ButtonUp ) || ( nPart == ControlPart::ButtonLeft ) )
        {
            if ( has_backward )
                rIsInside |= aBackward.IsInside( aPos );
            if ( has_backward2 )
                rIsInside |= aForward.IsInside( aPos );
        }
        else
        {
            if ( has_forward )
                rIsInside |= aBackward.IsInside( aPos );
            if ( has_forward2 )
                rIsInside |= aForward.IsInside( aPos );
        }
        return true;
    }

    if( isNativeControlSupported(nType, nPart) )
    {
        rIsInside = rControlRegion.IsInside( aPos );
        return true;
    }
    else
    {
        return false;
    }
}

bool GtkSalGraphics::drawNativeControl(ControlType nType, ControlPart nPart,
        const tools::Rectangle& rControlRegion, ControlState nState,
        const ImplControlValue& aValue, const OUString& /*rCaption*/)
{
    // get a GC with current clipping region set
    GetFontGC();

    // theme changed ?
    if( GtkSalGraphics::bThemeChanged )
    {
        // invalidate caches
        for( size_t i = 0; i < gWidgetData.size(); i++ )
            if( gWidgetData[i].gNWPixmapCacheList )
                gWidgetData[i].gNWPixmapCacheList->ThemeChanged();
        GtkSalGraphics::bThemeChanged = false;
    }

    tools::Rectangle aCtrlRect( rControlRegion );
    vcl::Region aClipRegion( m_aClipRegion );
    if( aClipRegion.IsNull() )
        aClipRegion = aCtrlRect;

    // make pixmap a little larger since some themes draw decoration
    // outside the rectangle, see e.g. checkbox
    tools::Rectangle aPixmapRect(Point( aCtrlRect.Left()-1, aCtrlRect.Top()-1 ),
                            Size( aCtrlRect.GetWidth()+2, aCtrlRect.GetHeight()+2) );

    ControlCacheKey aControlCacheKey(nType, nPart, nState, aPixmapRect.GetSize());
    if (aControlCacheKey.canCacheControl()
        && TryRenderCachedNativeControl(aControlCacheKey, aPixmapRect.Left(), aPixmapRect.Top()))
    {
        return true;
    }

    std::vector< tools::Rectangle > aClip;
    int nPasses = 0;
    GdkDrawable* gdkDrawable[2];
    std::unique_ptr<GdkX11Pixmap> xPixmap;
    std::unique_ptr<GdkX11Pixmap> xMask;

    if ((bNeedPixmapPaint || (nState & ControlState::DOUBLEBUFFERING))
        && nType != ControlType::Scrollbar
        && nType != ControlType::Spinbox
        && nType != ControlType::TabItem
        && nType != ControlType::TabPane
        && nType != ControlType::Progress
        && ! (nType == ControlType::Toolbar && (nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert) )
        )
    {
        if( bNeedTwoPasses )
        {
            xPixmap = NWGetPixmapFromScreen( aPixmapRect, BG_WHITE );
            xMask = NWGetPixmapFromScreen( aPixmapRect, BG_BLACK );
            if( !xPixmap || !xMask )
                return false;
            nPasses = 2;
            gdkDrawable[0] = xPixmap->GetGdkDrawable();
            gdkDrawable[1] = xMask->GetGdkDrawable();
        }
        else
        {
            xPixmap = NWGetPixmapFromScreen( aPixmapRect, BG_FILL );
            if( !xPixmap )
                return false;
            nPasses = 1;
            gdkDrawable[0] = xPixmap->GetGdkDrawable();
        }

        aCtrlRect = tools::Rectangle( Point(1,1), aCtrlRect.GetSize() );
        aClip.push_back( aCtrlRect );
    }
    else
    {
        nPasses = 1;
        gdkDrawable[0] = GDK_DRAWABLE( GetGdkWindow() );
        RectangleVector aRectangles;
        aClipRegion.GetRegionRectangles(aRectangles);

        for (auto const& rectangle : aRectangles)
        {
            tools::Rectangle aPaintRect = aCtrlRect.GetIntersection(rectangle);
            if( aPaintRect.IsEmpty() )
                continue;
            aClip.push_back( aPaintRect );
        }
    }

    bool returnVal = false;

    for( int i = 0; i < nPasses; ++i )
    {
        assert(gdkDrawable[i] && "rhbz#1050162");
        if( gdkDrawable[i] == nullptr )
            return false;

        returnVal = DoDrawNativeControl(gdkDrawable[i], nType, nPart, aCtrlRect, aClip,
                                        nState, aValue, aControlCacheKey);
        if( !returnVal )
            break;
    }

    if( xPixmap )
        returnVal = returnVal && RenderAndCacheNativeControl(xPixmap.get(), xMask.get(),
                                                             aPixmapRect.Left(), aPixmapRect.Top(),
                                                             aControlCacheKey);

    return returnVal;
}


bool GtkSalGraphics::DoDrawNativeControl(
                            GdkDrawable* pDrawable,
                            ControlType nType,
                            ControlPart nPart,
                            const tools::Rectangle& aCtrlRect,
                            const std::vector< tools::Rectangle >& aClip,
                            ControlState nState,
                            const ImplControlValue& aValue,
                            ControlCacheKey& rControlCacheKey)
{
    if ( (nType==ControlType::Pushbutton) && (nPart==ControlPart::Entire) )
    {
        return NWPaintGTKButton( pDrawable, aCtrlRect, aClip, nState );
    }
    else if ( (nType==ControlType::Radiobutton) && (nPart==ControlPart::Entire) )
    {
        return NWPaintGTKRadio( pDrawable, aCtrlRect, aClip, nState, aValue );
    }
    else if ( (nType==ControlType::Checkbox) && (nPart==ControlPart::Entire) )
    {
        return NWPaintGTKCheck( pDrawable, aCtrlRect, aClip, nState, aValue );
    }
    else if ( (nType==ControlType::Scrollbar) && ((nPart==ControlPart::DrawBackgroundHorz) || (nPart==ControlPart::DrawBackgroundVert)) )
    {
        return NWPaintGTKScrollbar( nPart, aCtrlRect, nState, aValue );
    }
    else if ( ((nType==ControlType::Editbox) && ((nPart==ControlPart::Entire) || (nPart==ControlPart::HasBackgroundTexture)) )
        || ((nType==ControlType::Spinbox) && (nPart==ControlPart::HasBackgroundTexture))
    || ((nType==ControlType::Combobox) && (nPart==ControlPart::HasBackgroundTexture))
    || ((nType==ControlType::Listbox) && (nPart==ControlPart::HasBackgroundTexture)) )
    {
        return NWPaintGTKEditBox( pDrawable, nType, aCtrlRect, aClip, nState );
    }
    else if ( (nType==ControlType::MultilineEditbox) && ((nPart==ControlPart::Entire) || (nPart==ControlPart::HasBackgroundTexture)) )
    {
        return NWPaintGTKEditBox( pDrawable, nType, aCtrlRect, aClip, nState );
    }
    else if ( ((nType==ControlType::Spinbox) || (nType==ControlType::SpinButtons))
        && ((nPart==ControlPart::Entire) || (nPart==ControlPart::AllButtons)) )
    {
        return NWPaintGTKSpinBox(nType, nPart, aCtrlRect, nState, aValue, rControlCacheKey);
    }
    else if ( (nType == ControlType::Combobox) &&
        ( (nPart==ControlPart::Entire)
        ||(nPart==ControlPart::ButtonDown)
        ) )
    {
        return NWPaintGTKComboBox( pDrawable, nType, nPart, aCtrlRect, aClip, nState );
    }
    else if ( (nType==ControlType::TabItem) || (nType==ControlType::TabPane) || (nType==ControlType::TabBody) )
    {
        if ( nType == ControlType::TabBody )
            return true;
        else
            return NWPaintGTKTabItem( nType, aCtrlRect, nState, aValue);
    }
    else if ( (nType==ControlType::Listbox) && ((nPart==ControlPart::Entire) || (nPart==ControlPart::ListboxWindow)) )
    {
        return NWPaintGTKListBox( pDrawable, nPart, aCtrlRect, aClip, nState );
    }
    else if ( nType== ControlType::Toolbar )
    {
        return NWPaintGTKToolbar( pDrawable, nPart, aCtrlRect, aClip, nState, aValue );
    }
    else if ( nType== ControlType::Menubar )
    {
        return NWPaintGTKMenubar( pDrawable, nPart, aCtrlRect, aClip, nState );
    }
    else if(    (nType == ControlType::MenuPopup)
        && (  (nPart == ControlPart::Entire)
    || (nPart == ControlPart::MenuItem)
    || (nPart == ControlPart::MenuItemCheckMark)
    || (nPart == ControlPart::MenuItemRadioMark)
    || (nPart == ControlPart::Separator)
    || (nPart == ControlPart::SubmenuArrow)
    )
    )
    {
        return NWPaintGTKPopupMenu( pDrawable, nPart, aCtrlRect, aClip, nState );
    }
    else if( (nType == ControlType::Tooltip) && (nPart == ControlPart::Entire) )
    {
        return NWPaintGTKTooltip( pDrawable, aCtrlRect, aClip );
    }
    else if( (nType == ControlType::Progress) && (nPart == ControlPart::Entire) )
    {
        return NWPaintGTKProgress( aCtrlRect, aValue );
    }
    else if( (nType == ControlType::ListNode) && (nPart == ControlPart::Entire) )
    {
        return NWPaintGTKListNode( pDrawable, aCtrlRect, nState, aValue );
    }
    else if( (nType == ControlType::ListNet) && (nPart == ControlPart::Entire) )
    {
        // don't actually draw anything; gtk treeviews do not draw lines
        return TRUE;
    }
    else if( nType == ControlType::Slider )
    {
        return NWPaintGTKSlider(pDrawable, nPart, aCtrlRect, nState, aValue);
    }
    else if( nType == ControlType::WindowBackground )
    {
        return NWPaintGTKWindowBackground( pDrawable, aCtrlRect, aClip );
    }
    else if( nType == ControlType::Fixedline )
    {
        return NWPaintGTKFixedLine( pDrawable, nPart, aCtrlRect );
    }
    else if(nType==ControlType::Frame)
    {
        return NWPaintGTKFrame( pDrawable, aCtrlRect, aClip, aValue);
    }
    else if(nType==ControlType::ListHeader)
    {
        if(nPart == ControlPart::Button)
            return NWPaintGTKListHeader( pDrawable, aCtrlRect, aClip, nState );
        else if(nPart == ControlPart::Arrow)
            return NWPaintGTKArrow( pDrawable, aCtrlRect, aClip, nState, aValue );
    }

    return false;
}

bool GtkSalGraphics::getNativeControlRegion(  ControlType nType,
                                ControlPart nPart,
                                const tools::Rectangle& rControlRegion,
                                ControlState nState,
                                const ImplControlValue& aValue,
                                const OUString& /*rCaption*/,
                                tools::Rectangle &rNativeBoundingRegion,
                                tools::Rectangle &rNativeContentRegion )
{
    bool returnVal = false;

    if ( (nType==ControlType::Pushbutton) && (nPart==ControlPart::Entire)
        && (rControlRegion.GetWidth() > 16)
    && (rControlRegion.GetHeight() > 16) )
    {
        rNativeBoundingRegion = NWGetButtonArea( m_nXScreen, rControlRegion, nState );
        rNativeContentRegion = rControlRegion;
        returnVal = true;
    }
    if (nType == ControlType::TabItem && nPart == ControlPart::Entire)
    {
        rNativeBoundingRegion = NWGetTabItemRect(m_nXScreen, rControlRegion);
        rNativeContentRegion = rNativeBoundingRegion;
        returnVal = true;
    }
    if ( (nType==ControlType::Combobox) && ((nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
    {
        rNativeBoundingRegion = NWGetComboBoxButtonRect( m_nXScreen, nPart, rControlRegion);
        rNativeContentRegion = rNativeBoundingRegion;

        returnVal = true;
    }
    if ( (nType==ControlType::Spinbox) && ((nPart==ControlPart::ButtonUp) || (nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
    {

        rNativeBoundingRegion = NWGetSpinButtonRect( m_nXScreen, nPart, rControlRegion );
        rNativeContentRegion = rNativeBoundingRegion;

        returnVal = true;
    }
    if ( (nType==ControlType::Listbox) && ((nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
    {
        rNativeBoundingRegion = NWGetListBoxButtonRect( m_nXScreen, nPart, rControlRegion);
        rNativeContentRegion = rNativeBoundingRegion;

        returnVal = true;
    }
    if ( (nType==ControlType::Toolbar) &&
        ((nPart==ControlPart::DrawBackgroundHorz)    ||
        (nPart==ControlPart::DrawBackgroundVert)    ||
        (nPart==ControlPart::ThumbHorz)            ||
        (nPart==ControlPart::ThumbVert)            ||
        (nPart==ControlPart::Button)
        ))
    {
        rNativeBoundingRegion = NWGetToolbarRect( m_nXScreen, nPart, rControlRegion );
        rNativeContentRegion = rNativeBoundingRegion;
        returnVal = true;
    }
    if ( (nType==ControlType::Scrollbar) && ((nPart==ControlPart::ButtonLeft) || (nPart==ControlPart::ButtonRight) ||
        (nPart==ControlPart::ButtonUp) || (nPart==ControlPart::ButtonDown)  ) )
    {
        rNativeBoundingRegion = NWGetScrollButtonRect( m_nXScreen, nPart, rControlRegion );
        rNativeContentRegion = rNativeBoundingRegion;

        //See fdo#33523, possibly makes sense to do this test for all return values
        if (!rNativeContentRegion.GetWidth())
            rNativeContentRegion.SetRight( rNativeContentRegion.Left() + 1 );
        if (!rNativeContentRegion.GetHeight())
            rNativeContentRegion.SetBottom( rNativeContentRegion.Top() + 1 );
        returnVal = true;
    }
    if( (nType == ControlType::Menubar) && (nPart == ControlPart::Entire) )
    {
        NWEnsureGTKMenubar( m_nXScreen );
        GtkRequisition aReq;
        gtk_widget_size_request( gWidgetData[m_nXScreen].gMenubarWidget, &aReq );
        rNativeBoundingRegion = tools::Rectangle( rControlRegion.TopLeft(),
                                       Size( rControlRegion.GetWidth(), aReq.height+1 ) );
        rNativeContentRegion = rNativeBoundingRegion;
        returnVal = true;
    }
    if( nType == ControlType::MenuPopup )
    {
        if( (nPart == ControlPart::MenuItemCheckMark) ||
            (nPart == ControlPart::MenuItemRadioMark) )
        {
            NWEnsureGTKMenu( m_nXScreen );

            gint indicator_size = 0;
            GtkWidget* pWidget = (nPart == ControlPart::MenuItemCheckMark) ?
                gWidgetData[m_nXScreen].gMenuItemCheckMenuWidget : gWidgetData[m_nXScreen].gMenuItemRadioMenuWidget;
            gtk_widget_style_get( pWidget,
                                  "indicator_size", &indicator_size,
                                  nullptr );
            rNativeBoundingRegion = rControlRegion;
            tools::Rectangle aIndicatorRect( Point( 0,
                                             (rControlRegion.GetHeight()-indicator_size)/2),
                                      Size( indicator_size, indicator_size ) );
            rNativeContentRegion = aIndicatorRect;
            returnVal = true;
        }
        else if( nPart == ControlPart::SubmenuArrow )
        {
            GtkWidget* widget = gWidgetData[m_nXScreen].gMenuItemMenuWidget;
            GtkWidget* child;
            PangoContext *context;
            PangoFontMetrics *metrics;
            gint arrow_size;
            gint arrow_extent;
            guint horizontal_padding;
            gfloat arrow_scaling = 0.4; // Default for early GTK versions

            gtk_widget_style_get( widget,
                                  "horizontal-padding", &horizontal_padding,
                                  nullptr );

            // Use arrow-scaling property if available (2.15+), avoid warning otherwise
            if ( gtk_widget_class_find_style_property( GTK_WIDGET_GET_CLASS( widget ),
                                                       "arrow-scaling" ) )
            {
                gtk_widget_style_get( widget,
                                      "arrow-scaling", &arrow_scaling,
                                      nullptr );
            }

            child = GTK_BIN( widget )->child;

            context = gtk_widget_get_pango_context( child );
            metrics = pango_context_get_metrics( context,
                                                 child->style->font_desc,
                                                 pango_context_get_language( context ) );

            arrow_size = PANGO_PIXELS( pango_font_metrics_get_ascent( metrics ) +
                                       pango_font_metrics_get_descent( metrics ) );

            pango_font_metrics_unref( metrics );

            arrow_extent = static_cast<gint>(arrow_size * arrow_scaling);

            rNativeContentRegion = tools::Rectangle( Point( 0, 0 ),
                                              Size( arrow_extent, arrow_extent ));
            rNativeBoundingRegion = tools::Rectangle( Point( 0, 0 ),
                                               Size( arrow_extent + horizontal_padding, arrow_extent ));
            returnVal = true;
        }
    }
    if( nType == ControlType::Radiobutton || nType == ControlType::Checkbox )
    {
        NWEnsureGTKRadio( m_nXScreen );
        NWEnsureGTKCheck( m_nXScreen );
        GtkWidget* widget = (nType == ControlType::Radiobutton) ? gWidgetData[m_nXScreen].gRadioWidget : gWidgetData[m_nXScreen].gCheckWidget;
        gint indicator_size, indicator_spacing, focusPad, focusWidth;
        gtk_widget_style_get( widget,
                              "indicator_size", &indicator_size,
                              "indicator_spacing", &indicator_spacing,
                              "focus-line-width", &focusWidth,
                              "focus-padding", &focusPad,
                              nullptr);
        indicator_size += 2*indicator_spacing + 2*(focusWidth + focusWidth);
        rNativeBoundingRegion = rControlRegion;
        tools::Rectangle aIndicatorRect( Point( 0,
                                         (rControlRegion.GetHeight()-indicator_size)/2),
                                  Size( indicator_size, indicator_size ) );
        rNativeContentRegion = aIndicatorRect;
        returnVal = true;
    }
    if( (nType == ControlType::Editbox || nType == ControlType::Spinbox || nType == ControlType::Combobox) && nPart == ControlPart::Entire )
    {
        NWEnsureGTKEditBox( m_nXScreen );
        GtkWidget* widget = gWidgetData[m_nXScreen].gEditBoxWidget;
        GtkRequisition aReq;
        gtk_widget_size_request( widget, &aReq );
        tools::Rectangle aEditRect = rControlRegion;
        long nHeight = std::max<long>(aEditRect.GetHeight(), aReq.height);
        aEditRect = tools::Rectangle( aEditRect.TopLeft(),
                               Size( aEditRect.GetWidth(), nHeight ) );
        rNativeBoundingRegion = aEditRect;
        rNativeContentRegion = rNativeBoundingRegion;
        returnVal = true;
    }
    if( (nType == ControlType::Slider) && (nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert) )
    {
        NWEnsureGTKSlider( m_nXScreen );
        GtkWidget* widget = (nPart == ControlPart::ThumbHorz) ? gWidgetData[m_nXScreen].gHScale : gWidgetData[m_nXScreen].gVScale;
        gint slider_length = 10;
        gint slider_width = 10;
        gtk_widget_style_get( widget,
                              "slider-width", &slider_width,
                              "slider-length", &slider_length,
                              nullptr);
        tools::Rectangle aRect( rControlRegion );
        if( nPart == ControlPart::ThumbHorz )
        {
            aRect.SetRight( aRect.Left() + slider_length - 1 );
            aRect.SetBottom( aRect.Top() + slider_width - 1 );
        }
        else
        {
            aRect.SetBottom( aRect.Top() + slider_length - 1 );
            aRect.SetRight( aRect.Left() + slider_width - 1 );
        }
        rNativeBoundingRegion = rNativeContentRegion = aRect;
        returnVal = true;
    }
    if( nType == ControlType::Frame && nPart == ControlPart::Border )
    {
        int frameWidth = getFrameWidth(gWidgetData[m_nXScreen].gFrame);
        rNativeBoundingRegion = rControlRegion;
        DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(aValue.getNumericVal() & 0xfff0);
        int x1=rControlRegion.Left();
        int y1=rControlRegion.Top();
        int x2=rControlRegion.Right();
        int y2=rControlRegion.Bottom();

        if( nStyle & DrawFrameFlags::NoDraw )
        {
            rNativeContentRegion = tools::Rectangle(x1+frameWidth,
                                             y1+frameWidth,
                                             x2-frameWidth,
                                             y2-frameWidth);
        }
        else
            rNativeContentRegion = rControlRegion;
        returnVal=true;
    }

    return returnVal;
}

/************************************************************************
 * Individual control drawing functions
 ************************************************************************/

// macros to call before and after the rendering code for a widget
// it takes care of creating the needed pixmaps
#define BEGIN_PIXMAP_RENDER(aRect, gdkPixmap) \
    std::unique_ptr<GdkX11Pixmap> _pixmap, _mask; \
    int _nPasses = 0; \
    if( bNeedTwoPasses ) \
    { \
        _nPasses = 2; \
        _pixmap = NWGetPixmapFromScreen( aRect, BG_WHITE ); \
        _mask = NWGetPixmapFromScreen( aRect, BG_BLACK ); \
    } \
    else \
    { \
        _nPasses = 1; \
        _pixmap = NWGetPixmapFromScreen( aRect, BG_FILL ); \
    } \
    if( !_pixmap || ( bNeedTwoPasses && !_mask ) ) \
        return false; \
    for( int i = 0; i < _nPasses; ++i ) \
    { \
        GdkPixmap* gdkPixmap = (i == 0) ? _pixmap->GetGdkPixmap() \
                                        : _mask->GetGdkPixmap();

#define END_PIXMAP_RENDER(aRect) \
    } \
    if( !NWRenderPixmapToScreen( _pixmap.get(), _mask.get(), aRect ) ) \
        return false;

#define END_PIXMAP_RENDER_WITH_CONTROL_KEY(aRect, aControlKey) \
    } \
    if( !RenderAndCacheNativeControl( _pixmap.get(), _mask.get(), aRect.Left(), aRect.Top(), aControlKey ) ) \
        return false;

// same as above but with pixmaps that should be kept for caching
#define BEGIN_CACHE_PIXMAP_RENDER(aRect, pixmap, mask, gdkPixmap) \
    int _nPasses = 0; \
    if( bNeedTwoPasses ) \
    { \
        _nPasses = 2; \
        pixmap = NWGetPixmapFromScreen( aRect, BG_WHITE ).release(); \
        mask = NWGetPixmapFromScreen( aRect, BG_BLACK ).release(); \
    } \
    else \
    { \
        _nPasses = 1; \
        pixmap = NWGetPixmapFromScreen( aRect, BG_FILL ).release(); \
        mask = nullptr; \
    } \
    if( !pixmap || ( bNeedTwoPasses && !mask ) ) \
        return false; \
    for( int i = 0; i < _nPasses; ++i ) \
    { \
        GdkPixmap* gdkPixmap = (i == 0) ? pixmap->GetGdkPixmap() \
                                        : mask->GetGdkPixmap();

#define END_CACHE_PIXMAP_RENDER(aRect, pixmap, mask) \
    } \
    if( !NWRenderPixmapToScreen( pixmap, mask, aRect ) ) \
        return false;

bool GtkSalGraphics::NWPaintGTKArrow(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState, const ImplControlValue& aValue)
{
    GtkArrowType arrowType(aValue.getNumericVal()&1?GTK_ARROW_DOWN:GTK_ARROW_UP);
    GtkStateType stateType(nState&ControlState::PRESSED?GTK_STATE_ACTIVE:GTK_STATE_NORMAL);

    GdkRectangle clipRect;
    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        gtk_paint_arrow(m_pWindow->style,gdkDrawable,stateType,GTK_SHADOW_NONE,&clipRect,
                m_pWindow,"arrow",arrowType,true,
                rControlRectangle.Left(),
                rControlRectangle.Top(),
                rControlRectangle.GetWidth(),
                rControlRectangle.GetHeight());
    }
    return true;
}

bool GtkSalGraphics::NWPaintGTKListHeader(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState )
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    NWEnsureGTKTreeView( m_nXScreen );
    GtkWidget* &treeview(gWidgetData[m_nXScreen].gTreeView);
    GtkTreeViewColumn* column=gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),0);
    GtkWidget* button=gtk_tree_view_column_get_widget(column);
    while(button && !GTK_IS_BUTTON(button))
        button=gtk_widget_get_parent(button);
    if(!button)
        // Shouldn't ever happen
        return false;
    gtk_widget_realize(button);
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );
    NWSetWidgetState( button, nState, stateType );

    GdkRectangle clipRect;
    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        gtk_paint_box(button->style,gdkDrawable,stateType,shadowType,&clipRect,
                button,"button",
                rControlRectangle.Left()-1,
                rControlRectangle.Top(),
                rControlRectangle.GetWidth()+1,
                rControlRectangle.GetHeight());
    }
    return true;
}

bool GtkSalGraphics::NWPaintGTKFixedLine(
            GdkDrawable* gdkDrawable,
            ControlPart nPart,
            const tools::Rectangle& rControlRectangle )
{
    if(nPart == ControlPart::SeparatorHorz)
        gtk_paint_hline(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,nullptr,m_pWindow,"hseparator",rControlRectangle.Left(),rControlRectangle.Right(),rControlRectangle.Top());
    else
        gtk_paint_vline(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,nullptr,m_pWindow,"vseparator",rControlRectangle.Top(),rControlRectangle.Bottom(),rControlRectangle.Left());

    return true;
}

bool GtkSalGraphics::NWPaintGTKFrame(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            const ImplControlValue& aValue )
{
    GdkRectangle clipRect;
    int frameWidth=getFrameWidth(gWidgetData[m_nXScreen].gFrame);
    GtkShadowType shadowType=GTK_SHADOW_IN;
    DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(aValue.getNumericVal() & 0x0f);
    if( nStyle == DrawFrameStyle::In )
        shadowType=GTK_SHADOW_OUT;
    if( nStyle == DrawFrameStyle::Out )
        shadowType=GTK_SHADOW_IN;

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        // Draw background first

        // Top
        gtk_paint_flat_box(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,GTK_SHADOW_OUT,&clipRect,
                         m_pWindow,"base",
                         rControlRectangle.Left(),
                         rControlRectangle.Top(),
                         rControlRectangle.GetWidth(),
                         frameWidth);
        // Bottom
        gtk_paint_flat_box(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,GTK_SHADOW_OUT,&clipRect,
                         m_pWindow,"base",
                         rControlRectangle.Left(),
                         rControlRectangle.Top()+rControlRectangle.GetHeight()-frameWidth,
                         rControlRectangle.GetWidth(),
                         frameWidth);
        // Left
        gtk_paint_flat_box(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,GTK_SHADOW_OUT,&clipRect,
                         m_pWindow,"base",
                         rControlRectangle.Left(),
                         rControlRectangle.Top(),
                         2*frameWidth,
                         rControlRectangle.GetHeight());
        // Right
        gtk_paint_flat_box(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,GTK_SHADOW_OUT,&clipRect,
                         m_pWindow,"base",
                         rControlRectangle.Left()+rControlRectangle.GetWidth()-frameWidth,
                         rControlRectangle.Top(),
                         2*frameWidth,
                         rControlRectangle.GetHeight());

        // Now render the frame
        gtk_paint_shadow(gWidgetData[m_nXScreen].gFrame->style,gdkDrawable,GTK_STATE_NORMAL,shadowType,&clipRect,
                         gWidgetData[m_nXScreen].gFrame,"base",
                         rControlRectangle.Left(),
                         rControlRectangle.Top(),
                         rControlRectangle.GetWidth(),
                         rControlRectangle.GetHeight());
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKWindowBackground(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList )
{
    GdkRectangle clipRect;
    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        gtk_paint_flat_box(m_pWindow->style,gdkDrawable,GTK_STATE_NORMAL,GTK_SHADOW_NONE,&clipRect,
                           m_pWindow,"base",
                           rControlRectangle.Left(),
                           rControlRectangle.Top(),
                           rControlRectangle.GetWidth(),
                           rControlRectangle.GetHeight());
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKButtonReal(
            GtkWidget* button,
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState )
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    gboolean        interiorFocus;
    gint            focusWidth;
    gint            focusPad;
    bool            bDrawFocus = true;
    gint            x, y, w, h;
    GtkBorder        aDefBorder;
    GtkBorder*        pBorder;
    GdkRectangle    clipRect;

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKToolbar( m_nXScreen );

    // Flat toolbutton has a bit bigger variety of states than normal buttons, so handle it differently
    if(GTK_IS_TOGGLE_BUTTON(button))
    {
       if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
           shadowType=GTK_SHADOW_IN;
       else
           shadowType=GTK_SHADOW_OUT;

       if(nState & ControlState::ROLLOVER)
           stateType=GTK_STATE_PRELIGHT;
       else
           stateType=GTK_STATE_NORMAL;

       if(nState & ControlState::PRESSED)
       {
           stateType=GTK_STATE_ACTIVE;
           shadowType=GTK_SHADOW_IN;
       }
    }
    else
    {
        NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );
        NWSetWidgetState( gWidgetData[m_nXScreen].gBtnWidget, nState, stateType );
    }

    x = rControlRectangle.Left();
    y = rControlRectangle.Top();
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    gint internal_padding = 0;
    if(GTK_IS_TOOL_ITEM(button))
    {
        gtk_widget_style_get (GTK_WIDGET (gWidgetData[m_nXScreen].gToolbarWidget),
                "internal-padding", &internal_padding,
                nullptr);
        x += internal_padding/2;
        w -= internal_padding;
        stateType = GTK_STATE_PRELIGHT;
    }

    // Grab some button style attributes
    gtk_widget_style_get( gWidgetData[m_nXScreen].gBtnWidget,    "focus-line-width",    &focusWidth,
                                "focus-padding",     &focusPad,
                                 "interior_focus",    &interiorFocus,
                                nullptr );
    gtk_widget_style_get( gWidgetData[m_nXScreen].gBtnWidget,
                                "default_border",    &pBorder,
                                nullptr );

    // Make sure the border values exist, otherwise use some defaults
    if ( pBorder )
    {
        NW_gtk_border_set_from_border( aDefBorder, pBorder );
        gtk_border_free( pBorder );
    }
    else NW_gtk_border_set_from_border( aDefBorder, &aDefDefBorder );

    // If the button is too small, don't ever draw focus or grab more space
    if ( (w < 16) || (h < 16) )
        bDrawFocus = false;

    gint xi = x, yi = y, wi = w, hi = h;
    if ( (nState & ControlState::DEFAULT) && bDrawFocus )
    {
        xi += aDefBorder.left;
        yi += aDefBorder.top;
        wi -= aDefBorder.left + aDefBorder.right;
        hi -= aDefBorder.top + aDefBorder.bottom;
    }

    if ( !interiorFocus && bDrawFocus )
    {
        xi += focusWidth + focusPad;
        yi += focusWidth + focusPad;
        wi -= 2 * (focusWidth + focusPad);
        hi -= 2 * (focusWidth + focusPad);
    }
    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        // Buttons must paint opaque since some themes have alpha-channel enabled buttons
        if(button == gWidgetData[m_nXScreen].gToolbarButtonWidget)
        {
            gtk_paint_box( gWidgetData[m_nXScreen].gToolbarWidget->style, gdkDrawable, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
                                &clipRect, gWidgetData[m_nXScreen].gToolbarWidget, "toolbar", x, y, w, h );
        }
        else
        {
            gtk_paint_box( m_pWindow->style, gdkDrawable, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
                                &clipRect, m_pWindow, "base", x, y, w, h );
        }

        if ( GTK_IS_BUTTON(button) )
        {
            if ( nState & ControlState::DEFAULT )
                gtk_paint_box( button->style, gdkDrawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
                               &clipRect, button, "buttondefault", x, y, w, h );

            /* don't draw "button", because it can be a tool_button, and
             * it causes some weird things, so, the default button is
             * just fine */
            gtk_paint_box( button->style, gdkDrawable, stateType, shadowType,
                           &clipRect, button, "button", xi, yi, wi, hi );
        }
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKButton(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState)
{
        return NWPaintGTKButtonReal(
            gWidgetData[m_nXScreen].gBtnWidget,
            gdkDrawable,
            rControlRectangle,
            rClipList,
            nState );
}

static tools::Rectangle NWGetButtonArea( SalX11Screen nScreen,
                                  tools::Rectangle aAreaRect, ControlState nState )
{
    gboolean        interiorFocus;
    gint            focusWidth;
    gint            focusPad;
    GtkBorder        aDefBorder;
    GtkBorder *    pBorder;
    bool            bDrawFocus = true;
    tools::Rectangle        aRect;
    gint            x, y, w, h;

    NWEnsureGTKButton( nScreen );
    gtk_widget_style_get( gWidgetData[nScreen].gBtnWidget,
                                "focus-line-width",    &focusWidth,
                                "focus-padding",     &focusPad,
                                 "interior_focus",    &interiorFocus,
                                "default_border",    &pBorder,
                                nullptr );

    // Make sure the border values exist, otherwise use some defaults
    if ( pBorder )
    {
        NW_gtk_border_set_from_border( aDefBorder, pBorder );
        gtk_border_free( pBorder );
    }
    else NW_gtk_border_set_from_border( aDefBorder, &aDefDefBorder );

    x = aAreaRect.Left();
    y = aAreaRect.Top();
    w = aAreaRect.GetWidth();
    h = aAreaRect.GetHeight();

    // If the button is too small, don't ever draw focus or grab more space
    if ( (w < 16) || (h < 16) )
        bDrawFocus = false;

    if ( (nState & ControlState::DEFAULT) && bDrawFocus )
    {
        x -= aDefBorder.left;
        y -= aDefBorder.top;
        w += aDefBorder.left + aDefBorder.right;
        h += aDefBorder.top + aDefBorder.bottom;
    }

    aRect = tools::Rectangle( Point( x, y ), Size( w, h ) );

    return aRect;
}

static tools::Rectangle NWGetTabItemRect( SalX11Screen nScreen, tools::Rectangle aAreaRect )
{
    NWEnsureGTKNotebook( nScreen );

    gint            x, y, w, h;

    x = aAreaRect.Left();
    y = aAreaRect.Top();
    w = aAreaRect.GetWidth();
    h = aAreaRect.GetHeight();

    gint xthickness = gWidgetData[nScreen].gNotebookWidget->style->xthickness;
    gint ythickness = gWidgetData[nScreen].gNotebookWidget->style->ythickness;

    x -= xthickness;
    y -= ythickness;
    w += xthickness*2;
    h += ythickness*2;

    return tools::Rectangle( Point( x, y ), Size( w, h ) );
}

bool GtkSalGraphics::NWPaintGTKRadio( GdkDrawable* gdkDrawable,
                                      const tools::Rectangle& rControlRectangle,
                                      const std::vector< tools::Rectangle >& rClipList,
                                      ControlState nState,
                                      const ImplControlValue& aValue )
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    bool            isChecked = (aValue.getTristateVal()==ButtonValue::On);
    gint            x, y;
    GdkRectangle    clipRect;

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKRadio( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    gint indicator_size;
    gtk_widget_style_get( gWidgetData[m_nXScreen].gRadioWidget, "indicator_size", &indicator_size, nullptr);

    x = rControlRectangle.Left() + (rControlRectangle.GetWidth()-indicator_size)/2;
    y = rControlRectangle.Top() + (rControlRectangle.GetHeight()-indicator_size)/2;

    // Set the shadow based on if checked or not so we get a freakin checkmark.
    shadowType = isChecked ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
    NWSetWidgetState( gWidgetData[m_nXScreen].gRadioWidget, nState, stateType );
    NWSetWidgetState( gWidgetData[m_nXScreen].gRadioWidgetSibling, nState, stateType );

    // GTK enforces radio groups, so that if we don't have 2 buttons in the group,
    // the single button will always be active.  So we have to have 2 buttons.

    // #i59666# set the members directly where we should use
    // gtk_toggle_button_set_active. reason: there are animated themes
    // which are in active state only after a while leading to painting
    // intermediate states between active/inactive. Let's hope that
    // GtkToggleButtone stays binary compatible.
    if (!isChecked)
        GTK_TOGGLE_BUTTON(gWidgetData[m_nXScreen].gRadioWidgetSibling)->active = true;
    GTK_TOGGLE_BUTTON(gWidgetData[m_nXScreen].gRadioWidget)->active = isChecked;

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        gtk_paint_option( gWidgetData[m_nXScreen].gRadioWidget->style, gdkDrawable, stateType, shadowType,
                          &clipRect, gWidgetData[m_nXScreen].gRadioWidget, "radiobutton",
                          x, y, indicator_size, indicator_size );
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKCheck( GdkDrawable* gdkDrawable,
                                      const tools::Rectangle& rControlRectangle,
                                      const std::vector< tools::Rectangle >& rClipList,
                                      ControlState nState,
                                      const ImplControlValue& aValue )
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    bool            isChecked = (aValue.getTristateVal() == ButtonValue::On);
    bool            isInconsistent = (aValue.getTristateVal() == ButtonValue::Mixed);
    GdkRectangle    clipRect;
    gint            x,y;

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKCheck( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    gint indicator_size;
    gtk_widget_style_get( gWidgetData[m_nXScreen].gCheckWidget, "indicator_size", &indicator_size, nullptr);

    x = rControlRectangle.Left() + (rControlRectangle.GetWidth()-indicator_size)/2;
    y = rControlRectangle.Top() + (rControlRectangle.GetHeight()-indicator_size)/2;

    // Set the shadow based on if checked or not so we get a checkmark.
    shadowType = isChecked ? GTK_SHADOW_IN : isInconsistent ? GTK_SHADOW_ETCHED_IN : GTK_SHADOW_OUT;
    NWSetWidgetState( gWidgetData[m_nXScreen].gCheckWidget, nState, stateType );
    GTK_TOGGLE_BUTTON(gWidgetData[m_nXScreen].gCheckWidget)->active = isChecked;

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        gtk_paint_check( gWidgetData[m_nXScreen].gCheckWidget->style, gdkDrawable, stateType, shadowType,
                         &clipRect, gWidgetData[m_nXScreen].gCheckWidget, "checkbutton",
                         x, y, indicator_size, indicator_size );
    }

    return true;
}

static void NWCalcArrowRect( const tools::Rectangle& rButton, tools::Rectangle& rArrow )
{
    // Size the arrow appropriately
    Size aSize( rButton.GetWidth()/2, rButton.GetHeight()/2 );
    rArrow.SetSize( aSize );

    rArrow.SetPos( Point(
        rButton.Left() + ( rButton.GetWidth()  - rArrow.GetWidth()  ) / 2,
        rButton.Top() + ( rButton.GetHeight() - rArrow.GetHeight() ) / 2
        ) );
}

bool GtkSalGraphics::NWPaintGTKScrollbar( ControlPart nPart,
                                          const tools::Rectangle& rControlRectangle,
                                          ControlState nState,
                                          const ImplControlValue& aValue )
{
    assert(aValue.getType() == ControlType::Scrollbar);
    const ScrollbarValue& rScrollbarVal = static_cast<const ScrollbarValue&>(aValue);
    std::unique_ptr<GdkX11Pixmap> pixmap;
    tools::Rectangle        pixmapRect, scrollbarRect;
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    GtkScrollbar *    scrollbarWidget;
    GtkStyle *    style;
    GtkAdjustment* scrollbarValues = nullptr;
    GtkOrientation    scrollbarOrientation;
    tools::Rectangle        thumbRect = rScrollbarVal.maThumbRect;
    tools::Rectangle        button11BoundRect = rScrollbarVal.maButton1Rect;   // backward
    tools::Rectangle        button22BoundRect = rScrollbarVal.maButton2Rect;   // forward
    tools::Rectangle        button12BoundRect = rScrollbarVal.maButton1Rect;   // secondary forward
    tools::Rectangle        button21BoundRect = rScrollbarVal.maButton2Rect;   // secondary backward
    GtkArrowType    button1Type;                                        // backward
    GtkArrowType    button2Type;                                        // forward
    gchar *        scrollbarTagH = const_cast<gchar *>("hscrollbar");
    gchar *        scrollbarTagV = const_cast<gchar *>("vscrollbar");
    gchar *        scrollbarTag = nullptr;
    tools::Rectangle        arrowRect;
    gint            slider_width = 0;
    gint            stepper_size = 0;
    gint            stepper_spacing = 0;
    gint            trough_border = 0;
    gint            min_slider_length = 0;
    gint            vShim = 0;
    gint            hShim = 0;
    gint            x,y,w,h;

    // make controlvalue rectangles relative to area
    thumbRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
    button11BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
    button22BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
    button12BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
    button21BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKScrollbars( m_nXScreen );
    NWEnsureGTKArrow( m_nXScreen );

    // Find the overall bounding rect of the control
    pixmapRect = rControlRectangle;
    scrollbarRect = pixmapRect;

    if ( (scrollbarRect.GetWidth() <= 1) || (scrollbarRect.GetHeight() <= 1) )
        return true;

    // Grab some button style attributes
    gtk_widget_style_get( gWidgetData[m_nXScreen].gScrollHorizWidget,
                                      "slider_width", &slider_width,
                                      "stepper_size", &stepper_size,
                                      "trough_border", &trough_border,
                                      "stepper_spacing", &stepper_spacing,
                                      "min_slider_length", &min_slider_length, nullptr );
    gboolean has_forward;
    gboolean has_forward2;
    gboolean has_backward;
    gboolean has_backward2;

    gtk_widget_style_get( gWidgetData[m_nXScreen].gScrollHorizWidget, "has-forward-stepper", &has_forward,
                                      "has-secondary-forward-stepper", &has_forward2,
                                      "has-backward-stepper", &has_backward,
                                         "has-secondary-backward-stepper", &has_backward2, nullptr );
    gint magic = trough_border ? 1 : 0;

    if ( nPart == ControlPart::DrawBackgroundHorz )
    {
        unsigned int sliderHeight = slider_width + (trough_border * 2);
        vShim = (pixmapRect.GetHeight() - sliderHeight) / 2;
        bool bRTLSwap = button11BoundRect.Left() > button22BoundRect.Left();

        scrollbarRect.Move( 0, vShim );
        scrollbarRect.SetSize( Size( scrollbarRect.GetWidth(), sliderHeight ) );

        scrollbarWidget = GTK_SCROLLBAR( gWidgetData[m_nXScreen].gScrollHorizWidget );
        scrollbarOrientation = GTK_ORIENTATION_HORIZONTAL;
        scrollbarTag = scrollbarTagH;
        button1Type = bRTLSwap? GTK_ARROW_RIGHT: GTK_ARROW_LEFT;
        button2Type = bRTLSwap? GTK_ARROW_LEFT: GTK_ARROW_RIGHT;

        if ( has_backward )
        {
            button12BoundRect.Move( stepper_size - trough_border,
                                    (scrollbarRect.GetHeight() - slider_width) / 2 );
        }

        button11BoundRect.Move( trough_border, (scrollbarRect.GetHeight() - slider_width) / 2 );
        button11BoundRect.SetSize( Size( stepper_size, slider_width ) );
        button12BoundRect.SetSize( Size( stepper_size, slider_width ) );

        if ( has_backward2 )
        {
            button22BoundRect.Move( stepper_size+(trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
            button21BoundRect.Move( (trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
        }
        else
        {
            button22BoundRect.Move( (trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
        }

        button21BoundRect.SetSize( Size( stepper_size, slider_width ) );
        button22BoundRect.SetSize( Size( stepper_size, slider_width ) );

        thumbRect.SetBottom( thumbRect.Top() + slider_width - 1 );
        // Make sure the thumb is at least the default width (so we don't get tiny thumbs),
        // but if the VCL gives us a size smaller than the theme's default thumb size,
        // honor the VCL size
        thumbRect.AdjustRight(magic );
        // Center vertically in the track
        thumbRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
    }
    else
    {
        unsigned int sliderWidth = slider_width + (trough_border * 2);
        hShim = (pixmapRect.GetWidth() - sliderWidth) / 2;

        scrollbarRect.Move( hShim, 0 );
        scrollbarRect.SetSize( Size( sliderWidth, scrollbarRect.GetHeight() ) );

        scrollbarWidget = GTK_SCROLLBAR( gWidgetData[m_nXScreen].gScrollVertWidget );
        scrollbarOrientation = GTK_ORIENTATION_VERTICAL;
        scrollbarTag = scrollbarTagV;
        button1Type = GTK_ARROW_UP;
        button2Type = GTK_ARROW_DOWN;

        if ( has_backward )
        {
            button12BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2,
                                    stepper_size + trough_border );
        }
        button11BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, trough_border );
        button11BoundRect.SetSize( Size( slider_width, stepper_size ) );
        button12BoundRect.SetSize( Size( slider_width, stepper_size ) );

        if ( has_backward2 )
        {
            button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, stepper_size+(trough_border+1)/2 );
            button21BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, (trough_border+1)/2 );
        }
        else
        {
            button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, (trough_border+1)/2 );
        }

        button21BoundRect.SetSize( Size( slider_width, stepper_size ) );
        button22BoundRect.SetSize( Size( slider_width, stepper_size ) );

        thumbRect.SetRight( thumbRect.Left() + slider_width - 1 );

        thumbRect.AdjustBottom(magic );
        // Center horizontally in the track
        thumbRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
    }

    bool has_slider = ( thumbRect.GetWidth() > 0 && thumbRect.GetHeight() > 0 );

    scrollbarValues = gtk_range_get_adjustment( GTK_RANGE(scrollbarWidget) );
    if ( scrollbarValues == nullptr )
        scrollbarValues = GTK_ADJUSTMENT( gtk_adjustment_new(0, 0, 0, 0, 0, 0) );
    if ( nPart == ControlPart::DrawBackgroundHorz )
    {
        scrollbarValues->lower = rScrollbarVal.mnMin;
        scrollbarValues->upper = rScrollbarVal.mnMax;
        scrollbarValues->value = rScrollbarVal.mnCur;
        scrollbarValues->page_size = scrollbarRect.GetWidth() / 2;
    }
    else
    {
        scrollbarValues->lower = rScrollbarVal.mnMin;
        scrollbarValues->upper = rScrollbarVal.mnMax;
        scrollbarValues->value = rScrollbarVal.mnCur;
        scrollbarValues->page_size = scrollbarRect.GetHeight() / 2;
    }
    gtk_adjustment_changed( scrollbarValues );

    // as multiple paints are required for the scrollbar
    // painting them directly to the window flickers
    pixmap = NWGetPixmapFromScreen( pixmapRect );
    if( ! pixmap )
        return false;
    x = y = 0;

    w = pixmapRect.GetWidth();
    h = pixmapRect.GetHeight();

    GdkDrawable* const &gdkDrawable = pixmap->GetGdkDrawable();
    GdkRectangle* gdkRect = nullptr;

    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );
    NWSetWidgetState( GTK_WIDGET(scrollbarWidget), nState, stateType );
    NWSetWidgetState( gWidgetData[m_nXScreen].gBtnWidget, nState, stateType );
    style = GTK_WIDGET( scrollbarWidget )->style;

    gtk_style_apply_default_background( m_pWindow->style, gdkDrawable, TRUE,
                                        GTK_STATE_NORMAL, gdkRect,
                                        x, y, w, h );

    // ----------------- TROUGH
    // Pass coordinates of draw rect: window(0,0) -> widget(bottom-right) (coords relative to widget)
    gtk_paint_flat_box(m_pWindow->style, gdkDrawable,
                        GTK_STATE_NORMAL, GTK_SHADOW_NONE, gdkRect,
                        m_pWindow, "base", x-pixmapRect.Left(),y-pixmapRect.Top(),x+pixmapRect.Right(),y+pixmapRect.Bottom());

    gtk_paint_box( style, gdkDrawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN,
                   gdkRect, GTK_WIDGET(scrollbarWidget), "trough",
                   x, y,
                   scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );

    if ( nState & ControlState::FOCUSED )
    {
        gtk_paint_focus( style, gdkDrawable, GTK_STATE_ACTIVE,
                         gdkRect, GTK_WIDGET(scrollbarWidget), "trough",
                         x, y,
                         scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
    }

    // ----------------- THUMB
    if ( has_slider )
    {
        NWConvertVCLStateToGTKState( rScrollbarVal.mnThumbState, &stateType, &shadowType );
        gtk_paint_slider( style, gdkDrawable, stateType, GTK_SHADOW_OUT,
                        gdkRect, GTK_WIDGET(scrollbarWidget), "slider",
                        x+hShim+thumbRect.Left(), y+vShim+thumbRect.Top(),
                        thumbRect.GetWidth(), thumbRect.GetHeight(), scrollbarOrientation );
    }

    // Some engines require allocation, e.g. Clearlooks uses it to identify
    // positions of the buttons, whereupon the first and the last button will
    // have rounded corners.
    GTK_WIDGET(scrollbarWidget)->allocation.x = x;
    GTK_WIDGET(scrollbarWidget)->allocation.y = y;
    GTK_WIDGET(scrollbarWidget)->allocation.width = w;
    GTK_WIDGET(scrollbarWidget)->allocation.height = h;

    bool backwardButtonInsensitive =
        rScrollbarVal.mnCur == rScrollbarVal.mnMin;
    bool forwardButtonInsensitive = rScrollbarVal.mnMax == 0 ||
        rScrollbarVal.mnCur + rScrollbarVal.mnVisibleSize >= rScrollbarVal.mnMax;

    // ----------------- BUTTON 1
    if ( has_backward )
    {
        NWConvertVCLStateToGTKState( rScrollbarVal.mnButton1State, &stateType, &shadowType );
        if ( backwardButtonInsensitive )
            stateType = GTK_STATE_INSENSITIVE;
        gtk_paint_box( style, gdkDrawable, stateType, shadowType,
                       gdkRect, GTK_WIDGET(scrollbarWidget), scrollbarTag,
                       x+hShim+button11BoundRect.Left(), y+vShim+button11BoundRect.Top(),
                       button11BoundRect.GetWidth(), button11BoundRect.GetHeight() );
        // ----------------- ARROW 1
        NWCalcArrowRect( button11BoundRect, arrowRect );
        gtk_paint_arrow( style, gdkDrawable, stateType, shadowType,
                         gdkRect, GTK_WIDGET(scrollbarWidget), scrollbarTag, button1Type, true,
                         x+hShim+arrowRect.Left(), y+vShim+arrowRect.Top(),
                         arrowRect.GetWidth(), arrowRect.GetHeight() );
    }
    if ( has_forward2 )
    {
        NWConvertVCLStateToGTKState( rScrollbarVal.mnButton2State, &stateType, &shadowType );
        if ( forwardButtonInsensitive )
            stateType = GTK_STATE_INSENSITIVE;
        gtk_paint_box( style, gdkDrawable, stateType, shadowType,
                       gdkRect, GTK_WIDGET(scrollbarWidget), scrollbarTag,
                       x+hShim+button12BoundRect.Left(), y+vShim+button12BoundRect.Top(),
                       button12BoundRect.GetWidth(), button12BoundRect.GetHeight() );
        // ----------------- ARROW 1
        NWCalcArrowRect( button12BoundRect, arrowRect );
        gtk_paint_arrow( style, gdkDrawable, stateType, shadowType,
                         gdkRect, GTK_WIDGET(scrollbarWidget), scrollbarTag, button2Type, true,
                         x+hShim+arrowRect.Left(), y+vShim+arrowRect.Top(),
                         arrowRect.GetWidth(), arrowRect.GetHeight() );
    }
    // ----------------- BUTTON 2
    if ( has_backward2 )
    {
        NWConvertVCLStateToGTKState( rScrollbarVal.mnButton1State, &stateType, &shadowType );
        if ( backwardButtonInsensitive )
            stateType = GTK_STATE_INSENSITIVE;
        gtk_paint_box( style, gdkDrawable, stateType, shadowType, gdkRect,
                       GTK_WIDGET(scrollbarWidget), scrollbarTag,
                       x+hShim+button21BoundRect.Left(), y+vShim+button21BoundRect.Top(),
                       button21BoundRect.GetWidth(), button21BoundRect.GetHeight() );
        // ----------------- ARROW 2
        NWCalcArrowRect( button21BoundRect, arrowRect );
        gtk_paint_arrow( style, gdkDrawable, stateType, shadowType,
                         gdkRect, GTK_WIDGET(scrollbarWidget), scrollbarTag, button1Type, true,
                         x+hShim+arrowRect.Left(), y+vShim+arrowRect.Top(),
                         arrowRect.GetWidth(), arrowRect.GetHeight() );
    }
    if ( has_forward )
    {
        NWConvertVCLStateToGTKState( rScrollbarVal.mnButton2State, &stateType, &shadowType );
        if ( forwardButtonInsensitive )
            stateType = GTK_STATE_INSENSITIVE;
        gtk_paint_box( style, gdkDrawable, stateType, shadowType, gdkRect,
                       GTK_WIDGET(scrollbarWidget), scrollbarTag,
                       x+hShim+button22BoundRect.Left(), y+vShim+button22BoundRect.Top(),
                       button22BoundRect.GetWidth(), button22BoundRect.GetHeight() );
        // ----------------- ARROW 2
        NWCalcArrowRect( button22BoundRect, arrowRect );
        gtk_paint_arrow( style, gdkDrawable, stateType, shadowType,
                         gdkRect, GTK_WIDGET(scrollbarWidget), scrollbarTag, button2Type, true,
                         x+hShim+arrowRect.Left(), y+vShim+arrowRect.Top(),
                         arrowRect.GetWidth(), arrowRect.GetHeight() );
    }

    bool bRet = NWRenderPixmapToScreen( pixmap.get(), nullptr, pixmapRect );

    return bRet;
}

static tools::Rectangle NWGetScrollButtonRect(    SalX11Screen nScreen, ControlPart nPart, tools::Rectangle aAreaRect )
{
    gint slider_width;
    gint stepper_size;
    gint stepper_spacing;
    gint trough_border;

    NWEnsureGTKScrollbars( nScreen );

    // Grab some button style attributes
    gtk_widget_style_get( gWidgetData[nScreen].gScrollHorizWidget,
                                      "slider-width", &slider_width,
                                      "stepper-size", &stepper_size,
                                      "trough-border", &trough_border,
                                         "stepper-spacing", &stepper_spacing, nullptr );

    gboolean has_forward;
    gboolean has_forward2;
    gboolean has_backward;
    gboolean has_backward2;

    gtk_widget_style_get( gWidgetData[nScreen].gScrollHorizWidget,
                                      "has-forward-stepper", &has_forward,
                                      "has-secondary-forward-stepper", &has_forward2,
                                      "has-backward-stepper", &has_backward,
                                         "has-secondary-backward-stepper", &has_backward2, nullptr );
    gint       buttonWidth;
    gint       buttonHeight;
    tools::Rectangle  buttonRect;

    gint nFirst = 0;
    gint nSecond = 0;

    if ( has_forward )   nSecond += 1;
    if ( has_forward2 )  nFirst  += 1;
    if ( has_backward )  nFirst  += 1;
    if ( has_backward2 ) nSecond += 1;

    if ( ( nPart == ControlPart::ButtonUp ) || ( nPart == ControlPart::ButtonDown ) )
    {
        buttonWidth = slider_width + 2 * trough_border;
        buttonHeight = stepper_size + trough_border + stepper_spacing;
    }
    else
    {
        buttonWidth = stepper_size + trough_border + stepper_spacing;
        buttonHeight = slider_width + 2 * trough_border;
    }

    if ( nPart == ControlPart::ButtonUp )
    {
        buttonHeight *= nFirst;
        buttonHeight -= 1;
        buttonRect.setX( aAreaRect.Left() );
        buttonRect.setY( aAreaRect.Top() );
    }
    else if ( nPart == ControlPart::ButtonLeft )
    {
        buttonWidth *= nFirst;
        buttonWidth -= 1;
        buttonRect.setX( aAreaRect.Left() );
        buttonRect.setY( aAreaRect.Top() );
    }
    else if ( nPart == ControlPart::ButtonDown )
    {
        buttonHeight *= nSecond;
        buttonRect.setX( aAreaRect.Left() );
        buttonRect.setY( aAreaRect.Top() + aAreaRect.GetHeight() - buttonHeight );
    }
    else if ( nPart == ControlPart::ButtonRight )
    {
        buttonWidth *= nSecond;
        buttonRect.setX( aAreaRect.Left() + aAreaRect.GetWidth() - buttonWidth );
        buttonRect.setY( aAreaRect.Top() );
    }

    buttonRect.SetSize( Size( buttonWidth, buttonHeight ) );

    return buttonRect;
}

bool GtkSalGraphics::NWPaintGTKEditBox( GdkDrawable* gdkDrawable,
                                        ControlType nType,
                                        const tools::Rectangle& rControlRectangle,
                                        const std::vector< tools::Rectangle >& rClipList,
                                        ControlState nState )
{
    tools::Rectangle        pixmapRect;
    GdkRectangle    clipRect;

    // Find the overall bounding rect of the buttons's drawing area,
    // plus its actual draw rect excluding adornment
    pixmapRect = NWGetEditBoxPixmapRect( m_nXScreen, rControlRectangle );
    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        NWPaintOneEditBox( m_nXScreen, gdkDrawable, &clipRect, nType, pixmapRect, nState );
    }

    return true;
}

/* Take interior/exterior focus into account and return
 * the bounding rectangle of the edit box including
 * any focus requirements.
 */
static tools::Rectangle NWGetEditBoxPixmapRect(SalX11Screen nScreen,
                                        tools::Rectangle aAreaRect)
{
    tools::Rectangle        pixmapRect = aAreaRect;
    gboolean        interiorFocus;
    gint            focusWidth;

    NWEnsureGTKEditBox( nScreen );

    // Grab some entry style attributes
    gtk_widget_style_get( gWidgetData[nScreen].gEditBoxWidget,
                                    "focus-line-width",    &focusWidth,
                                     "interior-focus",    &interiorFocus, nullptr );

    if ( !interiorFocus )
    {
        pixmapRect.Move( -focusWidth, -focusWidth );
        pixmapRect.SetSize( Size( pixmapRect.GetWidth() + (2*focusWidth),
                                  pixmapRect.GetHeight() + (2*focusWidth) ) );
    }

    return pixmapRect;
}

/* Paint a GTK Entry widget into the specified GdkPixmap.
 * All coordinates should be local to the Pixmap, NOT
 * screen/window coordinates.
 */
static void NWPaintOneEditBox(  SalX11Screen nScreen,
                                GdkDrawable * gdkDrawable,
                                GdkRectangle const *   gdkRect,
                                ControlType            nType,
                                tools::Rectangle       aEditBoxRect,
                                ControlState           nState )
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    GtkWidget      *widget;

    NWEnsureGTKButton( nScreen );
    NWEnsureGTKEditBox( nScreen );
    NWEnsureGTKSpinButton( nScreen );
    NWEnsureGTKCombo( nScreen );
    NWEnsureGTKScrolledWindow( nScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    switch ( nType )
    {
        case ControlType::Spinbox:
            widget = gWidgetData[nScreen].gSpinButtonWidget;
            break;

        case ControlType::MultilineEditbox:
            widget = gWidgetData[nScreen].gScrolledWindowWidget;
            break;
        case ControlType::Combobox:
            widget = GTK_COMBO(gWidgetData[nScreen].gComboWidget)->entry;
            break;

        default:
            widget = gWidgetData[nScreen].gEditBoxWidget;
            break;
    }

    if ( stateType == GTK_STATE_PRELIGHT )
        stateType = GTK_STATE_NORMAL;

    NWSetWidgetState( widget, nState, stateType );

    gint xborder = widget->style->xthickness;
    gint yborder = widget->style->ythickness;
    gint bInteriorFocus, nFocusLineWidth;
    gtk_widget_style_get( widget,
        "interior-focus",   &bInteriorFocus,
        "focus-line-width", &nFocusLineWidth,
        nullptr);
    if ( !bInteriorFocus )
    {
        xborder += nFocusLineWidth;
        yborder += nFocusLineWidth;
    }

    gtk_paint_flat_box( widget->style, gdkDrawable, stateType, GTK_SHADOW_NONE,
                        gdkRect, widget, "entry_bg",
                        aEditBoxRect.Left() + xborder, aEditBoxRect.Top() + yborder,
                        aEditBoxRect.GetWidth() - 2*xborder, aEditBoxRect.GetHeight() - 2*yborder );
    gtk_paint_shadow( widget->style, gdkDrawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
                      gdkRect, widget, "entry",
                      aEditBoxRect.Left(), aEditBoxRect.Top(),
                      aEditBoxRect.GetWidth(), aEditBoxRect.GetHeight() );

}

bool GtkSalGraphics::NWPaintGTKSpinBox(ControlType nType, ControlPart nPart,
                                       const tools::Rectangle& rControlRectangle,
                                       ControlState nState,
                                       const ImplControlValue& aValue,
                                       ControlCacheKey& rControlCacheKey)
{
    tools::Rectangle            pixmapRect;
    GtkStateType        stateType;
    GtkShadowType        shadowType;
    const SpinbuttonValue *    pSpinVal = (aValue.getType() == ControlType::SpinButtons) ? static_cast<const SpinbuttonValue *>(&aValue) : nullptr;
    tools::Rectangle            upBtnRect;
    ControlPart        upBtnPart = ControlPart::ButtonUp;
    ControlState        upBtnState = ControlState::ENABLED;
    tools::Rectangle            downBtnRect;
    ControlPart        downBtnPart = ControlPart::ButtonDown;
    ControlState        downBtnState = ControlState::ENABLED;

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKSpinButton( m_nXScreen );
    NWEnsureGTKArrow( m_nXScreen );

    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    if ( pSpinVal )
    {
        upBtnPart = pSpinVal->mnUpperPart;
        upBtnState = pSpinVal->mnUpperState;

        downBtnPart = pSpinVal->mnLowerPart;
        downBtnState = pSpinVal->mnLowerState;
    }

    pixmapRect = rControlRectangle;

    BEGIN_PIXMAP_RENDER( pixmapRect, gdkPixmap )
    {
        // First render background
        gtk_paint_flat_box(m_pWindow->style,gdkPixmap,GTK_STATE_NORMAL,GTK_SHADOW_NONE,nullptr,m_pWindow,"base",
                -pixmapRect.Left(),
                -pixmapRect.Top(),
                pixmapRect.Right(),
                pixmapRect.Bottom() );

        upBtnRect = NWGetSpinButtonRect( m_nXScreen, upBtnPart, pixmapRect );
        downBtnRect = NWGetSpinButtonRect( m_nXScreen, downBtnPart, pixmapRect );

        if ( (nType==ControlType::Spinbox) && (nPart!=ControlPart::AllButtons) )
        {
            // Draw an edit field for SpinBoxes and ComboBoxes
            tools::Rectangle aEditBoxRect( pixmapRect );
            aEditBoxRect.SetSize( Size( pixmapRect.GetWidth() - upBtnRect.GetWidth(), aEditBoxRect.GetHeight() ) );
            if( AllSettings::GetLayoutRTL() )
                aEditBoxRect.setX( upBtnRect.GetWidth() );
            else
                aEditBoxRect.setX( 0 );
            aEditBoxRect.setY( 0 );

            NWPaintOneEditBox( m_nXScreen, gdkPixmap, nullptr, nType, aEditBoxRect, nState );
        }

        NWSetWidgetState( gWidgetData[m_nXScreen].gSpinButtonWidget, nState, stateType );
        gtk_widget_style_get( gWidgetData[m_nXScreen].gSpinButtonWidget, "shadow_type", &shadowType, nullptr );

        if ( shadowType != GTK_SHADOW_NONE )
        {
            tools::Rectangle        shadowRect( upBtnRect );

            shadowRect.Union( downBtnRect );
            gtk_paint_box( gWidgetData[m_nXScreen].gSpinButtonWidget->style, gdkPixmap, GTK_STATE_NORMAL, shadowType, nullptr,
                gWidgetData[m_nXScreen].gSpinButtonWidget, "spinbutton",
                (shadowRect.Left() - pixmapRect.Left()), (shadowRect.Top() - pixmapRect.Top()),
                shadowRect.GetWidth(), shadowRect.GetHeight() );
        }

        NWPaintOneSpinButton( m_nXScreen, gdkPixmap, upBtnPart, pixmapRect, upBtnState );
        NWPaintOneSpinButton( m_nXScreen, gdkPixmap, downBtnPart, pixmapRect, downBtnState );
    }
    END_PIXMAP_RENDER_WITH_CONTROL_KEY(pixmapRect, rControlCacheKey);

    return true;
}

static tools::Rectangle NWGetSpinButtonRect( SalX11Screen     nScreen,
                                             ControlPart      nPart,
                                             tools::Rectangle aAreaRect )
{
    gint            buttonSize;
    tools::Rectangle        buttonRect;

    NWEnsureGTKSpinButton( nScreen );

    buttonSize = MAX( PANGO_PIXELS( pango_font_description_get_size(GTK_WIDGET(gWidgetData[nScreen].gSpinButtonWidget)->style->font_desc) ),
                   MIN_SPIN_ARROW_WIDTH );
    buttonSize -= buttonSize % 2 - 1; /* force odd */
    buttonRect.SetSize( Size( buttonSize + 2 * gWidgetData[nScreen].gSpinButtonWidget->style->xthickness,
                              buttonRect.GetHeight() ) );
    if( AllSettings::GetLayoutRTL() )
        buttonRect.setX( aAreaRect.Left() );
    else
        buttonRect.setX( aAreaRect.Left() + (aAreaRect.GetWidth() - buttonRect.GetWidth()) );
    if ( nPart == ControlPart::ButtonUp )
    {
        buttonRect.setY( aAreaRect.Top() );
        buttonRect.SetBottom( buttonRect.Top() + (aAreaRect.GetHeight() / 2) );
    }
    else if( nPart == ControlPart::ButtonDown )
    {
        buttonRect.setY( aAreaRect.Top() + (aAreaRect.GetHeight() / 2) );
        buttonRect.SetBottom( aAreaRect.Bottom() ); // cover area completely
    }
    else
    {
        if( AllSettings::GetLayoutRTL() ) {
            buttonRect.SetLeft( buttonRect.Right()+1 );
            buttonRect.SetRight( aAreaRect.Right() );
        } else {
            buttonRect.SetRight( buttonRect.Left()-1 );
            buttonRect.SetLeft( aAreaRect.Left() );
        }
        buttonRect.SetTop( aAreaRect.Top() );
        buttonRect.SetBottom( aAreaRect.Bottom() );
    }

    return buttonRect;
}

static void NWPaintOneSpinButton( SalX11Screen          nScreen,
                                  GdkPixmap*            pixmap,
                                  ControlPart           nPart,
                                  tools::Rectangle      aAreaRect,
                                  ControlState          nState )
{
    tools::Rectangle            buttonRect;
    GtkStateType        stateType;
    GtkShadowType        shadowType;
    tools::Rectangle            arrowRect;
    gint                arrowSize;

    NWEnsureGTKSpinButton( nScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    buttonRect = NWGetSpinButtonRect( nScreen, nPart, aAreaRect );

    NWSetWidgetState( gWidgetData[nScreen].gSpinButtonWidget, nState, stateType );
    gtk_paint_box( gWidgetData[nScreen].gSpinButtonWidget->style, pixmap, stateType, shadowType, nullptr, gWidgetData[nScreen].gSpinButtonWidget,
            (nPart == ControlPart::ButtonUp) ? "spinbutton_up" : "spinbutton_down",
            (buttonRect.Left() - aAreaRect.Left()), (buttonRect.Top() - aAreaRect.Top()),
            buttonRect.GetWidth(), buttonRect.GetHeight() );

    arrowSize = (buttonRect.GetWidth() - (2 * gWidgetData[nScreen].gSpinButtonWidget->style->xthickness)) - 4;
    arrowSize -= arrowSize % 2 - 1; /* force odd */
    arrowRect.SetSize( Size( arrowSize, arrowSize ) );
    arrowRect.setX( buttonRect.Left() + (buttonRect.GetWidth() - arrowRect.GetWidth()) / 2 );
    if ( nPart == ControlPart::ButtonUp )
        arrowRect.setY( buttonRect.Top() + (buttonRect.GetHeight() - arrowRect.GetHeight()) / 2 + 1);
    else
        arrowRect.setY( buttonRect.Top() + (buttonRect.GetHeight() - arrowRect.GetHeight()) / 2 - 1);

    gtk_paint_arrow( gWidgetData[nScreen].gSpinButtonWidget->style, pixmap, stateType, GTK_SHADOW_OUT, nullptr, gWidgetData[nScreen].gSpinButtonWidget,
            "spinbutton", (nPart == ControlPart::ButtonUp) ? GTK_ARROW_UP : GTK_ARROW_DOWN, true,
            (arrowRect.Left() - aAreaRect.Left()), (arrowRect.Top() - aAreaRect.Top()),
            arrowRect.GetWidth(), arrowRect.GetHeight() );
}

bool GtkSalGraphics::NWPaintGTKComboBox( GdkDrawable* gdkDrawable,
                                         ControlType nType, ControlPart nPart,
                                         const tools::Rectangle& rControlRectangle,
                                         const std::vector< tools::Rectangle >& rClipList,
                                         ControlState nState )
{
    tools::Rectangle        pixmapRect;
    tools::Rectangle        buttonRect;
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    tools::Rectangle        arrowRect;
    gint            x,y;
    GdkRectangle    clipRect;

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKArrow( m_nXScreen );
    NWEnsureGTKCombo( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    // Find the overall bounding rect of the buttons's drawing area,
    // plus its actual draw rect excluding adornment
    pixmapRect = rControlRectangle;
    x = rControlRectangle.Left();
    y = rControlRectangle.Top();

    NWSetWidgetState( gWidgetData[m_nXScreen].gBtnWidget, nState, stateType );
    NWSetWidgetState( gWidgetData[m_nXScreen].gComboWidget, nState, stateType );
    NWSetWidgetState( gWidgetData[m_nXScreen].gArrowWidget, nState, stateType );

    buttonRect = NWGetComboBoxButtonRect( m_nXScreen, ControlPart::ButtonDown, pixmapRect );
    if( nPart == ControlPart::ButtonDown )
        buttonRect.AdjustLeft(1 );

    tools::Rectangle        aEditBoxRect( pixmapRect );
    aEditBoxRect.SetSize( Size( pixmapRect.GetWidth() - buttonRect.GetWidth(), aEditBoxRect.GetHeight() ) );
    if( AllSettings::GetLayoutRTL() )
        aEditBoxRect.SetPos( Point( x + buttonRect.GetWidth() , y ) );

    #define ARROW_EXTENT        0.7
    arrowRect.SetSize( Size( gint(MIN_ARROW_SIZE * ARROW_EXTENT),
                             gint(MIN_ARROW_SIZE * ARROW_EXTENT) ) );
    arrowRect.SetPos( Point( buttonRect.Left() + static_cast<gint>((buttonRect.GetWidth() - arrowRect.GetWidth()) / 2),
                             buttonRect.Top() + static_cast<gint>((buttonRect.GetHeight() - arrowRect.GetHeight()) / 2) ) );

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        if( nPart == ControlPart::Entire )
            NWPaintOneEditBox( m_nXScreen, gdkDrawable, &clipRect, nType, aEditBoxRect,
                               nState );

        // Buttons must paint opaque since some themes have alpha-channel enabled buttons
        gtk_paint_flat_box( m_pWindow->style, gdkDrawable, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
                            &clipRect, m_pWindow, "base",
                            x+(buttonRect.Left() - pixmapRect.Left()),
                            y+(buttonRect.Top() - pixmapRect.Top()),
                            buttonRect.GetWidth(), buttonRect.GetHeight() );
        gtk_paint_box( GTK_COMBO(gWidgetData[m_nXScreen].gComboWidget)->button->style, gdkDrawable, stateType, shadowType,
                       &clipRect, GTK_COMBO(gWidgetData[m_nXScreen].gComboWidget)->button, "button",
                       x+(buttonRect.Left() - pixmapRect.Left()),
                       y+(buttonRect.Top() - pixmapRect.Top()),
                       buttonRect.GetWidth(), buttonRect.GetHeight() );

        gtk_paint_arrow( gWidgetData[m_nXScreen].gArrowWidget->style, gdkDrawable, stateType, shadowType,
                         &clipRect, gWidgetData[m_nXScreen].gArrowWidget, "arrow", GTK_ARROW_DOWN, true,
                         x+(arrowRect.Left() - pixmapRect.Left()), y+(arrowRect.Top() - pixmapRect.Top()),
                         arrowRect.GetWidth(), arrowRect.GetHeight() );
    }

    return true;
}

static tools::Rectangle NWGetComboBoxButtonRect( SalX11Screen nScreen,
                                          ControlPart nPart,
                                          tools::Rectangle                aAreaRect )
{
    tools::Rectangle    aButtonRect;
    gint        nArrowWidth;
    gint        nButtonWidth;
    gint        nFocusWidth;
    gint        nFocusPad;

    NWEnsureGTKArrow( nScreen );

    // Grab some button style attributes
    gtk_widget_style_get( gWidgetData[nScreen].gDropdownWidget,
                                      "focus-line-width",    &nFocusWidth,
                                    "focus-padding",     &nFocusPad, nullptr );

    nArrowWidth = MIN_ARROW_SIZE + (GTK_MISC(gWidgetData[nScreen].gArrowWidget)->xpad * 2);
    nButtonWidth = nArrowWidth +
                   ((BTN_CHILD_SPACING + gWidgetData[nScreen].gDropdownWidget->style->xthickness) * 2)
                   + (2 * (nFocusWidth+nFocusPad));
    if( nPart == ControlPart::ButtonDown )
    {
        aButtonRect.SetSize( Size( nButtonWidth, aAreaRect.GetHeight() ) );
        if( AllSettings::GetLayoutRTL() )
            aButtonRect.SetPos( Point( aAreaRect.Left(), aAreaRect.Top() ) );
        else
            aButtonRect.SetPos( Point( aAreaRect.Left() + aAreaRect.GetWidth() - nButtonWidth,
                                       aAreaRect.Top() ) );
    }
    else if( nPart == ControlPart::SubEdit )
    {
        NWEnsureGTKCombo( nScreen );

        gint adjust_x = GTK_CONTAINER(gWidgetData[nScreen].gComboWidget)->border_width +
                        nFocusWidth +
                        nFocusPad;
        gint adjust_y = adjust_x + gWidgetData[nScreen].gComboWidget->style->ythickness;
        adjust_x     += gWidgetData[nScreen].gComboWidget->style->xthickness;
        aButtonRect.SetSize( Size( aAreaRect.GetWidth() - nButtonWidth - 2 * adjust_x,
                                   aAreaRect.GetHeight() - 2 * adjust_y ) );
        Point aEditPos = aAreaRect.TopLeft();
        aEditPos.AdjustX(adjust_x );
        aEditPos.AdjustY(adjust_y );
        if( AllSettings::GetLayoutRTL() )
            aEditPos.AdjustX(nButtonWidth );
        aButtonRect.SetPos( aEditPos );
    }

    return aButtonRect;
}

bool GtkSalGraphics::NWPaintGTKTabItem( ControlType nType,
                                        const tools::Rectangle& rControlRectangle,
                                        ControlState nState,
                                        const ImplControlValue& aValue )
{
    OSL_ASSERT( nType != ControlType::TabItem || aValue.getType() == ControlType::TabItem );
    GdkX11Pixmap *   pixmap;
    GdkX11Pixmap *   mask;
    tools::Rectangle        pixmapRect;
    tools::Rectangle        tabRect;
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    if( ! gWidgetData[ m_nXScreen ].gCacheTabItems )
    {
        gWidgetData[ m_nXScreen ].gCacheTabItems = new NWPixmapCache( m_nXScreen );
        gWidgetData[ m_nXScreen ].gCacheTabPages = new NWPixmapCache( m_nXScreen );
    }
    NWPixmapCache& aCacheItems = *gWidgetData[ m_nXScreen ].gCacheTabItems;
    NWPixmapCache& aCachePage = *gWidgetData[ m_nXScreen ].gCacheTabPages;

    if( !aCacheItems.GetSize() )
        aCacheItems.SetSize( 20 );
    if( !aCachePage.GetSize() )
        aCachePage.SetSize( 1 );

    if ( (nType == ControlType::TabItem) && (aValue.getType() != ControlType::TabItem) )
    {
        return false;
    }

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKNotebook( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    // Find the overall bounding rect of the buttons's drawing area,
    // plus its actual draw rect excluding adornment
    pixmapRect = rControlRectangle;
    if ( nType == ControlType::TabItem )
    {
        const TabitemValue *    pTabitemValue = static_cast<const TabitemValue *>(&aValue);
        if ( !pTabitemValue->isFirst() )
        {
            // GTK+ tabs overlap on the right edge (the top tab obscures the
            // left edge of the tab right "below" it, so adjust the rectangle
            // to draw tabs slightly large so the overlap happens
            pixmapRect.Move( -2, 0 );
            pixmapRect.SetSize( Size( pixmapRect.GetWidth() + 2, pixmapRect.GetHeight() ) );
        }
        if ( nState & ControlState::SELECTED )
        {
            // In GTK+, the selected tab is 2px taller than all other tabs
            pixmapRect.Move( 0, -2 );
            pixmapRect.AdjustBottom(2 );
            tabRect = pixmapRect;
            // Only draw over 1 pixel of the tab pane that this tab is drawn on top of.
            tabRect.AdjustBottom( -1 );
        }
        else
            tabRect = pixmapRect;

        // Allow the tab to draw a right border if needed
        tabRect.AdjustRight( -1 );

        // avoid degenerate cases which might lead to crashes
        if( tabRect.GetWidth() <= 1 || tabRect.GetHeight() <= 1 )
            return false;
    }

    if( nType == ControlType::TabItem )
    {
        if( aCacheItems.Find( nType, nState, pixmapRect, &pixmap, &mask ) )
            return NWRenderPixmapToScreen( pixmap, mask, pixmapRect );
    }
    else
    {
        if( aCachePage.Find( nType, nState, pixmapRect, &pixmap, &mask ) )
            return NWRenderPixmapToScreen( pixmap, mask, pixmapRect );
    }

    GdkRectangle paintRect;
    paintRect.x = paintRect.y = 0;
    paintRect.width = pixmapRect.GetWidth();
    paintRect.height = pixmapRect.GetHeight();

    BEGIN_CACHE_PIXMAP_RENDER( pixmapRect, pixmap, mask, gdkPixmap )
    {
        gtk_paint_flat_box( m_pWindow->style, gdkPixmap, GTK_STATE_NORMAL,
                            GTK_SHADOW_NONE, &paintRect, m_pWindow, "base",
                            -rControlRectangle.Left(),
                            -rControlRectangle.Top(),
                            pixmapRect.GetWidth()+rControlRectangle.Left(),
                            pixmapRect.GetHeight()+rControlRectangle.Top());

        NWSetWidgetState( gWidgetData[m_nXScreen].gNotebookWidget, nState, stateType );

        switch( nType )
        {
            case ControlType::TabBody:
                break;

            case ControlType::TabPane:
                gtk_paint_box_gap( gWidgetData[m_nXScreen].gNotebookWidget->style, gdkPixmap, GTK_STATE_NORMAL, GTK_SHADOW_OUT, nullptr, gWidgetData[m_nXScreen].gNotebookWidget,
                    "notebook", 0, 0, pixmapRect.GetWidth(), pixmapRect.GetHeight(), GTK_POS_TOP, 0, 0 );
                break;

            case ControlType::TabItem:
            {
                stateType = ( nState & ControlState::SELECTED ) ? GTK_STATE_NORMAL : GTK_STATE_ACTIVE;

                // First draw the background
                gtk_paint_flat_box(gWidgetData[m_nXScreen].gNotebookWidget->style, gdkPixmap,
                                       GTK_STATE_NORMAL, GTK_SHADOW_NONE, nullptr, m_pWindow, "base",
                                       -rControlRectangle.Left(),
                                       -rControlRectangle.Top(),
                                       pixmapRect.GetWidth()+rControlRectangle.Left(),
                                       pixmapRect.GetHeight()+rControlRectangle.Top());

                // Now the tab itself
                if( nState & ControlState::ROLLOVER )
                    g_object_set_data(G_OBJECT(gdkPixmap),tabPrelitDataName,reinterpret_cast<gpointer>(TRUE));

                gtk_paint_extension( gWidgetData[m_nXScreen].gNotebookWidget->style, gdkPixmap, stateType, GTK_SHADOW_OUT, nullptr, gWidgetData[m_nXScreen].gNotebookWidget,
                    "tab", (tabRect.Left() - pixmapRect.Left()), (tabRect.Top() - pixmapRect.Top()),
                    tabRect.GetWidth(), tabRect.GetHeight(), GTK_POS_BOTTOM );

                g_object_steal_data(G_OBJECT(gdkPixmap),tabPrelitDataName);

                if ( nState & ControlState::SELECTED )
                {
                    gtk_paint_flat_box( m_pWindow->style, gdkPixmap, stateType, GTK_SHADOW_NONE, nullptr, m_pWindow,
                        "base", 0, (pixmapRect.GetHeight() - 1), pixmapRect.GetWidth(), 1 );
                }
                break;
            }

            default:
                break;
        }
    }
    END_CACHE_PIXMAP_RENDER( pixmapRect, pixmap, mask )

    // cache data
    if( nType == ControlType::TabItem )
        aCacheItems.Fill( nType, nState, pixmapRect, std::unique_ptr<GdkX11Pixmap>(pixmap), std::unique_ptr<GdkX11Pixmap>(mask) );
    else
        aCachePage.Fill( nType, nState, pixmapRect, std::unique_ptr<GdkX11Pixmap>(pixmap), std::unique_ptr<GdkX11Pixmap>(mask) );

    return true;
}

bool GtkSalGraphics::NWPaintGTKListBox( GdkDrawable* gdkDrawable,
                                        ControlPart nPart,
                                        const tools::Rectangle& rControlRectangle,
                                        const std::vector< tools::Rectangle >& rClipList,
                                        ControlState nState  )
{
    tools::Rectangle        aIndicatorRect;
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    gint            bInteriorFocus;
    gint            nFocusLineWidth;
    gint            nFocusPadding;
    gint            x,y,w,h;
    GdkRectangle    clipRect;

    NWEnsureGTKButton( m_nXScreen );
    NWEnsureGTKOptionMenu( m_nXScreen );
    NWEnsureGTKScrolledWindow( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    // set up references to correct drawable and cliprect
    NWSetWidgetState( gWidgetData[m_nXScreen].gBtnWidget, nState, stateType );
    NWSetWidgetState( gWidgetData[m_nXScreen].gOptionMenuWidget, nState, stateType );
    NWSetWidgetState( gWidgetData[m_nXScreen].gScrolledWindowWidget, nState, stateType );

    x = rControlRectangle.Left();
    y = rControlRectangle.Top();
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    if ( nPart != ControlPart::ListboxWindow )
    {
        gtk_widget_style_get( gWidgetData[m_nXScreen].gOptionMenuWidget,
            "interior_focus",    &bInteriorFocus,
            "focus_line_width",    &nFocusLineWidth,
            "focus_padding",    &nFocusPadding,
            nullptr);
    }

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        if ( nPart != ControlPart::ListboxWindow )
        {
            // Listboxes must paint opaque since some themes have alpha-channel enabled bodies
            gtk_paint_flat_box( m_pWindow->style, gdkDrawable, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
                                &clipRect, m_pWindow, "base", x, y, w, h);
            gtk_paint_box( gWidgetData[m_nXScreen].gOptionMenuWidget->style, gdkDrawable, stateType, shadowType, &clipRect,
                           gWidgetData[m_nXScreen].gOptionMenuWidget, "optionmenu",
                           x, y, w, h);
            aIndicatorRect = NWGetListBoxIndicatorRect( m_nXScreen, rControlRectangle);
            gtk_paint_tab( gWidgetData[m_nXScreen].gOptionMenuWidget->style, gdkDrawable, stateType, shadowType, &clipRect,
                           gWidgetData[m_nXScreen].gOptionMenuWidget, "optionmenutab",
                           aIndicatorRect.Left(), aIndicatorRect.Top(),
                           aIndicatorRect.GetWidth(), aIndicatorRect.GetHeight() );
        }
        else
        {
            shadowType = GTK_SHADOW_IN;

            gtk_paint_shadow( gWidgetData[m_nXScreen].gScrolledWindowWidget->style, gdkDrawable, GTK_STATE_NORMAL, shadowType,
                &clipRect, gWidgetData[m_nXScreen].gScrolledWindowWidget, "scrolled_window",
                x, y, w, h );
        }
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKToolbar(
            GdkDrawable* gdkDrawable,
            ControlPart nPart,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState, const ImplControlValue& aValue)
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    gint            x, y, w, h;
    gint            g_x=0, g_y=0, g_w=10, g_h=10;
    GtkWidget*      pButtonWidget = gWidgetData[m_nXScreen].gToolbarButtonWidget;
    GdkRectangle    clipRect;

    NWEnsureGTKToolbar( m_nXScreen );
    if( nPart == ControlPart::Button ) // toolbar buttons cannot focus in gtk
        nState &= ~ControlState::FOCUSED;
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    x = rControlRectangle.Left();
    y = rControlRectangle.Top();
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    // handle toolbar
    if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert )
    {
        NWSetWidgetState( gWidgetData[m_nXScreen].gToolbarWidget, nState, stateType );

        GTK_WIDGET_UNSET_FLAGS( gWidgetData[m_nXScreen].gToolbarWidget, GTK_SENSITIVE );
        if ( nState & ControlState::ENABLED )
            GTK_WIDGET_SET_FLAGS( gWidgetData[m_nXScreen].gToolbarWidget, GTK_SENSITIVE );

        if( nPart == ControlPart::DrawBackgroundHorz )
            gtk_toolbar_set_orientation( GTK_TOOLBAR(gWidgetData[m_nXScreen].gToolbarWidget), GTK_ORIENTATION_HORIZONTAL );
        else
            gtk_toolbar_set_orientation( GTK_TOOLBAR(gWidgetData[m_nXScreen].gToolbarWidget), GTK_ORIENTATION_VERTICAL );
    }
    // handle grip
    else if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert )
    {
        NWSetWidgetState( gWidgetData[m_nXScreen].gHandleBoxWidget, nState, stateType );

        GTK_WIDGET_UNSET_FLAGS( gWidgetData[m_nXScreen].gHandleBoxWidget, GTK_SENSITIVE );
        if ( nState & ControlState::ENABLED )
            GTK_WIDGET_SET_FLAGS( gWidgetData[m_nXScreen].gHandleBoxWidget, GTK_SENSITIVE );

        gtk_handle_box_set_shadow_type( GTK_HANDLE_BOX(gWidgetData[m_nXScreen].gHandleBoxWidget), shadowType );

        // evaluate grip rect
        if( aValue.getType() == ControlType::Toolbar )
        {
            const ToolbarValue* pVal = static_cast<const ToolbarValue*>(&aValue);
            g_x = pVal->maGripRect.Left();
            g_y = pVal->maGripRect.Top();
            g_w = pVal->maGripRect.GetWidth();
            g_h = pVal->maGripRect.GetHeight();
        }
    }
    // handle button
    else if( nPart == ControlPart::Button )
    {
        bool bPaintButton = (nState & ControlState::PRESSED)
            || (nState & ControlState::ROLLOVER);
        if( aValue.getTristateVal() == ButtonValue::On )
        {
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pButtonWidget),TRUE);
            bPaintButton = true;
        }
        else
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pButtonWidget),FALSE);

        NWSetWidgetState( pButtonWidget, nState, stateType );
        gtk_widget_ensure_style( pButtonWidget );
        if(bPaintButton)
            NWPaintGTKButtonReal(pButtonWidget, gdkDrawable, rControlRectangle, rClipList, nState);
    }

    if( nPart != ControlPart::Button )
    {
        for (auto const& clip : rClipList)
        {
            clipRect.x = clip.Left();
            clipRect.y = clip.Top();
            clipRect.width = clip.GetWidth();
            clipRect.height = clip.GetHeight();

            // draw toolbar
            if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert )
            {
                gtk_paint_flat_box( gWidgetData[m_nXScreen].gToolbarWidget->style,
                                    gdkDrawable,
                                    GTK_STATE_NORMAL,
                                    GTK_SHADOW_NONE,
                                    &clipRect,
                                    gWidgetData[m_nXScreen].gToolbarWidget,
                                    "base",
                                    x, y, w, h );
                gtk_paint_box( gWidgetData[m_nXScreen].gToolbarWidget->style,
                               gdkDrawable,
                               stateType,
                               shadowType,
                               &clipRect,
                               gWidgetData[m_nXScreen].gToolbarWidget,
                               "toolbar",
                               x, y, w, h );
            }
            // draw grip
            else if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert )
            {
                gtk_paint_handle( gWidgetData[m_nXScreen].gHandleBoxWidget->style,
                                  gdkDrawable,
                                  GTK_STATE_NORMAL,
                                  GTK_SHADOW_OUT,
                                  &clipRect,
                                  gWidgetData[m_nXScreen].gHandleBoxWidget,
                                  "handlebox",
                                  g_x, g_y, g_w, g_h,
                                  nPart == ControlPart::ThumbHorz ?
                                  GTK_ORIENTATION_HORIZONTAL :
                                  GTK_ORIENTATION_VERTICAL
                                  );
            }
            else if( nPart == ControlPart::SeparatorHorz || nPart == ControlPart::SeparatorVert )
            {
                const double shim = 0.2;

                gint separator_height, separator_width, wide_separators = 0;

                gtk_widget_style_get (gWidgetData[m_nXScreen].gSeparator,
                                      "wide-separators",  &wide_separators,
                                      "separator-width",  &separator_width,
                                      "separator-height", &separator_height,
                                      nullptr);

                if (wide_separators)
                {
                    if (nPart == ControlPart::SeparatorVert)
                        gtk_paint_box (gWidgetData[m_nXScreen].gSeparator->style, gdkDrawable,
                                       GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
                                       &clipRect, gWidgetData[m_nXScreen].gSeparator, "vseparator",
                                       x + (w - separator_width) / 2, y + h * shim,
                                       separator_width, h * (1 - 2*shim));
                    else
                        gtk_paint_box (gWidgetData[m_nXScreen].gSeparator->style, gdkDrawable,
                                       GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
                                       &clipRect, gWidgetData[m_nXScreen].gSeparator, "hseparator",
                                       x + w * shim, y + (h - separator_width) / 2,
                                       w * (1 - 2*shim), separator_width);
                }
                else
                {
                    if (nPart == ControlPart::SeparatorVert)
                        gtk_paint_vline (gWidgetData[m_nXScreen].gSeparator->style, gdkDrawable,
                                         GTK_STATE_NORMAL,
                                         &clipRect, gWidgetData[m_nXScreen].gSeparator, "vseparator",
                                         y + h * shim, y + h * (1 - shim), x + w/2 - 1);
                    else
                        gtk_paint_hline (gWidgetData[m_nXScreen].gSeparator->style, gdkDrawable,
                                         GTK_STATE_NORMAL,
                                         &clipRect, gWidgetData[m_nXScreen].gSeparator, "hseparator",
                                         x + w * shim, x + w * (1 - shim), y + h/2 - 1);
                }
            }
        }
    }

    return true;
}

/// Converts a VCL Rectangle to a GdkRectangle.
static void lcl_rectangleToGdkRectangle(const tools::Rectangle& rRectangle, GdkRectangle& rGdkRectangle)
{
    rGdkRectangle.x = rRectangle.Left();
    rGdkRectangle.y = rRectangle.Top();
    rGdkRectangle.width = rRectangle.GetWidth();
    rGdkRectangle.height = rRectangle.GetHeight();
}

bool GtkSalGraphics::NWPaintGTKMenubar(
            GdkDrawable* gdkDrawable,
            ControlPart nPart,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState )
{
    GtkStateType    stateType;
    GtkShadowType    shadowType;
    GtkShadowType   selected_shadow_type = GTK_SHADOW_OUT;
    gint            x, y, w, h;
    GdkRectangle    clipRect;

    NWEnsureGTKMenubar( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    x = rControlRectangle.Left();
    y = rControlRectangle.Top();
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    if( nPart == ControlPart::MenuItem )
    {
        if( nState & ControlState::SELECTED )
        {
            gtk_widget_style_get( gWidgetData[m_nXScreen].gMenuItemMenubarWidget,
                                  "selected_shadow_type", &selected_shadow_type,
                                  nullptr);
        }
    }

    for (auto const& clip : rClipList)
    {
        lcl_rectangleToGdkRectangle(clip, clipRect);

        // handle Menubar
        if( nPart == ControlPart::Entire )
        {
            NWSetWidgetState( gWidgetData[m_nXScreen].gMenubarWidget, nState, stateType );

            GTK_WIDGET_UNSET_FLAGS( gWidgetData[m_nXScreen].gMenubarWidget, GTK_SENSITIVE );
            if ( nState & ControlState::ENABLED )
                GTK_WIDGET_SET_FLAGS( gWidgetData[m_nXScreen].gMenubarWidget, GTK_SENSITIVE );

            // for translucent menubar styles paint background first
            gtk_paint_flat_box( gWidgetData[m_nXScreen].gMenubarWidget->style,
                                gdkDrawable,
                                GTK_STATE_NORMAL,
                                GTK_SHADOW_NONE,
                                &clipRect,
                                GTK_WIDGET(m_pWindow),
                                "base",
                                x, y, w, h );

            // Do the conversion again, in case clipRect has been modified.
            lcl_rectangleToGdkRectangle(clip, clipRect);

            gtk_paint_box( gWidgetData[m_nXScreen].gMenubarWidget->style,
                           gdkDrawable,
                           stateType,
                           shadowType,
                           &clipRect,
                           gWidgetData[m_nXScreen].gMenubarWidget,
                           "menubar",
                           x, y, w, h );
        }

        else if( nPart == ControlPart::MenuItem )
        {
            if( nState & ControlState::SELECTED )
            {
                gtk_paint_box( gWidgetData[m_nXScreen].gMenuItemMenubarWidget->style,
                               gdkDrawable,
                               GTK_STATE_PRELIGHT,
                               selected_shadow_type,
                               &clipRect,
                               gWidgetData[m_nXScreen].gMenuItemMenubarWidget,
                               "menuitem",
                               x, y, w, h);
            }
        }
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKPopupMenu(
            GdkDrawable* gdkDrawable,
            ControlPart nPart,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList,
            ControlState nState )
{
    // #i50745# gtk does not draw disabled menu entries (and crux theme
    // even crashes) in very old (Fedora Core 4 vintage) gtk's
    if (gtk_major_version <= 2 && gtk_minor_version <= 8 &&
        nPart == ControlPart::MenuItem && ! (nState & ControlState::ENABLED) )
        return true;

    GtkStateType    stateType;
    GtkShadowType    shadowType;
    GtkShadowType   selected_shadow_type = GTK_SHADOW_OUT;
    gint            x, y, w, h;
    GdkRectangle    clipRect;

    NWEnsureGTKMenu( m_nXScreen );
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    x = rControlRectangle.Left();
    y = rControlRectangle.Top();
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    if( nPart == ControlPart::MenuItem &&
        ( nState & (ControlState::SELECTED|ControlState::ROLLOVER) ) )
    {
        gtk_widget_style_get( gWidgetData[m_nXScreen].gMenuItemMenuWidget,
                              "selected_shadow_type", &selected_shadow_type,
                              nullptr);
    }

    NWSetWidgetState( gWidgetData[m_nXScreen].gMenuWidget, nState, stateType );

    GTK_WIDGET_UNSET_FLAGS( gWidgetData[m_nXScreen].gMenuWidget, GTK_SENSITIVE );
    if ( nState & ControlState::ENABLED )
        GTK_WIDGET_SET_FLAGS( gWidgetData[m_nXScreen].gMenuWidget, GTK_SENSITIVE );

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        if( nPart == ControlPart::Entire )
        {
            // for translucent menubar styles paint background first
            gtk_paint_flat_box( gWidgetData[m_nXScreen].gMenuWidget->style,
                                gdkDrawable,
                                GTK_STATE_NORMAL,
                                GTK_SHADOW_NONE,
                                &clipRect,
                                GTK_WIDGET(m_pWindow),
                                "base",
                                x, y, w, h );
            gtk_paint_box( gWidgetData[m_nXScreen].gMenuWidget->style,
                           gdkDrawable,
                           GTK_STATE_NORMAL,
                           GTK_SHADOW_OUT,
                           &clipRect,
                           gWidgetData[m_nXScreen].gMenuWidget,
                           "menu",
                           x, y, w, h );
        }
        else if( nPart == ControlPart::MenuItem )
        {
            if( nState & (ControlState::SELECTED|ControlState::ROLLOVER) )
            {
                if( nState & ControlState::ENABLED )
                gtk_paint_box( gWidgetData[m_nXScreen].gMenuItemMenuWidget->style,
                               gdkDrawable,
                               GTK_STATE_PRELIGHT,
                               selected_shadow_type,
                               &clipRect,
                               gWidgetData[m_nXScreen].gMenuItemMenuWidget,
                               "menuitem",
                               x, y, w, h);
            }
        }
        else if( nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark )
        {
            GtkWidget* pWidget = (nPart == ControlPart::MenuItemCheckMark) ?
                                 gWidgetData[m_nXScreen].gMenuItemCheckMenuWidget :
                                 gWidgetData[m_nXScreen].gMenuItemRadioMenuWidget;

            GtkStateType nStateType;
            GtkShadowType nShadowType;
            NWConvertVCLStateToGTKState( nState, &nStateType, &nShadowType );

            if ( (nState & ControlState::SELECTED) && (nState & ControlState::ENABLED) )
                nStateType = GTK_STATE_PRELIGHT;

            NWSetWidgetState( pWidget, nState, nStateType );

            if ( nPart == ControlPart::MenuItemCheckMark )
            {
                gtk_paint_check( pWidget->style,
                                 gdkDrawable,
                                 nStateType,
                                 nShadowType,
                                 &clipRect,
                                 gWidgetData[m_nXScreen].gMenuItemMenuWidget,
                                 "check",
                                 x, y, w, h );
            }
            else
            {
                gtk_paint_option( pWidget->style,
                                  gdkDrawable,
                                  nStateType,
                                  nShadowType,
                                  &clipRect,
                                  gWidgetData[m_nXScreen].gMenuItemMenuWidget,
                                  "option",
                                  x, y, w, h );
            }
        }
        else if( nPart == ControlPart::Separator )
        {
            gtk_paint_hline( gWidgetData[m_nXScreen].gMenuItemSeparatorMenuWidget->style,
                             gdkDrawable,
                             GTK_STATE_NORMAL,
                             &clipRect,
                             gWidgetData[m_nXScreen].gMenuItemSeparatorMenuWidget,
                             "menuitem",
                             x, x + w, y + h / 2);
        }
        else if( nPart == ControlPart::SubmenuArrow )
        {
            GtkStateType nStateType;
            GtkShadowType nShadowType;
            NWConvertVCLStateToGTKState( nState, &nStateType, &nShadowType );

            if ( (nState & ControlState::SELECTED) && (nState & ControlState::ENABLED) )
                nStateType = GTK_STATE_PRELIGHT;

            NWSetWidgetState( gWidgetData[m_nXScreen].gMenuItemMenuWidget,
                              nState, nStateType );

            GtkArrowType eArrow;
            if( AllSettings::GetLayoutRTL() )
                eArrow = GTK_ARROW_LEFT;
            else
                eArrow = GTK_ARROW_RIGHT;

            gtk_paint_arrow( gWidgetData[m_nXScreen].gMenuItemMenuWidget->style,
                             gdkDrawable,
                             nStateType,
                             nShadowType,
                             &clipRect,
                             gWidgetData[m_nXScreen].gMenuItemMenuWidget,
                             "menuitem",
                             eArrow, TRUE,
                             x, y, w, h);
        }
    }

    return true;
}

bool GtkSalGraphics::NWPaintGTKTooltip(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            const std::vector< tools::Rectangle >& rClipList )
{
    NWEnsureGTKTooltip( m_nXScreen );

    gint            x, y, w, h;
    GdkRectangle    clipRect;

    x = rControlRectangle.Left();
    y = rControlRectangle.Top();
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    for (auto const& clip : rClipList)
    {
        clipRect.x = clip.Left();
        clipRect.y = clip.Top();
        clipRect.width = clip.GetWidth();
        clipRect.height = clip.GetHeight();

        gtk_paint_flat_box( gWidgetData[m_nXScreen].gTooltipPopup->style,
                            gdkDrawable,
                            GTK_STATE_NORMAL,
                            GTK_SHADOW_OUT,
                            &clipRect,
                            gWidgetData[m_nXScreen].gTooltipPopup,
                            "tooltip",
                            x, y, w, h );
    }

    return true;
}

namespace
{
void NWPaintGTKListNodeReal(SalX11Screen nXScreen, GdkDrawable* gdkDrawable, GtkStateType stateType,
                            gint w, int h, GtkExpanderStyle eStyle)
{
    gtk_paint_expander(gWidgetData[nXScreen].gTreeView->style, gdkDrawable, stateType, nullptr,
                       gWidgetData[nXScreen].gTreeView, "treeview", w / 2, h / 2, eStyle);
}
}

bool GtkSalGraphics::NWPaintGTKListNode(
            GdkDrawable* gdkDrawable,
            const tools::Rectangle& rControlRectangle,
            ControlState nState, const ImplControlValue& rValue )
{
    NWEnsureGTKTreeView( m_nXScreen );

    tools::Rectangle aRect( rControlRectangle );
    aRect.AdjustLeft( -2 );
    aRect.AdjustRight(2 );
    aRect.AdjustTop( -2 );
    aRect.AdjustBottom(2 );
    gint            w, h;
    w = aRect.GetWidth();
    h = aRect.GetHeight();

    GtkStateType    stateType;
    GtkShadowType    shadowType;
    NWConvertVCLStateToGTKState( nState, &stateType, &shadowType );

    ButtonValue aButtonValue = rValue.getTristateVal();
    GtkExpanderStyle eStyle = GTK_EXPANDER_EXPANDED;

    switch( aButtonValue )
    {
        case ButtonValue::On: eStyle = GTK_EXPANDER_EXPANDED;break;
        case ButtonValue::Off: eStyle = GTK_EXPANDER_COLLAPSED; break;
        default:
            break;
    }

    if (GtkSalGraphics::bNeedPixmapPaint)
    {
        NWPaintGTKListNodeReal(m_nXScreen, gdkDrawable, stateType, w, h, eStyle);
        return true;
    }

    BEGIN_PIXMAP_RENDER( aRect, pixDrawable )
    {
        NWPaintGTKListNodeReal(m_nXScreen, pixDrawable, stateType, w, h, eStyle);
    }
    END_PIXMAP_RENDER( aRect )

    return true;
}

bool GtkSalGraphics::NWPaintGTKProgress(
            const tools::Rectangle& rControlRectangle,
            const ImplControlValue& rValue )
{
    NWEnsureGTKProgressBar( m_nXScreen );

    gint            w, h;
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();
    tools::Rectangle aRect( Point( 0, 0 ), Size( w, h ) );

    long nProgressWidth = rValue.getNumericVal();

    BEGIN_PIXMAP_RENDER( aRect, pixDrawable )
    {
        // paint background
        gtk_paint_flat_box(gWidgetData[m_nXScreen].gProgressBar->style, pixDrawable,
                               GTK_STATE_NORMAL, GTK_SHADOW_NONE, nullptr, m_pWindow, "base",
                               -rControlRectangle.Left(),-rControlRectangle.Top(),
                               rControlRectangle.Left()+w,rControlRectangle.Top()+h);

        gtk_paint_flat_box( gWidgetData[m_nXScreen].gProgressBar->style,
                            pixDrawable,
                            GTK_STATE_NORMAL,
                            GTK_SHADOW_NONE,
                            nullptr,
                            gWidgetData[m_nXScreen].gProgressBar,
                            "trough",
                            0, 0, w, h );
        if( nProgressWidth > 0 )
        {
            // paint progress
            if( AllSettings::GetLayoutRTL() )
            {
                gtk_paint_box( gWidgetData[m_nXScreen].gProgressBar->style,
                               pixDrawable,
                               GTK_STATE_PRELIGHT, GTK_SHADOW_OUT,
                               nullptr,
                               gWidgetData[m_nXScreen].gProgressBar,
                               "bar",
                               w-nProgressWidth, 0, nProgressWidth, h
                               );
            }
            else
            {
                gtk_paint_box( gWidgetData[m_nXScreen].gProgressBar->style,
                               pixDrawable,
                               GTK_STATE_PRELIGHT, GTK_SHADOW_OUT,
                               nullptr,
                               gWidgetData[m_nXScreen].gProgressBar,
                               "bar",
                               0, 0, nProgressWidth, h
                               );
            }
        }
    }
    END_PIXMAP_RENDER( rControlRectangle )

    return true;
}

namespace
{
void NWPaintGTKSliderReal(SalX11Screen nXScreen, GdkDrawable* gdkDrawable, ControlPart nPart,
                          const tools::Rectangle& rControlRectangle, ControlState nState,
                          const ImplControlValue& rValue)
{
    gint w, h;
    w = rControlRectangle.GetWidth();
    h = rControlRectangle.GetHeight();

    const SliderValue* pVal = static_cast<const SliderValue*>(&rValue);

    GtkWidget* pWidget = (nPart == ControlPart::TrackHorzArea)
                             ? GTK_WIDGET(gWidgetData[nXScreen].gHScale)
                             : GTK_WIDGET(gWidgetData[nXScreen].gVScale);
    const gchar* pDetail = (nPart == ControlPart::TrackHorzArea) ? "hscale" : "vscale";
    GtkOrientation eOri = (nPart == ControlPart::TrackHorzArea) ? GTK_ORIENTATION_HORIZONTAL
                                                                : GTK_ORIENTATION_VERTICAL;
    gint slider_width = 10;
    gint slider_length = 10;
    gint trough_border = 0;
    gtk_widget_style_get(pWidget, "slider-width", &slider_width, "slider-length", &slider_length,
                         "trough-border", &trough_border, nullptr);

    GtkStateType eState
        = (nState & ControlState::ENABLED) ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE;
    if (nPart == ControlPart::TrackHorzArea)
    {
        gtk_paint_box(pWidget->style, gdkDrawable, eState, GTK_SHADOW_IN, nullptr, pWidget,
                      "trough", 0, (h - slider_width - 2 * trough_border) / 2, w,
                      slider_width + 2 * trough_border);
        gint x
            = (w - slider_length + 1) * (pVal->mnCur - pVal->mnMin) / (pVal->mnMax - pVal->mnMin);
        gtk_paint_slider(pWidget->style, gdkDrawable, eState, GTK_SHADOW_OUT, nullptr, pWidget,
                         pDetail, x, (h - slider_width) / 2, slider_length, slider_width, eOri);
    }
    else
    {
        gtk_paint_box(pWidget->style, gdkDrawable, eState, GTK_SHADOW_IN, nullptr, pWidget,
                      "trough", (w - slider_width - 2 * trough_border) / 2, 0,
                      slider_width + 2 * trough_border, h);
        gint y
            = (h - slider_length + 1) * (pVal->mnCur - pVal->mnMin) / (pVal->mnMax - pVal->mnMin);
        gtk_paint_slider(pWidget->style, gdkDrawable, eState, GTK_SHADOW_OUT, nullptr, pWidget,
                         pDetail, (w - slider_width) / 2, y, slider_width, slider_length, eOri);
    }
}
}

bool GtkSalGraphics::NWPaintGTKSlider(
            GdkDrawable* gdkDrawable,
            ControlPart nPart,
            const tools::Rectangle& rControlRectangle,
            ControlState nState, const ImplControlValue& rValue )
{
    OSL_ASSERT( rValue.getType() == ControlType::Slider );
    NWEnsureGTKSlider( m_nXScreen );

    if (GtkSalGraphics::bNeedPixmapPaint)
    {
        NWPaintGTKSliderReal(m_nXScreen, gdkDrawable, nPart, rControlRectangle, nState, rValue);
        return true;
    }

    BEGIN_PIXMAP_RENDER( rControlRectangle, pixDrawable )
    {
        NWPaintGTKSliderReal(m_nXScreen, pixDrawable, nPart, rControlRectangle, nState, rValue);
    }
    END_PIXMAP_RENDER( rControlRectangle )

    return true;
}

static int getFrameWidth(GtkWidget const * widget)
{
    return widget->style->xthickness;
}

static tools::Rectangle NWGetListBoxButtonRect( SalX11Screen nScreen,
                                         ControlPart    nPart,
                                         tools::Rectangle      aAreaRect )
{
    tools::Rectangle       aPartRect;
    GtkRequisition *pIndicatorSize = nullptr;
    GtkBorder      *pIndicatorSpacing = nullptr;
    gint            width = 13;    // GTK+ default
    gint            right = 5;    // GTK+ default
    gint            nButtonAreaWidth = 0;
    gint            xthickness = 0;

    NWEnsureGTKOptionMenu( nScreen );

    gtk_widget_style_get( gWidgetData[nScreen].gOptionMenuWidget,
            "indicator_size",    &pIndicatorSize,
            "indicator_spacing",&pIndicatorSpacing, nullptr);

    if ( pIndicatorSize )
        width = pIndicatorSize->width;

    if ( pIndicatorSpacing )
        right = pIndicatorSpacing->right;

    Size aPartSize( 0, aAreaRect.GetHeight() );
    Point aPartPos ( 0, aAreaRect.Top() );

    xthickness = gWidgetData[nScreen].gOptionMenuWidget->style->xthickness;
    nButtonAreaWidth = width + right + (xthickness * 2);
    switch( nPart )
    {
        case ControlPart::ButtonDown:
            aPartSize.setWidth( nButtonAreaWidth );
            aPartPos.setX( aAreaRect.Left() + aAreaRect.GetWidth() - aPartSize.Width() );
            break;

        case ControlPart::SubEdit:
            aPartSize.setWidth( aAreaRect.GetWidth() - nButtonAreaWidth - xthickness );
            if( AllSettings::GetLayoutRTL() )
                aPartPos.setX( aAreaRect.Left() + nButtonAreaWidth );
            else
                aPartPos.setX( aAreaRect.Left() + xthickness );
            break;

        default:
            aPartSize.setWidth( aAreaRect.GetWidth() );
            aPartPos.setX( aAreaRect.Left() );
            break;
    }
    aPartRect = tools::Rectangle( aPartPos, aPartSize );

    if ( pIndicatorSize )
        gtk_requisition_free( pIndicatorSize );
    if ( pIndicatorSpacing )
        gtk_border_free( pIndicatorSpacing );

    return aPartRect;
}

static tools::Rectangle NWGetListBoxIndicatorRect( SalX11Screen nScreen,
                                            tools::Rectangle                aAreaRect )
{
    tools::Rectangle       aIndicatorRect;
    GtkRequisition *pIndicatorSize = nullptr;
    GtkBorder      *pIndicatorSpacing = nullptr;
    gint            width = 13;    // GTK+ default
    gint            height = 13;    // GTK+ default
    gint            right = 5;    // GTK+ default
    gint            x;

    NWEnsureGTKOptionMenu( nScreen );

    gtk_widget_style_get( gWidgetData[nScreen].gOptionMenuWidget,
            "indicator_size",    &pIndicatorSize,
            "indicator_spacing",&pIndicatorSpacing, nullptr);

    if ( pIndicatorSize )
    {
        width = pIndicatorSize->width;
        height = pIndicatorSize->height;
    }

    if ( pIndicatorSpacing )
        right = pIndicatorSpacing->right;

    aIndicatorRect.SetSize( Size( width, height ) );
    if( AllSettings::GetLayoutRTL() )
        x = aAreaRect.Left() + right;
    else
        x = aAreaRect.Left() + aAreaRect.GetWidth() - width - right - gWidgetData[nScreen].gOptionMenuWidget->style->xthickness;
    aIndicatorRect.SetPos( Point( x, aAreaRect.Top() + ((aAreaRect.GetHeight() - height) / 2) ) );

    // If height is odd, move the indicator down 1 pixel
    if ( aIndicatorRect.GetHeight() % 2 )
        aIndicatorRect.Move( 0, 1 );

    if ( pIndicatorSize )
        gtk_requisition_free( pIndicatorSize );
    if ( pIndicatorSpacing )
        gtk_border_free( pIndicatorSpacing );

    return aIndicatorRect;
}

static tools::Rectangle NWGetToolbarRect(  SalX11Screen nScreen,
                                    ControlPart                nPart,
                                    tools::Rectangle                aAreaRect )
{
    tools::Rectangle aRet;

    if( nPart == ControlPart::DrawBackgroundHorz ||
        nPart == ControlPart::DrawBackgroundVert )
        aRet = aAreaRect;
    else if( nPart == ControlPart::ThumbHorz )
        aRet = tools::Rectangle( Point( 0, 0 ), Size( aAreaRect.GetWidth(), 10 ) );
    else if( nPart == ControlPart::ThumbVert )
        aRet = tools::Rectangle( Point( 0, 0 ), Size( 10, aAreaRect.GetHeight() ) );
    else if( nPart == ControlPart::Button )
    {
        aRet = aAreaRect;

        NWEnsureGTKToolbar( nScreen );

        gint nMinWidth =
            2*gWidgetData[nScreen].gToolbarButtonWidget->style->xthickness
            + 1 // CHILD_SPACING constant, found in gtk_button.c
            + 3*gWidgetData[nScreen].gToolbarButtonWidget->style->xthickness; // Murphy factor
        gint nMinHeight =
            2*gWidgetData[nScreen].gToolbarButtonWidget->style->ythickness
            + 1 // CHILD_SPACING constant, found in gtk_button.c
            + 3*gWidgetData[nScreen].gToolbarButtonWidget->style->ythickness; // Murphy factor

        gtk_widget_ensure_style( gWidgetData[nScreen].gToolbarButtonWidget );
        if( aAreaRect.GetWidth() < nMinWidth )
            aRet.SetRight( aRet.Left() + nMinWidth );
        if( aAreaRect.GetHeight() < nMinHeight  )
            aRet.SetBottom( aRet.Top() + nMinHeight );
    }

    return aRet;
}

/************************************************************************
 * helper for GtkSalFrame
 ************************************************************************/
static Color getColor( const GdkColor& rCol )
{
    return Color( rCol.red >> 8, rCol.green >> 8, rCol.blue >> 8 );
}

#if OSL_DEBUG_LEVEL > 1

void printColor( const char* name, const GdkColor& rCol )
{
    std::fprintf( stderr, "   %s = 0x%2x 0x%2x 0x%2x\n",
             name,
             rCol.red >> 8, rCol.green >> 8, rCol.blue >> 8 );
}

void printStyleColors( GtkStyle* pStyle )
{
    static const char* pStates[] = { "NORMAL", "ACTIVE", "PRELIGHT", "SELECTED", "INSENSITIVE" };

    for( int i = 0; i < 5; i++ )
    {
        std::fprintf( stderr, "state %s colors:\n", pStates[i] );
        printColor( "bg     ", pStyle->bg[i] );
        printColor( "fg     ", pStyle->fg[i] );
        printColor( "light  ", pStyle->light[i] );
        printColor( "dark   ", pStyle->dark[i] );
        printColor( "mid    ", pStyle->mid[i] );
        printColor( "text   ", pStyle->text[i] );
        printColor( "base   ", pStyle->base[i] );
        printColor( "text_aa", pStyle->text_aa[i] );
    }
}
#endif

void GtkSalGraphics::signalSettingsNotify( GObject *pSettings, GParamSpec *pSpec, gpointer )
{
    g_return_if_fail( pSpec != nullptr );

    if( !strcmp( pSpec->name, "gtk-fontconfig-timestamp" ) )
        GtkSalGraphics::refreshFontconfig( GTK_SETTINGS( pSettings ) );
}

void GtkSalGraphics::refreshFontconfig( GtkSettings *pSettings )
{
    guint latest_fontconfig_timestamp = 0;
    static guint our_fontconfig_timestamp = 0;
    g_object_get( pSettings, "gtk-fontconfig-timestamp", &latest_fontconfig_timestamp, nullptr );
    if (latest_fontconfig_timestamp != our_fontconfig_timestamp)
    {
        bool bFirstTime = our_fontconfig_timestamp == 0;
        our_fontconfig_timestamp = latest_fontconfig_timestamp;
        if (!bFirstTime)
        {
            psp::PrintFontManager::get().initialize();
        }
    }
}

bool GtkSalGraphics::updateSettings( AllSettings& rSettings )
{
    gtk_widget_ensure_style( m_pWindow );
    GtkStyle* pStyle = gtk_widget_get_style( m_pWindow );
    GtkSettings* pSettings = gtk_widget_get_settings( m_pWindow );
    StyleSettings aStyleSet = rSettings.GetStyleSettings();

    // Listen for font changes
    if( !g_object_get_data( G_OBJECT( pSettings ), "libo:listening" ) )
    {
        g_object_set_data( G_OBJECT( pSettings ), "libo:listening",
                           GUINT_TO_POINTER( 1 ) );
        g_signal_connect_data( G_OBJECT( pSettings ), "notify",
                               G_CALLBACK( signalSettingsNotify ),
                               nullptr, nullptr, G_CONNECT_AFTER );
    }

    refreshFontconfig( pSettings );

    // get the widgets in place
    NWEnsureGTKMenu( m_nXScreen );
    NWEnsureGTKMenubar( m_nXScreen );
    NWEnsureGTKToolbar( m_nXScreen );
    NWEnsureGTKScrollbars( m_nXScreen );
    NWEnsureGTKEditBox( m_nXScreen );
    NWEnsureGTKTooltip( m_nXScreen );
    NWEnsureGTKDialog( m_nXScreen );
    NWEnsureGTKFrame( m_nXScreen );

#if OSL_DEBUG_LEVEL > 2
    printStyleColors( pStyle );
#endif

    // text colors
    Color aTextColor = getColor( pStyle->text[GTK_STATE_NORMAL] );
    aStyleSet.SetDialogTextColor( aTextColor );
    aStyleSet.SetWindowTextColor( aTextColor );
    aStyleSet.SetFieldTextColor( aTextColor );
    aTextColor = getColor( pStyle->fg[GTK_STATE_NORMAL] );
    aStyleSet.SetButtonTextColor( aTextColor );
    aStyleSet.SetRadioCheckTextColor( aTextColor );
    aStyleSet.SetGroupTextColor( aTextColor );
    aStyleSet.SetLabelTextColor( aTextColor );
    aStyleSet.SetTabTextColor( aTextColor );
    aStyleSet.SetTabRolloverTextColor( aTextColor );
    aStyleSet.SetTabHighlightTextColor( aTextColor );

    // Tooltip colors
    GtkStyle* pTooltipStyle = gtk_widget_get_style( gWidgetData[m_nXScreen].gTooltipPopup );
    aTextColor = getColor( pTooltipStyle->fg[ GTK_STATE_NORMAL ] );
    aStyleSet.SetHelpTextColor( aTextColor );

    DialogStyle aDialogStyle(aStyleSet.GetDialogStyle());
    gtk_widget_style_get (gWidgetData[m_nXScreen].gDialog,
        "content-area-border", &aDialogStyle.content_area_border,
        "content-area-spacing", &aDialogStyle.content_area_spacing,
        "button-spacing", &aDialogStyle.button_spacing,
        "action-area-border", &aDialogStyle.action_area_border,
        nullptr);
    aStyleSet.SetDialogStyle(aDialogStyle);

    FrameStyle aFrameStyle(aStyleSet.GetFrameStyle());
    aFrameStyle.left = aFrameStyle.right =
        gWidgetData[m_nXScreen].gFrame->style->xthickness;
    aFrameStyle.top = aFrameStyle.bottom =
        gWidgetData[m_nXScreen].gFrame->style->ythickness;
    aStyleSet.SetFrameStyle(aFrameStyle);

    // mouse over text colors
    aTextColor = getColor( pStyle->fg[ GTK_STATE_PRELIGHT ] );
    aStyleSet.SetButtonRolloverTextColor( aTextColor );
    aStyleSet.SetButtonPressedRolloverTextColor( aTextColor );
    aStyleSet.SetFieldRolloverTextColor( aTextColor );

    // background colors
    Color aBackColor = getColor( pStyle->bg[GTK_STATE_NORMAL] );
    Color aBackFieldColor = getColor( pStyle->base[GTK_STATE_NORMAL] );
    aStyleSet.BatchSetBackgrounds( aBackColor );

    aStyleSet.SetFieldColor( aBackFieldColor );
    aStyleSet.SetWindowColor( aBackFieldColor );

    // Dark shadow color
    Color aDarkShadowColor = getColor( pStyle->fg[GTK_STATE_INSENSITIVE] );
    aStyleSet.SetDarkShadowColor( aDarkShadowColor );

    ::Color aShadowColor(aBackColor);
    if (aDarkShadowColor.GetLuminance() > aBackColor.GetLuminance())
        aShadowColor.IncreaseLuminance(64);
    else
        aShadowColor.DecreaseLuminance(64);
    aStyleSet.SetShadowColor(aShadowColor);

    // highlighting colors
    Color aHighlightColor = getColor( pStyle->base[GTK_STATE_SELECTED] );
    Color aHighlightTextColor = getColor( pStyle->text[GTK_STATE_SELECTED] );
    aStyleSet.SetHighlightColor( aHighlightColor );
    aStyleSet.SetHighlightTextColor( aHighlightTextColor );

    // hyperlink colors
    GdkColor *link_color = nullptr;
    gtk_widget_style_get (m_pWindow, "link-color", &link_color, nullptr);
    if (link_color)
    {
        aStyleSet.SetLinkColor(getColor(*link_color));
        gdk_color_free (link_color);
        link_color = nullptr;
    }
    gtk_widget_style_get (m_pWindow, "visited-link-color", &link_color, nullptr);
    if (link_color)
    {
        aStyleSet.SetVisitedLinkColor(getColor(*link_color));
        gdk_color_free (link_color);
    }

    // Tab colors
    aStyleSet.SetActiveTabColor( aBackFieldColor ); // same as the window color.
    Color aSelectedBackColor = getColor( pStyle->bg[GTK_STATE_ACTIVE] );
    aStyleSet.SetInactiveTabColor( aSelectedBackColor );

    // menu disabled entries handling
    aStyleSet.SetSkipDisabledInMenus( true );
    aStyleSet.SetPreferredContextMenuShortcuts( false );
    // menu colors
    GtkStyle* pMenuStyle = gtk_widget_get_style( gWidgetData[m_nXScreen].gMenuWidget );
    GtkStyle* pMenuItemStyle = gtk_rc_get_style( gWidgetData[m_nXScreen].gMenuItemMenuWidget );
    GtkStyle* pMenubarStyle = gtk_rc_get_style( gWidgetData[m_nXScreen].gMenubarWidget );
    GtkStyle* pMenuTextStyle = gtk_rc_get_style( gtk_bin_get_child( GTK_BIN(gWidgetData[m_nXScreen].gMenuItemMenuWidget) ) );
    aBackColor = getColor( pMenubarStyle->bg[GTK_STATE_NORMAL] );
    aStyleSet.SetMenuBarColor( aBackColor );
    aStyleSet.SetMenuBarRolloverColor( aBackColor );
    aBackColor = getColor( pMenuStyle->bg[GTK_STATE_NORMAL] );
    aTextColor = getColor( pMenuTextStyle->fg[GTK_STATE_NORMAL] );
    aStyleSet.SetMenuColor( aBackColor );
    aStyleSet.SetMenuTextColor( aTextColor );

    aTextColor = aStyleSet.GetPersonaMenuBarTextColor().get_value_or( getColor( pMenubarStyle->fg[GTK_STATE_NORMAL] ) );
    aStyleSet.SetMenuBarTextColor( aTextColor );
    aStyleSet.SetMenuBarRolloverTextColor(getColor(pMenubarStyle->fg[GTK_STATE_PRELIGHT]));
    aStyleSet.SetMenuBarHighlightTextColor(getColor(pMenubarStyle->fg[GTK_STATE_SELECTED]));

    // toolbar colors
    GtkStyle* pToolbarButtonStyle = gtk_rc_get_style( gWidgetData[m_nXScreen].gToolbarButtonWidget );
    aStyleSet.SetToolTextColor(getColor(pToolbarButtonStyle->fg[GTK_STATE_NORMAL]));

#if OSL_DEBUG_LEVEL > 1
    std::fprintf( stderr, "==\n" );
    std::fprintf( stderr, "MenuColor = %x (%d)\n", (int)aStyleSet.GetMenuColor(), aStyleSet.GetMenuColor().GetLuminance() );
    std::fprintf( stderr, "MenuTextColor = %x (%d)\n", (int)aStyleSet.GetMenuTextColor(), aStyleSet.GetMenuTextColor().GetLuminance() );
    std::fprintf( stderr, "MenuBarColor = %x (%d)\n", (int)aStyleSet.GetMenuBarColor(), aStyleSet.GetMenuBarColor().GetLuminance() );
    std::fprintf( stderr, "MenuBarRolloverColor = %x (%d)\n", (int)aStyleSet.GetMenuBarRolloverColor(), aStyleSet.GetMenuBarRolloverColor().GetLuminance() );
    std::fprintf( stderr, "MenuBarTextColor = %x (%d)\n", (int)aStyleSet.GetMenuBarTextColor(), aStyleSet.GetMenuBarTextColor().GetLuminance() );
    std::fprintf( stderr, "MenuBarRolloverTextColor = %x (%d)\n", (int)aStyleSet.GetMenuBarRolloverTextColor(), aStyleSet.GetMenuBarRolloverTextColor().GetLuminance() );
    std::fprintf( stderr, "LightColor = %x (%d)\n", (int)aStyleSet.GetLightColor(), aStyleSet.GetLightColor().GetLuminance() );
    std::fprintf( stderr, "ShadowColor = %x (%d)\n", (int)aStyleSet.GetShadowColor(), aStyleSet.GetShadowColor().GetLuminance() );
    std::fprintf( stderr, "DarkShadowColor = %x (%d)\n", (int)aStyleSet.GetDarkShadowColor(), aStyleSet.GetDarkShadowColor().GetLuminance() );
#endif

    aHighlightColor = getColor( pMenuItemStyle->bg[ GTK_STATE_SELECTED ] );
    aHighlightTextColor = getColor( pMenuItemStyle->fg[ GTK_STATE_SELECTED ] );
    aStyleSet.SetMenuHighlightColor( aHighlightColor );
    aStyleSet.SetMenuHighlightTextColor( aHighlightTextColor );

    // UI font
    OString    aFamily        = pango_font_description_get_family( pStyle->font_desc );
    int nPangoHeight    = pango_font_description_get_size( pStyle->font_desc );
    PangoStyle    eStyle    = pango_font_description_get_style( pStyle->font_desc );
    PangoWeight    eWeight    = pango_font_description_get_weight( pStyle->font_desc );
    PangoStretch eStretch = pango_font_description_get_stretch( pStyle->font_desc );

    psp::FastPrintFontInfo aInfo;
    // set family name
    aInfo.m_aFamilyName = OStringToOUString( aFamily, RTL_TEXTENCODING_UTF8 );
    // set italic
    switch( eStyle )
    {
        case PANGO_STYLE_NORMAL:    aInfo.m_eItalic = ITALIC_NONE;break;
        case PANGO_STYLE_ITALIC:    aInfo.m_eItalic = ITALIC_NORMAL;break;
        case PANGO_STYLE_OBLIQUE:    aInfo.m_eItalic = ITALIC_OBLIQUE;break;
    }
    // set weight
    if( eWeight <= PANGO_WEIGHT_ULTRALIGHT )
        aInfo.m_eWeight = WEIGHT_ULTRALIGHT;
    else if( eWeight <= PANGO_WEIGHT_LIGHT )
        aInfo.m_eWeight = WEIGHT_LIGHT;
    else if( eWeight <= PANGO_WEIGHT_NORMAL )
        aInfo.m_eWeight = WEIGHT_NORMAL;
    else if( eWeight <= PANGO_WEIGHT_BOLD )
        aInfo.m_eWeight = WEIGHT_BOLD;
    else
        aInfo.m_eWeight = WEIGHT_ULTRABOLD;
    // set width
    switch( eStretch )
    {
        case PANGO_STRETCH_ULTRA_CONDENSED:    aInfo.m_eWidth = WIDTH_ULTRA_CONDENSED;break;
        case PANGO_STRETCH_EXTRA_CONDENSED:    aInfo.m_eWidth = WIDTH_EXTRA_CONDENSED;break;
        case PANGO_STRETCH_CONDENSED:        aInfo.m_eWidth = WIDTH_CONDENSED;break;
        case PANGO_STRETCH_SEMI_CONDENSED:    aInfo.m_eWidth = WIDTH_SEMI_CONDENSED;break;
        case PANGO_STRETCH_NORMAL:            aInfo.m_eWidth = WIDTH_NORMAL;break;
        case PANGO_STRETCH_SEMI_EXPANDED:    aInfo.m_eWidth = WIDTH_SEMI_EXPANDED;break;
        case PANGO_STRETCH_EXPANDED:        aInfo.m_eWidth = WIDTH_EXPANDED;break;
        case PANGO_STRETCH_EXTRA_EXPANDED:    aInfo.m_eWidth = WIDTH_EXTRA_EXPANDED;break;
        case PANGO_STRETCH_ULTRA_EXPANDED:    aInfo.m_eWidth = WIDTH_ULTRA_EXPANDED;break;
    }

#if OSL_DEBUG_LEVEL > 1
    std::fprintf( stderr, "font name BEFORE system match: \"%s\"\n", aFamily.getStr() );
#endif

    // match font to e.g. resolve "Sans"
    psp::PrintFontManager::get().matchFont( aInfo, rSettings.GetUILanguageTag().getLocale() );

#if OSL_DEBUG_LEVEL > 1
    std::fprintf( stderr, "font match %s, name AFTER: \"%s\"\n",
             aInfo.m_nID != 0 ? "succeeded" : "failed",
             OUStringToOString( aInfo.m_aFamilyName, RTL_TEXTENCODING_ISO_8859_1 ).getStr() );
#endif

    sal_Int32 nDispDPIY = GetDisplay()->GetResolution().B();
    int nPointHeight;
    if (pango_font_description_get_size_is_absolute(pStyle->font_desc))
        nPointHeight = (nPangoHeight * 72 + nDispDPIY*PANGO_SCALE/2) / (nDispDPIY * PANGO_SCALE);
    else
        nPointHeight = nPangoHeight/PANGO_SCALE;

    vcl::Font aFont( aInfo.m_aFamilyName, Size( 0, nPointHeight ) );
    if( aInfo.m_eWeight != WEIGHT_DONTKNOW )
        aFont.SetWeight( aInfo.m_eWeight );
    if( aInfo.m_eWidth != WIDTH_DONTKNOW )
        aFont.SetWidthType( aInfo.m_eWidth );
    if( aInfo.m_eItalic != ITALIC_DONTKNOW )
        aFont.SetItalic( aInfo.m_eItalic );
    if( aInfo.m_ePitch != PITCH_DONTKNOW )
        aFont.SetPitch( aInfo.m_ePitch );

    aStyleSet.BatchSetFonts( aFont, aFont );

    aFont.SetWeight( WEIGHT_BOLD );
    aStyleSet.SetTitleFont( aFont );
    aStyleSet.SetFloatTitleFont( aFont );

    // Cursor width
    gfloat caretAspectRatio = 0.04f;
    gtk_widget_style_get( gWidgetData[m_nXScreen].gEditBoxWidget, "cursor-aspect-ratio", &caretAspectRatio, nullptr );
    // Assume 20px tall for the ratio computation, which should give reasonable results
    aStyleSet.SetCursorSize( 20 * caretAspectRatio + 1 );

    // get cursor blink time
    gboolean blink = false;

    g_object_get( pSettings, "gtk-cursor-blink", &blink, nullptr );
    if( blink )
    {
        gint blink_time = static_cast<gint>(STYLE_CURSOR_NOBLINKTIME);
        g_object_get( pSettings, "gtk-cursor-blink-time", &blink_time, nullptr );
        // set the blink_time if there is a setting and it is reasonable
        // else leave the default value
        if( blink_time > 100 )
            aStyleSet.SetCursorBlinkTime( blink_time/2 );
    }
    else
        aStyleSet.SetCursorBlinkTime( STYLE_CURSOR_NOBLINKTIME );

    MouseSettings aMouseSettings = rSettings.GetMouseSettings();
    int iDoubleClickTime, iDoubleClickDistance, iDragThreshold, iMenuPopupDelay;
    g_object_get( pSettings,
                  "gtk-double-click-time", &iDoubleClickTime,
                  "gtk-double-click-distance", &iDoubleClickDistance,
                  "gtk-dnd-drag-threshold", &iDragThreshold,
                  "gtk-menu-popup-delay", &iMenuPopupDelay,
                  nullptr );
    aMouseSettings.SetDoubleClickTime( iDoubleClickTime );
    aMouseSettings.SetDoubleClickWidth( iDoubleClickDistance );
    aMouseSettings.SetDoubleClickHeight( iDoubleClickDistance );
    aMouseSettings.SetStartDragWidth( iDragThreshold );
    aMouseSettings.SetStartDragHeight( iDragThreshold );
    aMouseSettings.SetMenuDelay( iMenuPopupDelay );
    rSettings.SetMouseSettings( aMouseSettings );

    gboolean showmenuicons = true, primarybuttonwarps = false;
    g_object_get( pSettings,
        "gtk-menu-images", &showmenuicons,
        nullptr );
    if( g_object_class_find_property(
            G_OBJECT_GET_CLASS(pSettings), "gtk-primary-button-warps-slider") )
    {
        g_object_get( pSettings,
            "gtk-primary-button-warps-slider", &primarybuttonwarps,
            nullptr );
    }
    aStyleSet.SetPreferredUseImagesInMenus(showmenuicons);
    aStyleSet.SetPrimaryButtonWarpsSlider(primarybuttonwarps);

    // set scrollbar settings
    gint slider_width = 14;
    gint trough_border = 1;
    gint min_slider_length = 21;

    // Grab some button style attributes
    gtk_widget_style_get( gWidgetData[m_nXScreen].gScrollHorizWidget,
                          "slider-width", &slider_width,
                          "trough-border", &trough_border,
                          "min-slider-length", &min_slider_length,
                          nullptr );
    gint magic = trough_border ? 1 : 0;
    aStyleSet.SetScrollBarSize( slider_width + 2*trough_border );
    aStyleSet.SetMinThumbSize( min_slider_length - magic );

    // preferred icon style
    gchar* pIconThemeName = nullptr;
    g_object_get( pSettings, "gtk-icon-theme-name", &pIconThemeName, nullptr );
    aStyleSet.SetPreferredIconTheme( OUString::createFromAscii( pIconThemeName ) );
    g_free( pIconThemeName );

    aStyleSet.SetToolbarIconSize( ToolbarIconSize::Large );

    // finally update the collected settings
    rSettings.SetStyleSettings( aStyleSet );

    return true;
}

/************************************************************************
 * Create a GdkPixmap filled with the contents of an area of an Xlib window
 ************************************************************************/

std::unique_ptr<GdkX11Pixmap> GtkSalGraphics::NWGetPixmapFromScreen( tools::Rectangle srcRect, int nBgColor )
{
    int nDepth = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetVisual( m_nXScreen ).GetDepth();

    std::unique_ptr<GdkX11Pixmap> pPixmap(new GdkX11Pixmap( srcRect.GetWidth(), srcRect.GetHeight(), nDepth ));

    if( nBgColor == BG_FILL )
    {
        FillPixmapFromScreen( pPixmap.get(), srcRect.Left(), srcRect.Top() );
    }
    else if( nBgColor != BG_NONE )
    {
        cairo_t *cr = gdk_cairo_create( pPixmap->GetGdkDrawable() );
        if( nBgColor == BG_BLACK)
            cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
        else
            cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
        cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
        cairo_paint (cr);
        cairo_destroy(cr);
    }

    return pPixmap;
}

/************************************************************************
 * Copy an alpha pixmap to screen using a gc with clipping
 ************************************************************************/

bool GtkSalGraphics::NWRenderPixmapToScreen( GdkX11Pixmap* pPixmap, GdkX11Pixmap* pMask, tools::Rectangle dstRect )
{
    return RenderPixmapToScreen( pPixmap, pMask, dstRect.Left(), dstRect.Top() );
}

/************************************************************************
 * State conversion
 ************************************************************************/
static void NWConvertVCLStateToGTKState( ControlState nVCLState,
            GtkStateType* nGTKState, GtkShadowType* nGTKShadow )
{
    *nGTKShadow = GTK_SHADOW_OUT;
    *nGTKState = GTK_STATE_INSENSITIVE;

    if ( nVCLState & ControlState::ENABLED )
    {
        if ( nVCLState & ControlState::PRESSED )
        {
            *nGTKState = GTK_STATE_ACTIVE;
            *nGTKShadow = GTK_SHADOW_IN;
        }
        else if ( nVCLState & ControlState::ROLLOVER )
        {
            *nGTKState = GTK_STATE_PRELIGHT;
            *nGTKShadow = GTK_SHADOW_OUT;
        }
        else
        {
            *nGTKState = GTK_STATE_NORMAL;
            *nGTKShadow = GTK_SHADOW_OUT;
        }
    }
}

/************************************************************************
 * Set widget flags
 ************************************************************************/
static void NWSetWidgetState( GtkWidget* widget, ControlState nState, GtkStateType nGtkState )
{
    // Set to default state, then build up from there
    GTK_WIDGET_UNSET_FLAGS( widget, GTK_HAS_DEFAULT );
    GTK_WIDGET_UNSET_FLAGS( widget, GTK_HAS_FOCUS );
    GTK_WIDGET_UNSET_FLAGS( widget, GTK_SENSITIVE );
    GTK_WIDGET_SET_FLAGS( widget, gWidgetDefaultFlags[reinterpret_cast<long>(widget)] );

    if ( nState & ControlState::DEFAULT )
        GTK_WIDGET_SET_FLAGS( widget, GTK_HAS_DEFAULT );
    if ( !GTK_IS_TOGGLE_BUTTON(widget) && (nState & ControlState::FOCUSED) )
        GTK_WIDGET_SET_FLAGS( widget, GTK_HAS_FOCUS );
    if ( nState & ControlState::ENABLED )
        GTK_WIDGET_SET_FLAGS( widget, GTK_SENSITIVE );
    gtk_widget_set_state( widget, nGtkState );
}

/************************************************************************
 * Widget ensure functions - make sure cached objects are valid
 ************************************************************************/

static void NWAddWidgetToCacheWindow( GtkWidget* widget, SalX11Screen nScreen )
{
    NWFWidgetData& rData = gWidgetData[nScreen];
    if ( !rData.gCacheWindow || !rData.gDumbContainer )
    {
        if ( !rData.gCacheWindow )
        {
            rData.gCacheWindow = gtk_window_new( GTK_WINDOW_TOPLEVEL );
            g_object_set_data( G_OBJECT( rData.gCacheWindow ), "libo-version",
                               const_cast<char *>(LIBO_VERSION_DOTTED) );

            GdkScreen* pScreen = gdk_display_get_screen( gdk_display_get_default(),
                                                         nScreen.getXScreen() );
            if( pScreen )
                gtk_window_set_screen( GTK_WINDOW(rData.gCacheWindow), pScreen );
        }
        if ( !rData.gDumbContainer )
            rData.gDumbContainer = gtk_fixed_new();
        gtk_container_add( GTK_CONTAINER(rData.gCacheWindow), rData.gDumbContainer );
        gtk_widget_realize( rData.gDumbContainer );
        gtk_widget_realize( rData.gCacheWindow );
    }

    gtk_container_add( GTK_CONTAINER(rData.gDumbContainer), widget );
    gtk_widget_realize( widget );
    gtk_widget_ensure_style( widget );

    // Store widget's default flags
    gWidgetDefaultFlags[ reinterpret_cast<long>(widget) ] = GTK_WIDGET_FLAGS( widget );
}

static void NWEnsureGTKButton( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gBtnWidget )
    {
        gWidgetData[nScreen].gBtnWidget = gtk_button_new_with_label( "" );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gBtnWidget, nScreen );
    }
}

static void NWEnsureGTKRadio( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gRadioWidget || !gWidgetData[nScreen].gRadioWidgetSibling )
    {
        gWidgetData[nScreen].gRadioWidget = gtk_radio_button_new( nullptr );
        gWidgetData[nScreen].gRadioWidgetSibling = gtk_radio_button_new_from_widget( GTK_RADIO_BUTTON(gWidgetData[nScreen].gRadioWidget) );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gRadioWidget, nScreen );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gRadioWidgetSibling, nScreen );
    }
}

static void NWEnsureGTKCheck( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gCheckWidget )
    {
        gWidgetData[nScreen].gCheckWidget = gtk_check_button_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gCheckWidget, nScreen );
    }
}

static void NWEnsureGTKScrollbars( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gScrollHorizWidget )
    {
        gWidgetData[nScreen].gScrollHorizWidget = gtk_hscrollbar_new( nullptr );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gScrollHorizWidget, nScreen );
    }

    if ( !gWidgetData[nScreen].gScrollVertWidget )
    {
        gWidgetData[nScreen].gScrollVertWidget = gtk_vscrollbar_new( nullptr );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gScrollVertWidget, nScreen );
    }
}

static void NWEnsureGTKArrow( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gArrowWidget || !gWidgetData[nScreen].gDropdownWidget )
    {
        gWidgetData[nScreen].gDropdownWidget = gtk_toggle_button_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gDropdownWidget, nScreen );
        gWidgetData[nScreen].gArrowWidget = gtk_arrow_new( GTK_ARROW_DOWN, GTK_SHADOW_OUT );
        gtk_container_add( GTK_CONTAINER(gWidgetData[nScreen].gDropdownWidget), gWidgetData[nScreen].gArrowWidget );
        gtk_widget_set_rc_style( gWidgetData[nScreen].gArrowWidget );
        gtk_widget_realize( gWidgetData[nScreen].gArrowWidget );
    }
}

static void NWEnsureGTKEditBox( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gEditBoxWidget )
    {
        gWidgetData[nScreen].gEditBoxWidget = gtk_entry_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gEditBoxWidget, nScreen );
    }
}

static void NWEnsureGTKSpinButton( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gSpinButtonWidget )
    {
        GtkAdjustment *adj = GTK_ADJUSTMENT( gtk_adjustment_new(0, 0, 1, 1, 1, 0) );
        gWidgetData[nScreen].gSpinButtonWidget = gtk_spin_button_new( adj, 1, 2 );

        //Setting non-editable means it doesn't blink, so there's no timeouts
        //running around to nobble us
        gtk_editable_set_editable(GTK_EDITABLE(gWidgetData[nScreen].gSpinButtonWidget), false);

        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gSpinButtonWidget, nScreen );
    }
}

static void NWEnsureGTKNotebook( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gNotebookWidget )
    {
        gWidgetData[nScreen].gNotebookWidget = gtk_notebook_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gNotebookWidget, nScreen );
    }
}

static void NWEnsureGTKOptionMenu( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gOptionMenuWidget )
    {
        gWidgetData[nScreen].gOptionMenuWidget = gtk_option_menu_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gOptionMenuWidget, nScreen );
    }
}

static void NWEnsureGTKCombo( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gComboWidget )
    {
        gWidgetData[nScreen].gComboWidget = gtk_combo_new();

        // #i59129# Setting non-editable means it doesn't blink, so
        // there are no timeouts running around to nobble us
        gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(gWidgetData[nScreen].gComboWidget)->entry), false);

        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gComboWidget, nScreen );
        // Must realize the ComboBox's children, since GTK
        // does not do this for us in GtkCombo::gtk_widget_realize()
        gtk_widget_realize( GTK_COMBO(gWidgetData[nScreen].gComboWidget)->button );
        gtk_widget_realize( GTK_COMBO(gWidgetData[nScreen].gComboWidget)->entry );
    }
}

static void NWEnsureGTKScrolledWindow( SalX11Screen nScreen )
{
    if ( !gWidgetData[nScreen].gScrolledWindowWidget )
    {
        GtkAdjustment *hadj = GTK_ADJUSTMENT( gtk_adjustment_new(0, 0, 0, 0, 0, 0) );
        GtkAdjustment *vadj = GTK_ADJUSTMENT( gtk_adjustment_new(0, 0, 0, 0, 0, 0) );

        gWidgetData[nScreen].gScrolledWindowWidget = gtk_scrolled_window_new( hadj, vadj );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gScrolledWindowWidget, nScreen );
    }
}

static void NWEnsureGTKToolbar( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gToolbarWidget )
    {
        gWidgetData[nScreen].gToolbarWidget = gtk_toolbar_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gToolbarWidget, nScreen );
        gWidgetData[nScreen].gToolbarButtonWidget = GTK_WIDGET(gtk_toggle_button_new());
        gWidgetData[nScreen].gSeparator = GTK_WIDGET(gtk_separator_tool_item_new());
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gSeparator, nScreen );

        GtkReliefStyle aRelief = GTK_RELIEF_NORMAL;
        gtk_widget_ensure_style( gWidgetData[nScreen].gToolbarWidget );
        gtk_widget_style_get( gWidgetData[nScreen].gToolbarWidget,
                              "button_relief", &aRelief,
                              nullptr);

        gtk_button_set_relief( GTK_BUTTON(gWidgetData[nScreen].gToolbarButtonWidget), aRelief );
        GTK_WIDGET_UNSET_FLAGS( gWidgetData[nScreen].gToolbarButtonWidget, GTK_CAN_FOCUS );
        GTK_WIDGET_UNSET_FLAGS( gWidgetData[nScreen].gToolbarButtonWidget, GTK_CAN_DEFAULT );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gToolbarButtonWidget, nScreen );

    }
    if( ! gWidgetData[nScreen].gHandleBoxWidget )
    {
        gWidgetData[nScreen].gHandleBoxWidget = gtk_handle_box_new();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gHandleBoxWidget, nScreen );
    }
}

static void NWEnsureGTKMenubar( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gMenubarWidget )
    {
        gWidgetData[nScreen].gMenubarWidget = gtk_menu_bar_new();
        gWidgetData[nScreen].gMenuItemMenubarWidget = gtk_menu_item_new_with_label( "b" );
        gtk_menu_shell_append( GTK_MENU_SHELL( gWidgetData[nScreen].gMenubarWidget ), gWidgetData[nScreen].gMenuItemMenubarWidget );
        gtk_widget_show( gWidgetData[nScreen].gMenuItemMenubarWidget );
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gMenubarWidget, nScreen );
        gtk_widget_show( gWidgetData[nScreen].gMenubarWidget );

        // do what NWAddWidgetToCacheWindow does except adding to def container
        gtk_widget_realize( gWidgetData[nScreen].gMenuItemMenubarWidget );
        gtk_widget_ensure_style( gWidgetData[nScreen].gMenuItemMenubarWidget );

        gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gMenuItemMenubarWidget) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gMenuItemMenubarWidget );
    }
}

static void NWEnsureGTKMenu( SalX11Screen nScreen )
{
    if( gWidgetData[nScreen].gMenuWidget )
        return;

    gWidgetData[nScreen].gMenuWidget                  = gtk_menu_new();
    gWidgetData[nScreen].gMenuItemMenuWidget          = gtk_menu_item_new_with_label( "b" );
    gWidgetData[nScreen].gMenuItemCheckMenuWidget     = gtk_check_menu_item_new_with_label( "b" );
    gWidgetData[nScreen].gMenuItemRadioMenuWidget     = gtk_radio_menu_item_new_with_label( nullptr, "b" );
    gWidgetData[nScreen].gMenuItemSeparatorMenuWidget = gtk_separator_menu_item_new();
    gWidgetData[nScreen].gImageMenuItem               = gtk_image_menu_item_new();

    g_object_ref_sink (gWidgetData[nScreen].gMenuWidget);

    gtk_menu_shell_append( GTK_MENU_SHELL( gWidgetData[nScreen].gMenuWidget ), gWidgetData[nScreen].gMenuItemMenuWidget );
    gtk_menu_shell_append( GTK_MENU_SHELL( gWidgetData[nScreen].gMenuWidget ), gWidgetData[nScreen].gMenuItemCheckMenuWidget );
    gtk_menu_shell_append( GTK_MENU_SHELL( gWidgetData[nScreen].gMenuWidget ), gWidgetData[nScreen].gMenuItemRadioMenuWidget );
    gtk_menu_shell_append( GTK_MENU_SHELL( gWidgetData[nScreen].gMenuWidget ), gWidgetData[nScreen].gMenuItemSeparatorMenuWidget );
    gtk_menu_shell_append( GTK_MENU_SHELL( gWidgetData[nScreen].gMenuWidget ), gWidgetData[nScreen].gImageMenuItem );

    // do what NWAddWidgetToCacheWindow does except adding to def container
    gtk_widget_realize( gWidgetData[nScreen].gMenuWidget );
    gtk_widget_ensure_style( gWidgetData[nScreen].gMenuWidget );

    gtk_widget_realize( gWidgetData[nScreen].gMenuItemMenuWidget );
    gtk_widget_ensure_style( gWidgetData[nScreen].gMenuItemMenuWidget );

    gtk_widget_realize( gWidgetData[nScreen].gMenuItemCheckMenuWidget );
    gtk_widget_ensure_style( gWidgetData[nScreen].gMenuItemCheckMenuWidget );

    gtk_widget_realize( gWidgetData[nScreen].gMenuItemRadioMenuWidget );
    gtk_widget_ensure_style( gWidgetData[nScreen].gMenuItemRadioMenuWidget );

    gtk_widget_realize( gWidgetData[nScreen].gMenuItemSeparatorMenuWidget );
    gtk_widget_ensure_style( gWidgetData[nScreen].gMenuItemSeparatorMenuWidget );

    gtk_widget_realize( gWidgetData[nScreen].gImageMenuItem );
    gtk_widget_ensure_style( gWidgetData[nScreen].gImageMenuItem );

    gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gMenuWidget) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gMenuWidget );
    gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gMenuItemMenuWidget) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gMenuItemMenuWidget );
    gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gMenuItemCheckMenuWidget) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gMenuItemCheckMenuWidget );
    gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gMenuItemRadioMenuWidget) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gMenuItemRadioMenuWidget );
    gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gMenuItemSeparatorMenuWidget) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gMenuItemSeparatorMenuWidget );
    gWidgetDefaultFlags[ reinterpret_cast<long>(gWidgetData[nScreen].gImageMenuItem) ] = GTK_WIDGET_FLAGS( gWidgetData[nScreen].gImageMenuItem );

}

static void NWEnsureGTKTooltip( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gTooltipPopup )
    {
        gWidgetData[nScreen].gTooltipPopup = gtk_window_new (GTK_WINDOW_POPUP);
        GdkScreen* pScreen = gdk_display_get_screen( gdk_display_get_default(),
                                                     nScreen.getXScreen() );
        if( pScreen )
            gtk_window_set_screen( GTK_WINDOW(gWidgetData[nScreen].gTooltipPopup), pScreen );
        gtk_widget_set_name( gWidgetData[nScreen].gTooltipPopup, "gtk-tooltips");
        gtk_widget_realize( gWidgetData[nScreen].gTooltipPopup );
        gtk_widget_ensure_style( gWidgetData[nScreen].gTooltipPopup );
    }
}

static void NWEnsureGTKDialog( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gDialog )
    {
        gWidgetData[nScreen].gDialog = gtk_dialog_new();
        GdkScreen* pScreen = gdk_display_get_screen( gdk_display_get_default(),
                                                     nScreen.getXScreen() );
        if( pScreen )
            gtk_window_set_screen( GTK_WINDOW(gWidgetData[nScreen].gDialog), pScreen );
        gtk_widget_realize(gWidgetData[nScreen].gDialog);
        gtk_widget_ensure_style(gWidgetData[nScreen].gDialog);
    }
}

static void NWEnsureGTKFrame( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gFrame )
    {
        gWidgetData[nScreen].gFrame = gtk_frame_new(nullptr);
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gFrame, nScreen );
    }
}

static void NWEnsureGTKProgressBar( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gProgressBar )
    {
        gWidgetData[nScreen].gProgressBar = gtk_progress_bar_new ();
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gProgressBar, nScreen );
    }
}

static void NWEnsureGTKTreeView( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gTreeView )
    {
        gWidgetData[nScreen].gTreeView = gtk_tree_view_new ();

        // Columns will be used for tree header rendering
        GtkCellRenderer* renderer=gtk_cell_renderer_text_new();
        GtkTreeViewColumn* column=gtk_tree_view_column_new_with_attributes("",renderer,"text",0,nullptr);
        gtk_tree_view_column_set_widget(column,gtk_label_new(""));
        gtk_tree_view_append_column(GTK_TREE_VIEW(gWidgetData[nScreen].gTreeView), column);

        // Add one more column so that some engines like clearlooks did render separators between columns
        column=gtk_tree_view_column_new_with_attributes("",renderer,"text",0,nullptr);
        gtk_tree_view_append_column(GTK_TREE_VIEW(gWidgetData[nScreen].gTreeView), column);

        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gTreeView, nScreen );
    }
}

static void NWEnsureGTKSlider( SalX11Screen nScreen )
{
    if( !gWidgetData[nScreen].gHScale )
    {
        gWidgetData[nScreen].gHScale = gtk_hscale_new_with_range(0, 10, 1);
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gHScale, nScreen );
    }
    if( !gWidgetData[nScreen].gVScale )
    {
        gWidgetData[nScreen].gVScale = gtk_vscale_new_with_range(0, 10, 1);
        NWAddWidgetToCacheWindow( gWidgetData[nScreen].gVScale, nScreen );
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/salprn-gtk.cxx b/vcl/unx/gtk/salprn-gtk.cxx
deleted file mode 100644
index e5e17a5..0000000
--- a/vcl/unx/gtk/salprn-gtk.cxx
+++ /dev/null
@@ -1,970 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <unx/gtk/gtkprintwrapper.hxx>

#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkprn.hxx>

#include <vcl/configsettings.hxx>
#include <vcl/help.hxx>
#include <vcl/print.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>

#include <gtk/gtk.h>

#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/document/XExporter.hpp>
#include <com/sun/star/document/XFilter.hpp>
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
#include <com/sun/star/sheet/XSpreadsheet.hpp>
#include <com/sun/star/sheet/XSpreadsheetView.hpp>
#include <com/sun/star/view/PrintableState.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>

#include <officecfg/Office/Common.hxx>

#include <rtl/ustring.hxx>
#include <sal/log.hxx>

#include <unotools/streamwrap.hxx>

#include <cstring>
#include <map>

namespace beans = com::sun::star::beans;
namespace uno = com::sun::star::uno;
namespace view = com::sun::star::view;

using vcl::unx::GtkPrintWrapper;

using uno::UNO_QUERY;

class GtkPrintDialog
{
public:
    explicit GtkPrintDialog(vcl::PrinterController& io_rController);
    bool run();
    GtkPrinter* getPrinter() const
    {
        return m_xWrapper->print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(m_pDialog));
    }
    GtkPrintSettings* getSettings() const
    {
        return m_xWrapper->print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog));
    }
    void updateControllerPrintRange();

    ~GtkPrintDialog();

    static void UIOption_CheckHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
    {
        io_pThis->impl_UIOption_CheckHdl(i_pWidget);
    }
    static void UIOption_RadioHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
    {
        io_pThis->impl_UIOption_RadioHdl(i_pWidget);
    }
    static void UIOption_SelectHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
    {
        io_pThis->impl_UIOption_SelectHdl(i_pWidget);
    }

private:
    beans::PropertyValue* impl_queryPropertyValue(GtkWidget* i_pWidget) const;
    void impl_checkOptionalControlDependencies();

    void impl_UIOption_CheckHdl(GtkWidget* i_pWidget);
    void impl_UIOption_RadioHdl(GtkWidget* i_pWidget);
    void impl_UIOption_SelectHdl(GtkWidget* i_pWidget);

    void impl_initDialog();
    void impl_initCustomTab();
    void impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled);

    void impl_readFromSettings();
    void impl_storeToSettings() const;

private:
    GtkWidget* m_pDialog;
    vcl::PrinterController& m_rController;
    std::map<GtkWidget*, OUString> m_aControlToPropertyMap;
    std::map<GtkWidget*, sal_Int32> m_aControlToNumValMap;
    std::shared_ptr<GtkPrintWrapper> m_xWrapper;
};

struct GtkSalPrinter_Impl
{
    OString m_sSpoolFile;
    OUString m_sJobName;
    GtkPrinter* m_pPrinter;
    GtkPrintSettings* m_pSettings;

    GtkSalPrinter_Impl();
    ~GtkSalPrinter_Impl();
};

GtkSalPrinter_Impl::GtkSalPrinter_Impl()
    : m_pPrinter(nullptr)
    , m_pSettings(nullptr)
{
}

GtkSalPrinter_Impl::~GtkSalPrinter_Impl()
{
    if (m_pPrinter)
    {
        g_object_unref(G_OBJECT(m_pPrinter));
        m_pPrinter = nullptr;
    }
    if (m_pSettings)
    {
        g_object_unref(G_OBJECT(m_pSettings));
        m_pSettings = nullptr;
    }
}

namespace
{

GtkInstance const&
lcl_getGtkSalInstance()
{
    // we _know_ this is GtkInstance
    return *static_cast<GtkInstance*>(GetGtkSalData()->m_pInstance);
}

bool
lcl_useSystemPrintDialog()
{
    return officecfg::Office::Common::Misc::UseSystemPrintDialog::get()
        && officecfg::Office::Common::Misc::ExperimentalMode::get()
        && lcl_getGtkSalInstance().getPrintWrapper()->supportsPrinting();
}

}

GtkSalPrinter::GtkSalPrinter(SalInfoPrinter* const i_pInfoPrinter)
    : PspSalPrinter(i_pInfoPrinter)
{
}

GtkSalPrinter::~GtkSalPrinter() = default;

bool
GtkSalPrinter::impl_doJob(
        const OUString* const i_pFileName,
        const OUString& i_rJobName,
        const OUString& i_rAppName,
        ImplJobSetup* const io_pSetupData,
        const bool i_bCollate,
        vcl::PrinterController& io_rController)
{
    io_rController.setJobState(view::PrintableState_JOB_STARTED);
    io_rController.jobStarted();
    const bool bJobStarted(
            PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName,
                1/*i_nCopies*/, i_bCollate, true, io_pSetupData))
        ;

    if (bJobStarted)
    {
        io_rController.createProgressDialog();
        const int nPages(io_rController.getFilteredPageCount());
        for (int nPage(0); nPage != nPages; ++nPage)
        {
            if (nPage == nPages - 1)
                io_rController.setLastPage(true);
            io_rController.printFilteredPage(nPage);
        }
        io_rController.setJobState(view::PrintableState_JOB_COMPLETED);
    }

    return bJobStarted;
}

bool
GtkSalPrinter::StartJob(
        const OUString* const i_pFileName,
        const OUString& i_rJobName,
        const OUString& i_rAppName,
        ImplJobSetup* io_pSetupData,
        vcl::PrinterController& io_rController)
{
    if (!lcl_useSystemPrintDialog())
        return PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName, io_pSetupData, io_rController);

    assert(!m_xImpl);

    m_xImpl.reset(new GtkSalPrinter_Impl());
    m_xImpl->m_sJobName = i_rJobName;

    OString sFileName;
    if (i_pFileName)
        sFileName = OUStringToOString(*i_pFileName, osl_getThreadTextEncoding());

    GtkPrintDialog aDialog(io_rController);
    if (!aDialog.run())
    {
        io_rController.abortJob();
        return false;
    }
    aDialog.updateControllerPrintRange();
    m_xImpl->m_pPrinter = aDialog.getPrinter();
    m_xImpl->m_pSettings = aDialog.getSettings();

    //To-Do proper name, watch for encodings
    sFileName = OString("/tmp/hacking.ps");
    m_xImpl->m_sSpoolFile = sFileName;

    OUString aFileName = OStringToOUString(sFileName, osl_getThreadTextEncoding());

    //To-Do, swap ps/pdf for gtk_printer_accepts_ps()/gtk_printer_accepts_pdf() ?

    return impl_doJob(&aFileName, i_rJobName, i_rAppName, io_pSetupData, /*bCollate*/false, io_rController);
}

bool
GtkSalPrinter::EndJob()
{
    bool bRet = PspSalPrinter::EndJob();

    if (!lcl_useSystemPrintDialog())
        return bRet;

    assert(m_xImpl);

    if (!bRet || m_xImpl->m_sSpoolFile.isEmpty())
        return bRet;

    std::shared_ptr<GtkPrintWrapper> const xWrapper(lcl_getGtkSalInstance().getPrintWrapper());

    GtkPageSetup* pPageSetup = xWrapper->page_setup_new();

    GtkPrintJob* const pJob = xWrapper->print_job_new(
        OUStringToOString(m_xImpl->m_sJobName, RTL_TEXTENCODING_UTF8).getStr(),
        m_xImpl->m_pPrinter, m_xImpl->m_pSettings, pPageSetup);

    GError* error = nullptr;
    bRet = xWrapper->print_job_set_source_file(pJob, m_xImpl->m_sSpoolFile.getStr(), &error);
    if (bRet)
        xWrapper->print_job_send(pJob, nullptr, nullptr, nullptr);
    else
    {
        //To-Do, do something with this
        fprintf(stderr, "error was %s\n", error->message);
        g_error_free(error);
    }

    g_object_unref(pPageSetup);
    m_xImpl.reset();

    //To-Do, remove temp spool file

    return bRet;
}

namespace
{

void
lcl_setHelpText(
        GtkWidget* const io_pWidget,
        const uno::Sequence<OUString>& i_rHelpTexts,
        const sal_Int32 i_nIndex)
{
    if (i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength())
        gtk_widget_set_tooltip_text(io_pWidget,
            OUStringToOString(i_rHelpTexts.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8).getStr());
}

GtkWidget*
lcl_makeFrame(
        GtkWidget* const i_pChild,
        const OUString &i_rText,
        const uno::Sequence<OUString> &i_rHelpTexts,
        sal_Int32* const io_pCurHelpText)
{
    GtkWidget* const pLabel = gtk_label_new(nullptr);
    lcl_setHelpText(pLabel, i_rHelpTexts, !io_pCurHelpText ? 0 : (*io_pCurHelpText)++);
    gtk_misc_set_alignment(GTK_MISC(pLabel), 0.0, 0.5);

    {
        gchar* const pText = g_markup_printf_escaped("<b>%s</b>",
            OUStringToOString(i_rText, RTL_TEXTENCODING_UTF8).getStr());
        gtk_label_set_markup_with_mnemonic(GTK_LABEL(pLabel), pText);
        g_free(pText);
    }

    GtkWidget* const pFrame = gtk_vbox_new(FALSE, 6);
    gtk_box_pack_start(GTK_BOX(pFrame), pLabel, FALSE, FALSE, 0);

    GtkWidget* const pAlignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    gtk_alignment_set_padding(GTK_ALIGNMENT(pAlignment), 0, 0, 12, 0);
    gtk_box_pack_start(GTK_BOX(pFrame), pAlignment, FALSE, FALSE, 0);

    gtk_container_add(GTK_CONTAINER(pAlignment), i_pChild);
    return pFrame;
}

void
lcl_extractHelpTextsOrIds(
        const beans::PropertyValue& rEntry,
        uno::Sequence<OUString>& rHelpStrings)
{
    if (!(rEntry.Value >>= rHelpStrings))
    {
        OUString aHelpString;
        if (rEntry.Value >>= aHelpString)
        {
            rHelpStrings.realloc(1);
            *rHelpStrings.getArray() = aHelpString;
        }
    }
}

GtkWidget*
lcl_combo_box_text_new()
{
#if GTK_CHECK_VERSION(3,0,0)
    return gtk_combo_box_text_new();
#else
    return gtk_combo_box_new_text();
#endif
}

void
lcl_combo_box_text_append(GtkWidget* const pWidget, gchar const* const pText)
{
#if GTK_CHECK_VERSION(3,0,0)
    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(pWidget), pText);
#else
    gtk_combo_box_append_text(GTK_COMBO_BOX(pWidget), pText);
#endif
}

}

GtkPrintDialog::GtkPrintDialog(vcl::PrinterController& io_rController)
    : m_rController(io_rController)
    , m_xWrapper(lcl_getGtkSalInstance().getPrintWrapper())
{
    assert(m_xWrapper->supportsPrinting());
    impl_initDialog();
    impl_initCustomTab();
    impl_readFromSettings();
}

void
GtkPrintDialog::impl_initDialog()
{
    //To-Do, like fpicker, set UI language
    m_pDialog = m_xWrapper->print_unix_dialog_new();

    vcl::Window* const pTopWindow(Application::GetActiveTopWindow());
    if (pTopWindow)
    {
        GtkSalFrame* const pFrame(dynamic_cast<GtkSalFrame*>(pTopWindow->ImplGetFrame()));
        if (pFrame)
        {
            GtkWindow* const pParent(GTK_WINDOW(pFrame->getWindow()));
            if (pParent)
                gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
        }
    }

    m_xWrapper->print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(m_pDialog),
        GtkPrintCapabilities(GTK_PRINT_CAPABILITY_COPIES
            | GTK_PRINT_CAPABILITY_COLLATE
            | GTK_PRINT_CAPABILITY_REVERSE
            | GTK_PRINT_CAPABILITY_GENERATE_PS
            | GTK_PRINT_CAPABILITY_NUMBER_UP
            | GTK_PRINT_CAPABILITY_NUMBER_UP_LAYOUT
       ));
}

void
GtkPrintDialog::impl_initCustomTab()
{
    typedef std::vector<std::pair<GtkWidget*, OUString> > CustomTabs_t;

    const uno::Sequence<beans::PropertyValue>& rOptions(m_rController.getUIOptions());
    std::map<OUString, GtkWidget*> aPropertyToDependencyRowMap;
    CustomTabs_t aCustomTabs;
    GtkWidget* pCurParent = nullptr;
    GtkWidget* pCurTabPage = nullptr;
    GtkWidget* pCurSubGroup = nullptr;
    bool bIgnoreSubgroup = false;
    for (const auto& rOption : rOptions)
    {
        uno::Sequence<beans::PropertyValue> aOptProp;
        rOption.Value >>= aOptProp;

        OUString aCtrlType;
        OUString aText;
        OUString aPropertyName;
        uno::Sequence<OUString> aChoices;
        uno::Sequence<sal_Bool> aChoicesDisabled;
        uno::Sequence<OUString> aHelpTexts;
        sal_Int64 nMinValue = 0, nMaxValue = 0;
        sal_Int32 nCurHelpText = 0;
        OUString aDependsOnName;
        sal_Int32 nDependsOnValue = 0;
        bool bUseDependencyRow = false;
        bool bIgnore = false;
        GtkWidget* pGroup = nullptr;
        bool bGtkInternal = false;

        //Fix fdo#69381
        //Next options if this one is empty
        if (!aOptProp.hasElements())
            continue;

        for (const beans::PropertyValue& rEntry : std::as_const(aOptProp))
        {
            if ( rEntry.Name == "Text" )
            {
                OUString aValue;
                rEntry.Value >>= aValue;
                aText = aValue.replace('~', '_');
            }
            else if ( rEntry.Name == "ControlType" )
                rEntry.Value >>= aCtrlType;
            else if ( rEntry.Name == "Choices" )
                rEntry.Value >>= aChoices;
            else if ( rEntry.Name == "ChoicesDisabled" )
                rEntry.Value >>= aChoicesDisabled;
            else if ( rEntry.Name == "Property" )
            {
                beans::PropertyValue aVal;
                rEntry.Value >>= aVal;
                aPropertyName = aVal.Name;
            }
            else if ( rEntry.Name == "DependsOnName" )
                rEntry.Value >>= aDependsOnName;
            else if ( rEntry.Name == "DependsOnEntry" )
                rEntry.Value >>= nDependsOnValue;
            else if ( rEntry.Name == "AttachToDependency" )
                rEntry.Value >>= bUseDependencyRow;
            else if ( rEntry.Name == "MinValue" )
                rEntry.Value >>= nMinValue;
            else if ( rEntry.Name == "MaxValue" )
                rEntry.Value >>= nMaxValue;
            else if ( rEntry.Name == "HelpId" )
            {
                uno::Sequence<OUString> aHelpIds;
                lcl_extractHelpTextsOrIds(rEntry, aHelpIds);
                Help* const pHelp = Application::GetHelp();
                if (pHelp)
                {
                    const int nLen = aHelpIds.getLength();
                    aHelpTexts.realloc(nLen);
                    std::transform(aHelpIds.begin(), aHelpIds.end(), aHelpTexts.begin(),
                        [&pHelp](const OUString& rHelpId) { return pHelp->GetHelpText(rHelpId, static_cast<weld::Widget*>(nullptr)); });
                }
                else // fallback
                    aHelpTexts = aHelpIds;
            }
            else if ( rEntry.Name == "HelpText" )
                lcl_extractHelpTextsOrIds(rEntry, aHelpTexts);
            else if ( rEntry.Name == "InternalUIOnly" )
                rEntry.Value >>= bIgnore;
            else if ( rEntry.Name == "Enabled" )
            {
                // Ignore this. We use UIControlOptions::isUIOptionEnabled
                // to check whether a control should be enabled.
            }
            else if ( rEntry.Name == "GroupingHint" )
            {
                // Ignore this. We cannot add/modify controls to/on existing
                // tabs of the Gtk print dialog.
            }
            else
            {
                SAL_INFO("vcl.gtk", "unhandled UI option entry: " << rEntry.Name);
            }
        }

        if ( aPropertyName == "PrintContent" )
            bGtkInternal = true;

        if (aCtrlType == "Group" || !pCurParent)
        {
            pCurTabPage = gtk_vbox_new(FALSE, 12);
            gtk_container_set_border_width(GTK_CONTAINER(pCurTabPage), 6);
            lcl_setHelpText(pCurTabPage, aHelpTexts, 0);

            pCurParent = pCurTabPage;
            aCustomTabs.emplace_back(pCurTabPage, aText);
        }
        else if (aCtrlType == "Subgroup")
        {
            bIgnoreSubgroup = bIgnore;
            if (bIgnore)
                continue;
            pCurParent = gtk_vbox_new(FALSE, 12);
            gtk_container_set_border_width(GTK_CONTAINER(pCurParent), 0);

            pCurSubGroup = lcl_makeFrame(pCurParent, aText, aHelpTexts, nullptr);
            gtk_box_pack_start(GTK_BOX(pCurTabPage), pCurSubGroup, FALSE, FALSE, 0);
        }
        // special case: we need to map these to controls of the gtk print dialog
        else if (bGtkInternal)
        {
            if ( aPropertyName == "PrintContent" )
            {
                // What to print? And, more importantly, is there a selection?
                impl_initPrintContent(aChoicesDisabled);
            }
        }
        else if (bIgnoreSubgroup || bIgnore)
            continue;
        else
        {
            // change handlers for all the controls set up in this block
            // should be set _after_ the control has been made (in)active,
            // because:
            // 1. value of the property is _known_--we are using it to
            //    _set_ the control, right?--no need to change it back .-)
            // 2. it may cause warning because the widget may not
            //    have been placed in m_aControlToPropertyMap yet

            GtkWidget* pWidget = nullptr;
            beans::PropertyValue* pVal = nullptr;
            if (aCtrlType == "Bool" && pCurParent)
            {
                pWidget = gtk_check_button_new_with_mnemonic(
                    OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
                lcl_setHelpText(pWidget, aHelpTexts, 0);
                m_aControlToPropertyMap[pWidget] = aPropertyName;

                bool bVal = false;
                pVal = m_rController.getValue(aPropertyName);
                if (pVal)
                    pVal->Value >>= bVal;
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bVal);
                gtk_widget_set_sensitive(pWidget,
                    m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
                g_signal_connect(pWidget, "toggled", G_CALLBACK(GtkPrintDialog::UIOption_CheckHdl), this);
            }
            else if (aCtrlType == "Radio" && pCurParent)
            {
                GtkWidget* const pVbox = gtk_vbox_new(FALSE, 12);
                gtk_container_set_border_width(GTK_CONTAINER(pVbox), 0);

                if (!aText.isEmpty())
                    pGroup = lcl_makeFrame(pVbox, aText, aHelpTexts, &nCurHelpText);

                sal_Int32 nSelectVal = 0;
                pVal = m_rController.getValue(aPropertyName);
                if (pVal && pVal->Value.hasValue())
                    pVal->Value >>= nSelectVal;

                for (sal_Int32 m = 0; m != aChoices.getLength(); m++)
                {
                    pWidget = gtk_radio_button_new_with_mnemonic_from_widget(
                        GTK_RADIO_BUTTON(m == 0 ? nullptr : pWidget),
                        OUStringToOString(aChoices[m].replace('~', '_'), RTL_TEXTENCODING_UTF8).getStr());
                    lcl_setHelpText(pWidget, aHelpTexts, nCurHelpText++);
                    m_aControlToPropertyMap[pWidget] = aPropertyName;
                    m_aControlToNumValMap[pWidget] = m;
                    GtkWidget* const pRow = gtk_hbox_new(FALSE, 12);
                    gtk_box_pack_start(GTK_BOX(pVbox), pRow, FALSE, FALSE, 0);
                    gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
                    aPropertyToDependencyRowMap[aPropertyName + OUString::number(m)] = pRow;
                    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), m == nSelectVal);
                    gtk_widget_set_sensitive(pWidget,
                        m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
                    g_signal_connect(pWidget, "toggled",
                            G_CALLBACK(GtkPrintDialog::UIOption_RadioHdl), this);
                }

                if (pGroup)
                    pWidget = pGroup;
                else
                    pWidget = pVbox;
            }
            else if ((aCtrlType == "List"   ||
                       aCtrlType == "Range"  ||
                       aCtrlType == "Edit"
                    ) && pCurParent)
            {
                GtkWidget* const pHbox = gtk_hbox_new(FALSE, 12);
                gtk_container_set_border_width(GTK_CONTAINER(pHbox), 0);

                if ( aCtrlType == "List" )
                {
                   pWidget = lcl_combo_box_text_new();

                   for (const auto& rChoice : std::as_const(aChoices))
                   {
                       lcl_combo_box_text_append(pWidget,
                           OUStringToOString(rChoice, RTL_TEXTENCODING_UTF8).getStr());
                   }

                   sal_Int32 nSelectVal = 0;
                   pVal = m_rController.getValue(aPropertyName);
                   if (pVal && pVal->Value.hasValue())
                       pVal->Value >>= nSelectVal;
                   gtk_combo_box_set_active(GTK_COMBO_BOX(pWidget), nSelectVal);
                   g_signal_connect(pWidget, "changed", G_CALLBACK(GtkPrintDialog::UIOption_SelectHdl), this);
                }
                else if (aCtrlType == "Edit" && pCurParent)
                {
                   pWidget = gtk_entry_new();

                   OUString aCurVal;
                   pVal = m_rController.getValue(aPropertyName);
                   if (pVal && pVal->Value.hasValue())
                       pVal->Value >>= aCurVal;
                   gtk_entry_set_text(GTK_ENTRY(pWidget),
                       OUStringToOString(aCurVal, RTL_TEXTENCODING_UTF8).getStr());
                }
                else if (aCtrlType == "Range" && pCurParent)
                {
                    pWidget = gtk_spin_button_new_with_range(nMinValue, nMaxValue, 1.0);

                    sal_Int64 nCurVal = 0;
                    pVal = m_rController.getValue(aPropertyName);
                    if (pVal && pVal->Value.hasValue())
                        pVal->Value >>= nCurVal;
                    gtk_spin_button_set_value(GTK_SPIN_BUTTON(pWidget), nCurVal);
                }

                lcl_setHelpText(pWidget, aHelpTexts, 0);
                m_aControlToPropertyMap[pWidget] = aPropertyName;

                gtk_widget_set_sensitive(pWidget,
                    m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);

                if (!aText.isEmpty())
                {
                    GtkWidget* const pLabel = gtk_label_new_with_mnemonic(
                        OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
                    gtk_label_set_mnemonic_widget(GTK_LABEL(pLabel), pWidget);
                    gtk_box_pack_start(GTK_BOX(pHbox), pLabel, FALSE, FALSE, 0);
                }

                gtk_box_pack_start(GTK_BOX(pHbox), pWidget, FALSE, FALSE, 0);

                pWidget = pHbox;

            }
            else
                SAL_INFO("vcl.gtk", "unhandled option type: " << aCtrlType);

            GtkWidget* pRow = nullptr;
            if (pWidget)
            {
                if (bUseDependencyRow && !aDependsOnName.isEmpty())
                {
                    pRow = aPropertyToDependencyRowMap[aDependsOnName + OUString::number(nDependsOnValue)];
                    if (!pRow)
                    {
                        gtk_widget_destroy(pWidget);
                        pWidget = nullptr;
                    }
                }
            }
            if (pWidget)
            {
                if (!pRow)
                {
                    pRow = gtk_hbox_new(FALSE, 12);
                    gtk_box_pack_start(GTK_BOX(pCurParent), pRow, FALSE, FALSE, 0);
                }
                if (!pGroup)
                    aPropertyToDependencyRowMap[aPropertyName + OUString::number(0)] = pRow;
                gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
            }
        }
    }

    CustomTabs_t::const_reverse_iterator aEnd = aCustomTabs.rend();
    for (CustomTabs_t::const_reverse_iterator aI = aCustomTabs.rbegin(); aI != aEnd; ++aI)
    {
        gtk_widget_show_all(aI->first);
        m_xWrapper->print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(m_pDialog), aI->first,
            gtk_label_new(OUStringToOString(aI->second, RTL_TEXTENCODING_UTF8).getStr()));
    }
}

void
GtkPrintDialog::impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled)
{
    SAL_WARN_IF(i_rDisabled.getLength() != 3, "vcl.gtk", "there is more choices than we expected");
    if (i_rDisabled.getLength() != 3)
        return;

    GtkPrintUnixDialog* const pDialog(GTK_PRINT_UNIX_DIALOG(m_pDialog));

    // XXX: This is a hack that depends on the number and the ordering of
    // the controls in the rDisabled sequence (cf. the initialization of
    // the "PrintContent" UI option in SwPrintUIOptions::SwPrintUIOptions,
    // sw/source/core/view/printdata.cxx)
    if (m_xWrapper->supportsPrintSelection() && !i_rDisabled[2])
    {
        m_xWrapper->print_unix_dialog_set_support_selection(pDialog, TRUE);
        m_xWrapper->print_unix_dialog_set_has_selection(pDialog, TRUE);
    }

    beans::PropertyValue* const pPrintContent(
            m_rController.getValue(OUString("PrintContent")));

    if (pPrintContent)
    {
        sal_Int32 nSelectionType(0);
        pPrintContent->Value >>= nSelectionType;
        GtkPrintSettings* const pSettings(getSettings());
        GtkPrintPages ePrintPages(GTK_PRINT_PAGES_ALL);
        switch (nSelectionType)
        {
            case 0:
                ePrintPages = GTK_PRINT_PAGES_ALL;
                break;
            case 1:
                ePrintPages = GTK_PRINT_PAGES_RANGES;
                break;
            case 2:
                if (m_xWrapper->supportsPrintSelection())
                    ePrintPages = GTK_PRINT_PAGES_SELECTION;
                else
                    SAL_INFO("vcl.gtk", "the application wants to print a selection, but the present gtk version does not support it");
                break;
            default:
                SAL_WARN("vcl.gtk", "unexpected selection type: " << nSelectionType);
        }
        m_xWrapper->print_settings_set_print_pages(pSettings, ePrintPages);
        m_xWrapper->print_unix_dialog_set_settings(pDialog, pSettings);
        g_object_unref(G_OBJECT(pSettings));
    }
}

void
GtkPrintDialog::impl_checkOptionalControlDependencies()
{
    for (auto& rEntry : m_aControlToPropertyMap)
    {
        gtk_widget_set_sensitive(rEntry.first, m_rController.isUIOptionEnabled(rEntry.second));
    }
}

beans::PropertyValue*
GtkPrintDialog::impl_queryPropertyValue(GtkWidget* const i_pWidget) const
{
    beans::PropertyValue* pVal(nullptr);
    std::map<GtkWidget*, OUString>::const_iterator aIt(m_aControlToPropertyMap.find(i_pWidget));
    if (aIt != m_aControlToPropertyMap.end())
    {
        pVal = m_rController.getValue(aIt->second);
        SAL_WARN_IF(!pVal, "vcl.gtk", "property value not found");
    }
    else
    {
        SAL_WARN("vcl.gtk", "changed control not in property map");
    }
    return pVal;
}

void
GtkPrintDialog::impl_UIOption_CheckHdl(GtkWidget* const i_pWidget)
{
    beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
    if (pVal)
    {
        const bool bVal = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget));
        pVal->Value <<= bVal;

        impl_checkOptionalControlDependencies();
    }
}

void
GtkPrintDialog::impl_UIOption_RadioHdl(GtkWidget* const i_pWidget)
{
    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget)))
    {
        beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
        std::map<GtkWidget*, sal_Int32>::const_iterator it = m_aControlToNumValMap.find(i_pWidget);
        if (pVal && it != m_aControlToNumValMap.end())
        {

            const sal_Int32 nVal = it->second;
            pVal->Value <<= nVal;

            impl_checkOptionalControlDependencies();
        }
    }
}

void
GtkPrintDialog::impl_UIOption_SelectHdl(GtkWidget* const i_pWidget)
{
    beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
    if (pVal)
    {
        const sal_Int32 nVal(gtk_combo_box_get_active(GTK_COMBO_BOX(i_pWidget)));
        pVal->Value <<= nVal;

        impl_checkOptionalControlDependencies();
    }
}

bool
GtkPrintDialog::run()
{
    bool bDoJob = false;
    bool bContinue = true;
    while (bContinue)
    {
        bContinue = false;
        const gint nStatus = gtk_dialog_run(GTK_DIALOG(m_pDialog));
        switch (nStatus)
        {
            case GTK_RESPONSE_HELP:
                fprintf(stderr, "To-Do: Help ?\n");
                bContinue = true;
                break;
            case GTK_RESPONSE_OK:
                bDoJob = true;
                break;
            default:
                break;
        }
    }
    gtk_widget_hide(m_pDialog);
    impl_storeToSettings();
    return bDoJob;
}

void
GtkPrintDialog::updateControllerPrintRange()
{
    GtkPrintSettings* const pSettings(getSettings());
    // TODO: use get_print_pages
    if (const gchar* const pStr = m_xWrapper->print_settings_get(pSettings, GTK_PRINT_SETTINGS_PRINT_PAGES))
    {
        beans::PropertyValue* pVal = m_rController.getValue(OUString("PrintRange"));
        if (!pVal)
            pVal = m_rController.getValue(OUString("PrintContent"));
        SAL_WARN_IF(!pVal, "vcl.gtk", "Nothing to map standard print options to!");
        if (pVal)
        {
            sal_Int32 nVal = 0;
            if (!strcmp(pStr, "all"))
                nVal = 0;
            else if (!strcmp(pStr, "ranges"))
                nVal = 1;
            else if (!strcmp(pStr, "selection"))
                nVal = 2;
            pVal->Value <<= nVal;

            if (nVal == 1)
            {
                pVal = m_rController.getValue(OUString("PageRange"));
                SAL_WARN_IF(!pVal, "vcl.gtk", "PageRange doesn't exist!");
                if (pVal)
                {
                    OUStringBuffer sBuf;
                    gint num_ranges;
                    const GtkPageRange* const pRanges = m_xWrapper->print_settings_get_page_ranges(pSettings, &num_ranges);
                    for (gint i = 0; i != num_ranges && pRanges; ++i)
                    {
                        sBuf.append(sal_Int32(pRanges[i].start+1));
                        if (pRanges[i].start != pRanges[i].end)
                        {
                            sBuf.append('-');
                            sBuf.append(sal_Int32(pRanges[i].end+1));
                        }

                        if (i != num_ranges-1)
                            sBuf.append(',');
                    }
                    pVal->Value <<= sBuf.makeStringAndClear();
                }
            }
        }
    }
    g_object_unref(G_OBJECT(pSettings));
}

GtkPrintDialog::~GtkPrintDialog()
{
    gtk_widget_destroy(m_pDialog);
}

void
GtkPrintDialog::impl_readFromSettings()
{
    vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
    GtkPrintSettings* const pSettings(getSettings());

    const OUString aPrintDialogStr("PrintDialog");
    const OUString aCopyCount(pItem->getValue(aPrintDialogStr,
                "CopyCount"));
    const OUString aCollate(pItem->getValue(aPrintDialogStr,
                "Collate"));

    const gint nOldCopyCount(m_xWrapper->print_settings_get_n_copies(pSettings));
    const sal_Int32 nCopyCount(aCopyCount.toInt32());
    if (nCopyCount > 0 && nOldCopyCount != nCopyCount)
    {
        m_xWrapper->print_settings_set_n_copies(pSettings, sal::static_int_cast<gint>(nCopyCount));
    }

    const bool bOldCollate(m_xWrapper->print_settings_get_collate(pSettings));
    const bool bCollate(aCollate.equalsIgnoreAsciiCase("true"));
    if (bOldCollate != bCollate)
    {
        m_xWrapper->print_settings_set_collate(pSettings, bCollate);
    }

    m_xWrapper->print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog), pSettings);
    g_object_unref(G_OBJECT(pSettings));
}

void
GtkPrintDialog::impl_storeToSettings()
const
{
    vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
    GtkPrintSettings* const pSettings(getSettings());

    const OUString aPrintDialogStr("PrintDialog");
    pItem->setValue(aPrintDialogStr,
            "CopyCount",
            OUString::number(m_xWrapper->print_settings_get_n_copies(pSettings)));
    pItem->setValue(aPrintDialogStr,
            "Collate",
            m_xWrapper->print_settings_get_collate(pSettings)
                ? OUString("true")
                : OUString("false"))
        ;
    // pItem->setValue(aPrintDialog, OUString("ToFile"), );
    g_object_unref(G_OBJECT(pSettings));
    pItem->Commit();
}

sal_uInt32
GtkSalInfoPrinter::GetCapabilities(
        const ImplJobSetup* const i_pSetupData,
        const PrinterCapType i_nType)
{
    if (i_nType == PrinterCapType::ExternalDialog && lcl_useSystemPrintDialog())
        return 1;
    return PspSalInfoPrinter::GetCapabilities(i_pSetupData, i_nType);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/xid_fullscreen_on_all_monitors.c b/vcl/unx/gtk/xid_fullscreen_on_all_monitors.c
deleted file mode 100644
index 558ede5..0000000
--- a/vcl/unx/gtk/xid_fullscreen_on_all_monitors.c
+++ /dev/null
@@ -1,101 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <dlfcn.h>
#include <stdlib.h>

typedef int Window;
typedef union  _GdkEvent GdkEvent;
typedef struct _GdkWindow GdkWindow;
typedef struct _GdkDisplay GdkDisplay;
typedef struct _GdkScreen GdkScreen;

typedef enum
{
    GDK_FULLSCREEN_ON_CURRENT_MONITOR,
    GDK_FULLSCREEN_ON_ALL_MONITORS
} GdkFullscreenMode;

int main(int argc, char *argv[])
{
    void *handle;
    void (*gtk_init)(int*, char***);
    GdkWindow* (*gdk_x11_window_foreign_new_for_display)(GdkDisplay*, Window);
    GdkDisplay* (*gdk_display_get_default)(void);
    GdkEvent* (*gdk_event_get)(void);
    void (*gtk_main_do_event)(GdkEvent*);
    void (*gdk_event_free)(GdkEvent*);
    void (*gdk_window_fullscreen)(GdkWindow *);
    void (*gdk_window_set_fullscreen_mode)(GdkWindow *, GdkFullscreenMode);

    GdkEvent *event;
    GdkWindow *window;
    int windowid;
    int spanmonitors;

    handle = dlopen("libgtk-3.so.0", RTLD_LAZY);
    if( NULL == handle )
        return -1;

    gtk_init = (void (*) (int*, char***))
        dlsym(handle, "gtk_init");
    gdk_x11_window_foreign_new_for_display = (GdkWindow* (*)(GdkDisplay*, Window))
        dlsym(handle, "gdk_x11_window_foreign_new_for_display");
    gdk_display_get_default = (GdkDisplay* (*)(void))
        dlsym(handle, "gdk_display_get_default");
    gdk_event_get = (GdkEvent* (*)(void))
        dlsym(handle, "gdk_event_get");
    gtk_main_do_event = (void (*)(GdkEvent*))
        dlsym(handle, "gtk_main_do_event");
    gdk_event_free = (void (*)(GdkEvent*))
        dlsym(handle, "gdk_event_free");
    gdk_window_fullscreen = (void (*)(GdkWindow *))
        dlsym(handle, "gdk_window_fullscreen");
    gdk_window_set_fullscreen_mode = (void (*)(GdkWindow *, GdkFullscreenMode))
        dlsym(handle, "gdk_window_set_fullscreen_mode");

    if (!gtk_init ||
        !gdk_x11_window_foreign_new_for_display ||
        !gdk_display_get_default ||
        !gdk_event_get ||
        !gtk_main_do_event ||
        !gdk_event_free ||
        !gdk_window_fullscreen ||
        !gdk_window_set_fullscreen_mode)
    {
        dlclose(handle);
        return -1;
    }

    gtk_init(&argc, &argv);

    windowid = atoi(argv[1]);
    spanmonitors = atoi(argv[2]);

    window = gdk_x11_window_foreign_new_for_display(gdk_display_get_default(), windowid);
    if (!window)
    {
        dlclose(handle);
        return -1;
    }

    gdk_window_set_fullscreen_mode(window, spanmonitors ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR);
    gdk_window_fullscreen(window);

    while ((event = gdk_event_get()) != NULL)
    {
        gtk_main_do_event(event);
        gdk_event_free(event);
    }

    dlclose(handle);
    return 0;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

diff --git a/vcl/unx/gtk/a11y/TODO b/vcl/unx/gtk3/a11y/TODO
similarity index 100%
rename from vcl/unx/gtk/a11y/TODO
rename to vcl/unx/gtk3/a11y/TODO
diff --git a/vcl/unx/gtk/a11y/atkfactory.hxx b/vcl/unx/gtk3/a11y/atkfactory.hxx
similarity index 100%
rename from vcl/unx/gtk/a11y/atkfactory.hxx
rename to vcl/unx/gtk3/a11y/atkfactory.hxx
diff --git a/vcl/unx/gtk/a11y/atklistener.hxx b/vcl/unx/gtk3/a11y/atklistener.hxx
similarity index 100%
rename from vcl/unx/gtk/a11y/atklistener.hxx
rename to vcl/unx/gtk3/a11y/atklistener.hxx
diff --git a/vcl/unx/gtk/a11y/atkregistry.hxx b/vcl/unx/gtk3/a11y/atkregistry.hxx
similarity index 100%
rename from vcl/unx/gtk/a11y/atkregistry.hxx
rename to vcl/unx/gtk3/a11y/atkregistry.hxx
diff --git a/vcl/unx/gtk/a11y/atktextattributes.hxx b/vcl/unx/gtk3/a11y/atktextattributes.hxx
similarity index 100%
rename from vcl/unx/gtk/a11y/atktextattributes.hxx
rename to vcl/unx/gtk3/a11y/atktextattributes.hxx
diff --git a/vcl/unx/gtk/a11y/atkutil.hxx b/vcl/unx/gtk3/a11y/atkutil.hxx
similarity index 100%
rename from vcl/unx/gtk/a11y/atkutil.hxx
rename to vcl/unx/gtk3/a11y/atkutil.hxx
diff --git a/vcl/unx/gtk/a11y/atkwindow.hxx b/vcl/unx/gtk3/a11y/atkwindow.hxx
similarity index 100%
rename from vcl/unx/gtk/a11y/atkwindow.hxx
rename to vcl/unx/gtk3/a11y/atkwindow.hxx
diff --git a/vcl/unx/gtk/a11y/atkwrapper.hxx b/vcl/unx/gtk3/a11y/atkwrapper.hxx
similarity index 97%
rename from vcl/unx/gtk/a11y/atkwrapper.hxx
rename to vcl/unx/gtk3/a11y/atkwrapper.hxx
index 8725e54..7ae0379 100644
--- a/vcl/unx/gtk/a11y/atkwrapper.hxx
+++ b/vcl/unx/gtk3/a11y/atkwrapper.hxx
@@ -22,9 +22,7 @@

#include <atk/atk.h>
#include <gtk/gtk.h>
#if GTK_CHECK_VERSION(3,0,0)
#include <gtk/gtk-a11y.h>
#endif
#include <com/sun/star/accessibility/XAccessible.hpp>

extern "C" {
@@ -74,11 +72,7 @@

struct AtkObjectWrapperClass
{
#if GTK_CHECK_VERSION(3,0,0)
    GtkWidgetAccessibleClass aParentClass;
#else
    AtkObjectClass const aParentClass;
#endif
};

GType                  atk_object_wrapper_get_type() G_GNUC_CONST;
diff --git a/vcl/unx/gtk3/a11y/gtk3atkaction.cxx b/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
index a3fa632..81e7432 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
@@ -5,8 +5,271 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkaction.cxx"
#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleAction.hpp>
#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>

#include <com/sun/star/awt/Key.hpp>
#include <com/sun/star/awt/KeyModifier.hpp>

#include <rtl/strbuf.hxx>
#include <algorithm>
#include <map>

using namespace ::com::sun::star;

// FIXME
static G_CONST_RETURN gchar *
getAsConst( const OString& rString )
{
    static const int nMax = 10;
    static OString aUgly[nMax];
    static int nIdx = 0;
    nIdx = (nIdx + 1) % nMax;
    aUgly[nIdx] = rString;
    return aUgly[ nIdx ].getStr();
}

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleAction>
        getAction( AtkAction *action )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( action );

    if( pWrap )
    {
        if( !pWrap->mpAction.is() )
        {
            pWrap->mpAction.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpAction;
    }

    return css::uno::Reference<css::accessibility::XAccessibleAction>();
}

extern "C" {

static gboolean
action_wrapper_do_action (AtkAction *action,
                          gint       i)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
            return pAction->doAccessibleAction( i );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in doAccessibleAction()" );
    }

    return FALSE;
}

static gint
action_wrapper_get_n_actions (AtkAction *action)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
            return pAction->getAccessibleActionCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleActionCount()" );
    }

    return 0;
}

static G_CONST_RETURN gchar *
action_wrapper_get_description (AtkAction *, gint)
{
    // GAIL implement this only for cells
    g_warning( "Not implemented: get_description()" );
    return "";
}

static G_CONST_RETURN gchar *
action_wrapper_get_localized_name (AtkAction *, gint)
{
    // GAIL doesn't implement this as well
    g_warning( "Not implemented: get_localized_name()" );
    return "";
}

#define ACTION_NAME_PAIR( OOoName, AtkName ) \
    std::pair< const OUString, const gchar * > ( OUString( OOoName ), AtkName )

static G_CONST_RETURN gchar *
action_wrapper_get_name (AtkAction *action,
                         gint       i)
{
    static std::map< OUString, const gchar * > aNameMap;

    if( aNameMap.empty() )
    {
        aNameMap.insert( ACTION_NAME_PAIR( "click", "click" ) );
        aNameMap.insert( ACTION_NAME_PAIR( "select", "click" ) );
        aNameMap.insert( ACTION_NAME_PAIR( "togglePopup", "push" ) );
    }

    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
        {
            std::map< OUString, const gchar * >::iterator iter;

            OUString aDesc( pAction->getAccessibleActionDescription( i ) );

            iter = aNameMap.find( aDesc );
            if( iter != aNameMap.end() )
                return iter->second;

            std::pair< const OUString, const gchar * > aNewVal( aDesc,
                g_strdup( OUStringToConstGChar(aDesc) ) );

            if( aNameMap.insert( aNewVal ).second )
                return aNewVal.second;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleActionDescription()" );
    }

    return "";
}

/*
*  GNOME Expects a string in the format:
*
*  <mnemonic>;<full-path>;<accelerator>
*
*  The keybindings in <full-path> should be separated by ":"
*/

static void
appendKeyStrokes(OStringBuffer& rBuffer, const uno::Sequence< awt::KeyStroke >& rKeyStrokes)
{
    for( const auto& rKeyStroke : rKeyStrokes )
    {
        if( rKeyStroke.Modifiers &  awt::KeyModifier::SHIFT )
            rBuffer.append("<Shift>");
        if( rKeyStroke.Modifiers &  awt::KeyModifier::MOD1 )
            rBuffer.append("<Control>");
        if( rKeyStroke.Modifiers &  awt::KeyModifier::MOD2 )
            rBuffer.append("<Alt>");

        if( ( rKeyStroke.KeyCode >= awt::Key::A ) && ( rKeyStroke.KeyCode <= awt::Key::Z ) )
            rBuffer.append( static_cast<sal_Char>( 'a' + ( rKeyStroke.KeyCode - awt::Key::A ) ) );
        else
        {
            sal_Char c = '\0';

            switch( rKeyStroke.KeyCode )
            {
                case awt::Key::TAB:      c = '\t'; break;
                case awt::Key::SPACE:    c = ' '; break;
                case awt::Key::ADD:      c = '+'; break;
                case awt::Key::SUBTRACT: c = '-'; break;
                case awt::Key::MULTIPLY: c = '*'; break;
                case awt::Key::DIVIDE:   c = '/'; break;
                case awt::Key::POINT:    c = '.'; break;
                case awt::Key::COMMA:    c = ','; break;
                case awt::Key::LESS:     c = '<'; break;
                case awt::Key::GREATER:  c = '>'; break;
                case awt::Key::EQUAL:    c = '='; break;
                case 0:
                    break;
                default:
                    g_warning( "Unmapped KeyCode: %d", rKeyStroke.KeyCode );
                    break;
            }

            if( c != '\0' )
                rBuffer.append( c );
            else
            {
                // The KeyCode approach did not work, probably a non ascii character
                // let's hope that there is a character given in KeyChar.
                rBuffer.append( OUStringToGChar( OUString( rKeyStroke.KeyChar ) ) );
            }
        }
    }
}

static G_CONST_RETURN gchar *
action_wrapper_get_keybinding (AtkAction *action,
                               gint       i)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleAction> pAction
            = getAction( action );
        if( pAction.is() )
        {
            uno::Reference< accessibility::XAccessibleKeyBinding > xBinding( pAction->getAccessibleActionKeyBinding( i ));

            if( xBinding.is() )
            {
                OStringBuffer aRet;

                sal_Int32 nmax = std::min( xBinding->getAccessibleKeyBindingCount(), sal_Int32(3) );
                for( sal_Int32 n = 0; n < nmax; n++ )
                {
                    appendKeyStrokes( aRet,  xBinding->getAccessibleKeyBinding( n ) );

                    if( n < 2 )
                        aRet.append( ';' );
                }

                // !! FIXME !! remember keystroke in wrapper object ?
                return getAsConst( aRet.makeStringAndClear() );
            }
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in get_keybinding()" );
    }

    return "";
}

static gboolean
action_wrapper_set_description (AtkAction *, gint, const gchar *)
{
    return FALSE;
}

} // extern "C"

void
actionIfaceInit (AtkActionIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->do_action = action_wrapper_do_action;
  iface->get_n_actions = action_wrapper_get_n_actions;
  iface->get_description = action_wrapper_get_description;
  iface->get_keybinding = action_wrapper_get_keybinding;
  iface->get_name = action_wrapper_get_name;
  iface->get_localized_name = action_wrapper_get_localized_name;
  iface->set_description = action_wrapper_set_description;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx b/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
index d8e0879..0e2781c 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
@@ -5,8 +5,33 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkbridge.cxx"
#include <unx/gtk/atkbridge.hxx>
#include <unx/gtk/gtkframe.hxx>

#include "atkfactory.hxx"
#include "atkutil.hxx"
#include "atkwindow.hxx"

bool InitAtkBridge()
{
    ooo_atk_util_ensure_event_listener();
    return true;
}

void DeInitAtkBridge()
{
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx b/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
index e4eabec..e904b12 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
@@ -5,8 +5,379 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkcomponent.cxx"
#include "atkwrapper.hxx"
#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
#include <gtk/gtk.h>

using namespace ::com::sun::star;

static AtkObjectWrapper* getObjectWrapper(AtkComponent *pComponent)
{
    AtkObjectWrapper *pWrap = nullptr;
    if (ATK_IS_OBJECT_WRAPPER(pComponent))
        pWrap = ATK_OBJECT_WRAPPER(pComponent);
    else if (GTK_IS_DRAWING_AREA(pComponent)) //when using a GtkDrawingArea as a custom widget in welded gtk3
    {
        GtkWidget* pDrawingArea = GTK_WIDGET(pComponent);
        AtkObject* pAtkObject = gtk_widget_get_accessible(pDrawingArea);
        pWrap = ATK_IS_OBJECT_WRAPPER(pAtkObject) ? ATK_OBJECT_WRAPPER(pAtkObject) : nullptr;
    }
    return pWrap;
}

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleComponent>
    getComponent(AtkObjectWrapper *pWrap)
{
    if (pWrap)
    {
        if (!pWrap->mpComponent.is())
            pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
        return pWrap->mpComponent;
    }

    return css::uno::Reference<css::accessibility::XAccessibleComponent>();
}

/*****************************************************************************/

static awt::Point
translatePoint( css::uno::Reference<accessibility::XAccessibleComponent> const & pComponent,
                gint x, gint y, AtkCoordType t)
{
    awt::Point aOrigin( 0, 0 );
    if( t == ATK_XY_SCREEN )
        aOrigin = pComponent->getLocationOnScreen();
    return awt::Point( x - aOrigin.X, y - aOrigin.Y );
}

/*****************************************************************************/

extern "C" {

static gboolean
component_wrapper_grab_focus (AtkComponent *component)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
        return atk_component_grab_focus(ATK_COMPONENT(obj->mpOrig));

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
        {
            pComponent->grabFocus();
            return TRUE;
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in grabFocus()" );
    }

    return FALSE;
}

/*****************************************************************************/

static gboolean
component_wrapper_contains (AtkComponent *component,
                            gint          x,
                            gint          y,
                            AtkCoordType  coord_type)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
        return atk_component_contains(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
            return pComponent->containsPoint( translatePoint( pComponent, x, y, coord_type ) );
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in containsPoint()" );
    }

    return FALSE;
}

/*****************************************************************************/

static AtkObject *
component_wrapper_ref_accessible_at_point (AtkComponent *component,
                                           gint          x,
                                           gint          y,
                                           AtkCoordType  coord_type)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
        return atk_component_ref_accessible_at_point(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);

        if( pComponent.is() )
        {
            uno::Reference< accessibility::XAccessible > xAccessible = pComponent->getAccessibleAtPoint(
                translatePoint( pComponent, x, y, coord_type ) );
            return atk_object_wrapper_ref( xAccessible );
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in getAccessibleAtPoint()" );
    }

    return nullptr;
}

/*****************************************************************************/

static void
component_wrapper_get_position (AtkComponent   *component,
                                gint           *x,
                                gint           *y,
                                AtkCoordType   coord_type)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
    {
        atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), x, y, nullptr, nullptr, coord_type);
        return;
    }

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
        {
            awt::Point aPos;

            if( coord_type == ATK_XY_SCREEN )
                aPos = pComponent->getLocationOnScreen();
            else
                aPos = pComponent->getLocation();

            *x = aPos.X;
            *y = aPos.Y;
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in getLocation[OnScreen]()" );
    }
}

/*****************************************************************************/

static void
component_wrapper_get_size (AtkComponent   *component,
                            gint           *width,
                            gint           *height)
{
    AtkObjectWrapper* obj = getObjectWrapper(component);
    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj && obj->mpOrig)
    {
        atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), nullptr, nullptr, width, height, ATK_XY_WINDOW);
        return;
    }

    try
    {
        css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
            = getComponent(obj);
        if( pComponent.is() )
        {
            awt::Size aSize = pComponent->getSize();
            *width = aSize.Width;
            *height = aSize.Height;
        }
    }
    catch( const uno::Exception & )
    {
        g_warning( "Exception in getSize()" );
    }
}

/*****************************************************************************/

static void
component_wrapper_get_extents (AtkComponent *component,
                               gint         *x,
                               gint         *y,
                               gint         *width,
                               gint         *height,
                               AtkCoordType  coord_type)
{
    component_wrapper_get_position( component, x, y, coord_type );
    component_wrapper_get_size( component, width, height );
}

/*****************************************************************************/

static gboolean
component_wrapper_set_extents (AtkComponent *, gint, gint, gint, gint, AtkCoordType)
{
    g_warning( "AtkComponent::set_extents unimplementable" );
    return FALSE;
}

/*****************************************************************************/

static gboolean
component_wrapper_set_position (AtkComponent *, gint, gint, AtkCoordType)
{
    g_warning( "AtkComponent::set_position unimplementable" );
    return FALSE;
}

/*****************************************************************************/

static gboolean
component_wrapper_set_size (AtkComponent *, gint, gint)
{
    g_warning( "AtkComponent::set_size unimplementable" );
    return FALSE;
}

/*****************************************************************************/

static AtkLayer
component_wrapper_get_layer (AtkComponent   *component)
{
    AtkRole role = atk_object_get_role( ATK_OBJECT( component ) );
    AtkLayer layer = ATK_LAYER_WIDGET;

    switch (role)
    {
        case ATK_ROLE_POPUP_MENU:
        case ATK_ROLE_MENU_ITEM:
        case ATK_ROLE_CHECK_MENU_ITEM:
        case ATK_ROLE_SEPARATOR:
        case ATK_ROLE_LIST_ITEM:
            layer = ATK_LAYER_POPUP;
            break;
        case ATK_ROLE_MENU:
            {
                AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
                if( atk_object_get_role( parent ) != ATK_ROLE_MENU_BAR )
                    layer = ATK_LAYER_POPUP;
            }
            break;

        case ATK_ROLE_LIST:
            {
                AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
                if( atk_object_get_role( parent ) == ATK_ROLE_COMBO_BOX )
                    layer = ATK_LAYER_POPUP;
            }
            break;

        default:
            ;
    }

    return layer;
}

/*****************************************************************************/

static gint
component_wrapper_get_mdi_zorder (AtkComponent   *)
{
    // only needed for ATK_LAYER_MDI (not used) or ATK_LAYER_WINDOW (inherited from GAIL)
    return G_MININT;
}

/*****************************************************************************/

// This code is mostly stolen from libgail ..

static guint
component_wrapper_add_focus_handler (AtkComponent    *component,
                                     AtkFocusHandler  handler)
{
    GSignalMatchType match_type;
    gulong ret;
    guint signal_id;

    match_type = GSignalMatchType(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC);
    signal_id = g_signal_lookup( "focus-event", ATK_TYPE_OBJECT );

    ret = g_signal_handler_find( component, match_type, signal_id, 0, nullptr,
                                 static_cast<gpointer>(&handler), nullptr);
    if (!ret)
    {
        return g_signal_connect_closure_by_id (component,
                                               signal_id, 0,
                                               g_cclosure_new (
                                               G_CALLBACK (handler), nullptr,
                                               nullptr),
                                               FALSE);
    }
    else
    {
        return 0;
    }
}

/*****************************************************************************/

static void
component_wrapper_remove_focus_handler (AtkComponent  *component,
                                        guint         handler_id)
{
    g_signal_handler_disconnect (component, handler_id);
}

/*****************************************************************************/

} // extern "C"

void
componentIfaceInit (AtkComponentIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->add_focus_handler = component_wrapper_add_focus_handler;
  iface->contains = component_wrapper_contains;
  iface->get_extents = component_wrapper_get_extents;
  iface->get_layer = component_wrapper_get_layer;
  iface->get_mdi_zorder = component_wrapper_get_mdi_zorder;
  iface->get_position = component_wrapper_get_position;
  iface->get_size = component_wrapper_get_size;
  iface->grab_focus = component_wrapper_grab_focus;
  iface->ref_accessible_at_point = component_wrapper_ref_accessible_at_point;
  iface->remove_focus_handler = component_wrapper_remove_focus_handler;
  iface->set_extents = component_wrapper_set_extents;
  iface->set_position = component_wrapper_set_position;
  iface->set_size = component_wrapper_set_size;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx b/vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx
index ea3f089..49d3eb9 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx
@@ -5,8 +5,190 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkeditabletext.cxx"
#include "atkwrapper.hxx"
#include "atktextattributes.hxx"

#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
#include <com/sun/star/accessibility/TextSegment.hpp>

#include <string.h>

using namespace ::com::sun::star;

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleEditableText>
    getEditableText( AtkEditableText *pEditableText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pEditableText );
    if( pWrap )
    {
        if( !pWrap->mpEditableText.is() )
        {
            pWrap->mpEditableText.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpEditableText;
    }

    return css::uno::Reference<css::accessibility::XAccessibleEditableText>();
}

/*****************************************************************************/

extern "C" {

static gboolean
editable_text_wrapper_set_run_attributes( AtkEditableText  *text,
                                          AtkAttributeSet  *attribute_set,
                                          gint              nStartOffset,
                                          gint              nEndOffset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
        {
            uno::Sequence< beans::PropertyValue > aAttributeList;

            if( attribute_set_map_to_property_values( attribute_set, aAttributeList ) )
                return pEditableText->setAttributes(nStartOffset, nEndOffset, aAttributeList);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setAttributes()" );
    }

    return FALSE;
}

static void
editable_text_wrapper_set_text_contents( AtkEditableText  *text,
                                         const gchar      *string )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
        {
            OUString aString ( string, strlen(string), RTL_TEXTENCODING_UTF8 );
            pEditableText->setText( aString );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setText()" );
    }
}

static void
editable_text_wrapper_insert_text( AtkEditableText  *text,
                                   const gchar      *string,
                                   gint             length,
                                   gint             *pos )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
        {
            OUString aString ( string, length, RTL_TEXTENCODING_UTF8 );
            if( pEditableText->insertText( aString, *pos ) )
                *pos += length;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in insertText()" );
    }
}

static void
editable_text_wrapper_cut_text( AtkEditableText  *text,
                                gint             start,
                                gint             end )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->cutText( start, end );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in cutText()" );
    }
}

static void
editable_text_wrapper_delete_text( AtkEditableText  *text,
                                   gint             start,
                                   gint             end )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->deleteText( start, end );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in deleteText()" );
    }
}

static void
editable_text_wrapper_paste_text( AtkEditableText  *text,
                                  gint             pos )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->pasteText( pos );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in pasteText()" );
    }
}

static void
editable_text_wrapper_copy_text( AtkEditableText  *text,
                                 gint             start,
                                 gint             end )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleEditableText>
            pEditableText = getEditableText( text );
        if( pEditableText.is() )
            pEditableText->copyText( start, end );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in copyText()" );
    }
}

} // extern "C"

void
editableTextIfaceInit (AtkEditableTextIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->set_text_contents = editable_text_wrapper_set_text_contents;
  iface->insert_text = editable_text_wrapper_insert_text;
  iface->copy_text = editable_text_wrapper_copy_text;
  iface->cut_text = editable_text_wrapper_cut_text;
  iface->delete_text = editable_text_wrapper_delete_text;
  iface->paste_text = editable_text_wrapper_paste_text;
  iface->set_run_attributes = editable_text_wrapper_set_run_attributes;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkfactory.cxx b/vcl/unx/gtk3/a11y/gtk3atkfactory.cxx
index c60db2f..d7c8bf9 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkfactory.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkfactory.cxx
@@ -5,8 +5,182 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkfactory.cxx"
#include <unx/gtk/gtkframe.hxx>
#include <vcl/window.hxx>
#include "atkwrapper.hxx"
#include "atkfactory.hxx"
#include "atkregistry.hxx"

using namespace ::com::sun::star;

extern "C" {

/*
 *  Instances of this dummy object class are returned whenever we have to
 *  create an AtkObject, but can't touch the OOo object anymore since it
 *  is already disposed.
 */

static AtkStateSet *
noop_wrapper_ref_state_set( AtkObject * )
{
    AtkStateSet *state_set = atk_state_set_new();
    atk_state_set_add_state( state_set, ATK_STATE_DEFUNCT );
    return state_set;
}

static void
atk_noop_object_wrapper_class_init(AtkNoOpObjectClass *klass)
{
    AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );
    atk_class->ref_state_set = noop_wrapper_ref_state_set;
}

static GType
atk_noop_object_wrapper_get_type()
{
    static GType type = 0;

    if (!type)
    {
        static const GTypeInfo typeInfo =
        {
            sizeof (AtkNoOpObjectClass),
            nullptr,
            nullptr,
            reinterpret_cast<GClassInitFunc>(atk_noop_object_wrapper_class_init),
            nullptr,
            nullptr,
            sizeof (AtkObjectWrapper),
            0,
            nullptr,
            nullptr
        } ;

        type = g_type_register_static (ATK_TYPE_OBJECT, "OOoAtkNoOpObj", &typeInfo, GTypeFlags(0)) ;
    }
    return type;
}

static AtkObject*
atk_noop_object_wrapper_new()
{
  AtkObject *accessible;

  accessible = static_cast<AtkObject *>(g_object_new (atk_noop_object_wrapper_get_type(), nullptr));
  g_return_val_if_fail (accessible != nullptr, nullptr);

  accessible->role = ATK_ROLE_INVALID;
  accessible->layer = ATK_LAYER_INVALID;

  return accessible;
}

/*
 * The wrapper factory
 */

static GType
wrapper_factory_get_accessible_type()
{
  return atk_object_wrapper_get_type();
}

static AtkObject*
wrapper_factory_create_accessible( GObject *obj )
{
    GtkWidget* pEventBox = gtk_widget_get_parent(GTK_WIDGET(obj));

    // gail_container_real_remove_gtk tries to re-instantiate an accessible
    // for a widget that is about to vanish ..
    if (!pEventBox)
        return atk_noop_object_wrapper_new();

    GtkWidget* pTopLevelGrid = gtk_widget_get_parent(pEventBox);
    if (!pTopLevelGrid)
        return atk_noop_object_wrapper_new();

    GtkWidget* pTopLevel = gtk_widget_get_parent(pTopLevelGrid);
    if (!pTopLevel)
        return atk_noop_object_wrapper_new();

    GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(GTK_WINDOW(pTopLevel));
    g_return_val_if_fail( pFrame != nullptr, nullptr );

    vcl::Window* pFrameWindow = pFrame->GetWindow();
    if( pFrameWindow )
    {
        vcl::Window* pWindow = pFrameWindow;

        // skip accessible objects already exposed by the frame objects
        if( WindowType::BORDERWINDOW == pWindow->GetType() )
            pWindow = pFrameWindow->GetAccessibleChildWindow(0);

        if( pWindow )
        {
            uno::Reference< accessibility::XAccessible > xAccessible = pWindow->GetAccessible();
            if( xAccessible.is() )
            {
                AtkObject *accessible = ooo_wrapper_registry_get( xAccessible );

                if( accessible )
                    g_object_ref( G_OBJECT(accessible) );
                else
                    accessible = atk_object_wrapper_new( xAccessible, gtk_widget_get_accessible(pTopLevel) );

                return accessible;
            }
        }
    }

    return nullptr;
}

AtkObject* ooo_fixed_get_accessible(GtkWidget *obj)
{
    return wrapper_factory_create_accessible(G_OBJECT(obj));
}

static void
wrapper_factory_class_init( AtkObjectFactoryClass *klass )
{
  klass->create_accessible   = wrapper_factory_create_accessible;
  klass->get_accessible_type = wrapper_factory_get_accessible_type;
}

GType
wrapper_factory_get_type()
{
  static GType t = 0;

  if (!t) {
    static const GTypeInfo tinfo =
    {
      sizeof (AtkObjectFactoryClass),
      nullptr, nullptr, reinterpret_cast<GClassInitFunc>(wrapper_factory_class_init),
      nullptr, nullptr, sizeof (AtkObjectFactory), 0, nullptr, nullptr
    };

    t = g_type_register_static (
        ATK_TYPE_OBJECT_FACTORY, "OOoAtkObjectWrapperFactory",
        &tinfo, GTypeFlags(0));
  }

  return t;
}

} // extern C

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx b/vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx
index bb9749c..ab94126 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx
@@ -5,8 +5,269 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkhypertext.cxx"
#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>

using namespace ::com::sun::star;

// ---------------------- AtkHyperlink ----------------------

struct HyperLink {
    AtkHyperlink const atk_hyper_link;

    uno::Reference< accessibility::XAccessibleHyperlink > xLink;
};

static uno::Reference< accessibility::XAccessibleHyperlink > const &
    getHyperlink( AtkHyperlink *pHyperlink )
{
    HyperLink *pLink = reinterpret_cast<HyperLink *>(pHyperlink);
    return pLink->xLink;
}

static GObjectClass *hyper_parent_class = nullptr;

extern "C" {

static void
hyper_link_finalize (GObject *obj)
{
    HyperLink *hl = reinterpret_cast<HyperLink *>(obj);
    hl->xLink.clear();
    hyper_parent_class->finalize (obj);
}

static gchar *
hyper_link_get_uri( AtkHyperlink *pLink,
                    gint          i )
{
    try {
        uno::Any aAny = getHyperlink( pLink )->getAccessibleActionObject( i );
        OUString aUri = aAny.get< OUString > ();
        return OUStringToGChar(aUri);
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in hyper_link_get_uri" );
    }
    return nullptr;
}

static AtkObject *
hyper_link_get_object( AtkHyperlink *,
                       gint          )
{
    g_warning( "FIXME: hyper_link_get_object unimplemented" );
    return nullptr;
}
static gint
hyper_link_get_end_index( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->getEndIndex();
    }
    catch(const uno::Exception&) {
    }
    return -1;
}
static gint
hyper_link_get_start_index( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->getStartIndex();
    }
    catch(const uno::Exception&) {
    }
    return -1;
}
static gboolean
hyper_link_is_valid( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->isValid();
    }
    catch(const uno::Exception&) {
    }
    return FALSE;
}
static gint
hyper_link_get_n_anchors( AtkHyperlink *pLink )
{
    try {
        return getHyperlink( pLink )->getAccessibleActionCount();
    }
    catch(const uno::Exception&) {
    }
    return 0;
}

static guint
hyper_link_link_state( AtkHyperlink * )
{
    g_warning( "FIXME: hyper_link_link_state unimplemented" );
    return 0;
}
static gboolean
hyper_link_is_selected_link( AtkHyperlink * )
{
    g_warning( "FIXME: hyper_link_is_selected_link unimplemented" );
    return FALSE;
}

static void
hyper_link_class_init (AtkHyperlinkClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = hyper_link_finalize;

    hyper_parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));

    klass->get_uri = hyper_link_get_uri;
    klass->get_object = hyper_link_get_object;
    klass->get_end_index = hyper_link_get_end_index;
    klass->get_start_index = hyper_link_get_start_index;
    klass->is_valid = hyper_link_is_valid;
    klass->get_n_anchors = hyper_link_get_n_anchors;
    klass->link_state = hyper_link_link_state;
    klass->is_selected_link = hyper_link_is_selected_link;
}

static GType
hyper_link_get_type()
{
    static GType type = 0;

    if (!type) {
        static const GTypeInfo tinfo = {
            sizeof (AtkHyperlinkClass),
            nullptr,               /* base init */
            nullptr,               /* base finalize */
            reinterpret_cast<GClassInitFunc>(hyper_link_class_init),
            nullptr,               /* class finalize */
            nullptr,               /* class data */
            sizeof (HyperLink), /* instance size */
            0,                  /* nb preallocs */
            nullptr,               /* instance init */
            nullptr                /* value table */
        };

        static const GInterfaceInfo atk_action_info = {
            reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
            nullptr,
            nullptr
        };

        type = g_type_register_static (ATK_TYPE_HYPERLINK,
                                       "OOoAtkObjHyperLink", &tinfo,
                                       GTypeFlags(0));
        g_type_add_interface_static (type, ATK_TYPE_ACTION,
                                     &atk_action_info);
    }

    return type;
}

// ---------------------- AtkHyperText ----------------------

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleHypertext>
    getHypertext( AtkHypertext *pHypertext )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pHypertext );
    if( pWrap )
    {
        if( !pWrap->mpHypertext.is() )
        {
            pWrap->mpHypertext.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpHypertext;
    }

    return css::uno::Reference<css::accessibility::XAccessibleHypertext>();
}

static AtkHyperlink *
hypertext_get_link( AtkHypertext *hypertext,
                    gint          link_index)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
            = getHypertext( hypertext );
        if( pHypertext.is() )
        {
            HyperLink *pLink = static_cast<HyperLink *>(g_object_new( hyper_link_get_type(), nullptr ));
            pLink->xLink = pHypertext->getHyperLink( link_index );
            if( !pLink->xLink.is() ) {
                g_object_unref( G_OBJECT( pLink ) );
                pLink = nullptr;
            }
            return ATK_HYPERLINK( pLink );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getHyperLink()" );
    }

    return nullptr;
}

static gint
hypertext_get_n_links( AtkHypertext *hypertext )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
            = getHypertext( hypertext );
        if( pHypertext.is() )
            return pHypertext->getHyperLinkCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getHyperLinkCount()" );
    }

    return 0;
}

static gint
hypertext_get_link_index( AtkHypertext *hypertext,
                          gint          index)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
            = getHypertext( hypertext );
        if( pHypertext.is() )
            return pHypertext->getHyperLinkIndex( index );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getHyperLinkIndex()" );
    }

    return 0;
}

} // extern "C"

void
hypertextIfaceInit (AtkHypertextIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->get_link = hypertext_get_link;
  iface->get_n_links = hypertext_get_n_links;
  iface->get_link_index = hypertext_get_link_index;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkimage.cxx b/vcl/unx/gtk3/a11y/gtk3atkimage.cxx
index 4e2c77e..1c9bc2c 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkimage.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkimage.cxx
@@ -5,8 +5,128 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkimage.cxx"
#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleImage.hpp>

using namespace ::com::sun::star;

// FIXME
static G_CONST_RETURN gchar *
getAsConst( const OUString& rString )
{
    static const int nMax = 10;
    static OString aUgly[nMax];
    static int nIdx = 0;
    nIdx = (nIdx + 1) % nMax;
    aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
    return aUgly[ nIdx ].getStr();
}

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleImage>
    getImage( AtkImage *pImage )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pImage );
    if( pWrap )
    {
        if( !pWrap->mpImage.is() )
        {
            pWrap->mpImage.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpImage;
    }

    return css::uno::Reference<css::accessibility::XAccessibleImage>();
}

extern "C" {

static G_CONST_RETURN gchar *
image_get_image_description( AtkImage *image )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleImage> pImage
            = getImage( image );
        if( pImage.is() )
            return getAsConst( pImage->getAccessibleImageDescription() );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleImageDescription()" );
    }

    return nullptr;
}

static void
image_get_image_position( AtkImage     *image,
                          gint         *x,
                          gint         *y,
                          AtkCoordType  coord_type )
{
    *x = *y = 0;
    if( ATK_IS_COMPONENT( image ) )
    {
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
        atk_component_get_position( ATK_COMPONENT( image ), x, y, coord_type );
        SAL_WNODEPRECATED_DECLARATIONS_POP
    }
    else
        g_warning( "FIXME: no image position information" );
}

static void
image_get_image_size( AtkImage *image,
                      gint     *width,
                      gint     *height )
{
    *width = 0;
    *height = 0;
    try {
        css::uno::Reference<css::accessibility::XAccessibleImage> pImage
            = getImage( image );
        if( pImage.is() )
        {
            *width = pImage->getAccessibleImageWidth();
            *height = pImage->getAccessibleImageHeight();
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleImageHeight() or Width" );
    }
}

static gboolean
image_set_image_description( AtkImage *, const gchar * )
{
    g_warning ("FIXME: no set image description");
    return FALSE;
}

} // extern "C"

void
imageIfaceInit (AtkImageIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->set_image_description = image_set_image_description;
  iface->get_image_description = image_get_image_description;
  iface->get_image_position = image_get_image_position;
  iface->get_image_size = image_get_image_size;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atklistener.cxx b/vcl/unx/gtk3/a11y/gtk3atklistener.cxx
index eca1cd7..c30bc63 100644
--- a/vcl/unx/gtk3/a11y/gtk3atklistener.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atklistener.cxx
@@ -5,8 +5,735 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atklistener.cxx"
#ifdef AIX
#define _LINUX_SOURCE_COMPAT
#include <sys/timer.h>
#undef _LINUX_SOURCE_COMPAT
#endif

#include <com/sun/star/accessibility/TextSegment.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>

#include "atklistener.hxx"
#include "atkwrapper.hxx"
#include <vcl/svapp.hxx>

#include <rtl/ref.hxx>
#include <sal/log.hxx>

#define DEBUG_ATK_LISTENER 0

#if DEBUG_ATK_LISTENER
#include <iostream>
#include <sstream>
#endif

using namespace com::sun::star;

AtkListener::AtkListener( AtkObjectWrapper* pWrapper ) : mpWrapper( pWrapper )
{
    if( mpWrapper )
    {
        g_object_ref( mpWrapper );
        updateChildList( mpWrapper->mpContext );
    }
}

AtkListener::~AtkListener()
{
    if( mpWrapper )
        g_object_unref( mpWrapper );
}

/*****************************************************************************/

static AtkStateType mapState( const uno::Any &rAny )
{
    sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
    rAny >>= nState;
    return mapAtkState( nState );
}

/*****************************************************************************/

extern "C" {
    // rhbz#1001768 - down to horrific problems releasing the solar mutex
    // while destroying a Window - which occurs inside these notifications.
    static gboolean
    idle_defunc_state_change( AtkObject *atk_obj )
    {
        SolarMutexGuard aGuard;

        // This is an equivalent to a state change to DEFUNC(T).
        atk_object_notify_state_change( atk_obj, ATK_STATE_DEFUNCT, TRUE );
        if( atk_get_focus_object() == atk_obj )
        {
            SAL_WNODEPRECATED_DECLARATIONS_PUSH
            atk_focus_tracker_notify( nullptr );
            SAL_WNODEPRECATED_DECLARATIONS_POP
        }
        g_object_unref( G_OBJECT( atk_obj ) );
        return false;
    }
}

// XEventListener implementation
void AtkListener::disposing( const lang::EventObject& )
{
    if( mpWrapper )
    {
        AtkObject *atk_obj = ATK_OBJECT( mpWrapper );

        // Release all interface references to avoid shutdown problems with
        // global mutex
        atk_object_wrapper_dispose( mpWrapper );

        g_idle_add( reinterpret_cast<GSourceFunc>(idle_defunc_state_change),
                    g_object_ref( G_OBJECT( atk_obj ) ) );

        // Release the wrapper object so that it can vanish ..
        g_object_unref( mpWrapper );
        mpWrapper = nullptr;
    }
}

/*****************************************************************************/

static AtkObject *getObjFromAny( const uno::Any &rAny )
{
    uno::Reference< accessibility::XAccessible > xAccessible;
    rAny >>= xAccessible;
    return xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
}

/*****************************************************************************/

// Updates the child list held to provide the old IndexInParent on children_changed::remove
void AtkListener::updateChildList(
    css::uno::Reference<css::accessibility::XAccessibleContext> const &
        pContext)
{
     m_aChildList.clear();

     uno::Reference< accessibility::XAccessibleStateSet > xStateSet = pContext->getAccessibleStateSet();
     if( xStateSet.is()
         && !xStateSet->contains(accessibility::AccessibleStateType::DEFUNC)
         && !xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
     {
         sal_Int32 nChildren = pContext->getAccessibleChildCount();
         m_aChildList.resize(nChildren);
         for(sal_Int32 n = 0; n < nChildren; n++)
         {
             try
             {
                 m_aChildList[n] = pContext->getAccessibleChild(n);
             }
             catch (lang::IndexOutOfBoundsException const&)
             {
                 sal_Int32 nChildren2 = pContext->getAccessibleChildCount();
                 assert(nChildren2 <= n && "consistency?");
                 m_aChildList.resize(std::min(nChildren2, n));
                 break;
             }
         }
     }
}

/*****************************************************************************/

void AtkListener::handleChildAdded(
    const uno::Reference< accessibility::XAccessibleContext >& rxParent,
    const uno::Reference< accessibility::XAccessible>& rxAccessible)
{
    AtkObject * pChild = rxAccessible.is() ? atk_object_wrapper_ref( rxAccessible ) : nullptr;

    if( pChild )
    {
        updateChildList(rxParent);

        atk_object_wrapper_add_child( mpWrapper, pChild,
            atk_object_get_index_in_parent( pChild ));

        g_object_unref( pChild );
    }
}

/*****************************************************************************/

void AtkListener::handleChildRemoved(
    const uno::Reference< accessibility::XAccessibleContext >& rxParent,
    const uno::Reference< accessibility::XAccessible>& rxChild)
{
    sal_Int32 nIndex = -1;

    // Locate the child in the children list
    size_t n, nmax = m_aChildList.size();
    for( n = 0; n < nmax; ++n )
    {
        if( rxChild == m_aChildList[n] )
        {
            nIndex = n;
            break;
        }
    }

    // FIXME: two problems here:
    // a) we get child-removed events for objects that are no real children
    //    in the accessibility hierarchy or have been removed before due to
    //    some child removing batch.
    // b) spi_atk_bridge_signal_listener ignores the given parameters
    //    for children_changed events and always asks the parent for the
    //    0. child, which breaks somehow on vanishing list boxes.
    // Ignoring "remove" events for objects not in the m_aChildList
    // for now.
    if( nIndex >= 0 )
    {
        uno::Reference<accessibility::XAccessibleEventBroadcaster> xBroadcaster(
            rxChild->getAccessibleContext(), uno::UNO_QUERY);

        if (xBroadcaster.is())
        {
            uno::Reference<accessibility::XAccessibleEventListener> xListener(this);
            xBroadcaster->removeAccessibleEventListener(xListener);
        }

        updateChildList(rxParent);

        AtkObject * pChild = atk_object_wrapper_ref( rxChild, false );
        if( pChild )
        {
            atk_object_wrapper_remove_child( mpWrapper, pChild, nIndex );
            g_object_unref( pChild );
        }
    }
}

/*****************************************************************************/

void AtkListener::handleInvalidateChildren(
    const uno::Reference< accessibility::XAccessibleContext >& rxParent)
{
    // Send notifications for all previous children
    size_t n = m_aChildList.size();
    while( n-- > 0 )
    {
        if( m_aChildList[n].is() )
        {
            AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n], false );
            if( pChild )
            {
                atk_object_wrapper_remove_child( mpWrapper, pChild, n );
                g_object_unref( pChild );
            }
        }
    }

    updateChildList(rxParent);

    // Send notifications for all new children
    size_t nmax = m_aChildList.size();
    for( n = 0; n < nmax; ++n )
    {
        if( m_aChildList[n].is() )
        {
            AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n] );

            if( pChild )
            {
                atk_object_wrapper_add_child( mpWrapper, pChild, n );
                g_object_unref( pChild );
            }
        }
    }
}

/*****************************************************************************/

static uno::Reference< accessibility::XAccessibleContext >
getAccessibleContextFromSource( const uno::Reference< uno::XInterface >& rxSource )
{
    uno::Reference< accessibility::XAccessibleContext > xContext(rxSource, uno::UNO_QUERY);
    if( ! xContext.is() )
    {
         g_warning( "ERROR: Event source does not implement XAccessibleContext" );

         // Second try - query for XAccessible, which should give us access to
         // XAccessibleContext.
         uno::Reference< accessibility::XAccessible > xAccessible(rxSource, uno::UNO_QUERY);
         if( xAccessible.is() )
             xContext = xAccessible->getAccessibleContext();
    }

    return xContext;
}

#if DEBUG_ATK_LISTENER

namespace {

void printNotifyEvent( const accessibility::AccessibleEventObject& rEvent )
{
    static std::vector<const char*> aLabels = {
        0,
        "NAME_CHANGED",                                       // 01
        "DESCRIPTION_CHANGED",                                // 02
        "ACTION_CHANGED",                                     // 03
        "STATE_CHANGED",                                      // 04
        "ACTIVE_DESCENDANT_CHANGED",                          // 05
        "BOUNDRECT_CHANGED",                                  // 06
        "CHILD",                                              // 07
        "INVALIDATE_ALL_CHILDREN",                            // 08
        "SELECTION_CHANGED",                                  // 09
        "VISIBLE_DATA_CHANGED",                               // 10
        "VALUE_CHANGED",                                      // 11
        "CONTENT_FLOWS_FROM_RELATION_CHANGED",                // 12
        "CONTENT_FLOWS_TO_RELATION_CHANGED",                  // 13
        "CONTROLLED_BY_RELATION_CHANGED",                     // 14
        "CONTROLLER_FOR_RELATION_CHANGED",                    // 15
        "LABEL_FOR_RELATION_CHANGED",                         // 16
        "LABELED_BY_RELATION_CHANGED",                        // 17
        "MEMBER_OF_RELATION_CHANGED",                         // 18
        "SUB_WINDOW_OF_RELATION_CHANGED",                     // 19
        "CARET_CHANGED",                                      // 20
        "TEXT_SELECTION_CHANGED",                             // 21
        "TEXT_CHANGED",                                       // 22
        "TEXT_ATTRIBUTE_CHANGED",                             // 23
        "HYPERTEXT_CHANGED",                                  // 24
        "TABLE_CAPTION_CHANGED",                              // 25
        "TABLE_COLUMN_DESCRIPTION_CHANGED",                   // 26
        "TABLE_COLUMN_HEADER_CHANGED",                        // 27
        "TABLE_MODEL_CHANGED",                                // 28
        "TABLE_ROW_DESCRIPTION_CHANGED",                      // 29
        "TABLE_ROW_HEADER_CHANGED",                           // 30
        "TABLE_SUMMARY_CHANGED",                              // 31
        "LISTBOX_ENTRY_EXPANDED",                             // 32
        "LISTBOX_ENTRY_COLLAPSED",                            // 33
        "ACTIVE_DESCENDANT_CHANGED_NOFOCUS",                  // 34
        "SELECTION_CHANGED_ADD",                              // 35
        "SELECTION_CHANGED_REMOVE",                           // 36
        "SELECTION_CHANGED_WITHIN",                           // 37
        "PAGE_CHANGED",                                       // 38
        "SECTION_CHANGED",                                    // 39
        "COLUMN_CHANGED",                                     // 40
        "ROLE_CHANGED",                                       // 41
    };

    static std::vector<const char*> aStates = {
        "INVALID",             // 00
        "ACTIVE",              // 01
        "ARMED",               // 02
        "BUSY",                // 03
        "CHECKED",             // 04
        "DEFUNC",              // 05
        "EDITABLE",            // 06
        "ENABLED",             // 07
        "EXPANDABLE",          // 08
        "EXPANDED",            // 09
        "FOCUSABLE",           // 10
        "FOCUSED",             // 11
        "HORIZONTAL",          // 12
        "ICONIFIED",           // 13
        "INDETERMINATE",       // 14
        "MANAGES_DESCENDANTS", // 15
        "MODAL",               // 16
        "MULTI_LINE",          // 17
        "MULTI_SELECTABLE",    // 18
        "OPAQUE",              // 19
        "PRESSED",             // 20
        "RESIZABLE",           // 21
        "SELECTABLE",          // 22
        "SELECTED",            // 23
        "SENSITIVE",           // 24
        "SHOWING",             // 25
        "SINGLE_LINE",         // 26
        "STALE",               // 27
        "TRANSIENT",           // 28
        "VERTICAL",            // 29
        "VISIBLE",             // 30
        "MOVEABLE",            // 31
        "DEFAULT",             // 32
        "OFFSCREEN",           // 33
        "COLLAPSE",            // 34
    };

    auto getOrUnknown = [](const std::vector<const char*>& rCont, size_t nIndex) -> std::string
    {
        return (nIndex < rCont.size()) ? rCont[nIndex] : "<unknown>";
    };

    std::ostringstream os;
    os << "--" << std::endl;
    os << "* event = " << getOrUnknown(aLabels, rEvent.EventId) << std::endl;

    switch (rEvent.EventId)
    {
        case accessibility::AccessibleEventId::STATE_CHANGED:
        {
            sal_Int16 nState;
            if (rEvent.OldValue >>= nState)
                os << "  * old state = " << getOrUnknown(aStates, nState);
            if (rEvent.NewValue >>= nState)
                os << "  * new state = " << getOrUnknown(aStates, nState);

            os << std::endl;
            break;
        }
        default:
            ;
    }

    std::cout << os.str();
}

}

#endif

void AtkListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
{
    if( !mpWrapper )
        return;

    AtkObject *atk_obj = ATK_OBJECT( mpWrapper );

    switch( aEvent.EventId )
    {
    // AtkObject signals:
        // Hierarchy signals
        case accessibility::AccessibleEventId::CHILD:
        {
            uno::Reference< accessibility::XAccessibleContext > xParent;
            uno::Reference< accessibility::XAccessible > xChild;

            xParent = getAccessibleContextFromSource(aEvent.Source);
            g_return_if_fail( xParent.is() );

            if( aEvent.OldValue >>= xChild )
                handleChildRemoved(xParent, xChild);

            if( aEvent.NewValue >>= xChild )
                handleChildAdded(xParent, xChild);
            break;
        }

        case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
        {
            uno::Reference< accessibility::XAccessibleContext > xParent = getAccessibleContextFromSource(aEvent.Source);
            g_return_if_fail( xParent.is() );

            handleInvalidateChildren(xParent);
            break;
        }

        case accessibility::AccessibleEventId::NAME_CHANGED:
        {
            OUString aName;
            if( aEvent.NewValue >>= aName )
            {
                atk_object_set_name(atk_obj,
                    OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr());
            }
            break;
        }

        case accessibility::AccessibleEventId::DESCRIPTION_CHANGED:
        {
            OUString aDescription;
            if( aEvent.NewValue >>= aDescription )
            {
                atk_object_set_description(atk_obj,
                    OUStringToOString(aDescription, RTL_TEXTENCODING_UTF8).getStr());
            }
            break;
        }

        case accessibility::AccessibleEventId::STATE_CHANGED:
        {
            AtkStateType eOldState = mapState( aEvent.OldValue );
            AtkStateType eNewState = mapState( aEvent.NewValue );

            gboolean bState = eNewState != ATK_STATE_INVALID;
            AtkStateType eRealState = bState ? eNewState : eOldState;

            atk_object_notify_state_change( atk_obj, eRealState, bState );
            break;
        }

        case accessibility::AccessibleEventId::BOUNDRECT_CHANGED:

#ifdef HAS_ATKRECTANGLE
            if( ATK_IS_COMPONENT( atk_obj ) )
            {
                AtkRectangle rect;

                atk_component_get_extents( ATK_COMPONENT( atk_obj ),
                                           &rect.x,
                                           &rect.y,
                                           &rect.width,
                                           &rect.height,
                                           ATK_XY_SCREEN );

                g_signal_emit_by_name( atk_obj, "bounds_changed", &rect );
            }
            else
                g_warning( "bounds_changed event for object not implementing AtkComponent\n");
#endif

            break;

        case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED:
            g_signal_emit_by_name( atk_obj, "visible-data-changed" );
            break;

        case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
        {
            AtkObject *pChild = getObjFromAny( aEvent.NewValue );
            if( pChild )
            {
                g_signal_emit_by_name( atk_obj, "active-descendant-changed", pChild );
                g_object_unref( pChild );
            }
            break;
        }

        //ACTIVE_DESCENDANT_CHANGED_NOFOCUS (sic) appears to have been added
        //as a workaround or an aid for the ia2 winaccessibility implementation
        //so ignore it silently without warning here
        case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
            break;

        // #i92103#
        case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
        {
            AtkObject *pChild = getObjFromAny( aEvent.NewValue );
            if( pChild )
            {
                atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, true );
                g_object_unref( pChild );
            }
            break;
        }

        case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
        {
            AtkObject *pChild = getObjFromAny( aEvent.NewValue );
            if( pChild )
            {
                atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, false );
                g_object_unref( pChild );
            }
            break;
        }

        // AtkAction signals ...
        case accessibility::AccessibleEventId::ACTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-actions");
            break;

        // AtkText
        case accessibility::AccessibleEventId::CARET_CHANGED:
        {
            sal_Int32 nPos=0;
            aEvent.NewValue >>= nPos;
            g_signal_emit_by_name( atk_obj, "text_caret_moved", nPos );
            break;
        }
        case accessibility::AccessibleEventId::TEXT_CHANGED:
        {
            // TESTME: and remove this comment:
            // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
            accessibility::TextSegment aDeletedText;
            accessibility::TextSegment aInsertedText;

            // TODO: when GNOME starts to send "update" kind of events, change
            // we need to re-think this implementation as well
            if( aEvent.OldValue >>= aDeletedText )
            {
                /* Remember the text segment here to be able to return removed text in get_text().
                 * This is clearly a hack to be used until appropriate API exists in atk to pass
                 * the string value directly or we find a compelling reason to start caching the
                 * UTF-8 converted strings in the atk wrapper object.
                 */

                g_object_set_data( G_OBJECT(atk_obj), "ooo::text_changed::delete", &aDeletedText);

                g_signal_emit_by_name( atk_obj, "text_changed::delete",
                                       static_cast<gint>(aDeletedText.SegmentStart),
                                       static_cast<gint>( aDeletedText.SegmentEnd - aDeletedText.SegmentStart ) );

                g_object_steal_data( G_OBJECT(atk_obj), "ooo::text_changed::delete" );
            }

            if( aEvent.NewValue >>= aInsertedText )
                g_signal_emit_by_name( atk_obj, "text_changed::insert",
                                       static_cast<gint>(aInsertedText.SegmentStart),
                                       static_cast<gint>( aInsertedText.SegmentEnd - aInsertedText.SegmentStart ) );
            break;
        }

        case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED:
        {
            g_signal_emit_by_name( atk_obj, "text-selection-changed" );
            break;
        }

        case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
            g_signal_emit_by_name( atk_obj, "text-attributes-changed" );
            break;

        // AtkValue
        case accessibility::AccessibleEventId::VALUE_CHANGED:
            g_object_notify( G_OBJECT( atk_obj ), "accessible-value" );
            break;

        case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
        case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
        case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
        case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
        case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
        case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED:
        case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
        case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
            // FIXME: ask Bill how Atk copes with this little lot ...
            break;

        // AtkTable
        case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED:
        {
            accessibility::AccessibleTableModelChange aChange;
            aEvent.NewValue >>= aChange;

            sal_Int32 nRowsChanged = aChange.LastRow - aChange.FirstRow + 1;
            sal_Int32 nColumnsChanged = aChange.LastColumn - aChange.FirstColumn + 1;

            static const struct {
                    const char *row;
                    const char *col;
            } aSignalNames[] =
            {
                { nullptr, nullptr }, // dummy
                { "row_inserted", "column_inserted" }, // INSERT = 1
                { "row_deleted", "column_deleted" } // DELETE = 2
            };
            switch( aChange.Type )
            {
                case accessibility::AccessibleTableModelChangeType::INSERT:
                case accessibility::AccessibleTableModelChangeType::DELETE:
                    if( nRowsChanged > 0 )
                        g_signal_emit_by_name( G_OBJECT( atk_obj ),
                                               aSignalNames[aChange.Type].row,
                                               aChange.FirstRow, nRowsChanged );
                    if( nColumnsChanged > 0 )
                        g_signal_emit_by_name( G_OBJECT( atk_obj ),
                                               aSignalNames[aChange.Type].col,
                                               aChange.FirstColumn, nColumnsChanged );
                    break;

                case accessibility::AccessibleTableModelChangeType::UPDATE:
                    // This is not really a model change, is it ?
                    break;
                default:
                    g_warning( "TESTME: unusual table model change %d\n", aChange.Type );
                    break;
            }
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "model-changed" );
            break;
        }

        case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
        {
            accessibility::AccessibleTableModelChange aChange;
            aEvent.NewValue >>= aChange;

            AtkPropertyValues values;
            memset(&values,  0, sizeof(AtkPropertyValues));
            g_value_init (&values.new_value, G_TYPE_INT);
            values.property_name = "accessible-table-column-header";

            for (sal_Int32 nChangedColumn = aChange.FirstColumn; nChangedColumn <= aChange.LastColumn; ++nChangedColumn)
            {
                g_value_set_int (&values.new_value, nChangedColumn);
                g_signal_emit_by_name(G_OBJECT(atk_obj), "property_change::accessible-table-column-header", &values, nullptr);
            }
            break;
        }

        case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-caption");
            break;

        case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-description");
            break;

        case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-description");
            break;

        case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-header");
            break;

        case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-summary");
            break;

        case accessibility::AccessibleEventId::SELECTION_CHANGED:
        case accessibility::AccessibleEventId::SELECTION_CHANGED_ADD:
        case accessibility::AccessibleEventId::SELECTION_CHANGED_REMOVE:
        case accessibility::AccessibleEventId::SELECTION_CHANGED_WITHIN:
            if (ATK_IS_SELECTION(atk_obj))
                g_signal_emit_by_name(G_OBJECT(atk_obj), "selection_changed");
            else
            {
                // e.g. tdf#122353, when such dialogs become native the problem will go away anyway
                SAL_INFO("vcl.gtk", "selection change from obj which doesn't support XAccessibleSelection");
            }
            break;

        case accessibility::AccessibleEventId::HYPERTEXT_CHANGED:
            g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-hypertext-offset");
            break;

        case accessibility::AccessibleEventId::ROLE_CHANGED:
        {
            uno::Reference< accessibility::XAccessibleContext > xContext = getAccessibleContextFromSource( aEvent.Source );
            atk_object_wrapper_set_role( mpWrapper, xContext->getAccessibleRole() );
            break;
        }

        case accessibility::AccessibleEventId::PAGE_CHANGED:
        {
            /* // If we implemented AtkDocument then I imagine this is what this
               // handler should look like
               sal_Int32 nPos=0;
               aEvent.NewValue >>= nPos;
               g_signal_emit_by_name( G_OBJECT( atk_obj ), "page_changed", nPos );
            */
            break;
        }

        default:
            SAL_WARN("vcl.gtk", "Unknown event notification: " << aEvent.EventId);
            break;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkregistry.cxx b/vcl/unx/gtk3/a11y/gtk3atkregistry.cxx
index 126e97a..ff96378 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkregistry.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkregistry.cxx
@@ -5,8 +5,62 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkregistry.cxx"
#include "atkregistry.hxx"

using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;

static GHashTable *uno_to_gobject = nullptr;

/*****************************************************************************/

AtkObject *
ooo_wrapper_registry_get(const Reference< XAccessible >& rxAccessible)
{
    if( uno_to_gobject )
    {
        gpointer cached =
            g_hash_table_lookup(uno_to_gobject, static_cast<gpointer>(rxAccessible.get()));

        if( cached )
            return ATK_OBJECT( cached );
    }

    return nullptr;
}

/*****************************************************************************/

void
ooo_wrapper_registry_add(const Reference< XAccessible >& rxAccessible, AtkObject *obj)
{
   if( !uno_to_gobject )
        uno_to_gobject = g_hash_table_new (nullptr, nullptr);

   g_hash_table_insert( uno_to_gobject, static_cast<gpointer>(rxAccessible.get()), obj );
}

/*****************************************************************************/

void
ooo_wrapper_registry_remove(
    css::uno::Reference<css::accessibility::XAccessible> const & pAccessible)
{
    if( uno_to_gobject )
        g_hash_table_remove(
            uno_to_gobject, static_cast<gpointer>(pAccessible.get()) );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkselection.cxx b/vcl/unx/gtk3/a11y/gtk3atkselection.cxx
index f67b665..1d9772b 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkselection.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkselection.cxx
@@ -5,8 +5,186 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkselection.cxx"
#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleSelection.hpp>

using namespace ::com::sun::star;

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleSelection>
    getSelection( AtkSelection *pSelection )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pSelection );
    if( pWrap )
    {
        if( !pWrap->mpSelection.is() )
        {
            pWrap->mpSelection.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpSelection;
    }

    return css::uno::Reference<css::accessibility::XAccessibleSelection>();
}

extern "C" {

static gboolean
selection_add_selection( AtkSelection *selection,
                         gint          i )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->selectAccessibleChild( i );
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in selectAccessibleChild()" );
    }

    return FALSE;
}

static gboolean
selection_clear_selection( AtkSelection *selection )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->clearAccessibleSelection();
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in selectAccessibleChild()" );
    }

    return FALSE;
}

static AtkObject*
selection_ref_selection( AtkSelection *selection,
                         gint          i )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
            return atk_object_wrapper_ref( pSelection->getSelectedAccessibleChild( i ) );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChild()" );
    }

    return nullptr;
}

static gint
selection_get_selection_count( AtkSelection   *selection)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
            return pSelection->getSelectedAccessibleChildCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return -1;
}

static gboolean
selection_is_child_selected( AtkSelection   *selection,
                              gint           i)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
            return pSelection->isAccessibleChildSelected( i );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return FALSE;
}

static gboolean
selection_remove_selection( AtkSelection *selection,
                            gint           i )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->deselectAccessibleChild( i );
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return FALSE;
}

static gboolean
selection_select_all_selection( AtkSelection   *selection)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
            = getSelection( selection );
        if( pSelection.is() )
        {
            pSelection->selectAllAccessibleChildren();
            return TRUE;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleChildCount()" );
    }

    return FALSE;
}

} // extern "C"

void
selectionIfaceInit( AtkSelectionIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->add_selection = selection_add_selection;
  iface->clear_selection = selection_clear_selection;
  iface->ref_selection = selection_ref_selection;
  iface->get_selection_count = selection_get_selection_count;
  iface->is_child_selected = selection_is_child_selected;
  iface->remove_selection = selection_remove_selection;
  iface->select_all_selection = selection_select_all_selection;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atktable.cxx b/vcl/unx/gtk3/a11y/gtk3atktable.cxx
index d886ac0..29ffa48 100644
--- a/vcl/unx/gtk3/a11y/gtk3atktable.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atktable.cxx
@@ -5,8 +5,576 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atktable.cxx"
#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleTable.hpp>
#include <comphelper/sequence.hxx>

using namespace ::com::sun::star;

static AtkObject *
atk_object_wrapper_conditional_ref( const uno::Reference< accessibility::XAccessible >& rxAccessible )
{
    if( rxAccessible.is() )
        return atk_object_wrapper_ref( rxAccessible );

    return nullptr;
}

/*****************************************************************************/

// FIXME
static G_CONST_RETURN gchar *
getAsConst( const OUString& rString )
{
    static const int nMax = 10;
    static OString aUgly[nMax];
    static int nIdx = 0;
    nIdx = (nIdx + 1) % nMax;
    aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
    return aUgly[ nIdx ].getStr();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleTable>
    getTable( AtkTable *pTable )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pTable );
    if( pWrap )
    {
        if( !pWrap->mpTable.is() )
        {
            pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpTable;
    }

    return css::uno::Reference<css::accessibility::XAccessibleTable>();
}

/*****************************************************************************/

extern "C" {

static AtkObject*
table_wrapper_ref_at (AtkTable *table,
                      gint      row,
                      gint      column)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable = getTable( table );
        if( pTable.is() )
            return atk_object_wrapper_conditional_ref( pTable->getAccessibleCellAt( row, column ) );
    }

    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleCellAt()" );
    }

    return nullptr;
}

/*****************************************************************************/

static gint
table_wrapper_get_index_at (AtkTable      *table,
                            gint          row,
                            gint          column)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleIndex( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleIndex()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_column_at_index (AtkTable      *table,
                                   gint          nIndex)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleColumn( nIndex );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumn()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_row_at_index( AtkTable *table,
                                gint      nIndex )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleRow( nIndex );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRow()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_n_columns( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleColumnCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnCount()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_n_rows( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleRowCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowCount()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_column_extent_at( AtkTable *table,
                                    gint      row,
                                    gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleColumnExtentAt( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnExtentAt()" );
    }

    return -1;
}

/*****************************************************************************/

static gint
table_wrapper_get_row_extent_at( AtkTable *table,
                                 gint      row,
                                 gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->getAccessibleRowExtentAt( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowExtentAt()" );
    }

    return -1;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_caption( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return atk_object_wrapper_conditional_ref( pTable->getAccessibleCaption() );
    }

    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleCaption()" );
    }

    return nullptr;
}

/*****************************************************************************/

static G_CONST_RETURN gchar *
table_wrapper_get_row_description( AtkTable *table,
                                   gint      row )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return getAsConst( pTable->getAccessibleRowDescription( row ) );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowDescription()" );
    }

    return nullptr;
}

/*****************************************************************************/

static G_CONST_RETURN gchar *
table_wrapper_get_column_description( AtkTable *table,
                                      gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return getAsConst( pTable->getAccessibleColumnDescription( column ) );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnDescription()" );
    }

    return nullptr;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_row_header( AtkTable *table,
                              gint      row )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
        {
            uno::Reference< accessibility::XAccessibleTable > xRowHeaders( pTable->getAccessibleRowHeaders() );
            if( xRowHeaders.is() )
                return atk_object_wrapper_conditional_ref( xRowHeaders->getAccessibleCellAt( row, 0 ) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleRowHeaders()" );
    }

    return nullptr;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_column_header( AtkTable *table,
                                 gint      column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
        {
            uno::Reference< accessibility::XAccessibleTable > xColumnHeaders( pTable->getAccessibleColumnHeaders() );
            if( xColumnHeaders.is() )
                return atk_object_wrapper_conditional_ref( xColumnHeaders->getAccessibleCellAt( 0, column ) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleColumnHeaders()" );
    }

    return nullptr;
}

/*****************************************************************************/

static AtkObject *
table_wrapper_get_summary( AtkTable *table )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
        {
            return atk_object_wrapper_conditional_ref( pTable->getAccessibleSummary() );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getAccessibleSummary()" );
    }

    return nullptr;
}

/*****************************************************************************/

static gint
convertToGIntArray( const uno::Sequence< ::sal_Int32 >& aSequence, gint **pSelected )
{
    if( aSequence.hasElements() )
    {
        *pSelected = g_new( gint, aSequence.getLength() );

        *pSelected = comphelper::sequenceToArray(*pSelected, aSequence);
    }

    return aSequence.getLength();
}

/*****************************************************************************/

static gint
table_wrapper_get_selected_columns( AtkTable      *table,
                                    gint          **pSelected )
{
    *pSelected = nullptr;
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return convertToGIntArray( pTable->getSelectedAccessibleColumns(), pSelected );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleColumns()" );
    }

    return 0;
}

/*****************************************************************************/

static gint
table_wrapper_get_selected_rows( AtkTable      *table,
                                 gint          **pSelected )
{
    *pSelected = nullptr;
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return convertToGIntArray( pTable->getSelectedAccessibleRows(), pSelected );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectedAccessibleRows()" );
    }

    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_is_column_selected( AtkTable      *table,
                                  gint          column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->isAccessibleColumnSelected( column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in isAccessibleColumnSelected()" );
    }

    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_is_row_selected( AtkTable      *table,
                               gint          row )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->isAccessibleRowSelected( row );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in isAccessibleRowSelected()" );
    }

    return FALSE;
}

/*****************************************************************************/

static gboolean
table_wrapper_is_selected( AtkTable      *table,
                           gint          row,
                           gint          column )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleTable> pTable
            = getTable( table );
        if( pTable.is() )
            return pTable->isAccessibleSelected( row, column );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in isAccessibleSelected()" );
    }

    return FALSE;
}

/*****************************************************************************/

static gboolean
table_wrapper_add_row_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for add_row_selection" );
    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_remove_row_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for remove_row_selection" );
    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_add_column_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for add_column_selection" );
    return 0;
}

/*****************************************************************************/

static gboolean
table_wrapper_remove_column_selection( AtkTable *, gint )
{
    g_warning( "FIXME: no simple analogue for remove_column_selection" );
    return 0;
}

/*****************************************************************************/

static void
table_wrapper_set_caption( AtkTable *, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_column_description( AtkTable *, gint, const gchar * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_column_header( AtkTable *, gint, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_row_description( AtkTable *, gint, const gchar * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_row_header( AtkTable *, gint, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

static void
table_wrapper_set_summary( AtkTable *, AtkObject * )
{ // meaningless helper
}

/*****************************************************************************/

} // extern "C"

void
tableIfaceInit (AtkTableIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->ref_at = table_wrapper_ref_at;
  iface->get_n_rows = table_wrapper_get_n_rows;
  iface->get_n_columns = table_wrapper_get_n_columns;
  iface->get_index_at = table_wrapper_get_index_at;
  iface->get_column_at_index = table_wrapper_get_column_at_index;
  iface->get_row_at_index = table_wrapper_get_row_at_index;
  iface->is_row_selected = table_wrapper_is_row_selected;
  iface->is_selected = table_wrapper_is_selected;
  iface->get_selected_rows = table_wrapper_get_selected_rows;
  iface->add_row_selection = table_wrapper_add_row_selection;
  iface->remove_row_selection = table_wrapper_remove_row_selection;
  iface->add_column_selection = table_wrapper_add_column_selection;
  iface->remove_column_selection = table_wrapper_remove_column_selection;
  iface->get_selected_columns = table_wrapper_get_selected_columns;
  iface->is_column_selected = table_wrapper_is_column_selected;
  iface->get_column_extent_at = table_wrapper_get_column_extent_at;
  iface->get_row_extent_at = table_wrapper_get_row_extent_at;
  iface->get_row_header = table_wrapper_get_row_header;
  iface->set_row_header = table_wrapper_set_row_header;
  iface->get_column_header = table_wrapper_get_column_header;
  iface->set_column_header = table_wrapper_set_column_header;
  iface->get_caption = table_wrapper_get_caption;
  iface->set_caption = table_wrapper_set_caption;
  iface->get_summary = table_wrapper_get_summary;
  iface->set_summary = table_wrapper_set_summary;
  iface->get_row_description = table_wrapper_get_row_description;
  iface->set_row_description = table_wrapper_set_row_description;
  iface->get_column_description = table_wrapper_get_column_description;
  iface->set_column_description = table_wrapper_set_column_description;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atktext.cxx b/vcl/unx/gtk3/a11y/gtk3atktext.cxx
index e4bbd5a..532b5501 100644
--- a/vcl/unx/gtk3/a11y/gtk3atktext.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atktext.cxx
@@ -5,8 +5,833 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atktext.cxx"
#include "atkwrapper.hxx"
#include "atktextattributes.hxx"
#include <algorithm>

#include <osl/diagnose.h>

#include <com/sun/star/accessibility/AccessibleTextType.hpp>
#include <com/sun/star/accessibility/TextSegment.hpp>
#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
#include <com/sun/star/text/TextMarkupType.hpp>

using namespace ::com::sun::star;

static sal_Int16
text_type_from_boundary(AtkTextBoundary boundary_type)
{
    switch(boundary_type)
    {
        case ATK_TEXT_BOUNDARY_CHAR:
            return accessibility::AccessibleTextType::CHARACTER;
        case ATK_TEXT_BOUNDARY_WORD_START:
        case ATK_TEXT_BOUNDARY_WORD_END:
            return accessibility::AccessibleTextType::WORD;
        case ATK_TEXT_BOUNDARY_SENTENCE_START:
        case ATK_TEXT_BOUNDARY_SENTENCE_END:
            return accessibility::AccessibleTextType::SENTENCE;
        case ATK_TEXT_BOUNDARY_LINE_START:
        case ATK_TEXT_BOUNDARY_LINE_END:
            return accessibility::AccessibleTextType::LINE;
        default:
            return -1;
    }
}

/*****************************************************************************/

static gchar *
adjust_boundaries( css::uno::Reference<css::accessibility::XAccessibleText> const & pText,
                   accessibility::TextSegment const & rTextSegment,
                   AtkTextBoundary  boundary_type,
                   gint * start_offset, gint * end_offset )
{
    accessibility::TextSegment aTextSegment;
    OUString aString;
    gint start = 0, end = 0;

    if( !rTextSegment.SegmentText.isEmpty() )
    {
        switch(boundary_type)
        {
        case ATK_TEXT_BOUNDARY_CHAR:
        case ATK_TEXT_BOUNDARY_LINE_START:
        case ATK_TEXT_BOUNDARY_LINE_END:
        case ATK_TEXT_BOUNDARY_SENTENCE_START:
            start = rTextSegment.SegmentStart;
            end = rTextSegment.SegmentEnd;
            aString = rTextSegment.SegmentText;
            break;

        // the OOo break iterator behaves as SENTENCE_START
        case ATK_TEXT_BOUNDARY_SENTENCE_END:
            start = rTextSegment.SegmentStart;
            end = rTextSegment.SegmentEnd;

            if( start > 0 )
                --start;
            if( end > 0 && end < pText->getCharacterCount() - 1 )
                --end;

            aString = pText->getTextRange(start, end);
            break;

        case ATK_TEXT_BOUNDARY_WORD_START:
            start = rTextSegment.SegmentStart;

            // Determine the start index of the next segment
            aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd,
                                                     text_type_from_boundary(boundary_type));
            if( !aTextSegment.SegmentText.isEmpty() )
                end = aTextSegment.SegmentStart;
            else
                end = pText->getCharacterCount();

            aString = pText->getTextRange(start, end);
            break;

        case ATK_TEXT_BOUNDARY_WORD_END:
            end = rTextSegment.SegmentEnd;

            // Determine the end index of the previous segment
            aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart,
                                                     text_type_from_boundary(boundary_type));
            if( !aTextSegment.SegmentText.isEmpty() )
                start = aTextSegment.SegmentEnd;
            else
                start = 0;

            aString = pText->getTextRange(start, end);
            break;

        default:
            return nullptr;
        }
    }

    *start_offset = start;
    *end_offset   = end;

    return OUStringToGChar(aString);
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleText>
    getText( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpText.is() )
        {
            pWrap->mpText.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpText;
    }

    return css::uno::Reference<css::accessibility::XAccessibleText>();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
    getTextMarkup( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpTextMarkup.is() )
        {
            pWrap->mpTextMarkup.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpTextMarkup;
    }

    return css::uno::Reference<css::accessibility::XAccessibleTextMarkup>();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
    getTextAttributes( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpTextAttributes.is() )
        {
            pWrap->mpTextAttributes.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpTextAttributes;
    }

    return css::uno::Reference<css::accessibility::XAccessibleTextAttributes>();
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
    getMultiLineText( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpMultiLineText.is() )
        {
            pWrap->mpMultiLineText.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpMultiLineText;
    }

    return css::uno::Reference<css::accessibility::XAccessibleMultiLineText>();
}

/*****************************************************************************/

extern "C" {

static gchar *
text_wrapper_get_text (AtkText *text,
                       gint     start_offset,
                       gint     end_offset)
{
    gchar * ret = nullptr;

    g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), nullptr );

    /* at-spi expects the delete event to be send before the deletion happened
     * so we save the deleted string object in the UNO event notification and
     * fool libatk-bridge.so here ..
     */
    void * pData = g_object_get_data( G_OBJECT(text), "ooo::text_changed::delete" );
    if( pData != nullptr )
    {
        accessibility::TextSegment * pTextSegment =
            static_cast <accessibility::TextSegment *> (pData);

        if( pTextSegment->SegmentStart == start_offset &&
            pTextSegment->SegmentEnd == end_offset )
        {
            OString aUtf8 = OUStringToOString( pTextSegment->SegmentText, RTL_TEXTENCODING_UTF8 );
            return g_strdup( aUtf8.getStr() );
        }
    }

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            OUString aText;
            sal_Int32 n = pText->getCharacterCount();

            if( -1 == end_offset )
                aText = pText->getText();
            else if( start_offset < n )
                aText = pText->getTextRange(start_offset, end_offset);

            ret = g_strdup( OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getText()" );
    }

    return ret;
}

static gchar *
text_wrapper_get_text_after_offset (AtkText          *text,
                                    gint             offset,
                                    AtkTextBoundary  boundary_type,
                                    gint             *start_offset,
                                    gint             *end_offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type));
            return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in get_text_after_offset()" );
    }

    return nullptr;
}

static gchar *
text_wrapper_get_text_at_offset (AtkText          *text,
                                 gint             offset,
                                 AtkTextBoundary  boundary_type,
                                 gint             *start_offset,
                                 gint             *end_offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            /* If the user presses the 'End' key, the caret will be placed behind the last character,
             * which is the same index as the first character of the next line. In atk the magic offset
             * '-2' is used to cover this special case.
             */
            if (
                 -2 == offset &&
                     (ATK_TEXT_BOUNDARY_LINE_START == boundary_type ||
                      ATK_TEXT_BOUNDARY_LINE_END == boundary_type)
               )
            {
                css::uno::Reference<
                    css::accessibility::XAccessibleMultiLineText> pMultiLineText
                        = getMultiLineText( text );
                if( pMultiLineText.is() )
                {
                    accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret();
                    return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
                }
            }

            accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type));
            return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in get_text_at_offset()" );
    }

    return nullptr;
}

static gunichar
text_wrapper_get_character_at_offset (AtkText          *text,
                                      gint             offset)
{
    gint start, end;
    gunichar uc = 0;

    gchar * char_as_string =
        text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR,
                                        &start, &end);
    if( char_as_string )
    {
        uc = g_utf8_get_char( char_as_string );
        g_free( char_as_string );
    }

    return uc;
}

static gchar *
text_wrapper_get_text_before_offset (AtkText          *text,
                                     gint             offset,
                                     AtkTextBoundary  boundary_type,
                                     gint             *start_offset,
                                     gint             *end_offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type));
            return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in text_before_offset()" );
    }

    return nullptr;
}

static gint
text_wrapper_get_caret_offset (AtkText          *text)
{
    gint offset = -1;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            offset = pText->getCaretPosition();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCaretPosition()" );
    }

    return offset;
}

static gboolean
text_wrapper_set_caret_offset (AtkText *text,
                               gint     offset)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setCaretPosition( offset );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setCaretPosition()" );
    }

    return FALSE;
}

// #i92232#
static AtkAttributeSet*
handle_text_markup_as_run_attribute( css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup,
                                     const gint nTextMarkupType,
                                     const gint offset,
                                     AtkAttributeSet* pSet,
                                     gint *start_offset,
                                     gint *end_offset )
{
    const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) );
    if ( nTextMarkupCount > 0 )
    {
        for ( gint nTextMarkupIndex = 0;
              nTextMarkupIndex < nTextMarkupCount;
              ++nTextMarkupIndex )
        {
            accessibility::TextSegment aTextSegment =
                pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
            const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart;
            const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
            if ( nStartOffsetTextMarkup <= offset )
            {
                if ( offset < nEndOffsetTextMarkup )
                {
                    // text markup at <offset>
                    *start_offset = ::std::max( *start_offset,
                                                nStartOffsetTextMarkup );
                    *end_offset = ::std::min( *end_offset,
                                              nEndOffsetTextMarkup );
                    switch ( nTextMarkupType )
                    {
                        case css::text::TextMarkupType::SPELLCHECK:
                        {
                            pSet = attribute_set_prepend_misspelled( pSet );
                        }
                        break;
                        case css::text::TextMarkupType::TRACK_CHANGE_INSERTION:
                        {
                            pSet = attribute_set_prepend_tracked_change_insertion( pSet );
                        }
                        break;
                        case css::text::TextMarkupType::TRACK_CHANGE_DELETION:
                        {
                            pSet = attribute_set_prepend_tracked_change_deletion( pSet );
                        }
                        break;
                        case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
                        {
                            pSet = attribute_set_prepend_tracked_change_formatchange( pSet );
                        }
                        break;
                        default:
                        {
                            OSL_ASSERT( false );
                        }
                    }
                    break; // no further iteration needed.
                }
                else
                {
                    *start_offset = ::std::max( *start_offset,
                                                nEndOffsetTextMarkup );
                    // continue iteration.
                }
            }
            else
            {
                *end_offset = ::std::min( *end_offset,
                                          nStartOffsetTextMarkup );
                break; // no further iteration.
            }
        } // eof iteration over text markups
    }

    return pSet;
}

static AtkAttributeSet *
text_wrapper_get_run_attributes( AtkText        *text,
                                 gint           offset,
                                 gint           *start_offset,
                                 gint           *end_offset)
{
    AtkAttributeSet *pSet = nullptr;

    try {
        bool bOffsetsAreValid = false;

        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is())
        {
            uno::Sequence< beans::PropertyValue > aAttributeList;

            css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
                pTextAttributes = getTextAttributes( text );
            if(pTextAttributes.is()) // Text attributes are available for paragraphs only
            {
                aAttributeList = pTextAttributes->getRunAttributes( offset, uno::Sequence< OUString > () );
            }
            else // For other text objects use character attributes
            {
                aAttributeList = pText->getCharacterAttributes( offset, uno::Sequence< OUString > () );
            }

            pSet = attribute_set_new_from_property_values( aAttributeList, true, text );
            //  #i100938#
            // - always provide start_offset and end_offset
            {
                accessibility::TextSegment aTextSegment =
                    pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);

                *start_offset = aTextSegment.SegmentStart;
                // #i100938#
                // Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance
                *end_offset = aTextSegment.SegmentEnd;
                bOffsetsAreValid = true;
            }
        }

        // Special handling for misspelled text
        // #i92232#
        // - add special handling for tracked changes and refactor the
        //   corresponding code for handling misspelled text.
        css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
            pTextMarkup = getTextMarkup( text );
        if( pTextMarkup.is() )
        {
            // Get attribute run here if it hasn't been done before
            if (!bOffsetsAreValid && pText.is())
            {
                accessibility::TextSegment aAttributeTextSegment =
                    pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
                *start_offset = aAttributeTextSegment.SegmentStart;
                *end_offset = aAttributeTextSegment.SegmentEnd;
            }
            // handle misspelled text
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::SPELLCHECK,
                    offset, pSet, start_offset, end_offset );
            // handle tracked changes
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::TRACK_CHANGE_INSERTION,
                    offset, pSet, start_offset, end_offset );
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::TRACK_CHANGE_DELETION,
                    offset, pSet, start_offset, end_offset );
            pSet = handle_text_markup_as_run_attribute(
                    pTextMarkup,
                    css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE,
                    offset, pSet, start_offset, end_offset );
        }
    }
    catch(const uno::Exception&){

        g_warning( "Exception in get_run_attributes()" );

        if( pSet )
        {
            atk_attribute_set_free( pSet );
            pSet = nullptr;
        }
    }

    return pSet;
}

/*****************************************************************************/

static AtkAttributeSet *
text_wrapper_get_default_attributes( AtkText *text )
{
    AtkAttributeSet *pSet = nullptr;

    try {
        css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
            pTextAttributes = getTextAttributes( text );
        if( pTextAttributes.is() )
        {
            uno::Sequence< beans::PropertyValue > aAttributeList =
                pTextAttributes->getDefaultAttributes( uno::Sequence< OUString > () );

            pSet = attribute_set_new_from_property_values( aAttributeList, false, text );
        }
    }
    catch(const uno::Exception&) {

        g_warning( "Exception in get_default_attributes()" );

        if( pSet )
        {
            atk_attribute_set_free( pSet );
            pSet = nullptr;
        }
    }

    return pSet;
}

/*****************************************************************************/

static void
text_wrapper_get_character_extents( AtkText          *text,
                                    gint             offset,
                                    gint             *x,
                                    gint             *y,
                                    gint             *width,
                                    gint             *height,
                                    AtkCoordType      coords )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            *x = *y = *width = *height = 0;
            awt::Rectangle aRect = pText->getCharacterBounds( offset );

            gint origin_x = 0;
            gint origin_y = 0;

            if( coords == ATK_XY_SCREEN )
            {
                g_return_if_fail( ATK_IS_COMPONENT( text ) );
                SAL_WNODEPRECATED_DECLARATIONS_PUSH
                atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
                SAL_WNODEPRECATED_DECLARATIONS_POP
            }

            *x = aRect.X + origin_x;
            *y = aRect.Y + origin_y;
            *width = aRect.Width;
            *height = aRect.Height;
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCharacterBounds" );
    }
}

static gint
text_wrapper_get_character_count (AtkText *text)
{
    gint rv = 0;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            rv = pText->getCharacterCount();
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCharacterCount" );
    }

    return rv;
}

static gint
text_wrapper_get_offset_at_point (AtkText     *text,
                                  gint         x,
                                  gint         y,
                                  AtkCoordType coords)
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            gint origin_x = 0;
            gint origin_y = 0;

            if( coords == ATK_XY_SCREEN )
            {
                g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
                SAL_WNODEPRECATED_DECLARATIONS_PUSH
                atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
                SAL_WNODEPRECATED_DECLARATIONS_POP
            }

            return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getIndexAtPoint" );
    }

    return -1;
}

// FIXME: the whole series of selections API is problematic ...

static gint
text_wrapper_get_n_selections (AtkText *text)
{
    gint rv = 0;

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0;
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectionEnd() or getSelectionStart()" );
    }

    return rv;
}

static gchar *
text_wrapper_get_selection (AtkText *text,
                            gint     selection_num,
                            gint    *start_offset,
                            gint    *end_offset)
{
    g_return_val_if_fail( selection_num == 0, FALSE );

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
        {
            *start_offset = pText->getSelectionStart();
            *end_offset   = pText->getSelectionEnd();

            return OUStringToGChar( pText->getSelectedText() );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" );
    }

    return nullptr;
}

static gboolean
text_wrapper_add_selection (AtkText *text,
                            gint     start_offset,
                            gint     end_offset)
{
    // FIXME: can we try to be more compatible by expanding an
    //        existing adjacent selection ?

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setSelection( start_offset, end_offset ); // ?
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setSelection()" );
    }

    return FALSE;
}

static gboolean
text_wrapper_remove_selection (AtkText *text,
                               gint     selection_num)
{
    g_return_val_if_fail( selection_num == 0, FALSE );

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setSelection( 0, 0 ); // ?
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setSelection()" );
    }

    return FALSE;
}

static gboolean
text_wrapper_set_selection (AtkText *text,
                            gint     selection_num,
                            gint     start_offset,
                            gint     end_offset)
{
    g_return_val_if_fail( selection_num == 0, FALSE );

    try {
        css::uno::Reference<css::accessibility::XAccessibleText> pText
            = getText( text );
        if( pText.is() )
            return pText->setSelection( start_offset, end_offset );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in setSelection()" );
    }

    return FALSE;
}

} // extern "C"

void
textIfaceInit (AtkTextIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->get_text = text_wrapper_get_text;
  iface->get_character_at_offset = text_wrapper_get_character_at_offset;
  iface->get_text_before_offset = text_wrapper_get_text_before_offset;
  iface->get_text_at_offset = text_wrapper_get_text_at_offset;
  iface->get_text_after_offset = text_wrapper_get_text_after_offset;
  iface->get_caret_offset = text_wrapper_get_caret_offset;
  iface->set_caret_offset = text_wrapper_set_caret_offset;
  iface->get_character_count = text_wrapper_get_character_count;
  iface->get_n_selections = text_wrapper_get_n_selections;
  iface->get_selection = text_wrapper_get_selection;
  iface->add_selection = text_wrapper_add_selection;
  iface->remove_selection = text_wrapper_remove_selection;
  iface->set_selection = text_wrapper_set_selection;
  iface->get_run_attributes = text_wrapper_get_run_attributes;
  iface->get_default_attributes = text_wrapper_get_default_attributes;
  iface->get_character_extents = text_wrapper_get_character_extents;
  iface->get_offset_at_point = text_wrapper_get_offset_at_point;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx b/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
index b0edad0..0ba7fd5 100644
--- a/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
@@ -5,8 +5,1379 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atktextattributes.cxx"
#include "atktextattributes.hxx"

#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontUnderline.hpp>

#include <com/sun/star/style/CaseMap.hpp>
#include <com/sun/star/style/LineSpacing.hpp>
#include <com/sun/star/style/LineSpacingMode.hpp>
#include <com/sun/star/style/ParagraphAdjust.hpp>
#include <com/sun/star/style/TabAlign.hpp>
#include <com/sun/star/style/TabStop.hpp>

#include <com/sun/star/text/WritingMode2.hpp>

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleComponent.hpp>

#include <i18nlangtag/languagetag.hxx>
#include <vcl/svapp.hxx>
#include <vcl/outdev.hxx>

#include <stdio.h>
#include <string.h>

using namespace ::com::sun::star;

typedef gchar* (* AtkTextAttrFunc)       ( const uno::Any& rAny );
typedef bool   (* TextPropertyValueFunc) ( uno::Any& rAny, const gchar * value );

#define STRNCMP_PARAM( s )  s,sizeof( s )-1

/*****************************************************************************/

static AtkTextAttribute atk_text_attribute_paragraph_style = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_font_effect = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_decoration = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_line_height = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_rotation = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_shadow = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_tab_interval = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_tab_stops = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_writing_mode = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_vertical_align = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_misspelled = ATK_TEXT_ATTR_INVALID;
// #i92232#
static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID;
// #i92233#
static AtkTextAttribute atk_text_attribute_mm_to_pixel_ratio = ATK_TEXT_ATTR_INVALID;

/*****************************************************************************/

/**
  * !! IMPORTANT NOTE !! : when adding items to this list, KEEP THE LIST SORTED
  *                        and re-arrange the enum values accordingly.
  */

enum ExportedAttribute
{
    TEXT_ATTRIBUTE_BACKGROUND_COLOR = 0,
    TEXT_ATTRIBUTE_CASEMAP,
    TEXT_ATTRIBUTE_FOREGROUND_COLOR,
    TEXT_ATTRIBUTE_CONTOURED,
    TEXT_ATTRIBUTE_CHAR_ESCAPEMENT,
    TEXT_ATTRIBUTE_BLINKING,
    TEXT_ATTRIBUTE_FONT_NAME,
    TEXT_ATTRIBUTE_HEIGHT,
    TEXT_ATTRIBUTE_HIDDEN,
    TEXT_ATTRIBUTE_KERNING,
    TEXT_ATTRIBUTE_LOCALE,
    TEXT_ATTRIBUTE_POSTURE,
    TEXT_ATTRIBUTE_RELIEF,
    TEXT_ATTRIBUTE_ROTATION,
    TEXT_ATTRIBUTE_SCALE,
    TEXT_ATTRIBUTE_SHADOWED,
    TEXT_ATTRIBUTE_STRIKETHROUGH,
    TEXT_ATTRIBUTE_UNDERLINE,
    TEXT_ATTRIBUTE_WEIGHT,
    // #i92233#
    TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO,
    TEXT_ATTRIBUTE_JUSTIFICATION,
    TEXT_ATTRIBUTE_BOTTOM_MARGIN,
    TEXT_ATTRIBUTE_FIRST_LINE_INDENT,
    TEXT_ATTRIBUTE_LEFT_MARGIN,
    TEXT_ATTRIBUTE_LINE_SPACING,
    TEXT_ATTRIBUTE_RIGHT_MARGIN,
    TEXT_ATTRIBUTE_STYLE_NAME,
    TEXT_ATTRIBUTE_TAB_STOPS,
    TEXT_ATTRIBUTE_TOP_MARGIN,
    TEXT_ATTRIBUTE_WRITING_MODE,
    TEXT_ATTRIBUTE_LAST
};

static const char * ExportedTextAttributes[TEXT_ATTRIBUTE_LAST] =
{
    "CharBackColor",        // TEXT_ATTRIBUTE_BACKGROUND_COLOR
    "CharCaseMap",          // TEXT_ATTRIBUTE_CASEMAP
    "CharColor",            // TEXT_ATTRIBUTE_FOREGROUND_COLOR
    "CharContoured",        // TEXT_ATTRIBUTE_CONTOURED
    "CharEscapement",       // TEXT_ATTRIBUTE_CHAR_ESCAPEMENT
    "CharFlash",            // TEXT_ATTRIBUTE_BLINKING
    "CharFontName",         // TEXT_ATTRIBUTE_FONT_NAME
    "CharHeight",           // TEXT_ATTRIBUTE_HEIGHT
    "CharHidden",           // TEXT_ATTRIBUTE_HIDDEN
    "CharKerning",          // TEXT_ATTRIBUTE_KERNING
    "CharLocale",           // TEXT_ATTRIBUTE_LOCALE
    "CharPosture",          // TEXT_ATTRIBUTE_POSTURE
    "CharRelief",           // TEXT_ATTRIBUTE_RELIEF
    "CharRotation",         // TEXT_ATTRIBUTE_ROTATION
    "CharScaleWidth",       // TEXT_ATTRIBUTE_SCALE
    "CharShadowed",         // TEXT_ATTRIBUTE_SHADOWED
    "CharStrikeout",        // TEXT_ATTRIBUTE_STRIKETHROUGH
    "CharUnderline",        // TEXT_ATTRIBUTE_UNDERLINE
    "CharWeight",           // TEXT_ATTRIBUTE_WEIGHT
    // #i92233#
    "MMToPixelRatio",       // TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO
    "ParaAdjust",           // TEXT_ATTRIBUTE_JUSTIFICATION
    "ParaBottomMargin",     // TEXT_ATTRIBUTE_BOTTOM_MARGIN
    "ParaFirstLineIndent",  // TEXT_ATTRIBUTE_FIRST_LINE_INDENT
    "ParaLeftMargin",       // TEXT_ATTRIBUTE_LEFT_MARGIN
    "ParaLineSpacing",      // TEXT_ATTRIBUTE_LINE_SPACING
    "ParaRightMargin",      // TEXT_ATTRIBUTE_RIGHT_MARGIN
    "ParaStyleName",        // TEXT_ATTRIBUTE_STYLE_NAME
    "ParaTabStops",         // TEXT_ATTRIBUTE_TAB_STOPS
    "ParaTopMargin",        // TEXT_ATTRIBUTE_TOP_MARGIN
    "WritingMode"           // TEXT_ATTRIBUTE_WRITING_MODE
};

/*****************************************************************************/

static gchar*
get_value( const uno::Sequence< beans::PropertyValue >& rAttributeList,
           sal_Int32 nIndex, AtkTextAttrFunc func )
{
    if( nIndex != -1 )
        return func(rAttributeList[nIndex].Value);

    return nullptr;
}

#define get_bool_value( list, index ) get_value( list, index, Bool2String )
#define get_height_value( list, index ) get_value( list, index, Float2String )
#define get_justification_value( list, index ) get_value( list, index, Adjust2Justification )
#define get_cmm_value( list, index ) get_value( list, index, CMM2UnitString )
#define get_scale_width( list, index ) get_value( list, index, Scale2String )
#define get_strikethrough_value( list, index ) get_value( list, index, Strikeout2String )
#define get_string_value( list, index ) get_value( list, index, GetString )
#define get_style_value( list, index ) get_value( list, index, FontSlant2Style )
#define get_underline_value( list, index ) get_value( list, index, Underline2String )
#define get_variant_value( list, index ) get_value( list, index, CaseMap2String )
#define get_weight_value( list, index ) get_value( list, index, Weight2String )
#define get_language_string( list, index ) get_value( list, index, Locale2String )

static double toPoint(sal_Int16 n)
{
    // 100th mm -> pt
    return static_cast<double>(n * 72) / 2540;
}

/*****************************************************************************/

static bool
InvalidValue( uno::Any&, const gchar * )
{
    return false;
}

/*****************************************************************************/

static gchar*
Float2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%g", rAny.get<float>() );
}

static bool
String2Float( uno::Any& rAny, const gchar * value )
{
    float fval;

    if( 1 != sscanf( value, "%g", &fval ) )
        return false;

    rAny <<= fval;
    return true;
}

/*****************************************************************************/

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleComponent>
    getComponent( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpComponent.is() )
        {
            pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpComponent;
    }

    return css::uno::Reference<css::accessibility::XAccessibleComponent>();
}

static gchar*
get_color_value(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                const sal_Int32 * pIndexArray,
                ExportedAttribute attr,
                AtkText * text)
{
    sal_Int32 nColor = -1; // AUTOMATIC
    sal_Int32 nIndex = pIndexArray[attr];

    if( nIndex != -1 )
        nColor = rAttributeList[nIndex].Value.get<sal_Int32>();

    /*
     * Check for color value for 100% alpha white, which means
     * "automatic". Grab the RGB value from XAccessibleComponent
     * in this case.
     */

    if( (nColor == -1) && text )
    {
        try
        {
            css::uno::Reference<css::accessibility::XAccessibleComponent>
                pComponent = getComponent( text );
            if( pComponent.is() )
            {
                switch( attr )
                {
                    case TEXT_ATTRIBUTE_BACKGROUND_COLOR:
                        nColor = pComponent->getBackground();
                        break;
                    case TEXT_ATTRIBUTE_FOREGROUND_COLOR:
                        nColor = pComponent->getForeground();
                        break;
                    default:
                        break;
                }
            }
        }

        catch(const uno::Exception&) {
            g_warning( "Exception in get[Fore|Back]groundColor()" );
        }
    }

    if( nColor != -1 )
    {
        sal_uInt8 blue  = nColor & 0xFF;
        sal_uInt8 green = (nColor >> 8) & 0xFF;
        sal_uInt8 red   = (nColor >> 16) & 0xFF;

        return g_strdup_printf( "%u,%u,%u", red, green, blue );
    }

    return nullptr;
}

static bool
String2Color( uno::Any& rAny, const gchar * value )
{
    int red, green, blue;

    if( 3 != sscanf( value, "%d,%d,%d", &red, &green, &blue ) )
        return false;

    sal_Int32 nColor = static_cast<sal_Int32>(blue) | ( static_cast<sal_Int32>(green) << 8 ) | ( static_cast<sal_Int32>(red) << 16 );
    rAny <<= nColor;
    return true;
}

/*****************************************************************************/

static gchar*
FontSlant2Style(const uno::Any& rAny)
{
    const gchar * value = nullptr;

    awt::FontSlant aFontSlant;
    if(!(rAny >>= aFontSlant))
        return nullptr;

    switch( aFontSlant )
    {
        case awt::FontSlant_NONE:
            value = "normal";
            break;

        case awt::FontSlant_OBLIQUE:
            value = "oblique";
            break;

        case awt::FontSlant_ITALIC:
            value = "italic";
            break;

        case awt::FontSlant_REVERSE_OBLIQUE:
            value = "reverse oblique";
            break;

        case awt::FontSlant_REVERSE_ITALIC:
            value = "reverse italic";
            break;

        default:
            break;
    }

    if( value )
         return g_strdup( value );

    return nullptr;
}

static bool
Style2FontSlant( uno::Any& rAny, const gchar * value )
{
    awt::FontSlant aFontSlant;

    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
        aFontSlant = awt::FontSlant_NONE;
    else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) == 0 )
        aFontSlant = awt::FontSlant_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) == 0 )
        aFontSlant = awt::FontSlant_ITALIC;
    else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) == 0 )
        aFontSlant = awt::FontSlant_REVERSE_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) == 0 )
        aFontSlant = awt::FontSlant_REVERSE_ITALIC;
    else
        return false;

    rAny <<= aFontSlant;
    return true;
}

/*****************************************************************************/

static gchar*
Weight2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%g", rAny.get<float>() * 4 );
}

static bool
String2Weight( uno::Any& rAny, const gchar * value )
{
    float weight;

    if( 1 != sscanf( value, "%g", &weight ) )
        return false;

    rAny <<= weight / 4;
    return true;
}

/*****************************************************************************/

static gchar*
Adjust2Justification(const uno::Any& rAny)
{
    const gchar * value = nullptr;

    switch( static_cast<style::ParagraphAdjust>(rAny.get<short>()) )
    {
        case style::ParagraphAdjust_LEFT:
            value = "left";
            break;

        case style::ParagraphAdjust_RIGHT:
            value = "right";
            break;

        case style::ParagraphAdjust_BLOCK:
        case style::ParagraphAdjust_STRETCH:
            value = "fill";
            break;

        case style::ParagraphAdjust_CENTER:
            value = "center";
            break;

        default:
            break;
    }

    if( value )
        return g_strdup( value );

    return nullptr;
}

static bool
Justification2Adjust( uno::Any& rAny, const gchar * value )
{
    style::ParagraphAdjust nParagraphAdjust;

    if( strncmp( value, STRNCMP_PARAM( "left" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_LEFT;
    else if( strncmp( value, STRNCMP_PARAM( "right" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_RIGHT;
    else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_BLOCK;
    else if( strncmp( value, STRNCMP_PARAM( "center" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_CENTER;
    else
        return false;

    rAny <<= static_cast<short>(nParagraphAdjust);
    return true;
}

/*****************************************************************************/

const gchar * const font_strikethrough[] = {
    "none",   // FontStrikeout::NONE
    "single", // FontStrikeout::SINGLE
    "double", // FontStrikeout::DOUBLE
    nullptr,     // FontStrikeout::DONTKNOW
    "bold",   // FontStrikeout::BOLD
    "with /", // FontStrikeout::SLASH
    "with X"  // FontStrikeout::X
};

static gchar*
Strikeout2String(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( n >= 0 && n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)) )
        return g_strdup( font_strikethrough[n] );

    return nullptr;
}

static bool
String2Strikeout( uno::Any& rAny, const gchar * value )
{
    for( sal_Int16 n=0; n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)); ++n )
    {
        if( ( nullptr != font_strikethrough[n] ) &&
            0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) )
        {
            rAny <<= n;
            return true;
        }
    }

    return false;
}

/*****************************************************************************/

static gchar*
Underline2String(const uno::Any& rAny)
{
    const gchar * value = nullptr;

    switch( rAny.get<sal_Int16>() )
    {
        case awt::FontUnderline::NONE:
            value = "none";
            break;

        case awt::FontUnderline::SINGLE:
            value = "single";
            break;

        case awt::FontUnderline::DOUBLE:
            value = "double";
            break;

        default:
            break;
    }

    if( value )
        return g_strdup( value );

    return nullptr;
}

static bool
String2Underline( uno::Any& rAny, const gchar * value )
{
    short nUnderline;

    if( strncmp( value, STRNCMP_PARAM( "none" ) ) == 0 )
        nUnderline = awt::FontUnderline::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "single" ) ) == 0 )
        nUnderline = awt::FontUnderline::SINGLE;
    else if( strncmp( value, STRNCMP_PARAM( "double" ) ) == 0 )
        nUnderline = awt::FontUnderline::DOUBLE;
    else
        return false;

    rAny <<= nUnderline;
    return true;
}

/*****************************************************************************/

static gchar*
GetString(const uno::Any& rAny)
{
    OString aFontName = OUStringToOString( rAny.get< OUString > (), RTL_TEXTENCODING_UTF8 );

    if( !aFontName.isEmpty() )
        return g_strdup( aFontName.getStr() );

    return nullptr;
}

static bool
SetString( uno::Any& rAny, const gchar * value )
{
    OString aFontName( value );

    if( !aFontName.isEmpty() )
    {
        rAny <<= OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 );
        return true;
    }

    return false;
}

/*****************************************************************************/

// @see http://developer.gnome.org/doc/API/2.0/atk/AtkText.html#AtkTextAttribute

// CMM = 100th of mm
static gchar*
CMM2UnitString(const uno::Any& rAny)
{
    double fValue = rAny.get<sal_Int32>();
    fValue = fValue * 0.01;

    return g_strdup_printf( "%gmm", fValue );
}

static bool
UnitString2CMM( uno::Any& rAny, const gchar * value )
{
    float fValue = 0.0; // pb: don't use double here because of warning on linux

    if( 1 != sscanf( value, "%gmm", &fValue ) )
        return false;

    fValue = fValue * 100;

    rAny <<= static_cast<sal_Int32>(fValue);
    return true;
}

/*****************************************************************************/

static const gchar * bool_values[] = { "true", "false" };

static gchar *
Bool2String( const uno::Any& rAny )
{
    int n = 1;

    if( rAny.get<bool>() )
        n = 0;

    return g_strdup( bool_values[n] );
}

static bool
String2Bool( uno::Any& rAny, const gchar * value )
{
    bool bValue;

    if( strncmp( value, STRNCMP_PARAM( "true" ) ) == 0 )
        bValue = true;
    else if( strncmp( value, STRNCMP_PARAM( "false" ) ) == 0 )
        bValue = false;
    else
        return false;

    rAny <<= bValue;
    return true;
}

/*****************************************************************************/

static gchar*
Scale2String( const uno::Any& rAny )
{
    return g_strdup_printf( "%g", static_cast<double>(rAny.get< sal_Int16 > ()) / 100 );
}

static bool
String2Scale( uno::Any& rAny, const gchar * value )
{
    double dval;

    if( 1 != sscanf( value, "%lg", &dval ) )
        return false;

    rAny <<= static_cast<sal_Int16>(dval * 100);
    return true;
}

/*****************************************************************************/

static gchar *
CaseMap2String( const uno::Any& rAny )
{
    const gchar * value;

    switch( rAny.get<short>() )
    {
        case style::CaseMap::SMALLCAPS:
            value = "small_caps";
            break;

        default:
            value = "normal";
            break;
    }

    return g_strdup(value);
}

static bool
String2CaseMap( uno::Any& rAny, const gchar * value )
{
    short nCaseMap;

    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
        nCaseMap = style::CaseMap::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) == 0 )
        nCaseMap = style::CaseMap::SMALLCAPS;
    else
        return false;

    rAny <<= nCaseMap;
    return true;
}

/*****************************************************************************/

const gchar * const font_stretch[] = {
    "ultra_condensed",
    "extra_condensed",
    "condensed",
    "semi_condensed",
    "normal",
    "semi_expanded",
    "expanded",
    "extra_expanded",
    "ultra_expanded"
};

static gchar*
Kerning2Stretch(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();
    int i = 4;

    // No good idea for a mapping - just return the basic info
    if( n < 0 )
        i=2;
    else if( n > 0 )
        i=6;

    return g_strdup(font_stretch[i]);
}

/*****************************************************************************/

static gchar*
Locale2String(const uno::Any& rAny)
{
    /* FIXME-BCP47: support language tags? And why is country lowercase? */
    lang::Locale aLocale = rAny.get<lang::Locale> ();
    LanguageTag aLanguageTag( aLocale);
    return g_strdup_printf( "%s-%s",
        OUStringToOString( aLanguageTag.getLanguage(), RTL_TEXTENCODING_ASCII_US).getStr(),
        OUStringToOString( aLanguageTag.getCountry(), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() );
}

static bool
String2Locale( uno::Any& rAny, const gchar * value )
{
    /* FIXME-BCP47: support language tags? */
    bool ret = false;

    gchar ** str_array = g_strsplit_set( value, "-.@", -1 );
    if( str_array[0] != nullptr )
    {
        ret = true;

        lang::Locale aLocale;

        aLocale.Language = OUString::createFromAscii(str_array[0]);
        if( str_array[1] != nullptr )
        {
            gchar * country = g_ascii_strup(str_array[1], -1);
            aLocale.Country = OUString::createFromAscii(country);
            g_free(country);
        }

        rAny <<= aLocale;
    }

    g_strfreev(str_array);
    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-effect-prop
static const gchar * relief[] = { "none", "emboss", "engrave" };
static const gchar * const outline  = "outline";

static gchar *
get_font_effect(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                sal_Int32 nContourIndex, sal_Int32 nReliefIndex)
{
    if( nContourIndex != -1 )
    {
        if( rAttributeList[nContourIndex].Value.get<bool>() )
            return g_strdup(outline);
    }

    if( nReliefIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nReliefIndex].Value.get<sal_Int16>();
        if( n <  3)
            return g_strdup(relief[n]);
    }

    return nullptr;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/text.html#lining-striking-props

enum
{
    DECORATION_NONE = 0,
    DECORATION_BLINK,
    DECORATION_UNDERLINE,
    DECORATION_LINE_THROUGH
};

static const gchar * decorations[] = { "none", "blink", "underline", "line-through" };

static gchar *
get_text_decoration(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                    sal_Int32 nBlinkIndex, sal_Int32 nUnderlineIndex,
                    sal_Int16 nStrikeoutIndex)
{
    gchar * value_list[4] = { nullptr, nullptr, nullptr, nullptr };
    gint count = 0;

    // no property value found
    if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1))
        return nullptr;

    if( nBlinkIndex != -1 )
    {
        if( rAttributeList[nBlinkIndex].Value.get<bool>() )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_BLINK]);
    }
    if( nUnderlineIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get<sal_Int16> ();
        if( n != awt::FontUnderline::NONE )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_UNDERLINE]);
    }
    if( nStrikeoutIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get<sal_Int16> ();
        if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_LINE_THROUGH]);
    }

    if( count == 0 )
        value_list[count++] = const_cast <gchar *> (decorations[DECORATION_NONE]);

    return g_strjoinv(" ", value_list);
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/text.html#propdef-text-shadow

static const gchar * shadow_values[] = { "none", "black" };

static gchar *
Bool2Shadow( const uno::Any& rAny )
{
    int n = 0;

    if( rAny.get<bool>() )
        n = 1;

    return g_strdup( shadow_values[n] );
}

/*****************************************************************************/

static gchar *
Short2Degree( const uno::Any& rAny )
{
    float f = rAny.get<sal_Int16>() / 10.0;
    return g_strdup_printf( "%g", f );
}

/*****************************************************************************/

const gchar * const directions[] = { "ltr", "rtl", "rtl", "ltr", "none" };

static gchar *
WritingMode2Direction( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( 0 <= n && n <= text::WritingMode2::PAGE )
        return g_strdup(directions[n]);

    return nullptr;
}

// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection

const gchar * const writing_modes[] = { "lr-tb", "rl-tb", "tb-rl", "tb-lr", "none" };
static gchar *
WritingMode2String( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( 0 <= n && n <= text::WritingMode2::PAGE )
        return g_strdup(writing_modes[n]);

    return nullptr;
}

/*****************************************************************************/

const char * const baseline_values[] = { "baseline", "sub", "super" };

// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-vertical-align
static gchar *
Escapement2VerticalAlign( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();
    gchar * ret = nullptr;

    // Values are in %, 101% means "automatic"
    if( n == 0 )
        ret = g_strdup(baseline_values[0]);
    else if( n == 101 )
        ret = g_strdup(baseline_values[2]);
    else if( n == -101 )
        ret = g_strdup(baseline_values[1]);
    else
        ret = g_strdup_printf( "%d%%", n );

    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-line-height
static gchar *
LineSpacing2LineHeight( const uno::Any& rAny )
{
    style::LineSpacing ls;
    gchar * ret = nullptr;

    if( rAny >>= ls )
    {
        if( ls.Mode == style::LineSpacingMode::PROP )
            ret = g_strdup_printf( "%d%%", ls.Height );
        else if( ls.Mode == style::LineSpacingMode::FIX )
            ret = g_strdup_printf( "%.3gpt", toPoint(ls.Height) );
    }

    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/People/howcome/t/970224HTMLERB-CSS/WD-tabs-970117.html
static gchar *
TabStopList2String( const uno::Any& rAny, bool default_tabs )
{
    uno::Sequence< style::TabStop > theTabStops;
    gchar * ret = nullptr;

    if( rAny >>= theTabStops)
    {
        sal_Unicode lastFillChar = ' ';

        for( const auto& rTabStop : std::as_const(theTabStops) )
        {
            bool is_default_tab = (style::TabAlign_DEFAULT == rTabStop.Alignment);

            if( is_default_tab != default_tabs )
                continue;

            double fValue = rTabStop.Position;
            fValue = fValue * 0.01;

            const gchar * tab_align = "";
            switch( rTabStop.Alignment )
            {
                case style::TabAlign_LEFT :
                    tab_align = "left ";
                    break;
                case style::TabAlign_CENTER :
                    tab_align = "center ";
                    break;
                case style::TabAlign_RIGHT :
                    tab_align = "right ";
                    break;
                case style::TabAlign_DECIMAL :
                    tab_align = "decimal ";
                    break;
                default:
                    break;
            }

            const gchar * lead_char = "";

            if( rTabStop.FillChar != lastFillChar )
            {
                lastFillChar = rTabStop.FillChar;
                switch (lastFillChar)
                {
                    case ' ':
                        lead_char = "blank ";
                        break;

                    case '.':
                        lead_char = "dotted ";
                        break;

                    case '-':
                        lead_char = "dashed ";
                        break;

                    case '_':
                        lead_char = "lined ";
                        break;

                    default:
                        lead_char = "custom ";
                        break;
                }
            }

            gchar * tab_str = g_strdup_printf( "%s%s%gmm", lead_char, tab_align, fValue );

            if( ret )
            {
                gchar * old_tab_str = ret;
                ret = g_strconcat(old_tab_str, " ", tab_str, nullptr);
                g_free( old_tab_str );
            }
            else
                ret = tab_str;
        }
    }

    return ret;
}

static gchar *
TabStops2String( const uno::Any& rAny )
{
    return TabStopList2String(rAny, false);
}

static gchar *
DefaultTabStops2String( const uno::Any& rAny )
{
    return TabStopList2String(rAny, true);
}

/*****************************************************************************/

extern "C" {

static int
attr_compare(const void *p1,const void *p2)
{
    const rtl_uString * pustr = static_cast<const rtl_uString *>(p1);
    const char * pc = *static_cast<const char * const *>(p2);

    return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc);
}

}

static void
find_exported_attributes( sal_Int32 *pArray,
    const css::uno::Sequence< css::beans::PropertyValue >& rAttributeList )
{
    for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ )
    {
        const char ** pAttr = static_cast<const char **>(bsearch(rAttributeList[i].Name.pData,
            ExportedTextAttributes, TEXT_ATTRIBUTE_LAST, sizeof(const char *),
            attr_compare));

        if( pAttr )
        {
            sal_Int32 nIndex = pAttr - ExportedTextAttributes;
            pArray[nIndex] = i;
        }
    }
}

/*****************************************************************************/

static AtkAttributeSet*
attribute_set_prepend( AtkAttributeSet* attribute_set,
                       AtkTextAttribute attribute,
                       gchar * value )
{
    if( value )
    {
        AtkAttribute *at = static_cast<AtkAttribute *>(g_malloc( sizeof (AtkAttribute) ));
        at->name  = g_strdup( atk_text_attribute_get_name( attribute ) );
        at->value = value;

        return g_slist_prepend(attribute_set, at);
    }

    return attribute_set;
}

/*****************************************************************************/

AtkAttributeSet*
attribute_set_new_from_property_values(
    const uno::Sequence< beans::PropertyValue >& rAttributeList,
    bool run_attributes_only,
    AtkText *text)
{
    AtkAttributeSet* attribute_set = nullptr;

    sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 };

    // Initialize index array with -1
    for(sal_Int32 & rn : aIndexList)
        rn = -1;

    find_exported_attributes(aIndexList, rAttributeList);

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_BG_COLOR,
        get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_BACKGROUND_COLOR, run_attributes_only ? nullptr : text ) );

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FG_COLOR,
        get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_FOREGROUND_COLOR, run_attributes_only ? nullptr : text) );

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INVISIBLE,
        get_bool_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HIDDEN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_UNDERLINE,
        get_underline_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_UNDERLINE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRIKETHROUGH,
        get_strikethrough_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SIZE,
        get_height_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HEIGHT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_WEIGHT,
        get_weight_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WEIGHT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FAMILY_NAME,
        get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FONT_NAME]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_VARIANT,
        get_variant_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CASEMAP]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STYLE,
        get_style_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_POSTURE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SCALE,
        get_scale_width(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SCALE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LANGUAGE,
        get_language_string(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LOCALE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_DIRECTION,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2Direction));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRETCH,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_KERNING], Kerning2Stretch));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_font_effect )
        atk_text_attribute_font_effect = atk_text_attribute_register("font-effect");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_font_effect,
        get_font_effect(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CONTOURED], aIndexList[TEXT_ATTRIBUTE_RELIEF]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_decoration )
        atk_text_attribute_decoration = atk_text_attribute_register("text-decoration");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_decoration,
        get_text_decoration(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BLINKING],
            aIndexList[TEXT_ATTRIBUTE_UNDERLINE], aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_rotation )
        atk_text_attribute_rotation = atk_text_attribute_register("text-rotation");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_rotation,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_ROTATION], Short2Degree));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_shadow )
        atk_text_attribute_shadow = atk_text_attribute_register("text-shadow");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_shadow,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SHADOWED], Bool2Shadow));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_writing_mode )
        atk_text_attribute_writing_mode = atk_text_attribute_register("writing-mode");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_writing_mode,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2String));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_vertical_align )
        atk_text_attribute_vertical_align = atk_text_attribute_register("vertical-align");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_vertical_align,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CHAR_ESCAPEMENT], Escapement2VerticalAlign));

    if( run_attributes_only )
        return attribute_set;

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LEFT_MARGIN,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LEFT_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_RIGHT_MARGIN,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_RIGHT_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INDENT,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FIRST_LINE_INDENT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TOP_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BOTTOM_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_JUSTIFICATION,
        get_justification_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_JUSTIFICATION]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_paragraph_style )
        atk_text_attribute_paragraph_style = atk_text_attribute_register("paragraph-style");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_paragraph_style,
        get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STYLE_NAME]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_line_height )
        atk_text_attribute_line_height = atk_text_attribute_register("line-height");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_line_height,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LINE_SPACING], LineSpacing2LineHeight));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_interval )
        atk_text_attribute_tab_interval = atk_text_attribute_register("tab-interval");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_interval,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], DefaultTabStops2String));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_stops )
        atk_text_attribute_tab_stops = atk_text_attribute_register("tab-stops");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_stops,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], TabStops2String));

    // #i92233#
    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_mm_to_pixel_ratio )
        atk_text_attribute_mm_to_pixel_ratio = atk_text_attribute_register("mm-to-pixel-ratio");

    attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_mm_to_pixel_ratio,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO], Float2String));

    return attribute_set;
}

AtkAttributeSet*
attribute_set_new_from_extended_attributes(
    const css::uno::Reference< css::accessibility::XAccessibleExtendedAttributes >& rExtendedAttributes )
{
    AtkAttributeSet *pSet = nullptr;

    // extended attributes is a string of colon-separated pairs of property and value,
    // with pairs separated by semicolons. Example: "heading-level:2;weight:bold;"
    uno::Any anyVal = rExtendedAttributes->getExtendedAttributes();
    OUString sExtendedAttrs;
    anyVal >>= sExtendedAttrs;
    sal_Int32 nIndex = 0;
    do
    {
        OUString sProperty = sExtendedAttrs.getToken( 0, ';', nIndex );

        sal_Int32 nColonPos = 0;
        OString sPropertyName = OUStringToOString( sProperty.getToken( 0, ':', nColonPos ),
                                                   RTL_TEXTENCODING_UTF8 );
        OString sPropertyValue = OUStringToOString( sProperty.getToken( 0, ':', nColonPos ),
                                                    RTL_TEXTENCODING_UTF8 );

        pSet = attribute_set_prepend( pSet,
                                      atk_text_attribute_register( sPropertyName.getStr() ),
                                      g_strdup_printf( "%s", sPropertyValue.getStr() ) );
    }
    while ( nIndex >= 0 && nIndex < sExtendedAttrs.getLength() );

    return pSet;
}

AtkAttributeSet* attribute_set_prepend_misspelled( AtkAttributeSet* attribute_set )
{
    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_misspelled )
        atk_text_attribute_misspelled = atk_text_attribute_register( "text-spelling" );

    attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_misspelled,
        g_strdup_printf( "misspelled" ) );

    return attribute_set;
}

// #i92232#
AtkAttributeSet* attribute_set_prepend_tracked_change_insertion( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "insertion" ) );

    return attribute_set;
}

AtkAttributeSet* attribute_set_prepend_tracked_change_deletion( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "deletion" ) );

    return attribute_set;
}

AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "attribute-change" ) );

    return attribute_set;
}

/*****************************************************************************/

struct AtkTextAttrMapping
{
    const char *          name;
    TextPropertyValueFunc const toPropertyValue;
};

const AtkTextAttrMapping g_TextAttrMap[] =
{
    { "", InvalidValue },                       // ATK_TEXT_ATTR_INVALID = 0
    { "ParaLeftMargin", UnitString2CMM },       // ATK_TEXT_ATTR_LEFT_MARGIN
    { "ParaRightMargin", UnitString2CMM },      // ATK_TEXT_ATTR_RIGHT_MARGIN
    { "ParaFirstLineIndent", UnitString2CMM },  // ATK_TEXT_ATTR_INDENT
    { "CharHidden", String2Bool },              // ATK_TEXT_ATTR_INVISIBLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_EDITABLE
    { "ParaTopMargin", UnitString2CMM },        // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES
    { "ParaBottomMargin", UnitString2CMM },     // ATK_TEXT_ATTR_PIXELS_BELOW_LINES
    { "", InvalidValue },                       // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP
    { "", InvalidValue },                       // ATK_TEXT_ATTR_BG_FULL_HEIGHT
    { "", InvalidValue },                       // ATK_TEXT_ATTR_RISE
    { "CharUnderline", String2Underline },      // ATK_TEXT_ATTR_UNDERLINE
    { "CharStrikeout", String2Strikeout },      // ATK_TEXT_ATTR_STRIKETHROUGH
    { "CharHeight", String2Float },             // ATK_TEXT_ATTR_SIZE
    { "CharScaleWidth", String2Scale },         // ATK_TEXT_ATTR_SCALE
    { "CharWeight", String2Weight },            // ATK_TEXT_ATTR_WEIGHT
    { "CharLocale", String2Locale },            // ATK_TEXT_ATTR_LANGUAGE
    { "CharFontName",  SetString },             // ATK_TEXT_ATTR_FAMILY_NAME
    { "CharBackColor", String2Color },          // ATK_TEXT_ATTR_BG_COLOR
    { "CharColor", String2Color },              // ATK_TEXT_ATTR_FG_COLOR
    { "", InvalidValue },                       // ATK_TEXT_ATTR_BG_STIPPLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_FG_STIPPLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_WRAP_MODE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_DIRECTION
    { "ParaAdjust", Justification2Adjust },     // ATK_TEXT_ATTR_JUSTIFICATION
    { "", InvalidValue },                       // ATK_TEXT_ATTR_STRETCH
    { "CharCaseMap", String2CaseMap },          // ATK_TEXT_ATTR_VARIANT
    { "CharPosture", Style2FontSlant }          // ATK_TEXT_ATTR_STYLE
};

/*****************************************************************************/

bool
attribute_set_map_to_property_values(
    AtkAttributeSet* attribute_set,
    uno::Sequence< beans::PropertyValue >& rValueList )
{
    // Ensure enough space ..
    uno::Sequence< beans::PropertyValue > aAttributeList (SAL_N_ELEMENTS(g_TextAttrMap));

    sal_Int32 nIndex = 0;
    for( GSList * item = attribute_set; item != nullptr; item = g_slist_next( item ) )
    {
        AtkAttribute* attribute = reinterpret_cast<AtkAttribute *>(item);

        AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name );
        if( text_attr < SAL_N_ELEMENTS(g_TextAttrMap) )
        {
            if( g_TextAttrMap[text_attr].name[0] != '\0' )
            {
                if( ! g_TextAttrMap[text_attr].toPropertyValue( aAttributeList[nIndex].Value, attribute->value) )
                    return false;

                aAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name );
                aAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE;
                ++nIndex;
            }
        }
        else
        {
            // Unsupported text attribute
            return false;
        }
    }

    aAttributeList.realloc( nIndex );
    rValueList = aAttributeList;
    return true;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkutil.cxx b/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
index 8c1eeaf..cac3ac6 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
@@ -5,8 +5,785 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkutil.cxx"
#ifdef AIX
#define _LINUX_SOURCE_COMPAT
#include <sys/timer.h>
#undef _LINUX_SOURCE_COMPAT
#endif

#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/weakref.hxx>
#include <rtl/ref.hxx>
#include <sal/log.hxx>

#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/menu.hxx>
#include <vcl/toolbox.hxx>

#include <unx/gtk/gtkdata.hxx>
#include "atkwrapper.hxx"
#include "atkutil.hxx"

#include <gtk/gtk.h>
#include <config_version.h>

#include <cassert>
#include <set>

using namespace ::com::sun::star;

namespace
{
    struct theNextFocusObject :
        public rtl::Static< uno::WeakReference< accessibility::XAccessible >, theNextFocusObject>
    {
    };
}

static guint focus_notify_handler = 0;

/*****************************************************************************/

extern "C" {

static gboolean
atk_wrapper_focus_idle_handler (gpointer data)
{
    SolarMutexGuard aGuard;

    focus_notify_handler = 0;

    uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject::get();
    if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) )
    {
        AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
        // Gail does not notify focus changes to NULL, so do we ..
        if( atk_obj )
        {
            SAL_WNODEPRECATED_DECLARATIONS_PUSH
            atk_focus_tracker_notify(atk_obj);
            SAL_WNODEPRECATED_DECLARATIONS_POP
            // #i93269#
            // emit text_caret_moved event for <XAccessibleText> object,
            // if cursor is inside the <XAccessibleText> object.
            // also emit state-changed:focused event under the same condition.
            {
                AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj);
                if( wrapper_obj && !wrapper_obj->mpText.is() )
                {
                    wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY);
                    if ( wrapper_obj->mpText.is() )
                    {
                        gint caretPos = -1;

                        try {
                            caretPos = wrapper_obj->mpText->getCaretPosition();
                        }
                        catch(const uno::Exception&) {
                            g_warning( "Exception in getCaretPosition()" );
                        }

                        if ( caretPos != -1 )
                        {
                            atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, TRUE );
                            g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos );
                        }
                    }
                }
            }
            g_object_unref(atk_obj);
        }
    }

    return false;
}

} // extern "C"

/*****************************************************************************/

static void
atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible )
{
    if( focus_notify_handler )
        g_source_remove(focus_notify_handler);

    theNextFocusObject::get() = xAccessible;

    focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
}

/*****************************************************************************/

class DocumentFocusListener :
    public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
{

    std::set< uno::Reference< uno::XInterface > > m_aRefList;

public:
    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void attachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void attachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible,
        const uno::Reference< accessibility::XAccessibleContext >& xContext
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void attachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible,
        const uno::Reference< accessibility::XAccessibleContext >& xContext,
        const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void detachRecursive(
        const uno::Reference< accessibility::XAccessible >& xAccessible
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void detachRecursive(
        const uno::Reference< accessibility::XAccessibleContext >& xContext
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    void detachRecursive(
        const uno::Reference< accessibility::XAccessibleContext >& xContext,
        const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
    );

    /// @throws lang::IndexOutOfBoundsException
    /// @throws uno::RuntimeException
    static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );

    // XEventListener
    virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;

    // XAccessibleEventListener
    virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override;
};

/*****************************************************************************/

void DocumentFocusListener::disposing( const lang::EventObject& aEvent )
{

    // Unref the object here, but do not remove as listener since the object
    // might no longer be in a state that safely allows this.
    if( aEvent.Source.is() )
        m_aRefList.erase(aEvent.Source);

}

/*****************************************************************************/

void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
{
    try {
        switch( aEvent.EventId )
        {
            case accessibility::AccessibleEventId::STATE_CHANGED:
            {
                sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
                aEvent.NewValue >>= nState;

                if( accessibility::AccessibleStateType::FOCUSED == nState )
                    atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) );

                break;
            }

            case accessibility::AccessibleEventId::CHILD:
            {
                uno::Reference< accessibility::XAccessible > xChild;
                if( (aEvent.OldValue >>= xChild) && xChild.is() )
                    detachRecursive(xChild);

                if( (aEvent.NewValue >>= xChild) && xChild.is() )
                    attachRecursive(xChild);

                break;
            }

            case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
                SAL_INFO("vcl.a11y", "Invalidate all children called");
                break;

            default:
                break;
        }
    }
    catch( const lang::IndexOutOfBoundsException& e )
    {
        g_warning("Focused object has invalid index in parent");
    }
}

/*****************************************************************************/

uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
{
    uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);

    if( xAccessible.is() )
        return xAccessible;

    uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);

    if( xContext.is() )
    {
        uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
        if( xParent.is() )
        {
            uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
            if( xParentContext.is() )
            {
                return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
            }
        }
    }

    return uno::Reference< accessibility::XAccessible >();
}

/*****************************************************************************/

void DocumentFocusListener::attachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible
)
{
    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( xContext.is() )
        attachRecursive(xAccessible, xContext);
}

/*****************************************************************************/

void DocumentFocusListener::attachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible,
    const uno::Reference< accessibility::XAccessibleContext >& xContext
)
{
    uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
        xContext->getAccessibleStateSet();

    if( xStateSet.is() )
        attachRecursive(xAccessible, xContext, xStateSet);
}

/*****************************************************************************/

void DocumentFocusListener::attachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible,
    const uno::Reference< accessibility::XAccessibleContext >& xContext,
    const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
)
{
    if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED ) )
        atk_wrapper_focus_tracker_notify_when_idle( xAccessible );

    uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);

    if (!xBroadcaster.is())
        return;

    // If not already done, add the broadcaster to the list and attach as listener.
    const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
    if( m_aRefList.insert(xInterface).second )
    {
        xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));

        if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
        {
            sal_Int32 n, nmax = xContext->getAccessibleChildCount();
            for( n = 0; n < nmax; n++ )
            {
                uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );

                if( xChild.is() )
                    attachRecursive(xChild);
            }
        }
    }
}

/*****************************************************************************/

void DocumentFocusListener::detachRecursive(
    const uno::Reference< accessibility::XAccessible >& xAccessible
)
{
    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( xContext.is() )
        detachRecursive(xContext);
}

/*****************************************************************************/

void DocumentFocusListener::detachRecursive(
    const uno::Reference< accessibility::XAccessibleContext >& xContext
)
{
    uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
        xContext->getAccessibleStateSet();

    if( xStateSet.is() )
        detachRecursive(xContext, xStateSet);
}

/*****************************************************************************/

void DocumentFocusListener::detachRecursive(
    const uno::Reference< accessibility::XAccessibleContext >& xContext,
    const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
)
{
    uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);

    if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
    {
        xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));

        if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
        {
            sal_Int32 n, nmax = xContext->getAccessibleChildCount();
            for( n = 0; n < nmax; n++ )
            {
                uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );

                if( xChild.is() )
                    detachRecursive(xChild);
            }
        }
    }
}

/*****************************************************************************/

/*
 * page tabs in gtk are widgets, so we need to simulate focus events for those
 */

static void handle_tabpage_activated(vcl::Window *pWindow)
{
    uno::Reference< accessibility::XAccessible > xAccessible =
        pWindow->GetAccessible();

    if( ! xAccessible.is() )
        return;

    uno::Reference< accessibility::XAccessibleSelection > xSelection(
        xAccessible->getAccessibleContext(), uno::UNO_QUERY);

    if( xSelection.is() )
        atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) );
}

/*****************************************************************************/

/*
 * toolbar items in gtk are widgets, so we need to simulate focus events for those
 */

static void notify_toolbox_item_focus(ToolBox *pToolBox)
{
    uno::Reference< accessibility::XAccessible > xAccessible =
        pToolBox->GetAccessible();

    if( ! xAccessible.is() )
        return;

    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( ! xContext.is() )
        return;

    ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
    if( nPos != ToolBox::ITEM_NOTFOUND )
        atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
            //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32
}

static void handle_toolbox_highlight(vcl::Window *pWindow)
{
    ToolBox *pToolBox = static_cast <ToolBox *> (pWindow);

    // Make sure either the toolbox or its parent toolbox has the focus
    if ( ! pToolBox->HasFocus() )
    {
        ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() );
        if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
            return;
    }

    notify_toolbox_item_focus(pToolBox);
}

static void handle_toolbox_highlightoff(vcl::Window const *pWindow)
{
    ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() );

    // Notify when leaving sub toolboxes
    if( pToolBoxParent && pToolBoxParent->HasFocus() )
        notify_toolbox_item_focus( pToolBoxParent );
}

/*****************************************************************************/

static void create_wrapper_for_child(
    const uno::Reference< accessibility::XAccessibleContext >& xContext,
    sal_Int32 index)
{
    if( xContext.is() )
    {
        uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index));
        if( xChild.is() )
        {
            // create the wrapper object - it will survive the unref unless it is a transient object
            g_object_unref( atk_object_wrapper_ref( xChild ) );
        }
    }
}

/*****************************************************************************/

static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent)
{
    vcl::Window* pWindow = pEvent->GetWindow();
    sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData()));

    if( pWindow && pWindow->IsReallyVisible() )
    {
        uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible());
        if( xAccessible.is() )
        {
            create_wrapper_for_child(xAccessible->getAccessibleContext(), index);
        }
    }
}

/*****************************************************************************/

namespace {

struct WindowList {
    ~WindowList() { assert(list.empty()); };
        // needs to be empty already on DeInitVCL, but at least check it's empty
        // on exit

    std::set< VclPtr<vcl::Window> > list;
};

WindowList g_aWindowList;

}

DocumentFocusListener & GtkSalData::GetDocumentFocusListener()
{
    if (!m_pDocumentFocusListener)
    {
        m_pDocumentFocusListener = new DocumentFocusListener;
        m_xDocumentFocusListener.set(m_pDocumentFocusListener);
    }
    return *m_pDocumentFocusListener;
}

static void handle_get_focus(::VclWindowEvent const * pEvent)
{
    GtkSalData *const pSalData(GetGtkSalData());
    assert(pSalData);

    DocumentFocusListener & rDocumentFocusListener(pSalData->GetDocumentFocusListener());

    vcl::Window *pWindow = pEvent->GetWindow();

    // The menu bar is handled through VclEventId::MenuHighlightED
    if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
        return;

    // ToolBoxes are handled through VclEventId::ToolboxHighlight
    if( pWindow->GetType() == WindowType::TOOLBOX )
        return;

    if( pWindow->GetType() == WindowType::TABCONTROL )
    {
        handle_tabpage_activated( pWindow );
        return;
    }

    uno::Reference< accessibility::XAccessible > xAccessible =
        pWindow->GetAccessible();

    if( ! xAccessible.is() )
        return;

    uno::Reference< accessibility::XAccessibleContext > xContext =
        xAccessible->getAccessibleContext();

    if( ! xContext.is() )
        return;

    uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
        xContext->getAccessibleStateSet();

    if( ! xStateSet.is() )
        return;

/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
 * need to add listeners to the children instead of re-using the tabpage stuff
 */
    if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED) &&
        ( pWindow->GetType() != WindowType::TREELISTBOX ) )
    {
        atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
    }
    else
    {
        if( g_aWindowList.list.insert(pWindow).second )
        {
            try
            {
                rDocumentFocusListener.attachRecursive(xAccessible, xContext, xStateSet);
            }
            catch (const uno::Exception&)
            {
                g_warning( "Exception caught processing focus events" );
            }
        }
    }
}

/*****************************************************************************/

static void handle_menu_highlighted(::VclMenuEvent const * pEvent)
{
    try
    {
        Menu* pMenu = pEvent->GetMenu();
        sal_uInt16 nPos = pEvent->GetItemPos();

        if( pMenu &&  nPos != 0xFFFF)
        {
            uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() );

            if( xAccessible.is() )
            {
                uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() );

                if( xContext.is() )
                    atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
            }
        }
    }
    catch (const uno::Exception&)
    {
        g_warning( "Exception caught processing menu highlight events" );
    }
}

/*****************************************************************************/

static void WindowEventHandler(void *, VclSimpleEvent& rEvent)
{
    try
    {
        switch (rEvent.GetId())
        {
        case VclEventId::WindowShow:
            break;
        case VclEventId::WindowHide:
            break;
        case VclEventId::WindowClose:
            break;
        case VclEventId::WindowGetFocus:
            handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent));
            break;
        case VclEventId::WindowLoseFocus:
            break;
        case VclEventId::WindowMinimize:
            break;
        case VclEventId::WindowNormalize:
            break;
        case VclEventId::WindowKeyInput:
        case VclEventId::WindowKeyUp:
        case VclEventId::WindowCommand:
        case VclEventId::WindowMouseMove:
            break;

        case VclEventId::MenuHighlight:
            if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent))
            {
                handle_menu_highlighted(pMenuEvent);
            }
            else if (const VclAccessibleEvent* pAccEvent = dynamic_cast<const VclAccessibleEvent*>(&rEvent))
            {
                const uno::Reference< accessibility::XAccessible >& xAccessible = pAccEvent->GetAccessible();
                if (xAccessible.is())
                    atk_wrapper_focus_tracker_notify_when_idle(xAccessible);
            }
            break;

        case VclEventId::ToolboxHighlight:
            handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
            break;

        case VclEventId::ToolboxButtonStateChanged:
            handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent));
            break;

        case VclEventId::ObjectDying:
            g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() );
            [[fallthrough]];
        case VclEventId::ToolboxHighlightOff:
            handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
            break;

        case VclEventId::TabpageActivate:
            handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
            break;

        case VclEventId::ComboboxSetText:
            // This looks quite strange to me. Stumbled over this when fixing #i104290#.
            // This kicked in when leaving the combobox in the toolbar, after that the events worked.
            // I guess this was a try to work around missing combobox events, which didn't do the full job, and shouldn't be necessary anymore.
            // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general.
            // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow());
            break;

        default:
            break;
        }
    }
    catch (const lang::IndexOutOfBoundsException&)
    {
        g_warning("Focused object has invalid index in parent");
    }
}

static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler );

/*****************************************************************************/

extern "C" {

static G_CONST_RETURN gchar *
ooo_atk_util_get_toolkit_name()
{
    return "VCL";
}

/*****************************************************************************/

static G_CONST_RETURN gchar *
ooo_atk_util_get_toolkit_version()
{
    return LIBO_VERSION_DOTTED;
}

/*****************************************************************************/

/*
 * GObject inheritance
 */

static void
ooo_atk_util_class_init (AtkUtilClass *)
{
    AtkUtilClass *atk_class;
    gpointer data;

    data = g_type_class_peek (ATK_TYPE_UTIL);
    atk_class = ATK_UTIL_CLASS (data);

    atk_class->get_toolkit_name = ooo_atk_util_get_toolkit_name;
    atk_class->get_toolkit_version = ooo_atk_util_get_toolkit_version;

    ooo_atk_util_ensure_event_listener();
}

} // extern "C"

void ooo_atk_util_ensure_event_listener()
{
    static bool bInited;
    if (!bInited)
    {
        Application::AddEventListener( g_aEventListenerLink );
        bInited = true;
    }
}

GType
ooo_atk_util_get_type()
{
    static GType type = 0;

    if (!type)
    {
        GType parent_type = g_type_from_name( "GailUtil" );

        if( ! parent_type )
        {
            g_warning( "Unknown type: GailUtil" );
            parent_type = ATK_TYPE_UTIL;
        }

        GTypeQuery type_query;
        g_type_query( parent_type, &type_query );

        static const GTypeInfo typeInfo =
        {
            static_cast<guint16>(type_query.class_size),
            nullptr,
            nullptr,
            reinterpret_cast<GClassInitFunc>(ooo_atk_util_class_init),
            nullptr,
            nullptr,
            static_cast<guint16>(type_query.instance_size),
            0,
            nullptr,
            nullptr
        } ;

        type = g_type_register_static (parent_type, "OOoUtil", &typeInfo, GTypeFlags(0)) ;
    }

    return type;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkvalue.cxx b/vcl/unx/gtk3/a11y/gtk3atkvalue.cxx
index 3005794..f5e45d3 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkvalue.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkvalue.cxx
@@ -5,8 +5,134 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkvalue.cxx"
#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleValue.hpp>

#include <string.h>

using namespace ::com::sun::star;

/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleValue>
    getValue( AtkValue *pValue )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pValue );
    if( pWrap )
    {
        if( !pWrap->mpValue.is() )
        {
            pWrap->mpValue.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }

        return pWrap->mpValue;
    }

    return css::uno::Reference<css::accessibility::XAccessibleValue>();
}

static void anyToGValue( const uno::Any& aAny, GValue *pValue )
{
    // FIXME: expand to lots of types etc.
    double aDouble=0;
    aAny >>= aDouble;

    memset( pValue,  0, sizeof( GValue ) );
    g_value_init( pValue, G_TYPE_DOUBLE );
    g_value_set_double( pValue, aDouble );
}

extern "C" {

static void
value_wrapper_get_current_value( AtkValue *value,
                                 GValue   *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
            anyToGValue( pValue->getCurrentValue(), gval );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }
}

static void
value_wrapper_get_maximum_value( AtkValue *value,
                                 GValue   *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
            anyToGValue( pValue->getMaximumValue(), gval );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }
}

static void
value_wrapper_get_minimum_value( AtkValue *value,
                                 GValue   *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
            anyToGValue( pValue->getMinimumValue(), gval );
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }
}

static gboolean
value_wrapper_set_current_value( AtkValue     *value,
                                 const GValue *gval )
{
    try {
        css::uno::Reference<css::accessibility::XAccessibleValue> pValue
            = getValue( value );
        if( pValue.is() )
        {
            // FIXME - this needs expanding
            double aDouble = g_value_get_double( gval );
            return pValue->setCurrentValue( uno::Any(aDouble) );
        }
    }
    catch(const uno::Exception&) {
        g_warning( "Exception in getCurrentValue()" );
    }

    return FALSE;
}

} // extern "C"

void
valueIfaceInit (AtkValueIface *iface)
{
  g_return_if_fail (iface != nullptr);

  iface->get_current_value = value_wrapper_get_current_value;
  iface->get_maximum_value = value_wrapper_get_maximum_value;
  iface->get_minimum_value = value_wrapper_get_minimum_value;
  iface->set_current_value = value_wrapper_set_current_value;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkwindow.cxx b/vcl/unx/gtk3/a11y/gtk3atkwindow.cxx
index cd8479c..eb72edf 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkwindow.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkwindow.cxx
@@ -5,8 +5,326 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkwindow.cxx"
#include <unx/gtk/gtkframe.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/popupmenuwindow.hxx>
#include <sal/log.hxx>

#include "atkwindow.hxx"
#include "atkwrapper.hxx"
#include "atkregistry.hxx"

#include <com/sun/star/accessibility/AccessibleRole.hpp>

using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;

extern "C" {

static void (* window_real_initialize) (AtkObject *obj, gpointer data) = nullptr;
static void (* window_real_finalize) (GObject *obj) = nullptr;

static void
init_from_window( AtkObject *accessible, vcl::Window const *pWindow )
{
    static AtkRole aDefaultRole = ATK_ROLE_INVALID;

    // Special role for sub-menu and combo-box popups that are exposed directly
    // by their parents already.
    if( aDefaultRole == ATK_ROLE_INVALID )
    {
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
        aDefaultRole = atk_role_register( "redundant object" );
        SAL_WNODEPRECATED_DECLARATIONS_POP
    }

    AtkRole role = aDefaultRole;

    // Determine the appropriate role for the GtkWindow
    switch( pWindow->GetAccessibleRole() )
    {
        case AccessibleRole::ALERT:
            role = ATK_ROLE_ALERT;
            break;

        case AccessibleRole::DIALOG:
            role = ATK_ROLE_DIALOG;
            break;

        case AccessibleRole::FRAME:
            role = ATK_ROLE_FRAME;
            break;

        /* Ignore window objects for sub-menus, combo- and list boxes,
         *  which are exposed as children of their parents.
         */
        case AccessibleRole::WINDOW:
        {
            WindowType type = WindowType::WINDOW;
            bool parentIsMenuFloatingWindow = false;

            vcl::Window *pParent = pWindow->GetParent();
            if( pParent ) {
                type = pParent->GetType();
                parentIsMenuFloatingWindow = pParent->IsMenuFloatingWindow();
            }

            if( (WindowType::LISTBOX != type) && (WindowType::COMBOBOX != type) &&
                (WindowType::MENUBARWINDOW != type) && ! parentIsMenuFloatingWindow )
            {
                role = ATK_ROLE_WINDOW;
            }
        }
        break;

        default:
        {
            vcl::Window *pChild = pWindow->GetWindow(GetWindowType::FirstChild);
            if( pChild )
            {
                if( WindowType::HELPTEXTWINDOW == pChild->GetType() )
                {
                    role = ATK_ROLE_TOOL_TIP;
                    pChild->SetAccessibleRole( AccessibleRole::LABEL );
                    accessible->name = g_strdup( OUStringToOString( pChild->GetText(), RTL_TEXTENCODING_UTF8 ).getStr() );
                }
                else if ( pWindow->GetType() == WindowType::BORDERWINDOW && pChild->GetType() == WindowType::FLOATINGWINDOW )
                {
                    PopupMenuFloatingWindow* p = dynamic_cast<PopupMenuFloatingWindow*>(pChild);
                    if (p && p->IsPopupMenu() && p->GetMenuStackLevel() == 0)
                    {
                        // This is a top-level menu popup.  Register it.
                        role = ATK_ROLE_POPUP_MENU;
                        pChild->SetAccessibleRole( AccessibleRole::POPUP_MENU );
                        accessible->name = g_strdup( OUStringToOString( pChild->GetText(), RTL_TEXTENCODING_UTF8 ).getStr() );
                    }
                }
            }
            break;
        }
    }

    accessible->role = role;
}

/*****************************************************************************/

static gboolean
ooo_window_wrapper_clear_focus(gpointer)
{
    SolarMutexGuard aGuard;
    SAL_WNODEPRECATED_DECLARATIONS_PUSH
    atk_focus_tracker_notify( nullptr );
    SAL_WNODEPRECATED_DECLARATIONS_POP
    return false;
}

/*****************************************************************************/

static gboolean
ooo_window_wrapper_real_focus_gtk (GtkWidget *, GdkEventFocus *)
{
    g_idle_add( ooo_window_wrapper_clear_focus, nullptr );
    return false;
}

static gboolean ooo_tooltip_map( GtkWidget* pToolTip, gpointer )
{
    AtkObject* pAccessible = gtk_widget_get_accessible( pToolTip );
    if( pAccessible )
        atk_object_notify_state_change( pAccessible, ATK_STATE_SHOWING, TRUE );
    return FALSE;
}

static gboolean ooo_tooltip_unmap( GtkWidget* pToolTip, gpointer )
{
    AtkObject* pAccessible = gtk_widget_get_accessible( pToolTip );
    if( pAccessible )
        atk_object_notify_state_change( pAccessible, ATK_STATE_SHOWING, FALSE );
    return FALSE;
}

/*****************************************************************************/

static bool
isChildPopupMenu(vcl::Window* pWindow)
{
    vcl::Window* pChild = pWindow->GetAccessibleChildWindow(0);
    if (!pChild)
        return false;

    if (WindowType::FLOATINGWINDOW != pChild->GetType())
        return false;

    PopupMenuFloatingWindow* p = dynamic_cast<PopupMenuFloatingWindow*>(pChild);
    if (!p)
        return false;

    return p->IsPopupMenu();
}

static void
ooo_window_wrapper_real_initialize(AtkObject *obj, gpointer data)
{
    window_real_initialize(obj, data);

    GtkSalFrame *pFrame = GtkSalFrame::getFromWindow( GTK_WINDOW( data ) );
    if( pFrame )
    {
        vcl::Window *pWindow = pFrame->GetWindow();
        if( pWindow )
        {
            init_from_window( obj, pWindow );

            Reference< XAccessible > xAccessible( pWindow->GetAccessible() );

            /* We need the wrapper object for the top-level XAccessible to be
             * in the wrapper registry when atk traverses the hierarchy up on
             * focus events
             */
            if( WindowType::BORDERWINDOW == pWindow->GetType() )
            {
                if ( isChildPopupMenu(pWindow) )
                {
                    AtkObject *child = atk_object_wrapper_new( xAccessible, obj );
                    ooo_wrapper_registry_add( xAccessible, child );
                }
                else
                {
                    ooo_wrapper_registry_add( xAccessible, obj );
                    g_object_set_data( G_OBJECT(obj), "ooo:atk-wrapper-key", xAccessible.get() );
                }
            }
            else
            {
                AtkObject *child = atk_object_wrapper_new( xAccessible, obj );
                child->role = ATK_ROLE_FILLER;
                if( (ATK_ROLE_DIALOG == obj->role) || (ATK_ROLE_ALERT == obj->role) )
                    child->role = ATK_ROLE_OPTION_PANE;
                ooo_wrapper_registry_add( xAccessible, child );
            }
        }
    }

    g_signal_connect_after( GTK_WIDGET( data ), "focus-out-event",
                            G_CALLBACK (ooo_window_wrapper_real_focus_gtk),
                            nullptr);

    if( obj->role == ATK_ROLE_TOOL_TIP )
    {
        g_signal_connect_after( GTK_WIDGET( data ), "map-event",
                                G_CALLBACK (ooo_tooltip_map),
                                nullptr);
        g_signal_connect_after( GTK_WIDGET( data ), "unmap-event",
                                G_CALLBACK (ooo_tooltip_unmap),
                                nullptr);
    }
}

/*****************************************************************************/

static void
ooo_window_wrapper_real_finalize (GObject *obj)
{
    ooo_wrapper_registry_remove( static_cast<XAccessible *>(g_object_get_data( obj, "ooo:atk-wrapper-key" )));
    window_real_finalize( obj );
}

/*****************************************************************************/

static void
ooo_window_wrapper_class_init (AtkObjectClass *klass, gpointer)
{
    AtkObjectClass *atk_class;
    GObjectClass *gobject_class;
    gpointer data;

    /*
     * Patch the gobject vtable of GailWindow to refer to our instance of
     * "initialize".
     */

    data = g_type_class_peek_parent( klass );
    atk_class = ATK_OBJECT_CLASS (data);

    window_real_initialize = atk_class->initialize;
    atk_class->initialize = ooo_window_wrapper_real_initialize;

    gobject_class = G_OBJECT_CLASS (data);

    window_real_finalize = gobject_class->finalize;
    gobject_class->finalize = ooo_window_wrapper_real_finalize;
}

} // extern "C"

/*****************************************************************************/

GType
ooo_window_wrapper_get_type()
{
    static GType type = 0;

    if (!type)
    {
        GType parent_type = g_type_from_name( "GailWindow" );

        if( ! parent_type )
        {
            SAL_INFO("vcl.a11y", "Unknown type: GailWindow");
            parent_type = ATK_TYPE_OBJECT;
        }

        GTypeQuery type_query;
        g_type_query( parent_type, &type_query );

        static const GTypeInfo typeInfo =
        {
            static_cast<guint16>(type_query.class_size),
            nullptr,
            nullptr,
            reinterpret_cast<GClassInitFunc>(ooo_window_wrapper_class_init),
            nullptr,
            nullptr,
            static_cast<guint16>(type_query.instance_size),
            0,
            nullptr,
            nullptr
        } ;

        type = g_type_register_static (parent_type, "OOoWindowAtkObject", &typeInfo, GTypeFlags(0)) ;
    }

    return type;
}

void restore_gail_window_vtable()
{
    AtkObjectClass *atk_class;
    gpointer data;

    GType type = g_type_from_name( "GailWindow" );

    if( type == G_TYPE_INVALID )
        return;

    data = g_type_class_peek( type );
    atk_class = ATK_OBJECT_CLASS (data);

    atk_class->initialize = window_real_initialize;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx b/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
index 3b07e95..0998553 100644
--- a/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
+++ b/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
@@ -5,8 +5,955 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../../gtk/a11y/atkwrapper.cxx"
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Type.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/accessibility/AccessibleRelation.hpp>
#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleValue.hpp>
#include <com/sun/star/accessibility/XAccessibleAction.hpp>
#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <com/sun/star/accessibility/XAccessibleContext2.hpp>
#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
#include <com/sun/star/accessibility/XAccessibleStateSet.hpp>
#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
#include <com/sun/star/accessibility/XAccessibleTable.hpp>
#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleImage.hpp>
#include <com/sun/star/accessibility/XAccessibleHyperlink.hpp>
#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>
#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
#include <com/sun/star/awt/XExtendedToolkit.hpp>
#include <com/sun/star/awt/XTopWindow.hpp>
#include <com/sun/star/awt/XTopWindowListener.hpp>
#include <com/sun/star/awt/XWindow.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/beans/Property.hpp>

#include <rtl/ref.hxx>
#include <rtl/strbuf.hxx>
#include <osl/diagnose.h>
#include <cppuhelper/factory.hxx>
#include <cppuhelper/queryinterface.hxx>

#include "atkwrapper.hxx"
#include "atkregistry.hxx"
#include "atklistener.hxx"
#include "atktextattributes.hxx"

#include <string.h>
#include <vector>

using namespace ::com::sun::star;

static GObjectClass *parent_class = nullptr;

static AtkRelationType mapRelationType( sal_Int16 nRelation )
{
    AtkRelationType type = ATK_RELATION_NULL;

    switch( nRelation )
    {
        case accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM:
            type = ATK_RELATION_FLOWS_FROM;
            break;

        case accessibility::AccessibleRelationType::CONTENT_FLOWS_TO:
            type = ATK_RELATION_FLOWS_TO;
            break;

        case accessibility::AccessibleRelationType::CONTROLLED_BY:
            type = ATK_RELATION_CONTROLLED_BY;
            break;

        case accessibility::AccessibleRelationType::CONTROLLER_FOR:
            type = ATK_RELATION_CONTROLLER_FOR;
            break;

        case accessibility::AccessibleRelationType::LABEL_FOR:
            type = ATK_RELATION_LABEL_FOR;
            break;

        case accessibility::AccessibleRelationType::LABELED_BY:
            type = ATK_RELATION_LABELLED_BY;
            break;

        case accessibility::AccessibleRelationType::MEMBER_OF:
            type = ATK_RELATION_MEMBER_OF;
            break;

        case accessibility::AccessibleRelationType::SUB_WINDOW_OF:
            type = ATK_RELATION_SUBWINDOW_OF;
            break;

        case accessibility::AccessibleRelationType::NODE_CHILD_OF:
            type = ATK_RELATION_NODE_CHILD_OF;
            break;

        default:
            break;
    }

    return type;
}

AtkStateType mapAtkState( sal_Int16 nState )
{
    AtkStateType type = ATK_STATE_INVALID;

    // A perfect / complete mapping ...
    switch( nState )
    {
#define MAP_DIRECT( a ) \
        case accessibility::AccessibleStateType::a: \
            type = ATK_STATE_##a; break

        MAP_DIRECT( INVALID );
        MAP_DIRECT( ACTIVE );
        MAP_DIRECT( ARMED );
        MAP_DIRECT( BUSY );
        MAP_DIRECT( CHECKED );
        MAP_DIRECT( EDITABLE );
        MAP_DIRECT( ENABLED );
        MAP_DIRECT( EXPANDABLE );
        MAP_DIRECT( EXPANDED );
        MAP_DIRECT( FOCUSABLE );
        MAP_DIRECT( FOCUSED );
        MAP_DIRECT( HORIZONTAL );
        MAP_DIRECT( ICONIFIED );
        MAP_DIRECT( INDETERMINATE );
        MAP_DIRECT( MANAGES_DESCENDANTS );
        MAP_DIRECT( MODAL );
        MAP_DIRECT( MULTI_LINE );
        MAP_DIRECT( OPAQUE );
        MAP_DIRECT( PRESSED );
        MAP_DIRECT( RESIZABLE );
        MAP_DIRECT( SELECTABLE );
        MAP_DIRECT( SELECTED );
        MAP_DIRECT( SENSITIVE );
        MAP_DIRECT( SHOWING );
        MAP_DIRECT( SINGLE_LINE );
        MAP_DIRECT( STALE );
        MAP_DIRECT( TRANSIENT );
        MAP_DIRECT( VERTICAL );
        MAP_DIRECT( VISIBLE );
        MAP_DIRECT( DEFAULT );
        // a spelling error ...
        case accessibility::AccessibleStateType::DEFUNC:
            type = ATK_STATE_DEFUNCT; break;
        case accessibility::AccessibleStateType::MULTI_SELECTABLE:
            type = ATK_STATE_MULTISELECTABLE; break;
    default:
        //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped
        //NOTE! Do not report it
        type = ATK_STATE_LAST_DEFINED;
        break;
    }

    return type;
}

static AtkRole getRoleForName( const gchar * name )
{
    AtkRole ret = atk_role_for_name( name );
    if( ATK_ROLE_INVALID == ret )
    {
        // this should only happen in old ATK versions
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
        ret = atk_role_register( name );
        SAL_WNODEPRECATED_DECLARATIONS_POP
    }

    return ret;
}

static AtkRole mapToAtkRole( sal_Int16 nRole )
{
    AtkRole role = ATK_ROLE_UNKNOWN;

    static AtkRole roleMap[] = {
        ATK_ROLE_UNKNOWN,
        ATK_ROLE_ALERT,
        ATK_ROLE_COLUMN_HEADER,
        ATK_ROLE_CANVAS,
        ATK_ROLE_CHECK_BOX,
        ATK_ROLE_CHECK_MENU_ITEM,
        ATK_ROLE_COLOR_CHOOSER,
        ATK_ROLE_COMBO_BOX,
        ATK_ROLE_DATE_EDITOR,
        ATK_ROLE_DESKTOP_ICON,
        ATK_ROLE_DESKTOP_FRAME,   // ? pane
        ATK_ROLE_DIRECTORY_PANE,
        ATK_ROLE_DIALOG,
        ATK_ROLE_UNKNOWN,         // DOCUMENT - registered below
        ATK_ROLE_UNKNOWN,         // EMBEDDED_OBJECT - registered below
        ATK_ROLE_UNKNOWN,         // END_NOTE - registered below
        ATK_ROLE_FILE_CHOOSER,
        ATK_ROLE_FILLER,
        ATK_ROLE_FONT_CHOOSER,
        ATK_ROLE_FOOTER,
        ATK_ROLE_UNKNOWN,         // FOOTNOTE - registered below
        ATK_ROLE_FRAME,
        ATK_ROLE_GLASS_PANE,
        ATK_ROLE_IMAGE,           // GRAPHIC
        ATK_ROLE_UNKNOWN,         // GROUP_BOX - registered below
        ATK_ROLE_HEADER,
        ATK_ROLE_HEADING,
        ATK_ROLE_TEXT,            // HYPER_LINK - registered below
        ATK_ROLE_ICON,
        ATK_ROLE_INTERNAL_FRAME,
        ATK_ROLE_LABEL,
        ATK_ROLE_LAYERED_PANE,
        ATK_ROLE_LIST,
        ATK_ROLE_LIST_ITEM,
        ATK_ROLE_MENU,
        ATK_ROLE_MENU_BAR,
        ATK_ROLE_MENU_ITEM,
        ATK_ROLE_OPTION_PANE,
        ATK_ROLE_PAGE_TAB,
        ATK_ROLE_PAGE_TAB_LIST,
        ATK_ROLE_PANEL,
        ATK_ROLE_PARAGRAPH,
        ATK_ROLE_PASSWORD_TEXT,
        ATK_ROLE_POPUP_MENU,
        ATK_ROLE_PUSH_BUTTON,
        ATK_ROLE_PROGRESS_BAR,
        ATK_ROLE_RADIO_BUTTON,
        ATK_ROLE_RADIO_MENU_ITEM,
        ATK_ROLE_ROW_HEADER,
        ATK_ROLE_ROOT_PANE,
        ATK_ROLE_SCROLL_BAR,
        ATK_ROLE_SCROLL_PANE,
        ATK_ROLE_PANEL,         // SHAPE
        ATK_ROLE_SEPARATOR,
        ATK_ROLE_SLIDER,
        ATK_ROLE_SPIN_BUTTON,   // SPIN_BOX ?
        ATK_ROLE_SPLIT_PANE,
        ATK_ROLE_STATUSBAR,
        ATK_ROLE_TABLE,
        ATK_ROLE_TABLE_CELL,
        ATK_ROLE_TEXT,
        ATK_ROLE_PANEL,         // TEXT_FRAME
        ATK_ROLE_TOGGLE_BUTTON,
        ATK_ROLE_TOOL_BAR,
        ATK_ROLE_TOOL_TIP,
        ATK_ROLE_TREE,
        ATK_ROLE_VIEWPORT,
        ATK_ROLE_WINDOW,
        ATK_ROLE_PUSH_BUTTON,   // BUTTON_DROPDOWN
        ATK_ROLE_PUSH_BUTTON,   // BUTTON_MENU
        ATK_ROLE_UNKNOWN,       // CAPTION - registered below
        ATK_ROLE_UNKNOWN,       // CHART - registered below
        ATK_ROLE_UNKNOWN,       // EDIT_BAR - registered below
        ATK_ROLE_UNKNOWN,       // FORM - registered below
        ATK_ROLE_UNKNOWN,       // IMAGE_MAP - registered below
        ATK_ROLE_UNKNOWN,       // NOTE - registered below
        ATK_ROLE_UNKNOWN,       // PAGE - registered below
        ATK_ROLE_RULER,
        ATK_ROLE_UNKNOWN,       // SECTION - registered below
        ATK_ROLE_UNKNOWN,       // TREE_ITEM - registered below
        ATK_ROLE_TREE_TABLE,
        ATK_ROLE_SCROLL_PANE,   // COMMENT - mapped to atk_role_scroll_pane
        ATK_ROLE_UNKNOWN        // COMMENT_END - mapped to atk_role_unknown
#if defined(ATK_CHECK_VERSION)
        //older ver that doesn't define ATK_CHECK_VERSION doesn't have the following
        , ATK_ROLE_DOCUMENT_PRESENTATION
        , ATK_ROLE_DOCUMENT_SPREADSHEET
        , ATK_ROLE_DOCUMENT_TEXT
#if ATK_CHECK_VERSION(2,15,2)
        , ATK_ROLE_STATIC
#else
        , ATK_ROLE_LABEL
#endif
#else
        //older version should fallback to DOCUMENT_FRAME role
        , ATK_ROLE_DOCUMENT_FRAME
        , ATK_ROLE_DOCUMENT_FRAME
        , ATK_ROLE_DOCUMENT_FRAME
        , ATK_ROLE_LABEL
#endif
    };

    static bool initialized = false;

    if( ! initialized )
    {
        // the accessible roles below were added to ATK in later versions,
        // with role_for_name we will know if they exist in runtime.
        roleMap[accessibility::AccessibleRole::EDIT_BAR] = getRoleForName("edit bar");
        roleMap[accessibility::AccessibleRole::EMBEDDED_OBJECT] = getRoleForName("embedded");
        roleMap[accessibility::AccessibleRole::CHART] = getRoleForName("chart");
        roleMap[accessibility::AccessibleRole::CAPTION] = getRoleForName("caption");
        roleMap[accessibility::AccessibleRole::DOCUMENT] = getRoleForName("document frame");
        roleMap[accessibility::AccessibleRole::PAGE] = getRoleForName("page");
        roleMap[accessibility::AccessibleRole::SECTION] = getRoleForName("section");
        roleMap[accessibility::AccessibleRole::FORM] = getRoleForName("form");
        roleMap[accessibility::AccessibleRole::GROUP_BOX] = getRoleForName("grouping");
        roleMap[accessibility::AccessibleRole::COMMENT] = getRoleForName("comment");
        roleMap[accessibility::AccessibleRole::IMAGE_MAP] = getRoleForName("image map");
        roleMap[accessibility::AccessibleRole::TREE_ITEM] = getRoleForName("tree item");
        roleMap[accessibility::AccessibleRole::HYPER_LINK] = getRoleForName("link");
        roleMap[accessibility::AccessibleRole::END_NOTE] = getRoleForName("footnote");
        roleMap[accessibility::AccessibleRole::FOOTNOTE] = getRoleForName("footnote");
        roleMap[accessibility::AccessibleRole::NOTE] = getRoleForName("comment");

        initialized = true;
    }

    static const sal_Int32 nMapSize = SAL_N_ELEMENTS(roleMap);
    if( 0 <= nRole &&  nMapSize > nRole )
        role = roleMap[nRole];

    return role;
}

/*****************************************************************************/

extern "C" {

/*****************************************************************************/

static G_CONST_RETURN gchar*
wrapper_get_name( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    if( obj->mpContext.is() )
    {
        try {
            OString aName =
                OUStringToOString(
                    obj->mpContext->getAccessibleName(),
                    RTL_TEXTENCODING_UTF8);

            int nCmp = atk_obj->name ? rtl_str_compare( atk_obj->name, aName.getStr() ) : -1;
            if( nCmp != 0 )
            {
                if( atk_obj->name )
                    g_free(atk_obj->name);
                atk_obj->name = g_strdup(aName.getStr());
            }
        }
        catch(const uno::Exception&) {
            g_warning( "Exception in getAccessibleName()" );
        }
    }

    return ATK_OBJECT_CLASS (parent_class)->get_name(atk_obj);
}

/*****************************************************************************/

static G_CONST_RETURN gchar*
wrapper_get_description( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    if( obj->mpContext.is() )
    {
        try {
            OString aDescription =
                OUStringToOString(
                    obj->mpContext->getAccessibleDescription(),
                    RTL_TEXTENCODING_UTF8);

            g_free(atk_obj->description);
            atk_obj->description = g_strdup(aDescription.getStr());
        }
        catch(const uno::Exception&) {
            g_warning( "Exception in getAccessibleDescription()" );
        }
    }

    return ATK_OBJECT_CLASS (parent_class)->get_description(atk_obj);

}

/*****************************************************************************/

static AtkAttributeSet *
wrapper_get_attributes( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER( atk_obj );
    AtkAttributeSet *pSet = nullptr;

    try
    {
        uno::Reference< accessibility::XAccessibleExtendedAttributes >
            xExtendedAttrs( obj->mpContext, uno::UNO_QUERY );
        if( xExtendedAttrs.is() )
            pSet = attribute_set_new_from_extended_attributes( xExtendedAttrs );
    }
    catch(const uno::Exception&)
    {
        g_warning( "Exception in getAccessibleAttributes()" );
    }

    return pSet;
}

/*****************************************************************************/

static gint
wrapper_get_n_children( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
    gint n = 0;

    if( obj->mpContext.is() )
    {
        try {
            n = obj->mpContext->getAccessibleChildCount();
        }
        catch(const uno::Exception&) {
            OSL_FAIL("Exception in getAccessibleChildCount()" );
        }
    }

    return n;
}

/*****************************************************************************/

static AtkObject *
wrapper_ref_child( AtkObject *atk_obj,
                   gint       i )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
    AtkObject* child = nullptr;

    // see comments above atk_object_wrapper_remove_child
    if( -1 < i && obj->index_of_child_about_to_be_removed == i )
    {
        g_object_ref( obj->child_about_to_be_removed );
        return obj->child_about_to_be_removed;
    }

    if( obj->mpContext.is() )
    {
        try {
            uno::Reference< accessibility::XAccessible > xAccessible =
                obj->mpContext->getAccessibleChild( i );

            child = atk_object_wrapper_ref( xAccessible );
        }
        catch(const uno::Exception&) {
            OSL_FAIL("Exception in getAccessibleChild");
        }
    }

    return child;
}

/*****************************************************************************/

static gint
wrapper_get_index_in_parent( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
    if (obj->mpOrig)
        return atk_object_get_index_in_parent(obj->mpOrig);

    gint i = -1;

    if( obj->mpContext.is() )
    {
        try {
            i = obj->mpContext->getAccessibleIndexInParent();
        }
        catch(const uno::Exception&) {
            g_warning( "Exception in getAccessibleIndexInParent()" );
        }
    }
    return i;
}

/*****************************************************************************/

AtkRelation*
atk_object_wrapper_relation_new(const accessibility::AccessibleRelation& rRelation)
{
    sal_uInt32 nTargetCount = rRelation.TargetSet.getLength();

    std::vector<AtkObject*> aTargets;

    for (const auto& rTarget : rRelation.TargetSet)
    {
        uno::Reference< accessibility::XAccessible > xAccessible( rTarget, uno::UNO_QUERY );
        aTargets.push_back(atk_object_wrapper_ref(xAccessible));
    }

    AtkRelation *pRel =
        atk_relation_new(
            aTargets.data(), nTargetCount,
            mapRelationType( rRelation.RelationType )
        );

    return pRel;
}

static AtkRelationSet *
wrapper_ref_relation_set( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);

    //if we're a native GtkDrawingArea with custom a11y, use the default toolkit relation set impl
    if (obj->mpOrig)
        return atk_object_ref_relation_set(obj->mpOrig);

    AtkRelationSet *pSet = atk_relation_set_new();

    if( obj->mpContext.is() )
    {
        try {
            uno::Reference< accessibility::XAccessibleRelationSet > xRelationSet(
                    obj->mpContext->getAccessibleRelationSet()
            );

            sal_Int32 nRelations = xRelationSet.is() ? xRelationSet->getRelationCount() : 0;
            for( sal_Int32 n = 0; n < nRelations; n++ )
            {
                AtkRelation *pRel = atk_object_wrapper_relation_new(xRelationSet->getRelation(n));
                atk_relation_set_add(pSet, pRel);
                g_object_unref(pRel);
            }
        }
        catch(const uno::Exception &) {
            g_object_unref( G_OBJECT( pSet ) );
            pSet = nullptr;
        }
    }

    return pSet;
}

static AtkStateSet *
wrapper_ref_state_set( AtkObject *atk_obj )
{
    AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
    AtkStateSet *pSet = atk_state_set_new();

    if( obj->mpContext.is() )
    {
        try {
            uno::Reference< accessibility::XAccessibleStateSet > xStateSet(
                obj->mpContext->getAccessibleStateSet());

            if( xStateSet.is() )
            {
                uno::Sequence< sal_Int16 > aStates = xStateSet->getStates();

                for( const auto nState : aStates )
                {
                    // ATK_STATE_LAST_DEFINED is used to check if the state
                    // is unmapped, do not report it to Atk
                    if ( mapAtkState( nState ) != ATK_STATE_LAST_DEFINED )
                        atk_state_set_add_state( pSet, mapAtkState( nState ) );
                }

                // We need to emulate FOCUS state for menus, menu-items etc.
                if( atk_obj == atk_get_focus_object() )
                    atk_state_set_add_state( pSet, ATK_STATE_FOCUSED );
/* FIXME - should we do this ?
                else
                    atk_state_set_remove_state( pSet, ATK_STATE_FOCUSED );
*/
            }
        }

        catch(const uno::Exception &) {
            g_warning( "Exception in wrapper_ref_state_set" );
            atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );
        }
    }
    else
        atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );

    return pSet;
}

/*****************************************************************************/

static void
atk_object_wrapper_finalize (GObject *obj)
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER (obj);

    if( pWrap->mpAccessible.is() )
    {
        ooo_wrapper_registry_remove( pWrap->mpAccessible );
        pWrap->mpAccessible.clear();
    }

    atk_object_wrapper_dispose( pWrap );

    parent_class->finalize( obj );
}

static void
atk_object_wrapper_class_init (AtkObjectWrapperClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS( klass );
  AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );

  parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));

  // GObject methods
  gobject_class->finalize = atk_object_wrapper_finalize;

  // AtkObject methods
  atk_class->get_name = wrapper_get_name;
  atk_class->get_description = wrapper_get_description;
  atk_class->get_attributes = wrapper_get_attributes;
  atk_class->get_n_children = wrapper_get_n_children;
  atk_class->ref_child = wrapper_ref_child;
  atk_class->get_index_in_parent = wrapper_get_index_in_parent;
  atk_class->ref_relation_set = wrapper_ref_relation_set;
  atk_class->ref_state_set = wrapper_ref_state_set;
}

static void
atk_object_wrapper_init (AtkObjectWrapper      *wrapper,
                         AtkObjectWrapperClass*)
{
   wrapper->mpAction = nullptr;
   wrapper->mpComponent = nullptr;
   wrapper->mpEditableText = nullptr;
   wrapper->mpHypertext = nullptr;
   wrapper->mpImage = nullptr;
   wrapper->mpSelection = nullptr;
   wrapper->mpTable = nullptr;
   wrapper->mpText = nullptr;
   wrapper->mpValue = nullptr;
}

} // extern "C"

GType
atk_object_wrapper_get_type()
{
  static GType type = 0;

  if (!type)
    {
      static const GTypeInfo typeInfo =
      {
        sizeof (AtkObjectWrapperClass),
        nullptr,
        nullptr,
        reinterpret_cast<GClassInitFunc>(atk_object_wrapper_class_init),
        nullptr,
        nullptr,
        sizeof (AtkObjectWrapper),
        0,
        reinterpret_cast<GInstanceInitFunc>(atk_object_wrapper_init),
        nullptr
      } ;
      type = g_type_register_static (ATK_TYPE_OBJECT,
                                     "OOoAtkObj",
                                     &typeInfo, GTypeFlags(0)) ;
    }
  return type;
}

static bool
isOfType( uno::XInterface *pInterface, const uno::Type & rType )
{
    g_return_val_if_fail( pInterface != nullptr, false );

    bool bIs = false;
    try {
        uno::Any aRet = pInterface->queryInterface( rType );

        bIs = ( ( typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass ) &&
                ( aRet.pReserved != nullptr ) );
    } catch( const uno::Exception &) { }

    return bIs;
}

extern "C" {
typedef  GType (* GetGIfaceType ) ();
}
const struct {
        const char          *name;
        GInterfaceInitFunc const   aInit;
        GetGIfaceType const        aGetGIfaceType;
        const uno::Type &  (*aGetUnoType) ();
} aTypeTable[] = {
// re-location heaven:
    {
        "Comp", reinterpret_cast<GInterfaceInitFunc>(componentIfaceInit),
        atk_component_get_type,
        cppu::UnoType<accessibility::XAccessibleComponent>::get
    },
    {
        "Act",  reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
        atk_action_get_type,
        cppu::UnoType<accessibility::XAccessibleAction>::get
    },
    {
        "Txt",  reinterpret_cast<GInterfaceInitFunc>(textIfaceInit),
        atk_text_get_type,
        cppu::UnoType<accessibility::XAccessibleText>::get
    },
    {
        "Val",  reinterpret_cast<GInterfaceInitFunc>(valueIfaceInit),
        atk_value_get_type,
        cppu::UnoType<accessibility::XAccessibleValue>::get
    },
    {
        "Tab",  reinterpret_cast<GInterfaceInitFunc>(tableIfaceInit),
        atk_table_get_type,
        cppu::UnoType<accessibility::XAccessibleTable>::get
    },
    {
        "Edt",  reinterpret_cast<GInterfaceInitFunc>(editableTextIfaceInit),
        atk_editable_text_get_type,
        cppu::UnoType<accessibility::XAccessibleEditableText>::get
    },
    {
        "Img",  reinterpret_cast<GInterfaceInitFunc>(imageIfaceInit),
        atk_image_get_type,
        cppu::UnoType<accessibility::XAccessibleImage>::get
    },
    {
        "Hyp",  reinterpret_cast<GInterfaceInitFunc>(hypertextIfaceInit),
        atk_hypertext_get_type,
        cppu::UnoType<accessibility::XAccessibleHypertext>::get
    },
    {
        "Sel",  reinterpret_cast<GInterfaceInitFunc>(selectionIfaceInit),
        atk_selection_get_type,
        cppu::UnoType<accessibility::XAccessibleSelection>::get
    }
    // AtkDocument is a nastily broken interface (so far)
    //  we could impl. get_document_type perhaps though.
};

const int aTypeTableSize = G_N_ELEMENTS( aTypeTable );

static GType
ensureTypeFor( uno::XInterface *pAccessible )
{
    int i;
    bool bTypes[ aTypeTableSize ] = { false, };
    OStringBuffer aTypeNameBuf( "OOoAtkObj" );

    for( i = 0; i < aTypeTableSize; i++ )
    {
        if( isOfType( pAccessible, aTypeTable[i].aGetUnoType() ) )
        {
            aTypeNameBuf.append(aTypeTable[i].name);
            bTypes[i] = true;
        }
    }

    OString aTypeName = aTypeNameBuf.makeStringAndClear();
    GType nType = g_type_from_name( aTypeName.getStr() );
    if( nType == G_TYPE_INVALID )
    {
        GTypeInfo aTypeInfo = {
            sizeof( AtkObjectWrapperClass ),
            nullptr, nullptr, nullptr, nullptr, nullptr,
            sizeof( AtkObjectWrapper ),
            0, nullptr, nullptr
        } ;
        nType = g_type_register_static( ATK_TYPE_OBJECT_WRAPPER,
                                        aTypeName.getStr(), &aTypeInfo,
                                        GTypeFlags(0) ) ;

        for( int j = 0; j < aTypeTableSize; j++ )
            if( bTypes[j] )
            {
                GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr };
                aIfaceInfo.interface_init = aTypeTable[j].aInit;
                g_type_add_interface_static (nType, aTypeTable[j].aGetGIfaceType(),
                                             &aIfaceInfo);
            }
    }
    return nType;
}

AtkObject *
atk_object_wrapper_ref( const uno::Reference< accessibility::XAccessible > &rxAccessible, bool create )
{
    g_return_val_if_fail( rxAccessible.get() != nullptr, nullptr );

    AtkObject *obj = ooo_wrapper_registry_get(rxAccessible);
    if( obj )
    {
        g_object_ref( obj );
        return obj;
    }

    if( create )
        return atk_object_wrapper_new( rxAccessible );

    return nullptr;
}

AtkObject *
atk_object_wrapper_new( const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
                        AtkObject* parent, AtkObject* orig )
{
    g_return_val_if_fail( rxAccessible.get() != nullptr, nullptr );

    AtkObjectWrapper *pWrap = nullptr;

    try {
        uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext());

        g_return_val_if_fail( xContext.get() != nullptr, nullptr );

        GType nType = ensureTypeFor( xContext.get() );
        gpointer obj = g_object_new( nType, nullptr);

        pWrap = ATK_OBJECT_WRAPPER( obj );
        pWrap->mpAccessible = rxAccessible;

        pWrap->index_of_child_about_to_be_removed = -1;
        pWrap->child_about_to_be_removed = nullptr;

        pWrap->mpContext = xContext;
        pWrap->mpOrig = orig;

        AtkObject* atk_obj = ATK_OBJECT(pWrap);
        atk_obj->role = mapToAtkRole( xContext->getAccessibleRole() );
        atk_obj->accessible_parent = parent;

        ooo_wrapper_registry_add( rxAccessible, atk_obj );

        if( parent )
            g_object_ref( atk_obj->accessible_parent );
        else
        {
            /* gail_focus_tracker remembers the focused object at the first
             * parent in the hierarchy that is a Gtk+ widget, but at the time the
             * event gets processed (at idle), it may be too late to create the
             * hierarchy, so doing it now ..
             */
            uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );

            if( xParent.is() )
                atk_obj->accessible_parent = atk_object_wrapper_ref( xParent );
        }

        // Attach a listener to the UNO object if it's not TRANSIENT
        uno::Reference< accessibility::XAccessibleStateSet > xStateSet( xContext->getAccessibleStateSet() );
        if( xStateSet.is() && ! xStateSet->contains( accessibility::AccessibleStateType::TRANSIENT ) )
        {
            uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
            if( xBroadcaster.is() )
            {
                uno::Reference<accessibility::XAccessibleEventListener> xListener(new AtkListener(pWrap));
                xBroadcaster->addAccessibleEventListener(xListener);
            }
            else
                OSL_ASSERT( false );
        }

#if ATK_CHECK_VERSION(2,33,1)
        {
            css::uno::Reference<css::accessibility::XAccessibleContext2> xContext2(xContext, css::uno::UNO_QUERY);
            if( xContext2.is() )
            {
                OString aId = OUStringToOString( xContext2->getAccessibleId(), RTL_TEXTENCODING_UTF8);
                atk_object_set_accessible_id(atk_obj, aId.getStr());
            }
        }
#endif

        return ATK_OBJECT( pWrap );
    }
    catch (const uno::Exception &)
    {
        if( pWrap )
            g_object_unref( pWrap );

        return nullptr;
    }
}

/*****************************************************************************/

void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
{
    AtkObject *atk_obj = ATK_OBJECT( wrapper );

    atk_object_set_parent( child, atk_obj );
    g_signal_emit_by_name( atk_obj, "children_changed::add", index, child, nullptr );
}

/*****************************************************************************/

void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
{
    /*
     * the atk-bridge GTK+ module gets back to the event source to ref the child just
     * vanishing, so we keep this reference because the semantic on OOo side is different.
     */
    wrapper->child_about_to_be_removed = child;
    wrapper->index_of_child_about_to_be_removed = index;

    g_signal_emit_by_name( ATK_OBJECT( wrapper ), "children_changed::remove", index, child, nullptr );

    wrapper->index_of_child_about_to_be_removed = -1;
    wrapper->child_about_to_be_removed = nullptr;
}

/*****************************************************************************/

void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role)
{
    AtkObject *atk_obj = ATK_OBJECT( wrapper );
    atk_object_set_role( atk_obj, mapToAtkRole( role ) );
}

/*****************************************************************************/

void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper)
{
    wrapper->mpContext.clear();
    wrapper->mpAction.clear();
    wrapper->mpComponent.clear();
    wrapper->mpEditableText.clear();
    wrapper->mpHypertext.clear();
    wrapper->mpImage.clear();
    wrapper->mpSelection.clear();
    wrapper->mpMultiLineText.clear();
    wrapper->mpTable.clear();
    wrapper->mpText.clear();
    wrapper->mpTextMarkup.clear();
    wrapper->mpTextAttributes.clear();
    wrapper->mpValue.clear();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
similarity index 99%
rename from vcl/unx/gtk/fpicker/SalGtkFilePicker.cxx
rename to vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
index c106aa0..2552bb1 100644
--- a/vcl/unx/gtk/fpicker/SalGtkFilePicker.cxx
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
@@ -53,7 +53,7 @@
#include <set>
#include <string.h>

#include <gtk/fpicker/SalGtkFilePicker.hxx>
#include "SalGtkFilePicker.hxx"

using namespace ::com::sun::star;
using namespace ::com::sun::star::ui::dialogs;
@@ -437,15 +437,11 @@
{
    g_return_if_fail( GTK_IS_DIALOG( pDialog ) );

#if GTK_CHECK_VERSION(3,0,0)
    GtkWidget *pHeaderBar = gtk_dialog_get_header_bar(pDialog);
    if( pHeaderBar != nullptr )
        dialog_remove_buttons( pHeaderBar );
    else
        dialog_remove_buttons(gtk_dialog_get_action_area(pDialog));
#else
    dialog_remove_buttons(pDialog->action_area);
#endif
}

namespace {
@@ -798,14 +794,10 @@
                        }
                        if( bChangeFilter && bExtensionTypedIn )
                        {
#if GTK_CHECK_VERSION(3,0,0)
                            gchar* pCurrentName = gtk_file_chooser_get_current_name(GTK_FILE_CHOOSER(m_pDialog));
                            setCurrentFilter( aNewFilter );
                            gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(m_pDialog), pCurrentName);
                            g_free(pCurrentName);
#else
                            setCurrentFilter( aNewFilter );
#endif
                        }
                    }
                }
@@ -1009,12 +1001,7 @@
                            gtk_window_set_title( GTK_WINDOW( dlg ),
                                OUStringToOString(getResString(FILE_PICKER_TITLE_SAVE ),
                                RTL_TEXTENCODING_UTF8 ).getStr() );
#if GTK_CHECK_VERSION(3,0,0)
                            gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(m_pDialog));
#else
                            if (pParent)
                                gtk_window_set_transient_for(GTK_WINDOW(dlg), pParent);
#endif
                            RunDialog* pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop);
                            uno::Reference < awt::XTopWindowListener > xAnotherLifeCycle(pAnotherDialog);
                            btn = pAnotherDialog->run();
diff --git a/vcl/unx/gtk/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
similarity index 99%
rename from vcl/unx/gtk/fpicker/SalGtkFilePicker.hxx
rename to vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
index ae3e1ba..db471d5 100644
--- a/vcl/unx/gtk/fpicker/SalGtkFilePicker.hxx
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
@@ -32,7 +32,7 @@
#include <memory>
#include <rtl/ustring.hxx>

#include <gtk/fpicker/SalGtkPicker.hxx>
#include "SalGtkPicker.hxx"

// Implementation class for the XFilePicker Interface

diff --git a/vcl/unx/gtk/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
similarity index 98%
rename from vcl/unx/gtk/fpicker/SalGtkFolderPicker.cxx
rename to vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
index ade5ff5..13df539 100644
--- a/vcl/unx/gtk/fpicker/SalGtkFolderPicker.cxx
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
@@ -34,7 +34,7 @@
#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
#include <vcl/svapp.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <gtk/fpicker/SalGtkFolderPicker.hxx>
#include "SalGtkFolderPicker.hxx"
#include <sal/log.hxx>

#include <string.h>
diff --git a/vcl/unx/gtk/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
similarity index 97%
rename from vcl/unx/gtk/fpicker/SalGtkFolderPicker.hxx
rename to vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
index 46cf107..229bbe8 100644
--- a/vcl/unx/gtk/fpicker/SalGtkFolderPicker.hxx
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
@@ -25,7 +25,7 @@
#include <rtl/ustring.hxx>
#include <cppuhelper/implbase.hxx>

#include <gtk/fpicker/SalGtkPicker.hxx>
#include "SalGtkPicker.hxx"

class SalGtkFolderPicker :
    public SalGtkPicker,
diff --git a/vcl/unx/gtk/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
similarity index 99%
rename from vcl/unx/gtk/fpicker/SalGtkPicker.cxx
rename to vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
index 3e443ea..7db1140 100644
--- a/vcl/unx/gtk/fpicker/SalGtkPicker.cxx
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
@@ -36,7 +36,7 @@

#include <vcl/window.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <gtk/fpicker/SalGtkPicker.hxx>
#include "SalGtkPicker.hxx"

using namespace ::rtl;
using namespace ::com::sun::star;
diff --git a/vcl/unx/gtk/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
similarity index 100%
rename from vcl/unx/gtk/fpicker/SalGtkPicker.hxx
rename to vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
diff --git a/vcl/unx/gtk/fpicker/eventnotification.hxx b/vcl/unx/gtk3/fpicker/eventnotification.hxx
similarity index 100%
rename from vcl/unx/gtk/fpicker/eventnotification.hxx
rename to vcl/unx/gtk3/fpicker/eventnotification.hxx
diff --git a/vcl/unx/gtk/fpicker/resourceprovider.cxx b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
similarity index 98%
rename from vcl/unx/gtk/fpicker/resourceprovider.cxx
rename to vcl/unx/gtk3/fpicker/resourceprovider.cxx
index 745521a..818018b 100644
--- a/vcl/unx/gtk/fpicker/resourceprovider.cxx
+++ b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
@@ -24,7 +24,7 @@

#include <strings.hrc>
#include <svdata.hxx>
#include <gtk/fpicker/SalGtkPicker.hxx>
#include "SalGtkPicker.hxx"

using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
diff --git a/vcl/unx/gtk3/gtk3fpicker.cxx b/vcl/unx/gtk3/gtk3fpicker.cxx
deleted file mode 100644
index 9b83c57..0000000
--- a/vcl/unx/gtk3/gtk3fpicker.cxx
+++ /dev/null
@@ -1,15 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/fpicker/resourceprovider.cxx"
#include "../gtk/fpicker/SalGtkPicker.cxx"
#include "../gtk/fpicker/SalGtkFilePicker.cxx"
#include "../gtk/fpicker/SalGtkFolderPicker.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gloactiongroup.cxx b/vcl/unx/gtk3/gtk3gloactiongroup.cxx
index 749f543..19b412f 100644
--- a/vcl/unx/gtk3/gtk3gloactiongroup.cxx
+++ b/vcl/unx/gtk3/gtk3gloactiongroup.cxx
@@ -1,5 +1,403 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/gloactiongroup.cxx"
#include <unx/gtk/gtksalmenu.hxx>

#include <unx/gtk/gloactiongroup.h>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkframe.hxx>

#include <sal/log.hxx>

/*
 * GLOAction
 */

#define G_TYPE_LO_ACTION                                (g_lo_action_get_type ())
#define G_LO_ACTION(inst)                               (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
                                                         G_TYPE_LO_ACTION, GLOAction))

struct GLOAction
{
    GObject         parent_instance;

    gint            item_id;            // Menu item ID.
    gboolean        submenu;            // TRUE if action is a submenu action.
    gboolean        enabled;            // TRUE if action is enabled.
    GVariantType*   parameter_type;     // A GVariantType with the action parameter type.
    GVariantType*   state_type;         // A GVariantType with item state type
    GVariant*       state_hint;         // A GVariant with state hints.
    GVariant*       state;              // A GVariant with current item state
};

typedef GObjectClass GLOActionClass;

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
G_DEFINE_TYPE (GLOAction, g_lo_action, G_TYPE_OBJECT);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static GLOAction*
g_lo_action_new()
{
    return G_LO_ACTION (g_object_new (G_TYPE_LO_ACTION, nullptr));
}

static void
g_lo_action_init (GLOAction *action)
{
    action->item_id = -1;
    action->submenu = FALSE;
    action->enabled = TRUE;
    action->parameter_type = nullptr;
    action->state_type = nullptr;
    action->state_hint = nullptr;
    action->state = nullptr;
}

static void
g_lo_action_finalize (GObject *object)
{
    GLOAction* action = G_LO_ACTION(object);

    if (action->parameter_type)
        g_variant_type_free (action->parameter_type);

    if (action->state_type)
        g_variant_type_free (action->state_type);

    if (action->state_hint)
        g_variant_unref (action->state_hint);

    if (action->state)
        g_variant_unref (action->state);

    G_OBJECT_CLASS (g_lo_action_parent_class)->finalize (object);
}

static void
g_lo_action_class_init (GLOActionClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->finalize = g_lo_action_finalize;
}

/*
 * GLOActionGroup
 */

struct GLOActionGroupPrivate
{
    GHashTable  *table;    /* string -> GLOAction */
};

static void g_lo_action_group_iface_init (GActionGroupInterface *);

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
G_DEFINE_TYPE_WITH_CODE (GLOActionGroup,
    g_lo_action_group, G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
                           g_lo_action_group_iface_init));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static gchar **
g_lo_action_group_list_actions (GActionGroup *group)
{
    GLOActionGroup *loGroup = G_LO_ACTION_GROUP (group);
    GHashTableIter iter;
    gint n, i = 0;
    gchar **keys;
    gpointer key;

    n = g_hash_table_size (loGroup->priv->table);
    keys = g_new (gchar *, n + 1);

    g_hash_table_iter_init (&iter, loGroup->priv->table);
    while (g_hash_table_iter_next (&iter, &key, nullptr))
        keys[i++] = g_strdup (static_cast<gchar*>(key));
    g_assert_cmpint (i, ==, n);
    keys[n] = nullptr;

    return keys;
}

static gboolean
g_lo_action_group_query_action (GActionGroup        *group,
                                const gchar         *action_name,
                                gboolean            *enabled,
                                const GVariantType **parameter_type,
                                const GVariantType **state_type,
                                GVariant           **state_hint,
                                GVariant           **state)
{
    //SAL_INFO("vcl.unity", "g_lo_action_group_query_action on " << group);
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group);
    GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));

    if (action == nullptr)
        return FALSE;

    if (enabled)
    {
        *enabled = action->enabled;
    }

    if (parameter_type)
        *parameter_type = action->parameter_type;

    if (state_type)
        *state_type = action->state_type;

    if (state_hint)
        *state_hint = (action->state_hint) ? g_variant_ref (action->state_hint) : nullptr;

    if (state)
        *state = (action->state) ? g_variant_ref (action->state) : nullptr;

    return TRUE;
}

static void
g_lo_action_group_perform_submenu_action (GLOActionGroup *group,
                                          const gchar    *action_name,
                                          GVariant       *state)
{
    gboolean bState = g_variant_get_boolean (state);
    SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);

    if (bState)
        GtkSalMenu::Activate(action_name);
    else
        GtkSalMenu::Deactivate(action_name);
}

static void
g_lo_action_group_change_state (GActionGroup *group,
                                const gchar  *action_name,
                                GVariant     *value)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_change_state on " << group );
    g_return_if_fail (value != nullptr);

    g_variant_ref_sink (value);

    if (action_name != nullptr)
    {
        GLOActionGroup* lo_group = G_LO_ACTION_GROUP (group);
        GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));

        if (action != nullptr)
        {
            if (action->submenu)
                g_lo_action_group_perform_submenu_action (lo_group, action_name, value);
            else
            {
                gboolean is_new = FALSE;

                /* If action already exists but has no state, it should be removed and added again. */
                if (action->state_type == nullptr)
                {
                    g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
                    action->state_type = g_variant_type_copy (g_variant_get_type(value));
                    is_new = TRUE;
                }

                if (g_variant_is_of_type (value, action->state_type))
                {
                    if (action->state)
                        g_variant_unref(action->state);

                    action->state = g_variant_ref (value);

                    if (is_new)
                        g_action_group_action_added (G_ACTION_GROUP (group), action_name);
                    else
                        g_action_group_action_state_changed (group, action_name, value);
                }
            }
        }
    }

    g_variant_unref (value);
}

static void
g_lo_action_group_activate (GActionGroup *group,
                            const gchar  *action_name,
                            GVariant     *parameter)
{
    if (parameter != nullptr)
        g_action_group_change_action_state(group, action_name, parameter);
    GtkSalMenu::DispatchCommand(action_name);
}

void
g_lo_action_group_insert (GLOActionGroup *group,
                          const gchar    *action_name,
                          gint            item_id,
                          gboolean        submenu)
{
    g_lo_action_group_insert_stateful (group, action_name, item_id, submenu, nullptr, nullptr, nullptr, nullptr);
}

void
g_lo_action_group_insert_stateful (GLOActionGroup     *group,
                                   const gchar        *action_name,
                                   gint                item_id,
                                   gboolean            submenu,
                                   const GVariantType *parameter_type,
                                   const GVariantType *state_type,
                                   GVariant           *state_hint,
                                   GVariant           *state)
{
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    GLOAction* old_action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));

    if (old_action == nullptr || old_action->item_id != item_id)
    {
        if (old_action != nullptr)
            g_lo_action_group_remove (group, action_name);

        GLOAction* action = g_lo_action_new();

        g_hash_table_insert (group->priv->table, g_strdup (action_name), action);

        action->item_id = item_id;
        action->submenu = submenu;

        if (parameter_type)
            action->parameter_type = const_cast<GVariantType*>(parameter_type);

        if (state_type)
            action->state_type = const_cast<GVariantType*>(state_type);

        if (state_hint)
            action->state_hint = g_variant_ref_sink (state_hint);

        if (state)
            action->state = g_variant_ref_sink (state);

        g_action_group_action_added (G_ACTION_GROUP (group), action_name);
    }
}

static void
g_lo_action_group_finalize (GObject *object)
{
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (object);

    g_hash_table_unref (lo_group->priv->table);

    G_OBJECT_CLASS (g_lo_action_group_parent_class)->finalize (object);
}

static void
g_lo_action_group_init (GLOActionGroup *group)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_init on " << group);
    group->priv = G_TYPE_INSTANCE_GET_PRIVATE (group,
                                                 G_TYPE_LO_ACTION_GROUP,
                                                 GLOActionGroupPrivate);
    group->priv->table = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  g_free, g_object_unref);
}

static void
g_lo_action_group_class_init (GLOActionGroupClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = g_lo_action_group_finalize;
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
    g_type_class_add_private (klass, sizeof (GLOActionGroupPrivate));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}

static void
g_lo_action_group_iface_init (GActionGroupInterface *iface)
{
    iface->list_actions = g_lo_action_group_list_actions;
    iface->query_action = g_lo_action_group_query_action;
    iface->change_action_state = g_lo_action_group_change_state;
    iface->activate_action = g_lo_action_group_activate;
}

GLOActionGroup *
g_lo_action_group_new()
{
    GLOActionGroup* group = G_LO_ACTION_GROUP (g_object_new (G_TYPE_LO_ACTION_GROUP, nullptr));
    return group;
}

void
g_lo_action_group_set_action_enabled (GLOActionGroup *group,
                                      const gchar    *action_name,
                                      gboolean        enabled)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_set_action_enabled on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
    g_return_if_fail (action_name != nullptr);

    GLOAction* action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));

    if (action == nullptr)
        return;

    action->enabled = enabled;

    g_action_group_action_enabled_changed (G_ACTION_GROUP (group), action_name, enabled);
}

void
g_lo_action_group_remove (GLOActionGroup *group,
                          const gchar    *action_name)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_remove on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    if (action_name != nullptr)
    {
        g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
        g_hash_table_remove (group->priv->table, action_name);
    }
}

void
g_lo_action_group_clear (GLOActionGroup  *group)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_clear on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    GList* keys = g_hash_table_get_keys (group->priv->table);

    for (GList* element = g_list_first (keys); element != nullptr; element = g_list_next (element))
    {
        g_lo_action_group_remove (group, static_cast<gchar*>(element->data));
    }

    g_list_free (keys);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3glomenu.cxx b/vcl/unx/gtk3/gtk3glomenu.cxx
index e894b09..f20903d 100644
--- a/vcl/unx/gtk3/gtk3glomenu.cxx
+++ b/vcl/unx/gtk3/gtk3glomenu.cxx
@@ -1,5 +1,686 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/glomenu.cxx"
#include <string.h>

#include <unx/gtk/gtksalmenu.hxx>
#include <unx/gtk/glomenu.h>

struct GLOMenu
{
    GMenuModel const  parent_instance;

    GArray      *items;
};

typedef GMenuModelClass GLOMenuClass;

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
G_DEFINE_TYPE (GLOMenu, g_lo_menu, G_TYPE_MENU_MODEL);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

struct item
{
    GHashTable*     attributes;     // Item attributes.
    GHashTable*     links;          // Item links.
};

static void
g_lo_menu_struct_item_init (struct item *menu_item)
{
    menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, reinterpret_cast<GDestroyNotify>(g_variant_unref));
    menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}

/* We treat attribute names the same as GSettings keys:
 * - only lowercase ascii, digits and '-'
 * - must start with lowercase
 * - must not end with '-'
 * - no consecutive '-'
 * - not longer than 1024 chars
 */
static gboolean
valid_attribute_name (const gchar *name)
{
    gint i;

    if (!g_ascii_islower (name[0]))
        return FALSE;

    for (i = 1; name[i]; i++)
    {
        if (name[i] != '-' &&
                !g_ascii_islower (name[i]) &&
                !g_ascii_isdigit (name[i]))
            return FALSE;

        if (name[i] == '-' && name[i + 1] == '-')
            return FALSE;
    }

    if (name[i - 1] == '-')
        return FALSE;

    if (i > 1024)
        return FALSE;

    return TRUE;
}

/*
 * GLOMenu
 */

static gboolean
g_lo_menu_is_mutable (GMenuModel*)
{
    // Menu is always mutable.
    return TRUE;
}

static gint
g_lo_menu_get_n_items (GMenuModel *model)
{
    g_return_val_if_fail (model != nullptr, 0);
    GLOMenu *menu = G_LO_MENU (model);
    g_return_val_if_fail (menu->items != nullptr, 0);

    return menu->items->len;
}

gint
g_lo_menu_get_n_items_from_section (GLOMenu *menu,
                                    gint     section)
{
    g_return_val_if_fail (0 <= section && section < static_cast<gint>(menu->items->len), 0);

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_val_if_fail (model != nullptr, 0);

    gint length = model->items->len;

    g_object_unref (model);

    return length;
}

static void
g_lo_menu_get_item_attributes (GMenuModel  *model,
                               gint         position,
                               GHashTable **table)
{
    GLOMenu *menu = G_LO_MENU (model);
    *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes);
}

static void
g_lo_menu_get_item_links (GMenuModel  *model,
                          gint         position,
                          GHashTable **table)
{
    GLOMenu *menu = G_LO_MENU (model);
    *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links);
}

void
g_lo_menu_insert (GLOMenu     *menu,
                  gint         position,
                  const gchar *label)
{
    g_lo_menu_insert_section (menu, position, label, nullptr);
}

void
g_lo_menu_insert_in_section (GLOMenu     *menu,
                             gint         section,
                             gint         position,
                             const gchar *label)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= section && section < static_cast<gint>(menu->items->len));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_insert (model, position, label);

    g_object_unref (model);
}

GLOMenu *
g_lo_menu_new()
{
    return G_LO_MENU( g_object_new (G_TYPE_LO_MENU, nullptr) );
}

static void
g_lo_menu_set_attribute_value (GLOMenu     *menu,
                               gint         position,
                               const gchar *attribute,
                               GVariant    *value)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (attribute != nullptr);
    g_return_if_fail (valid_attribute_name (attribute));

    if (position >= static_cast<gint>(menu->items->len))
        return;

    struct item menu_item = g_array_index (menu->items, struct item, position);

    if (value != nullptr)
        g_hash_table_insert (menu_item.attributes, g_strdup (attribute), g_variant_ref_sink (value));
    else
        g_hash_table_remove (menu_item.attributes, attribute);
}

static GVariant*
g_lo_menu_get_attribute_value_from_item_in_section (GLOMenu            *menu,
                                                    gint                section,
                                                    gint                position,
                                                    const gchar        *attribute,
                                                    const GVariantType *type)
{
    GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));

    g_return_val_if_fail (model != nullptr, nullptr);

    GVariant *value = g_menu_model_get_item_attribute_value (model,
                                                             position,
                                                             attribute,
                                                             type);

    g_object_unref (model);

    return value;
}

void
g_lo_menu_set_label (GLOMenu     *menu,
                     gint         position,
                     const gchar *label)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GVariant *value;

    if (label != nullptr)
        value = g_variant_new_string (label);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_LABEL, value);
}

void
g_lo_menu_set_icon (GLOMenu     *menu,
                    gint         position,
                    const GIcon *icon)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GVariant *value;

    if (icon != nullptr)
    {
#if GLIB_CHECK_VERSION(2,38,0)
        value = g_icon_serialize (const_cast<GIcon*>(icon));
#else
        value = nullptr;
#endif
    }
    else
        value = nullptr;

#ifndef G_MENU_ATTRIBUTE_ICON
#    define G_MENU_ATTRIBUTE_ICON "icon"
#endif

    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ICON, value);
    if (value)
      g_variant_unref (value);
}

void
g_lo_menu_set_label_to_item_in_section (GLOMenu     *menu,
                                        gint         section,
                                        gint         position,
                                        const gchar *label)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_set_label (model, position, label);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

void
g_lo_menu_set_icon_to_item_in_section (GLOMenu     *menu,
                                       gint         section,
                                       gint         position,
                                       const GIcon *icon)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_set_icon (model, position, icon);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

gchar *
g_lo_menu_get_label_from_item_in_section (GLOMenu *menu,
                                          gint     section,
                                          gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    GVariant *label_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
                                                                                section,
                                                                                position,
                                                                                G_MENU_ATTRIBUTE_LABEL,
                                                                                G_VARIANT_TYPE_STRING);

    gchar *label = nullptr;

    if (label_value)
    {
        label = g_variant_dup_string (label_value, nullptr);
        g_variant_unref (label_value);
    }

    return label;
}

void
g_lo_menu_set_action_and_target_value (GLOMenu     *menu,
                                       gint         position,
                                       const gchar *action,
                                       GVariant    *target_value)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GVariant *action_value;

    if (action != nullptr)
    {
        action_value = g_variant_new_string (action);
    }
    else
    {
        action_value = nullptr;
        target_value = nullptr;
    }

    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ACTION, action_value);
    g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_TARGET, target_value);
    g_lo_menu_set_attribute_value (menu, position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, nullptr);

    g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 1);
}

void
g_lo_menu_set_action_and_target_value_to_item_in_section (GLOMenu     *menu,
                                                          gint         section,
                                                          gint         position,
                                                          const gchar *command,
                                                          GVariant    *target_value)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_set_action_and_target_value (model, position, command, target_value);

    g_object_unref (model);
}

void
g_lo_menu_set_accelerator_to_item_in_section (GLOMenu     *menu,
                                              gint         section,
                                              gint         position,
                                              const gchar *accelerator)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    GVariant *value;

    if (accelerator != nullptr)
        value = g_variant_new_string (accelerator);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_ACCELERATOR, value);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

gchar *
g_lo_menu_get_accelerator_from_item_in_section (GLOMenu *menu,
                                                gint     section,
                                                gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    GVariant *accel_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
                                                                                section,
                                                                                position,
                                                                                G_LO_MENU_ATTRIBUTE_ACCELERATOR,
                                                                                G_VARIANT_TYPE_STRING);

    gchar *accel = nullptr;

    if (accel_value != nullptr)
    {
        accel = g_variant_dup_string (accel_value, nullptr);
        g_variant_unref (accel_value);
    }

    return accel;
}

void
g_lo_menu_set_command_to_item_in_section (GLOMenu     *menu,
                                          gint         section,
                                          gint         position,
                                          const gchar *command)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    GVariant *value;

    if (command != nullptr)
        value = g_variant_new_string (command);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_COMMAND, value);

    // Notify the update.
    g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

    g_object_unref (model);
}

gchar *
g_lo_menu_get_command_from_item_in_section (GLOMenu *menu,
                                            gint     section,
                                            gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    GVariant *command_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
                                                                                  section,
                                                                                  position,
                                                                                  G_LO_MENU_ATTRIBUTE_COMMAND,
                                                                                  G_VARIANT_TYPE_STRING);

    gchar *command = nullptr;

    if (command_value != nullptr)
    {
        command = g_variant_dup_string (command_value, nullptr);
        g_variant_unref (command_value);
    }

    return command;
}

static void
g_lo_menu_set_link (GLOMenu     *menu,
                    gint         position,
                    const gchar *link,
                    GMenuModel  *model)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (link != nullptr);
    g_return_if_fail (valid_attribute_name (link));

    if (position < 0 || position >= static_cast<gint>(menu->items->len))
        position = menu->items->len - 1;

    struct item menu_item = g_array_index (menu->items, struct item, position);

    if (model != nullptr)
        g_hash_table_insert (menu_item.links, g_strdup (link), g_object_ref (model));
    else
        g_hash_table_remove (menu_item.links, link);
}

void
g_lo_menu_insert_section (GLOMenu     *menu,
                          gint         position,
                          const gchar *label,
                          GMenuModel  *section)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    if (position < 0 || position > static_cast<gint>(menu->items->len))
        position = menu->items->len;

    struct item menu_item;

    g_lo_menu_struct_item_init(&menu_item);

    g_array_insert_val (menu->items, position, menu_item);

    g_lo_menu_set_label (menu, position, label);
    g_lo_menu_set_link (menu, position, G_MENU_LINK_SECTION, section);

    g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1);
}

void
g_lo_menu_new_section (GLOMenu     *menu,
                       gint         position,
                       const gchar *label)
{
    GMenuModel *section = G_MENU_MODEL (g_lo_menu_new());

    g_lo_menu_insert_section (menu, position, label, section);

    g_object_unref (section);
}

GLOMenu *
g_lo_menu_get_section (GLOMenu *menu,
                       gint section)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);

    return G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
                      ->get_item_link (G_MENU_MODEL (menu), section, G_MENU_LINK_SECTION));
}

void
g_lo_menu_new_submenu_in_item_in_section (GLOMenu *menu,
                                          gint     section,
                                          gint     position)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= section && section < static_cast<gint>(menu->items->len));

    GLOMenu* model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    if (0 <= position && position < static_cast<gint>(model->items->len)) {
        GMenuModel* submenu = G_MENU_MODEL (g_lo_menu_new());

        g_lo_menu_set_link (model, position, G_MENU_LINK_SUBMENU, submenu);

        g_object_unref (submenu);

        g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);

        g_object_unref (model);
    }
}

GLOMenu *
g_lo_menu_get_submenu_from_item_in_section (GLOMenu *menu,
                                            gint     section,
                                            gint     position)
{
    g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
    g_return_val_if_fail (0 <= section && section < static_cast<gint>(menu->items->len), nullptr);

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_val_if_fail (model != nullptr, nullptr);

    GLOMenu *submenu = nullptr;

    if (0 <= position && position < static_cast<gint>(model->items->len))
        submenu = G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
                ->get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU));
        //submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU);

    g_object_unref (model);

    return submenu;
}

void
g_lo_menu_set_submenu_action_to_item_in_section (GLOMenu     *menu,
                                                 gint         section,
                                                 gint         position,
                                                 const gchar *action)
{
    g_return_if_fail (G_IS_LO_MENU (menu));

    GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));

    g_return_if_fail (model != nullptr);

    GVariant *value;

    if (action != nullptr)
        value = g_variant_new_string (action);
    else
        value = nullptr;

    g_lo_menu_set_attribute_value (G_LO_MENU (model), position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, value);

    // Notify the update.
    g_menu_model_items_changed (model, position, 1, 1);

    g_object_unref (model);
}

static void
g_lo_menu_clear_item (struct item *menu_item)
{
    if (menu_item->attributes != nullptr)
        g_hash_table_unref (menu_item->attributes);
    if (menu_item->links != nullptr)
        g_hash_table_unref (menu_item->links);
}

void
g_lo_menu_remove (GLOMenu *menu,
                  gint     position)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= position && position < static_cast<gint>(menu->items->len));

    g_lo_menu_clear_item (&g_array_index (menu->items, struct item, position));
    g_array_remove_index (menu->items, position);
    g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0);
}

void
g_lo_menu_remove_from_section (GLOMenu *menu,
                               gint     section,
                               gint     position)
{
    g_return_if_fail (G_IS_LO_MENU (menu));
    g_return_if_fail (0 <= section && section < static_cast<gint>(menu->items->len));

    GLOMenu *model = g_lo_menu_get_section (menu, section);

    g_return_if_fail (model != nullptr);

    g_lo_menu_remove (model, position);

    g_object_unref (model);
}

static void
g_lo_menu_finalize (GObject *object)
{
    GLOMenu *menu = G_LO_MENU (object);
    struct item *items;
    gint n_items;
    gint i;

    n_items = menu->items->len;
    items = reinterpret_cast<struct item *>(g_array_free (menu->items, FALSE));
    for (i = 0; i < n_items; i++)
        g_lo_menu_clear_item (&items[i]);
    g_free (items);

    G_OBJECT_CLASS (g_lo_menu_parent_class)
            ->finalize (object);
}

static void
g_lo_menu_init (GLOMenu *menu)
{
    menu->items = g_array_new (FALSE, FALSE, sizeof (struct item));
}

static void
g_lo_menu_class_init (GLOMenuClass *klass)
{
    GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass);
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = g_lo_menu_finalize;

    model_class->is_mutable = g_lo_menu_is_mutable;
    model_class->get_n_items = g_lo_menu_get_n_items;
    model_class->get_item_attributes = g_lo_menu_get_item_attributes;
    model_class->get_item_links = g_lo_menu_get_item_links;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
index 333db66..f5e26be 100644
--- a/vcl/unx/gtk3/gtk3gtkinst.cxx
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -7,8 +7,35 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/gtkinst.cxx"
#include "../gtk/a11y/atkwrapper.hxx"
#include <stack>
#include <string.h>
#include <osl/process.h>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/salobj.h>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/atkbridge.hxx>
#include <unx/gtk/gtkprn.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <headless/svpvd.hxx>
#include <headless/svpbmp.hxx>
#include <vcl/inputtypes.hxx>
#include <unx/genpspgraphics.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <rtl/uri.hxx>

#include <vcl/settings.hxx>

#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>

#include <unx/gtk/gtkprintwrapper.hxx>

#include "a11y/atkwrapper.hxx"
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
@@ -56,6 +83,376 @@
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;

extern "C"
{
    #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
    static void GdkThreadsEnter()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsEnter();
    }
    static void GdkThreadsLeave()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsLeave();
    }

    VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
    {
        SAL_INFO(
            "vcl.gtk",
            "create vcl plugin instance with gtk version " << gtk_major_version
                << " " << gtk_minor_version << " " << gtk_micro_version);

        if (gtk_major_version == 3 && gtk_minor_version < 18)
        {
            g_warning("require gtk >= 3.18 for theme expectations");
            return nullptr;
        }

        // for gtk2 it is always built with X support, so this is always called
        // for gtk3 it is normally built with X and Wayland support, if
        // X is supported GDK_WINDOWING_X11 is defined and this is always
        // called, regardless of if we're running under X or Wayland.
        // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
        // X, because we need to do it earlier than we have a display
#if defined(GDK_WINDOWING_X11)
        /* #i92121# workaround deadlocks in the X11 implementation
        */
        static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
        /* #i90094#
           from now on we know that an X connection will be
           established, so protect X against itself
        */
        if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
            XInitThreads();
#endif

        // init gdk thread protection
        bool const sup = g_thread_supported();
            // extracted from the 'if' to avoid Clang -Wunreachable-code
        if ( !sup )
            g_thread_init( nullptr );

        gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
        SAL_INFO("vcl.gtk", "Hooked gdk threads locks");

        auto pYieldMutex = std::make_unique<GtkYieldMutex>();

        gdk_threads_init();

        GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
        SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);

        // Create SalData, this does not leak
        new GtkSalData( pInstance );

        return pInstance;
    }
}

static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
{
    VclInputFlags nType = VclInputFlags::NONE;
    switch( pEvent->type )
    {
    case GDK_MOTION_NOTIFY:
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
    case GDK_SCROLL:
        nType = VclInputFlags::MOUSE;
        break;
    case GDK_KEY_PRESS:
    // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
        nType = VclInputFlags::KEYBOARD;
        break;
    case GDK_EXPOSE:
        nType = VclInputFlags::PAINT;
        break;
    default:
        nType = VclInputFlags::OTHER;
        break;
    }
    return nType;
}

GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
    : SvpSalInstance( std::move(pMutex) )
    , m_pTimer(nullptr)
    , bNeedsInit(true)
    , m_pLastCairoFontOptions(nullptr)
{
}

//We want to defer initializing gtk until we are after uno has been
//bootstrapped so we can ask the config what the UI language is so that we can
//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
//UI in a LTR locale
void GtkInstance::AfterAppInit()
{
    EnsureInit();
}

void GtkInstance::EnsureInit()
{
    if (!bNeedsInit)
        return;
    // initialize SalData
    GtkSalData *pSalData = GetGtkSalData();
    pSalData->Init();
    GtkSalData::initNWF();

    InitAtkBridge();

    ImplSVData* pSVData = ImplGetSVData();
#ifdef GTK_TOOLKIT_NAME
    pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
#else
    pSVData->maAppData.mxToolkitName = OUString("gtk3");
#endif

    bNeedsInit = false;
}

GtkInstance::~GtkInstance()
{
    assert( nullptr == m_pTimer );
    DeInitAtkBridge();
    ResetLastSeenCairoFontOptions(nullptr);
}

SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
{
    EnsureInit();
    return new GtkSalFrame( pParent, nStyle );
}

SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
{
    EnsureInit();
    return new GtkSalFrame( pParentData );
}

SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* /*pWindowData*/, bool bShow )
{
    EnsureInit();
    //FIXME: Missing CreateObject functionality ...
    return new GtkSalObject( static_cast<GtkSalFrame*>(pParent), bShow );
}

extern "C"
{
    typedef void*(* getDefaultFnc)();
    typedef void(* addItemFnc)(void *, const char *);
}

void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
{
    EnsureInit();
    OString sGtkURL;
    rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
    if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
        sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
    else
    {
        //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
        //Decode %XX components
        OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
        //Convert back to system locale encoding
        OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
        //Encode to an escaped ASCII-encoded URI
        gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
        sGtkURL = OString(g_uri);
        g_free(g_uri);
    }
    GtkRecentManager *manager = gtk_recent_manager_get_default ();
    gtk_recent_manager_add_item (manager, sGtkURL.getStr());
}

SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
    ImplJobSetup* pSetupData )
{
    EnsureInit();
    mbPrinterInit = true;
    // create and initialize SalInfoPrinter
    PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
    configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
    return pPrinter;
}

std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
    EnsureInit();
    mbPrinterInit = true;
    return std::unique_ptr<SalPrinter>(new GtkSalPrinter( pInfoPrinter ));
}

/*
 * These methods always occur in pairs
 * A ThreadsEnter is followed by a ThreadsLeave
 * We need to queue up the recursive lock count
 * for each pair, so we can accurately restore
 * it later.
 */
thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;

void GtkYieldMutex::ThreadsEnter()
{
    acquire();
    if (!yieldCounts.empty()) {
        auto n = yieldCounts.top();
        yieldCounts.pop();
        assert(n > 0);
        n--;
        if (n > 0)
            acquire(n);
    }
}

void GtkYieldMutex::ThreadsLeave()
{
    assert(m_nCount != 0);
    yieldCounts.push(m_nCount);
    release(true);
}

std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
                                                    long &nDX, long &nDY,
                                                    DeviceFormat eFormat,
                                                    const SystemGraphicsData* /*pGd*/ )
{
    EnsureInit();
    SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
    assert(pSvpSalGraphics);
    std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface()));
    pNew->SetSize( nDX, nDY );
    return pNew;
}

std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
{
    EnsureInit();
    return SvpSalInstance::CreateSalBitmap();
}

std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
{
    EnsureInit();
    GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
    pSalMenu->SetMenu( pVCLMenu );
    return std::unique_ptr<SalMenu>(pSalMenu);
}

std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
{
    EnsureInit();
    return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
}

SalTimer* GtkInstance::CreateSalTimer()
{
    EnsureInit();
    assert( nullptr == m_pTimer );
    if ( nullptr == m_pTimer )
        m_pTimer = new GtkSalTimer();
    return m_pTimer;
}

void GtkInstance::RemoveTimer ()
{
    EnsureInit();
    m_pTimer = nullptr;
}

bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{
    EnsureInit();
    return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
}

bool GtkInstance::IsTimerExpired()
{
    EnsureInit();
    return (m_pTimer && m_pTimer->Expired());
}

bool GtkInstance::AnyInput( VclInputFlags nType )
{
    EnsureInit();
    if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
        return true;
    if (!gdk_events_pending())
        return false;

    if (nType == VCL_INPUT_ANY)
        return true;

    bool bRet = false;
    std::stack<GdkEvent*> aEvents;
    GdkEvent *pEvent = nullptr;
    while ((pEvent = gdk_event_get()))
    {
        aEvents.push(pEvent);
        VclInputFlags nEventType = categorizeEvent(pEvent);
        if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
        {
            bRet = true;
            break;
        }
    }

    while (!aEvents.empty())
    {
        pEvent = aEvents.top();
        gdk_event_put(pEvent);
        gdk_event_free(pEvent);
        aEvents.pop();
    }
    return bRet;
}

std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
{
    EnsureInit();
    return std::make_unique<GenPspGraphics>();
}

std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
GtkInstance::getPrintWrapper() const
{
    if (!m_xPrintWrapper)
        m_xPrintWrapper.reset(new vcl::unx::GtkPrintWrapper);
    return m_xPrintWrapper;
}

const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
{
    const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
    if (!m_pLastCairoFontOptions && pCairoFontOptions)
        m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
    return pCairoFontOptions;
}

const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
{
    return m_pLastCairoFontOptions;
}

void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
{
    if (m_pLastCairoFontOptions)
        cairo_font_options_destroy(m_pLastCairoFontOptions);
    if (pCairoFontOptions)
        m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
    else
        m_pLastCairoFontOptions = nullptr;
}


namespace
{
    struct TypeEntry
diff --git a/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx b/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx
index edefca9..b1a8b9c 100644
--- a/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx
+++ b/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx
@@ -7,6 +7,154 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/gtkprintwrapper.cxx"
#include <cassert>

#include <rtl/ustring.hxx>

#include <unx/gtk/gtkprintwrapper.hxx>

namespace vcl
{
namespace unx
{

GtkPrintWrapper::GtkPrintWrapper()
{
}

GtkPrintWrapper::~GtkPrintWrapper()
{
}

bool GtkPrintWrapper::supportsPrinting() const
{
    (void) this; // loplugin:staticmethods
    return true;
}

bool GtkPrintWrapper::supportsPrintSelection() const
{
    (void) this; // loplugin:staticmethods
    return true;
}

GtkPageSetup* GtkPrintWrapper::page_setup_new() const
{
    (void) this; // loplugin:staticmethods
    return gtk_page_setup_new();
}

GtkPrintJob* GtkPrintWrapper::print_job_new(const gchar* title, GtkPrinter* printer, GtkPrintSettings* settings, GtkPageSetup* page_setup) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_job_new(title, printer, settings, page_setup);
}

void GtkPrintWrapper::print_job_send(GtkPrintJob* job, GtkPrintJobCompleteFunc callback, gpointer user_data, GDestroyNotify dnotify) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_job_send(job, callback, user_data, dnotify);
}

gboolean GtkPrintWrapper::print_job_set_source_file(GtkPrintJob* job, const gchar* filename, GError** error) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_job_set_source_file(job, filename, error);
}

const gchar* GtkPrintWrapper::print_settings_get(GtkPrintSettings* settings, const gchar* key) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get(settings, key);
}

gboolean GtkPrintWrapper::print_settings_get_collate(GtkPrintSettings* settings) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get_collate(settings);
}

void GtkPrintWrapper::print_settings_set_collate(GtkPrintSettings* settings, gboolean collate) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_settings_set_collate(settings, collate);
}

gint GtkPrintWrapper::print_settings_get_n_copies(GtkPrintSettings* settings) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get_n_copies(settings);
}

void GtkPrintWrapper::print_settings_set_n_copies(GtkPrintSettings* settings, gint num_copies) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_settings_set_n_copies(settings, num_copies);
}

GtkPageRange* GtkPrintWrapper::print_settings_get_page_ranges(GtkPrintSettings* settings, gint* num_ranges) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_settings_get_page_ranges(settings, num_ranges);
}

void GtkPrintWrapper::print_settings_set_print_pages(GtkPrintSettings* settings, GtkPrintPages pages) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_settings_set_print_pages(settings, pages);
}

GtkWidget* GtkPrintWrapper::print_unix_dialog_new() const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_unix_dialog_new(nullptr, nullptr);
}

void GtkPrintWrapper::print_unix_dialog_add_custom_tab(GtkPrintUnixDialog* dialog, GtkWidget* child, GtkWidget* tab_label) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_add_custom_tab(dialog, child, tab_label);
}

GtkPrinter* GtkPrintWrapper::print_unix_dialog_get_selected_printer(GtkPrintUnixDialog* dialog) const
{
    (void) this; // loplugin:staticmethods
    GtkPrinter* pRet = gtk_print_unix_dialog_get_selected_printer(dialog);
    g_object_ref(G_OBJECT(pRet));
    return pRet;
}

void GtkPrintWrapper::print_unix_dialog_set_manual_capabilities(GtkPrintUnixDialog* dialog, GtkPrintCapabilities capabilities) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_manual_capabilities(dialog, capabilities);
}

GtkPrintSettings* GtkPrintWrapper::print_unix_dialog_get_settings(GtkPrintUnixDialog* dialog) const
{
    (void) this; // loplugin:staticmethods
    return gtk_print_unix_dialog_get_settings(dialog);
}

void GtkPrintWrapper::print_unix_dialog_set_settings(GtkPrintUnixDialog* dialog, GtkPrintSettings* settings) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_settings(dialog, settings);
}

void GtkPrintWrapper::print_unix_dialog_set_support_selection(GtkPrintUnixDialog* dialog, gboolean support_selection) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_support_selection(dialog, support_selection);
}

void GtkPrintWrapper::print_unix_dialog_set_has_selection(GtkPrintUnixDialog* dialog, gboolean has_selection) const
{
    (void) this; // loplugin:staticmethods
    gtk_print_unix_dialog_set_has_selection(dialog, has_selection);
}

}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtksalmenu.cxx b/vcl/unx/gtk3/gtk3gtksalmenu.cxx
index d465594..81d5d65 100644
--- a/vcl/unx/gtk3/gtk3gtksalmenu.cxx
+++ b/vcl/unx/gtk3/gtk3gtksalmenu.cxx
@@ -1,5 +1,1396 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/gtksalmenu.cxx"
#include <unx/gtk/gtksalmenu.hxx>

#include <unx/gendata.hxx>
#include <unx/saldisp.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/glomenu.h>
#include <unx/gtk/gloactiongroup.h>
#include <vcl/floatwin.hxx>
#include <vcl/menu.hxx>
#include <vcl/pngwrite.hxx>
#include <unx/gtk/gtkinst.hxx>

#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <window.h>
#include <strings.hrc>

static bool bUnityMode = false;

/*
 * This function generates a unique command name for each menu item
 */
static gchar* GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
{
    OString aCommand("window-");
    aCommand = aCommand + OString::number(reinterpret_cast<unsigned long>(pParentMenu));
    aCommand = aCommand + "-" + OString::number(nItemId);
    return g_strdup(aCommand.getStr());
}

static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
{
    return GetCommandForItem(pSalMenuItem->mpParentMenu,
                             pSalMenuItem->mnId);
}

bool GtkSalMenu::PrepUpdate()
{
    return mpMenuModel && mpActionGroup;
}

/*
 * Menu updating methods
 */

static void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nSection, unsigned nValidItems )
{
    sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );

    while ( nSectionItems > static_cast<sal_Int32>(nValidItems) )
    {
        gchar* aCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, --nSectionItems );

        if ( aCommand != nullptr && pOldCommandList != nullptr )
            *pOldCommandList = g_list_append( *pOldCommandList, g_strdup( aCommand ) );

        g_free( aCommand );

        g_lo_menu_remove_from_section( pMenu, nSection, nSectionItems );
    }
}

typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;

namespace
{
    MenuAndId decode_command(const gchar *action_name)
    {
        OString sCommand(action_name);

        sal_Int32 nIndex = 0;
        OString sWindow = sCommand.getToken(0, '-', nIndex);
        OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
        OString sItemId = sCommand.getToken(0, '-', nIndex);

        GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());

        assert(sWindow == "window" && pSalSubMenu);
        (void) sWindow;

        return MenuAndId(pSalSubMenu, sItemId.toInt32());
    }
}

static void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList,
                                       sal_Int32 nSection, GActionGroup* pActionGroup)
{
    while (nSection >= 0)
    {
        sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
        while (nSectionItems--)
        {
            gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems);
            // remove disabled entries
            bool bRemove = !g_action_group_get_action_enabled(pActionGroup, pCommand);
            if (!bRemove)
            {
                //also remove any empty submenus
                GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems);
                if (pSubMenuModel)
                {
                    gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel));
                    if (nSubMenuSections == 0)
                        bRemove = true;
                    else if (nSubMenuSections == 1)
                    {
                        gint nItems = g_lo_menu_get_n_items_from_section(pSubMenuModel, 0);
                        if (nItems == 0)
                            bRemove = true;
                        else if (nItems == 1)
                        {
                            //If the only entry is the "No Selection Possible" entry, then we are allowed
                            //to removed it
                            gchar* pSubCommand = g_lo_menu_get_command_from_item_in_section(pSubMenuModel, 0, 0);
                            MenuAndId aMenuAndId(decode_command(pSubCommand));
                            bRemove = aMenuAndId.second == 0xFFFF;
                            g_free(pSubCommand);
                        }
                    }
                }
            }

            if (bRemove)
            {
                //but tdf#86850 Always display clipboard functions
                bRemove = g_strcmp0(pCommand, ".uno:Cut") &&
                          g_strcmp0(pCommand, ".uno:Copy") &&
                          g_strcmp0(pCommand, ".uno:Paste");
            }

            if (bRemove)
            {
                if (pCommand != nullptr && pOldCommandList != nullptr)
                    *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand));
                g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems);
            }

            g_free(pCommand);
        }
        --nSection;
    }
}

static void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection )
{
    if ( pMenu == nullptr || pOldCommandList == nullptr )
        return;

    sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1;

    for ( ; n > nLastSection; n--)
    {
        RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 );
        g_lo_menu_remove( pMenu, n );
    }
}

static gint CompareStr( gpointer str1, gpointer str2 )
{
    return g_strcmp0( static_cast<const gchar*>(str1), static_cast<const gchar*>(str2) );
}

static void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, GList* pNewCommandList )
{
    if ( pActionGroup == nullptr || pOldCommandList == nullptr )
    {
        g_list_free_full( pOldCommandList, g_free );
        g_list_free_full( pNewCommandList, g_free );
        return;
    }

    while ( pNewCommandList != nullptr )
    {
        GList* pNewCommand = g_list_first( pNewCommandList );
        pNewCommandList = g_list_remove_link( pNewCommandList, pNewCommand );

        gpointer aCommand = g_list_nth_data( pNewCommand, 0 );

        GList* pOldCommand = g_list_find_custom( pOldCommandList, aCommand, reinterpret_cast<GCompareFunc>(CompareStr) );

        if ( pOldCommand != nullptr )
        {
            pOldCommandList = g_list_remove_link( pOldCommandList, pOldCommand );
            g_list_free_full( pOldCommand, g_free );
        }

        g_list_free_full( pNewCommand, g_free );
    }

    while ( pOldCommandList != nullptr )
    {
        GList* pCommand = g_list_first( pOldCommandList );
        pOldCommandList = g_list_remove_link( pOldCommandList, pCommand );

        gchar* aCommand = static_cast<gchar*>(g_list_nth_data( pCommand, 0 ));

        g_lo_action_group_remove( pActionGroup, aCommand );

        g_list_free_full( pCommand, g_free );
    }
}

void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
{
    SolarMutexGuard aGuard;

    SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate");
    if( !PrepUpdate() )
        return;

    if (mbNeedsUpdate)
    {
        mbNeedsUpdate = false;
        if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
        {
            maUpdateMenuBarIdle.Stop();
            maUpdateMenuBarIdle.Invoke();
            return;
        }
    }

    Menu* pVCLMenu = mpVCLMenu;
    GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
    SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
    GList *pOldCommandList = nullptr;
    GList *pNewCommandList = nullptr;

    sal_uInt16 nLOMenuSize = g_menu_model_get_n_items( G_MENU_MODEL( pLOMenu ) );

    if ( nLOMenuSize == 0 )
        g_lo_menu_new_section( pLOMenu, 0, nullptr );

    sal_Int32 nSection = 0;
    sal_Int32 nItemPos = 0;
    sal_Int32 validItems = 0;
    sal_Int32 nItem;

    for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) {
        if ( !IsItemVisible( nItem ) )
            continue;

        GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
        sal_uInt16 nId = pSalMenuItem->mnId;

        // PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level
        // popup menu, but we have our own implementation below, so skip that one.
        if ( nId == 0xFFFF )
            continue;

        if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
        {
            // Delete extra items from current section.
            RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );

            nSection++;
            nItemPos = 0;
            validItems = 0;

            if ( nLOMenuSize <= nSection )
            {
                g_lo_menu_new_section( pLOMenu, nSection, nullptr );
                nLOMenuSize++;
            }

            continue;
        }

        if ( nItemPos >= g_lo_menu_get_n_items_from_section( pLOMenu, nSection ) )
            g_lo_menu_insert_in_section( pLOMenu, nSection, nItemPos, "EMPTY STRING" );

        // Get internal menu item values.
        OUString aText = pVCLMenu->GetItemText( nId );
        Image aImage = pVCLMenu->GetItemImage( nId );
        bool bEnabled = pVCLMenu->IsItemEnabled( nId );
        vcl::KeyCode nAccelKey = pVCLMenu->GetAccelKey( nId );
        bool bChecked = pVCLMenu->IsItemChecked( nId );
        MenuItemBits itemBits = pVCLMenu->GetItemBits( nId );

        // Store current item command in command list.
        gchar *aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pLOMenu, nSection, nItemPos );

        if ( aCurrentCommand != nullptr )
            pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );

        // Get the new command for the item.
        gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);

        // Force updating of native menu labels.
        NativeSetItemText( nSection, nItemPos, aText );
        NativeSetItemIcon( nSection, nItemPos, aImage );
        NativeSetAccelerator( nSection, nItemPos, nAccelKey, nAccelKey.GetName( GetFrame()->GetWindow() ) );

        if ( g_strcmp0( aNativeCommand, "" ) != 0 && pSalMenuItem->mpSubMenu == nullptr )
        {
            NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, bChecked, false );
            NativeCheckItem( nSection, nItemPos, itemBits, bChecked );
            NativeSetEnableItem( aNativeCommand, bEnabled );

            pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
        }

        GtkSalMenu* pSubmenu = pSalMenuItem->mpSubMenu;

        if ( pSubmenu && pSubmenu->GetMenu() )
        {
            bool bNonMenuChangedToMenu = NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, false, true );
            pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );

            GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );

            if ( pSubMenuModel == nullptr )
            {
                g_lo_menu_new_submenu_in_item_in_section( pLOMenu, nSection, nItemPos );
                pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
            }

            g_object_unref( pSubMenuModel );

            if (bRecurse || bNonMenuChangedToMenu)
            {
                SAL_INFO("vcl.unity", "preparing submenu  " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
                pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
                pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
                pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
            }
        }

        g_free( aNativeCommand );

        ++nItemPos;
        ++validItems;
    }

    if (bRemoveDisabledEntries)
    {
        // Delete disabled items in last section.
        RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
    }

    // Delete extra items in last section.
    RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );

    // Delete extra sections.
    RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );

    // Delete unused commands.
    RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );

    // Resolves: tdf#103166 if the menu is empty, add a disabled
    // <No Selection Possible> placeholder.
    sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
    gint nItemsCount = 0;
    for (nSection = 0; nSection < nSectionsCount; ++nSection)
    {
        nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
        if (nItemsCount)
            break;
    }
    if (!nItemsCount)
    {
        gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
        OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
        g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
                                    OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
        NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
        NativeSetEnableItem(aNativeCommand, false);
        g_free(aNativeCommand);
    }
}

void GtkSalMenu::Update()
{
    //find out if top level is a menubar or not, if not, then it's a popup menu
    //hierarchy and in those we hide (most) disabled entries
    const GtkSalMenu* pMenu = this;
    while (pMenu->mpParentSalMenu)
        pMenu = pMenu->mpParentSalMenu;
    ImplUpdate(false, !pMenu->mbMenuBar);
}

static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
{
    Point *pPos = static_cast<Point*>(user_data);
    *x = pPos->X();
    if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
    {
        GtkRequisition natural_size;
        gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
        *x -= natural_size.width;
    }
    *y = pPos->Y();
    *push_in = false;
}

bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
                                     FloatWinPopupFlags nFlags)
{
    VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
    mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());

    GLOActionGroup* pActionGroup = g_lo_action_group_new();
    mpActionGroup = G_ACTION_GROUP(pActionGroup);
    mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
    // Generate the main menu structure, populates mpMenuModel
    UpdateFull();

    GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
    gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
    gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);

    //run in a sub main loop because we need to keep vcl PopupMenu alive to use
    //it during DispatchCommand, returning now to the outer loop causes the
    //launching PopupMenu to be destroyed, instead run the subloop here
    //until the gtk menu is destroyed
    GMainLoop* pLoop = g_main_loop_new(nullptr, true);
    g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);

#if GTK_CHECK_VERSION(3,22,0)
    if (gtk_check_version(3, 22, 0) == nullptr)
    {
        GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;

        if (nFlags & FloatWinPopupFlags::Left)
        {
            rect_anchor = GDK_GRAVITY_NORTH_WEST;
            menu_anchor = GDK_GRAVITY_NORTH_EAST;
        }
        else if (nFlags & FloatWinPopupFlags::Up)
        {
            rect_anchor = GDK_GRAVITY_NORTH_WEST;
            menu_anchor = GDK_GRAVITY_SOUTH_WEST;
        }
        else if (nFlags & FloatWinPopupFlags::Right)
        {
            rect_anchor = GDK_GRAVITY_NORTH_EAST;
        }

        tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
        aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
        GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
                           static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};

        GdkWindow* gdkWindow = widget_get_window(mpFrame->getMouseEventWidget());
        gtk_menu_popup_at_rect(GTK_MENU(pWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
    }
    else
#endif
    {
        guint nButton;
        guint32 nTime;

        //typically there is an event, and we can then distinguish if this was
        //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
        //doesn't)
        GdkEvent *pEvent = gtk_get_current_event();
        if (pEvent)
        {
            gdk_event_get_button(pEvent, &nButton);
            nTime = gdk_event_get_time(pEvent);
        }
        else
        {
            nButton = 0;
            nTime = GtkSalFrame::GetLastInputEventTime();
        }

        // do the same strange semantics as vcl popup windows to arrive at a frame geometry
        // in mirrored UI case; best done by actually executing the same code
        sal_uInt16 nArrangeIndex;
        Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
        aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);

        gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
                       &aPos, nButton, nTime);
    }

    if (g_main_loop_is_running(pLoop))
    {
        gdk_threads_leave();
        g_main_loop_run(pLoop);
        gdk_threads_enter();
    }
    g_main_loop_unref(pLoop);

    mpVCLMenu->Deactivate();

    gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);

    gtk_widget_destroy(pWidget);

    g_object_unref(mpActionGroup);
    ClearActionGroupAndMenuModel();

    mpFrame = nullptr;

    return true;
}

/*
 * GtkSalMenu
 */

GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
    mbInActivateCallback( false ),
    mbMenuBar( bMenuBar ),
    mbNeedsUpdate( false ),
    mbReturnFocusToDocument( false ),
    mbAddedGrab( false ),
    mpMenuBarContainerWidget( nullptr ),
    mpMenuAllowShrinkWidget( nullptr ),
    mpMenuBarWidget( nullptr ),
    mpMenuBarContainerProvider( nullptr ),
    mpMenuBarProvider( nullptr ),
    mpCloseButton( nullptr ),
    mpVCLMenu( nullptr ),
    mpParentSalMenu( nullptr ),
    mpFrame( nullptr ),
    mpMenuModel( nullptr ),
    mpActionGroup( nullptr )
{
    //typically this only gets called after the menu has been customized on the
    //next idle slot, in the normal case of a new menubar SetFrame is called
    //directly long before this idle would get called.
    maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
    maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
    maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
}

IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
{
    SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
    if (!mpFrame)
        return;
    SetFrame(mpFrame);
}

void GtkSalMenu::SetNeedsUpdate()
{
    GtkSalMenu* pMenu = this;
    // start that the menu and its parents are in need of an update
    // on the next activation
    while (pMenu && !pMenu->mbNeedsUpdate)
    {
        pMenu->mbNeedsUpdate = true;
        pMenu = pMenu->mpParentSalMenu;
    }
    // only if a menubar is directly updated do we force in a full
    // structure update
    if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
        maUpdateMenuBarIdle.Start();
}

void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
{
    if (mpMenuModel)
        g_object_unref(mpMenuModel);
    mpMenuModel = pMenuModel;
    if (mpMenuModel)
        g_object_ref(mpMenuModel);
}

GtkSalMenu::~GtkSalMenu()
{
    SolarMutexGuard aGuard;

    DestroyMenuBarWidget();

    if (mpMenuModel)
        g_object_unref(mpMenuModel);

    maItems.clear();

    if (mpFrame)
        mpFrame->SetMenu(nullptr);
}

bool GtkSalMenu::VisibleMenuBar()
{
    return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
}

void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
{
    SolarMutexGuard aGuard;
    GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );

    if ( nPos == MENU_APPEND )
        maItems.push_back( pItem );
    else
        maItems.insert( maItems.begin() + nPos, pItem );

    pItem->mpParentMenu = this;

    SetNeedsUpdate();
}

void GtkSalMenu::RemoveItem( unsigned nPos )
{
    SolarMutexGuard aGuard;
    maItems.erase( maItems.begin() + nPos );
    SetNeedsUpdate();
}

void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
{
    SolarMutexGuard aGuard;
    GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
    GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );

    if ( pGtkSubMenu == nullptr )
        return;

    pGtkSubMenu->mpParentSalMenu = this;
    pItem->mpSubMenu = pGtkSubMenu;

    SetNeedsUpdate();
}

static void CloseMenuBar(GtkWidget *, gpointer pMenu)
{
    Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
}

void GtkSalMenu::ShowCloseButton(bool bShow)
{
    assert(mbMenuBar);
    if (!mpMenuBarContainerWidget)
        return;

    if (!bShow)
    {
        if (mpCloseButton)
            gtk_widget_destroy(mpCloseButton);
        return;
    }

    MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
    mpCloseButton = gtk_button_new();
    g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);

    gtk_button_set_relief(GTK_BUTTON(mpCloseButton), GTK_RELIEF_NONE);
    gtk_button_set_focus_on_click(GTK_BUTTON(mpCloseButton), false);
    gtk_widget_set_can_focus(mpCloseButton, false);

    GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(mpCloseButton));

    GtkCssProvider *pProvider = gtk_css_provider_new();
    static const gchar data[] = "* { "
      "padding: 0;"
      "margin-left: 8px;"
      "margin-right: 8px;"
      "min-width: 18px;"
      "min-height: 18px;"
      "}";
    const gchar olddata[] = "* { "
      "padding: 0;"
      "margin-left: 8px;"
      "margin-right: 8px;"
      "}";
    gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
    gtk_style_context_add_provider(pButtonContext,
                                   GTK_STYLE_PROVIDER(pProvider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    gtk_style_context_add_class(pButtonContext, "flat");
    gtk_style_context_add_class(pButtonContext, "small-button");

    GIcon* icon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
    GtkWidget* image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
    gtk_widget_show(image);
    g_object_unref(icon);

    OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
    gtk_widget_set_tooltip_text(mpCloseButton,
        OUStringToOString(sToolTip, RTL_TEXTENCODING_UTF8).getStr());

    gtk_widget_set_valign(mpCloseButton, GTK_ALIGN_CENTER);

    gtk_container_add(GTK_CONTAINER(mpCloseButton), image);
    gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), GTK_WIDGET(mpCloseButton), 1, 0, 1, 1);
    gtk_widget_show_all(mpCloseButton);
}

//Typically when the menubar is deactivated we want the focus to return
//to where it came from. If the menubar was activated because of F6
//moving focus into the associated VCL menubar then on pressing ESC
//or any other normal reason for deactivation we want focus to return
//to the document, definitely not still stuck in the associated
//VCL menubar. But if F6 is pressed while the menubar is activated
//we want to pass that F6 back to the VCL menubar which will move
//focus to the next pane by itself.
void GtkSalMenu::ReturnFocus()
{
    if (mbAddedGrab)
    {
        gtk_grab_remove(mpMenuBarWidget);
        mbAddedGrab = false;
    }
    if (!mbReturnFocusToDocument)
        gtk_widget_grab_focus(GTK_WIDGET(mpFrame->getEventBox()));
    else
        mpFrame->GetWindow()->GrabFocusToDocument();
    mbReturnFocusToDocument = false;
}

gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
{
    if (pEvent->keyval == GDK_KEY_F6)
    {
        mbReturnFocusToDocument = false;
        gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
        //because we return false here, the keypress will continue
        //to propagate and in the case that vcl focus is in
        //the vcl menubar then that will also process F6 and move
        //to the next pane
    }
    return false;
}

//The GtkSalMenu is owner by a Vcl Menu/MenuBar. In the menubar
//case the vcl menubar is present and "visible", but with a 0 height
//so it not apparent. Normally it acts as though it is not there when
//a Native menubar is active. If we return true here, then for keyboard
//activation and traversal with F6 through panes then the vcl menubar
//acts as though it *is* present and we translate its take focus and F6
//traversal key events into the gtk menubar equivalents.
bool GtkSalMenu::CanGetFocus() const
{
    return mpMenuBarWidget != nullptr;
}

bool GtkSalMenu::TakeFocus()
{
    if (!mpMenuBarWidget)
        return false;

    //Send a keyboard event to the gtk menubar to let it know it has been
    //activated via the keyboard. Doesn't do anything except cause the gtk
    //menubar "keyboard_mode" member to get set to true, so typically mnemonics
    //are shown which will serve as indication that the menubar has focus
    //(given that we want to show it with no menus popped down)
    GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
    gtk_widget_event(mpMenuBarWidget, event);
    gdk_event_free(event);

    //this pairing results in a menubar with keyboard focus with no menus
    //auto-popped down
    gtk_grab_add(mpMenuBarWidget);
    mbAddedGrab = true;
    gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
    gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
    mbReturnFocusToDocument = true;
    return true;
}

static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
{
    GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
    GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
    pMenu->ReturnFocus();
}

static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
{
    GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
    return pMenu->SignalKey(pEvent);
}

void GtkSalMenu::CreateMenuBarWidget()
{
    if (mpMenuBarContainerWidget)
        return;

    GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
    mpMenuBarContainerWidget = gtk_grid_new();

    gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
    gtk_grid_insert_row(pGrid, 0);
    gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);

    mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
    // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
    // but still allow scrolled window to not be sized to the child content.
    // So the menubar can be shrunk past its nominal smallest width.
    // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
    gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);

    mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);

    gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
    gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
    gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
    gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);

    g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
    g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);

    gtk_widget_show_all(mpMenuBarContainerWidget);

    ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );

    ApplyPersona();
}

void GtkSalMenu::ApplyPersona()
{
    if (!mpMenuBarContainerWidget)
        return;
    assert(mbMenuBar);
    // I'm dubious about the persona theming feature, but as it exists, lets try and support
    // it, apply the image to the mpMenuBarContainerWidget
    const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();

    GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
    if (mpMenuBarContainerProvider)
    {
        gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
        mpMenuBarContainerProvider = nullptr;
    }
    GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
    if (mpMenuBarProvider)
    {
        gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
        mpMenuBarProvider = nullptr;
    }

    if (!rPersonaBitmap.IsEmpty())
    {
        if (maPersonaBitmap != rPersonaBitmap)
        {
            vcl::PNGWriter aPNGWriter(rPersonaBitmap);
            mxPersonaImage.reset(new utl::TempFile);
            mxPersonaImage->EnableKillingFile(true);
            SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
            aPNGWriter.Write(*pStream);
            mxPersonaImage->CloseStream();
        }

        mpMenuBarContainerProvider = gtk_css_provider_new();
        OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
        OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
        gtk_css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength(), nullptr);
        gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);


        // force the menubar to be transparent when persona is active otherwise for
        // me the menubar becomes gray when its in the backdrop
        mpMenuBarProvider = gtk_css_provider_new();
        static const gchar data[] = "* { "
          "background-image: none;"
          "background-color: transparent;"
          "}";
        gtk_css_provider_load_from_data(mpMenuBarProvider, data, -1, nullptr);
        gtk_style_context_add_provider(pMenuBarContext,
                                       GTK_STYLE_PROVIDER(mpMenuBarProvider),
                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    }
    maPersonaBitmap = rPersonaBitmap;
}

void GtkSalMenu::DestroyMenuBarWidget()
{
    if (mpMenuBarContainerWidget)
    {
        gtk_widget_destroy(mpMenuBarContainerWidget);
        mpMenuBarContainerWidget = nullptr;
        mpCloseButton = nullptr;
    }
}

void GtkSalMenu::SetFrame(const SalFrame* pFrame)
{
    SolarMutexGuard aGuard;
    assert(mbMenuBar);
    SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
    mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));

    // if we had a menu on the GtkSalMenu we have to free it as we generate a
    // full menu anyway and we might need to reuse an existing model and
    // actiongroup
    mpFrame->SetMenu( this );
    mpFrame->EnsureAppMenuWatch();

    // Clean menu model and action group if needed.
    GtkWidget* pWidget = mpFrame->getWindow();
    GdkWindow* gdkWindow = gtk_widget_get_window( pWidget );

    GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
    SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);

    if ( pMenuModel )
    {
        if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
            g_lo_menu_remove( pMenuModel, 0 );

        mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
    }

    if ( pActionGroup )
    {
        g_lo_action_group_clear( pActionGroup );
        mpActionGroup = G_ACTION_GROUP( pActionGroup );
    }

    // Generate the main menu structure.
    if ( PrepUpdate() )
        UpdateFull();

    g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );

    if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
    {
        DestroyMenuBarWidget();
        CreateMenuBarWidget();
    }
}

const GtkSalFrame* GtkSalMenu::GetFrame() const
{
    SolarMutexGuard aGuard;
    const GtkSalMenu* pMenu = this;
    while( pMenu && ! pMenu->mpFrame )
        pMenu = pMenu->mpParentSalMenu;
    return pMenu ? pMenu->mpFrame : nullptr;
}

void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
{
    SolarMutexGuard aGuard;

    if ( mpActionGroup == nullptr )
        return;

    gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );

    if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
    {
        GVariant *pCheckValue = nullptr;
        GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );

        if ( bits & MenuItemBits::RADIOCHECK )
            pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
        else
        {
            // By default, all checked items are checkmark buttons.
            if (bCheck || pCurrentState != nullptr)
                pCheckValue = g_variant_new_boolean( bCheck );
        }

        if ( pCheckValue != nullptr )
        {
            if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
            {
                g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
            }
            else
            {
                g_variant_unref (pCheckValue);
            }
        }

        if ( pCurrentState != nullptr )
            g_variant_unref( pCurrentState );
    }

    if ( aCommand )
        g_free( aCommand );
}

void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
{
    SolarMutexGuard aGuard;
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );

    if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
        g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
}

void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
{
    SolarMutexGuard aGuard;
    // Escape all underscores so that they don't get interpreted as hotkeys
    OUString aText = rText.replaceAll( "_", "__" );
    // Replace the LibreOffice hotkey identifier with an underscore
    aText = aText.replace( '~', '_' );
    OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );

    // Update item text only when necessary.
    gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );

    if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
        g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );

    if ( aLabel )
        g_free( aLabel );
}

namespace
{
    void DestroyMemoryStream(gpointer data)
    {
        SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
        delete pMemStm;
    }
}

void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
{
#if GLIB_CHECK_VERSION(2,38,0)
    if (!!rImage && mbHasNullItemIcon)
        return;

    SolarMutexGuard aGuard;

    if (!!rImage)
    {
        SvMemoryStream* pMemStm = new SvMemoryStream;
        vcl::PNGWriter aWriter(rImage.GetBitmapEx());
        aWriter.Write(*pMemStm);

        GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
                                                    pMemStm->TellEnd(),
                                                    DestroyMemoryStream,
                                                    pMemStm);

        GIcon *pIcon = g_bytes_icon_new(pBytes);

        g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
        g_object_unref(pIcon);
        g_bytes_unref(pBytes);
        mbHasNullItemIcon = false;
    }
    else
    {
        g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
        mbHasNullItemIcon = true;
    }
#else
    (void)nSection;
    (void)nItemPos;
    (void)rImage;
#endif
}

void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, const OUString& rKeyName )
{
    SolarMutexGuard aGuard;

    if ( rKeyName.isEmpty() )
        return;

    guint nKeyCode;
    GdkModifierType nModifiers;
    GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);

    gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );

    gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );

    if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
        g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );

    g_free( aAccelerator );
    g_free( aCurrentAccel );
}

bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
                                       unsigned nItemPos,
                                       sal_uInt16 nId,
                                       const gchar* aCommand,
                                       MenuItemBits nBits,
                                       bool bChecked,
                                       bool bIsSubmenu )
{
    bool bSubMenuAddedOrRemoved = false;

    SolarMutexGuard aGuard;
    GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );

    GVariant *pTarget = nullptr;

    if (g_action_group_has_action(mpActionGroup, aCommand))
        g_lo_action_group_remove(pActionGroup, aCommand);

    if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
    {
        // Item is a checkmark button.
        GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
        GVariant* pState = g_variant_new_boolean( bChecked );

        g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
    }
    else if ( nBits & MenuItemBits::RADIOCHECK )
    {
        // Item is a radio button.
        GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
        GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
        GVariant* pState = g_variant_new_string( "" );
        pTarget = g_variant_new_string( aCommand );

        g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
    }
    else
    {
        // Item is not special, so insert a stateless action.
        g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
    }

    GLOMenu* pMenu = G_LO_MENU( mpMenuModel );

    // Menu item is not updated unless it's necessary.
    gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );

    if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
    {
        bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
        bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
        if (bSubMenuAddedOrRemoved)
        {
            //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
            //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
            //support achieving that
            gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
            g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
            g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
            g_free(pLabel);
        }

        g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );

        gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );

        if ( bIsSubmenu )
            g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
        else
        {
            g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
            pTarget = nullptr;
        }

        g_free( aItemCommand );
    }

    if ( aCurrentCommand )
        g_free( aCurrentCommand );

    if (pTarget)
        g_variant_unref(pTarget);

    return bSubMenuAddedOrRemoved;
}

GtkSalMenu* GtkSalMenu::GetTopLevel()
{
    GtkSalMenu *pMenu = this;
    while (pMenu->mpParentSalMenu)
        pMenu = pMenu->mpParentSalMenu;
    return pMenu;
}

void GtkSalMenu::DispatchCommand(const gchar *pCommand)
{
    SolarMutexGuard aGuard;
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalSubMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
    if (pTopLevel->mpMenuBarWidget)
    {
        // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
        // closing the menu. To handle this properly I imagine we need to set groups for the
        // radiobuttons so the others visually untoggle when the active one is toggled and
        // we would further need to teach vcl that the state can change more than once.
        //
        // or we could unconditionally deactivate the menus if regardless of what particular
        // type of menu item got activated
        gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
    }
    pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
}

void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
{
    for (GtkSalMenuItem* pSalItem : maItems)
    {
        if ( pSalItem->mpSubMenu != nullptr )
        {
            // We can re-enter this method via the new event loop that gets created
            // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
            // flag to detect that and skip some startup work.
            if (!pSalItem->mpSubMenu->mbInActivateCallback)
            {
                pSalItem->mpSubMenu->mbInActivateCallback = true;
                pMenuBar->HandleMenuActivateEvent(pSalItem->mpSubMenu->GetMenu());
                pSalItem->mpSubMenu->mbInActivateCallback = false;
                pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
                pSalItem->mpSubMenu->Update();
                pMenuBar->HandleMenuDeActivateEvent(pSalItem->mpSubMenu->GetMenu());
            }
        }
    }
}

void GtkSalMenu::ClearActionGroupAndMenuModel()
{
    SetMenuModel(nullptr);
    mpActionGroup = nullptr;
    for (GtkSalMenuItem* pSalItem : maItems)
    {
        if ( pSalItem->mpSubMenu != nullptr )
        {
            pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
        }
    }
}

void GtkSalMenu::Activate(const gchar* pCommand)
{
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
    Menu* pVclMenu = pSalMenu->GetMenu();
    Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
    GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;

    pSubMenu->mbInActivateCallback = true;
    pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
    pSubMenu->mbInActivateCallback = false;
    pVclSubMenu->UpdateNativeMenu();
}

void GtkSalMenu::Deactivate(const gchar* pCommand)
{
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
    Menu* pVclMenu = pSalMenu->GetMenu();
    Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
    pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
}

void GtkSalMenu::EnableUnity(bool bEnable)
{
    bUnityMode = bEnable;

    MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
    bool bDisplayable(pMenuBar->IsDisplayable());

    if (bEnable)
    {
        DestroyMenuBarWidget();
        UpdateFull();
        if (!bDisplayable)
            ShowMenuBar(false);
    }
    else
    {
        Update();
        ShowMenuBar(bDisplayable);
    }

    pMenuBar->LayoutChanged();
}

void GtkSalMenu::ShowMenuBar( bool bVisible )
{
    // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
    if (bUnityMode)
    {
        if (bVisible)
            Update();
        else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
            g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
    }
    else if (bVisible)
        CreateMenuBarWidget();
    else
        DestroyMenuBarWidget();
}

bool GtkSalMenu::IsItemVisible( unsigned nPos )
{
    SolarMutexGuard aGuard;
    bool bVisible = false;

    if ( nPos < maItems.size() )
        bVisible = maItems[ nPos ]->mbVisible;

    return bVisible;
}

void GtkSalMenu::CheckItem( unsigned, bool )
{
}

void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
{
    SolarMutexGuard aGuard;
    if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
    {
        gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
        NativeSetEnableItem( pCommand, bEnable );
        g_free( pCommand );
    }
}

void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
{
    SolarMutexGuard aGuard;
    if ( nPos < maItems.size() )
    {
        maItems[ nPos ]->mbVisible = bShow;
        if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
            Update();
    }
}

void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
{
    SolarMutexGuard aGuard;
    if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
    {
        gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );

        gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
        for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
        {
            gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
            for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
            {
                gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );

                if ( !g_strcmp0( pCommandFromModel, pCommand ) )
                {
                    NativeSetItemText( nSection, nItem, rText );
                    g_free( pCommandFromModel );
                    g_free( pCommand );
                    return;
                }

                g_free( pCommandFromModel );
            }
        }

        g_free( pCommand );
    }
}

void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
{
}

void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
{
}

void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
{
}

int GtkSalMenu::GetMenuBarHeight() const
{
    return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
}

/*
 * GtkSalMenuItem
 */

GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
    mpParentMenu( nullptr ),
    mpSubMenu( nullptr ),
    mnType( pItemData->eType ),
    mnId( pItemData->nId ),
    mbVisible( true )
{
}

GtkSalMenuItem::~GtkSalMenuItem()
{
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtksys.cxx b/vcl/unx/gtk3/gtk3gtksys.cxx
index 2406e0a..229a718 100644
--- a/vcl/unx/gtk3/gtk3gtksys.cxx
+++ b/vcl/unx/gtk3/gtk3gtksys.cxx
@@ -5,8 +5,279 @@
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "../gtk/gtksys.cxx"
#include <string.h>
#include <gmodule.h>
#include <gtk/gtk.h>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtksys.hxx>
#include <unx/gtk/gtkbackend.hxx>
#include <osl/module.h>

GtkSalSystem *GtkSalSystem::GetSingleton()
{
    static GtkSalSystem *pSingleton = new GtkSalSystem();
    return pSingleton;
}

SalSystem *GtkInstance::CreateSalSystem()
{
    return GtkSalSystem::GetSingleton();
}

GtkSalSystem::GtkSalSystem() : SalGenericSystem()
{
    mpDisplay = gdk_display_get_default();
    countScreenMonitors();
    // rhbz#1285356, native look will be gtk2, which crashes
    // when gtk3 is already loaded. Until there is a solution
    // java-side force look and feel to something that doesn't
    // crash when we are using gtk3
    setenv("STOC_FORCE_SYSTEM_LAF", "true", 1);
}

GtkSalSystem::~GtkSalSystem()
{
}

int
GtkSalSystem::GetDisplayXScreenCount()
{
    return gdk_display_get_n_screens (mpDisplay);
}

namespace
{

struct GdkRectangleCoincidentLess
{
    // fdo#78799 - detect and elide overlaying monitors of different sizes
    bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
    {
        return
            rLeft.x < rRight.x
            || rLeft.y < rRight.y
            ;
    }
};
struct GdkRectangleCoincident
{
    // fdo#78799 - detect and elide overlaying monitors of different sizes
    bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
    {
        return
            rLeft.x == rRight.x
            && rLeft.y == rRight.y
            ;
    }
};

}

/**
 * GtkSalSystem::countScreenMonitors()
 *
 * This method builds the vector which allows us to map from VCL's
 * idea of linear integer ScreenNumber to gtk+'s rather more
 * complicated screen + monitor concept.
 */
void
GtkSalSystem::countScreenMonitors()
{
    maScreenMonitors.clear();
    for (gint i = 0; i < gdk_display_get_n_screens(mpDisplay); i++)
    {
        GdkScreen* const pScreen(gdk_display_get_screen(mpDisplay, i));
        gint nMonitors(pScreen ? gdk_screen_get_n_monitors(pScreen) : 0);
        if (nMonitors > 1)
        {
            std::vector<GdkRectangle> aGeometries;
            aGeometries.reserve(nMonitors);
            for (gint j(0); j != nMonitors; ++j)
            {
                GdkRectangle aGeometry;
                gdk_screen_get_monitor_geometry(pScreen, j, &aGeometry);
                aGeometries.push_back(aGeometry);
            }
            std::sort(aGeometries.begin(), aGeometries.end(),
                    GdkRectangleCoincidentLess());
            const std::vector<GdkRectangle>::iterator aUniqueEnd(
                    std::unique(aGeometries.begin(), aGeometries.end(),
                    GdkRectangleCoincident()));
            nMonitors = std::distance(aGeometries.begin(), aUniqueEnd);
        }
        maScreenMonitors.emplace_back(pScreen, nMonitors);
    }
}

SalX11Screen
GtkSalSystem::getXScreenFromDisplayScreen(unsigned int nScreen)
{
    gint nMonitor;

    GdkScreen *pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
    if (!pScreen)
        return SalX11Screen (0);
    if (!DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
        return SalX11Screen (0);
    return SalX11Screen (gdk_x11_screen_get_screen_number (pScreen));
}

GdkScreen *
GtkSalSystem::getScreenMonitorFromIdx (int nIdx, gint &nMonitor)
{
    GdkScreen *pScreen = nullptr;
    for (auto const& screenMonitor : maScreenMonitors)
    {
        pScreen = screenMonitor.first;
        if (!pScreen)
            break;
        if (nIdx >= screenMonitor.second)
            nIdx -= screenMonitor.second;
        else
            break;
    }
    nMonitor = nIdx;

    // handle invalid monitor indexes as non-existent screens
    if (nMonitor < 0 || (pScreen && nMonitor >= gdk_screen_get_n_monitors (pScreen)))
        pScreen = nullptr;

    return pScreen;
}

int
GtkSalSystem::getScreenIdxFromPtr (GdkScreen *pScreen)
{
    int nIdx = 0;
    for (auto const& screenMonitor : maScreenMonitors)
    {
        if (screenMonitor.first == pScreen)
            return nIdx;
        nIdx += screenMonitor.second;
    }
    g_warning ("failed to find screen %p", pScreen);
    return 0;
}

int GtkSalSystem::getScreenMonitorIdx (GdkScreen *pScreen,
                                       int nX, int nY)
{
    // TODO: this will fail horribly for exotic combinations like two
    // monitors in mirror mode and one extra. Hopefully such
    // abominations are not used (or, even better, not possible) in
    // practice .-)
    return getScreenIdxFromPtr (pScreen) +
        gdk_screen_get_monitor_at_point (pScreen, nX, nY);
}

unsigned int GtkSalSystem::GetDisplayScreenCount()
{
    gint nMonitor;
    (void)getScreenMonitorFromIdx (G_MAXINT, nMonitor);
    return G_MAXINT - nMonitor;
}

bool GtkSalSystem::IsUnifiedDisplay()
{
    return gdk_display_get_n_screens (mpDisplay) == 1;
}

namespace {
int _fallback_get_primary_monitor (GdkScreen *pScreen)
{
    // Use monitor name as primacy heuristic
    int max = gdk_screen_get_n_monitors (pScreen);
    for (int i = 0; i < max; ++i)
    {
        char *name = gdk_screen_get_monitor_plug_name (pScreen, i);
        bool bLaptop = (name && !g_ascii_strncasecmp (name, "LVDS", 4));
        g_free (name);
        if (bLaptop)
            return i;
    }
    return 0;
}

int _get_primary_monitor (GdkScreen *pScreen)
{
    static int (*get_fn) (GdkScreen *) = nullptr;
    get_fn = gdk_screen_get_primary_monitor;
    // Perhaps we have a newer gtk+ with this symbol:
    if (!get_fn)
    {
        get_fn = reinterpret_cast<int(*)(GdkScreen*)>(osl_getAsciiFunctionSymbol(nullptr,
            "gdk_screen_get_primary_monitor"));
    }
    if (!get_fn)
        get_fn = _fallback_get_primary_monitor;
    if (get_fn)
        return get_fn (pScreen);
    else
        return 0;
}
} // end anonymous namespace

unsigned int GtkSalSystem::GetDisplayBuiltInScreen()
{
    GdkScreen *pDefault = gdk_display_get_default_screen (mpDisplay);
    int idx = getScreenIdxFromPtr (pDefault);
    return idx + _get_primary_monitor (pDefault);
}

tools::Rectangle GtkSalSystem::GetDisplayScreenPosSizePixel (unsigned int nScreen)
{
    gint nMonitor;
    GdkScreen *pScreen;
    GdkRectangle aRect;
    pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
    if (!pScreen)
        return tools::Rectangle();
    gdk_screen_get_monitor_geometry (pScreen, nMonitor, &aRect);
    return tools::Rectangle (Point(aRect.x, aRect.y), Size(aRect.width, aRect.height));
}

// convert ~ to indicate mnemonic to '_'
static OString MapToGtkAccelerator(const OUString &rStr)
{
    return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
}

int GtkSalSystem::ShowNativeDialog (const OUString& rTitle, const OUString& rMessage,
                                    const std::vector< OUString >& rButtonNames)
{
    OString aTitle (OUStringToOString (rTitle, RTL_TEXTENCODING_UTF8));
    OString aMessage (OUStringToOString (rMessage, RTL_TEXTENCODING_UTF8));

    GtkDialog *pDialog = GTK_DIALOG (
        g_object_new (GTK_TYPE_MESSAGE_DIALOG,
                      "title", aTitle.getStr(),
                      "message-type", int(GTK_MESSAGE_WARNING),
                      "text", aMessage.getStr(),
                      nullptr));
    int nButton = 0;
    for (auto const& buttonName : rButtonNames)
        gtk_dialog_add_button (pDialog, MapToGtkAccelerator(buttonName).getStr(), nButton++);
    gtk_dialog_set_default_response (pDialog, 0/*nDefaultButton*/);

    nButton = gtk_dialog_run (pDialog);
    if (nButton < 0)
        nButton = -1;

    gtk_widget_destroy (GTK_WIDGET (pDialog));

    return nButton;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3hudawareness.cxx b/vcl/unx/gtk3/gtk3hudawareness.cxx
index 3d928f0..0aa5878 100644
--- a/vcl/unx/gtk3/gtk3hudawareness.cxx
+++ b/vcl/unx/gtk3/gtk3hudawareness.cxx
@@ -1,4 +1,107 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "../gtk/hudawareness.cxx"
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <string.h>

#include <unx/gtk/gtksalmenu.hxx>
#include <unx/gtk/hudawareness.h>

struct HudAwarenessHandle
{
  GDBusConnection *connection;
  HudAwarenessCallback callback;
  gpointer user_data;
  GDestroyNotify notify;
};

static void
hud_awareness_method_call (GDBusConnection       * /* connection */,
                           const gchar           * /* sender */,
                           const gchar           * /* object_path */,
                           const gchar           * /* interface_name */,
                           const gchar           *method_name,
                           GVariant              *parameters,
                           GDBusMethodInvocation *invocation,
                           gpointer               user_data)
{
  HudAwarenessHandle *handle = static_cast<HudAwarenessHandle*>(user_data);

  if (g_str_equal (method_name, "HudActiveChanged"))
    {
      gboolean active;

      g_variant_get (parameters, "(b)", &active);

      (* handle->callback) (active, handle->user_data);
    }

  g_dbus_method_invocation_return_value (invocation, nullptr);
}

guint
hud_awareness_register (GDBusConnection       *connection,
                        const gchar           *object_path,
                        HudAwarenessCallback   callback,
                        gpointer               user_data,
                        GDestroyNotify         notify,
                        GError               **error)
{
  static GDBusInterfaceInfo *iface;
  static GDBusNodeInfo *info;
  GDBusInterfaceVTable vtable;
  HudAwarenessHandle *handle;
  guint object_id;

  memset (static_cast<void *>(&vtable), 0, sizeof (vtable));
  vtable.method_call = hud_awareness_method_call;

  if G_UNLIKELY (iface == nullptr)
    {
      GError *local_error = nullptr;

      info = g_dbus_node_info_new_for_xml ("<node>"
                                             "<interface name='com.canonical.hud.Awareness'>"
                                               "<method name='CheckAwareness'/>"
                                               "<method name='HudActiveChanged'>"
                                                 "<arg type='b'/>"
                                               "</method>"
                                             "</interface>"
                                           "</node>",
                                           &local_error);
      g_assert_no_error (local_error);
      iface = g_dbus_node_info_lookup_interface (info, "com.canonical.hud.Awareness");
      g_assert (iface != nullptr);
    }

  handle = static_cast<HudAwarenessHandle*>(g_malloc (sizeof (HudAwarenessHandle)));

  object_id = g_dbus_connection_register_object (connection, object_path, iface, &vtable, handle, &g_free, error);

  if (object_id == 0)
    {
      g_free (handle);
      return 0;
    }

  handle->connection = static_cast<GDBusConnection*>(g_object_ref (connection));
  handle->callback = callback;
  handle->user_data = user_data;
  handle->notify = notify;

  return object_id;
}

void
hud_awareness_unregister (GDBusConnection *connection,
                          guint            subscription_id)
{
  g_dbus_connection_unregister_object (connection, subscription_id);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3salprn-gtk.cxx b/vcl/unx/gtk3/gtk3salprn-gtk.cxx
index 16bf17c..aeb9b1d 100644
--- a/vcl/unx/gtk3/gtk3salprn-gtk.cxx
+++ b/vcl/unx/gtk3/gtk3salprn-gtk.cxx
@@ -7,6 +7,956 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../gtk/salprn-gtk.cxx"
#include <unx/gtk/gtkprintwrapper.hxx>

#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkprn.hxx>

#include <vcl/configsettings.hxx>
#include <vcl/help.hxx>
#include <vcl/print.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>

#include <gtk/gtk.h>

#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/document/XExporter.hpp>
#include <com/sun/star/document/XFilter.hpp>
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
#include <com/sun/star/sheet/XSpreadsheet.hpp>
#include <com/sun/star/sheet/XSpreadsheetView.hpp>
#include <com/sun/star/view/PrintableState.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>

#include <officecfg/Office/Common.hxx>

#include <rtl/ustring.hxx>
#include <sal/log.hxx>

#include <unotools/streamwrap.hxx>

#include <cstring>
#include <map>

namespace beans = com::sun::star::beans;
namespace uno = com::sun::star::uno;
namespace view = com::sun::star::view;

using vcl::unx::GtkPrintWrapper;

using uno::UNO_QUERY;

class GtkPrintDialog
{
public:
    explicit GtkPrintDialog(vcl::PrinterController& io_rController);
    bool run();
    GtkPrinter* getPrinter() const
    {
        return m_xWrapper->print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(m_pDialog));
    }
    GtkPrintSettings* getSettings() const
    {
        return m_xWrapper->print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog));
    }
    void updateControllerPrintRange();

    ~GtkPrintDialog();

    static void UIOption_CheckHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
    {
        io_pThis->impl_UIOption_CheckHdl(i_pWidget);
    }
    static void UIOption_RadioHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
    {
        io_pThis->impl_UIOption_RadioHdl(i_pWidget);
    }
    static void UIOption_SelectHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
    {
        io_pThis->impl_UIOption_SelectHdl(i_pWidget);
    }

private:
    beans::PropertyValue* impl_queryPropertyValue(GtkWidget* i_pWidget) const;
    void impl_checkOptionalControlDependencies();

    void impl_UIOption_CheckHdl(GtkWidget* i_pWidget);
    void impl_UIOption_RadioHdl(GtkWidget* i_pWidget);
    void impl_UIOption_SelectHdl(GtkWidget* i_pWidget);

    void impl_initDialog();
    void impl_initCustomTab();
    void impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled);

    void impl_readFromSettings();
    void impl_storeToSettings() const;

private:
    GtkWidget* m_pDialog;
    vcl::PrinterController& m_rController;
    std::map<GtkWidget*, OUString> m_aControlToPropertyMap;
    std::map<GtkWidget*, sal_Int32> m_aControlToNumValMap;
    std::shared_ptr<GtkPrintWrapper> m_xWrapper;
};

struct GtkSalPrinter_Impl
{
    OString m_sSpoolFile;
    OUString m_sJobName;
    GtkPrinter* m_pPrinter;
    GtkPrintSettings* m_pSettings;

    GtkSalPrinter_Impl();
    ~GtkSalPrinter_Impl();
};

GtkSalPrinter_Impl::GtkSalPrinter_Impl()
    : m_pPrinter(nullptr)
    , m_pSettings(nullptr)
{
}

GtkSalPrinter_Impl::~GtkSalPrinter_Impl()
{
    if (m_pPrinter)
    {
        g_object_unref(G_OBJECT(m_pPrinter));
        m_pPrinter = nullptr;
    }
    if (m_pSettings)
    {
        g_object_unref(G_OBJECT(m_pSettings));
        m_pSettings = nullptr;
    }
}

namespace
{

GtkInstance const&
lcl_getGtkSalInstance()
{
    // we _know_ this is GtkInstance
    return *static_cast<GtkInstance*>(GetGtkSalData()->m_pInstance);
}

bool
lcl_useSystemPrintDialog()
{
    return officecfg::Office::Common::Misc::UseSystemPrintDialog::get()
        && officecfg::Office::Common::Misc::ExperimentalMode::get()
        && lcl_getGtkSalInstance().getPrintWrapper()->supportsPrinting();
}

}

GtkSalPrinter::GtkSalPrinter(SalInfoPrinter* const i_pInfoPrinter)
    : PspSalPrinter(i_pInfoPrinter)
{
}

GtkSalPrinter::~GtkSalPrinter() = default;

bool
GtkSalPrinter::impl_doJob(
        const OUString* const i_pFileName,
        const OUString& i_rJobName,
        const OUString& i_rAppName,
        ImplJobSetup* const io_pSetupData,
        const bool i_bCollate,
        vcl::PrinterController& io_rController)
{
    io_rController.setJobState(view::PrintableState_JOB_STARTED);
    io_rController.jobStarted();
    const bool bJobStarted(
            PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName,
                1/*i_nCopies*/, i_bCollate, true, io_pSetupData))
        ;

    if (bJobStarted)
    {
        io_rController.createProgressDialog();
        const int nPages(io_rController.getFilteredPageCount());
        for (int nPage(0); nPage != nPages; ++nPage)
        {
            if (nPage == nPages - 1)
                io_rController.setLastPage(true);
            io_rController.printFilteredPage(nPage);
        }
        io_rController.setJobState(view::PrintableState_JOB_COMPLETED);
    }

    return bJobStarted;
}

bool
GtkSalPrinter::StartJob(
        const OUString* const i_pFileName,
        const OUString& i_rJobName,
        const OUString& i_rAppName,
        ImplJobSetup* io_pSetupData,
        vcl::PrinterController& io_rController)
{
    if (!lcl_useSystemPrintDialog())
        return PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName, io_pSetupData, io_rController);

    assert(!m_xImpl);

    m_xImpl.reset(new GtkSalPrinter_Impl());
    m_xImpl->m_sJobName = i_rJobName;

    OString sFileName;
    if (i_pFileName)
        sFileName = OUStringToOString(*i_pFileName, osl_getThreadTextEncoding());

    GtkPrintDialog aDialog(io_rController);
    if (!aDialog.run())
    {
        io_rController.abortJob();
        return false;
    }
    aDialog.updateControllerPrintRange();
    m_xImpl->m_pPrinter = aDialog.getPrinter();
    m_xImpl->m_pSettings = aDialog.getSettings();

    //To-Do proper name, watch for encodings
    sFileName = OString("/tmp/hacking.ps");
    m_xImpl->m_sSpoolFile = sFileName;

    OUString aFileName = OStringToOUString(sFileName, osl_getThreadTextEncoding());

    //To-Do, swap ps/pdf for gtk_printer_accepts_ps()/gtk_printer_accepts_pdf() ?

    return impl_doJob(&aFileName, i_rJobName, i_rAppName, io_pSetupData, /*bCollate*/false, io_rController);
}

bool
GtkSalPrinter::EndJob()
{
    bool bRet = PspSalPrinter::EndJob();

    if (!lcl_useSystemPrintDialog())
        return bRet;

    assert(m_xImpl);

    if (!bRet || m_xImpl->m_sSpoolFile.isEmpty())
        return bRet;

    std::shared_ptr<GtkPrintWrapper> const xWrapper(lcl_getGtkSalInstance().getPrintWrapper());

    GtkPageSetup* pPageSetup = xWrapper->page_setup_new();

    GtkPrintJob* const pJob = xWrapper->print_job_new(
        OUStringToOString(m_xImpl->m_sJobName, RTL_TEXTENCODING_UTF8).getStr(),
        m_xImpl->m_pPrinter, m_xImpl->m_pSettings, pPageSetup);

    GError* error = nullptr;
    bRet = xWrapper->print_job_set_source_file(pJob, m_xImpl->m_sSpoolFile.getStr(), &error);
    if (bRet)
        xWrapper->print_job_send(pJob, nullptr, nullptr, nullptr);
    else
    {
        //To-Do, do something with this
        fprintf(stderr, "error was %s\n", error->message);
        g_error_free(error);
    }

    g_object_unref(pPageSetup);
    m_xImpl.reset();

    //To-Do, remove temp spool file

    return bRet;
}

namespace
{

void
lcl_setHelpText(
        GtkWidget* const io_pWidget,
        const uno::Sequence<OUString>& i_rHelpTexts,
        const sal_Int32 i_nIndex)
{
    if (i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength())
        gtk_widget_set_tooltip_text(io_pWidget,
            OUStringToOString(i_rHelpTexts.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8).getStr());
}

GtkWidget*
lcl_makeFrame(
        GtkWidget* const i_pChild,
        const OUString &i_rText,
        const uno::Sequence<OUString> &i_rHelpTexts,
        sal_Int32* const io_pCurHelpText)
{
    GtkWidget* const pLabel = gtk_label_new(nullptr);
    lcl_setHelpText(pLabel, i_rHelpTexts, !io_pCurHelpText ? 0 : (*io_pCurHelpText)++);
    gtk_misc_set_alignment(GTK_MISC(pLabel), 0.0, 0.5);

    {
        gchar* const pText = g_markup_printf_escaped("<b>%s</b>",
            OUStringToOString(i_rText, RTL_TEXTENCODING_UTF8).getStr());
        gtk_label_set_markup_with_mnemonic(GTK_LABEL(pLabel), pText);
        g_free(pText);
    }

    GtkWidget* const pFrame = gtk_vbox_new(FALSE, 6);
    gtk_box_pack_start(GTK_BOX(pFrame), pLabel, FALSE, FALSE, 0);

    GtkWidget* const pAlignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    gtk_alignment_set_padding(GTK_ALIGNMENT(pAlignment), 0, 0, 12, 0);
    gtk_box_pack_start(GTK_BOX(pFrame), pAlignment, FALSE, FALSE, 0);

    gtk_container_add(GTK_CONTAINER(pAlignment), i_pChild);
    return pFrame;
}

void
lcl_extractHelpTextsOrIds(
        const beans::PropertyValue& rEntry,
        uno::Sequence<OUString>& rHelpStrings)
{
    if (!(rEntry.Value >>= rHelpStrings))
    {
        OUString aHelpString;
        if (rEntry.Value >>= aHelpString)
        {
            rHelpStrings.realloc(1);
            *rHelpStrings.getArray() = aHelpString;
        }
    }
}

GtkWidget*
lcl_combo_box_text_new()
{
    return gtk_combo_box_text_new();
}

void
lcl_combo_box_text_append(GtkWidget* const pWidget, gchar const* const pText)
{
    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(pWidget), pText);
}

}

GtkPrintDialog::GtkPrintDialog(vcl::PrinterController& io_rController)
    : m_rController(io_rController)
    , m_xWrapper(lcl_getGtkSalInstance().getPrintWrapper())
{
    assert(m_xWrapper->supportsPrinting());
    impl_initDialog();
    impl_initCustomTab();
    impl_readFromSettings();
}

void
GtkPrintDialog::impl_initDialog()
{
    //To-Do, like fpicker, set UI language
    m_pDialog = m_xWrapper->print_unix_dialog_new();

    vcl::Window* const pTopWindow(Application::GetActiveTopWindow());
    if (pTopWindow)
    {
        GtkSalFrame* const pFrame(dynamic_cast<GtkSalFrame*>(pTopWindow->ImplGetFrame()));
        if (pFrame)
        {
            GtkWindow* const pParent(GTK_WINDOW(pFrame->getWindow()));
            if (pParent)
                gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
        }
    }

    m_xWrapper->print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(m_pDialog),
        GtkPrintCapabilities(GTK_PRINT_CAPABILITY_COPIES
            | GTK_PRINT_CAPABILITY_COLLATE
            | GTK_PRINT_CAPABILITY_REVERSE
            | GTK_PRINT_CAPABILITY_GENERATE_PS
            | GTK_PRINT_CAPABILITY_NUMBER_UP
            | GTK_PRINT_CAPABILITY_NUMBER_UP_LAYOUT
       ));
}

void
GtkPrintDialog::impl_initCustomTab()
{
    typedef std::vector<std::pair<GtkWidget*, OUString> > CustomTabs_t;

    const uno::Sequence<beans::PropertyValue>& rOptions(m_rController.getUIOptions());
    std::map<OUString, GtkWidget*> aPropertyToDependencyRowMap;
    CustomTabs_t aCustomTabs;
    GtkWidget* pCurParent = nullptr;
    GtkWidget* pCurTabPage = nullptr;
    GtkWidget* pCurSubGroup = nullptr;
    bool bIgnoreSubgroup = false;
    for (const auto& rOption : rOptions)
    {
        uno::Sequence<beans::PropertyValue> aOptProp;
        rOption.Value >>= aOptProp;

        OUString aCtrlType;
        OUString aText;
        OUString aPropertyName;
        uno::Sequence<OUString> aChoices;
        uno::Sequence<sal_Bool> aChoicesDisabled;
        uno::Sequence<OUString> aHelpTexts;
        sal_Int64 nMinValue = 0, nMaxValue = 0;
        sal_Int32 nCurHelpText = 0;
        OUString aDependsOnName;
        sal_Int32 nDependsOnValue = 0;
        bool bUseDependencyRow = false;
        bool bIgnore = false;
        GtkWidget* pGroup = nullptr;
        bool bGtkInternal = false;

        //Fix fdo#69381
        //Next options if this one is empty
        if (!aOptProp.hasElements())
            continue;

        for (const beans::PropertyValue& rEntry : std::as_const(aOptProp))
        {
            if ( rEntry.Name == "Text" )
            {
                OUString aValue;
                rEntry.Value >>= aValue;
                aText = aValue.replace('~', '_');
            }
            else if ( rEntry.Name == "ControlType" )
                rEntry.Value >>= aCtrlType;
            else if ( rEntry.Name == "Choices" )
                rEntry.Value >>= aChoices;
            else if ( rEntry.Name == "ChoicesDisabled" )
                rEntry.Value >>= aChoicesDisabled;
            else if ( rEntry.Name == "Property" )
            {
                beans::PropertyValue aVal;
                rEntry.Value >>= aVal;
                aPropertyName = aVal.Name;
            }
            else if ( rEntry.Name == "DependsOnName" )
                rEntry.Value >>= aDependsOnName;
            else if ( rEntry.Name == "DependsOnEntry" )
                rEntry.Value >>= nDependsOnValue;
            else if ( rEntry.Name == "AttachToDependency" )
                rEntry.Value >>= bUseDependencyRow;
            else if ( rEntry.Name == "MinValue" )
                rEntry.Value >>= nMinValue;
            else if ( rEntry.Name == "MaxValue" )
                rEntry.Value >>= nMaxValue;
            else if ( rEntry.Name == "HelpId" )
            {
                uno::Sequence<OUString> aHelpIds;
                lcl_extractHelpTextsOrIds(rEntry, aHelpIds);
                Help* const pHelp = Application::GetHelp();
                if (pHelp)
                {
                    const int nLen = aHelpIds.getLength();
                    aHelpTexts.realloc(nLen);
                    std::transform(aHelpIds.begin(), aHelpIds.end(), aHelpTexts.begin(),
                        [&pHelp](const OUString& rHelpId) { return pHelp->GetHelpText(rHelpId, static_cast<weld::Widget*>(nullptr)); });
                }
                else // fallback
                    aHelpTexts = aHelpIds;
            }
            else if ( rEntry.Name == "HelpText" )
                lcl_extractHelpTextsOrIds(rEntry, aHelpTexts);
            else if ( rEntry.Name == "InternalUIOnly" )
                rEntry.Value >>= bIgnore;
            else if ( rEntry.Name == "Enabled" )
            {
                // Ignore this. We use UIControlOptions::isUIOptionEnabled
                // to check whether a control should be enabled.
            }
            else if ( rEntry.Name == "GroupingHint" )
            {
                // Ignore this. We cannot add/modify controls to/on existing
                // tabs of the Gtk print dialog.
            }
            else
            {
                SAL_INFO("vcl.gtk", "unhandled UI option entry: " << rEntry.Name);
            }
        }

        if ( aPropertyName == "PrintContent" )
            bGtkInternal = true;

        if (aCtrlType == "Group" || !pCurParent)
        {
            pCurTabPage = gtk_vbox_new(FALSE, 12);
            gtk_container_set_border_width(GTK_CONTAINER(pCurTabPage), 6);
            lcl_setHelpText(pCurTabPage, aHelpTexts, 0);

            pCurParent = pCurTabPage;
            aCustomTabs.emplace_back(pCurTabPage, aText);
        }
        else if (aCtrlType == "Subgroup")
        {
            bIgnoreSubgroup = bIgnore;
            if (bIgnore)
                continue;
            pCurParent = gtk_vbox_new(FALSE, 12);
            gtk_container_set_border_width(GTK_CONTAINER(pCurParent), 0);

            pCurSubGroup = lcl_makeFrame(pCurParent, aText, aHelpTexts, nullptr);
            gtk_box_pack_start(GTK_BOX(pCurTabPage), pCurSubGroup, FALSE, FALSE, 0);
        }
        // special case: we need to map these to controls of the gtk print dialog
        else if (bGtkInternal)
        {
            if ( aPropertyName == "PrintContent" )
            {
                // What to print? And, more importantly, is there a selection?
                impl_initPrintContent(aChoicesDisabled);
            }
        }
        else if (bIgnoreSubgroup || bIgnore)
            continue;
        else
        {
            // change handlers for all the controls set up in this block
            // should be set _after_ the control has been made (in)active,
            // because:
            // 1. value of the property is _known_--we are using it to
            //    _set_ the control, right?--no need to change it back .-)
            // 2. it may cause warning because the widget may not
            //    have been placed in m_aControlToPropertyMap yet

            GtkWidget* pWidget = nullptr;
            beans::PropertyValue* pVal = nullptr;
            if (aCtrlType == "Bool" && pCurParent)
            {
                pWidget = gtk_check_button_new_with_mnemonic(
                    OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
                lcl_setHelpText(pWidget, aHelpTexts, 0);
                m_aControlToPropertyMap[pWidget] = aPropertyName;

                bool bVal = false;
                pVal = m_rController.getValue(aPropertyName);
                if (pVal)
                    pVal->Value >>= bVal;
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bVal);
                gtk_widget_set_sensitive(pWidget,
                    m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
                g_signal_connect(pWidget, "toggled", G_CALLBACK(GtkPrintDialog::UIOption_CheckHdl), this);
            }
            else if (aCtrlType == "Radio" && pCurParent)
            {
                GtkWidget* const pVbox = gtk_vbox_new(FALSE, 12);
                gtk_container_set_border_width(GTK_CONTAINER(pVbox), 0);

                if (!aText.isEmpty())
                    pGroup = lcl_makeFrame(pVbox, aText, aHelpTexts, &nCurHelpText);

                sal_Int32 nSelectVal = 0;
                pVal = m_rController.getValue(aPropertyName);
                if (pVal && pVal->Value.hasValue())
                    pVal->Value >>= nSelectVal;

                for (sal_Int32 m = 0; m != aChoices.getLength(); m++)
                {
                    pWidget = gtk_radio_button_new_with_mnemonic_from_widget(
                        GTK_RADIO_BUTTON(m == 0 ? nullptr : pWidget),
                        OUStringToOString(aChoices[m].replace('~', '_'), RTL_TEXTENCODING_UTF8).getStr());
                    lcl_setHelpText(pWidget, aHelpTexts, nCurHelpText++);
                    m_aControlToPropertyMap[pWidget] = aPropertyName;
                    m_aControlToNumValMap[pWidget] = m;
                    GtkWidget* const pRow = gtk_hbox_new(FALSE, 12);
                    gtk_box_pack_start(GTK_BOX(pVbox), pRow, FALSE, FALSE, 0);
                    gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
                    aPropertyToDependencyRowMap[aPropertyName + OUString::number(m)] = pRow;
                    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), m == nSelectVal);
                    gtk_widget_set_sensitive(pWidget,
                        m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
                    g_signal_connect(pWidget, "toggled",
                            G_CALLBACK(GtkPrintDialog::UIOption_RadioHdl), this);
                }

                if (pGroup)
                    pWidget = pGroup;
                else
                    pWidget = pVbox;
            }
            else if ((aCtrlType == "List"   ||
                       aCtrlType == "Range"  ||
                       aCtrlType == "Edit"
                    ) && pCurParent)
            {
                GtkWidget* const pHbox = gtk_hbox_new(FALSE, 12);
                gtk_container_set_border_width(GTK_CONTAINER(pHbox), 0);

                if ( aCtrlType == "List" )
                {
                   pWidget = lcl_combo_box_text_new();

                   for (const auto& rChoice : std::as_const(aChoices))
                   {
                       lcl_combo_box_text_append(pWidget,
                           OUStringToOString(rChoice, RTL_TEXTENCODING_UTF8).getStr());
                   }

                   sal_Int32 nSelectVal = 0;
                   pVal = m_rController.getValue(aPropertyName);
                   if (pVal && pVal->Value.hasValue())
                       pVal->Value >>= nSelectVal;
                   gtk_combo_box_set_active(GTK_COMBO_BOX(pWidget), nSelectVal);
                   g_signal_connect(pWidget, "changed", G_CALLBACK(GtkPrintDialog::UIOption_SelectHdl), this);
                }
                else if (aCtrlType == "Edit" && pCurParent)
                {
                   pWidget = gtk_entry_new();

                   OUString aCurVal;
                   pVal = m_rController.getValue(aPropertyName);
                   if (pVal && pVal->Value.hasValue())
                       pVal->Value >>= aCurVal;
                   gtk_entry_set_text(GTK_ENTRY(pWidget),
                       OUStringToOString(aCurVal, RTL_TEXTENCODING_UTF8).getStr());
                }
                else if (aCtrlType == "Range" && pCurParent)
                {
                    pWidget = gtk_spin_button_new_with_range(nMinValue, nMaxValue, 1.0);

                    sal_Int64 nCurVal = 0;
                    pVal = m_rController.getValue(aPropertyName);
                    if (pVal && pVal->Value.hasValue())
                        pVal->Value >>= nCurVal;
                    gtk_spin_button_set_value(GTK_SPIN_BUTTON(pWidget), nCurVal);
                }

                lcl_setHelpText(pWidget, aHelpTexts, 0);
                m_aControlToPropertyMap[pWidget] = aPropertyName;

                gtk_widget_set_sensitive(pWidget,
                    m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);

                if (!aText.isEmpty())
                {
                    GtkWidget* const pLabel = gtk_label_new_with_mnemonic(
                        OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
                    gtk_label_set_mnemonic_widget(GTK_LABEL(pLabel), pWidget);
                    gtk_box_pack_start(GTK_BOX(pHbox), pLabel, FALSE, FALSE, 0);
                }

                gtk_box_pack_start(GTK_BOX(pHbox), pWidget, FALSE, FALSE, 0);

                pWidget = pHbox;

            }
            else
                SAL_INFO("vcl.gtk", "unhandled option type: " << aCtrlType);

            GtkWidget* pRow = nullptr;
            if (pWidget)
            {
                if (bUseDependencyRow && !aDependsOnName.isEmpty())
                {
                    pRow = aPropertyToDependencyRowMap[aDependsOnName + OUString::number(nDependsOnValue)];
                    if (!pRow)
                    {
                        gtk_widget_destroy(pWidget);
                        pWidget = nullptr;
                    }
                }
            }
            if (pWidget)
            {
                if (!pRow)
                {
                    pRow = gtk_hbox_new(FALSE, 12);
                    gtk_box_pack_start(GTK_BOX(pCurParent), pRow, FALSE, FALSE, 0);
                }
                if (!pGroup)
                    aPropertyToDependencyRowMap[aPropertyName + OUString::number(0)] = pRow;
                gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
            }
        }
    }

    CustomTabs_t::const_reverse_iterator aEnd = aCustomTabs.rend();
    for (CustomTabs_t::const_reverse_iterator aI = aCustomTabs.rbegin(); aI != aEnd; ++aI)
    {
        gtk_widget_show_all(aI->first);
        m_xWrapper->print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(m_pDialog), aI->first,
            gtk_label_new(OUStringToOString(aI->second, RTL_TEXTENCODING_UTF8).getStr()));
    }
}

void
GtkPrintDialog::impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled)
{
    SAL_WARN_IF(i_rDisabled.getLength() != 3, "vcl.gtk", "there is more choices than we expected");
    if (i_rDisabled.getLength() != 3)
        return;

    GtkPrintUnixDialog* const pDialog(GTK_PRINT_UNIX_DIALOG(m_pDialog));

    // XXX: This is a hack that depends on the number and the ordering of
    // the controls in the rDisabled sequence (cf. the initialization of
    // the "PrintContent" UI option in SwPrintUIOptions::SwPrintUIOptions,
    // sw/source/core/view/printdata.cxx)
    if (m_xWrapper->supportsPrintSelection() && !i_rDisabled[2])
    {
        m_xWrapper->print_unix_dialog_set_support_selection(pDialog, TRUE);
        m_xWrapper->print_unix_dialog_set_has_selection(pDialog, TRUE);
    }

    beans::PropertyValue* const pPrintContent(
            m_rController.getValue(OUString("PrintContent")));

    if (pPrintContent)
    {
        sal_Int32 nSelectionType(0);
        pPrintContent->Value >>= nSelectionType;
        GtkPrintSettings* const pSettings(getSettings());
        GtkPrintPages ePrintPages(GTK_PRINT_PAGES_ALL);
        switch (nSelectionType)
        {
            case 0:
                ePrintPages = GTK_PRINT_PAGES_ALL;
                break;
            case 1:
                ePrintPages = GTK_PRINT_PAGES_RANGES;
                break;
            case 2:
                if (m_xWrapper->supportsPrintSelection())
                    ePrintPages = GTK_PRINT_PAGES_SELECTION;
                else
                    SAL_INFO("vcl.gtk", "the application wants to print a selection, but the present gtk version does not support it");
                break;
            default:
                SAL_WARN("vcl.gtk", "unexpected selection type: " << nSelectionType);
        }
        m_xWrapper->print_settings_set_print_pages(pSettings, ePrintPages);
        m_xWrapper->print_unix_dialog_set_settings(pDialog, pSettings);
        g_object_unref(G_OBJECT(pSettings));
    }
}

void
GtkPrintDialog::impl_checkOptionalControlDependencies()
{
    for (auto& rEntry : m_aControlToPropertyMap)
    {
        gtk_widget_set_sensitive(rEntry.first, m_rController.isUIOptionEnabled(rEntry.second));
    }
}

beans::PropertyValue*
GtkPrintDialog::impl_queryPropertyValue(GtkWidget* const i_pWidget) const
{
    beans::PropertyValue* pVal(nullptr);
    std::map<GtkWidget*, OUString>::const_iterator aIt(m_aControlToPropertyMap.find(i_pWidget));
    if (aIt != m_aControlToPropertyMap.end())
    {
        pVal = m_rController.getValue(aIt->second);
        SAL_WARN_IF(!pVal, "vcl.gtk", "property value not found");
    }
    else
    {
        SAL_WARN("vcl.gtk", "changed control not in property map");
    }
    return pVal;
}

void
GtkPrintDialog::impl_UIOption_CheckHdl(GtkWidget* const i_pWidget)
{
    beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
    if (pVal)
    {
        const bool bVal = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget));
        pVal->Value <<= bVal;

        impl_checkOptionalControlDependencies();
    }
}

void
GtkPrintDialog::impl_UIOption_RadioHdl(GtkWidget* const i_pWidget)
{
    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget)))
    {
        beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
        std::map<GtkWidget*, sal_Int32>::const_iterator it = m_aControlToNumValMap.find(i_pWidget);
        if (pVal && it != m_aControlToNumValMap.end())
        {

            const sal_Int32 nVal = it->second;
            pVal->Value <<= nVal;

            impl_checkOptionalControlDependencies();
        }
    }
}

void
GtkPrintDialog::impl_UIOption_SelectHdl(GtkWidget* const i_pWidget)
{
    beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
    if (pVal)
    {
        const sal_Int32 nVal(gtk_combo_box_get_active(GTK_COMBO_BOX(i_pWidget)));
        pVal->Value <<= nVal;

        impl_checkOptionalControlDependencies();
    }
}

bool
GtkPrintDialog::run()
{
    bool bDoJob = false;
    bool bContinue = true;
    while (bContinue)
    {
        bContinue = false;
        const gint nStatus = gtk_dialog_run(GTK_DIALOG(m_pDialog));
        switch (nStatus)
        {
            case GTK_RESPONSE_HELP:
                fprintf(stderr, "To-Do: Help ?\n");
                bContinue = true;
                break;
            case GTK_RESPONSE_OK:
                bDoJob = true;
                break;
            default:
                break;
        }
    }
    gtk_widget_hide(m_pDialog);
    impl_storeToSettings();
    return bDoJob;
}

void
GtkPrintDialog::updateControllerPrintRange()
{
    GtkPrintSettings* const pSettings(getSettings());
    // TODO: use get_print_pages
    if (const gchar* const pStr = m_xWrapper->print_settings_get(pSettings, GTK_PRINT_SETTINGS_PRINT_PAGES))
    {
        beans::PropertyValue* pVal = m_rController.getValue(OUString("PrintRange"));
        if (!pVal)
            pVal = m_rController.getValue(OUString("PrintContent"));
        SAL_WARN_IF(!pVal, "vcl.gtk", "Nothing to map standard print options to!");
        if (pVal)
        {
            sal_Int32 nVal = 0;
            if (!strcmp(pStr, "all"))
                nVal = 0;
            else if (!strcmp(pStr, "ranges"))
                nVal = 1;
            else if (!strcmp(pStr, "selection"))
                nVal = 2;
            pVal->Value <<= nVal;

            if (nVal == 1)
            {
                pVal = m_rController.getValue(OUString("PageRange"));
                SAL_WARN_IF(!pVal, "vcl.gtk", "PageRange doesn't exist!");
                if (pVal)
                {
                    OUStringBuffer sBuf;
                    gint num_ranges;
                    const GtkPageRange* const pRanges = m_xWrapper->print_settings_get_page_ranges(pSettings, &num_ranges);
                    for (gint i = 0; i != num_ranges && pRanges; ++i)
                    {
                        sBuf.append(sal_Int32(pRanges[i].start+1));
                        if (pRanges[i].start != pRanges[i].end)
                        {
                            sBuf.append('-');
                            sBuf.append(sal_Int32(pRanges[i].end+1));
                        }

                        if (i != num_ranges-1)
                            sBuf.append(',');
                    }
                    pVal->Value <<= sBuf.makeStringAndClear();
                }
            }
        }
    }
    g_object_unref(G_OBJECT(pSettings));
}

GtkPrintDialog::~GtkPrintDialog()
{
    gtk_widget_destroy(m_pDialog);
}

void
GtkPrintDialog::impl_readFromSettings()
{
    vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
    GtkPrintSettings* const pSettings(getSettings());

    const OUString aPrintDialogStr("PrintDialog");
    const OUString aCopyCount(pItem->getValue(aPrintDialogStr,
                "CopyCount"));
    const OUString aCollate(pItem->getValue(aPrintDialogStr,
                "Collate"));

    const gint nOldCopyCount(m_xWrapper->print_settings_get_n_copies(pSettings));
    const sal_Int32 nCopyCount(aCopyCount.toInt32());
    if (nCopyCount > 0 && nOldCopyCount != nCopyCount)
    {
        m_xWrapper->print_settings_set_n_copies(pSettings, sal::static_int_cast<gint>(nCopyCount));
    }

    const bool bOldCollate(m_xWrapper->print_settings_get_collate(pSettings));
    const bool bCollate(aCollate.equalsIgnoreAsciiCase("true"));
    if (bOldCollate != bCollate)
    {
        m_xWrapper->print_settings_set_collate(pSettings, bCollate);
    }

    m_xWrapper->print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog), pSettings);
    g_object_unref(G_OBJECT(pSettings));
}

void
GtkPrintDialog::impl_storeToSettings()
const
{
    vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
    GtkPrintSettings* const pSettings(getSettings());

    const OUString aPrintDialogStr("PrintDialog");
    pItem->setValue(aPrintDialogStr,
            "CopyCount",
            OUString::number(m_xWrapper->print_settings_get_n_copies(pSettings)));
    pItem->setValue(aPrintDialogStr,
            "Collate",
            m_xWrapper->print_settings_get_collate(pSettings)
                ? OUString("true")
                : OUString("false"))
        ;
    // pItem->setValue(aPrintDialog, OUString("ToFile"), );
    g_object_unref(G_OBJECT(pSettings));
    pItem->Commit();
}

sal_uInt32
GtkSalInfoPrinter::GetCapabilities(
        const ImplJobSetup* const i_pSetupData,
        const PrinterCapType i_nType)
{
    if (i_nType == PrinterCapType::ExternalDialog && lcl_useSystemPrintDialog())
        return 1;
    return PspSalInfoPrinter::GetCapabilities(i_pSetupData, i_nType);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
index a3fa632..b05929b 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkaction.cxx"
#include "../../gtk3/a11y/gtk3atkaction.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
index d8e0879..d31e5e4 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkbridge.cxx"
#include "../../gtk3/a11y/gtk3atkbridge.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
index e4eabec..63f44a3 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkcomponent.cxx"
#include "../../gtk3/a11y/gtk3atkcomponent.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
index ea3f089..064e72d 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkeditabletext.cxx"
#include "../../gtk3/a11y/gtk3atkeditabletext.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
index c60db2f..86d9ac4 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkfactory.cxx"
#include "../../gtk3/a11y/gtk3atkfactory.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
index bb9749c..d2ce059 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkhypertext.cxx"
#include "../../gtk3/a11y/gtk3atkhypertext.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
index 4e2c77e..3a1234e 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkimage.cxx"
#include "../../gtk3/a11y/gtk3atkimage.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
index eca1cd7..51d53bf 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atklistener.cxx"
#include "../../gtk3/a11y/gtk3atklistener.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
index 126e97a..f5e7917 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkregistry.cxx"
#include "../../gtk3/a11y/gtk3atkregistry.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
index f67b665..ab39c0e 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkselection.cxx"
#include "../../gtk3/a11y/gtk3atkselection.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
index d886ac0..194791b 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atktable.cxx"
#include "../../gtk3/a11y/gtk3atktable.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
index e4bbd5a..8d668e6 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atktext.cxx"
#include "../../gtk3/a11y/gtk3atktext.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
index b0edad0..c767a95d 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atktextattributes.cxx"
#include "../../gtk3/a11y/gtk3atktextattributes.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
index 8c1eeaf..39eb5ae 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkutil.cxx"
#include "../../gtk3/a11y/gtk3atkutil.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
index 3005794..5a526e1 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkvalue.cxx"
#include "../../gtk3/a11y/gtk3atkvalue.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwindow.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwindow.cxx
index cd8479c..194e109 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwindow.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwindow.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkwindow.cxx"
#include "../../gtk3/a11y/gtk3atkwindow.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
index 3b07e95..b0029f2 100644
--- a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
@@ -7,6 +7,6 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "../../gtk/a11y/atkwrapper.cxx"
#include "../../gtk3/a11y/gtk3atkwrapper.cxx"

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */