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