diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/unx/gtk4 | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
31 files changed, 2563 insertions, 0 deletions
diff --git a/vcl/unx/gtk4/convert3to4.cxx b/vcl/unx/gtk4/convert3to4.cxx new file mode 100644 index 000000000..e00009cd1 --- /dev/null +++ b/vcl/unx/gtk4/convert3to4.cxx @@ -0,0 +1,1576 @@ +/* -*- 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 <cairo/cairo-gobject.h> +#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) +{ + OString sA(rA.second.toUtf8()); + OString sB(rB.second.toUtf8()); + //order within groups according to platform rules + return getButtonPriority(sA) < getButtonPriority(sB); +} + +// <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 insertAsFirstChild(const css::uno::Reference<css::xml::dom::XNode>& xParentNode, + const css::uno::Reference<css::xml::dom::XNode>& xChildNode) +{ + auto xFirstChild = xParentNode->getFirstChild(); + if (xFirstChild.is()) + xParentNode->insertBefore(xChildNode, xFirstChild); + else + xParentNode->appendChild(xChildNode); +} + +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") + { + insertAsFirstChild(xObjectCandidate, 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(); +} + +css::uno::Reference<css::xml::dom::XNode> +GetChildObject(const css::uno::Reference<css::xml::dom::XNode>& xChild) +{ + for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xChild->getFirstChild(); + xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getNextSibling()) + { + if (xObjectCandidate->getNodeName() == "object") + return xObjectCandidate; + } + return nullptr; +} + +// 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); + insertAsFirstChild(xNode, 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 MenuEntry +{ + bool m_bDrawAsRadio; + css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel; + + MenuEntry(bool bDrawAsRadio, const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel) + : m_bDrawAsRadio(bDrawAsRadio) + , m_xPropertyLabel(rPropertyLabel) + { + } +}; + +MenuEntry ConvertMenu(css::uno::Reference<css::xml::dom::XNode>& xMenuSection, + const css::uno::Reference<css::xml::dom::XNode>& xNode) +{ + bool bDrawAsRadio = false; + css::uno::Reference<css::xml::dom::XNode> xPropertyLabel; + + css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild(); + while (xChild.is()) + { + 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 == "label") + { + xPropertyLabel = xChild; + } + else if (sName == "draw-as-radio") + { + bDrawAsRadio = toBool(xChild->getFirstChild()->getNodeValue()); + } + } + + auto xNextChild = xChild->getNextSibling(); + + auto xCurrentMenuSection = xMenuSection; + + 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()); + + if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem") + { + /* <item> */ + css::uno::Reference<css::xml::dom::XElement> xItem = xDoc->createElement("item"); + xMenuSection->appendChild(xItem); + } + else if (sClass == "GtkSeparatorMenuItem") + { + /* <section> */ + css::uno::Reference<css::xml::dom::XElement> xSection + = xDoc->createElement("section"); + xMenuSection->getParentNode()->appendChild(xSection); + xMenuSection = xSection; + xCurrentMenuSection = xMenuSection; + } + else if (sClass == "GtkMenu") + { + xMenuSection->removeChild(xMenuSection->getLastChild()); // remove preceding <item> + + css::uno::Reference<css::xml::dom::XElement> xSubMenu + = xDoc->createElement("submenu"); + css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id"); + + css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id"); + OUString sId(xId->getNodeValue()); + + xIdAttr->setValue(sId); + xSubMenu->setAttributeNode(xIdAttr); + xMenuSection->appendChild(xSubMenu); + + css::uno::Reference<css::xml::dom::XElement> xSection + = xDoc->createElement("section"); + xSubMenu->appendChild(xSection); + + xMenuSection = xSubMenu; + } + } + + bool bChildDrawAsRadio = false; + css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel; + if (xChild->hasChildNodes()) + { + MenuEntry aEntry = ConvertMenu(xMenuSection, xChild); + bChildDrawAsRadio = aEntry.m_bDrawAsRadio; + xChildPropertyLabel = aEntry.m_xPropertyLabel; + } + + if (xChild->getNodeName() == "object") + { + xMenuSection = xCurrentMenuSection; + + 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()); + + if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem") + { + css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id"); + OUString sId = xId->getNodeValue(); + + /* + <attribute name='label' translatable='yes'>whatever</attribute> + <attribute name='action'>menu.action</attribute> + <attribute name='target'>id</attribute> + */ + auto xItem = xMenuSection->getLastChild(); + + if (xChildPropertyLabel) + { + css::uno::Reference<css::xml::dom::XElement> xChildPropertyElem( + xChildPropertyLabel, css::uno::UNO_QUERY_THROW); + + css::uno::Reference<css::xml::dom::XElement> xLabelAttr + = xDoc->createElement("attribute"); + + css::uno::Reference<css::xml::dom::XNamedNodeMap> xLabelMap + = xChildPropertyLabel->getAttributes(); + while (xLabelMap->getLength()) + { + css::uno::Reference<css::xml::dom::XAttr> xAttr(xLabelMap->item(0), + css::uno::UNO_QUERY_THROW); + xLabelAttr->setAttributeNode( + xChildPropertyElem->removeAttributeNode(xAttr)); + } + xLabelAttr->appendChild( + xChildPropertyLabel->removeChild(xChildPropertyLabel->getFirstChild())); + + xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel); + xItem->appendChild(xLabelAttr); + } + + css::uno::Reference<css::xml::dom::XElement> xActionAttr + = xDoc->createElement("attribute"); + css::uno::Reference<css::xml::dom::XAttr> xActionName + = xDoc->createAttribute("name"); + xActionName->setValue("action"); + xActionAttr->setAttributeNode(xActionName); + if (bChildDrawAsRadio) + xActionAttr->appendChild(xDoc->createTextNode("menu.radio." + sId)); + else + xActionAttr->appendChild(xDoc->createTextNode("menu.normal." + sId)); + xItem->appendChild(xActionAttr); + + css::uno::Reference<css::xml::dom::XElement> xTargetAttr + = xDoc->createElement("attribute"); + css::uno::Reference<css::xml::dom::XAttr> xTargetName + = xDoc->createAttribute("name"); + xTargetName->setValue("target"); + xTargetAttr->setAttributeNode(xTargetName); + xTargetAttr->appendChild(xDoc->createTextNode(sId)); + xItem->appendChild(xTargetAttr); + + css::uno::Reference<css::xml::dom::XElement> xHiddenWhenAttr + = xDoc->createElement("attribute"); + css::uno::Reference<css::xml::dom::XAttr> xHiddenWhenName + = xDoc->createAttribute("name"); + xHiddenWhenName->setValue("hidden-when"); + xHiddenWhenAttr->setAttributeNode(xHiddenWhenName); + xHiddenWhenAttr->appendChild(xDoc->createTextNode("action-missing")); + xItem->appendChild(xHiddenWhenAttr); + } + } + + xChild = xNextChild; + } + + return MenuEntry(bDrawAsRadio, xPropertyLabel); +} + +struct ConvertResult +{ + bool m_bChildCanFocus; + bool m_bHasVisible; + bool m_bHasIconSize; + bool m_bAlwaysShowImage; + bool m_bUseUnderline; + bool m_bVertOrientation; + bool m_bXAlign; + GtkPositionType m_eImagePos; + css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel; + css::uno::Reference<css::xml::dom::XNode> m_xPropertyIconName; + + ConvertResult(bool bChildCanFocus, bool bHasVisible, bool bHasIconSize, bool bAlwaysShowImage, + bool bUseUnderline, bool bVertOrientation, bool bXAlign, + GtkPositionType eImagePos, + const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel, + const css::uno::Reference<css::xml::dom::XNode>& rPropertyIconName) + : m_bChildCanFocus(bChildCanFocus) + , m_bHasVisible(bHasVisible) + , m_bHasIconSize(bHasIconSize) + , m_bAlwaysShowImage(bAlwaysShowImage) + , m_bUseUnderline(bUseUnderline) + , m_bVertOrientation(bVertOrientation) + , m_bXAlign(bXAlign) + , m_eImagePos(eImagePos) + , m_xPropertyLabel(rPropertyLabel) + , m_xPropertyIconName(rPropertyIconName) + { + } +}; + +bool IsAllowedBuiltInIcon(std::u16string_view iconName) +{ + // limit the named icons to those known by VclBuilder + return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW; +} + +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, false, false, false, GTK_POS_LEFT, nullptr, + nullptr); + } + + std::vector<css::uno::Reference<css::xml::dom::XNode>> xRemoveList; + + OUString sBorderWidth; + bool bChildCanFocus = false; + bool bHasVisible = false; + bool bHasIconSize = false; + bool bAlwaysShowImage = false; + GtkPositionType eImagePos = GTK_POS_LEFT; + bool bUseUnderline = false; + bool bVertOrientation = false; + bool bXAlign = false; + css::uno::Reference<css::xml::dom::XNode> xPropertyLabel; + css::uno::Reference<css::xml::dom::XNode> xPropertyIconName; + css::uno::Reference<css::xml::dom::XNode> xCantFocus; + + css::uno::Reference<css::xml::dom::XElement> xGeneratedImageChild; + + 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" + || sParentClass == "GtkViewport") + { + // 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") + { + OUString sParentClass = GetParentObjectType(xChild); + if (sParentClass == "GtkToolButton" || sParentClass == "GtkMenuToolButton" + || sParentClass == "GtkToggleToolButton") + { + xName->setNodeValue("tooltip-text"); + } + xPropertyLabel = xChild; + } + + if (sName == "modal") + { + OUString sParentClass = GetParentObjectType(xChild); + if (sParentClass == "GtkPopover") + xName->setNodeValue("autohide"); + } + + if (sName == "visible") + bHasVisible = true; + + if (sName == "icon-name") + xPropertyIconName = xChild; + + if (sName == "show-arrow") + xRemoveList.push_back(xChild); + + if (sName == "events") + xRemoveList.push_back(xChild); + + if (sName == "constrain-to") + 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. Unless the toplevel is a message dialog + if (sName == "label" && GetParentObjectType(xChild) == "GtkButton" + && !ToplevelIsMessageDialog(xChild)) + { + 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") + { + bHasIconSize = true; + + OUString sSize = xChild->getFirstChild()->getNodeValue(); + /* + old: + 3 -> GTK_ICON_SIZE_LARGE_TOOLBAR: Size appropriate for large toolbars (24px) + 5 -> GTK_ICON_SIZE_DND: Size appropriate for drag and drop (32px) + 6 -> GTK_ICON_SIZE_DIALOG: Size appropriate for dialogs (48px) + + new: + 2 -> GTK_ICON_SIZE_LARGE + */ + if (sSize == "3" || sSize == "5" || sSize == "6") + { + auto xDoc = xChild->getOwnerDocument(); + auto xIconSize = CreateProperty(xDoc, "icon-size", "2"); + xChild->getParentNode()->insertBefore(xIconSize, xChild); + } + + xRemoveList.push_back(xChild); + } + + if (GetParentObjectType(xChild) == "GtkToolbar") + xRemoveList.push_back(xChild); + } + + if (sName == "truncate-multiline") + { + if (GetParentObjectType(xChild) == "GtkSpinButton") + xRemoveList.push_back(xChild); + } + + if (sName == "has-frame") + { + if (GetParentObjectType(xChild) == "GtkSpinButton") + xRemoveList.push_back(xChild); + } + + if (sName == "toolbar-style") + { + // is there an equivalent for this ? + 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" + || GetParentObjectType(xChild) == "GtkMenuButton" + || GetParentObjectType(xChild) == "GtkToggleButton") + { + // 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 == "image-position") + { + 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. + OUString sImagePos = xChild->getFirstChild()->getNodeValue(); + if (sImagePos == "top") + eImagePos = GTK_POS_TOP; + else if (sImagePos == "bottom") + eImagePos = GTK_POS_BOTTOM; + else if (sImagePos == "right") + eImagePos = GTK_POS_RIGHT; + else + assert(sImagePos == "left"); + xRemoveList.push_back(xChild); + } + } + + if (sName == "use-underline") + bUseUnderline = toBool(xChild->getFirstChild()->getNodeValue()); + + if (sName == "orientation") + bVertOrientation = xChild->getFirstChild()->getNodeValue() == "vertical"; + + if (sName == "relief") + { + if (GetParentObjectType(xChild) == "GtkToggleButton" + || GetParentObjectType(xChild) == "GtkMenuButton" + || 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"); + bXAlign = true; + xRemoveList.push_back(xChild); + } + } + + if (sName == "use-popover") + { + if (GetParentObjectType(xChild) == "GtkMenuButton") + 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 == "popup") + { + if (GetParentObjectType(xChild) == "GtkMenuButton") + { + OUString sMenuName = xChild->getFirstChild()->getNodeValue(); + auto xDoc = xChild->getOwnerDocument(); + auto xPopover = CreateProperty(xDoc, "popover", sMenuName); + xChild->getParentNode()->insertBefore(xPopover, xChild); + xRemoveList.push_back(xChild); + } + } + + if (sName == "image") + { + if (GetParentObjectType(xChild) == "GtkButton" + || GetParentObjectType(xChild) == "GtkMenuButton" + || GetParentObjectType(xChild) == "GtkToggleButton") + { + // find the image object, expected to be a child of "interface" + 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(); + + // relocate it to be a child of this GtkButton + xGeneratedImageChild = xDoc->createElement("child"); + xGeneratedImageChild->appendChild( + xImageNode->getParentNode()->removeChild(xImageNode)); + xObjectCandidate->appendChild(xGeneratedImageChild); + } + + xRemoveList.push_back(xChild); + } + } + + if (sName == "draw-indicator") + { + assert(toBool(xChild->getFirstChild()->getNodeValue())); + if (GetParentObjectType(xChild) == "GtkMenuButton" && gtk_get_minor_version() >= 4) + { + auto xDoc = xChild->getOwnerDocument(); + auto xAlwaysShowArrow = CreateProperty(xDoc, "always-show-arrow", "True"); + xChild->getParentNode()->insertBefore(xAlwaysShowArrow, xChild); + } + 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); + } + + if (sName == "AtkObject::accessible-description") + xName->setNodeValue("description"); + + if (sName == "AtkObject::accessible-name") + xName->setNodeValue("label"); + } + 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); + } + } + + if (bContentArea) + { + css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xChild); + if (xObject) + { + auto xDoc = xChild->getOwnerDocument(); + + auto xVExpand = CreateProperty(xDoc, "vexpand", "True"); + insertAsFirstChild(xObject, xVExpand); + + if (!sBorderWidth.isEmpty()) + { + AddBorderAsMargins(xObject, sBorderWidth); + sBorderWidth.clear(); + } + } + } + } + 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); + } + else if (sName == "pack-type") + { + // turn parent tag of <child> into <child type="start"> + auto xParent = xChild->getParentNode(); + + css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap + = xParent->getAttributes(); + css::uno::Reference<css::xml::dom::XNode> xParentType + = xParentMap->getNamedItem("type"); + assert(!xParentType || xParentType->getNodeValue() == "titlebar"); + if (!xParentType) + { + css::uno::Reference<css::xml::dom::XAttr> xTypeStart + = xDoc->createAttribute("type"); + xTypeStart->setValue(xCurrent->getFirstChild()->getNodeValue()); + 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(); + css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xParent); + if (xObject) + xObject->appendChild(xNew); + } + + xRemoveList.push_back(xChild); + } + else if (xChild->getNodeName() == "accelerator") + { + // TODO is anything like this supported anymore in .ui files + xRemoveList.push_back(xChild); + } + else if (xChild->getNodeName() == "relation") + { + auto xDoc = xChild->getOwnerDocument(); + + css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes(); + css::uno::Reference<css::xml::dom::XNode> xType = xMap->getNamedItem("type"); + if (xType->getNodeValue() == "label-for") + { + // there will be a matching labelled-by which should be sufficient in the gtk4 world + xRemoveList.push_back(xChild); + } + if (xType->getNodeValue() == "controlled-by") + { + // there will be a matching controller-for converted to -> controls + // which should be sufficient in the gtk4 world + xRemoveList.push_back(xChild); + } + else + { + css::uno::Reference<css::xml::dom::XNode> xTarget = xMap->getNamedItem("target"); + + css::uno::Reference<css::xml::dom::XText> xValue + = xDoc->createTextNode(xTarget->getNodeValue()); + xChild->appendChild(xValue); + + css::uno::Reference<css::xml::dom::XAttr> xName = xDoc->createAttribute("name"); + if (xType->getNodeValue() == "controller-for") + xName->setValue("controls"); + else + xName->setValue(xType->getNodeValue()); + + css::uno::Reference<css::xml::dom::XElement> xElem(xChild, + css::uno::UNO_QUERY_THROW); + xElem->setAttributeNode(xName); + xElem->removeAttribute("type"); + xElem->removeAttribute("target"); + } + } + + auto xNextChild = xChild->getNextSibling(); + + 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()); + + if (sClass == "GtkMenu") + { + css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id"); + OUString sId(xId->getNodeValue() + "-menu-model"); + + // <menu id='menubar'> + css::uno::Reference<css::xml::dom::XElement> xMenu = xDoc->createElement("menu"); + css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id"); + xIdAttr->setValue(sId); + xMenu->setAttributeNode(xIdAttr); + xChild->getParentNode()->insertBefore(xMenu, xChild); + + css::uno::Reference<css::xml::dom::XElement> xSection + = xDoc->createElement("section"); + xMenu->appendChild(xSection); + + css::uno::Reference<css::xml::dom::XNode> xMenuSection(xSection); + ConvertMenu(xMenuSection, xChild); + + // now remove GtkMenu contents + while (true) + { + auto xFirstChild = xChild->getFirstChild(); + if (!xFirstChild.is()) + break; + xChild->removeChild(xFirstChild); + } + + // change to GtkPopoverMenu + xClass->setNodeValue("GtkPopoverMenu"); + + // <property name="menu-model"> + xChild->appendChild(CreateProperty(xDoc, "menu-model", sId)); + xChild->appendChild(CreateProperty(xDoc, "visible", "False")); + } + } + + bool bChildHasIconSize = false; + bool bChildHasVisible = false; + bool bChildAlwaysShowImage = false; + GtkPositionType eChildImagePos = GTK_POS_LEFT; + bool bChildUseUnderline = false; + bool bChildVertOrientation = false; + bool bChildXAlign = false; + css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel; + css::uno::Reference<css::xml::dom::XNode> xChildPropertyIconName; + if (xChild->hasChildNodes() && xChild != xGeneratedImageChild) + { + 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; + bChildHasIconSize = aChildRes.m_bHasIconSize; + bChildAlwaysShowImage = aChildRes.m_bAlwaysShowImage; + eChildImagePos = aChildRes.m_eImagePos; + bChildUseUnderline = aChildRes.m_bUseUnderline; + bChildVertOrientation = aChildRes.m_bVertOrientation; + bChildXAlign = aChildRes.m_bXAlign; + xChildPropertyLabel = aChildRes.m_xPropertyLabel; + xChildPropertyIconName = aChildRes.m_xPropertyIconName; + } + } + + 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> xInternalChild + = xInternalChildCandidateMap->getNamedItem("internal-child"); + + // turn default gtk3 invisibility for widget objects into explicit invisible, but ignore internal-children + if (!bChildHasVisible && !xInternalChild) + { + 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"); + insertAsFirstChild(xChild, xVisible); + } + } + + if (sClass == "GtkButtonBox") + { + if (xInternalChild && xInternalChild->getNodeValue() == "action_area" + && !ToplevelIsMessageDialog(xChild)) + { + xClass->setNodeValue("GtkHeaderBar"); + auto xSpacingNode = CreateProperty(xDoc, "show-title-buttons", "False"); + insertAsFirstChild(xChild, 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; + + css::uno::Reference<css::xml::dom::XNode> xObject + = GetChildObject(xTitleChild); + if (xObject) + { + css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap + = xObject->getAttributes(); + css::uno::Reference<css::xml::dom::XNode> xObjectId + = xObjectMap->getNamedItem("id"); + sNodeId = xObjectId->getNodeValue(); + } + + 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 == "GtkToolbar") + { + xClass->setNodeValue("GtkBox"); + css::uno::Reference<css::xml::dom::XElement> xStyle = xDoc->createElement("style"); + css::uno::Reference<css::xml::dom::XElement> xToolbarClass + = xDoc->createElement("class"); + css::uno::Reference<css::xml::dom::XAttr> xPropName = xDoc->createAttribute("name"); + xPropName->setValue("toolbar"); + xToolbarClass->setAttributeNode(xPropName); + xStyle->appendChild(xToolbarClass); + xChild->appendChild(xStyle); + } + else if (sClass == "GtkToolButton") + { + xClass->setNodeValue("GtkButton"); + } + else if (sClass == "GtkToolItem") + { + xClass->setNodeValue("GtkBox"); + } + else if (sClass == "GtkMenuToolButton") + { + xClass->setNodeValue("GtkMenuButton"); + if (gtk_get_minor_version() >= 4) + { + auto xAlwaysShowArrow = CreateProperty(xDoc, "always-show-arrow", "True"); + insertAsFirstChild(xChild, xAlwaysShowArrow); + } + } + else if (sClass == "GtkRadioToolButton") + { + xClass->setNodeValue("GtkCheckButton"); + } + else if (sClass == "GtkToggleToolButton") + { + xClass->setNodeValue("GtkToggleButton"); + } + else if (sClass == "GtkSeparatorToolItem") + { + xClass->setNodeValue("GtkSeparator"); + } + else if (sClass == "GtkBox") + { + // reverse the order of the pack-type=end widgets + std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackEnds; + std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackStarts; + css::uno::Reference<css::xml::dom::XNode> xBoxChild = xChild->getFirstChild(); + while (xBoxChild.is()) + { + auto xNextBoxChild = xBoxChild->getNextSibling(); + + if (xBoxChild->getNodeName() == "child") + { + css::uno::Reference<css::xml::dom::XNamedNodeMap> xBoxChildMap + = xBoxChild->getAttributes(); + css::uno::Reference<css::xml::dom::XNode> xType + = xBoxChildMap->getNamedItem("type"); + if (xType && xType->getNodeValue() == "end") + aPackEnds.push_back(xChild->removeChild(xBoxChild)); + else + aPackStarts.push_back(xBoxChild); + } + + xBoxChild = xNextBoxChild; + } + + if (!aPackEnds.empty()) + { + std::reverse(aPackEnds.begin(), aPackEnds.end()); + + if (!bChildVertOrientation) + { + bool bHasStartObject = false; + bool bLastStartExpands = false; + if (!aPackStarts.empty()) + { + css::uno::Reference<css::xml::dom::XNode> xLastStartObject; + for (auto it = aPackStarts.rbegin(); it != aPackStarts.rend(); ++it) + { + xLastStartObject = GetChildObject(*it); + if (xLastStartObject.is()) + { + bHasStartObject = true; + for (css::uno::Reference<css::xml::dom::XNode> xExpandCandidate + = xLastStartObject->getFirstChild(); + xExpandCandidate.is(); + xExpandCandidate = xExpandCandidate->getNextSibling()) + { + if (xExpandCandidate->getNodeName() == "property") + { + css::uno::Reference<css::xml::dom::XNamedNodeMap> + xExpandMap = xExpandCandidate->getAttributes(); + css::uno::Reference<css::xml::dom::XNode> xName + = xExpandMap->getNamedItem("name"); + OUString sPropName(xName->getNodeValue()); + if (sPropName == "hexpand") + { + bLastStartExpands + = toBool(xExpandCandidate->getFirstChild() + ->getNodeValue()); + break; + } + } + } + break; + } + } + } + + if (bHasStartObject && !bLastStartExpands) + { + auto xAlign = CreateProperty(xDoc, "halign", "end"); + insertAsFirstChild(GetChildObject(aPackEnds[0]), xAlign); + auto xExpand = CreateProperty(xDoc, "hexpand", "True"); + insertAsFirstChild(GetChildObject(aPackEnds[0]), xExpand); + } + } + + for (auto& xPackEnd : aPackEnds) + xChild->appendChild(xPackEnd); + } + } + else if (sClass == "GtkRadioButton") + { + xClass->setNodeValue("GtkCheckButton"); + } + else if (sClass == "GtkImage") + { + /* a) keep symbolic icon-names as GtkImage, e.g. writer, format, columns, next/prev + buttons + b) assume that an explicit icon-size represents a request for a scaled icon + so keep those as GtkImage. e.g. hyperlink dialog notebook tab images and + calc paste special button images + c) turn everything else into a GtkPicture, e.g. help, about. If a GtkPicture + ends up used to display just a simple icon that's generally not a problem. + */ + bool bKeepAsImage = false; + if (bChildHasIconSize) + bKeepAsImage = true; + else if (xChildPropertyIconName.is()) + { + OUString sIconName(xChildPropertyIconName->getFirstChild()->getNodeValue()); + bool bHasSymbolicIconName = IsAllowedBuiltInIcon(sIconName); + if (bHasSymbolicIconName) + { + if (sIconName != "missing-image") + bKeepAsImage = true; + else + { + // If the symbolic icon-name is missing-image then decide to make + // it a GtkPicture if it has a parent widget and keep it as GtkImage + // if it has just the root "interface" as parent. + // for e.g. view, user interface + css::uno::Reference<css::xml::dom::XNode> xParent + = xChild->getParentNode(); + bKeepAsImage = xParent->getNodeName() == "interface"; + if (!bKeepAsImage) + xChild->removeChild(xChildPropertyIconName); + } + } + else + { + // private:graphicrepository/ would be turned by gio (?) into private:///graphicrepository/ + // so use private:///graphicrepository/ here. At the moment we just want this to be transported + // as-is to postprocess_widget. Though it might be nice to register a protocol handler with gio + // to avoid us doing the load in that second pass. + auto xUri = CreateProperty(xDoc, "file", + "private:///graphicrepository/" + sIconName); + xChild->insertBefore(xUri, xChildPropertyIconName); + // calc, insert, header and footer, custom header menubutton icon + auto xCanShrink = CreateProperty(xDoc, "can-shrink", "False"); + xChild->insertBefore(xCanShrink, xChildPropertyIconName); + xChild->removeChild(xChildPropertyIconName); + } + } + if (!bKeepAsImage) + xClass->setNodeValue("GtkPicture"); + } + else if (sClass == "GtkPopover" && !bHasVisible) + { + auto xVisible = CreateProperty(xDoc, "visible", "False"); + insertAsFirstChild(xChild, xVisible); + } + else if (sClass == "AtkObject") + { + css::uno::Reference<css::xml::dom::XNode> xParentObject = xNode->getParentNode(); + css::uno::Reference<css::xml::dom::XElement> xAccessibility + = xDoc->createElement("accessibility"); + xParentObject->insertBefore(xAccessibility, xNode); + + // move the converted old AtkObject:: properties into a new accessibility parent + for (css::uno::Reference<css::xml::dom::XNode> xCurrent = xChild->getFirstChild(); + xCurrent.is(); xCurrent = xChild->getFirstChild()) + { + xAccessibility->appendChild(xChild->removeChild(xCurrent)); + } + } + + // only create the child box for GtkButton/GtkToggleButton + if (bChildAlwaysShowImage) + { + auto xImageCandidateNode = xChild->getLastChild(); + if (xImageCandidateNode && xImageCandidateNode->getNodeName() != "child") + xImageCandidateNode.clear(); + if (xImageCandidateNode) + xChild->removeChild(xImageCandidateNode); + + // for GtkMenuButton if this is a gearmenu with just an icon + // then "icon-name" is used for the indicator and there is + // expected to be no text. If there is a GtkPicture then treat + // this like a GtkButton and presumably it's a ToggleMenuButton + // and the relocation of contents happens in the builder + if (sClass == "GtkMenuButton") + { + bChildAlwaysShowImage = false; + if (xImageCandidateNode) + { + bChildAlwaysShowImage = true; + auto xImageObject = GetChildObject(xImageCandidateNode); + auto xProp = xImageObject->getFirstChild(); + while (xProp.is()) + { + if (xProp->getNodeName() == "property") + { + css::uno::Reference<css::xml::dom::XNamedNodeMap> xPropMap + = xProp->getAttributes(); + css::uno::Reference<css::xml::dom::XNode> xPropName + = xPropMap->getNamedItem("name"); + OUString sPropName(xPropName->getNodeValue().replace('_', '-')); + if (sPropName == "icon-name") + { + OUString sIconName(xProp->getFirstChild()->getNodeValue()); + auto xIconName = CreateProperty(xDoc, "icon-name", sIconName); + insertAsFirstChild(xChild, xIconName); + bChildAlwaysShowImage = false; + break; + } + } + + xProp = xProp->getNextSibling(); + } + } + } + + if (bChildAlwaysShowImage) + { + 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); + + if (eChildImagePos == GTK_POS_TOP || eChildImagePos == GTK_POS_BOTTOM) + { + auto xOrientation = CreateProperty(xDoc, "orientation", "vertical"); + xNewObjectNode->appendChild(xOrientation); + } + + xNewObjectNode->appendChild(CreateProperty(xDoc, "spacing", "6")); + if (!bChildXAlign) + xNewObjectNode->appendChild(CreateProperty(xDoc, "halign", "center")); + + 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)); + } + else + { + auto xNotVisible = CreateProperty(xDoc, "visible", "False"); + xNewChildObjectNode->appendChild(xNotVisible); + } + if (bChildUseUnderline) + { + auto xUseUnderline = CreateProperty(xDoc, "use-underline", "True"); + xNewChildObjectNode->appendChild(xUseUnderline); + } + xNewLabelChildNode->appendChild(xNewChildObjectNode); + + if (eChildImagePos == GTK_POS_LEFT || eChildImagePos == GTK_POS_TOP) + { + if (xImageCandidateNode) + xNewObjectNode->appendChild(xImageCandidateNode); + xNewObjectNode->appendChild(xNewLabelChildNode); + } + else + { + xNewObjectNode->appendChild(xNewLabelChildNode); + if (xImageCandidateNode) + xNewObjectNode->appendChild(xImageCandidateNode); + } + } + } + } + + xChild = xNextChild; + } + + if (!sBorderWidth.isEmpty()) + AddBorderAsMargins(xNode, sBorderWidth); + + for (auto& xRemove : xRemoveList) + xNode->removeChild(xRemove); + + // https://gitlab.gnome.org/GNOME/gtk/-/issues/4041 double encode ampersands if use-underline is used + if (gtk_check_version(4, 3, 2) != nullptr) + { + if (xPropertyLabel) + { + auto xLabelText = xPropertyLabel->getFirstChild(); + if (xLabelText.is()) + { + OString sText = xLabelText->getNodeValue().toUtf8(); + gchar* pText = g_markup_escape_text(sText.getStr(), sText.getLength()); + xLabelText->setNodeValue(OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8)); + g_free(pText); + } + } + } + + return ConvertResult(bChildCanFocus, bHasVisible, bHasIconSize, bAlwaysShowImage, bUseUnderline, + bVertOrientation, bXAlign, eImagePos, xPropertyLabel, xPropertyIconName); +} + +std::once_flag cairo_surface_type_flag; + +void ensure_cairo_surface_type() +{ + // cairo_gobject_surface_get_type needs to be called at least once for + // g_type_from_name to be able to resolve "CairoSurface". In gtk3 there was fallback + // mechanism to attempt to resolve such "missing" types which is not in place for + // gtk4 so ensure it can be found explicitly + std::call_once(cairo_surface_type_flag, []() { cairo_gobject_surface_get_type(); }); +} +} + +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_THROW); + css::uno::Reference<css::io::XStream> xTempStream(xTempFile, css::uno::UNO_QUERY_THROW); + xTempFile->setPropertyValue("RemoveFile", css::uno::Any(false)); + + // serialize it back to xml + css::uno::Reference<css::xml::sax::XSAXSerializable> xSerializer(xDocument, + css::uno::UNO_QUERY_THROW); + 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>()); + + ensure_cairo_surface_type(); + + // 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())); + bool 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/convert3to4.hxx b/vcl/unx/gtk4/convert3to4.hxx new file mode 100644 index 000000000..d75d9fd91 --- /dev/null +++ b/vcl/unx/gtk4/convert3to4.hxx @@ -0,0 +1,16 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <gtk/gtk.h> + +// convert gtk3 .ui to gtk4 and load it +void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/customcellrenderer.cxx b/vcl/unx/gtk4/customcellrenderer.cxx new file mode 100644 index 000000000..aebe2c89e --- /dev/null +++ b/vcl/unx/gtk4/customcellrenderer.cxx @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "../gtk3/customcellrenderer.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx new file mode 100644 index 000000000..a820b4995 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/SalGtkFilePicker.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx new file mode 100644 index 000000000..44ea7bd44 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/SalGtkFilePicker.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx new file mode 100644 index 000000000..41dfdf021 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/SalGtkFolderPicker.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx new file mode 100644 index 000000000..129a69933 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/SalGtkFolderPicker.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx new file mode 100644 index 000000000..159ef4704 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/SalGtkPicker.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx new file mode 100644 index 000000000..0432004a0 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/SalGtkPicker.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/eventnotification.hxx b/vcl/unx/gtk4/fpicker/eventnotification.hxx new file mode 100644 index 000000000..b1e16bd65 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/eventnotification.hxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/eventnotification.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/fpicker/resourceprovider.cxx b/vcl/unx/gtk4/fpicker/resourceprovider.cxx new file mode 100644 index 000000000..d4251cc94 --- /dev/null +++ b/vcl/unx/gtk4/fpicker/resourceprovider.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/fpicker/resourceprovider.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gloactiongroup.cxx b/vcl/unx/gtk4/gloactiongroup.cxx new file mode 100644 index 000000000..f147af283 --- /dev/null +++ b/vcl/unx/gtk4/gloactiongroup.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/gloactiongroup.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/glomenu.cxx b/vcl/unx/gtk4/glomenu.cxx new file mode 100644 index 000000000..cae9bc03a --- /dev/null +++ b/vcl/unx/gtk4/glomenu.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/glomenu.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkcairo.cxx b/vcl/unx/gtk4/gtkcairo.cxx new file mode 100644 index 000000000..3178fdea0 --- /dev/null +++ b/vcl/unx/gtk4/gtkcairo.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/gtkcairo.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkcairo.hxx b/vcl/unx/gtk4/gtkcairo.hxx new file mode 100644 index 000000000..97f63101d --- /dev/null +++ b/vcl/unx/gtk4/gtkcairo.hxx @@ -0,0 +1,12 @@ +/* -*- 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 "../../gtk3/gtkdata.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkdata.cxx b/vcl/unx/gtk4/gtkdata.cxx new file mode 100644 index 000000000..41d85217c --- /dev/null +++ b/vcl/unx/gtk4/gtkdata.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/gtkdata.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkframe.cxx b/vcl/unx/gtk4/gtkframe.cxx new file mode 100644 index 000000000..e446ba8a5 --- /dev/null +++ b/vcl/unx/gtk4/gtkframe.cxx @@ -0,0 +1,14 @@ +/* -*- 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 "transferableprovider.hxx" + +#include "../gtk3/gtkframe.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkinst.cxx b/vcl/unx/gtk4/gtkinst.cxx new file mode 100644 index 000000000..658c01233 --- /dev/null +++ b/vcl/unx/gtk4/gtkinst.cxx @@ -0,0 +1,21 @@ +/* -*- 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/. + */ + +// make gtk4 plug advertise correctly as gtk4 +#define GTK_TOOLKIT_NAME "gtk4" + +#include "convert3to4.hxx" +#include "notifyinglayout.hxx" +#include "surfacecellrenderer.hxx" +#include "surfacepaintable.hxx" +#include "transferableprovider.hxx" + +#include "../gtk3/gtkinst.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkobject.cxx b/vcl/unx/gtk4/gtkobject.cxx new file mode 100644 index 000000000..8eb5dcf1a --- /dev/null +++ b/vcl/unx/gtk4/gtkobject.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/gtkobject.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtksalmenu.cxx b/vcl/unx/gtk4/gtksalmenu.cxx new file mode 100644 index 000000000..895078124 --- /dev/null +++ b/vcl/unx/gtk4/gtksalmenu.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/gtksalmenu.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtksys.cxx b/vcl/unx/gtk4/gtksys.cxx new file mode 100644 index 000000000..dfdcf0a7c --- /dev/null +++ b/vcl/unx/gtk4/gtksys.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/gtksys.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/hudawareness.cxx b/vcl/unx/gtk4/hudawareness.cxx new file mode 100644 index 000000000..b2683b2bd --- /dev/null +++ b/vcl/unx/gtk4/hudawareness.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/hudawareness.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/notifyinglayout.cxx b/vcl/unx/gtk4/notifyinglayout.cxx new file mode 100644 index 000000000..46b9a2d95 --- /dev/null +++ b/vcl/unx/gtk4/notifyinglayout.cxx @@ -0,0 +1,88 @@ +/* -*- 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 "notifyinglayout.hxx" + +struct _NotifyingLayout +{ + GtkLayoutManager parent_instance; + + GtkWidget* m_pWidget; + GtkLayoutManager* m_pOrigManager; + Link<void*, void> m_aLink; +}; + +G_DEFINE_TYPE(NotifyingLayout, notifying_layout, GTK_TYPE_LAYOUT_MANAGER) + +static void notifying_layout_measure(GtkLayoutManager* pLayoutManager, GtkWidget* widget, + GtkOrientation orientation, int for_size, int* minimum, + int* natural, int* minimum_baseline, int* natural_baseline) +{ + NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager); + GtkLayoutManagerClass* pKlass + = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager)); + pKlass->measure(self->m_pOrigManager, widget, orientation, for_size, minimum, natural, + minimum_baseline, natural_baseline); +} + +static void notifying_layout_allocate(GtkLayoutManager* pLayoutManager, GtkWidget* widget, + int width, int height, int baseline) +{ + NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager); + GtkLayoutManagerClass* pKlass + = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager)); + pKlass->allocate(self->m_pOrigManager, widget, width, height, baseline); + self->m_aLink.Call(nullptr); +} + +static GtkSizeRequestMode notifying_layout_get_request_mode(GtkLayoutManager* pLayoutManager, + GtkWidget* widget) +{ + NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager); + GtkLayoutManagerClass* pKlass + = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager)); + return pKlass->get_request_mode(self->m_pOrigManager, widget); +} + +static void notifying_layout_class_init(NotifyingLayoutClass* klass) +{ + GtkLayoutManagerClass* layout_class = GTK_LAYOUT_MANAGER_CLASS(klass); + + layout_class->get_request_mode = notifying_layout_get_request_mode; + layout_class->measure = notifying_layout_measure; + layout_class->allocate = notifying_layout_allocate; +} + +static void notifying_layout_init(NotifyingLayout* self) +{ + self->m_pWidget = nullptr; + self->m_pOrigManager = nullptr; + + // prevent loplugin:unreffun firing on macro generated function + (void)notifying_layout_get_instance_private(self); +} + +void notifying_layout_start_watch(NotifyingLayout* self, GtkWidget* pWidget, + const Link<void*, void>& rLink) +{ + self->m_pWidget = pWidget; + self->m_aLink = rLink; + + self->m_pOrigManager = gtk_widget_get_layout_manager(self->m_pWidget); + g_object_ref(self->m_pOrigManager); + + gtk_widget_set_layout_manager(pWidget, GTK_LAYOUT_MANAGER(self)); +} + +void notifying_layout_stop_watch(NotifyingLayout* self) +{ + gtk_widget_set_layout_manager(self->m_pWidget, self->m_pOrigManager); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/notifyinglayout.hxx b/vcl/unx/gtk4/notifyinglayout.hxx new file mode 100644 index 000000000..a717a3061 --- /dev/null +++ b/vcl/unx/gtk4/notifyinglayout.hxx @@ -0,0 +1,36 @@ +/* -*- 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/. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <tools/link.hxx> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(NotifyingLayout, notifying_layout, NOTIFYING, LAYOUT, GtkLayoutManager) + +/* + Replace the existing GtkLayoutManager of pWidget with pLayout instead which will + forward all requests to the original GtkLayoutManager but additionally call + rLink when a size is allocated to the pWidget. + + This provides a workaround for the removal of the size-allocate signal in gtk4 +*/ +void notifying_layout_start_watch(NotifyingLayout* pLayout, GtkWidget* pWidget, + const Link<void*, void>& rLink); + +/* + Undo a previous notifying_layout_start_watch. +*/ +void notifying_layout_stop_watch(NotifyingLayout* pLayout); + +G_END_DECLS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/salnativewidgets-gtk.cxx b/vcl/unx/gtk4/salnativewidgets-gtk.cxx new file mode 100644 index 000000000..ed036e98c --- /dev/null +++ b/vcl/unx/gtk4/salnativewidgets-gtk.cxx @@ -0,0 +1,12 @@ +/* -*- 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 "../gtk3/salnativewidgets-gtk.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/surfacecellrenderer.cxx b/vcl/unx/gtk4/surfacecellrenderer.cxx new file mode 100644 index 000000000..2ae0782b9 --- /dev/null +++ b/vcl/unx/gtk4/surfacecellrenderer.cxx @@ -0,0 +1,241 @@ +/* -*- 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 <vcl/svapp.hxx> +#include "surfacecellrenderer.hxx" +#include <cairo/cairo-gobject.h> + +namespace +{ +struct _SurfaceCellRendererClass : public GtkCellRendererClass +{ +}; + +enum +{ + PROP_SURFACE = 10000, +}; +} + +G_DEFINE_TYPE(SurfaceCellRenderer, surface_cell_renderer, GTK_TYPE_CELL_RENDERER) + +static void surface_cell_renderer_init(SurfaceCellRenderer* self) +{ + self->surface = nullptr; + // prevent loplugin:unreffun firing on macro generated function + (void)surface_cell_renderer_get_instance_private(self); +} + +static void surface_cell_renderer_get_property(GObject* object, guint param_id, GValue* value, + GParamSpec* pspec) +{ + SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object); + + switch (param_id) + { + case PROP_SURFACE: + g_value_set_boxed(value, cellsurface->surface); + break; + default: + G_OBJECT_CLASS(surface_cell_renderer_parent_class) + ->get_property(object, param_id, value, pspec); + break; + } +} + +static void surface_cell_renderer_set_property(GObject* object, guint param_id, const GValue* value, + GParamSpec* pspec) +{ + SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object); + + switch (param_id) + { + case PROP_SURFACE: + if (cellsurface->surface) + cairo_surface_destroy(cellsurface->surface); + cellsurface->surface = static_cast<cairo_surface_t*>(g_value_get_boxed(value)); + if (cellsurface->surface) + cairo_surface_reference(cellsurface->surface); + break; + default: + G_OBJECT_CLASS(surface_cell_renderer_parent_class) + ->set_property(object, param_id, value, pspec); + break; + } +} + +static bool surface_cell_renderer_get_preferred_size(GtkCellRenderer* cell, + GtkOrientation orientation, gint* minimum_size, + gint* natural_size); + +static void surface_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot, + GtkWidget* widget, const GdkRectangle* background_area, + const GdkRectangle* cell_area, + GtkCellRendererState flags); + +static void surface_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* widget, + const GdkRectangle* background_area, + const GdkRectangle* cell_area, GtkCellRendererState flags); + +static void surface_cell_renderer_finalize(GObject* object) +{ + SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object); + + if (cellsurface->surface) + cairo_surface_destroy(cellsurface->surface); + + G_OBJECT_CLASS(surface_cell_renderer_parent_class)->finalize(object); +} + +static void surface_cell_renderer_get_preferred_width(GtkCellRenderer* cell, GtkWidget* widget, + gint* minimum_size, gint* natural_size) +{ + if (!surface_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_HORIZONTAL, minimum_size, + natural_size)) + { + // fallback to parent if we're empty + GTK_CELL_RENDERER_CLASS(surface_cell_renderer_parent_class) + ->get_preferred_width(cell, widget, minimum_size, natural_size); + } +} + +static void surface_cell_renderer_get_preferred_height(GtkCellRenderer* cell, GtkWidget* widget, + gint* minimum_size, gint* natural_size) +{ + if (!surface_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_VERTICAL, minimum_size, + natural_size)) + { + // fallback to parent if we're empty + GTK_CELL_RENDERER_CLASS(surface_cell_renderer_parent_class) + ->get_preferred_height(cell, widget, minimum_size, natural_size); + } +} + +static void surface_cell_renderer_get_preferred_height_for_width(GtkCellRenderer* cell, + GtkWidget* widget, gint /*width*/, + gint* minimum_height, + gint* natural_height) +{ + gtk_cell_renderer_get_preferred_height(cell, widget, minimum_height, natural_height); +} + +static void surface_cell_renderer_get_preferred_width_for_height(GtkCellRenderer* cell, + GtkWidget* widget, gint /*height*/, + gint* minimum_width, + gint* natural_width) +{ + gtk_cell_renderer_get_preferred_width(cell, widget, minimum_width, natural_width); +} + +void surface_cell_renderer_class_init(SurfaceCellRendererClass* klass) +{ + GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass); + GObjectClass* object_class = G_OBJECT_CLASS(klass); + + /* Hook up functions to set and get our custom cell renderer properties */ + object_class->get_property = surface_cell_renderer_get_property; + object_class->set_property = surface_cell_renderer_set_property; + + surface_cell_renderer_parent_class = g_type_class_peek_parent(klass); + object_class->finalize = surface_cell_renderer_finalize; + + cell_class->get_preferred_width = surface_cell_renderer_get_preferred_width; + cell_class->get_preferred_height = surface_cell_renderer_get_preferred_height; + cell_class->get_preferred_width_for_height + = surface_cell_renderer_get_preferred_width_for_height; + cell_class->get_preferred_height_for_width + = surface_cell_renderer_get_preferred_height_for_width; + + cell_class->snapshot = surface_cell_renderer_snapshot; + + g_object_class_install_property( + object_class, PROP_SURFACE, + g_param_spec_boxed("surface", "Surface", "The cairo surface to render", + CAIRO_GOBJECT_TYPE_SURFACE, G_PARAM_READWRITE)); +} + +GtkCellRenderer* surface_cell_renderer_new() +{ + return GTK_CELL_RENDERER(g_object_new(SURFACE_TYPE_CELL_RENDERER, nullptr)); +} + +static void get_surface_size(cairo_surface_t* pSurface, int& rWidth, int& rHeight) +{ + double x1, x2, y1, y2; + cairo_t* cr = cairo_create(pSurface); + cairo_clip_extents(cr, &x1, &y1, &x2, &y2); + cairo_destroy(cr); + + rWidth = x2 - x1; + rHeight = y2 - y1; +} + +bool surface_cell_renderer_get_preferred_size(GtkCellRenderer* cell, GtkOrientation orientation, + gint* minimum_size, gint* natural_size) +{ + SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(cell); + + int nWidth = 0; + int nHeight = 0; + + if (cellsurface->surface) + get_surface_size(cellsurface->surface, nWidth, nHeight); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (minimum_size) + *minimum_size = nWidth; + + if (natural_size) + *natural_size = nWidth; + } + else + { + if (minimum_size) + *minimum_size = nHeight; + + if (natural_size) + *natural_size = nHeight; + } + + return true; +} + +void surface_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* /*widget*/, + const GdkRectangle* /*background_area*/, + const GdkRectangle* cell_area, GtkCellRendererState /*flags*/) +{ + SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(cell); + if (!cellsurface->surface) + return; + + int nWidth, nHeight; + get_surface_size(cellsurface->surface, nWidth, nHeight); + int nXOffset = (cell_area->width - nWidth) / 2; + int nYOffset = (cell_area->height - nHeight) / 2; + + cairo_set_source_surface(cr, cellsurface->surface, cell_area->x + nXOffset, + cell_area->y + nYOffset); + cairo_paint(cr); +} + +static void surface_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot, + GtkWidget* widget, const GdkRectangle* background_area, + const GdkRectangle* cell_area, + GtkCellRendererState flags) +{ + graphene_rect_t rect = GRAPHENE_RECT_INIT( + static_cast<float>(cell_area->x), static_cast<float>(cell_area->y), + static_cast<float>(cell_area->width), static_cast<float>(cell_area->height)); + cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect); + surface_cell_renderer_render(cell, cr, widget, background_area, cell_area, flags); + cairo_destroy(cr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/surfacecellrenderer.hxx b/vcl/unx/gtk4/surfacecellrenderer.hxx new file mode 100644 index 000000000..d908c04ce --- /dev/null +++ b/vcl/unx/gtk4/surfacecellrenderer.hxx @@ -0,0 +1,42 @@ +/* -*- 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/. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <cairo.h> + +G_BEGIN_DECLS + +struct _SurfaceCellRenderer +{ + GtkCellRenderer parent; + cairo_surface_t* surface; +}; + +/* + Provide a mechanism to support rendering a cairo surface in a GtkComboBox +*/ + +G_DECLARE_FINAL_TYPE(SurfaceCellRenderer, surface_cell_renderer, SURFACE, CELL_RENDERER, + GtkCellRenderer) + +#define SURFACE_TYPE_CELL_RENDERER (surface_cell_renderer_get_type()) + +#define SURFACE_CELL_RENDERER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), SURFACE_TYPE_CELL_RENDERER, SurfaceCellRenderer)) + +#define SURFACE_IS_CELL_RENDERER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), SURFACE_TYPE_CELL_RENDERER)) + +GtkCellRenderer* surface_cell_renderer_new(); + +G_END_DECLS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/surfacepaintable.cxx b/vcl/unx/gtk4/surfacepaintable.cxx new file mode 100644 index 000000000..2e8aa98b2 --- /dev/null +++ b/vcl/unx/gtk4/surfacepaintable.cxx @@ -0,0 +1,100 @@ +/* -*- 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 "surfacepaintable.hxx" + +struct _SurfacePaintable +{ + GObject parent_instance; + int width; + int height; + cairo_surface_t* surface; +}; + +namespace +{ +struct _SurfacePaintableClass : public GObjectClass +{ +}; +} + +static void surface_paintable_snapshot(GdkPaintable* paintable, GdkSnapshot* snapshot, double width, + double height) +{ + graphene_rect_t rect + = GRAPHENE_RECT_INIT(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)); + SurfacePaintable* self = SURFACE_PAINTABLE(paintable); + cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect); + cairo_set_source_surface(cr, self->surface, 0, 0); + cairo_paint(cr); + cairo_destroy(cr); +} + +static GdkPaintableFlags surface_paintable_get_flags(GdkPaintable* /*paintable*/) +{ + return static_cast<GdkPaintableFlags>(GDK_PAINTABLE_STATIC_SIZE + | GDK_PAINTABLE_STATIC_CONTENTS); +} + +static int surface_paintable_get_intrinsic_width(GdkPaintable* paintable) +{ + SurfacePaintable* self = SURFACE_PAINTABLE(paintable); + return self->width; +} + +static int surface_paintable_get_intrinsic_height(GdkPaintable* paintable) +{ + SurfacePaintable* self = SURFACE_PAINTABLE(paintable); + return self->height; +} + +static void surface_paintable_init_interface(GdkPaintableInterface* iface) +{ + iface->snapshot = surface_paintable_snapshot; + iface->get_flags = surface_paintable_get_flags; + iface->get_intrinsic_width = surface_paintable_get_intrinsic_width; + iface->get_intrinsic_height = surface_paintable_get_intrinsic_height; +} + +G_DEFINE_TYPE_WITH_CODE(SurfacePaintable, surface_paintable, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GDK_TYPE_PAINTABLE, + surface_paintable_init_interface)); + +static void surface_paintable_init(SurfacePaintable* self) +{ + self->width = 0; + self->height = 0; + self->surface = nullptr; + + // prevent loplugin:unreffun firing on macro generated function + (void)surface_paintable_get_instance_private(self); +} + +static void surface_paintable_dispose(GObject* object) +{ + SurfacePaintable* self = SURFACE_PAINTABLE(object); + cairo_surface_destroy(self->surface); + G_OBJECT_CLASS(surface_paintable_parent_class)->dispose(object); +} + +static void surface_paintable_class_init(SurfacePaintableClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = surface_paintable_dispose; +} + +void surface_paintable_set_source(SurfacePaintable* pPaintable, cairo_surface_t* pSource, + int nWidth, int nHeight) +{ + pPaintable->surface = pSource; + pPaintable->width = nWidth; + pPaintable->height = nHeight; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/surfacepaintable.hxx b/vcl/unx/gtk4/surfacepaintable.hxx new file mode 100644 index 000000000..3e8c79f8a --- /dev/null +++ b/vcl/unx/gtk4/surfacepaintable.hxx @@ -0,0 +1,32 @@ +/* -*- 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/. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <cairo.h> + +G_BEGIN_DECLS + +/* + Provide a mechanism to allow continuing to use cairo surface where a GdkPaintable + is required +*/ + +G_DECLARE_FINAL_TYPE(SurfacePaintable, surface_paintable, SURFACE, PAINTABLE, GObject) + +/* + Set the surface to paint, takes ownership of pSource +*/ +void surface_paintable_set_source(SurfacePaintable* pPaintable, cairo_surface_t* pSource, + int nWidth, int nHeight); + +G_END_DECLS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/transferableprovider.cxx b/vcl/unx/gtk4/transferableprovider.cxx new file mode 100644 index 000000000..554b80c0d --- /dev/null +++ b/vcl/unx/gtk4/transferableprovider.cxx @@ -0,0 +1,118 @@ +/* -*- 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/datatransfer/XTransferable.hpp> +#include <unx/gtk/gtkinst.hxx> +#include "transferableprovider.hxx" + +#define TRANSFERABLE_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), transerable_content_get_type(), TransferableContent)) + +struct _TransferableContent +{ + GdkContentProvider parent; + VclToGtkHelper* m_pConversionHelper; + css::datatransfer::XTransferable* m_pContents; + Link<void*, void> m_aDetachClipboardLink; +}; + +namespace +{ +struct _TransferableContentClass : public GdkContentProviderClass +{ +}; +} + +G_DEFINE_TYPE(TransferableContent, transerable_content, GDK_TYPE_CONTENT_PROVIDER) + +static void transerable_content_write_mime_type_async(GdkContentProvider* provider, + const char* mime_type, GOutputStream* stream, + int io_priority, GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TransferableContent* self = TRANSFERABLE_CONTENT(provider); + if (!self->m_pContents) + return; + // tdf#129809 take a reference in case m_aContents is replaced during this + // call + css::uno::Reference<css::datatransfer::XTransferable> xCurrentContents(self->m_pContents); + self->m_pConversionHelper->setSelectionData(xCurrentContents, provider, mime_type, stream, + io_priority, cancellable, callback, user_data); +} + +static gboolean transerable_content_write_mime_type_finish(GdkContentProvider*, + GAsyncResult* result, GError** error) +{ + return g_task_propagate_boolean(G_TASK(result), error); +} + +static GdkContentFormats* transerable_content_ref_formats(GdkContentProvider* provider) +{ + TransferableContent* self = TRANSFERABLE_CONTENT(provider); + css::uno::Reference<css::datatransfer::XTransferable> xCurrentContents(self->m_pContents); + if (!xCurrentContents) + return nullptr; + + auto aFormats = xCurrentContents->getTransferDataFlavors(); + std::vector<OString> aGtkTargets(self->m_pConversionHelper->FormatsToGtk(aFormats)); + + GdkContentFormatsBuilder* pBuilder = gdk_content_formats_builder_new(); + for (const auto& rFormat : aGtkTargets) + gdk_content_formats_builder_add_mime_type(pBuilder, rFormat.getStr()); + return gdk_content_formats_builder_free_to_formats(pBuilder); +} + +static void transerable_content_detach_clipboard(GdkContentProvider* provider, GdkClipboard*) +{ + TransferableContent* self = TRANSFERABLE_CONTENT(provider); + self->m_aDetachClipboardLink.Call(nullptr); +} + +static void transerable_content_class_init(TransferableContentClass* klass) +{ + GdkContentProviderClass* provider_class = GDK_CONTENT_PROVIDER_CLASS(klass); + + provider_class->ref_formats = transerable_content_ref_formats; + provider_class->detach_clipboard = transerable_content_detach_clipboard; + provider_class->write_mime_type_async = transerable_content_write_mime_type_async; + provider_class->write_mime_type_finish = transerable_content_write_mime_type_finish; +} + +static void transerable_content_init(TransferableContent* self) +{ + self->m_pConversionHelper = nullptr; + self->m_pContents = nullptr; + // prevent loplugin:unreffun firing on macro generated function + (void)transerable_content_get_instance_private(self); +} + +void transerable_content_set_transferable(TransferableContent* pContent, + css::datatransfer::XTransferable* pTransferable) +{ + pContent->m_pContents = pTransferable; +} + +void transerable_content_set_detach_clipboard_link(TransferableContent* pContent, + const Link<void*, void>& rDetachClipboardLink) +{ + pContent->m_aDetachClipboardLink = rDetachClipboardLink; +} + +GdkContentProvider* transerable_content_new(VclToGtkHelper* pConversionHelper, + css::datatransfer::XTransferable* pTransferable) +{ + TransferableContent* content + = TRANSFERABLE_CONTENT(g_object_new(transerable_content_get_type(), nullptr)); + content->m_pConversionHelper = pConversionHelper; + content->m_pContents = pTransferable; + return GDK_CONTENT_PROVIDER(content); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/transferableprovider.hxx b/vcl/unx/gtk4/transferableprovider.hxx new file mode 100644 index 000000000..df4d643d9 --- /dev/null +++ b/vcl/unx/gtk4/transferableprovider.hxx @@ -0,0 +1,51 @@ +/* -*- 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/. + */ + +#pragma once + +#include <tools/link.hxx> + +#include <gtk/gtk.h> + +struct VclToGtkHelper; + +namespace com::sun::star::datatransfer +{ +class XTransferable; +} + +G_BEGIN_DECLS + +/* + Provide a mechanism to provide data from a LibreOffice XTransferable via a + GdkContentProvider for gtk clipboard or dnd +*/ + +G_DECLARE_FINAL_TYPE(TransferableContent, transerable_content, TRANSFERABLE, CONTENT, + GdkContentProvider) + +GdkContentProvider* transerable_content_new(VclToGtkHelper* pConversionHelper, + css::datatransfer::XTransferable* pTransferable); + +/* + Change to a new XTransferable + */ +void transerable_content_set_transferable(TransferableContent* pContent, + css::datatransfer::XTransferable* pTransferable); + +/* + If the GdkContentProvider is used by a clipboard call rDetachClipboardLink on losing + ownership of the clipboard +*/ +void transerable_content_set_detach_clipboard_link(TransferableContent* pContent, + const Link<void*, void>& rDetachClipboardLink); + +G_END_DECLS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |