1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <com/sun/star/beans/XPropertySet.hpp>
11 #include <com/sun/star/io/TempFile.hpp>
12 #include <com/sun/star/xml/dom/DocumentBuilder.hpp>
13 #include <com/sun/star/xml/sax/Writer.hpp>
14 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
15 #include <comphelper/processfactory.hxx>
16 #include <unx/gtk/gtkdata.hxx>
17 #include <vcl/builder.hxx>
18 #include "convert3to4.hxx"
19 
20 namespace
21 {
22 typedef std::pair<css::uno::Reference<css::xml::dom::XNode>, OUString> named_node;
23 
sortButtonNodes(const named_node & rA,const named_node & rB)24 bool sortButtonNodes(const named_node& rA, const named_node& rB)
25 {
26     //order within groups according to platform rules
27     return getButtonPriority("/" + rA.second.toUtf8())
28            < getButtonPriority("/" + rB.second.toUtf8());
29 }
30 
31 // <property name="spacing">6</property>
32 css::uno::Reference<css::xml::dom::XNode>
CreateProperty(const css::uno::Reference<css::xml::dom::XDocument> & xDoc,const OUString & rPropName,const OUString & rValue)33 CreateProperty(const css::uno::Reference<css::xml::dom::XDocument>& xDoc, const OUString& rPropName,
34                const OUString& rValue)
35 {
36     css::uno::Reference<css::xml::dom::XElement> xProperty = xDoc->createElement("property");
37     css::uno::Reference<css::xml::dom::XAttr> xPropName = xDoc->createAttribute("name");
38     xPropName->setValue(rPropName);
39     xProperty->setAttributeNode(xPropName);
40     css::uno::Reference<css::xml::dom::XText> xValue = xDoc->createTextNode(rValue);
41     xProperty->appendChild(xValue);
42     return xProperty;
43 }
44 
ToplevelIsMessageDialog(const css::uno::Reference<css::xml::dom::XNode> & xNode)45 bool ToplevelIsMessageDialog(const css::uno::Reference<css::xml::dom::XNode>& xNode)
46 {
47     for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xNode->getParentNode();
48          xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getParentNode())
49     {
50         if (xObjectCandidate->getNodeName() == "object")
51         {
52             css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
53                 = xObjectCandidate->getAttributes();
54             css::uno::Reference<css::xml::dom::XNode> xClass = xObjectMap->getNamedItem("class");
55             if (xClass->getNodeValue() == "GtkMessageDialog")
56                 return true;
57         }
58     }
59     return false;
60 }
61 
insertAsFirstChild(const css::uno::Reference<css::xml::dom::XNode> & xParentNode,const css::uno::Reference<css::xml::dom::XNode> & xChildNode)62 void insertAsFirstChild(const css::uno::Reference<css::xml::dom::XNode>& xParentNode,
63                         const css::uno::Reference<css::xml::dom::XNode>& xChildNode)
64 {
65     auto xFirstChild = xParentNode->getFirstChild();
66     if (xFirstChild.is())
67         xParentNode->insertBefore(xChildNode, xFirstChild);
68     else
69         xParentNode->appendChild(xChildNode);
70 }
71 
SetPropertyOnTopLevel(const css::uno::Reference<css::xml::dom::XNode> & xNode,const css::uno::Reference<css::xml::dom::XNode> & xProperty)72 void SetPropertyOnTopLevel(const css::uno::Reference<css::xml::dom::XNode>& xNode,
73                            const css::uno::Reference<css::xml::dom::XNode>& xProperty)
74 {
75     for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xNode->getParentNode();
76          xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getParentNode())
77     {
78         if (xObjectCandidate->getNodeName() == "object")
79         {
80             css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
81                 = xObjectCandidate->getAttributes();
82             css::uno::Reference<css::xml::dom::XNode> xClass = xObjectMap->getNamedItem("class");
83             if (xClass->getNodeValue() == "GtkDialog")
84             {
85                 insertAsFirstChild(xObjectCandidate, xProperty);
86                 break;
87             }
88         }
89     }
90 }
91 
GetParentObjectType(const css::uno::Reference<css::xml::dom::XNode> & xNode)92 OUString GetParentObjectType(const css::uno::Reference<css::xml::dom::XNode>& xNode)
93 {
94     auto xParent = xNode->getParentNode();
95     assert(xParent->getNodeName() == "object");
96     css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = xParent->getAttributes();
97     css::uno::Reference<css::xml::dom::XNode> xClass = xParentMap->getNamedItem("class");
98     return xClass->getNodeValue();
99 }
100 
101 css::uno::Reference<css::xml::dom::XNode>
GetChildObject(const css::uno::Reference<css::xml::dom::XNode> & xChild)102 GetChildObject(const css::uno::Reference<css::xml::dom::XNode>& xChild)
103 {
104     for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xChild->getFirstChild();
105          xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getNextSibling())
106     {
107         if (xObjectCandidate->getNodeName() == "object")
108             return xObjectCandidate;
109     }
110     return nullptr;
111 }
112 
113 // currently runs the risk of duplicate margin-* properties if there was already such as well
114 // as the border
AddBorderAsMargins(const css::uno::Reference<css::xml::dom::XNode> & xNode,const OUString & rBorderWidth)115 void AddBorderAsMargins(const css::uno::Reference<css::xml::dom::XNode>& xNode,
116                         const OUString& rBorderWidth)
117 {
118     auto xDoc = xNode->getOwnerDocument();
119 
120     auto xMarginEnd = CreateProperty(xDoc, "margin-end", rBorderWidth);
121     insertAsFirstChild(xNode, xMarginEnd);
122     xNode->insertBefore(CreateProperty(xDoc, "margin-top", rBorderWidth), xMarginEnd);
123     xNode->insertBefore(CreateProperty(xDoc, "margin-bottom", rBorderWidth), xMarginEnd);
124     xNode->insertBefore(CreateProperty(xDoc, "margin-start", rBorderWidth), xMarginEnd);
125 }
126 
127 struct MenuEntry
128 {
129     bool m_bDrawAsRadio;
130     css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
131 
MenuEntry__anon7de393400111::MenuEntry132     MenuEntry(bool bDrawAsRadio, const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel)
133         : m_bDrawAsRadio(bDrawAsRadio)
134         , m_xPropertyLabel(rPropertyLabel)
135     {
136     }
137 };
138 
ConvertMenu(const css::uno::Reference<css::xml::dom::XNode> & xOutMenu,const css::uno::Reference<css::xml::dom::XNode> & xNode)139 MenuEntry ConvertMenu(const css::uno::Reference<css::xml::dom::XNode>& xOutMenu,
140                       const css::uno::Reference<css::xml::dom::XNode>& xNode)
141 {
142     css::uno::Reference<css::xml::dom::XNode> xMenu(xOutMenu);
143 
144     bool bDrawAsRadio = false;
145     css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
146 
147     css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
148     while (xChild.is())
149     {
150         if (xChild->getNodeName() == "property")
151         {
152             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
153             css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
154             OUString sName(xName->getNodeValue().replace('_', '-'));
155 
156             if (sName == "label")
157             {
158                 xPropertyLabel = xChild;
159             }
160             else if (sName == "draw-as-radio")
161             {
162                 bDrawAsRadio = toBool(xChild->getFirstChild()->getNodeValue());
163             }
164         }
165 
166         auto xNextChild = xChild->getNextSibling();
167 
168         auto xCurrentMenu = xMenu;
169 
170         if (xChild->getNodeName() == "object")
171         {
172             auto xDoc = xChild->getOwnerDocument();
173 
174             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
175             css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
176             OUString sClass(xClass->getNodeValue());
177 
178             if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem")
179             {
180                 /* <item> */
181                 css::uno::Reference<css::xml::dom::XElement> xItem = xDoc->createElement("item");
182                 xMenu->appendChild(xItem);
183             }
184             else if (sClass == "GtkMenu")
185             {
186                 xMenu->removeChild(xMenu->getLastChild()); // remove preceding <item>
187 
188                 css::uno::Reference<css::xml::dom::XElement> xSubMenu
189                     = xDoc->createElement("submenu");
190                 css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id");
191 
192                 css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
193                 OUString sId(xId->getNodeValue());
194 
195                 xIdAttr->setValue(sId);
196                 xSubMenu->setAttributeNode(xIdAttr);
197                 xMenu->appendChild(xSubMenu);
198 
199                 css::uno::Reference<css::xml::dom::XElement> xSection
200                     = xDoc->createElement("section");
201                 xSubMenu->appendChild(xSection);
202 
203                 xMenu = xSubMenu;
204             }
205         }
206 
207         bool bChildDrawAsRadio = false;
208         css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
209         if (xChild->hasChildNodes())
210         {
211             MenuEntry aEntry = ConvertMenu(xMenu, xChild);
212             bChildDrawAsRadio = aEntry.m_bDrawAsRadio;
213             xChildPropertyLabel = aEntry.m_xPropertyLabel;
214         }
215 
216         if (xChild->getNodeName() == "object")
217         {
218             xMenu = xCurrentMenu;
219 
220             auto xDoc = xChild->getOwnerDocument();
221 
222             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
223             css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
224             OUString sClass(xClass->getNodeValue());
225 
226             if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem")
227             {
228                 css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
229                 OUString sId = xId->getNodeValue();
230 
231                 /*
232                     <attribute name='label' translatable='yes'>whatever</attribute>
233                     <attribute name='action'>menu.action</attribute>
234                     <attribute name='target'>id</attribute>
235                 */
236                 auto xItem = xMenu->getLastChild();
237 
238                 if (xChildPropertyLabel)
239                 {
240                     css::uno::Reference<css::xml::dom::XElement> xChildPropertyElem(
241                         xChildPropertyLabel, css::uno::UNO_QUERY_THROW);
242 
243                     css::uno::Reference<css::xml::dom::XElement> xLabelAttr
244                         = xDoc->createElement("attribute");
245 
246                     css::uno::Reference<css::xml::dom::XNamedNodeMap> xLabelMap
247                         = xChildPropertyLabel->getAttributes();
248                     while (xLabelMap->getLength())
249                     {
250                         css::uno::Reference<css::xml::dom::XAttr> xAttr(xLabelMap->item(0),
251                                                                         css::uno::UNO_QUERY_THROW);
252                         xLabelAttr->setAttributeNode(
253                             xChildPropertyElem->removeAttributeNode(xAttr));
254                     }
255                     xLabelAttr->appendChild(
256                         xChildPropertyLabel->removeChild(xChildPropertyLabel->getFirstChild()));
257 
258                     xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel);
259                     xItem->appendChild(xLabelAttr);
260                 }
261 
262                 css::uno::Reference<css::xml::dom::XElement> xActionAttr
263                     = xDoc->createElement("attribute");
264                 css::uno::Reference<css::xml::dom::XAttr> xActionName
265                     = xDoc->createAttribute("name");
266                 xActionName->setValue("action");
267                 xActionAttr->setAttributeNode(xActionName);
268                 if (bChildDrawAsRadio)
269                     xActionAttr->appendChild(xDoc->createTextNode("menu.radio." + sId));
270                 else
271                     xActionAttr->appendChild(xDoc->createTextNode("menu.normal." + sId));
272                 xItem->appendChild(xActionAttr);
273 
274                 css::uno::Reference<css::xml::dom::XElement> xTargetAttr
275                     = xDoc->createElement("attribute");
276                 css::uno::Reference<css::xml::dom::XAttr> xTargetName
277                     = xDoc->createAttribute("name");
278                 xTargetName->setValue("target");
279                 xTargetAttr->setAttributeNode(xTargetName);
280                 xTargetAttr->appendChild(xDoc->createTextNode(sId));
281                 xItem->appendChild(xTargetAttr);
282             }
283         }
284 
285         xChild = xNextChild;
286     }
287 
288     return MenuEntry(bDrawAsRadio, xPropertyLabel);
289 }
290 
291 struct ConvertResult
292 {
293     bool m_bChildCanFocus;
294     bool m_bHasVisible;
295     bool m_bHasSymbolicIconName;
296     bool m_bAlwaysShowImage;
297     bool m_bUseUnderline;
298     bool m_bVertOrientation;
299     css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
300 
ConvertResult__anon7de393400111::ConvertResult301     ConvertResult(bool bChildCanFocus, bool bHasVisible, bool bHasSymbolicIconName,
302                   bool bAlwaysShowImage, bool bUseUnderline, bool bVertOrientation,
303                   const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel)
304         : m_bChildCanFocus(bChildCanFocus)
305         , m_bHasVisible(bHasVisible)
306         , m_bHasSymbolicIconName(bHasSymbolicIconName)
307         , m_bAlwaysShowImage(bAlwaysShowImage)
308         , m_bUseUnderline(bUseUnderline)
309         , m_bVertOrientation(bVertOrientation)
310         , m_xPropertyLabel(rPropertyLabel)
311     {
312     }
313 };
314 
IsAllowedBuiltInIcon(std::u16string_view iconName)315 bool IsAllowedBuiltInIcon(std::u16string_view iconName)
316 {
317     // limit the named icons to those known by VclBuilder
318     return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
319 }
320 
Convert3To4(const css::uno::Reference<css::xml::dom::XNode> & xNode)321 ConvertResult Convert3To4(const css::uno::Reference<css::xml::dom::XNode>& xNode)
322 {
323     css::uno::Reference<css::xml::dom::XNodeList> xNodeList = xNode->getChildNodes();
324     if (!xNodeList.is())
325         return ConvertResult(false, false, false, false, false, false, nullptr);
326 
327     std::vector<css::uno::Reference<css::xml::dom::XNode>> xRemoveList;
328 
329     OUString sBorderWidth;
330     bool bChildCanFocus = false;
331     bool bHasVisible = false;
332     bool bHasSymbolicIconName = false;
333     bool bAlwaysShowImage = false;
334     bool bUseUnderline = false;
335     bool bVertOrientation = false;
336     css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
337     css::uno::Reference<css::xml::dom::XNode> xCantFocus;
338 
339     css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
340     while (xChild.is())
341     {
342         if (xChild->getNodeName() == "requires")
343         {
344             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
345             css::uno::Reference<css::xml::dom::XNode> xLib = xMap->getNamedItem("lib");
346             assert(xLib->getNodeValue() == "gtk+");
347             xLib->setNodeValue("gtk");
348             css::uno::Reference<css::xml::dom::XNode> xVersion = xMap->getNamedItem("version");
349             assert(xVersion->getNodeValue() == "3.20");
350             xVersion->setNodeValue("4.0");
351         }
352         else if (xChild->getNodeName() == "property")
353         {
354             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
355             css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
356             OUString sName(xName->getNodeValue().replace('_', '-'));
357 
358             if (sName == "border-width")
359                 sBorderWidth = xChild->getFirstChild()->getNodeValue();
360 
361             if (sName == "has-default")
362             {
363                 css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
364                     = xChild->getParentNode()->getAttributes();
365                 css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
366                 auto xDoc = xChild->getOwnerDocument();
367                 auto xDefaultWidget = CreateProperty(xDoc, "default-widget", xId->getNodeValue());
368                 SetPropertyOnTopLevel(xChild, xDefaultWidget);
369                 xRemoveList.push_back(xChild);
370             }
371 
372             if (sName == "has-focus" || sName == "is-focus")
373             {
374                 css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
375                     = xChild->getParentNode()->getAttributes();
376                 css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
377                 auto xDoc = xChild->getOwnerDocument();
378                 auto xDefaultWidget = CreateProperty(xDoc, "focus-widget", xId->getNodeValue());
379                 SetPropertyOnTopLevel(xChild, xDefaultWidget);
380                 xRemoveList.push_back(xChild);
381             }
382 
383             if (sName == "can-focus")
384             {
385                 bChildCanFocus = toBool(xChild->getFirstChild()->getNodeValue());
386                 if (!bChildCanFocus)
387                 {
388                     OUString sParentClass = GetParentObjectType(xChild);
389                     if (sParentClass == "GtkBox" || sParentClass == "GtkGrid")
390                     {
391                         // e.g. for the case of notebooks without children yet, just remove the can't focus property
392                         // from Boxes and Grids
393                         xRemoveList.push_back(xChild);
394                     }
395                     else if (sParentClass == "GtkComboBoxText")
396                     {
397                         // this was always a bit finicky in gtk3, fix it up to default to can-focus
398                         xRemoveList.push_back(xChild);
399                     }
400                     else
401                     {
402                         // otherwise mark the property as needing removal if there turns out to be a child
403                         // with can-focus of true, in which case remove this parent conflicting property
404                         xCantFocus = xChild;
405                     }
406                 }
407             }
408 
409             if (sName == "label")
410                 xPropertyLabel = xChild;
411 
412             if (sName == "visible")
413                 bHasVisible = true;
414 
415             if (sName == "icon-name")
416             {
417                 OUString sIconName(xChild->getFirstChild()->getNodeValue());
418                 bHasSymbolicIconName = IsAllowedBuiltInIcon(sIconName);
419                 if (!bHasSymbolicIconName)
420                 {
421                     auto xDoc = xChild->getOwnerDocument();
422                     // private:graphicrepository/ would be turned by gio (?) into private:///graphicrepository/
423                     // so use private:///graphicrepository/ here. At the moment we just want this to be transported
424                     // as-is to postprocess_widget. Though it might be nice to register a protocol handler with gio
425                     // to avoid us doing the load in that second pass.
426                     auto xUri
427                         = CreateProperty(xDoc, "file", "private:///graphicrepository/" + sIconName);
428                     xChild->getParentNode()->insertBefore(xUri, xChild);
429                     xRemoveList.push_back(xChild);
430                 }
431             }
432 
433             if (sName == "events")
434                 xRemoveList.push_back(xChild);
435 
436             if (sName == "activates-default")
437             {
438                 if (GetParentObjectType(xChild) == "GtkSpinButton")
439                     xRemoveList.push_back(xChild);
440             }
441 
442             if (sName == "width-chars")
443             {
444                 if (GetParentObjectType(xChild) == "GtkEntry")
445                 {
446                     // I don't quite get what the difference should be wrt width-chars and max-width-chars
447                     // but glade doesn't write max-width-chars and in gtk4 where we have width-chars, e.g
448                     // print dialog, then max-width-chars gives the effect we wanted with width-chars
449                     auto xDoc = xChild->getOwnerDocument();
450                     auto mMaxWidthChars = CreateProperty(xDoc, "max-width-chars",
451                                                          xChild->getFirstChild()->getNodeValue());
452                     xChild->getParentNode()->insertBefore(mMaxWidthChars, xChild);
453                 }
454             }
455 
456             // remove 'Help' button label and replace with a help icon instead
457             if (sName == "label" && GetParentObjectType(xChild) == "GtkButton")
458             {
459                 css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
460                     = xChild->getParentNode()->getAttributes();
461                 css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
462                 if (xId && xId->getNodeValue() == "help")
463                 {
464                     auto xDoc = xChild->getOwnerDocument();
465                     auto xIconName = CreateProperty(xDoc, "icon-name", "help-browser-symbolic");
466                     xChild->getParentNode()->insertBefore(xIconName, xChild);
467                     xRemoveList.push_back(xChild);
468                 }
469             }
470 
471             if (sName == "icon-size")
472             {
473                 if (GetParentObjectType(xChild) == "GtkImage")
474                 {
475                     if (xChild->getFirstChild()->getNodeValue() == "6")
476                     {
477                         auto xDoc = xChild->getOwnerDocument();
478                         // convert old GTK_ICON_SIZE_DIALOG to new GTK_ICON_SIZE_LARGE
479                         auto xIconSize = CreateProperty(xDoc, "icon-size", "2");
480                         xChild->getParentNode()->insertBefore(xIconSize, xChild);
481                         xRemoveList.push_back(xChild);
482                     }
483                     else
484                         SAL_WARN("vcl.gtk", "what should we do with an icon-size of: "
485                                                 << xChild->getFirstChild()->getNodeValue());
486                 }
487                 else if (GetParentObjectType(xChild) == "GtkPicture")
488                     xRemoveList.push_back(xChild);
489             }
490 
491             if (sName == "truncate-multiline")
492             {
493                 if (GetParentObjectType(xChild) == "GtkSpinButton")
494                     xRemoveList.push_back(xChild);
495             }
496 
497             if (sName == "homogeneous")
498             {
499                 // e.g. the buttonbox in xml filter dialog
500                 if (GetParentObjectType(xChild) == "GtkButtonBox")
501                     xRemoveList.push_back(xChild);
502             }
503 
504             if (sName == "shadow-type")
505             {
506                 if (GetParentObjectType(xChild) == "GtkFrame")
507                     xRemoveList.push_back(xChild);
508                 else if (GetParentObjectType(xChild) == "GtkScrolledWindow")
509                 {
510                     bool bHasFrame = xChild->getFirstChild()->getNodeValue() != "none";
511                     auto xDoc = xChild->getOwnerDocument();
512                     auto xHasFrame = CreateProperty(
513                         xDoc, "has-frame", bHasFrame ? OUString("True") : OUString("False"));
514                     xChild->getParentNode()->insertBefore(xHasFrame, xChild);
515                     xRemoveList.push_back(xChild);
516                 }
517             }
518 
519             if (sName == "always-show-image")
520             {
521                 if (GetParentObjectType(xChild) == "GtkButton"
522                     || GetParentObjectType(xChild) == "GtkMenuButton"
523                     || GetParentObjectType(xChild) == "GtkToggleButton")
524                 {
525                     // we will turn always-show-image into a GtkBox child for
526                     // GtkButton and a GtkLabel child for the GtkBox and move
527                     // the label property into it.
528                     bAlwaysShowImage = toBool(xChild->getFirstChild()->getNodeValue());
529                     xRemoveList.push_back(xChild);
530                 }
531             }
532 
533             if (sName == "use-underline")
534                 bUseUnderline = toBool(xChild->getFirstChild()->getNodeValue());
535 
536             if (sName == "orientation")
537                 bVertOrientation = xChild->getFirstChild()->getNodeValue() == "vertical";
538 
539             if (sName == "relief")
540             {
541                 if (GetParentObjectType(xChild) == "GtkToggleButton"
542                     || GetParentObjectType(xChild) == "GtkMenuButton"
543                     || GetParentObjectType(xChild) == "GtkLinkButton"
544                     || GetParentObjectType(xChild) == "GtkButton")
545                 {
546                     assert(xChild->getFirstChild()->getNodeValue() == "none");
547                     auto xDoc = xChild->getOwnerDocument();
548                     auto xHasFrame = CreateProperty(xDoc, "has-frame", "False");
549                     xChild->getParentNode()->insertBefore(xHasFrame, xChild);
550                     xRemoveList.push_back(xChild);
551                 }
552             }
553 
554             if (sName == "xalign")
555             {
556                 if (GetParentObjectType(xChild) == "GtkLinkButton"
557                     || GetParentObjectType(xChild) == "GtkMenuButton"
558                     || GetParentObjectType(xChild) == "GtkButton")
559                 {
560                     // TODO expand into a GtkLabel child with alignment on that instead
561                     assert(xChild->getFirstChild()->getNodeValue() == "0");
562                     xRemoveList.push_back(xChild);
563                 }
564             }
565 
566             if (sName == "use-popover")
567             {
568                 if (GetParentObjectType(xChild) == "GtkMenuButton")
569                     xRemoveList.push_back(xChild);
570             }
571 
572             if (sName == "hscrollbar-policy")
573             {
574                 if (GetParentObjectType(xChild) == "GtkScrolledWindow")
575                 {
576                     if (xChild->getFirstChild()->getNodeValue() == "never")
577                     {
578                         auto xDoc = xChild->getOwnerDocument();
579                         auto xHasFrame = CreateProperty(xDoc, "propagate-natural-width", "True");
580                         xChild->getParentNode()->insertBefore(xHasFrame, xChild);
581                     }
582                 }
583             }
584 
585             if (sName == "vscrollbar-policy")
586             {
587                 if (GetParentObjectType(xChild) == "GtkScrolledWindow")
588                 {
589                     if (xChild->getFirstChild()->getNodeValue() == "never")
590                     {
591                         auto xDoc = xChild->getOwnerDocument();
592                         auto xHasFrame = CreateProperty(xDoc, "propagate-natural-height", "True");
593                         xChild->getParentNode()->insertBefore(xHasFrame, xChild);
594                     }
595                 }
596             }
597 
598             if (sName == "popup")
599             {
600                 if (GetParentObjectType(xChild) == "GtkMenuButton")
601                 {
602                     OUString sMenuName = xChild->getFirstChild()->getNodeValue();
603                     auto xDoc = xChild->getOwnerDocument();
604                     auto xPopover = CreateProperty(xDoc, "popover", sMenuName);
605                     xChild->getParentNode()->insertBefore(xPopover, xChild);
606                     xRemoveList.push_back(xChild);
607                 }
608             }
609 
610             if (sName == "image")
611             {
612                 if (GetParentObjectType(xChild) == "GtkButton"
613                     || GetParentObjectType(xChild) == "GtkMenuButton"
614                     || GetParentObjectType(xChild) == "GtkToggleButton")
615                 {
616                     // find the image object, expected to be a child of "interface"
617                     auto xObjectCandidate = xChild->getParentNode();
618                     if (xObjectCandidate->getNodeName() == "object")
619                     {
620                         OUString sImageId = xChild->getFirstChild()->getNodeValue();
621 
622                         css::uno::Reference<css::xml::dom::XNode> xRootCandidate
623                             = xChild->getParentNode();
624                         while (xRootCandidate)
625                         {
626                             if (xRootCandidate->getNodeName() == "interface")
627                                 break;
628                             xRootCandidate = xRootCandidate->getParentNode();
629                         }
630 
631                         css::uno::Reference<css::xml::dom::XNode> xImageNode;
632 
633                         for (auto xImageCandidate = xRootCandidate->getFirstChild();
634                              xImageCandidate.is();
635                              xImageCandidate = xImageCandidate->getNextSibling())
636                         {
637                             css::uno::Reference<css::xml::dom::XNamedNodeMap> xImageCandidateMap
638                                 = xImageCandidate->getAttributes();
639                             if (!xImageCandidateMap.is())
640                                 continue;
641                             css::uno::Reference<css::xml::dom::XNode> xId
642                                 = xImageCandidateMap->getNamedItem("id");
643                             if (xId && xId->getNodeValue() == sImageId)
644                             {
645                                 xImageNode = xImageCandidate;
646                                 break;
647                             }
648                         }
649 
650                         auto xDoc = xChild->getOwnerDocument();
651 
652                         if (GetParentObjectType(xChild) == "GtkButton"
653                             || GetParentObjectType(xChild) == "GtkToggleButton")
654                         {
655                             // relocate it to be a child of this GtkButton
656                             css::uno::Reference<css::xml::dom::XElement> xImageChild
657                                 = xDoc->createElement("child");
658                             xImageChild->appendChild(
659                                 xImageNode->getParentNode()->removeChild(xImageNode));
660                             xObjectCandidate->appendChild(xImageChild);
661                         }
662                         else if (GetParentObjectType(xChild) == "GtkMenuButton")
663                         {
664                             auto xProp = xImageNode->getFirstChild();
665                             while (xProp.is())
666                             {
667                                 if (xProp->getNodeName() == "property")
668                                 {
669                                     css::uno::Reference<css::xml::dom::XNamedNodeMap> xPropMap
670                                         = xProp->getAttributes();
671                                     css::uno::Reference<css::xml::dom::XNode> xPropName
672                                         = xPropMap->getNamedItem("name");
673                                     OUString sPropName(xPropName->getNodeValue().replace('_', '-'));
674                                     if (sPropName == "icon-name")
675                                     {
676                                         OUString sIconName(xProp->getFirstChild()->getNodeValue());
677                                         auto xIconName
678                                             = CreateProperty(xDoc, "icon-name", sIconName);
679                                         xObjectCandidate->insertBefore(xIconName, xChild);
680                                         break;
681                                     }
682                                 }
683 
684                                 xProp = xProp->getNextSibling();
685                             }
686                             xImageNode->getParentNode()->removeChild(xImageNode);
687                         }
688                     }
689 
690                     xRemoveList.push_back(xChild);
691                 }
692             }
693 
694             if (sName == "draw-indicator")
695             {
696                 assert(toBool(xChild->getFirstChild()->getNodeValue()));
697                 xRemoveList.push_back(xChild);
698             }
699 
700             if (sName == "type-hint" || sName == "skip-taskbar-hint" || sName == "can-default"
701                 || sName == "border-width" || sName == "layout-style" || sName == "no-show-all"
702                 || sName == "ignore-hidden" || sName == "window-position")
703             {
704                 xRemoveList.push_back(xChild);
705             }
706         }
707         else if (xChild->getNodeName() == "child")
708         {
709             bool bContentArea = false;
710 
711             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
712             css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("internal-child");
713             if (xName)
714             {
715                 OUString sName(xName->getNodeValue());
716                 if (sName == "vbox")
717                 {
718                     xName->setNodeValue("content_area");
719                     bContentArea = true;
720                 }
721                 else if (sName == "accessible")
722                 {
723                     // TODO what's the replacement for this going to be?
724                     xRemoveList.push_back(xChild);
725                 }
726             }
727 
728             if (bContentArea)
729             {
730                 css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xChild);
731                 if (xObject)
732                 {
733                     auto xDoc = xChild->getOwnerDocument();
734 
735                     auto xVExpand = CreateProperty(xDoc, "vexpand", "True");
736                     insertAsFirstChild(xObject, xVExpand);
737 
738                     if (!sBorderWidth.isEmpty())
739                     {
740                         AddBorderAsMargins(xObject, sBorderWidth);
741                         sBorderWidth.clear();
742                     }
743                 }
744             }
745         }
746         else if (xChild->getNodeName() == "packing")
747         {
748             // remove "packing" and if its grid packing insert a replacement "layout" into
749             // the associated "object"
750             auto xDoc = xChild->getOwnerDocument();
751             css::uno::Reference<css::xml::dom::XElement> xNew = xDoc->createElement("layout");
752 
753             bool bGridPacking = false;
754 
755             // iterate over all children and append them to the new element
756             for (css::uno::Reference<css::xml::dom::XNode> xCurrent = xChild->getFirstChild();
757                  xCurrent.is(); xCurrent = xChild->getFirstChild())
758             {
759                 css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xCurrent->getAttributes();
760                 if (xMap.is())
761                 {
762                     css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
763                     OUString sName(xName->getNodeValue().replace('_', '-'));
764                     if (sName == "left-attach")
765                     {
766                         xName->setNodeValue("column");
767                         bGridPacking = true;
768                     }
769                     else if (sName == "top-attach")
770                     {
771                         xName->setNodeValue("row");
772                         bGridPacking = true;
773                     }
774                     else if (sName == "width")
775                     {
776                         xName->setNodeValue("column-span");
777                         bGridPacking = true;
778                     }
779                     else if (sName == "height")
780                     {
781                         xName->setNodeValue("row-span");
782                         bGridPacking = true;
783                     }
784                     else if (sName == "secondary")
785                     {
786                         // turn parent tag of <child> into <child type="start">
787                         auto xParent = xChild->getParentNode();
788                         css::uno::Reference<css::xml::dom::XAttr> xTypeStart
789                             = xDoc->createAttribute("type");
790                         xTypeStart->setValue("start");
791                         css::uno::Reference<css::xml::dom::XElement> xElem(
792                             xParent, css::uno::UNO_QUERY_THROW);
793                         xElem->setAttributeNode(xTypeStart);
794                     }
795                     else if (sName == "pack-type")
796                     {
797                         // turn parent tag of <child> into <child type="start">
798                         auto xParent = xChild->getParentNode();
799 
800                         css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
801                             = xParent->getAttributes();
802                         css::uno::Reference<css::xml::dom::XNode> xParentType
803                             = xParentMap->getNamedItem("type");
804                         assert(!xParentType || xParentType->getNodeValue() == "titlebar");
805                         if (!xParentType)
806                         {
807                             css::uno::Reference<css::xml::dom::XAttr> xTypeStart
808                                 = xDoc->createAttribute("type");
809                             xTypeStart->setValue(xCurrent->getFirstChild()->getNodeValue());
810                             css::uno::Reference<css::xml::dom::XElement> xElem(
811                                 xParent, css::uno::UNO_QUERY_THROW);
812                             xElem->setAttributeNode(xTypeStart);
813                         }
814                     }
815                 }
816                 xNew->appendChild(xChild->removeChild(xCurrent));
817             }
818 
819             if (bGridPacking)
820             {
821                 // go back to parent and find the object child and insert this "layout" as a
822                 // new child of the object
823                 auto xParent = xChild->getParentNode();
824                 css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xParent);
825                 if (xObject)
826                     xObject->appendChild(xNew);
827             }
828 
829             xRemoveList.push_back(xChild);
830         }
831         else if (xChild->getNodeName() == "accessibility")
832         {
833             // TODO <relation type="labelled-by" target="pagenumcb"/> -> <relation name="labelled-by">pagenumcb</relation>
834             xRemoveList.push_back(xChild);
835         }
836         else if (xChild->getNodeName() == "accelerator")
837         {
838             // TODO is anything like this supported anymore in .ui files
839             xRemoveList.push_back(xChild);
840         }
841 
842         auto xNextChild = xChild->getNextSibling();
843 
844         if (xChild->getNodeName() == "object")
845         {
846             auto xDoc = xChild->getOwnerDocument();
847 
848             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
849             css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
850             OUString sClass(xClass->getNodeValue());
851 
852             if (sClass == "GtkMenu")
853             {
854                 css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
855                 OUString sId(xId->getNodeValue() + "-menu-model");
856 
857                 // <menu id='menubar'>
858                 css::uno::Reference<css::xml::dom::XElement> xMenu = xDoc->createElement("menu");
859                 css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id");
860                 xIdAttr->setValue(sId);
861                 xMenu->setAttributeNode(xIdAttr);
862                 xChild->getParentNode()->insertBefore(xMenu, xChild);
863 
864                 css::uno::Reference<css::xml::dom::XElement> xSection
865                     = xDoc->createElement("section");
866                 xMenu->appendChild(xSection);
867 
868                 ConvertMenu(xSection, xChild);
869 
870                 // now remove GtkMenu contents
871                 while (true)
872                 {
873                     auto xFirstChild = xChild->getFirstChild();
874                     if (!xFirstChild.is())
875                         break;
876                     xChild->removeChild(xFirstChild);
877                 }
878 
879                 // change to GtkPopoverMenu
880                 xClass->setNodeValue("GtkPopoverMenu");
881 
882                 // <property name="menu-model">
883                 xChild->appendChild(CreateProperty(xDoc, "menu-model", sId));
884                 xChild->appendChild(CreateProperty(xDoc, "visible", "False"));
885             }
886         }
887 
888         bool bChildHasSymbolicIconName = false;
889         bool bChildHasVisible = false;
890         bool bChildAlwaysShowImage = false;
891         bool bChildUseUnderline = false;
892         bool bChildVertOrientation = false;
893         css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
894         if (xChild->hasChildNodes())
895         {
896             auto aChildRes = Convert3To4(xChild);
897             bChildCanFocus |= aChildRes.m_bChildCanFocus;
898             if (bChildCanFocus && xCantFocus.is())
899             {
900                 xNode->removeChild(xCantFocus);
901                 xCantFocus.clear();
902             }
903             if (xChild->getNodeName() == "object")
904             {
905                 bChildHasVisible = aChildRes.m_bHasVisible;
906                 bChildHasSymbolicIconName = aChildRes.m_bHasSymbolicIconName;
907                 bChildAlwaysShowImage = aChildRes.m_bAlwaysShowImage;
908                 bChildUseUnderline = aChildRes.m_bUseUnderline;
909                 bChildVertOrientation = aChildRes.m_bVertOrientation;
910                 xChildPropertyLabel = aChildRes.m_xPropertyLabel;
911             }
912         }
913 
914         if (xChild->getNodeName() == "object")
915         {
916             auto xDoc = xChild->getOwnerDocument();
917 
918             css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
919             css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
920             OUString sClass(xClass->getNodeValue());
921 
922             auto xInternalChildCandidate = xChild->getParentNode();
923             css::uno::Reference<css::xml::dom::XNamedNodeMap> xInternalChildCandidateMap
924                 = xInternalChildCandidate->getAttributes();
925             css::uno::Reference<css::xml::dom::XNode> xInternalChild
926                 = xInternalChildCandidateMap->getNamedItem("internal-child");
927 
928             // turn default gtk3 invisibility for widget objects into explicit invisible, but ignore internal-children
929             if (!bChildHasVisible && !xInternalChild)
930             {
931                 if (sClass == "GtkBox" || sClass == "GtkButton" || sClass == "GtkCalendar"
932                     || sClass == "GtkCheckButton" || sClass == "GtkRadioButton"
933                     || sClass == "GtkComboBox" || sClass == "GtkComboBoxText"
934                     || sClass == "GtkDrawingArea" || sClass == "GtkEntry" || sClass == "GtkExpander"
935                     || sClass == "GtkFrame" || sClass == "GtkGrid" || sClass == "GtkImage"
936                     || sClass == "GtkLabel" || sClass == "GtkMenuButton" || sClass == "GtkNotebook"
937                     || sClass == "GtkOverlay" || sClass == "GtkPaned" || sClass == "GtkProgressBar"
938                     || sClass == "GtkScrolledWindow" || sClass == "GtkSeparator"
939                     || sClass == "GtkSpinButton" || sClass == "GtkSpinner"
940                     || sClass == "GtkTextView" || sClass == "GtkTreeView" || sClass == "GtkViewport"
941                     || sClass == "GtkLinkButton" || sClass == "GtkToggleButton"
942                     || sClass == "GtkButtonBox")
943 
944                 {
945                     auto xVisible = CreateProperty(xDoc, "visible", "False");
946                     insertAsFirstChild(xChild, xVisible);
947                 }
948             }
949 
950             if (sClass == "GtkButtonBox")
951             {
952                 if (xInternalChild && xInternalChild->getNodeValue() == "action_area"
953                     && !ToplevelIsMessageDialog(xChild))
954                 {
955                     xClass->setNodeValue("GtkHeaderBar");
956                     auto xSpacingNode = CreateProperty(xDoc, "show-title-buttons", "False");
957                     insertAsFirstChild(xChild, xSpacingNode);
958 
959                     // move the replacement GtkHeaderBar up to before the content_area
960                     auto xContentAreaCandidate = xChild->getParentNode();
961                     while (xContentAreaCandidate)
962                     {
963                         css::uno::Reference<css::xml::dom::XNamedNodeMap> xChildMap
964                             = xContentAreaCandidate->getAttributes();
965                         css::uno::Reference<css::xml::dom::XNode> xName
966                             = xChildMap->getNamedItem("internal-child");
967                         if (xName && xName->getNodeValue() == "content_area")
968                         {
969                             auto xActionArea = xChild->getParentNode();
970 
971                             xActionArea->getParentNode()->removeChild(xActionArea);
972 
973                             css::uno::Reference<css::xml::dom::XAttr> xTypeTitleBar
974                                 = xDoc->createAttribute("type");
975                             xTypeTitleBar->setValue("titlebar");
976                             css::uno::Reference<css::xml::dom::XElement> xElem(
977                                 xActionArea, css::uno::UNO_QUERY_THROW);
978                             xElem->setAttributeNode(xTypeTitleBar);
979                             xElem->removeAttribute("internal-child");
980 
981                             xContentAreaCandidate->getParentNode()->insertBefore(
982                                 xActionArea, xContentAreaCandidate);
983 
984                             std::vector<named_node> aChildren;
985 
986                             css::uno::Reference<css::xml::dom::XNode> xTitleChild
987                                 = xChild->getFirstChild();
988                             while (xTitleChild.is())
989                             {
990                                 auto xNextTitleChild = xTitleChild->getNextSibling();
991                                 if (xTitleChild->getNodeName() == "child")
992                                 {
993                                     OUString sNodeId;
994 
995                                     css::uno::Reference<css::xml::dom::XNode> xObject
996                                         = GetChildObject(xTitleChild);
997                                     if (xObject)
998                                     {
999                                         css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
1000                                             = xObject->getAttributes();
1001                                         css::uno::Reference<css::xml::dom::XNode> xObjectId
1002                                             = xObjectMap->getNamedItem("id");
1003                                         sNodeId = xObjectId->getNodeValue();
1004                                     }
1005 
1006                                     aChildren.push_back(std::make_pair(xTitleChild, sNodeId));
1007                                 }
1008                                 else if (xTitleChild->getNodeName() == "property")
1009                                 {
1010                                     // remove any <property name="homogeneous"> tag
1011                                     css::uno::Reference<css::xml::dom::XNamedNodeMap> xTitleChildMap
1012                                         = xTitleChild->getAttributes();
1013                                     css::uno::Reference<css::xml::dom::XNode> xPropName
1014                                         = xTitleChildMap->getNamedItem("name");
1015                                     OUString sPropName(xPropName->getNodeValue().replace('_', '-'));
1016                                     if (sPropName == "homogeneous")
1017                                         xChild->removeChild(xTitleChild);
1018                                 }
1019 
1020                                 xTitleChild = xNextTitleChild;
1021                             }
1022 
1023                             //sort child order within parent so that we match the platform button order
1024                             std::stable_sort(aChildren.begin(), aChildren.end(), sortButtonNodes);
1025 
1026                             int nNonHelpButtonCount = 0;
1027 
1028                             for (const auto& rTitleChild : aChildren)
1029                             {
1030                                 xChild->removeChild(rTitleChild.first);
1031                                 if (rTitleChild.second != "help")
1032                                     ++nNonHelpButtonCount;
1033                             }
1034 
1035                             std::reverse(aChildren.begin(), aChildren.end());
1036 
1037                             for (const auto& rTitleChild : aChildren)
1038                             {
1039                                 xChild->appendChild(rTitleChild.first);
1040 
1041                                 css::uno::Reference<css::xml::dom::XElement> xChildElem(
1042                                     rTitleChild.first, css::uno::UNO_QUERY_THROW);
1043                                 if (!xChildElem->hasAttribute("type"))
1044                                 {
1045                                     // turn parent tag of <child> into <child type="end"> except for cancel/close which we'll
1046                                     // put at start unless there is nothing at end
1047                                     css::uno::Reference<css::xml::dom::XAttr> xTypeEnd
1048                                         = xDoc->createAttribute("type");
1049                                     if (nNonHelpButtonCount >= 2
1050                                         && (rTitleChild.second == "cancel"
1051                                             || rTitleChild.second == "close"))
1052                                         xTypeEnd->setValue("start");
1053                                     else
1054                                         xTypeEnd->setValue("end");
1055                                     xChildElem->setAttributeNode(xTypeEnd);
1056                                 }
1057                             }
1058 
1059                             auto xUseHeaderBar = CreateProperty(xDoc, "use-header-bar", "1");
1060                             SetPropertyOnTopLevel(xContentAreaCandidate, xUseHeaderBar);
1061 
1062                             break;
1063                         }
1064                         xContentAreaCandidate = xContentAreaCandidate->getParentNode();
1065                     }
1066                 }
1067                 else // GtkMessageDialog
1068                     xClass->setNodeValue("GtkBox");
1069             }
1070             else if (sClass == "GtkBox")
1071             {
1072                 // reverse the order of the pack-type=end widgets
1073                 std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackEnds;
1074                 std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackStarts;
1075                 css::uno::Reference<css::xml::dom::XNode> xBoxChild = xChild->getFirstChild();
1076                 while (xBoxChild.is())
1077                 {
1078                     auto xNextBoxChild = xBoxChild->getNextSibling();
1079 
1080                     if (xBoxChild->getNodeName() == "child")
1081                     {
1082                         css::uno::Reference<css::xml::dom::XNamedNodeMap> xBoxChildMap
1083                             = xBoxChild->getAttributes();
1084                         css::uno::Reference<css::xml::dom::XNode> xType
1085                             = xBoxChildMap->getNamedItem("type");
1086                         if (xType && xType->getNodeValue() == "end")
1087                             aPackEnds.push_back(xChild->removeChild(xBoxChild));
1088                         else
1089                             aPackStarts.push_back(xBoxChild);
1090                     }
1091 
1092                     xBoxChild = xNextBoxChild;
1093                 }
1094 
1095                 if (!aPackEnds.empty())
1096                 {
1097                     std::reverse(aPackEnds.begin(), aPackEnds.end());
1098 
1099                     if (!bChildVertOrientation)
1100                     {
1101                         bool bHasStartObject = false;
1102                         bool bLastStartExpands = false;
1103                         if (!aPackStarts.empty())
1104                         {
1105                             css::uno::Reference<css::xml::dom::XNode> xLastStartObject;
1106                             for (auto it = aPackStarts.rbegin(); it != aPackStarts.rend(); ++it)
1107                             {
1108                                 xLastStartObject = GetChildObject(*it);
1109                                 if (xLastStartObject.is())
1110                                 {
1111                                     bHasStartObject = true;
1112                                     for (css::uno::Reference<css::xml::dom::XNode> xExpandCandidate
1113                                          = xLastStartObject->getFirstChild();
1114                                          xExpandCandidate.is();
1115                                          xExpandCandidate = xExpandCandidate->getNextSibling())
1116                                     {
1117                                         if (xExpandCandidate->getNodeName() == "property")
1118                                         {
1119                                             css::uno::Reference<css::xml::dom::XNamedNodeMap>
1120                                                 xExpandMap = xExpandCandidate->getAttributes();
1121                                             css::uno::Reference<css::xml::dom::XNode> xName
1122                                                 = xExpandMap->getNamedItem("name");
1123                                             OUString sPropName(xName->getNodeValue());
1124                                             if (sPropName == "hexpand")
1125                                             {
1126                                                 bLastStartExpands
1127                                                     = toBool(xExpandCandidate->getFirstChild()
1128                                                                  ->getNodeValue());
1129                                                 break;
1130                                             }
1131                                         }
1132                                     }
1133                                     break;
1134                                 }
1135                             }
1136                         }
1137 
1138                         if (bHasStartObject && !bLastStartExpands)
1139                         {
1140                             auto xAlign = CreateProperty(xDoc, "halign", "end");
1141                             insertAsFirstChild(GetChildObject(aPackEnds[0]), xAlign);
1142                             auto xExpand = CreateProperty(xDoc, "hexpand", "True");
1143                             insertAsFirstChild(GetChildObject(aPackEnds[0]), xExpand);
1144                         }
1145                     }
1146 
1147                     for (auto& xPackEnd : aPackEnds)
1148                         xChild->appendChild(xPackEnd);
1149                 }
1150             }
1151             else if (sClass == "GtkRadioButton")
1152             {
1153                 xClass->setNodeValue("GtkCheckButton");
1154             }
1155             else if (sClass == "GtkImage" && !bChildHasSymbolicIconName)
1156             {
1157                 xClass->setNodeValue("GtkPicture");
1158             }
1159             else if (sClass == "GtkPopover" && !bHasVisible)
1160             {
1161                 auto xVisible = CreateProperty(xDoc, "visible", "False");
1162                 insertAsFirstChild(xChild, xVisible);
1163             }
1164 
1165             // only create the child box for GtkButton/GtkToggleButton
1166             if (bChildAlwaysShowImage && sClass != "GtkMenuButton")
1167             {
1168                 auto xImageCandidateNode = xChild->getLastChild();
1169                 if (xImageCandidateNode && xImageCandidateNode->getNodeName() != "child")
1170                     xImageCandidateNode.clear();
1171                 if (xImageCandidateNode)
1172                     xChild->removeChild(xImageCandidateNode);
1173 
1174                 css::uno::Reference<css::xml::dom::XElement> xNewChildNode
1175                     = xDoc->createElement("child");
1176                 css::uno::Reference<css::xml::dom::XElement> xNewObjectNode
1177                     = xDoc->createElement("object");
1178                 css::uno::Reference<css::xml::dom::XAttr> xBoxClassName
1179                     = xDoc->createAttribute("class");
1180                 xBoxClassName->setValue("GtkBox");
1181                 xNewObjectNode->setAttributeNode(xBoxClassName);
1182 
1183                 auto xSpacing = CreateProperty(xDoc, "spacing", "6");
1184                 xNewObjectNode->appendChild(xSpacing);
1185 
1186                 xNewChildNode->appendChild(xNewObjectNode);
1187 
1188                 xChild->appendChild(xNewChildNode);
1189 
1190                 css::uno::Reference<css::xml::dom::XElement> xNewLabelChildNode
1191                     = xDoc->createElement("child");
1192                 css::uno::Reference<css::xml::dom::XElement> xNewChildObjectNode
1193                     = xDoc->createElement("object");
1194                 css::uno::Reference<css::xml::dom::XAttr> xLabelClassName
1195                     = xDoc->createAttribute("class");
1196                 xLabelClassName->setValue("GtkLabel");
1197                 xNewChildObjectNode->setAttributeNode(xLabelClassName);
1198                 if (xChildPropertyLabel)
1199                 {
1200                     xNewChildObjectNode->appendChild(
1201                         xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel));
1202                 }
1203                 else
1204                 {
1205                     auto xNotVisible = CreateProperty(xDoc, "visible", "False");
1206                     xNewChildObjectNode->appendChild(xNotVisible);
1207                 }
1208                 if (bChildUseUnderline)
1209                 {
1210                     auto xUseUnderline = CreateProperty(xDoc, "use-underline", "True");
1211                     xNewChildObjectNode->appendChild(xUseUnderline);
1212                 }
1213                 xNewLabelChildNode->appendChild(xNewChildObjectNode);
1214 
1215                 if (xImageCandidateNode)
1216                     xNewObjectNode->appendChild(xImageCandidateNode);
1217                 xNewObjectNode->appendChild(xNewLabelChildNode);
1218             }
1219         }
1220 
1221         xChild = xNextChild;
1222     }
1223 
1224     if (!sBorderWidth.isEmpty())
1225         AddBorderAsMargins(xNode, sBorderWidth);
1226 
1227     for (auto& xRemove : xRemoveList)
1228         xNode->removeChild(xRemove);
1229 
1230     return ConvertResult(bChildCanFocus, bHasVisible, bHasSymbolicIconName, bAlwaysShowImage,
1231                          bUseUnderline, bVertOrientation, xPropertyLabel);
1232 }
1233 }
1234 
builder_add_from_gtk3_file(GtkBuilder * pBuilder,const OUString & rUri)1235 void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri)
1236 {
1237     GError* err = nullptr;
1238 
1239     // load the xml
1240     css::uno::Reference<css::uno::XComponentContext> xContext
1241         = ::comphelper::getProcessComponentContext();
1242     css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder
1243         = css::xml::dom::DocumentBuilder::create(xContext);
1244     css::uno::Reference<css::xml::dom::XDocument> xDocument = xBuilder->parseURI(rUri);
1245 
1246     // convert it from gtk3 to gtk4
1247     Convert3To4(xDocument);
1248 
1249     css::uno::Reference<css::beans::XPropertySet> xTempFile(css::io::TempFile::create(xContext),
1250                                                             css::uno::UNO_QUERY_THROW);
1251     css::uno::Reference<css::io::XStream> xTempStream(xTempFile, css::uno::UNO_QUERY_THROW);
1252     xTempFile->setPropertyValue("RemoveFile", css::uno::makeAny(false));
1253 
1254     // serialize it back to xml
1255     css::uno::Reference<css::xml::sax::XSAXSerializable> xSerializer(xDocument,
1256                                                                      css::uno::UNO_QUERY_THROW);
1257     css::uno::Reference<css::xml::sax::XWriter> xWriter = css::xml::sax::Writer::create(xContext);
1258     css::uno::Reference<css::io::XOutputStream> xTempOut = xTempStream->getOutputStream();
1259     xWriter->setOutputStream(xTempOut);
1260     xSerializer->serialize(
1261         css::uno::Reference<css::xml::sax::XDocumentHandler>(xWriter, css::uno::UNO_QUERY_THROW),
1262         css::uno::Sequence<css::beans::StringPair>());
1263 
1264     // feed it to GtkBuilder
1265     css::uno::Reference<css::io::XSeekable> xTempSeek(xTempStream, css::uno::UNO_QUERY_THROW);
1266     xTempSeek->seek(0);
1267     auto xInput = xTempStream->getInputStream();
1268     css::uno::Sequence<sal_Int8> bytes;
1269     sal_Int32 nToRead = xInput->available();
1270     while (true)
1271     {
1272         sal_Int32 nRead = xInput->readBytes(bytes, std::max<sal_Int32>(nToRead, 4096));
1273         if (!nRead)
1274             break;
1275         // fprintf(stderr, "text is %s\n", reinterpret_cast<const gchar*>(bytes.getArray()));
1276         auto rc = gtk_builder_add_from_string(
1277             pBuilder, reinterpret_cast<const gchar*>(bytes.getArray()), nRead, &err);
1278         if (!rc)
1279         {
1280             SAL_WARN("vcl.gtk",
1281                      "GtkInstanceBuilder: error when calling gtk_builder_add_from_string: "
1282                          << err->message);
1283             g_error_free(err);
1284         }
1285         assert(rc && "could not load UI file");
1286         // in the real world the first loop has read the entire file because its all 'available' without blocking
1287     }
1288 }
1289 
1290 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1291