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