vcl/Library_vclplug_gtk4.mk  |    1 
 vcl/inc/unx/gtk/gtkdata.hxx  |    2 
 vcl/unx/gtk3/gtkdata.cxx     |   57 ++
 vcl/unx/gtk3/gtkinst.cxx     |  892 -------------------------------------------
 vcl/unx/gtk4/convert3to4.cxx |  889 ++++++++++++++++++++++++++++++++++++++++++
 vcl/unx/gtk4/gtkinst.cxx     |    2 
 6 files changed, 957 insertions(+), 886 deletions(-)

New commits:
commit 774be6d793203183fe1856ffb8b720f00b48c2bb
Author:     Caolán McNamara <caol...@redhat.com>
AuthorDate: Thu Jun 3 17:07:33 2021 +0100
Commit:     Caolán McNamara <caol...@redhat.com>
CommitDate: Thu Jun 3 21:10:27 2021 +0200

    split .ui conversion code into its own file
    
    Change-Id: Ie27990a497e39ab2fd82a711fa4ec49b472616f6
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116677
    Tested-by: Jenkins
    Reviewed-by: Caolán McNamara <caol...@redhat.com>

diff --git a/vcl/Library_vclplug_gtk4.mk b/vcl/Library_vclplug_gtk4.mk
index 3f1c3ebe88a7..e3cc415be738 100644
--- a/vcl/Library_vclplug_gtk4.mk
+++ b/vcl/Library_vclplug_gtk4.mk
@@ -84,6 +84,7 @@ $(eval $(call gb_Library_add_exception_objects,vclplug_gtk4,\
     vcl/unx/gtk4/fpicker/SalGtkFilePicker \
     vcl/unx/gtk4/fpicker/SalGtkFolderPicker \
     vcl/unx/gtk4/fpicker/SalGtkPicker \
+    vcl/unx/gtk4/convert3to4 \
     vcl/unx/gtk4/gtkdata \
     vcl/unx/gtk4/gtkinst \
     vcl/unx/gtk4/gtksys \
diff --git a/vcl/inc/unx/gtk/gtkdata.hxx b/vcl/inc/unx/gtk/gtkdata.hxx
index cc37cd12a19b..ef257c7d83b9 100644
--- a/vcl/inc/unx/gtk/gtkdata.hxx
+++ b/vcl/inc/unx/gtk/gtkdata.hxx
@@ -195,6 +195,8 @@ inline GdkGLContext* surface_create_gl_context(GdkSurface* 
pSurface)
 typedef GtkClipboard GdkClipboard;
 #endif
 
+int getButtonPriority(const OString &rType);
+
 class GtkSalTimer final : public SalTimer
 {
     struct SalGtkTimeoutSource *m_pTimeout;
diff --git a/vcl/unx/gtk3/gtkdata.cxx b/vcl/unx/gtk3/gtkdata.cxx
index 5bd818e83491..14877985919c 100644
--- a/vcl/unx/gtk3/gtkdata.cxx
+++ b/vcl/unx/gtk3/gtkdata.cxx
@@ -843,4 +843,61 @@ void GtkSalDisplay::deregisterFrame( SalFrame* pFrame )
     SalGenericDisplay::deregisterFrame( pFrame );
 }
 
+namespace {
+
+struct ButtonOrder
+{
+    const char * m_aType;
+    int m_nPriority;
+};
+
+}
+
+int getButtonPriority(const OString &rType)
+{
+    static const size_t N_TYPES = 8;
+    static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+    {
+        { "/discard", 0 },
+        { "/cancel", 1 },
+        { "/close", 1 },
+        { "/no", 2 },
+        { "/open", 3 },
+        { "/save", 3 },
+        { "/yes", 3 },
+        { "/ok", 3 }
+    };
+
+    static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
+    {
+        { "/open", 0 },
+        { "/save", 0 },
+        { "/yes", 0 },
+        { "/ok", 0 },
+        { "/discard", 1 },
+        { "/no", 1 },
+        { "/cancel", 2 },
+        { "/close", 2 }
+    };
+
+    const ButtonOrder* pOrder = &aDiscardCancelSave[0];
+
+    const OUString &rEnv = Application::GetDesktopEnvironment();
+
+    if (rEnv.equalsIgnoreAsciiCase("windows") ||
+        rEnv.equalsIgnoreAsciiCase("tde") ||
+        rEnv.startsWithIgnoreAsciiCase("kde"))
+    {
+        pOrder = &aSaveDiscardCancel[0];
+    }
+
+    for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
+    {
+        if (rType.endsWith(pOrder->m_aType))
+            return pOrder->m_nPriority;
+    }
+
+    return -1;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
index 3a42aace7132..90cc8603b855 100644
--- a/vcl/unx/gtk3/gtkinst.cxx
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -27,9 +27,9 @@
 #include <headless/svpvd.hxx>
 #include <headless/svpbmp.hxx>
 #include <vcl/builder.hxx>
-#include <vcl/toolkit/floatwin.hxx>
 #include <vcl/inputtypes.hxx>
 #include <vcl/transfer.hxx>
+#include <vcl/toolkit/floatwin.hxx>
 #include <unx/genpspgraphics.h>
 #include <rtl/strbuf.hxx>
 #include <sal/log.hxx>
@@ -44,8 +44,6 @@
 #if !GTK_CHECK_VERSION(4, 0, 0)
 #include "a11y/atkwrapper.hxx"
 #endif
-#include <com/sun/star/beans/XPropertySet.hpp>
-#include <com/sun/star/io/TempFile.hpp>
 #include <com/sun/star/datatransfer/XTransferable.hpp>
 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
@@ -59,14 +57,11 @@
 #include <com/sun/star/lang/XServiceInfo.hpp>
 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
 #include <com/sun/star/lang/XInitialization.hpp>
-#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
-#include <com/sun/star/xml/sax/Writer.hpp>
-#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
 #include <comphelper/lok.hxx>
 #include <comphelper/processfactory.hxx>
 #include <comphelper/sequence.hxx>
-#include <cppuhelper/compbase.hxx>
 #include <comphelper/string.hxx>
+#include <cppuhelper/compbase.hxx>
 #include <cppuhelper/implbase.hxx>
 #include <cppuhelper/supportsservice.hxx>
 #include <officecfg/Office/Common.hxx>
@@ -148,16 +143,16 @@ extern "C"
             XInitThreads();
 #endif
 
+#if !GTK_CHECK_VERSION(4, 0, 0)
         // 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 );
 
-#if !GTK_CHECK_VERSION(4, 0, 0)
         gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
-#endif
         SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
+#endif
 
         auto pYieldMutex = std::make_unique<GtkYieldMutex>();
 
@@ -5173,70 +5168,6 @@ std::unique_ptr<weld::Container> 
GtkInstanceWidget::weld_parent() const
 
 namespace {
 
-struct ButtonOrder
-{
-    const char * m_aType;
-    int m_nPriority;
-};
-
-int getButtonPriority(const OString &rType)
-{
-    static const size_t N_TYPES = 8;
-    static const ButtonOrder aDiscardCancelSave[N_TYPES] =
-    {
-        { "/discard", 0 },
-        { "/cancel", 1 },
-        { "/close", 1 },
-        { "/no", 2 },
-        { "/open", 3 },
-        { "/save", 3 },
-        { "/yes", 3 },
-        { "/ok", 3 }
-    };
-
-    static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
-    {
-        { "/open", 0 },
-        { "/save", 0 },
-        { "/yes", 0 },
-        { "/ok", 0 },
-        { "/discard", 1 },
-        { "/no", 1 },
-        { "/cancel", 2 },
-        { "/close", 2 }
-    };
-
-    const ButtonOrder* pOrder = &aDiscardCancelSave[0];
-
-    const OUString &rEnv = Application::GetDesktopEnvironment();
-
-    if (rEnv.equalsIgnoreAsciiCase("windows") ||
-        rEnv.equalsIgnoreAsciiCase("tde") ||
-        rEnv.startsWithIgnoreAsciiCase("kde"))
-    {
-        pOrder = &aSaveDiscardCancel[0];
-    }
-
-    for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
-    {
-        if (rType.endsWith(pOrder->m_aType))
-            return pOrder->m_nPriority;
-    }
-
-    return -1;
-}
-
-#if GTK_CHECK_VERSION(4, 0, 0)
-typedef std::pair<css::uno::Reference<css::xml::dom::XNode>, OUString> 
named_node;
-
-bool sortButtonNodes(const named_node& rA, const named_node& rB)
-{
-    //order within groups according to platform rules
-    return getButtonPriority("/" + rA.second.toUtf8()) <
-           getButtonPriority("/" + rB.second.toUtf8());
-}
-#endif
-
 bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
 {
     //order within groups according to platform rules
@@ -21007,825 +20938,14 @@ bool IsAllowedBuiltInIcon(std::u16string_view 
iconName)
 
 namespace {
 
-#if GTK_CHECK_VERSION(4, 0, 0)
-
-// <property name="spacing">6</property>
-Reference<css::xml::dom::XNode> CreateProperty(const 
css::uno::Reference<css::xml::dom::XDocument>& xDoc,
-                                               const OUString& rPropName, 
const OUString& rValue)
-{
-    css::uno::Reference<css::xml::dom::XElement> xProperty = 
xDoc->createElement("property");
-    css::uno::Reference<css::xml::dom::XAttr> xPropName = 
xDoc->createAttribute("name");
-    xPropName->setValue(rPropName);
-    xProperty->setAttributeNode(xPropName);
-    css::uno::Reference<css::xml::dom::XText> xValue = 
xDoc->createTextNode(rValue);
-    xProperty->appendChild(xValue);
-    return xProperty;
-}
-
-bool ToplevelIsMessageDialog(const Reference<css::xml::dom::XNode>& xNode)
-{
-    for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = 
xNode->getParentNode();
-         xObjectCandidate.is();
-         xObjectCandidate = xObjectCandidate->getParentNode())
-    {
-        if (xObjectCandidate->getNodeName() == "object")
-        {
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap = 
xObjectCandidate->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xClass = 
xObjectMap->getNamedItem("class");
-            if (xClass->getNodeValue() == "GtkMessageDialog")
-                return true;
-        }
-    }
-    return false;
-}
-
-void SetPropertyOnTopLevel(const Reference<css::xml::dom::XNode>& xNode, const 
Reference<css::xml::dom::XNode>& xProperty)
-{
-    for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = 
xNode->getParentNode();
-         xObjectCandidate.is();
-         xObjectCandidate = xObjectCandidate->getParentNode())
-    {
-        if (xObjectCandidate->getNodeName() == "object")
-        {
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap = 
xObjectCandidate->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xClass = 
xObjectMap->getNamedItem("class");
-            if (xClass->getNodeValue() == "GtkDialog")
-            {
-                auto xFirstChild = xObjectCandidate->getFirstChild();
-                if (xFirstChild.is())
-                    xObjectCandidate->insertBefore(xProperty, xFirstChild);
-                else
-                    xObjectCandidate->appendChild(xProperty);
-
-                break;
-            }
-        }
-    }
-}
-
-OUString GetParentObjectType(const Reference<css::xml::dom::XNode>& xNode)
-{
-    auto xParent = xNode->getParentNode();
-    assert(xParent->getNodeName() == "object");
-    css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = 
xParent->getAttributes();
-    css::uno::Reference<css::xml::dom::XNode> xClass = 
xParentMap->getNamedItem("class");
-    return xClass->getNodeValue();
-}
-
-// currently runs the risk of duplicate margin-* properties if there was 
already such as well
-// as the border
-void AddBorderAsMargins(const Reference<css::xml::dom::XNode>& xNode, const 
OUString& rBorderWidth)
-{
-    auto xDoc = xNode->getOwnerDocument();
-
-    auto xMarginEnd = CreateProperty(xDoc, "margin-end", rBorderWidth);
-
-    auto xFirstChild = xNode->getFirstChild();
-    if (xFirstChild.is())
-        xNode->insertBefore(xMarginEnd, xFirstChild);
-    else
-        xNode->appendChild(xMarginEnd);
-
-    xNode->insertBefore(CreateProperty(xDoc, "margin-top", rBorderWidth), 
xMarginEnd);
-    xNode->insertBefore(CreateProperty(xDoc, "margin-bottom", rBorderWidth), 
xMarginEnd);
-    xNode->insertBefore(CreateProperty(xDoc, "margin-start", rBorderWidth), 
xMarginEnd);
-}
-
-struct ConvertResult
-{
-    bool m_bChildCanFocus;
-    bool m_bHasVisible;
-    bool m_bHasIconName;
-    bool m_bAlwaysShowImage;
-    css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
-
-    ConvertResult(bool bChildCanFocus,
-                  bool bHasVisible,
-                  bool bHasIconName,
-                  bool bAlwaysShowImage,
-                  const css::uno::Reference<css::xml::dom::XNode>& 
rPropertyLabel)
-        : m_bChildCanFocus(bChildCanFocus)
-        , m_bHasVisible(bHasVisible)
-        , m_bHasIconName(bHasIconName)
-        , m_bAlwaysShowImage(bAlwaysShowImage)
-        , m_xPropertyLabel(rPropertyLabel)
-    {
-    }
-};
-
-ConvertResult Convert3To4(const Reference<css::xml::dom::XNode>& xNode)
-{
-    css::uno::Reference<css::xml::dom::XNodeList> xNodeList = 
xNode->getChildNodes();
-    if (!xNodeList.is())
-        return ConvertResult(false, false, false, false, nullptr);
-
-    std::vector<css::uno::Reference<css::xml::dom::XNode>> xRemoveList;
-
-    OUString sBorderWidth;
-    bool bChildCanFocus = false;
-    bool bHasVisible = false;
-    bool bHasIconName = false;
-    bool bAlwaysShowImage = false;
-    css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
-    css::uno::Reference<css::xml::dom::XNode> xCantFocus;
-
-    css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
-    while (xChild.is())
-    {
-        if (xChild->getNodeName() == "requires")
-        {
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xLib = 
xMap->getNamedItem("lib");
-            assert(xLib->getNodeValue() == "gtk+");
-            xLib->setNodeValue("gtk");
-            css::uno::Reference<css::xml::dom::XNode> xVersion = 
xMap->getNamedItem("version");
-            assert(xVersion->getNodeValue() == "3.20");
-            xVersion->setNodeValue("4.0");
-        }
-        else if (xChild->getNodeName() == "property")
-        {
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xName = 
xMap->getNamedItem("name");
-            OUString sName(xName->getNodeValue().replace('_', '-'));
-
-            if (sName == "border-width")
-                sBorderWidth = xChild->getFirstChild()->getNodeValue();
-
-            if (sName == "has-default")
-            {
-                css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = 
xChild->getParentNode()->getAttributes();
-                css::uno::Reference<css::xml::dom::XNode> xId = 
xParentMap->getNamedItem("id");
-                auto xDoc = xChild->getOwnerDocument();
-                auto xDefaultWidget = CreateProperty(xDoc, "default-widget", 
xId->getNodeValue());
-                SetPropertyOnTopLevel(xChild, xDefaultWidget);
-                xRemoveList.push_back(xChild);
-            }
-
-            if (sName == "has-focus" || sName == "is-focus")
-            {
-                css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = 
xChild->getParentNode()->getAttributes();
-                css::uno::Reference<css::xml::dom::XNode> xId = 
xParentMap->getNamedItem("id");
-                auto xDoc = xChild->getOwnerDocument();
-                auto xDefaultWidget = CreateProperty(xDoc, "focus-widget", 
xId->getNodeValue());
-                SetPropertyOnTopLevel(xChild, xDefaultWidget);
-                xRemoveList.push_back(xChild);
-            }
-
-            if (sName == "can-focus")
-            {
-                bChildCanFocus = 
toBool(xChild->getFirstChild()->getNodeValue());
-                if (!bChildCanFocus)
-                {
-                    OUString sParentClass = GetParentObjectType(xChild);
-                    if (sParentClass == "GtkBox" || sParentClass == "GtkGrid")
-                    {
-                        // e.g. for the case of notebooks without children 
yet, just remove the can't focus property
-                        // from Boxes and Grids
-                        xRemoveList.push_back(xChild);
-                    }
-                    else if (sParentClass == "GtkComboBoxText")
-                    {
-                        // this was always a bit finicky in gtk3, fix it up to 
default to can-focus
-                        xRemoveList.push_back(xChild);
-                    }
-                    else
-                    {
-                        // otherwise mark the property as needing removal if 
there turns out to be a child
-                        // with can-focus of true, in which case remove this 
parent conflicting property
-                        xCantFocus = xChild;
-                    }
-                }
-            }
-
-            if (sName == "label")
-                xPropertyLabel = xChild;
-
-            if (sName == "visible")
-                bHasVisible = true;
-
-            if (sName == "icon-name")
-                bHasIconName = true;
-
-            if (sName == "events")
-                xRemoveList.push_back(xChild);
-
-            if (sName == "activates-default")
-            {
-                if (GetParentObjectType(xChild) == "GtkSpinButton")
-                    xRemoveList.push_back(xChild);
-            }
-
-            if (sName == "width-chars")
-            {
-                if (GetParentObjectType(xChild) == "GtkEntry")
-                {
-                    // I don't quite get what the difference should be wrt 
width-chars and max-width-chars
-                    // but glade doesn't write max-width-chars and in gtk4 
where we have width-chars, e.g
-                    // print dialog, then max-width-chars gives the effect we 
wanted with width-chars
-                    auto xDoc = xChild->getOwnerDocument();
-                    auto mMaxWidthChars = CreateProperty(xDoc, 
"max-width-chars", xChild->getFirstChild()->getNodeValue());
-                    xChild->getParentNode()->insertBefore(mMaxWidthChars, 
xChild);
-                }
-            }
-
-            // remove 'Help' button label and replace with a help icon instead
-            if (sName == "label" && GetParentObjectType(xChild) == "GtkButton")
-            {
-                css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = 
xChild->getParentNode()->getAttributes();
-                css::uno::Reference<css::xml::dom::XNode> xId = 
xParentMap->getNamedItem("id");
-                if (xId && xId->getNodeValue() == "help")
-                {
-                    auto xDoc = xChild->getOwnerDocument();
-                    auto xIconName = CreateProperty(xDoc, "icon-name", 
"help-browser-symbolic");
-                    xChild->getParentNode()->insertBefore(xIconName, xChild);
-                    xRemoveList.push_back(xChild);
-                }
-            }
-
-            if (sName == "icon-size")
-            {
-                if (GetParentObjectType(xChild) == "GtkImage")
-                {
-                    if (xChild->getFirstChild()->getNodeValue() == "6")
-                    {
-                        auto xDoc = xChild->getOwnerDocument();
-                        // convert old GTK_ICON_SIZE_DIALOG to new 
GTK_ICON_SIZE_LARGE
-                        auto xIconSize = CreateProperty(xDoc, "icon-size", 
"2");
-                        xChild->getParentNode()->insertBefore(xIconSize, 
xChild);
-                        xRemoveList.push_back(xChild);
-                    }
-                    else
-                        SAL_WARN( "vcl.gtk", "what should we do with an 
icon-size of: " << xChild->getFirstChild()->getNodeValue());
-                }
-            }
-
-            if (sName == "truncate-multiline")
-            {
-                if (GetParentObjectType(xChild) == "GtkSpinButton")
-                    xRemoveList.push_back(xChild);
-            }
-
-            if (sName == "homogeneous")
-            {
-                // e.g. the buttonbox in xml filter dialog
-                if (GetParentObjectType(xChild) == "GtkButtonBox")
-                    xRemoveList.push_back(xChild);
-            }
-
-            if (sName == "shadow-type")
-            {
-                if (GetParentObjectType(xChild) == "GtkFrame")
-                    xRemoveList.push_back(xChild);
-                else if (GetParentObjectType(xChild) == "GtkScrolledWindow")
-                {
-                    bool bHasFrame = xChild->getFirstChild()->getNodeValue() 
!= "none";
-                    auto xDoc = xChild->getOwnerDocument();
-                    auto xHasFrame = CreateProperty(xDoc, "has-frame",
-                                                    bHasFrame ? 
OUString("True") : OUString("False"));
-                    xChild->getParentNode()->insertBefore(xHasFrame, xChild);
-                    xRemoveList.push_back(xChild);
-                }
-            }
-
-            if (sName == "always-show-image")
-            {
-                if (GetParentObjectType(xChild) == "GtkButton")
-                {
-                    // we will turn always-show-image into a GtkBox child for
-                    // GtkButton and a GtkLabel child for the GtkBox and move
-                    // the label property into it.
-                    bAlwaysShowImage = 
toBool(xChild->getFirstChild()->getNodeValue());
-                    xRemoveList.push_back(xChild);
-                }
-            }
-
-            if (sName == "relief")
-            {
-                if (GetParentObjectType(xChild) == "GtkLinkButton" ||
-                    GetParentObjectType(xChild) == "GtkButton")
-                {
-                    assert(xChild->getFirstChild()->getNodeValue() == "none");
-                    auto xDoc = xChild->getOwnerDocument();
-                    auto xHasFrame = CreateProperty(xDoc, "has-frame", 
"False");
-                    xChild->getParentNode()->insertBefore(xHasFrame, xChild);
-                    xRemoveList.push_back(xChild);
-                }
-            }
-
-            if (sName == "xalign")
-            {
-                if (GetParentObjectType(xChild) == "GtkLinkButton" ||
-                    GetParentObjectType(xChild) == "GtkMenuButton" ||
-                    GetParentObjectType(xChild) == "GtkButton")
-                {
-                    // TODO expand into a GtkLabel child with alignment on 
that instead
-                    assert(xChild->getFirstChild()->getNodeValue() == "0");
-                    xRemoveList.push_back(xChild);
-                }
-            }
-
-            if (sName == "hscrollbar-policy")
-            {
-                if (GetParentObjectType(xChild) == "GtkScrolledWindow")
-                {
-                    if (xChild->getFirstChild()->getNodeValue() == "never")
-                    {
-                        auto xDoc = xChild->getOwnerDocument();
-                        auto xHasFrame = CreateProperty(xDoc, 
"propagate-natural-width", "True");
-                        xChild->getParentNode()->insertBefore(xHasFrame, 
xChild);
-                    }
-                }
-            }
-
-            if (sName == "vscrollbar-policy")
-            {
-                if (GetParentObjectType(xChild) == "GtkScrolledWindow")
-                {
-                    if (xChild->getFirstChild()->getNodeValue() == "never")
-                    {
-                        auto xDoc = xChild->getOwnerDocument();
-                        auto xHasFrame = CreateProperty(xDoc, 
"propagate-natural-height", "True");
-                        xChild->getParentNode()->insertBefore(xHasFrame, 
xChild);
-                    }
-                }
-            }
-
-            if (sName == "image")
-            {
-                if (GetParentObjectType(xChild) == "GtkButton")
-                {
-                    // find the image object, expected to be a child of 
"interface" and relocate
-                    // it to be a child of this GtkButton
-                    auto xObjectCandidate = xChild->getParentNode();
-                    if (xObjectCandidate->getNodeName() == "object")
-                    {
-                        OUString sImageId = 
xChild->getFirstChild()->getNodeValue();
-
-                        css::uno::Reference<css::xml::dom::XNode> 
xRootCandidate = xChild->getParentNode();
-                        while (xRootCandidate)
-                        {
-                             if (xRootCandidate->getNodeName() == "interface")
-                                 break;
-                             xRootCandidate = xRootCandidate->getParentNode();
-                        }
-
-                        css::uno::Reference<css::xml::dom::XNode> xImageNode;
-
-                        for (auto xImageCandidate = 
xRootCandidate->getFirstChild(); xImageCandidate.is(); xImageCandidate= 
xImageCandidate->getNextSibling())
-                        {
-                            css::uno::Reference<css::xml::dom::XNamedNodeMap> 
xImageCandidateMap = xImageCandidate->getAttributes();
-                            if (!xImageCandidateMap.is())
-                                continue;
-                            css::uno::Reference<css::xml::dom::XNode> xId = 
xImageCandidateMap->getNamedItem("id");
-                            if (xId && xId->getNodeValue() == sImageId)
-                            {
-                                xImageNode = xImageCandidate;
-                                break;
-                            }
-                        }
-
-                        auto xDoc = xChild->getOwnerDocument();
-                        css::uno::Reference<css::xml::dom::XElement> 
xImageChild = xDoc->createElement("child");
-                        
xImageChild->appendChild(xImageNode->getParentNode()->removeChild(xImageNode));
-                        xObjectCandidate->appendChild(xImageChild);
-                    }
-
-                    xRemoveList.push_back(xChild);
-                }
-            }
-
-            if (sName == "draw-indicator")
-            {
-                assert(toBool(xChild->getFirstChild()->getNodeValue()));
-                xRemoveList.push_back(xChild);
-            }
-
-            if (sName == "type-hint" || sName == "skip-taskbar-hint" ||
-                sName == "can-default" || sName == "border-width" ||
-                sName == "layout-style" || sName == "no-show-all" ||
-                sName == "ignore-hidden" || sName == "window-position")
-            {
-                xRemoveList.push_back(xChild);
-            }
-        }
-        else if (xChild->getNodeName() == "child")
-        {
-            bool bContentArea = false;
-
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xName = 
xMap->getNamedItem("internal-child");
-            if (xName)
-            {
-                OUString sName(xName->getNodeValue());
-                if (sName == "vbox")
-                {
-                    xName->setNodeValue("content_area");
-                    bContentArea = true;
-                }
-                else if (sName == "accessible")
-                    xRemoveList.push_back(xChild); // Yikes!, what's the 
replacement for this going to be
-            }
-
-            if (bContentArea)
-            {
-                for (css::uno::Reference<css::xml::dom::XNode> 
xObjectCandidate = xChild->getFirstChild();
-                     xObjectCandidate.is();
-                     xObjectCandidate = xObjectCandidate->getNextSibling())
-                {
-                    if (xObjectCandidate->getNodeName() == "object")
-                    {
-                        auto xDoc = xChild->getOwnerDocument();
-
-                        auto xVExpand = CreateProperty(xDoc, "vexpand", 
"True");
-                        auto xFirstChild = xObjectCandidate->getFirstChild();
-                        if (xFirstChild.is())
-                            xObjectCandidate->insertBefore(xVExpand, 
xFirstChild);
-                        else
-                            xObjectCandidate->appendChild(xVExpand);
-
-                        if (!sBorderWidth.isEmpty())
-                        {
-                            AddBorderAsMargins(xObjectCandidate, sBorderWidth);
-                            sBorderWidth.clear();
-                        }
-
-                        break;
-                    }
-                }
-            }
-        }
-        else if (xChild->getNodeName() == "packing")
-        {
-            // remove "packing" and if its grid packing insert a replacement 
"layout" into
-            // the associated "object"
-            auto xDoc = xChild->getOwnerDocument();
-            css::uno::Reference<css::xml::dom::XElement> xNew = 
xDoc->createElement("layout");
-
-            bool bGridPacking = false;
-
-            // iterate over all children and append them to the new element
-            for (css::uno::Reference<css::xml::dom::XNode> xCurrent = 
xChild->getFirstChild();
-                 xCurrent.is();
-                 xCurrent = xChild->getFirstChild())
-            {
-                css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xCurrent->getAttributes();
-                if (xMap.is())
-                {
-                    css::uno::Reference<css::xml::dom::XNode> xName = 
xMap->getNamedItem("name");
-                    OUString sName(xName->getNodeValue().replace('_', '-'));
-                    if (sName == "left-attach")
-                    {
-                        xName->setNodeValue("column");
-                        bGridPacking = true;
-                    }
-                    else if (sName == "top-attach")
-                    {
-                        xName->setNodeValue("row");
-                        bGridPacking = true;
-                    }
-                    else if (sName == "width")
-                    {
-                        xName->setNodeValue("column-span");
-                        bGridPacking = true;
-                    }
-                    else if (sName == "height")
-                    {
-                        xName->setNodeValue("row-span");
-                        bGridPacking = true;
-                    }
-                    else if (sName == "secondary")
-                    {
-                        // turn parent tag of <child> into <child type="start">
-                        auto xParent = xChild->getParentNode();
-                        css::uno::Reference<css::xml::dom::XAttr> xTypeStart = 
xDoc->createAttribute("type");
-                        xTypeStart->setValue("start");
-                        css::uno::Reference<css::xml::dom::XElement> 
xElem(xParent, css::uno::UNO_QUERY_THROW);
-                        xElem->setAttributeNode(xTypeStart);
-                    }
-                }
-                xNew->appendChild(xChild->removeChild(xCurrent));
-            }
-
-            if (bGridPacking)
-            {
-                // go back to parent and find the object child and insert this 
"layout" as a
-                // new child of the object
-                auto xParent = xChild->getParentNode();
-                for (css::uno::Reference<css::xml::dom::XNode> 
xObjectCandidate = xParent->getFirstChild();
-                     xObjectCandidate.is();
-                     xObjectCandidate = xObjectCandidate->getNextSibling())
-                {
-                    if (xObjectCandidate->getNodeName() == "object")
-                    {
-                        xObjectCandidate->appendChild(xNew);
-                        break;
-                    }
-                }
-            }
-
-            xRemoveList.push_back(xChild);
-        }
-        else if (xChild->getNodeName() == "accessibility")
-        {
-            // TODO <relation type="labelled-by" target="pagenumcb"/> -> 
<relation name="labelled-by">pagenumcb</relation>
-            xRemoveList.push_back(xChild);
-        }
-        else if (xChild->getNodeName() == "accelerator")
-        {
-            // TODO is anything like this supported anymore in .ui files
-            xRemoveList.push_back(xChild);
-        }
-
-        auto xNextChild = xChild->getNextSibling();
-
-        bool bChildHasIconName = false;
-        bool bChildHasVisible = false;
-        bool bChildAlwaysShowImage = false;
-        css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
-        if (xChild->hasChildNodes())
-        {
-            auto aChildRes = Convert3To4(xChild);
-            bChildCanFocus |= aChildRes.m_bChildCanFocus;
-            if (bChildCanFocus && xCantFocus.is())
-            {
-                xNode->removeChild(xCantFocus);
-                xCantFocus.clear();
-            }
-            if (xChild->getNodeName() == "object")
-            {
-                bChildHasVisible = aChildRes.m_bHasVisible;
-                bChildHasIconName = aChildRes.m_bHasIconName;
-                bChildAlwaysShowImage = aChildRes.m_bAlwaysShowImage;
-                xChildPropertyLabel = aChildRes.m_xPropertyLabel;
-            }
-        }
-
-        if (xChild->getNodeName() == "object")
-        {
-            auto xDoc = xChild->getOwnerDocument();
-
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xClass = 
xMap->getNamedItem("class");
-            OUString sClass(xClass->getNodeValue());
-
-            auto xInternalChildCandidate = xChild->getParentNode();
-            css::uno::Reference<css::xml::dom::XNamedNodeMap> 
xInternalChildCandidateMap = xInternalChildCandidate->getAttributes();
-            css::uno::Reference<css::xml::dom::XNode> xId = 
xInternalChildCandidateMap->getNamedItem("internal-child");
-
-            // turn default gtk3 invisibility for widget objects into explicit 
invisible, but ignore internal-children
-            if (!bChildHasVisible && !xId)
-            {
-                if (sClass == "GtkBox" || sClass == "GtkButton" ||
-                    sClass == "GtkCalendar" || sClass == "GtkCheckButton" ||
-                    sClass == "GtkRadioButton" || sClass == "GtkComboBox" ||
-                    sClass == "GtkComboBoxText" || sClass == "GtkDrawingArea" 
||
-                    sClass == "GtkEntry" || sClass == "GtkExpander" ||
-                    sClass == "GtkFrame" || sClass == "GtkGrid" ||
-                    sClass == "GtkImage" || sClass == "GtkLabel" ||
-                    sClass == "GtkMenuButton" || sClass == "GtkNotebook" ||
-                    sClass == "GtkOverlay" || sClass == "GtkPaned" ||
-                    sClass == "GtkProgressBar" || sClass == 
"GtkScrolledWindow" ||
-                    sClass == "GtkSeparator" || sClass == "GtkSpinButton" ||
-                    sClass == "GtkSpinner" || sClass == "GtkTextView" ||
-                    sClass == "GtkTreeView" || sClass == "GtkViewport" ||
-                    sClass == "GtkLinkButton" || sClass == "GtkToggleButton" ||
-                    sClass == "GtkButtonBox")
-
-                {
-                    auto xVisible = CreateProperty(xDoc, "visible", "False");
-                    auto xFirstChild = xChild->getFirstChild();
-                    if (xFirstChild.is())
-                        xChild->insertBefore(xVisible, xFirstChild);
-                    else
-                        xChild->appendChild(xVisible);
-                }
-            }
-
-            if (sClass == "GtkButtonBox")
-            {
-                if (xId && xId->getNodeValue() == "action_area" && 
!ToplevelIsMessageDialog(xChild))
-                {
-                    xClass->setNodeValue("GtkHeaderBar");
-                    auto xSpacingNode = CreateProperty(xDoc, 
"show-title-buttons", "False");
-                    auto xFirstChild = xChild->getFirstChild();
-                    if (xFirstChild.is())
-                        xChild->insertBefore(xSpacingNode, xFirstChild);
-                    else
-                        xChild->appendChild(xSpacingNode);
-
-                    // move the replacement GtkHeaderBar up to before the 
content_area
-                    auto xContentAreaCandidate = xChild->getParentNode();
-                    while (xContentAreaCandidate)
-                    {
-                        css::uno::Reference<css::xml::dom::XNamedNodeMap> 
xChildMap = xContentAreaCandidate->getAttributes();
-                        css::uno::Reference<css::xml::dom::XNode> xName = 
xChildMap->getNamedItem("internal-child");
-                        if (xName && xName->getNodeValue() == "content_area")
-                        {
-                            auto xActionArea = xChild->getParentNode();
-
-                            
xActionArea->getParentNode()->removeChild(xActionArea);
-
-                            css::uno::Reference<css::xml::dom::XAttr> 
xTypeTitleBar = xDoc->createAttribute("type");
-                            xTypeTitleBar->setValue("titlebar");
-                            css::uno::Reference<css::xml::dom::XElement> 
xElem(xActionArea, css::uno::UNO_QUERY_THROW);
-                            xElem->setAttributeNode(xTypeTitleBar);
-                            xElem->removeAttribute("internal-child");
-
-                            
xContentAreaCandidate->getParentNode()->insertBefore(xActionArea, 
xContentAreaCandidate);
-
-                            std::vector<named_node> aChildren;
-
-                            css::uno::Reference<css::xml::dom::XNode> 
xTitleChild = xChild->getFirstChild();
-                            while (xTitleChild.is())
-                            {
-                                auto xNextTitleChild = 
xTitleChild->getNextSibling();
-                                if (xTitleChild->getNodeName() == "child")
-                                {
-                                    OUString sNodeId;
-
-                                    for 
(css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = 
xTitleChild->getFirstChild();
-                                         xObjectCandidate.is();
-                                         xObjectCandidate = 
xObjectCandidate->getNextSibling())
-                                    {
-                                        if (xObjectCandidate->getNodeName() == 
"object")
-                                        {
-                                            
css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap = 
xObjectCandidate->getAttributes();
-                                            
css::uno::Reference<css::xml::dom::XNode> xObjectId = 
xObjectMap->getNamedItem("id");
-                                            sNodeId = 
xObjectId->getNodeValue();
-                                            break;
-                                        }
-                                    }
-
-                                    
aChildren.push_back(std::make_pair(xTitleChild, sNodeId));
-                                }
-                                else if (xTitleChild->getNodeName() == 
"property")
-                                {
-                                    // remove any <property 
name="homogeneous"> tag
-                                    
css::uno::Reference<css::xml::dom::XNamedNodeMap> xTitleChildMap = 
xTitleChild->getAttributes();
-                                    css::uno::Reference<css::xml::dom::XNode> 
xPropName = xTitleChildMap->getNamedItem("name");
-                                    OUString 
sPropName(xPropName->getNodeValue().replace('_', '-'));
-                                    if (sPropName == "homogeneous")
-                                        xChild->removeChild(xTitleChild);
-                                }
-
-                                xTitleChild = xNextTitleChild;
-                            }
-
-                            //sort child order within parent so that we match 
the platform button order
-                            std::stable_sort(aChildren.begin(), 
aChildren.end(), sortButtonNodes);
-
-                            int nNonHelpButtonCount = 0;
-
-                            for (const auto& rTitleChild : aChildren)
-                            {
-                                xChild->removeChild(rTitleChild.first);
-                                if (rTitleChild.second != "help")
-                                    ++nNonHelpButtonCount;
-                            }
-
-                            std::reverse(aChildren.begin(), aChildren.end());
-
-                            for (const auto& rTitleChild : aChildren)
-                            {
-                                xChild->appendChild(rTitleChild.first);
-
-                                css::uno::Reference<css::xml::dom::XElement> 
xChildElem(rTitleChild.first, css::uno::UNO_QUERY_THROW);
-                                if (!xChildElem->hasAttribute("type"))
-                                {
-                                    // turn parent tag of <child> into <child 
type="end"> except for cancel/close which we'll
-                                    // put at start unless there is nothing at 
end
-                                    css::uno::Reference<css::xml::dom::XAttr> 
xTypeEnd = xDoc->createAttribute("type");
-                                    if (nNonHelpButtonCount >= 2 && 
(rTitleChild.second == "cancel" || rTitleChild.second == "close"))
-                                        xTypeEnd->setValue("start");
-                                    else
-                                        xTypeEnd->setValue("end");
-                                    xChildElem->setAttributeNode(xTypeEnd);
-                                }
-                            }
-
-                            auto xUseHeaderBar = CreateProperty(xDoc, 
"use-header-bar", "1");
-                            SetPropertyOnTopLevel(xContentAreaCandidate, 
xUseHeaderBar);
-
-                            break;
-                        }
-                        xContentAreaCandidate = 
xContentAreaCandidate->getParentNode();
-                    }
-                }
-                else // GtkMessageDialog
-                    xClass->setNodeValue("GtkBox");
-            }
-            else if (sClass == "GtkRadioButton")
-            {
-                xClass->setNodeValue("GtkCheckButton");
-            }
-            else if (sClass == "GtkImage" && !bChildHasIconName)
-            {
-                xClass->setNodeValue("GtkPicture");
-            }
-            else if (sClass == "GtkPopover" && !bHasVisible)
-            {
-                auto xVisible = CreateProperty(xDoc, "visible", "False");
-                auto xFirstChild = xChild->getFirstChild();
-                if (xFirstChild.is())
-                    xChild->insertBefore(xVisible, xFirstChild);
-                else
-                    xChild->appendChild(xVisible);
-            }
-
-            if (bChildAlwaysShowImage)
-            {
-                auto xImageCandidateNode = xChild->getLastChild();
-                if (xImageCandidateNode && xImageCandidateNode->getNodeName() 
!= "child")
-                    xImageCandidateNode.clear();
-                if (xImageCandidateNode)
-                    xChild->removeChild(xImageCandidateNode);
-
-                css::uno::Reference<css::xml::dom::XElement> xNewChildNode = 
xDoc->createElement("child");
-                css::uno::Reference<css::xml::dom::XElement> xNewObjectNode = 
xDoc->createElement("object");
-                css::uno::Reference<css::xml::dom::XAttr> xBoxClassName = 
xDoc->createAttribute("class");
-                xBoxClassName->setValue("GtkBox");
-                xNewObjectNode->setAttributeNode(xBoxClassName);
-                xNewChildNode->appendChild(xNewObjectNode);
-
-                xChild->appendChild(xNewChildNode);
-
-                css::uno::Reference<css::xml::dom::XElement> 
xNewLabelChildNode = xDoc->createElement("child");
-                css::uno::Reference<css::xml::dom::XElement> 
xNewChildObjectNode = xDoc->createElement("object");
-                css::uno::Reference<css::xml::dom::XAttr> xLabelClassName = 
xDoc->createAttribute("class");
-                xLabelClassName->setValue("GtkLabel");
-                xNewChildObjectNode->setAttributeNode(xLabelClassName);
-                if (xChildPropertyLabel)
-                    
xNewChildObjectNode->appendChild(xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel));
-                xNewLabelChildNode->appendChild(xNewChildObjectNode);
-
-                if (xImageCandidateNode)
-                    xNewObjectNode->appendChild(xImageCandidateNode);
-                xNewObjectNode->appendChild(xNewLabelChildNode);
-            }
-        }
-
-        xChild = xNextChild;
-    }
-
-    if (!sBorderWidth.isEmpty())
-        AddBorderAsMargins(xNode, sBorderWidth);
-
-    for (auto& xRemove : xRemoveList)
-        xNode->removeChild(xRemove);
-
-    return ConvertResult(bChildCanFocus, bHasVisible, bHasIconName, 
bAlwaysShowImage, xPropertyLabel);
-}
-#endif
-
 void load_ui_file(GtkBuilder* pBuilder, const OUString& rUri)
 {
-    GError *err = nullptr;
-
 #if GTK_CHECK_VERSION(4, 0, 0)
-    // load the xml
-    css::uno::Reference<css::uno::XComponentContext> xContext = 
::comphelper::getProcessComponentContext();
-    css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder = 
xml::dom::DocumentBuilder::create(xContext);
-    css::uno::Reference<css::xml::dom::XDocument> xDocument = 
xBuilder->parseURI(rUri);
-
-    // convert it from gtk3 to gtk4
-    Convert3To4(xDocument);
-
-    css::uno::Reference<css::beans::XPropertySet> 
xTempFile(io::TempFile::create(xContext), css::uno::UNO_QUERY);
-    css::uno::Reference<css::io::XStream> xTempStream(xTempFile, 
css::uno::UNO_QUERY_THROW);
-    xTempFile->setPropertyValue("RemoveFile", css::uno::makeAny(false));
-
-    // serialize it back to xml
-    css::uno::Reference<css::xml::sax::XSAXSerializable> 
xSerializer(xDocument, css::uno::UNO_QUERY);
-    css::uno::Reference<css::xml::sax::XWriter> xWriter = 
css::xml::sax::Writer::create(xContext);
-    css::uno::Reference<css::io::XOutputStream> xTempOut = 
xTempStream->getOutputStream();
-    xWriter->setOutputStream(xTempOut);
-    
xSerializer->serialize(css::uno::Reference<css::xml::sax::XDocumentHandler>(xWriter,
 css::uno::UNO_QUERY_THROW),
-            css::uno::Sequence<css::beans::StringPair>());
-
-    // feed it to GtkBuilder
-    css::uno::Reference<css::io::XSeekable> xTempSeek(xTempStream, 
css::uno::UNO_QUERY_THROW);
-    xTempSeek->seek(0);
-    auto xInput = xTempStream->getInputStream();
-    css::uno::Sequence<sal_Int8> bytes;
-    sal_Int32 nToRead = xInput->available();
-    while (true)
-    {
-        sal_Int32 nRead = xInput->readBytes(bytes, 
std::max<sal_Int32>(nToRead, 4096));
-        if (!nRead)
-            break;
-        // fprintf(stderr, "text is %s\n", reinterpret_cast<const 
gchar*>(bytes.getArray()));
-        auto rc = gtk_builder_add_from_string(pBuilder, reinterpret_cast<const 
gchar*>(bytes.getArray()), nRead, &err);
-        if (!rc)
-        {
-            SAL_WARN( "vcl.gtk", "GtkInstanceBuilder: error when calling 
gtk_builder_add_from_string: " << err->message);
-            g_error_free(err);
-        }
-        assert(rc && "could not load UI file");
-        // in the real world the first loop has read the entire file because 
its all 'available' without blocking
-    }
+    builder_add_from_gtk3_file(pBuilder, rUri);
 #else
     OUString aPath;
     osl::FileBase::getSystemPathFromFileURL(rUri, aPath);
+    GError *err = nullptr;
     auto rc = gtk_builder_add_from_file(pBuilder, OUStringToOString(aPath, 
RTL_TEXTENCODING_UTF8).getStr(), &err);
 
     if (!rc)
diff --git a/vcl/unx/gtk4/convert3to4.cxx b/vcl/unx/gtk4/convert3to4.cxx
new file mode 100644
index 000000000000..06ca97f88abb
--- /dev/null
+++ b/vcl/unx/gtk4/convert3to4.cxx
@@ -0,0 +1,889 @@
+/* -*- 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 <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
+#include <comphelper/processfactory.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <vcl/builder.hxx>
+#include "convert3to4.hxx"
+
+namespace
+{
+typedef std::pair<css::uno::Reference<css::xml::dom::XNode>, OUString> 
named_node;
+
+bool sortButtonNodes(const named_node& rA, const named_node& rB)
+{
+    //order within groups according to platform rules
+    return getButtonPriority("/" + rA.second.toUtf8())
+           < getButtonPriority("/" + rB.second.toUtf8());
+}
+
+// <property name="spacing">6</property>
+css::uno::Reference<css::xml::dom::XNode>
+CreateProperty(const css::uno::Reference<css::xml::dom::XDocument>& xDoc, 
const OUString& rPropName,
+               const OUString& rValue)
+{
+    css::uno::Reference<css::xml::dom::XElement> xProperty = 
xDoc->createElement("property");
+    css::uno::Reference<css::xml::dom::XAttr> xPropName = 
xDoc->createAttribute("name");
+    xPropName->setValue(rPropName);
+    xProperty->setAttributeNode(xPropName);
+    css::uno::Reference<css::xml::dom::XText> xValue = 
xDoc->createTextNode(rValue);
+    xProperty->appendChild(xValue);
+    return xProperty;
+}
+
+bool ToplevelIsMessageDialog(const css::uno::Reference<css::xml::dom::XNode>& 
xNode)
+{
+    for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = 
xNode->getParentNode();
+         xObjectCandidate.is(); xObjectCandidate = 
xObjectCandidate->getParentNode())
+    {
+        if (xObjectCandidate->getNodeName() == "object")
+        {
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+                = xObjectCandidate->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xClass = 
xObjectMap->getNamedItem("class");
+            if (xClass->getNodeValue() == "GtkMessageDialog")
+                return true;
+        }
+    }
+    return false;
+}
+
+void SetPropertyOnTopLevel(const css::uno::Reference<css::xml::dom::XNode>& 
xNode,
+                           const css::uno::Reference<css::xml::dom::XNode>& 
xProperty)
+{
+    for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = 
xNode->getParentNode();
+         xObjectCandidate.is(); xObjectCandidate = 
xObjectCandidate->getParentNode())
+    {
+        if (xObjectCandidate->getNodeName() == "object")
+        {
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+                = xObjectCandidate->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xClass = 
xObjectMap->getNamedItem("class");
+            if (xClass->getNodeValue() == "GtkDialog")
+            {
+                auto xFirstChild = xObjectCandidate->getFirstChild();
+                if (xFirstChild.is())
+                    xObjectCandidate->insertBefore(xProperty, xFirstChild);
+                else
+                    xObjectCandidate->appendChild(xProperty);
+
+                break;
+            }
+        }
+    }
+}
+
+OUString GetParentObjectType(const css::uno::Reference<css::xml::dom::XNode>& 
xNode)
+{
+    auto xParent = xNode->getParentNode();
+    assert(xParent->getNodeName() == "object");
+    css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = 
xParent->getAttributes();
+    css::uno::Reference<css::xml::dom::XNode> xClass = 
xParentMap->getNamedItem("class");
+    return xClass->getNodeValue();
+}
+
+// currently runs the risk of duplicate margin-* properties if there was 
already such as well
+// as the border
+void AddBorderAsMargins(const css::uno::Reference<css::xml::dom::XNode>& xNode,
+                        const OUString& rBorderWidth)
+{
+    auto xDoc = xNode->getOwnerDocument();
+
+    auto xMarginEnd = CreateProperty(xDoc, "margin-end", rBorderWidth);
+
+    auto xFirstChild = xNode->getFirstChild();
+    if (xFirstChild.is())
+        xNode->insertBefore(xMarginEnd, xFirstChild);
+    else
+        xNode->appendChild(xMarginEnd);
+
+    xNode->insertBefore(CreateProperty(xDoc, "margin-top", rBorderWidth), 
xMarginEnd);
+    xNode->insertBefore(CreateProperty(xDoc, "margin-bottom", rBorderWidth), 
xMarginEnd);
+    xNode->insertBefore(CreateProperty(xDoc, "margin-start", rBorderWidth), 
xMarginEnd);
+}
+
+struct ConvertResult
+{
+    bool m_bChildCanFocus;
+    bool m_bHasVisible;
+    bool m_bHasIconName;
+    bool m_bAlwaysShowImage;
+    css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
+
+    ConvertResult(bool bChildCanFocus, bool bHasVisible, bool bHasIconName, 
bool bAlwaysShowImage,
+                  const css::uno::Reference<css::xml::dom::XNode>& 
rPropertyLabel)
+        : m_bChildCanFocus(bChildCanFocus)
+        , m_bHasVisible(bHasVisible)
+        , m_bHasIconName(bHasIconName)
+        , m_bAlwaysShowImage(bAlwaysShowImage)
+        , m_xPropertyLabel(rPropertyLabel)
+    {
+    }
+};
+
+ConvertResult Convert3To4(const css::uno::Reference<css::xml::dom::XNode>& 
xNode)
+{
+    css::uno::Reference<css::xml::dom::XNodeList> xNodeList = 
xNode->getChildNodes();
+    if (!xNodeList.is())
+        return ConvertResult(false, false, false, false, nullptr);
+
+    std::vector<css::uno::Reference<css::xml::dom::XNode>> xRemoveList;
+
+    OUString sBorderWidth;
+    bool bChildCanFocus = false;
+    bool bHasVisible = false;
+    bool bHasIconName = false;
+    bool bAlwaysShowImage = false;
+    css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
+    css::uno::Reference<css::xml::dom::XNode> xCantFocus;
+
+    css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
+    while (xChild.is())
+    {
+        if (xChild->getNodeName() == "requires")
+        {
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xLib = 
xMap->getNamedItem("lib");
+            assert(xLib->getNodeValue() == "gtk+");
+            xLib->setNodeValue("gtk");
+            css::uno::Reference<css::xml::dom::XNode> xVersion = 
xMap->getNamedItem("version");
+            assert(xVersion->getNodeValue() == "3.20");
+            xVersion->setNodeValue("4.0");
+        }
+        else if (xChild->getNodeName() == "property")
+        {
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xName = 
xMap->getNamedItem("name");
+            OUString sName(xName->getNodeValue().replace('_', '-'));
+
+            if (sName == "border-width")
+                sBorderWidth = xChild->getFirstChild()->getNodeValue();
+
+            if (sName == "has-default")
+            {
+                css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+                    = xChild->getParentNode()->getAttributes();
+                css::uno::Reference<css::xml::dom::XNode> xId = 
xParentMap->getNamedItem("id");
+                auto xDoc = xChild->getOwnerDocument();
+                auto xDefaultWidget = CreateProperty(xDoc, "default-widget", 
xId->getNodeValue());
+                SetPropertyOnTopLevel(xChild, xDefaultWidget);
+                xRemoveList.push_back(xChild);
+            }
+
+            if (sName == "has-focus" || sName == "is-focus")
+            {
+                css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+                    = xChild->getParentNode()->getAttributes();
+                css::uno::Reference<css::xml::dom::XNode> xId = 
xParentMap->getNamedItem("id");
+                auto xDoc = xChild->getOwnerDocument();
+                auto xDefaultWidget = CreateProperty(xDoc, "focus-widget", 
xId->getNodeValue());
+                SetPropertyOnTopLevel(xChild, xDefaultWidget);
+                xRemoveList.push_back(xChild);
+            }
+
+            if (sName == "can-focus")
+            {
+                bChildCanFocus = 
toBool(xChild->getFirstChild()->getNodeValue());
+                if (!bChildCanFocus)
+                {
+                    OUString sParentClass = GetParentObjectType(xChild);
+                    if (sParentClass == "GtkBox" || sParentClass == "GtkGrid")
+                    {
+                        // e.g. for the case of notebooks without children 
yet, just remove the can't focus property
+                        // from Boxes and Grids
+                        xRemoveList.push_back(xChild);
+                    }
+                    else if (sParentClass == "GtkComboBoxText")
+                    {
+                        // this was always a bit finicky in gtk3, fix it up to 
default to can-focus
+                        xRemoveList.push_back(xChild);
+                    }
+                    else
+                    {
+                        // otherwise mark the property as needing removal if 
there turns out to be a child
+                        // with can-focus of true, in which case remove this 
parent conflicting property
+                        xCantFocus = xChild;
+                    }
+                }
+            }
+
+            if (sName == "label")
+                xPropertyLabel = xChild;
+
+            if (sName == "visible")
+                bHasVisible = true;
+
+            if (sName == "icon-name")
+                bHasIconName = true;
+
+            if (sName == "events")
+                xRemoveList.push_back(xChild);
+
+            if (sName == "activates-default")
+            {
+                if (GetParentObjectType(xChild) == "GtkSpinButton")
+                    xRemoveList.push_back(xChild);
+            }
+
+            if (sName == "width-chars")
+            {
+                if (GetParentObjectType(xChild) == "GtkEntry")
+                {
+                    // I don't quite get what the difference should be wrt 
width-chars and max-width-chars
+                    // but glade doesn't write max-width-chars and in gtk4 
where we have width-chars, e.g
+                    // print dialog, then max-width-chars gives the effect we 
wanted with width-chars
+                    auto xDoc = xChild->getOwnerDocument();
+                    auto mMaxWidthChars = CreateProperty(xDoc, 
"max-width-chars",
+                                                         
xChild->getFirstChild()->getNodeValue());
+                    xChild->getParentNode()->insertBefore(mMaxWidthChars, 
xChild);
+                }
+            }
+
+            // remove 'Help' button label and replace with a help icon instead
+            if (sName == "label" && GetParentObjectType(xChild) == "GtkButton")
+            {
+                css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+                    = xChild->getParentNode()->getAttributes();
+                css::uno::Reference<css::xml::dom::XNode> xId = 
xParentMap->getNamedItem("id");
+                if (xId && xId->getNodeValue() == "help")
+                {
+                    auto xDoc = xChild->getOwnerDocument();
+                    auto xIconName = CreateProperty(xDoc, "icon-name", 
"help-browser-symbolic");
+                    xChild->getParentNode()->insertBefore(xIconName, xChild);
+                    xRemoveList.push_back(xChild);
+                }
+            }
+
+            if (sName == "icon-size")
+            {
+                if (GetParentObjectType(xChild) == "GtkImage")
+                {
+                    if (xChild->getFirstChild()->getNodeValue() == "6")
+                    {
+                        auto xDoc = xChild->getOwnerDocument();
+                        // convert old GTK_ICON_SIZE_DIALOG to new 
GTK_ICON_SIZE_LARGE
+                        auto xIconSize = CreateProperty(xDoc, "icon-size", 
"2");
+                        xChild->getParentNode()->insertBefore(xIconSize, 
xChild);
+                        xRemoveList.push_back(xChild);
+                    }
+                    else
+                        SAL_WARN("vcl.gtk", "what should we do with an 
icon-size of: "
+                                                << 
xChild->getFirstChild()->getNodeValue());
+                }
+            }
+
+            if (sName == "truncate-multiline")
+            {
+                if (GetParentObjectType(xChild) == "GtkSpinButton")
+                    xRemoveList.push_back(xChild);
+            }
+
+            if (sName == "homogeneous")
+            {
+                // e.g. the buttonbox in xml filter dialog
+                if (GetParentObjectType(xChild) == "GtkButtonBox")
+                    xRemoveList.push_back(xChild);
+            }
+
+            if (sName == "shadow-type")
+            {
+                if (GetParentObjectType(xChild) == "GtkFrame")
+                    xRemoveList.push_back(xChild);
+                else if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+                {
+                    bool bHasFrame = xChild->getFirstChild()->getNodeValue() 
!= "none";
+                    auto xDoc = xChild->getOwnerDocument();
+                    auto xHasFrame = CreateProperty(
+                        xDoc, "has-frame", bHasFrame ? OUString("True") : 
OUString("False"));
+                    xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+                    xRemoveList.push_back(xChild);
+                }
+            }
+
+            if (sName == "always-show-image")
+            {
+                if (GetParentObjectType(xChild) == "GtkButton")
+                {
+                    // we will turn always-show-image into a GtkBox child for
+                    // GtkButton and a GtkLabel child for the GtkBox and move
+                    // the label property into it.
+                    bAlwaysShowImage = 
toBool(xChild->getFirstChild()->getNodeValue());
+                    xRemoveList.push_back(xChild);
+                }
+            }
+
+            if (sName == "relief")
+            {
+                if (GetParentObjectType(xChild) == "GtkLinkButton"
+                    || GetParentObjectType(xChild) == "GtkButton")
+                {
+                    assert(xChild->getFirstChild()->getNodeValue() == "none");
+                    auto xDoc = xChild->getOwnerDocument();
+                    auto xHasFrame = CreateProperty(xDoc, "has-frame", 
"False");
+                    xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+                    xRemoveList.push_back(xChild);
+                }
+            }
+
+            if (sName == "xalign")
+            {
+                if (GetParentObjectType(xChild) == "GtkLinkButton"
+                    || GetParentObjectType(xChild) == "GtkMenuButton"
+                    || GetParentObjectType(xChild) == "GtkButton")
+                {
+                    // TODO expand into a GtkLabel child with alignment on 
that instead
+                    assert(xChild->getFirstChild()->getNodeValue() == "0");
+                    xRemoveList.push_back(xChild);
+                }
+            }
+
+            if (sName == "hscrollbar-policy")
+            {
+                if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+                {
+                    if (xChild->getFirstChild()->getNodeValue() == "never")
+                    {
+                        auto xDoc = xChild->getOwnerDocument();
+                        auto xHasFrame = CreateProperty(xDoc, 
"propagate-natural-width", "True");
+                        xChild->getParentNode()->insertBefore(xHasFrame, 
xChild);
+                    }
+                }
+            }
+
+            if (sName == "vscrollbar-policy")
+            {
+                if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+                {
+                    if (xChild->getFirstChild()->getNodeValue() == "never")
+                    {
+                        auto xDoc = xChild->getOwnerDocument();
+                        auto xHasFrame = CreateProperty(xDoc, 
"propagate-natural-height", "True");
+                        xChild->getParentNode()->insertBefore(xHasFrame, 
xChild);
+                    }
+                }
+            }
+
+            if (sName == "image")
+            {
+                if (GetParentObjectType(xChild) == "GtkButton")
+                {
+                    // find the image object, expected to be a child of 
"interface" and relocate
+                    // it to be a child of this GtkButton
+                    auto xObjectCandidate = xChild->getParentNode();
+                    if (xObjectCandidate->getNodeName() == "object")
+                    {
+                        OUString sImageId = 
xChild->getFirstChild()->getNodeValue();
+
+                        css::uno::Reference<css::xml::dom::XNode> 
xRootCandidate
+                            = xChild->getParentNode();
+                        while (xRootCandidate)
+                        {
+                            if (xRootCandidate->getNodeName() == "interface")
+                                break;
+                            xRootCandidate = xRootCandidate->getParentNode();
+                        }
+
+                        css::uno::Reference<css::xml::dom::XNode> xImageNode;
+
+                        for (auto xImageCandidate = 
xRootCandidate->getFirstChild();
+                             xImageCandidate.is();
+                             xImageCandidate = 
xImageCandidate->getNextSibling())
+                        {
+                            css::uno::Reference<css::xml::dom::XNamedNodeMap> 
xImageCandidateMap
+                                = xImageCandidate->getAttributes();
+                            if (!xImageCandidateMap.is())
+                                continue;
+                            css::uno::Reference<css::xml::dom::XNode> xId
+                                = xImageCandidateMap->getNamedItem("id");
+                            if (xId && xId->getNodeValue() == sImageId)
+                            {
+                                xImageNode = xImageCandidate;
+                                break;
+                            }
+                        }
+
+                        auto xDoc = xChild->getOwnerDocument();
+                        css::uno::Reference<css::xml::dom::XElement> 
xImageChild
+                            = xDoc->createElement("child");
+                        xImageChild->appendChild(
+                            
xImageNode->getParentNode()->removeChild(xImageNode));
+                        xObjectCandidate->appendChild(xImageChild);
+                    }
+
+                    xRemoveList.push_back(xChild);
+                }
+            }
+
+            if (sName == "draw-indicator")
+            {
+                assert(toBool(xChild->getFirstChild()->getNodeValue()));
+                xRemoveList.push_back(xChild);
+            }
+
+            if (sName == "type-hint" || sName == "skip-taskbar-hint" || sName 
== "can-default"
+                || sName == "border-width" || sName == "layout-style" || sName 
== "no-show-all"
+                || sName == "ignore-hidden" || sName == "window-position")
+            {
+                xRemoveList.push_back(xChild);
+            }
+        }
+        else if (xChild->getNodeName() == "child")
+        {
+            bool bContentArea = false;
+
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xName = 
xMap->getNamedItem("internal-child");
+            if (xName)
+            {
+                OUString sName(xName->getNodeValue());
+                if (sName == "vbox")
+                {
+                    xName->setNodeValue("content_area");
+                    bContentArea = true;
+                }
+                else if (sName == "accessible")
+                    xRemoveList.push_back(
+                        xChild); // Yikes!, what's the replacement for this 
going to be
+            }
+
+            if (bContentArea)
+            {
+                for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate
+                     = xChild->getFirstChild();
+                     xObjectCandidate.is(); xObjectCandidate = 
xObjectCandidate->getNextSibling())
+                {
+                    if (xObjectCandidate->getNodeName() == "object")
+                    {
+                        auto xDoc = xChild->getOwnerDocument();
+
+                        auto xVExpand = CreateProperty(xDoc, "vexpand", 
"True");
+                        auto xFirstChild = xObjectCandidate->getFirstChild();
+                        if (xFirstChild.is())
+                            xObjectCandidate->insertBefore(xVExpand, 
xFirstChild);
+                        else
+                            xObjectCandidate->appendChild(xVExpand);
+
+                        if (!sBorderWidth.isEmpty())
+                        {
+                            AddBorderAsMargins(xObjectCandidate, sBorderWidth);
+                            sBorderWidth.clear();
+                        }
+
+                        break;
+                    }
+                }
+            }
+        }
+        else if (xChild->getNodeName() == "packing")
+        {
+            // remove "packing" and if its grid packing insert a replacement 
"layout" into
+            // the associated "object"
+            auto xDoc = xChild->getOwnerDocument();
+            css::uno::Reference<css::xml::dom::XElement> xNew = 
xDoc->createElement("layout");
+
+            bool bGridPacking = false;
+
+            // iterate over all children and append them to the new element
+            for (css::uno::Reference<css::xml::dom::XNode> xCurrent = 
xChild->getFirstChild();
+                 xCurrent.is(); xCurrent = xChild->getFirstChild())
+            {
+                css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xCurrent->getAttributes();
+                if (xMap.is())
+                {
+                    css::uno::Reference<css::xml::dom::XNode> xName = 
xMap->getNamedItem("name");
+                    OUString sName(xName->getNodeValue().replace('_', '-'));
+                    if (sName == "left-attach")
+                    {
+                        xName->setNodeValue("column");
+                        bGridPacking = true;
+                    }
+                    else if (sName == "top-attach")
+                    {
+                        xName->setNodeValue("row");
+                        bGridPacking = true;
+                    }
+                    else if (sName == "width")
+                    {
+                        xName->setNodeValue("column-span");
+                        bGridPacking = true;
+                    }
+                    else if (sName == "height")
+                    {
+                        xName->setNodeValue("row-span");
+                        bGridPacking = true;
+                    }
+                    else if (sName == "secondary")
+                    {
+                        // turn parent tag of <child> into <child type="start">
+                        auto xParent = xChild->getParentNode();
+                        css::uno::Reference<css::xml::dom::XAttr> xTypeStart
+                            = xDoc->createAttribute("type");
+                        xTypeStart->setValue("start");
+                        css::uno::Reference<css::xml::dom::XElement> xElem(
+                            xParent, css::uno::UNO_QUERY_THROW);
+                        xElem->setAttributeNode(xTypeStart);
+                    }
+                }
+                xNew->appendChild(xChild->removeChild(xCurrent));
+            }
+
+            if (bGridPacking)
+            {
+                // go back to parent and find the object child and insert this 
"layout" as a
+                // new child of the object
+                auto xParent = xChild->getParentNode();
+                for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate
+                     = xParent->getFirstChild();
+                     xObjectCandidate.is(); xObjectCandidate = 
xObjectCandidate->getNextSibling())
+                {
+                    if (xObjectCandidate->getNodeName() == "object")
+                    {
+                        xObjectCandidate->appendChild(xNew);
+                        break;
+                    }
+                }
+            }
+
+            xRemoveList.push_back(xChild);
+        }
+        else if (xChild->getNodeName() == "accessibility")
+        {
+            // TODO <relation type="labelled-by" target="pagenumcb"/> -> 
<relation name="labelled-by">pagenumcb</relation>
+            xRemoveList.push_back(xChild);
+        }
+        else if (xChild->getNodeName() == "accelerator")
+        {
+            // TODO is anything like this supported anymore in .ui files
+            xRemoveList.push_back(xChild);
+        }
+
+        auto xNextChild = xChild->getNextSibling();
+
+        bool bChildHasIconName = false;
+        bool bChildHasVisible = false;
+        bool bChildAlwaysShowImage = false;
+        css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
+        if (xChild->hasChildNodes())
+        {
+            auto aChildRes = Convert3To4(xChild);
+            bChildCanFocus |= aChildRes.m_bChildCanFocus;
+            if (bChildCanFocus && xCantFocus.is())
+            {
+                xNode->removeChild(xCantFocus);
+                xCantFocus.clear();
+            }
+            if (xChild->getNodeName() == "object")
+            {
+                bChildHasVisible = aChildRes.m_bHasVisible;
+                bChildHasIconName = aChildRes.m_bHasIconName;
+                bChildAlwaysShowImage = aChildRes.m_bAlwaysShowImage;
+                xChildPropertyLabel = aChildRes.m_xPropertyLabel;
+            }
+        }
+
+        if (xChild->getNodeName() == "object")
+        {
+            auto xDoc = xChild->getOwnerDocument();
+
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = 
xChild->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xClass = 
xMap->getNamedItem("class");
+            OUString sClass(xClass->getNodeValue());
+
+            auto xInternalChildCandidate = xChild->getParentNode();
+            css::uno::Reference<css::xml::dom::XNamedNodeMap> 
xInternalChildCandidateMap
+                = xInternalChildCandidate->getAttributes();
+            css::uno::Reference<css::xml::dom::XNode> xId
+                = xInternalChildCandidateMap->getNamedItem("internal-child");
+
+            // turn default gtk3 invisibility for widget objects into explicit 
invisible, but ignore internal-children
+            if (!bChildHasVisible && !xId)
+            {
+                if (sClass == "GtkBox" || sClass == "GtkButton" || sClass == 
"GtkCalendar"
+                    || sClass == "GtkCheckButton" || sClass == "GtkRadioButton"
+                    || sClass == "GtkComboBox" || sClass == "GtkComboBoxText"
+                    || sClass == "GtkDrawingArea" || sClass == "GtkEntry" || 
sClass == "GtkExpander"
+                    || sClass == "GtkFrame" || sClass == "GtkGrid" || sClass 
== "GtkImage"
+                    || sClass == "GtkLabel" || sClass == "GtkMenuButton" || 
sClass == "GtkNotebook"
+                    || sClass == "GtkOverlay" || sClass == "GtkPaned" || 
sClass == "GtkProgressBar"
+                    || sClass == "GtkScrolledWindow" || sClass == 
"GtkSeparator"
+                    || sClass == "GtkSpinButton" || sClass == "GtkSpinner"
+                    || sClass == "GtkTextView" || sClass == "GtkTreeView" || 
sClass == "GtkViewport"
+                    || sClass == "GtkLinkButton" || sClass == "GtkToggleButton"
+                    || sClass == "GtkButtonBox")
+
+                {
+                    auto xVisible = CreateProperty(xDoc, "visible", "False");
+                    auto xFirstChild = xChild->getFirstChild();
+                    if (xFirstChild.is())
+                        xChild->insertBefore(xVisible, xFirstChild);
+                    else
+                        xChild->appendChild(xVisible);
+                }
+            }
+
+            if (sClass == "GtkButtonBox")
+            {
+                if (xId && xId->getNodeValue() == "action_area" && 
!ToplevelIsMessageDialog(xChild))
+                {
+                    xClass->setNodeValue("GtkHeaderBar");
+                    auto xSpacingNode = CreateProperty(xDoc, 
"show-title-buttons", "False");
+                    auto xFirstChild = xChild->getFirstChild();
+                    if (xFirstChild.is())
+                        xChild->insertBefore(xSpacingNode, xFirstChild);
+                    else
+                        xChild->appendChild(xSpacingNode);
+
+                    // move the replacement GtkHeaderBar up to before the 
content_area
+                    auto xContentAreaCandidate = xChild->getParentNode();
+                    while (xContentAreaCandidate)
+                    {
+                        css::uno::Reference<css::xml::dom::XNamedNodeMap> 
xChildMap
+                            = xContentAreaCandidate->getAttributes();
+                        css::uno::Reference<css::xml::dom::XNode> xName
+                            = xChildMap->getNamedItem("internal-child");
+                        if (xName && xName->getNodeValue() == "content_area")
+                        {
+                            auto xActionArea = xChild->getParentNode();
+
+                            
xActionArea->getParentNode()->removeChild(xActionArea);
+
+                            css::uno::Reference<css::xml::dom::XAttr> 
xTypeTitleBar
+                                = xDoc->createAttribute("type");
+                            xTypeTitleBar->setValue("titlebar");
+                            css::uno::Reference<css::xml::dom::XElement> xElem(
+                                xActionArea, css::uno::UNO_QUERY_THROW);
+                            xElem->setAttributeNode(xTypeTitleBar);
+                            xElem->removeAttribute("internal-child");
+
+                            
xContentAreaCandidate->getParentNode()->insertBefore(
+                                xActionArea, xContentAreaCandidate);
+
+                            std::vector<named_node> aChildren;
+
+                            css::uno::Reference<css::xml::dom::XNode> 
xTitleChild
+                                = xChild->getFirstChild();
+                            while (xTitleChild.is())
+                            {
+                                auto xNextTitleChild = 
xTitleChild->getNextSibling();
+                                if (xTitleChild->getNodeName() == "child")
+                                {
+                                    OUString sNodeId;
+
+                                    for 
(css::uno::Reference<css::xml::dom::XNode> xObjectCandidate
+                                         = xTitleChild->getFirstChild();
+                                         xObjectCandidate.is();
+                                         xObjectCandidate = 
xObjectCandidate->getNextSibling())
+                                    {
+                                        if (xObjectCandidate->getNodeName() == 
"object")
+                                        {
+                                            
css::uno::Reference<css::xml::dom::XNamedNodeMap>
+                                                xObjectMap = 
xObjectCandidate->getAttributes();
+                                            
css::uno::Reference<css::xml::dom::XNode> xObjectId
+                                                = 
xObjectMap->getNamedItem("id");
+                                            sNodeId = 
xObjectId->getNodeValue();
+                                            break;
+                                        }
+                                    }
+
+                                    
aChildren.push_back(std::make_pair(xTitleChild, sNodeId));
+                                }
+                                else if (xTitleChild->getNodeName() == 
"property")
+                                {
+                                    // remove any <property 
name="homogeneous"> tag
+                                    
css::uno::Reference<css::xml::dom::XNamedNodeMap> xTitleChildMap
+                                        = xTitleChild->getAttributes();
+                                    css::uno::Reference<css::xml::dom::XNode> 
xPropName
+                                        = xTitleChildMap->getNamedItem("name");
+                                    OUString 
sPropName(xPropName->getNodeValue().replace('_', '-'));
+                                    if (sPropName == "homogeneous")
+                                        xChild->removeChild(xTitleChild);
+                                }
+
+                                xTitleChild = xNextTitleChild;
+                            }
+
+                            //sort child order within parent so that we match 
the platform button order
+                            std::stable_sort(aChildren.begin(), 
aChildren.end(), sortButtonNodes);
+
+                            int nNonHelpButtonCount = 0;
+
+                            for (const auto& rTitleChild : aChildren)
+                            {
+                                xChild->removeChild(rTitleChild.first);
+                                if (rTitleChild.second != "help")
+                                    ++nNonHelpButtonCount;
+                            }
+
+                            std::reverse(aChildren.begin(), aChildren.end());
+
+                            for (const auto& rTitleChild : aChildren)
+                            {
+                                xChild->appendChild(rTitleChild.first);
+
+                                css::uno::Reference<css::xml::dom::XElement> 
xChildElem(
+                                    rTitleChild.first, 
css::uno::UNO_QUERY_THROW);
+                                if (!xChildElem->hasAttribute("type"))
+                                {
+                                    // turn parent tag of <child> into <child 
type="end"> except for cancel/close which we'll
+                                    // put at start unless there is nothing at 
end
+                                    css::uno::Reference<css::xml::dom::XAttr> 
xTypeEnd
+                                        = xDoc->createAttribute("type");
+                                    if (nNonHelpButtonCount >= 2
+                                        && (rTitleChild.second == "cancel"
+                                            || rTitleChild.second == "close"))
+                                        xTypeEnd->setValue("start");
+                                    else
+                                        xTypeEnd->setValue("end");
+                                    xChildElem->setAttributeNode(xTypeEnd);
+                                }
+                            }
+
+                            auto xUseHeaderBar = CreateProperty(xDoc, 
"use-header-bar", "1");
+                            SetPropertyOnTopLevel(xContentAreaCandidate, 
xUseHeaderBar);
+
+                            break;
+                        }
+                        xContentAreaCandidate = 
xContentAreaCandidate->getParentNode();
+                    }
+                }
+                else // GtkMessageDialog
+                    xClass->setNodeValue("GtkBox");
+            }
+            else if (sClass == "GtkRadioButton")
+            {
+                xClass->setNodeValue("GtkCheckButton");
+            }
+            else if (sClass == "GtkImage" && !bChildHasIconName)
+            {
+                xClass->setNodeValue("GtkPicture");
+            }
+            else if (sClass == "GtkPopover" && !bHasVisible)
+            {
+                auto xVisible = CreateProperty(xDoc, "visible", "False");
+                auto xFirstChild = xChild->getFirstChild();
+                if (xFirstChild.is())
+                    xChild->insertBefore(xVisible, xFirstChild);
+                else
+                    xChild->appendChild(xVisible);
+            }
+
+            if (bChildAlwaysShowImage)
+            {
+                auto xImageCandidateNode = xChild->getLastChild();
+                if (xImageCandidateNode && xImageCandidateNode->getNodeName() 
!= "child")
+                    xImageCandidateNode.clear();
+                if (xImageCandidateNode)
+                    xChild->removeChild(xImageCandidateNode);
+
+                css::uno::Reference<css::xml::dom::XElement> xNewChildNode
+                    = xDoc->createElement("child");
+                css::uno::Reference<css::xml::dom::XElement> xNewObjectNode
+                    = xDoc->createElement("object");
+                css::uno::Reference<css::xml::dom::XAttr> xBoxClassName
+                    = xDoc->createAttribute("class");
+                xBoxClassName->setValue("GtkBox");
+                xNewObjectNode->setAttributeNode(xBoxClassName);
+                xNewChildNode->appendChild(xNewObjectNode);
+
+                xChild->appendChild(xNewChildNode);
+
+                css::uno::Reference<css::xml::dom::XElement> xNewLabelChildNode
+                    = xDoc->createElement("child");
+                css::uno::Reference<css::xml::dom::XElement> 
xNewChildObjectNode
+                    = xDoc->createElement("object");
+                css::uno::Reference<css::xml::dom::XAttr> xLabelClassName
+                    = xDoc->createAttribute("class");
+                xLabelClassName->setValue("GtkLabel");
+                xNewChildObjectNode->setAttributeNode(xLabelClassName);
+                if (xChildPropertyLabel)
+                    xNewChildObjectNode->appendChild(
+                        
xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel));
+                xNewLabelChildNode->appendChild(xNewChildObjectNode);
+
+                if (xImageCandidateNode)
+                    xNewObjectNode->appendChild(xImageCandidateNode);
+                xNewObjectNode->appendChild(xNewLabelChildNode);
+            }
+        }
+
+        xChild = xNextChild;
+    }
+
+    if (!sBorderWidth.isEmpty())
+        AddBorderAsMargins(xNode, sBorderWidth);
+
+    for (auto& xRemove : xRemoveList)
+        xNode->removeChild(xRemove);
+
+    return ConvertResult(bChildCanFocus, bHasVisible, bHasIconName, 
bAlwaysShowImage,
+                         xPropertyLabel);
+}
+}
+
+void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri)
+{
+    GError* err = nullptr;
+
+    // load the xml
+    css::uno::Reference<css::uno::XComponentContext> xContext
+        = ::comphelper::getProcessComponentContext();
+    css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder
+        = css::xml::dom::DocumentBuilder::create(xContext);
+    css::uno::Reference<css::xml::dom::XDocument> xDocument = 
xBuilder->parseURI(rUri);
+
+    // convert it from gtk3 to gtk4
+    Convert3To4(xDocument);
+
+    css::uno::Reference<css::beans::XPropertySet> 
xTempFile(css::io::TempFile::create(xContext),
+                                                            
css::uno::UNO_QUERY);
+    css::uno::Reference<css::io::XStream> xTempStream(xTempFile, 
css::uno::UNO_QUERY_THROW);
+    xTempFile->setPropertyValue("RemoveFile", css::uno::makeAny(false));
+
+    // serialize it back to xml
+    css::uno::Reference<css::xml::sax::XSAXSerializable> xSerializer(xDocument,
+                                                                     
css::uno::UNO_QUERY);
+    css::uno::Reference<css::xml::sax::XWriter> xWriter = 
css::xml::sax::Writer::create(xContext);
+    css::uno::Reference<css::io::XOutputStream> xTempOut = 
xTempStream->getOutputStream();
+    xWriter->setOutputStream(xTempOut);
+    xSerializer->serialize(
+        css::uno::Reference<css::xml::sax::XDocumentHandler>(xWriter, 
css::uno::UNO_QUERY_THROW),
+        css::uno::Sequence<css::beans::StringPair>());
+
+    // feed it to GtkBuilder
+    css::uno::Reference<css::io::XSeekable> xTempSeek(xTempStream, 
css::uno::UNO_QUERY_THROW);
+    xTempSeek->seek(0);
+    auto xInput = xTempStream->getInputStream();
+    css::uno::Sequence<sal_Int8> bytes;
+    sal_Int32 nToRead = xInput->available();
+    while (true)
+    {
+        sal_Int32 nRead = xInput->readBytes(bytes, 
std::max<sal_Int32>(nToRead, 4096));
+        if (!nRead)
+            break;
+        // fprintf(stderr, "text is %s\n", reinterpret_cast<const 
gchar*>(bytes.getArray()));
+        auto rc = gtk_builder_add_from_string(
+            pBuilder, reinterpret_cast<const gchar*>(bytes.getArray()), nRead, 
&err);
+        if (!rc)
+        {
+            SAL_WARN("vcl.gtk",
+                     "GtkInstanceBuilder: error when calling 
gtk_builder_add_from_string: "
+                         << err->message);
+            g_error_free(err);
+        }
+        assert(rc && "could not load UI file");
+        // in the real world the first loop has read the entire file because 
its all 'available' without blocking
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkinst.cxx b/vcl/unx/gtk4/gtkinst.cxx
index a1066a6d3548..e166777412b0 100644
--- a/vcl/unx/gtk4/gtkinst.cxx
+++ b/vcl/unx/gtk4/gtkinst.cxx
@@ -10,6 +10,8 @@
 // make gtk4 plug advertise correctly as gtk4
 #define GTK_TOOLKIT_NAME "gtk4"
 
+#include "convert3to4.hxx"
+
 #include "../gtk3/gtkinst.cxx"
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to