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 <caolanm@redhat.com>
diff --git a/vcl/Library_vclplug_gtk4.mk b/vcl/Library_vclplug_gtk4.mk
index 3f1c3eb..e3cc415 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 cc37cd1..ef257c7 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 5bd818e..1487798 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 3a42aac..90cc860 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 0000000..06ca97f
--- /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 a1066a6..e166777 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: */