xref: /core/vcl/source/window/builder.cxx (revision 40dde438)
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 <config_feature_desktop.h>
11 #include <config_options.h>
12 #include <config_vclplug.h>
13 
14 #include <memory>
15 #include <string_view>
16 #include <unordered_map>
17 #include <com/sun/star/accessibility/AccessibleRole.hpp>
18 
19 #include <comphelper/lok.hxx>
20 #include <i18nutil/unicode.hxx>
21 #include <jsdialog/enabled.hxx>
22 #include <o3tl/string_view.hxx>
23 #include <officecfg/Office/Common.hxx>
24 #include <osl/module.hxx>
25 #include <sal/log.hxx>
26 #include <unotools/localedatawrapper.hxx>
27 #include <unotools/resmgr.hxx>
28 #include <utility>
29 #include <vcl/builder.hxx>
30 #include <vcl/dialoghelper.hxx>
31 #include <vcl/menu.hxx>
32 #include <vcl/toolkit/button.hxx>
33 #include <vcl/toolkit/dialog.hxx>
34 #include <vcl/toolkit/edit.hxx>
35 #include <vcl/toolkit/field.hxx>
36 #include <vcl/fieldvalues.hxx>
37 #include <vcl/toolkit/fmtfield.hxx>
38 #include <vcl/toolkit/fixed.hxx>
39 #include <vcl/toolkit/fixedhyper.hxx>
40 #include <vcl/headbar.hxx>
41 #include <vcl/notebookbar/NotebookBarAddonsMerger.hxx>
42 #include <vcl/toolkit/ivctrl.hxx>
43 #include <vcl/layout.hxx>
44 #include <vcl/toolkit/lstbox.hxx>
45 #include <vcl/toolkit/menubtn.hxx>
46 #include <vcl/mnemonic.hxx>
47 #include <vcl/toolkit/prgsbar.hxx>
48 #include <vcl/toolkit/scrbar.hxx>
49 #include <vcl/split.hxx>
50 #include <vcl/svapp.hxx>
51 #include <vcl/toolkit/svtabbx.hxx>
52 #include <vcl/tabctrl.hxx>
53 #include <vcl/tabpage.hxx>
54 #include <vcl/toolkit/throbber.hxx>
55 #include <vcl/toolbox.hxx>
56 #include <vcl/toolkit/treelistentry.hxx>
57 #include <vcl/toolkit/vclmedit.hxx>
58 #include <vcl/settings.hxx>
59 #include <slider.hxx>
60 #include <vcl/weld.hxx>
61 #include <vcl/weldutils.hxx>
62 #include <vcl/commandinfoprovider.hxx>
63 #include <iconview.hxx>
64 #include <svdata.hxx>
65 #include <bitmaps.hlst>
66 #include <managedmenubutton.hxx>
67 #include <messagedialog.hxx>
68 #include <ContextVBox.hxx>
69 #include <DropdownBox.hxx>
70 #include <IPrioritable.hxx>
71 #include <OptionalBox.hxx>
72 #include <PriorityMergedHBox.hxx>
73 #include <PriorityHBox.hxx>
74 #include <window.h>
75 #include <xmlreader/xmlreader.hxx>
76 #include <desktop/crashreport.hxx>
77 #include <calendar.hxx>
78 #include <menutogglebutton.hxx>
79 #include <salinst.hxx>
80 #include <strings.hrc>
81 #include <treeglue.hxx>
82 #include <comphelper/diagnose_ex.hxx>
83 #include <verticaltabctrl.hxx>
84 #include <wizdlg.hxx>
85 #include <tools/svlibrary.h>
86 #include <jsdialog/jsdialogbuilder.hxx>
87 
88 #if defined(DISABLE_DYNLOADING) || defined(LINUX)
89 #include <dlfcn.h>
90 #endif
91 
toBool(std::string_view rValue)92 static bool toBool(std::string_view rValue)
93 {
94     return (!rValue.empty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1'));
95 }
96 
97 namespace
98 {
mapStockToImageResource(std::u16string_view sType)99     OUString mapStockToImageResource(std::u16string_view sType)
100     {
101         if (sType == u"view-refresh")
102             return SV_RESID_BITMAP_REFRESH;
103         else if (sType == u"dialog-error")
104             return IMG_ERROR;
105         else if (sType == u"list-add")
106             return IMG_ADD;
107         else if (sType == u"list-remove")
108             return IMG_REMOVE;
109         else if (sType == u"edit-copy")
110             return IMG_COPY;
111         else if (sType == u"edit-paste")
112             return IMG_PASTE;
113         else if (sType == u"document-open")
114             return IMG_OPEN;
115         else if (sType == u"open-menu-symbolic")
116             return IMG_MENU;
117         else if (sType == u"window-close-symbolic")
118             return SV_RESID_BITMAP_CLOSEDOC;
119         else if (sType == u"x-office-calendar")
120             return IMG_CALENDAR;
121         else if (sType == u"accessories-character-map")
122             return IMG_CHARACTER_MAP;
123         return OUString();
124     }
125 
126 }
127 
mapStockToSymbol(std::u16string_view sType)128 SymbolType VclBuilder::mapStockToSymbol(std::u16string_view sType)
129 {
130     SymbolType eRet = SymbolType::DONTKNOW;
131     if (sType == u"media-skip-forward")
132         eRet = SymbolType::NEXT;
133     else if (sType == u"media-skip-backward")
134         eRet = SymbolType::PREV;
135     else if (sType == u"media-playback-start")
136         eRet = SymbolType::PLAY;
137     else if (sType == u"media-playback-stop")
138         eRet = SymbolType::STOP;
139     else if (sType == u"go-first")
140         eRet = SymbolType::FIRST;
141     else if (sType == u"go-last")
142         eRet = SymbolType::LAST;
143     else if (sType == u"go-previous")
144         eRet = SymbolType::ARROW_LEFT;
145     else if (sType == u"go-next")
146         eRet = SymbolType::ARROW_RIGHT;
147     else if (sType == u"go-up")
148         eRet = SymbolType::ARROW_UP;
149     else if (sType == u"go-down")
150         eRet = SymbolType::ARROW_DOWN;
151     else if (sType == u"missing-image")
152         eRet = SymbolType::IMAGE;
153     else if (sType == u"help-browser" || sType == u"help-browser-symbolic")
154         eRet = SymbolType::HELP;
155     else if (sType == u"window-close")
156         eRet = SymbolType::CLOSE;
157     else if (sType == u"document-new")
158         eRet = SymbolType::PLUS;
159     else if (sType == u"pan-down-symbolic")
160         eRet = SymbolType::SPIN_DOWN;
161     else if (sType == u"pan-up-symbolic")
162         eRet = SymbolType::SPIN_UP;
163     else if (!mapStockToImageResource(sType).isEmpty())
164         eRet = SymbolType::IMAGE;
165     return eRet;
166 }
167 
168 namespace
169 {
170     void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame);
171 
172 #if defined SAL_LOG_WARN
isButtonType(WindowType nType)173     bool isButtonType(WindowType nType)
174     {
175         return nType == WindowType::PUSHBUTTON ||
176                nType == WindowType::OKBUTTON ||
177                nType == WindowType::CANCELBUTTON ||
178                nType == WindowType::HELPBUTTON ||
179                nType == WindowType::IMAGEBUTTON ||
180                nType == WindowType::MENUBUTTON ||
181                nType == WindowType::MOREBUTTON ||
182                nType == WindowType::SPINBUTTON;
183     }
184 #endif
185 
186 }
187 
CreateBuilder(weld::Widget * pParent,const OUString & rUIFile,bool bMobile,sal_uInt64 nLOKWindowId)188 std::unique_ptr<weld::Builder> Application::CreateBuilder(weld::Widget* pParent, const OUString &rUIFile, bool bMobile, sal_uInt64 nLOKWindowId)
189 {
190     if (comphelper::LibreOfficeKit::isActive())
191     {
192         if (jsdialog::isBuilderEnabledForSidebar(rUIFile))
193             return JSInstanceBuilder::CreateSidebarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, nLOKWindowId);
194         else if (jsdialog::isBuilderEnabledForPopup(rUIFile))
195             return JSInstanceBuilder::CreatePopupBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile);
196         else if (jsdialog::isBuilderEnabled(rUIFile, bMobile))
197             return JSInstanceBuilder::CreateDialogBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile);
198     }
199 
200     return ImplGetSVData()->mpDefInst->CreateBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile);
201 }
202 
CreateInterimBuilder(vcl::Window * pParent,const OUString & rUIFile,bool bAllowCycleFocusOut,sal_uInt64 nLOKWindowId)203 std::unique_ptr<weld::Builder> Application::CreateInterimBuilder(vcl::Window* pParent, const OUString &rUIFile, bool bAllowCycleFocusOut, sal_uInt64 nLOKWindowId)
204 {
205     if (comphelper::LibreOfficeKit::isActive())
206     {
207         // Notebookbar sub controls
208         if (jsdialog::isInterimBuilderEnabledForNotebookbar(rUIFile))
209             return JSInstanceBuilder::CreateNotebookbarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, css::uno::Reference<css::frame::XFrame>(), nLOKWindowId);
210         else if (rUIFile == u"modules/scalc/ui/inputbar.ui")
211             return JSInstanceBuilder::CreateFormulabarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, nLOKWindowId);
212     }
213 
214     return ImplGetSVData()->mpDefInst->CreateInterimBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, bAllowCycleFocusOut, nLOKWindowId);
215 }
216 
CreateMessageDialog(weld::Widget * pParent,VclMessageType eMessageType,VclButtonsType eButtonType,const OUString & rPrimaryMessage,const ILibreOfficeKitNotifier * pNotifier)217 weld::MessageDialog* Application::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
218                                                       VclButtonsType eButtonType, const OUString& rPrimaryMessage,
219                                                       const ILibreOfficeKitNotifier* pNotifier)
220 {
221     if (comphelper::LibreOfficeKit::isActive())
222         return JSInstanceBuilder::CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage, pNotifier);
223     else
224         return ImplGetSVData()->mpDefInst->CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage);
225 }
226 
GetFrameWeld(const css::uno::Reference<css::awt::XWindow> & rWindow)227 weld::Window* Application::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
228 {
229     return ImplGetSVData()->mpDefInst->GetFrameWeld(rWindow);
230 }
231 
232 namespace weld
233 {
MetricToString(FieldUnit rUnit)234     OUString MetricSpinButton::MetricToString(FieldUnit rUnit)
235     {
236         const FieldUnitStringList& rList = ImplGetFieldUnits();
237         // return unit's default string (ie, the first one )
238         auto it = std::find_if(
239             rList.begin(), rList.end(),
240             [&rUnit](const std::pair<OUString, FieldUnit>& rItem) { return rItem.second == rUnit; });
241         if (it != rList.end())
242             return it->first;
243 
244         return OUString();
245     }
246 
IMPL_LINK_NOARG(MetricSpinButton,spin_button_value_changed,SpinButton &,void)247     IMPL_LINK_NOARG(MetricSpinButton, spin_button_value_changed, SpinButton&, void)
248     {
249         signal_value_changed();
250     }
251 
IMPL_LINK(MetricSpinButton,spin_button_output,SpinButton &,rSpinButton,void)252     IMPL_LINK(MetricSpinButton, spin_button_output, SpinButton&, rSpinButton, void)
253     {
254         OUString sNewText(format_number(rSpinButton.get_value()));
255         if (sNewText != rSpinButton.get_text())
256             rSpinButton.set_text(sNewText);
257     }
258 
update_width_chars()259     void MetricSpinButton::update_width_chars()
260     {
261         sal_Int64 min, max;
262         m_xSpinButton->get_range(min, max);
263         auto width = std::max(m_xSpinButton->get_pixel_size(format_number(min)).Width(),
264                               m_xSpinButton->get_pixel_size(format_number(max)).Width());
265         int chars = ceil(width / m_xSpinButton->get_approximate_digit_width());
266         m_xSpinButton->set_width_chars(chars);
267     }
268 
Power10(unsigned int n)269     unsigned int SpinButton::Power10(unsigned int n)
270     {
271         unsigned int nValue = 1;
272         for (unsigned int i = 0; i < n; ++i)
273             nValue *= 10;
274         return nValue;
275     }
276 
denormalize(sal_Int64 nValue) const277     sal_Int64 SpinButton::denormalize(sal_Int64 nValue) const
278     {
279         const int nFactor = Power10(get_digits());
280 
281         if ((nValue < (std::numeric_limits<sal_Int64>::min() + nFactor)) ||
282             (nValue > (std::numeric_limits<sal_Int64>::max() - nFactor)))
283         {
284             return nValue / nFactor;
285         }
286 
287         const int nHalf = nFactor / 2;
288 
289         if (nValue < 0)
290             return (nValue - nHalf) / nFactor;
291         return (nValue + nHalf) / nFactor;
292     }
293 
format_number(sal_Int64 nValue) const294     OUString MetricSpinButton::format_number(sal_Int64 nValue) const
295     {
296         OUString aStr;
297 
298         const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
299 
300         unsigned int nDecimalDigits = m_xSpinButton->get_digits();
301         //pawn percent off to icu to decide whether percent is separated from its number for this locale
302         if (m_eSrcUnit == FieldUnit::PERCENT)
303         {
304             double fValue = nValue;
305             fValue /= SpinButton::Power10(nDecimalDigits);
306             aStr = unicode::formatPercent(fValue, rLocaleData.getLanguageTag());
307         }
308         else
309         {
310             aStr = rLocaleData.getNum(nValue, nDecimalDigits, true, true);
311             OUString aSuffix = MetricToString(m_eSrcUnit);
312             if (m_eSrcUnit != FieldUnit::NONE && m_eSrcUnit != FieldUnit::DEGREE && m_eSrcUnit != FieldUnit::INCH && m_eSrcUnit != FieldUnit::FOOT)
313                 aStr += " ";
314             if (m_eSrcUnit == FieldUnit::INCH)
315             {
316                 OUString sDoublePrime = u"\u2033"_ustr;
317                 if (aSuffix != "\"" && aSuffix != sDoublePrime)
318                     aStr += " ";
319                 else
320                     aSuffix = sDoublePrime;
321             }
322             else if (m_eSrcUnit == FieldUnit::FOOT)
323             {
324                 OUString sPrime = u"\u2032"_ustr;
325                 if (aSuffix != "'" && aSuffix != sPrime)
326                     aStr += " ";
327                 else
328                     aSuffix = sPrime;
329             }
330 
331             assert(m_eSrcUnit != FieldUnit::PERCENT);
332             aStr += aSuffix;
333         }
334 
335         return aStr;
336     }
337 
set_digits(unsigned int digits)338     void MetricSpinButton::set_digits(unsigned int digits)
339     {
340         int step, page;
341         get_increments(step, page, m_eSrcUnit);
342         sal_Int64 value = get_value(m_eSrcUnit);
343         m_xSpinButton->set_digits(digits);
344         set_increments(step, page, m_eSrcUnit);
345         set_value(value, m_eSrcUnit);
346         update_width_chars();
347     }
348 
set_unit(FieldUnit eUnit)349     void MetricSpinButton::set_unit(FieldUnit eUnit)
350     {
351         if (eUnit != m_eSrcUnit)
352         {
353             int step, page;
354             get_increments(step, page, m_eSrcUnit);
355             sal_Int64 value = get_value(m_eSrcUnit);
356             m_eSrcUnit = eUnit;
357             set_increments(step, page, m_eSrcUnit);
358             set_value(value, m_eSrcUnit);
359             spin_button_output(*m_xSpinButton);
360             update_width_chars();
361         }
362     }
363 
ConvertValue(sal_Int64 nValue,FieldUnit eInUnit,FieldUnit eOutUnit) const364     sal_Int64 MetricSpinButton::ConvertValue(sal_Int64 nValue, FieldUnit eInUnit, FieldUnit eOutUnit) const
365     {
366         return vcl::ConvertValue(nValue, 0, m_xSpinButton->get_digits(), eInUnit, eOutUnit);
367     }
368 
IMPL_LINK(MetricSpinButton,spin_button_input,int *,result,bool)369     IMPL_LINK(MetricSpinButton, spin_button_input, int*, result, bool)
370     {
371         const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
372         double fResult(0.0);
373         bool bRet = vcl::TextToValue(get_text(), fResult, 0, m_xSpinButton->get_digits(), rLocaleData, m_eSrcUnit);
374         if (bRet)
375         {
376             if (fResult > SAL_MAX_INT32)
377                 fResult = SAL_MAX_INT32;
378             else if (fResult < SAL_MIN_INT32)
379                 fResult = SAL_MIN_INT32;
380             *result = fResult;
381         }
382         return bRet;
383     }
384 
EntryTreeView(std::unique_ptr<Entry> xEntry,std::unique_ptr<TreeView> xTreeView)385     EntryTreeView::EntryTreeView(std::unique_ptr<Entry> xEntry, std::unique_ptr<TreeView> xTreeView)
386         : m_xEntry(std::move(xEntry))
387         , m_xTreeView(std::move(xTreeView))
388     {
389         m_xTreeView->connect_changed(LINK(this, EntryTreeView, ClickHdl));
390         m_xEntry->connect_changed(LINK(this, EntryTreeView, ModifyHdl));
391     }
392 
IMPL_LINK(EntryTreeView,ClickHdl,weld::TreeView &,rView,void)393     IMPL_LINK(EntryTreeView, ClickHdl, weld::TreeView&, rView, void)
394     {
395         m_xEntry->set_text(rView.get_selected_text());
396         m_aChangeHdl.Call(*this);
397     }
398 
IMPL_LINK_NOARG(EntryTreeView,ModifyHdl,weld::Entry &,void)399     IMPL_LINK_NOARG(EntryTreeView, ModifyHdl, weld::Entry&, void)
400     {
401         m_aChangeHdl.Call(*this);
402     }
403 
set_height_request_by_rows(int nRows)404     void EntryTreeView::set_height_request_by_rows(int nRows)
405     {
406         int nHeight = nRows == -1 ? -1 : m_xTreeView->get_height_rows(nRows);
407         m_xTreeView->set_size_request(m_xTreeView->get_size_request().Width(), nHeight);
408     }
409 
GetAbsPos(const weld::TreeView & rTreeView,const weld::TreeIter & rIter)410     size_t GetAbsPos(const weld::TreeView& rTreeView, const weld::TreeIter& rIter)
411     {
412         size_t nAbsPos = 0;
413 
414         std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter));
415         if (!rTreeView.get_iter_first(*xEntry))
416             xEntry.reset();
417 
418         while (xEntry && rTreeView.iter_compare(*xEntry, rIter) != 0)
419         {
420             if (!rTreeView.iter_next(*xEntry))
421                 xEntry.reset();
422             nAbsPos++;
423         }
424 
425         return nAbsPos;
426     }
427 
IsEntryVisible(const weld::TreeView & rTreeView,const weld::TreeIter & rIter)428     bool IsEntryVisible(const weld::TreeView& rTreeView, const weld::TreeIter& rIter)
429     {
430         // short circuit for the common case
431         if (rTreeView.get_iter_depth(rIter) == 0)
432             return true;
433 
434         std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter));
435         bool bRetVal = false;
436         do
437         {
438             if (rTreeView.get_iter_depth(*xEntry) == 0)
439             {
440                 bRetVal = true;
441                 break;
442             }
443         }  while (rTreeView.iter_parent(*xEntry) && rTreeView.get_row_expanded(*xEntry));
444         return bRetVal;
445     }
446 }
447 
VclBuilder(vcl::Window * pParent,const OUString & sUIDir,const OUString & sUIFile,OUString sID,css::uno::Reference<css::frame::XFrame> xFrame,bool bLegacy,const NotebookBarAddonsItem * pNotebookBarAddonsItem)448 VclBuilder::VclBuilder(vcl::Window* pParent, const OUString& sUIDir, const OUString& sUIFile,
449                        OUString sID, css::uno::Reference<css::frame::XFrame> xFrame,
450                        bool bLegacy, const NotebookBarAddonsItem* pNotebookBarAddonsItem)
451     : m_pNotebookBarAddonsItem(pNotebookBarAddonsItem
452                                    ? new NotebookBarAddonsItem(*pNotebookBarAddonsItem)
453                                    : new NotebookBarAddonsItem{})
454     , m_sID(std::move(sID))
455     , m_sHelpRoot(sUIFile)
456     , m_pStringReplace(Translate::GetReadStringHook())
457     , m_pParent(pParent)
458     , m_bToplevelParentFound(false)
459     , m_bLegacy(bLegacy)
460     , m_pParserState(new ParserState)
461     , m_xFrame(std::move(xFrame))
462 {
463     m_bToplevelHasDeferredInit = pParent &&
464         ((pParent->IsSystemWindow() && static_cast<SystemWindow*>(pParent)->isDeferredInit()) ||
465          (pParent->IsDockingWindow() && static_cast<DockingWindow*>(pParent)->isDeferredInit()));
466     m_bToplevelHasDeferredProperties = m_bToplevelHasDeferredInit;
467 
468     sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.');
469     if (nIdx != -1)
470         m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
471     m_sHelpRoot += "/";
472 
473     try
474     {
475         xmlreader::XmlReader reader(sUIDir + sUIFile);
476 
477         handleChild(pParent, nullptr, reader);
478     }
479     catch (const css::uno::Exception &rExcept)
480     {
481         TOOLS_WARN_EXCEPTION("vcl.builder", "Unable to read .ui file " << sUIDir << sUIFile);
482         CrashReporter::addKeyValue(u"VclBuilderException"_ustr, "Unable to read .ui file: " + rExcept.Message, CrashReporter::Write);
483         assert(false && "missing ui file or missing gb_CppunitTest_use_uiconfigs dependency");
484         throw;
485     }
486 
487     //Set Mnemonic widgets when everything has been imported
488     for (auto const& mnemonicWidget : m_pParserState->m_aMnemonicWidgetMaps)
489     {
490         FixedText *pOne = get<FixedText>(mnemonicWidget.m_sID);
491         vcl::Window *pOther = get(mnemonicWidget.m_sValue);
492         SAL_WARN_IF(!pOne || !pOther, "vcl", "missing either source " << mnemonicWidget.m_sID
493             << " or target " << mnemonicWidget.m_sValue << " member of Mnemonic Widget Mapping");
494         if (pOne && pOther)
495             pOne->set_mnemonic_widget(pOther);
496     }
497 
498     //Set a11y relations and role when everything has been imported
499     for (auto const& elemAtk : m_pParserState->m_aAtkInfo)
500     {
501         vcl::Window *pSource = elemAtk.first;
502         const stringmap &rMap = elemAtk.second;
503 
504         for (auto const& [ rType, rParam ] : rMap)
505         {
506             if (rType == "role")
507             {
508                 sal_Int16 role = BuilderUtils::getRoleFromName(rParam);
509                 if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
510                     pSource->SetAccessibleRole(role);
511             }
512             else
513             {
514                 vcl::Window *pTarget = get(rParam);
515                 SAL_WARN_IF(!pTarget, "vcl", "missing parameter of a11y relation: " << rParam);
516                 if (!pTarget)
517                     continue;
518                 if (rType == "labelled-by")
519                     pSource->SetAccessibleRelationLabeledBy(pTarget);
520                 else if (rType == "label-for")
521                     pSource->SetAccessibleRelationLabelFor(pTarget);
522                 else
523                 {
524                     SAL_WARN("vcl.builder", "unhandled a11y relation :" << rType);
525                 }
526             }
527         }
528     }
529 
530     //Set radiobutton groups when everything has been imported
531     for (auto const& elem : m_pParserState->m_aGroupMaps)
532     {
533         RadioButton *pOne = get<RadioButton>(elem.m_sID);
534         RadioButton *pOther = get<RadioButton>(elem.m_sValue);
535         SAL_WARN_IF(!pOne || !pOther, "vcl", "missing member of radiobutton group");
536         if (pOne && pOther)
537         {
538             if (m_bLegacy)
539                 pOne->group(*pOther);
540             else
541             {
542                 pOther->group(*pOne);
543                 std::stable_sort(pOther->m_xGroup->begin(), pOther->m_xGroup->end(), sortIntoBestTabTraversalOrder(this));
544             }
545         }
546     }
547 
548 #ifndef NDEBUG
549     o3tl::sorted_vector<OUString> models;
550 #endif
551     //Set ComboBox models when everything has been imported
552     for (auto const& elem : m_pParserState->m_aModelMaps)
553     {
554         assert(models.insert(elem.m_sValue).second && "a liststore or treestore is used in duplicate widgets");
555         vcl::Window* pTarget = get(elem.m_sID);
556         ListBox *pListBoxTarget = dynamic_cast<ListBox*>(pTarget);
557         ComboBox *pComboBoxTarget = dynamic_cast<ComboBox*>(pTarget);
558         SvTabListBox *pTreeBoxTarget = dynamic_cast<SvTabListBox*>(pTarget);
559         // pStore may be empty
560         const ListStore *pStore = get_model_by_name(elem.m_sValue);
561         SAL_WARN_IF(!pListBoxTarget && !pComboBoxTarget && !pTreeBoxTarget && !dynamic_cast<IconView*>(pTarget), "vcl", "missing elements of combobox");
562         if (pListBoxTarget && pStore)
563             mungeModel(*pListBoxTarget, *pStore, elem.m_nActiveId);
564         else if (pComboBoxTarget && pStore)
565             mungeModel(*pComboBoxTarget, *pStore, elem.m_nActiveId);
566         else if (pTreeBoxTarget && pStore)
567             mungeModel(*pTreeBoxTarget, *pStore, elem.m_nActiveId);
568     }
569 
570     //Set TextView buffers when everything has been imported
571     for (auto const& elem : m_pParserState->m_aTextBufferMaps)
572     {
573         VclMultiLineEdit *pTarget = get<VclMultiLineEdit>(elem.m_sID);
574         const TextBuffer *pBuffer = get_buffer_by_name(elem.m_sValue);
575         SAL_WARN_IF(!pTarget || !pBuffer, "vcl", "missing elements of textview/textbuffer");
576         if (pTarget && pBuffer)
577             mungeTextBuffer(*pTarget, *pBuffer);
578     }
579 
580     //Set SpinButton adjustments when everything has been imported
581     for (auto const& elem : m_pParserState->m_aNumericFormatterAdjustmentMaps)
582     {
583         NumericFormatter *pTarget = dynamic_cast<NumericFormatter*>(get(elem.m_sID));
584         const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue);
585         SAL_WARN_IF(!pTarget, "vcl", "missing NumericFormatter element of spinbutton/adjustment");
586         SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
587         if (pTarget && pAdjustment)
588             mungeAdjustment(*pTarget, *pAdjustment);
589     }
590 
591     for (auto const& elem : m_pParserState->m_aFormattedFormatterAdjustmentMaps)
592     {
593         FormattedField *pTarget = dynamic_cast<FormattedField*>(get(elem.m_sID));
594         const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue);
595         SAL_WARN_IF(!pTarget, "vcl", "missing FormattedField element of spinbutton/adjustment");
596         SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
597         if (pTarget && pAdjustment)
598             mungeAdjustment(*pTarget, *pAdjustment);
599     }
600 
601     //Set ScrollBar adjustments when everything has been imported
602     for (auto const& elem : m_pParserState->m_aScrollAdjustmentMaps)
603     {
604         ScrollBar *pTarget = get<ScrollBar>(elem.m_sID);
605         const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue);
606         SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scrollbar/adjustment");
607         if (pTarget && pAdjustment)
608             mungeAdjustment(*pTarget, *pAdjustment);
609     }
610 
611     //Set Scale(Slider) adjustments
612     for (auto const& elem : m_pParserState->m_aSliderAdjustmentMaps)
613     {
614         Slider* pTarget = dynamic_cast<Slider*>(get(elem.m_sID));
615         const Adjustment* pAdjustment = get_adjustment_by_name(elem.m_sValue);
616         SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scale(slider)/adjustment");
617         if (pTarget && pAdjustment)
618         {
619             mungeAdjustment(*pTarget, *pAdjustment);
620         }
621     }
622 
623     //Set size-groups when all widgets have been imported
624     for (auto const& sizeGroup : m_pParserState->m_aSizeGroups)
625     {
626         std::shared_ptr<VclSizeGroup> xGroup(std::make_shared<VclSizeGroup>());
627 
628         for (auto const& [ rKey, rValue ] : sizeGroup.m_aProperties)
629             xGroup->set_property(rKey, rValue);
630 
631         for (auto const& elem : sizeGroup.m_aWidgets)
632         {
633             vcl::Window* pWindow = get(elem);
634             pWindow->add_to_size_group(xGroup);
635         }
636     }
637 
638     //Set button images when everything has been imported
639     std::set<OUString> aImagesToBeRemoved;
640     for (auto const& elem : m_pParserState->m_aButtonImageWidgetMaps)
641     {
642         PushButton *pTargetButton = nullptr;
643         RadioButton *pTargetRadio = nullptr;
644         Button *pTarget = nullptr;
645 
646         if (!elem.m_bRadio)
647         {
648             pTargetButton = get<PushButton>(elem.m_sID);
649             pTarget = pTargetButton;
650         }
651         else
652         {
653             pTargetRadio = get<RadioButton>(elem.m_sID);
654             pTarget = pTargetRadio;
655         }
656 
657         FixedImage *pImage = get<FixedImage>(elem.m_sValue);
658         SAL_WARN_IF(!pTarget || !pImage,
659             "vcl", "missing elements of button/image/stock");
660         if (!pTarget || !pImage)
661             continue;
662         aImagesToBeRemoved.insert(elem.m_sValue);
663 
664         if (!elem.m_bRadio)
665         {
666             const Image& rImage = pImage->GetImage();
667             SymbolType eSymbol = mapStockToSymbol(rImage.GetStock());
668             if (eSymbol != SymbolType::IMAGE && eSymbol != SymbolType::DONTKNOW)
669             {
670                 pTargetButton->SetSymbol(eSymbol);
671                 //fdo#76457 keep symbol images small e.g. tools->customize->menu
672                 //but images the right size. Really the PushButton::CalcMinimumSize
673                 //and PushButton::ImplDrawPushButton are the better place to handle
674                 //this, but its such a train-wreck
675                 pTargetButton->SetStyle(pTargetButton->GetStyle() | WB_SMALLSTYLE);
676             }
677             else
678             {
679                 pTargetButton->SetModeImage(rImage);
680                 if (pImage->GetStyle() & WB_SMALLSTYLE)
681                 {
682                     Size aSz(rImage.GetSizePixel());
683                     aSz.AdjustWidth(6);
684                     aSz.AdjustHeight(6);
685                     if (pTargetButton->get_width_request() == -1)
686                         pTargetButton->set_width_request(aSz.Width());
687                     if (pTargetButton->get_height_request() == -1)
688                         pTargetButton->set_height_request(aSz.Height());
689                 }
690             }
691         }
692         else
693             pTargetRadio->SetModeRadioImage(pImage->GetImage());
694 
695         auto aFind = m_pParserState->m_aImageSizeMap.find(elem.m_sValue);
696         if (aFind != m_pParserState->m_aImageSizeMap.end())
697         {
698             switch (aFind->second)
699             {
700                 case 1:
701                     pTarget->SetSmallSymbol();
702                     break;
703                 case 2:
704                     assert(pImage->GetStyle() & WB_SMALLSTYLE);
705                     pTarget->SetStyle(pTarget->GetStyle() | WB_SMALLSTYLE);
706                     break;
707                 case 3:
708                     pTarget->SetStyle(pTarget->GetStyle() | WB_SMALLSTYLE);
709                     // large toolbar, make bigger than normal (4)
710                     pTarget->set_width_request(pTarget->GetOptimalSize().Width() * 1.5);
711                     pTarget->set_height_request(pTarget->GetOptimalSize().Height() * 1.5);
712                     break;
713                 case 4:
714                     break;
715                 default:
716                     SAL_WARN("vcl.builder", "unsupported image size " << aFind->second);
717                     break;
718             }
719             m_pParserState->m_aImageSizeMap.erase(aFind);
720         }
721     }
722 
723     //There may be duplicate use of an Image, so we used a set to collect and
724     //now we can remove them from the tree after their final munge
725     for (auto const& elem : aImagesToBeRemoved)
726     {
727         delete_by_name(elem);
728     }
729 
730     //Set button menus when everything has been imported
731     for (auto const& elem : m_pParserState->m_aButtonMenuMaps)
732     {
733         MenuButton *pTarget = get<MenuButton>(elem.m_sID);
734         PopupMenu *pMenu = get_menu(elem.m_sValue);
735         SAL_WARN_IF(!pTarget || !pMenu,
736             "vcl", "missing elements of button/menu");
737         if (!pTarget || !pMenu)
738             continue;
739         pTarget->SetPopupMenu(pMenu);
740     }
741 
742     //Remove ScrollWindow parent widgets whose children in vcl implement scrolling
743     //internally.
744     for (auto const& elem : m_pParserState->m_aRedundantParentWidgets)
745     {
746         delete_by_window(elem.first);
747     }
748 
749     //fdo#67378 merge the label into the disclosure button
750     for (auto const& elem : m_pParserState->m_aExpanderWidgets)
751     {
752         vcl::Window *pChild = elem->get_child();
753         vcl::Window* pLabel = elem->GetWindow(GetWindowType::LastChild);
754         if (pLabel && pLabel != pChild && pLabel->GetType() == WindowType::FIXEDTEXT)
755         {
756             FixedText *pLabelWidget = static_cast<FixedText*>(pLabel);
757             elem->set_label(pLabelWidget->GetText());
758             if (pLabelWidget->IsControlFont())
759                 elem->get_label_widget()->SetControlFont(pLabelWidget->GetControlFont());
760             delete_by_window(pLabel);
761         }
762     }
763 
764     // create message dialog message area now
765     for (auto const& elem : m_pParserState->m_aMessageDialogs)
766         elem->create_message_area();
767 
768     //drop maps, etc. that we don't need again
769     m_pParserState.reset();
770 
771     SAL_WARN_IF(!m_sID.isEmpty() && (!m_bToplevelParentFound && !get_by_name(m_sID)), "vcl.builder",
772         "Requested top level widget \"" << m_sID << "\" not found in " << sUIFile);
773 
774 #if defined SAL_LOG_WARN
775     if (m_bToplevelParentFound && m_pParent->IsDialog())
776     {
777         int nButtons = 0;
778         bool bHasDefButton = false;
779         for (auto const& child : m_aChildren)
780         {
781             if (isButtonType(child.m_pWindow->GetType()))
782             {
783                 ++nButtons;
784                 if (child.m_pWindow->GetStyle() & WB_DEFBUTTON)
785                 {
786                     bHasDefButton = true;
787                     break;
788                 }
789             }
790         }
791         SAL_WARN_IF(nButtons && !bHasDefButton, "vcl.builder", "No default button defined in " << sUIFile);
792     }
793 #endif
794 
795     const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
796         officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
797     if (bHideHelp)
798     {
799         if (vcl::Window *pHelpButton = get(u"help"_ustr))
800             pHelpButton->Hide();
801     }
802 }
803 
~VclBuilder()804 VclBuilder::~VclBuilder()
805 {
806     disposeBuilder();
807 }
808 
disposeBuilder()809 void VclBuilder::disposeBuilder()
810 {
811     for (std::vector<WinAndId>::reverse_iterator aI = m_aChildren.rbegin(),
812          aEnd = m_aChildren.rend(); aI != aEnd; ++aI)
813     {
814         aI->m_pWindow.disposeAndClear();
815     }
816     m_aChildren.clear();
817 
818     for (std::vector<MenuAndId>::reverse_iterator aI = m_aMenus.rbegin(),
819          aEnd = m_aMenus.rend(); aI != aEnd; ++aI)
820     {
821         aI->m_pMenu.disposeAndClear();
822     }
823     m_aMenus.clear();
824     m_pParent.clear();
825 }
826 
827 namespace
828 {
extractHasFrame(VclBuilder::stringmap & rMap)829     bool extractHasFrame(VclBuilder::stringmap& rMap)
830     {
831         bool bHasFrame = true;
832         VclBuilder::stringmap::iterator aFind = rMap.find(u"has-frame"_ustr);
833         if (aFind != rMap.end())
834         {
835             bHasFrame = toBool(aFind->second);
836             rMap.erase(aFind);
837         }
838         return bHasFrame;
839     }
840 
extractDrawValue(VclBuilder::stringmap & rMap)841     bool extractDrawValue(VclBuilder::stringmap& rMap)
842     {
843         bool bDrawValue = true;
844         VclBuilder::stringmap::iterator aFind = rMap.find(u"draw-value"_ustr);
845         if (aFind != rMap.end())
846         {
847             bDrawValue = toBool(aFind->second);
848             rMap.erase(aFind);
849         }
850         return bDrawValue;
851     }
852 
extractPopupMenu(VclBuilder::stringmap & rMap)853     OUString extractPopupMenu(VclBuilder::stringmap& rMap)
854     {
855         OUString sRet;
856         VclBuilder::stringmap::iterator aFind = rMap.find(u"popup"_ustr);
857         if (aFind != rMap.end())
858         {
859             sRet = aFind->second;
860             rMap.erase(aFind);
861         }
862         return sRet;
863     }
864 
extractWidgetName(VclBuilder::stringmap & rMap)865     OUString extractWidgetName(VclBuilder::stringmap& rMap)
866     {
867         OUString sRet;
868         VclBuilder::stringmap::iterator aFind = rMap.find(u"name"_ustr);
869         if (aFind != rMap.end())
870         {
871             sRet = aFind->second;
872             rMap.erase(aFind);
873         }
874         return sRet;
875     }
876 
extractValuePos(VclBuilder::stringmap & rMap)877     OUString extractValuePos(VclBuilder::stringmap& rMap)
878     {
879         OUString sRet(u"top"_ustr);
880         VclBuilder::stringmap::iterator aFind = rMap.find(u"value-pos"_ustr);
881         if (aFind != rMap.end())
882         {
883             sRet = aFind->second;
884             rMap.erase(aFind);
885         }
886         return sRet;
887     }
888 
extractTypeHint(VclBuilder::stringmap & rMap)889     OUString extractTypeHint(VclBuilder::stringmap &rMap)
890     {
891         OUString sRet(u"normal"_ustr);
892         VclBuilder::stringmap::iterator aFind = rMap.find(u"type-hint"_ustr);
893         if (aFind != rMap.end())
894         {
895             sRet = aFind->second;
896             rMap.erase(aFind);
897         }
898         return sRet;
899     }
900 
extractResizable(VclBuilder::stringmap & rMap)901     bool extractResizable(VclBuilder::stringmap &rMap)
902     {
903         bool bResizable = true;
904         VclBuilder::stringmap::iterator aFind = rMap.find(u"resizable"_ustr);
905         if (aFind != rMap.end())
906         {
907             bResizable = toBool(aFind->second);
908             rMap.erase(aFind);
909         }
910         return bResizable;
911     }
912 
913 #if HAVE_FEATURE_DESKTOP
extractModal(VclBuilder::stringmap & rMap)914     bool extractModal(VclBuilder::stringmap &rMap)
915     {
916         bool bModal = false;
917         VclBuilder::stringmap::iterator aFind = rMap.find(u"modal"_ustr);
918         if (aFind != rMap.end())
919         {
920             bModal = toBool(aFind->second);
921             rMap.erase(aFind);
922         }
923         return bModal;
924     }
925 #endif
926 
extractDecorated(VclBuilder::stringmap & rMap)927     bool extractDecorated(VclBuilder::stringmap &rMap)
928     {
929         bool bDecorated = true;
930         VclBuilder::stringmap::iterator aFind = rMap.find(u"decorated"_ustr);
931         if (aFind != rMap.end())
932         {
933             bDecorated = toBool(aFind->second);
934             rMap.erase(aFind);
935         }
936         return bDecorated;
937     }
938 
extractCloseable(VclBuilder::stringmap & rMap)939     bool extractCloseable(VclBuilder::stringmap &rMap)
940     {
941         bool bCloseable = true;
942         VclBuilder::stringmap::iterator aFind = rMap.find(u"deletable"_ustr);
943         if (aFind != rMap.end())
944         {
945             bCloseable = toBool(aFind->second);
946             rMap.erase(aFind);
947         }
948         return bCloseable;
949     }
950 
extractEntry(VclBuilder::stringmap & rMap)951     bool extractEntry(VclBuilder::stringmap &rMap)
952     {
953         bool bHasEntry = false;
954         VclBuilder::stringmap::iterator aFind = rMap.find(u"has-entry"_ustr);
955         if (aFind != rMap.end())
956         {
957             bHasEntry = toBool(aFind->second);
958             rMap.erase(aFind);
959         }
960         return bHasEntry;
961     }
962 
extractOrientation(VclBuilder::stringmap & rMap)963     bool extractOrientation(VclBuilder::stringmap &rMap)
964     {
965         bool bVertical = false;
966         VclBuilder::stringmap::iterator aFind = rMap.find(u"orientation"_ustr);
967         if (aFind != rMap.end())
968         {
969             bVertical = aFind->second.equalsIgnoreAsciiCase("vertical");
970             rMap.erase(aFind);
971         }
972         return bVertical;
973     }
974 
extractVerticalTabPos(VclBuilder::stringmap & rMap)975     bool extractVerticalTabPos(VclBuilder::stringmap &rMap)
976     {
977         bool bVertical = false;
978         VclBuilder::stringmap::iterator aFind = rMap.find(u"tab-pos"_ustr);
979         if (aFind != rMap.end())
980         {
981             bVertical = aFind->second.equalsIgnoreAsciiCase("left") ||
982                         aFind->second.equalsIgnoreAsciiCase("right");
983             rMap.erase(aFind);
984         }
985         return bVertical;
986     }
987 
extractVerticalTabsWithIcons(VclBuilder::stringmap & rMap)988     bool extractVerticalTabsWithIcons(VclBuilder::stringmap &rMap)
989     {
990         bool bWithIcons = false;
991         VclBuilder::stringmap::iterator aFind = rMap.find(u"group-name"_ustr);
992         if (aFind != rMap.end())
993         {
994             bWithIcons = aFind->second.equalsIgnoreAsciiCase("icons");
995             rMap.erase(aFind);
996         }
997         return bWithIcons;
998     }
999 
extractInconsistent(VclBuilder::stringmap & rMap)1000     bool extractInconsistent(VclBuilder::stringmap &rMap)
1001     {
1002         bool bInconsistent = false;
1003         VclBuilder::stringmap::iterator aFind = rMap.find(u"inconsistent"_ustr);
1004         if (aFind != rMap.end())
1005         {
1006             bInconsistent = toBool(aFind->second);
1007             rMap.erase(aFind);
1008         }
1009         return bInconsistent;
1010     }
1011 
extractIconName(VclBuilder::stringmap & rMap)1012     OUString extractIconName(VclBuilder::stringmap &rMap)
1013     {
1014         OUString sIconName;
1015         // allow pixbuf, but prefer icon-name
1016         {
1017             VclBuilder::stringmap::iterator aFind = rMap.find(u"pixbuf"_ustr);
1018             if (aFind != rMap.end())
1019             {
1020                 sIconName = aFind->second;
1021                 rMap.erase(aFind);
1022             }
1023         }
1024         {
1025             VclBuilder::stringmap::iterator aFind = rMap.find(u"icon-name"_ustr);
1026             if (aFind != rMap.end())
1027             {
1028                 sIconName = aFind->second;
1029                 rMap.erase(aFind);
1030             }
1031         }
1032         if (sIconName == "missing-image")
1033             return OUString();
1034         OUString sReplace = mapStockToImageResource(sIconName);
1035         return !sReplace.isEmpty() ? sReplace : sIconName;
1036     }
1037 
extractRelief(VclBuilder::stringmap & rMap)1038     WinBits extractRelief(VclBuilder::stringmap &rMap)
1039     {
1040         WinBits nBits = WB_3DLOOK;
1041         VclBuilder::stringmap::iterator aFind = rMap.find(u"relief"_ustr);
1042         if (aFind != rMap.end())
1043         {
1044             assert(aFind->second != "half" && "relief of 'half' unsupported");
1045             if (aFind->second == "none")
1046                 nBits = WB_FLATBUTTON;
1047             rMap.erase(aFind);
1048         }
1049         return nBits;
1050     }
1051 
extractLabel(VclBuilder::stringmap & rMap)1052     OUString extractLabel(VclBuilder::stringmap &rMap)
1053     {
1054         OUString sType;
1055         VclBuilder::stringmap::iterator aFind = rMap.find(u"label"_ustr);
1056         if (aFind != rMap.end())
1057         {
1058             sType = aFind->second;
1059             rMap.erase(aFind);
1060         }
1061         return sType;
1062     }
1063 
extractActionName(VclBuilder::stringmap & rMap)1064     OUString extractActionName(VclBuilder::stringmap &rMap)
1065     {
1066         OUString sActionName;
1067         VclBuilder::stringmap::iterator aFind = rMap.find(u"action-name"_ustr);
1068         if (aFind != rMap.end())
1069         {
1070             sActionName = aFind->second;
1071             rMap.erase(aFind);
1072         }
1073         return sActionName;
1074     }
1075 
extractVisible(VclBuilder::stringmap & rMap)1076     bool extractVisible(VclBuilder::stringmap &rMap)
1077     {
1078         bool bRet = false;
1079         VclBuilder::stringmap::iterator aFind = rMap.find(u"visible"_ustr);
1080         if (aFind != rMap.end())
1081         {
1082             bRet = toBool(aFind->second);
1083             rMap.erase(aFind);
1084         }
1085         return bRet;
1086     }
1087 
extractSizeRequest(VclBuilder::stringmap & rMap)1088     Size extractSizeRequest(VclBuilder::stringmap &rMap)
1089     {
1090         OUString sWidthRequest(u"0"_ustr);
1091         OUString sHeightRequest(u"0"_ustr);
1092         VclBuilder::stringmap::iterator aFind = rMap.find(u"width-request"_ustr);
1093         if (aFind != rMap.end())
1094         {
1095             sWidthRequest = aFind->second;
1096             rMap.erase(aFind);
1097         }
1098         aFind = rMap.find(u"height-request"_ustr);
1099         if (aFind != rMap.end())
1100         {
1101             sHeightRequest = aFind->second;
1102             rMap.erase(aFind);
1103         }
1104         return Size(sWidthRequest.toInt32(), sHeightRequest.toInt32());
1105     }
1106 
extractTooltipText(VclBuilder::stringmap & rMap)1107     OUString extractTooltipText(VclBuilder::stringmap &rMap)
1108     {
1109         OUString sTooltipText;
1110         VclBuilder::stringmap::iterator aFind = rMap.find(u"tooltip-text"_ustr);
1111         if (aFind == rMap.end())
1112             aFind = rMap.find(u"tooltip-markup"_ustr);
1113         if (aFind != rMap.end())
1114         {
1115             sTooltipText = aFind->second;
1116             rMap.erase(aFind);
1117         }
1118         return sTooltipText;
1119     }
1120 
extractAlignment(VclBuilder::stringmap & rMap)1121     float extractAlignment(VclBuilder::stringmap &rMap)
1122     {
1123         float f = 0.0;
1124         VclBuilder::stringmap::iterator aFind = rMap.find(u"alignment"_ustr);
1125         if (aFind != rMap.end())
1126         {
1127             f = aFind->second.toFloat();
1128             rMap.erase(aFind);
1129         }
1130         return f;
1131     }
1132 
extractTitle(VclBuilder::stringmap & rMap)1133     OUString extractTitle(VclBuilder::stringmap &rMap)
1134     {
1135         OUString sTitle;
1136         VclBuilder::stringmap::iterator aFind = rMap.find(u"title"_ustr);
1137         if (aFind != rMap.end())
1138         {
1139             sTitle = aFind->second;
1140             rMap.erase(aFind);
1141         }
1142         return sTitle;
1143     }
1144 
extractHeadersVisible(VclBuilder::stringmap & rMap)1145     bool extractHeadersVisible(VclBuilder::stringmap &rMap)
1146     {
1147         bool bHeadersVisible = true;
1148         VclBuilder::stringmap::iterator aFind = rMap.find(u"headers-visible"_ustr);
1149         if (aFind != rMap.end())
1150         {
1151             bHeadersVisible = toBool(aFind->second);
1152             rMap.erase(aFind);
1153         }
1154         return bHeadersVisible;
1155     }
1156 
extractSortIndicator(VclBuilder::stringmap & rMap)1157     bool extractSortIndicator(VclBuilder::stringmap &rMap)
1158     {
1159         bool bSortIndicator = false;
1160         VclBuilder::stringmap::iterator aFind = rMap.find(u"sort-indicator"_ustr);
1161         if (aFind != rMap.end())
1162         {
1163             bSortIndicator = toBool(aFind->second);
1164             rMap.erase(aFind);
1165         }
1166         return bSortIndicator;
1167     }
1168 
extractClickable(VclBuilder::stringmap & rMap)1169     bool extractClickable(VclBuilder::stringmap &rMap)
1170     {
1171         bool bClickable = false;
1172         VclBuilder::stringmap::iterator aFind = rMap.find(u"clickable"_ustr);
1173         if (aFind != rMap.end())
1174         {
1175             bClickable = toBool(aFind->second);
1176             rMap.erase(aFind);
1177         }
1178         return bClickable;
1179     }
1180 
setupFromActionName(Button * pButton,VclBuilder::stringmap & rMap,const css::uno::Reference<css::frame::XFrame> & rFrame)1181     void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame)
1182     {
1183         if (!rFrame.is())
1184             return;
1185 
1186         OUString aCommand(extractActionName(rMap));
1187         if (aCommand.isEmpty())
1188             return;
1189 
1190         OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
1191         auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommand, aModuleName);
1192         OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties));
1193         if (!aLabel.isEmpty())
1194             pButton->SetText(aLabel);
1195 
1196         OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(aCommand, aProperties, rFrame));
1197         if (!aTooltip.isEmpty())
1198             pButton->SetQuickHelpText(aTooltip);
1199 
1200         Image aImage(vcl::CommandInfoProvider::GetImageForCommand(aCommand, rFrame));
1201         pButton->SetModeImage(aImage);
1202 
1203         pButton->SetCommandHandler(aCommand, rFrame);
1204     }
1205 
extractStockAndBuildPushButton(vcl::Window * pParent,VclBuilder::stringmap & rMap,bool bToggle)1206     VclPtr<Button> extractStockAndBuildPushButton(vcl::Window *pParent, VclBuilder::stringmap &rMap, bool bToggle)
1207     {
1208         WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER;
1209         if (bToggle)
1210             nBits |= WB_TOGGLE;
1211 
1212         nBits |= extractRelief(rMap);
1213 
1214         VclPtr<Button> xWindow = VclPtr<PushButton>::Create(pParent, nBits);
1215         return xWindow;
1216     }
1217 
extractStockAndBuildMenuButton(vcl::Window * pParent,VclBuilder::stringmap & rMap)1218     VclPtr<MenuButton> extractStockAndBuildMenuButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
1219     {
1220         WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
1221 
1222         nBits |= extractRelief(rMap);
1223 
1224         VclPtr<MenuButton> xWindow = VclPtr<MenuButton>::Create(pParent, nBits);
1225         return xWindow;
1226     }
1227 
extractStockAndBuildMenuToggleButton(vcl::Window * pParent,VclBuilder::stringmap & rMap)1228     VclPtr<MenuButton> extractStockAndBuildMenuToggleButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
1229     {
1230         WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
1231 
1232         nBits |= extractRelief(rMap);
1233 
1234         VclPtr<MenuButton> xWindow = VclPtr<MenuToggleButton>::Create(pParent, nBits);
1235         return xWindow;
1236     }
1237 
extractDeferredBits(VclBuilder::stringmap & rMap)1238     WinBits extractDeferredBits(VclBuilder::stringmap &rMap)
1239     {
1240         WinBits nBits = WB_3DLOOK|WB_HIDE;
1241         if (extractResizable(rMap))
1242             nBits |= WB_SIZEABLE;
1243         if (extractCloseable(rMap))
1244             nBits |= WB_CLOSEABLE;
1245         if (!extractDecorated(rMap))
1246             nBits |= WB_OWNERDRAWDECORATION;
1247         OUString sType(extractTypeHint(rMap));
1248         if (sType == "utility")
1249             nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_MOVEABLE;
1250         else if (sType == "popup-menu")
1251             nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_POPUP;
1252         else if (sType == "dock")
1253             nBits |= WB_DOCKABLE | WB_MOVEABLE;
1254         else
1255             nBits |= WB_MOVEABLE;
1256         return nBits;
1257     }
1258 }
1259 
extractGroup(const OUString & id,stringmap & rMap)1260 void VclBuilder::extractGroup(const OUString &id, stringmap &rMap)
1261 {
1262     VclBuilder::stringmap::iterator aFind = rMap.find(u"group"_ustr);
1263     if (aFind != rMap.end())
1264     {
1265         OUString sID = aFind->second;
1266         sal_Int32 nDelim = sID.indexOf(':');
1267         if (nDelim != -1)
1268             sID = sID.copy(0, nDelim);
1269         m_pParserState->m_aGroupMaps.emplace_back(id, sID);
1270         rMap.erase(aFind);
1271     }
1272 }
1273 
connectNumericFormatterAdjustment(const OUString & id,const OUString & rAdjustment)1274 void VclBuilder::connectNumericFormatterAdjustment(const OUString &id, const OUString &rAdjustment)
1275 {
1276     if (!rAdjustment.isEmpty())
1277         m_pParserState->m_aNumericFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
1278 }
1279 
connectFormattedFormatterAdjustment(const OUString & id,const OUString & rAdjustment)1280 void VclBuilder::connectFormattedFormatterAdjustment(const OUString &id, const OUString &rAdjustment)
1281 {
1282     if (!rAdjustment.isEmpty())
1283         m_pParserState->m_aFormattedFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
1284 }
1285 
extractAdjustmentToMap(const OUString & id,VclBuilder::stringmap & rMap,std::vector<WidgetAdjustmentMap> & rAdjustmentMap)1286 bool VclBuilder::extractAdjustmentToMap(const OUString& id, VclBuilder::stringmap& rMap, std::vector<WidgetAdjustmentMap>& rAdjustmentMap)
1287 {
1288     VclBuilder::stringmap::iterator aFind = rMap.find(u"adjustment"_ustr);
1289     if (aFind != rMap.end())
1290     {
1291         rAdjustmentMap.emplace_back(id, aFind->second);
1292         rMap.erase(aFind);
1293         return true;
1294     }
1295     return false;
1296 }
1297 
1298 namespace
1299 {
extractActive(VclBuilder::stringmap & rMap)1300     sal_Int32 extractActive(VclBuilder::stringmap &rMap)
1301     {
1302         sal_Int32 nActiveId = 0;
1303         VclBuilder::stringmap::iterator aFind = rMap.find(u"active"_ustr);
1304         if (aFind != rMap.end())
1305         {
1306             nActiveId = aFind->second.toInt32();
1307             rMap.erase(aFind);
1308         }
1309         return nActiveId;
1310     }
1311 
extractSelectable(VclBuilder::stringmap & rMap)1312     bool extractSelectable(VclBuilder::stringmap &rMap)
1313     {
1314         bool bSelectable = false;
1315         VclBuilder::stringmap::iterator aFind = rMap.find(u"selectable"_ustr);
1316         if (aFind != rMap.end())
1317         {
1318             bSelectable = toBool(aFind->second);
1319             rMap.erase(aFind);
1320         }
1321         return bSelectable;
1322     }
1323 
extractAdjustment(VclBuilder::stringmap & rMap)1324     OUString extractAdjustment(VclBuilder::stringmap &rMap)
1325     {
1326         OUString sAdjustment;
1327         VclBuilder::stringmap::iterator aFind = rMap.find(u"adjustment"_ustr);
1328         if (aFind != rMap.end())
1329         {
1330             sAdjustment= aFind->second;
1331             rMap.erase(aFind);
1332             return sAdjustment;
1333         }
1334         return sAdjustment;
1335     }
1336 
extractDrawIndicator(VclBuilder::stringmap & rMap)1337     bool extractDrawIndicator(VclBuilder::stringmap &rMap)
1338     {
1339         bool bDrawIndicator = false;
1340         VclBuilder::stringmap::iterator aFind = rMap.find(u"draw-indicator"_ustr);
1341         if (aFind != rMap.end())
1342         {
1343             bDrawIndicator = toBool(aFind->second);
1344             rMap.erase(aFind);
1345         }
1346         return bDrawIndicator;
1347     }
1348 }
1349 
extractModel(const OUString & id,stringmap & rMap)1350 void VclBuilder::extractModel(const OUString &id, stringmap &rMap)
1351 {
1352     VclBuilder::stringmap::iterator aFind = rMap.find(u"model"_ustr);
1353     if (aFind != rMap.end())
1354     {
1355         m_pParserState->m_aModelMaps.emplace_back(id, aFind->second,
1356             extractActive(rMap));
1357         rMap.erase(aFind);
1358     }
1359 }
1360 
extractBuffer(const OUString & id,stringmap & rMap)1361 void VclBuilder::extractBuffer(const OUString &id, stringmap &rMap)
1362 {
1363     VclBuilder::stringmap::iterator aFind = rMap.find(u"buffer"_ustr);
1364     if (aFind != rMap.end())
1365     {
1366         m_pParserState->m_aTextBufferMaps.emplace_back(id, aFind->second);
1367         rMap.erase(aFind);
1368     }
1369 }
1370 
getImageSize(const stringmap & rMap)1371 int VclBuilder::getImageSize(const stringmap &rMap)
1372 {
1373     int nSize = 4;
1374     auto aFind = rMap.find(u"icon-size"_ustr);
1375     if (aFind != rMap.end())
1376         nSize = aFind->second.toInt32();
1377     return nSize;
1378 }
1379 
extractButtonImage(const OUString & id,stringmap & rMap,bool bRadio)1380 void VclBuilder::extractButtonImage(const OUString &id, stringmap &rMap, bool bRadio)
1381 {
1382     VclBuilder::stringmap::iterator aFind = rMap.find(u"image"_ustr);
1383     if (aFind != rMap.end())
1384     {
1385         m_pParserState->m_aButtonImageWidgetMaps.emplace_back(id, aFind->second, bRadio);
1386         rMap.erase(aFind);
1387     }
1388 }
1389 
extractMnemonicWidget(const OUString & rLabelID,stringmap & rMap)1390 void VclBuilder::extractMnemonicWidget(const OUString &rLabelID, stringmap &rMap)
1391 {
1392     VclBuilder::stringmap::iterator aFind = rMap.find(u"mnemonic-widget"_ustr);
1393     if (aFind != rMap.end())
1394     {
1395         OUString sID = aFind->second;
1396         sal_Int32 nDelim = sID.indexOf(':');
1397         if (nDelim != -1)
1398             sID = sID.copy(0, nDelim);
1399         m_pParserState->m_aMnemonicWidgetMaps.emplace_back(rLabelID, sID);
1400         rMap.erase(aFind);
1401     }
1402 }
1403 
prepareWidgetOwnScrolling(vcl::Window * pParent,WinBits & rWinStyle)1404 vcl::Window* VclBuilder::prepareWidgetOwnScrolling(vcl::Window *pParent, WinBits &rWinStyle)
1405 {
1406     //For Widgets that manage their own scrolling, if one appears as a child of
1407     //a scrolling window shoehorn that scrolling settings to this widget and
1408     //return the real parent to use
1409     if (pParent && pParent->GetType() == WindowType::SCROLLWINDOW)
1410     {
1411         WinBits nScrollBits = pParent->GetStyle();
1412         nScrollBits &= (WB_AUTOHSCROLL|WB_HSCROLL|WB_AUTOVSCROLL|WB_VSCROLL);
1413         rWinStyle |= nScrollBits;
1414         if (static_cast<VclScrolledWindow*>(pParent)->HasVisibleBorder())
1415             rWinStyle |= WB_BORDER;
1416         pParent = pParent->GetParent();
1417     }
1418 
1419     return pParent;
1420 }
1421 
cleanupWidgetOwnScrolling(vcl::Window * pScrollParent,vcl::Window * pWindow,stringmap & rMap)1422 void VclBuilder::cleanupWidgetOwnScrolling(vcl::Window *pScrollParent, vcl::Window *pWindow, stringmap &rMap)
1423 {
1424     //remove the redundant scrolling parent
1425     sal_Int32 nWidthReq = pScrollParent->get_width_request();
1426     rMap[u"width-request"_ustr] = OUString::number(nWidthReq);
1427     sal_Int32 nHeightReq = pScrollParent->get_height_request();
1428     rMap[u"height-request"_ustr] = OUString::number(nHeightReq);
1429 
1430     m_pParserState->m_aRedundantParentWidgets[pScrollParent] = pWindow;
1431 }
1432 
1433 #ifndef DISABLE_DYNLOADING
1434 
thisModule()1435 extern "C" { static void thisModule() {} }
1436 
1437 namespace {
1438 
1439 // Don't unload the module on destruction
1440 class NoAutoUnloadModule : public osl::Module
1441 {
1442 public:
~NoAutoUnloadModule()1443     ~NoAutoUnloadModule() { release(); }
1444 };
1445 
1446 }
1447 
1448 typedef std::map<OUString, std::shared_ptr<NoAutoUnloadModule>> ModuleMap;
1449 static ModuleMap g_aModuleMap;
1450 
1451 #if ENABLE_MERGELIBS
1452 static std::shared_ptr<NoAutoUnloadModule> g_pMergedLib = std::make_shared<NoAutoUnloadModule>();
1453 #endif
1454 
1455 #ifndef SAL_DLLPREFIX
1456 #  define SAL_DLLPREFIX ""
1457 #endif
1458 
1459 #endif
1460 
1461 namespace vcl {
1462 
VclBuilderPreload()1463 void VclBuilderPreload()
1464 {
1465 #ifndef DISABLE_DYNLOADING
1466 
1467 #if ENABLE_MERGELIBS
1468     g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
1469 #else
1470 // find -name '*ui*' | xargs grep 'class=".*lo-' |
1471 //     sed 's/.*class="//' | sed 's/-.*$//' | sort | uniq
1472     static const char *aWidgetLibs[] = {
1473         "sfxlo",  "svtlo"
1474     };
1475     for (const auto & lib : aWidgetLibs)
1476     {
1477         std::unique_ptr<NoAutoUnloadModule> pModule(new NoAutoUnloadModule);
1478         OUString sModule = SAL_DLLPREFIX + OUString::createFromAscii(lib) + SAL_DLLEXTENSION;
1479         if (pModule->loadRelative(&thisModule, sModule))
1480             g_aModuleMap.insert(std::make_pair(sModule, std::move(pModule)));
1481     }
1482 #endif // ENABLE_MERGELIBS
1483 #endif // DISABLE_DYNLOADING
1484 }
1485 
1486 }
1487 
1488 #if defined DISABLE_DYNLOADING && !HAVE_FEATURE_DESKTOP
1489 
1490 // This ifdef branch is mainly for building for the Collabora Online
1491 // -based mobile apps for Android and iOS.
1492 
1493 extern "C" VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name);
1494 
1495 #elif defined EMSCRIPTEN && !ENABLE_QT5
1496 
1497 // This branch is mainly for building for WASM, and especially for
1498 // Collabora Online in the browser, where code from core and Collabora
1499 // Online is compiled to WASM and linked into a single WASM binary.
1500 // (Not for Allotropia's Qt-based LibreOffice in the browser.)
1501 
1502 // When building core for WASM it doesn't use the same
1503 // solenv/bin/native-code.py thing as the mobile apps, even if in both
1504 // cases everything is linked statically. So there is no generated
1505 // native-code.h, and we can't use lo_get_custom_widget_func() from
1506 // that. So cheat and duplicate the code from an existing generated
1507 // native-code.h. It's just a handful of lines anyway.
1508 
1509 extern "C" void makeNotebookbarTabControl(VclPtr<vcl::Window> &rRet, const VclPtr<vcl::Window> &pParent, VclBuilder::stringmap &rVec);
1510 extern "C" void makeNotebookbarToolBox(VclPtr<vcl::Window> &rRet, const VclPtr<vcl::Window> &pParent, VclBuilder::stringmap &rVec);
1511 
1512 static struct { const char *name; VclBuilder::customMakeWidget func; } custom_widgets[] = {
1513     { "makeNotebookbarTabControl", makeNotebookbarTabControl },
1514     { "makeNotebookbarToolBox", makeNotebookbarToolBox },
1515 };
1516 
lo_get_custom_widget_func(const char * name)1517 static VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name)
1518 {
1519     for (size_t i = 0; i < sizeof(custom_widgets) / sizeof(custom_widgets[0]); i++)
1520         if (strcmp(name, custom_widgets[i].name) == 0)
1521             return custom_widgets[i].func;
1522     return nullptr;
1523 }
1524 
1525 #endif
1526 
1527 namespace
1528 {
1529 // Takes a string like "sfxlo-NotebookbarToolBox"
GetCustomMakeWidget(const OUString & rName)1530 VclBuilder::customMakeWidget GetCustomMakeWidget(const OUString& rName)
1531 {
1532     const OUString name = rName == "sfxlo-SidebarToolBox" ? u"sfxlo-NotebookbarToolBox"_ustr : rName;
1533     VclBuilder::customMakeWidget pFunction = nullptr;
1534     if (sal_Int32 nDelim = name.indexOf('-'); nDelim != -1)
1535     {
1536         const OUString sFunction(OUString::Concat("make") + name.subView(nDelim + 1));
1537 
1538 #ifndef DISABLE_DYNLOADING
1539         const OUString sModule = OUString::Concat(SAL_DLLPREFIX)
1540                                  + name.subView(0, nDelim)
1541                                  + SAL_DLLEXTENSION;
1542         ModuleMap::iterator aI = g_aModuleMap.find(sModule);
1543         if (aI == g_aModuleMap.end())
1544         {
1545             std::shared_ptr<NoAutoUnloadModule> pModule;
1546 #if ENABLE_MERGELIBS
1547             if (!g_pMergedLib->is())
1548                 g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
1549             if ((pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
1550                      g_pMergedLib->getFunctionSymbol(sFunction))))
1551                 pModule = g_pMergedLib;
1552 #endif
1553             if (!pFunction)
1554             {
1555                 pModule = std::make_shared<NoAutoUnloadModule>();
1556                 bool ok = pModule->loadRelative(&thisModule, sModule);
1557                 if (!ok)
1558                 {
1559 #ifdef LINUX
1560                     // in the case of preloading, we don't have eg. the
1561                     // libcuilo.so, but still need to dlsym the symbols -
1562                     // which are already in-process
1563                     if (comphelper::LibreOfficeKit::isActive())
1564                     {
1565                         pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(dlsym(RTLD_DEFAULT, OUStringToOString(sFunction, RTL_TEXTENCODING_UTF8).getStr()));
1566                         ok = !!pFunction;
1567                         assert(ok && "couldn't even directly dlsym the sFunction (available via preload)");
1568                     }
1569 #endif
1570                     assert(ok && "bad module name in .ui");
1571                 }
1572                 else
1573                 {
1574                     pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
1575                             pModule->getFunctionSymbol(sFunction));
1576                 }
1577             }
1578             g_aModuleMap.insert(std::make_pair(sModule, pModule));
1579         }
1580         else
1581             pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
1582                 aI->second->getFunctionSymbol(sFunction));
1583 #elif !HAVE_FEATURE_DESKTOP || (defined EMSCRIPTEN && !ENABLE_QT5)
1584         // This ifdef branch is mainly for building for either the
1585         // Android or iOS apps, or the Collabora Online as WASM thing.
1586         pFunction = lo_get_custom_widget_func(sFunction.toUtf8().getStr());
1587         SAL_WARN_IF(!pFunction, "vcl.builder", "Could not find " << sFunction);
1588         assert(pFunction);
1589 #else
1590         pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(
1591             osl_getFunctionSymbol((oslModule)RTLD_DEFAULT, sFunction.pData));
1592 #endif
1593     }
1594     return pFunction;
1595 }
1596 }
1597 
makeObject(vcl::Window * pParent,const OUString & name,const OUString & id,stringmap & rMap)1598 VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window *pParent, const OUString &name, const OUString &id,
1599     stringmap &rMap)
1600 {
1601     bool bIsPlaceHolder = name.isEmpty();
1602     bool bVertical = false;
1603 
1604     if (pParent && (pParent->GetType() == WindowType::TABCONTROL ||
1605                     pParent->GetType() == WindowType::VERTICALTABCONTROL))
1606     {
1607         bool bTopLevel(name == "GtkDialog" || name == "GtkMessageDialog" ||
1608                        name == "GtkWindow" || name == "GtkPopover" || name == "GtkAssistant");
1609         if (!bTopLevel)
1610         {
1611             if (pParent->GetType() == WindowType::TABCONTROL)
1612             {
1613                 //We have to add a page
1614                 //make default pageid == position
1615                 TabControl *pTabControl = static_cast<TabControl*>(pParent);
1616                 sal_uInt16 nNewPageCount = pTabControl->GetPageCount()+1;
1617                 sal_uInt16 nNewPageId = nNewPageCount;
1618                 pTabControl->InsertPage(nNewPageId, OUString());
1619                 pTabControl->SetCurPageId(nNewPageId);
1620                 SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages");
1621                 if (!bIsPlaceHolder)
1622                 {
1623                     VclPtrInstance<TabPage> pPage(pTabControl);
1624                     pPage->Show();
1625 
1626                     //Make up a name for it
1627                     OUString sTabPageId = get_by_window(pParent) +
1628                         "-page" +
1629                         OUString::number(nNewPageCount);
1630                     m_aChildren.emplace_back(sTabPageId, pPage, false);
1631                     pPage->SetHelpId(m_sHelpRoot + sTabPageId);
1632 
1633                     pParent = pPage;
1634 
1635                     pTabControl->SetTabPage(nNewPageId, pPage);
1636                 }
1637             }
1638             else
1639             {
1640                 VerticalTabControl *pTabControl = static_cast<VerticalTabControl*>(pParent);
1641                 SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages");
1642                 if (!bIsPlaceHolder)
1643                     pParent = pTabControl->GetPageParent();
1644             }
1645         }
1646     }
1647 
1648     if (bIsPlaceHolder || name == "GtkTreeSelection")
1649         return nullptr;
1650 
1651     ToolBox *pToolBox = (pParent && pParent->GetType() == WindowType::TOOLBOX) ? static_cast<ToolBox*>(pParent) : nullptr;
1652 
1653     extractButtonImage(id, rMap, name == "GtkRadioButton");
1654 
1655     VclPtr<vcl::Window> xWindow;
1656     if (name == "GtkDialog" || name == "GtkAssistant")
1657     {
1658         // WB_ALLOWMENUBAR because we don't know in advance if we will encounter
1659         // a menubar, and menubars need a BorderWindow in the toplevel, and
1660         // such border windows need to be in created during the dialog ctor
1661         WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_ALLOWMENUBAR;
1662         if (extractResizable(rMap))
1663             nBits |= WB_SIZEABLE;
1664         if (extractCloseable(rMap))
1665             nBits |= WB_CLOSEABLE;
1666         Dialog::InitFlag eInit = !pParent ? Dialog::InitFlag::NoParent : Dialog::InitFlag::Default;
1667         if (name == "GtkAssistant")
1668             xWindow = VclPtr<vcl::RoadmapWizard>::Create(pParent, nBits, eInit);
1669         else
1670             xWindow = VclPtr<Dialog>::Create(pParent, nBits, eInit);
1671 #if HAVE_FEATURE_DESKTOP
1672         if (!extractModal(rMap))
1673             xWindow->SetType(WindowType::MODELESSDIALOG);
1674 #endif
1675     }
1676     else if (name == "GtkMessageDialog")
1677     {
1678         WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE;
1679         if (extractResizable(rMap))
1680             nBits |= WB_SIZEABLE;
1681         VclPtr<MessageDialog> xDialog(VclPtr<MessageDialog>::Create(pParent, nBits));
1682         m_pParserState->m_aMessageDialogs.push_back(xDialog);
1683         xWindow = xDialog;
1684 #if defined _WIN32
1685         xWindow->set_border_width(3);
1686 #else
1687         xWindow->set_border_width(12);
1688 #endif
1689     }
1690     else if (name == "GtkBox" || name == "GtkStatusbar")
1691     {
1692         bVertical = extractOrientation(rMap);
1693         if (bVertical)
1694             xWindow = VclPtr<VclVBox>::Create(pParent);
1695         else
1696             xWindow = VclPtr<VclHBox>::Create(pParent);
1697 
1698         if (name == "GtkStatusbar")
1699             xWindow->SetAccessibleRole(css::accessibility::AccessibleRole::STATUS_BAR);
1700     }
1701     else if (name == "GtkPaned")
1702     {
1703         bVertical = extractOrientation(rMap);
1704         if (bVertical)
1705             xWindow = VclPtr<VclVPaned>::Create(pParent);
1706         else
1707             xWindow = VclPtr<VclHPaned>::Create(pParent);
1708     }
1709     else if (name == "GtkHBox")
1710         xWindow = VclPtr<VclHBox>::Create(pParent);
1711     else if (name == "GtkVBox")
1712         xWindow = VclPtr<VclVBox>::Create(pParent);
1713     else if (name == "GtkButtonBox")
1714     {
1715         bVertical = extractOrientation(rMap);
1716         if (bVertical)
1717             xWindow = VclPtr<VclVButtonBox>::Create(pParent);
1718         else
1719             xWindow = VclPtr<VclHButtonBox>::Create(pParent);
1720     }
1721     else if (name == "GtkHButtonBox")
1722         xWindow = VclPtr<VclHButtonBox>::Create(pParent);
1723     else if (name == "GtkVButtonBox")
1724         xWindow = VclPtr<VclVButtonBox>::Create(pParent);
1725     else if (name == "GtkGrid")
1726         xWindow = VclPtr<VclGrid>::Create(pParent);
1727     else if (name == "GtkFrame")
1728         xWindow = VclPtr<VclFrame>::Create(pParent);
1729     else if (name == "GtkExpander")
1730     {
1731         VclPtrInstance<VclExpander> pExpander(pParent);
1732         m_pParserState->m_aExpanderWidgets.push_back(pExpander);
1733         xWindow = pExpander;
1734     }
1735     else if (name == "GtkButton" || (!m_bLegacy && name == "GtkToggleButton"))
1736     {
1737         VclPtr<Button> xButton;
1738         OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
1739         if (sMenu.isEmpty())
1740             xButton = extractStockAndBuildPushButton(pParent, rMap, name == "GtkToggleButton");
1741         else
1742         {
1743             assert(m_bLegacy && "use GtkMenuButton");
1744             xButton = extractStockAndBuildMenuButton(pParent, rMap);
1745             m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
1746         }
1747         xButton->SetImageAlign(ImageAlign::Left); //default to left
1748         setupFromActionName(xButton, rMap, m_xFrame);
1749         xWindow = xButton;
1750     }
1751     else if (name == "GtkMenuButton")
1752     {
1753         VclPtr<MenuButton> xButton;
1754 
1755         OUString sMenu = extractPopupMenu(rMap);
1756         if (!sMenu.isEmpty())
1757             m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
1758 
1759         OUString sType = extractWidgetName(rMap);
1760         if (sType.isEmpty())
1761         {
1762             xButton = extractStockAndBuildMenuButton(pParent, rMap);
1763             xButton->SetAccessibleRole(css::accessibility::AccessibleRole::BUTTON_MENU);
1764         }
1765         else
1766         {
1767             xButton = extractStockAndBuildMenuToggleButton(pParent, rMap);
1768         }
1769 
1770         xButton->SetImageAlign(ImageAlign::Left); //default to left
1771 
1772         if (!extractDrawIndicator(rMap))
1773             xButton->SetDropDown(PushButtonDropdownStyle::NONE);
1774 
1775         setupFromActionName(xButton, rMap, m_xFrame);
1776         xWindow = xButton;
1777     }
1778     else if (name == "GtkToggleButton" && m_bLegacy)
1779     {
1780         VclPtr<Button> xButton;
1781         OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
1782         assert(sMenu.getLength() && "not implemented yet");
1783         xButton = extractStockAndBuildMenuToggleButton(pParent, rMap);
1784         m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
1785         xButton->SetImageAlign(ImageAlign::Left); //default to left
1786         setupFromActionName(xButton, rMap, m_xFrame);
1787         xWindow = xButton;
1788     }
1789     else if (name == "GtkRadioButton")
1790     {
1791         extractGroup(id, rMap);
1792         WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
1793         VclPtr<RadioButton> xButton = VclPtr<RadioButton>::Create(pParent, true, nBits);
1794         xButton->SetImageAlign(ImageAlign::Left); //default to left
1795         xWindow = xButton;
1796     }
1797     else if (name == "GtkCheckButton")
1798     {
1799         WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
1800         bool bIsTriState = extractInconsistent(rMap);
1801         VclPtr<CheckBox> xCheckBox = VclPtr<CheckBox>::Create(pParent, nBits);
1802         if (bIsTriState)
1803         {
1804             xCheckBox->EnableTriState(true);
1805             xCheckBox->SetState(TRISTATE_INDET);
1806         }
1807         xCheckBox->SetImageAlign(ImageAlign::Left); //default to left
1808 
1809         xWindow = xCheckBox;
1810     }
1811     else if (name == "GtkSpinButton")
1812     {
1813         OUString sAdjustment = extractAdjustment(rMap);
1814 
1815         WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_3DLOOK|WB_SPIN|WB_REPEAT;
1816         if (extractHasFrame(rMap))
1817             nBits |= WB_BORDER;
1818 
1819         connectFormattedFormatterAdjustment(id, sAdjustment);
1820         VclPtrInstance<FormattedField> xField(pParent, nBits);
1821         xField->GetFormatter().SetMinValue(0);
1822         xWindow = xField;
1823     }
1824     else if (name == "GtkLinkButton")
1825         xWindow = VclPtr<FixedHyperlink>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_NOLABEL);
1826     else if (name == "GtkComboBox" || name == "GtkComboBoxText")
1827     {
1828         extractModel(id, rMap);
1829 
1830         WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
1831 
1832         bool bDropdown = BuilderUtils::extractDropdown(rMap);
1833 
1834         if (bDropdown)
1835             nBits |= WB_DROPDOWN;
1836 
1837         if (extractEntry(rMap))
1838         {
1839             VclPtrInstance<ComboBox> xComboBox(pParent, nBits);
1840             xComboBox->EnableAutoSize(true);
1841             xWindow = xComboBox;
1842         }
1843         else
1844         {
1845             VclPtrInstance<ListBox> xListBox(pParent, nBits|WB_SIMPLEMODE);
1846             xListBox->EnableAutoSize(true);
1847             xWindow = xListBox;
1848         }
1849     }
1850     else if (name == "VclOptionalBox" || name == "sfxlo-OptionalBox")
1851     {
1852         // tdf#135495 fallback sfxlo-OptionalBox to VclOptionalBox as a stopgap
1853         xWindow = VclPtr<OptionalBox>::Create(pParent);
1854     }
1855     else if (name == "svtlo-ManagedMenuButton")
1856     {
1857         // like tdf#135495 keep the name svtlo-ManagedMenuButton even though it's a misnomer
1858         // and is not dlsymed from the svt library
1859         xWindow = VclPtr<ManagedMenuButton>::Create(pParent, WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_FLATBUTTON);
1860         OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
1861         if (!sMenu.isEmpty())
1862             m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
1863         setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
1864     }
1865     else if (name == "sfxlo-PriorityMergedHBox")
1866     {
1867         // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
1868         xWindow = VclPtr<PriorityMergedHBox>::Create(pParent);
1869     }
1870     else if (name == "sfxlo-PriorityHBox")
1871     {
1872         // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
1873         xWindow = VclPtr<PriorityHBox>::Create(pParent);
1874     }
1875     else if (name == "sfxlo-DropdownBox")
1876     {
1877         // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
1878         xWindow = VclPtr<DropdownBox>::Create(pParent);
1879     }
1880     else if (name == "sfxlo-ContextVBox")
1881     {
1882         // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore
1883         xWindow = VclPtr<ContextVBox>::Create(pParent);
1884     }
1885     else if (name == "GtkIconView")
1886     {
1887         assert(rMap.find(u"model"_ustr) != rMap.end() && "GtkIconView must have a model");
1888 
1889         //window we want to apply the packing props for this GtkIconView to
1890         VclPtr<vcl::Window> xWindowForPackingProps;
1891         extractModel(id, rMap);
1892         WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
1893         //IconView manages its own scrolling,
1894         vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
1895 
1896         VclPtr<IconView> xBox = VclPtr<IconView>::Create(pRealParent, nWinStyle);
1897         xWindowForPackingProps = xBox;
1898 
1899         xWindow = xBox;
1900         xBox->SetNoAutoCurEntry(true);
1901         xBox->SetQuickSearch(true);
1902 
1903         if (pRealParent != pParent)
1904             cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap);
1905     }
1906     else if (name == "GtkTreeView")
1907     {
1908         if (!m_bLegacy)
1909         {
1910             assert(rMap.find(u"model"_ustr) != rMap.end() && "GtkTreeView must have a model");
1911         }
1912 
1913         //window we want to apply the packing props for this GtkTreeView to
1914         VclPtr<vcl::Window> xWindowForPackingProps;
1915         //To-Do
1916         //a) make SvHeaderTabListBox/SvTabListBox the default target for GtkTreeView
1917         //b) remove the non-drop down mode of ListBox and convert
1918         //   everything over to SvHeaderTabListBox/SvTabListBox
1919         extractModel(id, rMap);
1920         WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
1921         if (m_bLegacy)
1922         {
1923             OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
1924             if (!sBorder.isEmpty())
1925                 nWinStyle |= WB_BORDER;
1926         }
1927         else
1928         {
1929             nWinStyle |= WB_HASBUTTONS | WB_HASBUTTONSATROOT;
1930         }
1931         //ListBox/SvHeaderTabListBox manages its own scrolling,
1932         vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
1933         if (m_bLegacy)
1934         {
1935             xWindow = VclPtr<ListBox>::Create(pRealParent, nWinStyle | WB_SIMPLEMODE);
1936             xWindowForPackingProps = xWindow;
1937         }
1938         else
1939         {
1940             VclPtr<SvTabListBox> xBox;
1941             bool bHeadersVisible = extractHeadersVisible(rMap);
1942             if (bHeadersVisible)
1943             {
1944                 VclPtr<VclVBox> xContainer = VclPtr<VclVBox>::Create(pRealParent);
1945                 OUString containerid(id + "-container");
1946                 xContainer->SetHelpId(m_sHelpRoot + containerid);
1947                 m_aChildren.emplace_back(containerid, xContainer, true);
1948 
1949                 VclPtrInstance<HeaderBar> xHeader(xContainer, WB_BUTTONSTYLE | WB_BORDER | WB_TABSTOP | WB_3DLOOK);
1950                 xHeader->set_width_request(0); // let the headerbar width not affect the size request
1951                 OUString headerid(id + "-header");
1952                 xHeader->SetHelpId(m_sHelpRoot + headerid);
1953                 m_aChildren.emplace_back(headerid, xHeader, true);
1954 
1955                 VclPtr<LclHeaderTabListBox> xHeaderBox = VclPtr<LclHeaderTabListBox>::Create(xContainer, nWinStyle);
1956                 xHeaderBox->InitHeaderBar(xHeader);
1957                 xContainer->set_expand(true);
1958                 xHeader->Show();
1959                 xContainer->Show();
1960                 xBox = xHeaderBox;
1961                 xWindowForPackingProps = xContainer;
1962             }
1963             else
1964             {
1965                 xBox = VclPtr<LclTabListBox>::Create(pRealParent, nWinStyle);
1966                 xWindowForPackingProps = xBox;
1967             }
1968             xWindow = xBox;
1969             xBox->SetNoAutoCurEntry(true);
1970             xBox->SetQuickSearch(true);
1971             xBox->SetSpaceBetweenEntries(3);
1972             xBox->SetEntryHeight(16);
1973             xBox->SetHighlightRange(); // select over the whole width
1974         }
1975         if (pRealParent != pParent)
1976             cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap);
1977     }
1978     else if (name == "GtkTreeViewColumn")
1979     {
1980         if (!m_bLegacy)
1981         {
1982             SvHeaderTabListBox* pTreeView = dynamic_cast<SvHeaderTabListBox*>(pParent);
1983             if (HeaderBar* pHeaderBar = pTreeView ? pTreeView->GetHeaderBar() : nullptr)
1984             {
1985                 HeaderBarItemBits nBits = HeaderBarItemBits::LEFTIMAGE;
1986                 if (extractClickable(rMap))
1987                     nBits |= HeaderBarItemBits::CLICKABLE;
1988                 if (extractSortIndicator(rMap))
1989                     nBits |= HeaderBarItemBits::DOWNARROW;
1990                 float fAlign = extractAlignment(rMap);
1991                 if (fAlign == 0.0)
1992                     nBits |= HeaderBarItemBits::LEFT;
1993                 else if (fAlign == 1.0)
1994                     nBits |= HeaderBarItemBits::RIGHT;
1995                 else if (fAlign == 0.5)
1996                     nBits |= HeaderBarItemBits::CENTER;
1997                 auto nItemId = pHeaderBar->GetItemCount() + 1;
1998                 OUString sTitle(extractTitle(rMap));
1999                 pHeaderBar->InsertItem(nItemId, sTitle, 100, nBits);
2000             }
2001         }
2002     }
2003     else if (name == "GtkLabel")
2004     {
2005         WinBits nWinStyle = WB_CENTER|WB_VCENTER|WB_3DLOOK;
2006         extractMnemonicWidget(id, rMap);
2007         if (extractSelectable(rMap))
2008             xWindow = VclPtr<SelectableFixedText>::Create(pParent, nWinStyle);
2009         else
2010             xWindow = VclPtr<FixedText>::Create(pParent, nWinStyle);
2011     }
2012     else if (name == "GtkImage")
2013     {
2014         VclPtr<FixedImage> xFixedImage = VclPtr<FixedImage>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_SCALE);
2015         OUString sIconName = extractIconName(rMap);
2016         if (!sIconName.isEmpty())
2017             xFixedImage->SetImage(FixedImage::loadThemeImage(sIconName));
2018         m_pParserState->m_aImageSizeMap[id] = getImageSize(rMap);
2019         xWindow = xFixedImage;
2020         //such parentless GtkImages are temps used to set icons on buttons
2021         //default them to hidden to stop e.g. insert->index entry flicking temp
2022         //full screen windows
2023         if (!pParent)
2024         {
2025             rMap[u"visible"_ustr] = "false";
2026         }
2027     }
2028     else if (name == "GtkSeparator")
2029     {
2030         bVertical = extractOrientation(rMap);
2031         xWindow = VclPtr<FixedLine>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
2032     }
2033     else if (name == "GtkScrollbar")
2034     {
2035         extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
2036         bVertical = extractOrientation(rMap);
2037         xWindow = VclPtr<ScrollBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
2038     }
2039     else if (name == "GtkProgressBar")
2040     {
2041         extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
2042         bVertical = extractOrientation(rMap);
2043         xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ, ProgressBar::BarStyle::Progress);
2044     }
2045     else if (name == "GtkLevelBar")
2046     {
2047         extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
2048         bVertical = extractOrientation(rMap);
2049         xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ, ProgressBar::BarStyle::Level);
2050     }
2051     else if (name == "GtkScrolledWindow")
2052     {
2053         xWindow = VclPtr<VclScrolledWindow>::Create(pParent);
2054     }
2055     else if (name == "GtkViewport")
2056     {
2057         xWindow = VclPtr<VclViewport>::Create(pParent);
2058     }
2059     else if (name == "GtkEventBox")
2060     {
2061         xWindow = VclPtr<VclEventBox>::Create(pParent);
2062     }
2063     else if (name == "GtkEntry")
2064     {
2065         WinBits nWinStyle = WB_LEFT|WB_VCENTER|WB_3DLOOK;
2066         if (extractHasFrame(rMap))
2067             nWinStyle |= WB_BORDER;
2068         xWindow = VclPtr<Edit>::Create(pParent, nWinStyle);
2069         BuilderUtils::ensureDefaultWidthChars(rMap);
2070     }
2071     else if (name == "GtkNotebook")
2072     {
2073         if (!extractVerticalTabPos(rMap))
2074             xWindow = VclPtr<TabControl>::Create(pParent, WB_STDTABCONTROL|WB_3DLOOK);
2075         else
2076             xWindow = VclPtr<VerticalTabControl>::Create(pParent, extractVerticalTabsWithIcons(rMap));
2077     }
2078     else if (name == "GtkDrawingArea")
2079     {
2080         xWindow = VclPtr<VclDrawingArea>::Create(pParent, WB_TABSTOP);
2081     }
2082     else if (name == "GtkTextView")
2083     {
2084         extractBuffer(id, rMap);
2085 
2086         WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT;
2087         //VclMultiLineEdit manages its own scrolling,
2088         vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
2089         xWindow = VclPtr<VclMultiLineEdit>::Create(pRealParent, nWinStyle);
2090         if (pRealParent != pParent)
2091             cleanupWidgetOwnScrolling(pParent, xWindow, rMap);
2092     }
2093     else if (name == "GtkSpinner")
2094     {
2095         xWindow = VclPtr<Throbber>::Create(pParent, WB_3DLOOK);
2096     }
2097     else if (name == "GtkScale")
2098     {
2099         extractAdjustmentToMap(id, rMap, m_pParserState->m_aSliderAdjustmentMaps);
2100         bool bDrawValue = extractDrawValue(rMap);
2101         if (bDrawValue)
2102         {
2103             OUString sValuePos = extractValuePos(rMap);
2104             (void)sValuePos;
2105         }
2106         bVertical = extractOrientation(rMap);
2107 
2108         WinBits nWinStyle = bVertical ? WB_VERT : WB_HORZ;
2109 
2110         xWindow = VclPtr<Slider>::Create(pParent, nWinStyle);
2111     }
2112     else if (name == "GtkToolbar")
2113     {
2114         xWindow = VclPtr<ToolBox>::Create(pParent, WB_3DLOOK | WB_TABSTOP);
2115     }
2116     else if(name == "NotebookBarAddonsToolMergePoint")
2117     {
2118         customMakeWidget pFunction = GetCustomMakeWidget(u"sfxlo-NotebookbarToolBox"_ustr);
2119         if(pFunction != nullptr)
2120             NotebookBarAddonsMerger::MergeNotebookBarAddons(pParent, pFunction, m_xFrame, *m_pNotebookBarAddonsItem, rMap);
2121         return nullptr;
2122     }
2123     else if (isToolbarItemClass(name))
2124     {
2125         if (pToolBox)
2126         {
2127             OUString aCommand(extractActionName(rMap));
2128 
2129             ToolBoxItemId nItemId(0);
2130             ToolBoxItemBits nBits = ToolBoxItemBits::NONE;
2131             if (name == "GtkMenuToolButton")
2132                 nBits |= ToolBoxItemBits::DROPDOWN;
2133             else if (name == "GtkToggleToolButton")
2134                 nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::CHECKABLE;
2135             else if (name == "GtkRadioToolButton")
2136                 nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::RADIOCHECK;
2137 
2138             if (!aCommand.isEmpty() && m_xFrame.is())
2139             {
2140                 pToolBox->InsertItem(aCommand, m_xFrame, nBits, extractSizeRequest(rMap));
2141                 nItemId = pToolBox->GetItemId(aCommand);
2142             }
2143             else
2144             {
2145                 nItemId = ToolBoxItemId(pToolBox->GetItemCount() + 1);
2146                     //TODO: ImplToolItems::size_type -> sal_uInt16!
2147                 if (aCommand.isEmpty() && !m_bLegacy)
2148                     aCommand = id;
2149                 pToolBox->InsertItem(nItemId, extractLabel(rMap), aCommand, nBits);
2150             }
2151 
2152             pToolBox->SetHelpId(nItemId, m_sHelpRoot + id);
2153             OUString sTooltip(extractTooltipText(rMap));
2154             if (!sTooltip.isEmpty())
2155                 pToolBox->SetQuickHelpText(nItemId, sTooltip);
2156 
2157             OUString sIconName(extractIconName(rMap));
2158             if (!sIconName.isEmpty())
2159                 pToolBox->SetItemImage(nItemId, FixedImage::loadThemeImage(sIconName));
2160 
2161             if (!extractVisible(rMap))
2162                 pToolBox->HideItem(nItemId);
2163 
2164             m_pParserState->m_nLastToolbarId = nItemId;
2165 
2166             return nullptr; // no widget to be created
2167         }
2168     }
2169     else if (name == "GtkSeparatorToolItem")
2170     {
2171         if (pToolBox)
2172         {
2173             pToolBox->InsertSeparator();
2174             return nullptr; // no widget to be created
2175         }
2176     }
2177     else if (name == "GtkWindow")
2178     {
2179         WinBits nBits = extractDeferredBits(rMap);
2180         if (nBits & WB_DOCKABLE)
2181             xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_MOVEABLE);
2182         else
2183             xWindow = VclPtr<FloatingWindow>::Create(pParent, nBits|WB_MOVEABLE);
2184     }
2185     else if (name == "GtkPopover")
2186     {
2187         WinBits nBits = extractDeferredBits(rMap);
2188         xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_DOCKABLE|WB_MOVEABLE);
2189     }
2190     else if (name == "GtkCalendar")
2191     {
2192         WinBits nBits = extractDeferredBits(rMap);
2193         xWindow = VclPtr<Calendar>::Create(pParent, nBits);
2194     }
2195     else
2196     {
2197         if (customMakeWidget pFunction = GetCustomMakeWidget(name))
2198         {
2199             pFunction(xWindow, pParent, rMap);
2200             if (xWindow->GetType() == WindowType::PUSHBUTTON)
2201                 setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
2202             else if (xWindow->GetType() == WindowType::MENUBUTTON)
2203             {
2204                 OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
2205                 if (!sMenu.isEmpty())
2206                     m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
2207                 setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
2208             }
2209         }
2210     }
2211 
2212     SAL_INFO_IF(!xWindow, "vcl.builder", "probably need to implement " << name << " or add a make" << name << " function");
2213     if (xWindow)
2214     {
2215         // child windows of disabled windows are made disabled by vcl by default, we don't want that
2216         WindowImpl *pWindowImpl = xWindow->ImplGetWindowImpl();
2217         pWindowImpl->mbDisabled = false;
2218 
2219         xWindow->SetHelpId(m_sHelpRoot + id);
2220         SAL_INFO("vcl.builder", "for name '" << name << "' and id '" << id <<
2221             "', created " << xWindow.get() << " child of " <<
2222             pParent << "(" << xWindow->ImplGetWindowImpl()->mpParent.get() << "/" <<
2223             xWindow->ImplGetWindowImpl()->mpRealParent.get() << "/" <<
2224             xWindow->ImplGetWindowImpl()->mpBorderWindow.get() << ") with helpid " <<
2225             xWindow->GetHelpId());
2226         m_aChildren.emplace_back(id, xWindow, bVertical);
2227 
2228         // if the parent was a toolbox set it as an itemwindow for the latest itemid
2229         if (pToolBox)
2230         {
2231             Size aSize(xWindow->GetSizePixel());
2232             aSize.setHeight(xWindow->get_preferred_size().Height());
2233             xWindow->SetSizePixel(aSize);
2234             pToolBox->SetItemWindow(m_pParserState->m_nLastToolbarId, xWindow);
2235             pToolBox->SetItemExpand(m_pParserState->m_nLastToolbarId, true);
2236         }
2237     }
2238     return xWindow;
2239 }
2240 
2241 namespace
2242 {
2243     //return true for window types which exist in vcl but are not themselves
2244     //represented in the .ui format, i.e. only their children exist.
isConsideredGtkPseudo(vcl::Window const * pWindow)2245     bool isConsideredGtkPseudo(vcl::Window const *pWindow)
2246     {
2247         return pWindow->GetType() == WindowType::TABPAGE;
2248     }
2249 }
2250 
2251 //Any properties from .ui load we couldn't set because of potential virtual methods
2252 //during ctor are applied here
setDeferredProperties()2253 void VclBuilder::setDeferredProperties()
2254 {
2255     if (!m_bToplevelHasDeferredProperties)
2256         return;
2257     stringmap aDeferredProperties;
2258     aDeferredProperties.swap(m_aDeferredProperties);
2259     m_bToplevelHasDeferredProperties = false;
2260     BuilderUtils::set_properties(m_pParent, aDeferredProperties);
2261 }
2262 
2263 namespace BuilderUtils
2264 {
set_properties(vcl::Window * pWindow,const VclBuilder::stringmap & rProps)2265     void set_properties(vcl::Window *pWindow, const VclBuilder::stringmap &rProps)
2266     {
2267         for (auto const& [rKey, rValue] : rProps)
2268             pWindow->set_property(rKey, rValue);
2269     }
2270 
convertMnemonicMarkup(std::u16string_view rIn)2271     OUString convertMnemonicMarkup(std::u16string_view rIn)
2272     {
2273         OUStringBuffer aRet(rIn);
2274         for (sal_Int32 nI = 0; nI < aRet.getLength(); ++nI)
2275         {
2276             if (aRet[nI] == '_' && nI+1 < aRet.getLength())
2277             {
2278                 if (aRet[nI+1] != '_')
2279                     aRet[nI] = MNEMONIC_CHAR;
2280                 else
2281                     aRet.remove(nI, 1);
2282                 ++nI;
2283             }
2284         }
2285         return aRet.makeStringAndClear();
2286     }
2287 
extractCustomProperty(VclBuilder::stringmap & rMap)2288     OUString extractCustomProperty(VclBuilder::stringmap &rMap)
2289     {
2290         OUString sCustomProperty;
2291         VclBuilder::stringmap::iterator aFind = rMap.find(u"customproperty"_ustr);
2292         if (aFind != rMap.end())
2293         {
2294             sCustomProperty = aFind->second;
2295             rMap.erase(aFind);
2296         }
2297         return sCustomProperty;
2298     }
2299 
ensureDefaultWidthChars(VclBuilder::stringmap & rMap)2300     void ensureDefaultWidthChars(VclBuilder::stringmap &rMap)
2301     {
2302         OUString sWidthChars(u"width-chars"_ustr);
2303         VclBuilder::stringmap::iterator aFind = rMap.find(sWidthChars);
2304         if (aFind == rMap.end())
2305             rMap[sWidthChars] = "20";
2306     }
2307 
extractDropdown(VclBuilder::stringmap & rMap)2308     bool extractDropdown(VclBuilder::stringmap &rMap)
2309     {
2310         bool bDropdown = true;
2311         VclBuilder::stringmap::iterator aFind = rMap.find(u"dropdown"_ustr);
2312         if (aFind != rMap.end())
2313         {
2314             bDropdown = toBool(aFind->second);
2315             rMap.erase(aFind);
2316         }
2317         return bDropdown;
2318     }
2319 
reorderWithinParent(vcl::Window & rWindow,sal_uInt16 nNewPosition)2320     void reorderWithinParent(vcl::Window &rWindow, sal_uInt16 nNewPosition)
2321     {
2322         WindowImpl *pWindowImpl = rWindow.ImplGetWindowImpl();
2323         if (pWindowImpl->mpParent != pWindowImpl->mpRealParent)
2324         {
2325             assert(pWindowImpl->mpBorderWindow == pWindowImpl->mpParent);
2326             assert(pWindowImpl->mpBorderWindow->ImplGetWindowImpl()->mpParent == pWindowImpl->mpRealParent);
2327             reorderWithinParent(*pWindowImpl->mpBorderWindow, nNewPosition);
2328             return;
2329         }
2330         rWindow.reorderWithinParent(nNewPosition);
2331     }
2332 
reorderWithinParent(std::vector<vcl::Window * > & rChilds,bool bIsButtonBox)2333     void reorderWithinParent(std::vector<vcl::Window*>& rChilds, bool bIsButtonBox)
2334     {
2335         for (size_t i = 0; i < rChilds.size(); ++i)
2336         {
2337             reorderWithinParent(*rChilds[i], i);
2338 
2339             if (!bIsButtonBox)
2340                 continue;
2341 
2342             //The first member of the group for legacy code needs WB_GROUP set and the
2343             //others not
2344             WinBits nBits = rChilds[i]->GetStyle();
2345             nBits &= ~WB_GROUP;
2346             if (i == 0)
2347                 nBits |= WB_GROUP;
2348             rChilds[i]->SetStyle(nBits);
2349         }
2350     }
2351 
getRoleFromName(const OUString & roleName)2352     sal_Int16 getRoleFromName(const OUString& roleName)
2353     {
2354         using namespace com::sun::star::accessibility;
2355 
2356         static const std::unordered_map<OUString, sal_Int16> aAtkRoleToAccessibleRole = {
2357             /* This is in atkobject.h's AtkRole order */
2358             { "invalid",               AccessibleRole::UNKNOWN },
2359             { "accelerator label",     AccessibleRole::UNKNOWN },
2360             { "alert",                 AccessibleRole::ALERT },
2361             { "animation",             AccessibleRole::UNKNOWN },
2362             { "arrow",                 AccessibleRole::UNKNOWN },
2363             { "calendar",              AccessibleRole::UNKNOWN },
2364             { "canvas",                AccessibleRole::CANVAS },
2365             { "check box",             AccessibleRole::CHECK_BOX },
2366             { "check menu item",       AccessibleRole::CHECK_MENU_ITEM },
2367             { "color chooser",         AccessibleRole::COLOR_CHOOSER },
2368             { "column header",         AccessibleRole::COLUMN_HEADER },
2369             { "combo box",             AccessibleRole::COMBO_BOX },
2370             { "date editor",           AccessibleRole::DATE_EDITOR },
2371             { "desktop icon",          AccessibleRole::DESKTOP_ICON },
2372             { "desktop frame",         AccessibleRole::DESKTOP_PANE }, // ?
2373             { "dial",                  AccessibleRole::UNKNOWN },
2374             { "dialog",                AccessibleRole::DIALOG },
2375             { "directory pane",        AccessibleRole::DIRECTORY_PANE },
2376             { "drawing area",          AccessibleRole::UNKNOWN },
2377             { "file chooser",          AccessibleRole::FILE_CHOOSER },
2378             { "filler",                AccessibleRole::FILLER },
2379             { "font chooser",          AccessibleRole::FONT_CHOOSER },
2380             { "frame",                 AccessibleRole::FRAME },
2381             { "glass pane",            AccessibleRole::GLASS_PANE },
2382             { "html container",        AccessibleRole::UNKNOWN },
2383             { "icon",                  AccessibleRole::ICON },
2384             { "image",                 AccessibleRole::GRAPHIC },
2385             { "internal frame",        AccessibleRole::INTERNAL_FRAME },
2386             { "label",                 AccessibleRole::LABEL },
2387             { "layered pane",          AccessibleRole::LAYERED_PANE },
2388             { "list",                  AccessibleRole::LIST },
2389             { "list item",             AccessibleRole::LIST_ITEM },
2390             { "menu",                  AccessibleRole::MENU },
2391             { "menu bar",              AccessibleRole::MENU_BAR },
2392             { "menu item",             AccessibleRole::MENU_ITEM },
2393             { "option pane",           AccessibleRole::OPTION_PANE },
2394             { "page tab",              AccessibleRole::PAGE_TAB },
2395             { "page tab list",         AccessibleRole::PAGE_TAB_LIST },
2396             { "panel",                 AccessibleRole::PANEL }, // or SHAPE or TEXT_FRAME ?
2397             { "password text",         AccessibleRole::PASSWORD_TEXT },
2398             { "popup menu",            AccessibleRole::POPUP_MENU },
2399             { "progress bar",          AccessibleRole::PROGRESS_BAR },
2400             { "push button",           AccessibleRole::PUSH_BUTTON }, // or BUTTON_DROPDOWN or BUTTON_MENU
2401             { "radio button",          AccessibleRole::RADIO_BUTTON },
2402             { "radio menu item",       AccessibleRole::RADIO_MENU_ITEM },
2403             { "root pane",             AccessibleRole::ROOT_PANE },
2404             { "row header",            AccessibleRole::ROW_HEADER },
2405             { "scroll bar",            AccessibleRole::SCROLL_BAR },
2406             { "scroll pane",           AccessibleRole::SCROLL_PANE },
2407             { "separator",             AccessibleRole::SEPARATOR },
2408             { "slider",                AccessibleRole::SLIDER },
2409             { "split pane",            AccessibleRole::SPLIT_PANE },
2410             { "spin button",           AccessibleRole::SPIN_BOX }, // ?
2411             { "statusbar",             AccessibleRole::STATUS_BAR },
2412             { "table",                 AccessibleRole::TABLE },
2413             { "table cell",            AccessibleRole::TABLE_CELL },
2414             { "table column header",   AccessibleRole::COLUMN_HEADER }, // approximate
2415             { "table row header",      AccessibleRole::ROW_HEADER }, // approximate
2416             { "tear off menu item",    AccessibleRole::UNKNOWN },
2417             { "terminal",              AccessibleRole::UNKNOWN },
2418             { "text",                  AccessibleRole::TEXT },
2419             { "toggle button",         AccessibleRole::TOGGLE_BUTTON },
2420             { "tool bar",              AccessibleRole::TOOL_BAR },
2421             { "tool tip",              AccessibleRole::TOOL_TIP },
2422             { "tree",                  AccessibleRole::TREE },
2423             { "tree table",            AccessibleRole::TREE_TABLE },
2424             { "unknown",               AccessibleRole::UNKNOWN },
2425             { "viewport",              AccessibleRole::VIEW_PORT },
2426             { "window",                AccessibleRole::WINDOW },
2427             { "header",                AccessibleRole::HEADER },
2428             { "footer",                AccessibleRole::FOOTER },
2429             { "paragraph",             AccessibleRole::PARAGRAPH },
2430             { "ruler",                 AccessibleRole::RULER },
2431             { "application",           AccessibleRole::UNKNOWN },
2432             { "autocomplete",          AccessibleRole::UNKNOWN },
2433             { "edit bar",              AccessibleRole::EDIT_BAR },
2434             { "embedded",              AccessibleRole::EMBEDDED_OBJECT },
2435             { "entry",                 AccessibleRole::UNKNOWN },
2436             { "chart",                 AccessibleRole::CHART },
2437             { "caption",               AccessibleRole::CAPTION },
2438             { "document frame",        AccessibleRole::DOCUMENT },
2439             { "heading",               AccessibleRole::HEADING },
2440             { "page",                  AccessibleRole::PAGE },
2441             { "section",               AccessibleRole::SECTION },
2442             { "redundant object",      AccessibleRole::UNKNOWN },
2443             { "form",                  AccessibleRole::FORM },
2444             { "link",                  AccessibleRole::HYPER_LINK },
2445             { "input method window",   AccessibleRole::UNKNOWN },
2446             { "table row",             AccessibleRole::UNKNOWN },
2447             { "tree item",             AccessibleRole::TREE_ITEM },
2448             { "document spreadsheet",  AccessibleRole::DOCUMENT_SPREADSHEET },
2449             { "document presentation", AccessibleRole::DOCUMENT_PRESENTATION },
2450             { "document text",         AccessibleRole::DOCUMENT_TEXT },
2451             { "document web",          AccessibleRole::DOCUMENT }, // approximate
2452             { "document email",        AccessibleRole::DOCUMENT }, // approximate
2453             { "comment",               AccessibleRole::COMMENT }, // or NOTE or END_NOTE or FOOTNOTE or SCROLL_PANE
2454             { "list box",              AccessibleRole::UNKNOWN },
2455             { "grouping",              AccessibleRole::GROUP_BOX },
2456             { "image map",             AccessibleRole::IMAGE_MAP },
2457             { "notification",          AccessibleRole::NOTIFICATION },
2458             { "info bar",              AccessibleRole::UNKNOWN },
2459             { "level bar",             AccessibleRole::UNKNOWN },
2460             { "title bar",             AccessibleRole::UNKNOWN },
2461             { "block quote",           AccessibleRole::BLOCK_QUOTE },
2462             { "audio",                 AccessibleRole::UNKNOWN },
2463             { "video",                 AccessibleRole::UNKNOWN },
2464             { "definition",            AccessibleRole::UNKNOWN },
2465             { "article",               AccessibleRole::UNKNOWN },
2466             { "landmark",              AccessibleRole::UNKNOWN },
2467             { "log",                   AccessibleRole::UNKNOWN },
2468             { "marquee",               AccessibleRole::UNKNOWN },
2469             { "math",                  AccessibleRole::UNKNOWN },
2470             { "rating",                AccessibleRole::UNKNOWN },
2471             { "timer",                 AccessibleRole::UNKNOWN },
2472             { "description list",      AccessibleRole::UNKNOWN },
2473             { "description term",      AccessibleRole::UNKNOWN },
2474             { "description value",     AccessibleRole::UNKNOWN },
2475             { "static",                AccessibleRole::STATIC },
2476             { "math fraction",         AccessibleRole::UNKNOWN },
2477             { "math root",             AccessibleRole::UNKNOWN },
2478             { "subscript",             AccessibleRole::UNKNOWN },
2479             { "superscript",           AccessibleRole::UNKNOWN },
2480             { "footnote",              AccessibleRole::FOOTNOTE },
2481         };
2482 
2483         auto it = aAtkRoleToAccessibleRole.find(roleName);
2484         if (it == aAtkRoleToAccessibleRole.end())
2485             return AccessibleRole::UNKNOWN;
2486         return it->second;
2487     }
2488 }
2489 
insertObject(vcl::Window * pParent,const OUString & rClass,const OUString & rID,stringmap & rProps,stringmap & rPango,stringmap & rAtk)2490 VclPtr<vcl::Window> VclBuilder::insertObject(vcl::Window *pParent, const OUString &rClass,
2491     const OUString &rID, stringmap &rProps, stringmap &rPango, stringmap &rAtk)
2492 {
2493     VclPtr<vcl::Window> pCurrentChild;
2494 
2495     if (m_pParent && !isConsideredGtkPseudo(m_pParent) && !m_sID.isEmpty() && rID == m_sID)
2496     {
2497         pCurrentChild = m_pParent;
2498 
2499         //toplevels default to resizable and apparently you can't change them
2500         //afterwards, so we need to wait until now before we can truly
2501         //initialize the dialog.
2502         if (pParent && pParent->IsSystemWindow())
2503         {
2504             SystemWindow *pSysWin = static_cast<SystemWindow*>(pCurrentChild.get());
2505             pSysWin->doDeferredInit(extractDeferredBits(rProps));
2506             m_bToplevelHasDeferredInit = false;
2507         }
2508         else if (pParent && pParent->IsDockingWindow())
2509         {
2510             DockingWindow *pDockWin = static_cast<DockingWindow*>(pCurrentChild.get());
2511             pDockWin->doDeferredInit(extractDeferredBits(rProps));
2512             m_bToplevelHasDeferredInit = false;
2513         }
2514 
2515         if (pCurrentChild->GetHelpId().isEmpty())
2516         {
2517             pCurrentChild->SetHelpId(m_sHelpRoot + m_sID);
2518             SAL_INFO("vcl.builder", "for toplevel dialog " << this << " " <<
2519                 rID << ", set helpid " << pCurrentChild->GetHelpId());
2520         }
2521         m_bToplevelParentFound = true;
2522     }
2523     else
2524     {
2525         //if we're being inserting under a toplevel dialog whose init is
2526         //deferred due to waiting to encounter it in this .ui, and it hasn't
2527         //been seen yet, then make unattached widgets parent-less toplevels
2528         if (pParent == m_pParent.get() && m_bToplevelHasDeferredInit)
2529             pParent = nullptr;
2530         pCurrentChild = makeObject(pParent, rClass, rID, rProps);
2531     }
2532 
2533     if (pCurrentChild)
2534     {
2535         pCurrentChild->set_id(rID);
2536         if (pCurrentChild == m_pParent.get() && m_bToplevelHasDeferredProperties)
2537             m_aDeferredProperties = rProps;
2538         else
2539             BuilderUtils::set_properties(pCurrentChild, rProps);
2540 
2541         // tdf#119827 handle size before scale so we can trivially
2542         // scale on the current font size whether size is present
2543         // or not.
2544         VclBuilder::stringmap::iterator aSize = rPango.find(u"size"_ustr);
2545         if (aSize != rPango.end())
2546         {
2547             pCurrentChild->set_font_attribute(aSize->first, aSize->second);
2548             rPango.erase(aSize);
2549         }
2550         for (auto const& [ rKey, rValue ] : rPango)
2551             pCurrentChild->set_font_attribute(rKey, rValue);
2552 
2553         m_pParserState->m_aAtkInfo[pCurrentChild] = rAtk;
2554     }
2555 
2556     rProps.clear();
2557     rPango.clear();
2558     rAtk.clear();
2559 
2560     if (!pCurrentChild)
2561     {
2562         bool bToolbarParent = (pParent && pParent->GetType() == WindowType::TOOLBOX);
2563         pCurrentChild = (m_aChildren.empty() || bToolbarParent) ? pParent : m_aChildren.back().m_pWindow.get();
2564     }
2565     return pCurrentChild;
2566 }
2567 
handleTabChild(vcl::Window * pParent,xmlreader::XmlReader & reader)2568 void VclBuilder::handleTabChild(vcl::Window *pParent, xmlreader::XmlReader &reader)
2569 {
2570     TabControl *pTabControl = pParent && pParent->GetType() == WindowType::TABCONTROL ?
2571         static_cast<TabControl*>(pParent) : nullptr;
2572 
2573     std::vector<OUString> sIDs;
2574 
2575     int nLevel = 1;
2576     stringmap aProperties;
2577     stringmap aAtkProperties;
2578     std::vector<vcl::EnumContext::Context> context;
2579 
2580     while(true)
2581     {
2582         xmlreader::Span name;
2583         int nsId;
2584 
2585         xmlreader::XmlReader::Result res = reader.nextItem(
2586             xmlreader::XmlReader::Text::NONE, &name, &nsId);
2587 
2588         if (res == xmlreader::XmlReader::Result::Begin)
2589         {
2590             ++nLevel;
2591             if (name == "object")
2592             {
2593                 while (reader.nextAttribute(&nsId, &name))
2594                 {
2595                     if (name == "id")
2596                     {
2597                         name = reader.getAttributeValue(false);
2598                         OUString sID(name.begin, name.length, RTL_TEXTENCODING_UTF8);
2599                         sal_Int32 nDelim = sID.indexOf(':');
2600                         if (nDelim != -1)
2601                         {
2602                             aProperties[u"customproperty"_ustr] = sID.copy(nDelim + 1);
2603                             sID = sID.copy(0, nDelim);
2604                         }
2605                         sIDs.push_back(sID);
2606                     }
2607                 }
2608             }
2609             else if (name == "style")
2610             {
2611                 int nPriority = 0;
2612                 context = handleStyle(reader, nPriority);
2613                 --nLevel;
2614             }
2615             else if (name == "property")
2616                 collectProperty(reader, aProperties);
2617             else if (pTabControl && name == "child")
2618             {
2619                 // just to collect the atk properties (if any) for the label
2620                 handleChild(nullptr, &aAtkProperties, reader);
2621                 --nLevel;
2622             }
2623         }
2624 
2625         if (res == xmlreader::XmlReader::Result::End)
2626             --nLevel;
2627 
2628         if (!nLevel)
2629             break;
2630 
2631         if (res == xmlreader::XmlReader::Result::Done)
2632             break;
2633     }
2634 
2635     if (!pParent)
2636         return;
2637 
2638     VerticalTabControl *pVerticalTabControl = pParent->GetType() == WindowType::VERTICALTABCONTROL ?
2639         static_cast<VerticalTabControl*>(pParent) : nullptr;
2640     assert(pTabControl || pVerticalTabControl);
2641     VclBuilder::stringmap::iterator aFind = aProperties.find(u"label"_ustr);
2642     if (aFind != aProperties.end())
2643     {
2644         OUString sTooltip(extractTooltipText(aProperties));
2645         if (pTabControl)
2646         {
2647             sal_uInt16 nPageId = pTabControl->GetCurPageId();
2648             pTabControl->SetPageText(nPageId, aFind->second);
2649             pTabControl->SetPageName(nPageId, sIDs.back());
2650             pTabControl->SetHelpText(nPageId, sTooltip);
2651             if (!context.empty())
2652             {
2653                 TabPage* pPage = pTabControl->GetTabPage(nPageId);
2654                 pPage->SetContext(std::move(context));
2655             }
2656 
2657             for (auto const& [ rKey, rValue ] : aAtkProperties)
2658             {
2659                 if (rKey == "AtkObject::accessible-name")
2660                     pTabControl->SetAccessibleName(nPageId, rValue);
2661                 else if (rKey == "AtkObject::accessible-description")
2662                     pTabControl->SetAccessibleDescription(nPageId, rValue);
2663                 else
2664                     SAL_INFO("vcl.builder", "unhandled atk property: " << rKey);
2665             }
2666 
2667         }
2668         else
2669         {
2670             OUString sLabel(BuilderUtils::convertMnemonicMarkup(aFind->second));
2671             OUString sIconName(extractIconName(aProperties));
2672             pVerticalTabControl->InsertPage(sIDs.front(), sLabel, FixedImage::loadThemeImage(sIconName), sTooltip,
2673                                             pVerticalTabControl->GetPageParent()->GetWindow(GetWindowType::LastChild));
2674         }
2675     }
2676     else
2677     {
2678         if (pTabControl)
2679             pTabControl->RemovePage(pTabControl->GetCurPageId());
2680     }
2681 }
2682 
2683 //so that tabbing between controls goes in a visually sensible sequence
2684 //we sort these into a best-tab-order sequence
operator ()(const vcl::Window * pA,const vcl::Window * pB) const2685 bool VclBuilder::sortIntoBestTabTraversalOrder::operator()(const vcl::Window *pA, const vcl::Window *pB) const
2686 {
2687     //sort child order within parent list by grid position
2688     sal_Int32 nTopA = pA->get_grid_top_attach();
2689     sal_Int32 nTopB = pB->get_grid_top_attach();
2690     if (nTopA < nTopB)
2691         return true;
2692     if (nTopA > nTopB)
2693         return false;
2694     sal_Int32 nLeftA = pA->get_grid_left_attach();
2695     sal_Int32 nLeftB = pB->get_grid_left_attach();
2696     if (nLeftA < nLeftB)
2697         return true;
2698     if (nLeftA > nLeftB)
2699         return false;
2700     //sort into two groups of pack start and pack end
2701     VclPackType ePackA = pA->get_pack_type();
2702     VclPackType ePackB = pB->get_pack_type();
2703     if (ePackA < ePackB)
2704         return true;
2705     if (ePackA > ePackB)
2706         return false;
2707     bool bVerticalContainer = m_pBuilder->get_window_packing_data(pA->GetParent()).m_bVerticalOrient;
2708     bool bPackA = pA->get_secondary();
2709     bool bPackB = pB->get_secondary();
2710     if (!bVerticalContainer)
2711     {
2712         //for horizontal boxes group secondaries before primaries
2713         if (bPackA > bPackB)
2714             return true;
2715         if (bPackA < bPackB)
2716             return false;
2717     }
2718     else
2719     {
2720         //for vertical boxes group secondaries after primaries
2721         if (bPackA < bPackB)
2722             return true;
2723         if (bPackA > bPackB)
2724             return false;
2725     }
2726     //honour relative box positions with pack group, (numerical order is reversed
2727     //for VclPackType::End, they are packed from the end back, but here we need
2728     //them in visual layout order so that tabbing works as expected)
2729     sal_Int32 nPackA = m_pBuilder->get_window_packing_data(pA).m_nPosition;
2730     sal_Int32 nPackB = m_pBuilder->get_window_packing_data(pB).m_nPosition;
2731     if (nPackA < nPackB)
2732         return ePackA == VclPackType::Start;
2733     if (nPackA > nPackB)
2734         return ePackA != VclPackType::Start;
2735     //sort labels of Frames before body
2736     if (pA->GetParent() == pB->GetParent())
2737     {
2738         const VclFrame *pFrameParent = dynamic_cast<const VclFrame*>(pA->GetParent());
2739         if (pFrameParent)
2740         {
2741             const vcl::Window *pLabel = pFrameParent->get_label_widget();
2742             int nFramePosA = (pA == pLabel) ? 0 : 1;
2743             int nFramePosB = (pB == pLabel) ? 0 : 1;
2744             return nFramePosA < nFramePosB;
2745         }
2746     }
2747     return false;
2748 }
2749 
tweakInsertedChild(vcl::Window * pParent,vcl::Window * pCurrentChild,std::string_view sType,std::string_view sInternalChild)2750 void VclBuilder::tweakInsertedChild(vcl::Window *pParent, vcl::Window* pCurrentChild,
2751                                     std::string_view sType, std::string_view sInternalChild)
2752 {
2753     assert(pCurrentChild);
2754 
2755     //Internal-children default in glade to not having their visible bits set
2756     //even though they are visible (generally anyway)
2757     if (!sInternalChild.empty())
2758         pCurrentChild->Show();
2759 
2760     //Select the first page if it's a notebook
2761     if (pCurrentChild->GetType() == WindowType::TABCONTROL)
2762     {
2763         TabControl *pTabControl = static_cast<TabControl*>(pCurrentChild);
2764         pTabControl->SetCurPageId(pTabControl->GetPageId(0));
2765 
2766         //To-Do add reorder capability to the TabControl
2767     }
2768     else
2769     {
2770         // We want to sort labels before contents of frames
2771         // for keyboard traversal, especially if there
2772         // are multiple widgets using the same mnemonic
2773         if (sType == "label")
2774         {
2775             if (VclFrame *pFrameParent = dynamic_cast<VclFrame*>(pParent))
2776                 pFrameParent->designate_label(pCurrentChild);
2777         }
2778         if (sInternalChild.starts_with("vbox") || sInternalChild.starts_with("messagedialog-vbox"))
2779         {
2780             if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pParent))
2781                 pBoxParent->set_content_area(static_cast<VclBox*>(pCurrentChild)); // FIXME-VCLPTR
2782         }
2783         else if (sInternalChild.starts_with("action_area") || sInternalChild.starts_with("messagedialog-action_area"))
2784         {
2785             vcl::Window *pContentArea = pCurrentChild->GetParent();
2786             if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pContentArea ? pContentArea->GetParent() : nullptr))
2787             {
2788                 pBoxParent->set_action_area(static_cast<VclButtonBox*>(pCurrentChild)); // FIXME-VCLPTR
2789             }
2790         }
2791 
2792         bool bIsButtonBox = dynamic_cast<VclButtonBox*>(pCurrentChild) != nullptr;
2793 
2794         //To-Do make reorder a virtual in Window, move this foo
2795         //there and see above
2796         std::vector<vcl::Window*> aChilds;
2797         for (vcl::Window* pChild = pCurrentChild->GetWindow(GetWindowType::FirstChild); pChild;
2798              pChild = pChild->GetWindow(GetWindowType::Next))
2799         {
2800             if (bIsButtonBox)
2801             {
2802                 if (PushButton* pPushButton = dynamic_cast<PushButton*>(pChild))
2803                     pPushButton->setAction(true);
2804             }
2805 
2806             aChilds.push_back(pChild);
2807         }
2808 
2809         //sort child order within parent so that tabbing
2810         //between controls goes in a visually sensible sequence
2811         std::stable_sort(aChilds.begin(), aChilds.end(), sortIntoBestTabTraversalOrder(this));
2812         BuilderUtils::reorderWithinParent(aChilds, bIsButtonBox);
2813     }
2814 }
2815 
handleChild(vcl::Window * pParent,stringmap * pAtkProps,xmlreader::XmlReader & reader,bool bToolbarItem)2816 void VclBuilder::handleChild(vcl::Window *pParent, stringmap* pAtkProps, xmlreader::XmlReader &reader, bool bToolbarItem)
2817 {
2818     xmlreader::Span name;
2819     int nsId;
2820     OString sType, sInternalChild;
2821 
2822     while (reader.nextAttribute(&nsId, &name))
2823     {
2824         if (name == "type")
2825         {
2826             name = reader.getAttributeValue(false);
2827             sType = OString(name.begin, name.length);
2828         }
2829         else if (name == "internal-child")
2830         {
2831             name = reader.getAttributeValue(false);
2832             sInternalChild = OString(name.begin, name.length);
2833         }
2834     }
2835 
2836     if (sType == "tab")
2837     {
2838         handleTabChild(pParent, reader);
2839         return;
2840     }
2841 
2842     VclPtr<vcl::Window> pCurrentChild;
2843     int nLevel = 1;
2844     while(true)
2845     {
2846         xmlreader::XmlReader::Result res = reader.nextItem(
2847             xmlreader::XmlReader::Text::NONE, &name, &nsId);
2848 
2849         if (res == xmlreader::XmlReader::Result::Begin)
2850         {
2851             if (name == "object" || name == "placeholder")
2852             {
2853                 pCurrentChild = handleObject(pParent, pAtkProps, reader, bToolbarItem);
2854 
2855                 bool bObjectInserted = pCurrentChild && pParent != pCurrentChild;
2856                 if (bObjectInserted)
2857                     tweakInsertedChild(pParent, pCurrentChild, sType, sInternalChild);
2858             }
2859             else if (name == "packing")
2860             {
2861                 handlePacking(pCurrentChild, pParent, reader);
2862             }
2863             else if (name == "interface")
2864             {
2865                 while (reader.nextAttribute(&nsId, &name))
2866                 {
2867                     if (name == "domain")
2868                     {
2869                         name = reader.getAttributeValue(false);
2870                         sType = OString(name.begin, name.length);
2871                         m_pParserState->m_aResLocale = Translate::Create(sType);
2872                     }
2873                 }
2874                 ++nLevel;
2875             }
2876             else
2877                 ++nLevel;
2878         }
2879 
2880         if (res == xmlreader::XmlReader::Result::End)
2881             --nLevel;
2882 
2883         if (!nLevel)
2884             break;
2885 
2886         if (res == xmlreader::XmlReader::Result::Done)
2887             break;
2888     }
2889 }
2890 
collectPangoAttribute(xmlreader::XmlReader & reader,stringmap & rMap)2891 void BuilderBase::collectPangoAttribute(xmlreader::XmlReader& reader, stringmap& rMap)
2892 {
2893     xmlreader::Span span;
2894     int nsId;
2895 
2896     OUString sProperty;
2897     OUString sValue;
2898 
2899     while (reader.nextAttribute(&nsId, &span))
2900     {
2901         if (span == "name")
2902         {
2903             span = reader.getAttributeValue(false);
2904             sProperty = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
2905         }
2906         else if (span == "value")
2907         {
2908             span = reader.getAttributeValue(false);
2909             sValue = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
2910         }
2911     }
2912 
2913     if (!sProperty.isEmpty())
2914         rMap[sProperty] = sValue;
2915 }
2916 
collectAtkRelationAttribute(xmlreader::XmlReader & reader,stringmap & rMap)2917 void BuilderBase::collectAtkRelationAttribute(xmlreader::XmlReader& reader, stringmap& rMap)
2918 {
2919     xmlreader::Span span;
2920     int nsId;
2921 
2922     OUString sProperty;
2923     OUString sValue;
2924 
2925     while (reader.nextAttribute(&nsId, &span))
2926     {
2927         if (span == "type")
2928         {
2929             span = reader.getAttributeValue(false);
2930             sProperty = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
2931         }
2932         else if (span == "target")
2933         {
2934             span = reader.getAttributeValue(false);
2935             sValue = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
2936             sal_Int32 nDelim = sValue.indexOf(':');
2937             if (nDelim != -1)
2938                 sValue = sValue.copy(0, nDelim);
2939         }
2940     }
2941 
2942     if (!sProperty.isEmpty())
2943         rMap[sProperty] = sValue;
2944 }
2945 
collectAtkRoleAttribute(xmlreader::XmlReader & reader,stringmap & rMap)2946 void BuilderBase::collectAtkRoleAttribute(xmlreader::XmlReader& reader, stringmap& rMap)
2947 {
2948     xmlreader::Span span;
2949     int nsId;
2950 
2951     OUString sProperty;
2952 
2953     while (reader.nextAttribute(&nsId, &span))
2954     {
2955         if (span == "type")
2956         {
2957             span = reader.getAttributeValue(false);
2958             sProperty = OUString(span.begin, span.length, RTL_TEXTENCODING_UTF8);
2959         }
2960     }
2961 
2962     if (!sProperty.isEmpty())
2963         rMap[u"role"_ustr] = sProperty;
2964 }
2965 
handleRow(xmlreader::XmlReader & reader,const OUString & rID)2966 void VclBuilder::handleRow(xmlreader::XmlReader &reader, const OUString &rID)
2967 {
2968     int nLevel = 1;
2969 
2970     ListStore::row aRow;
2971 
2972     while(true)
2973     {
2974         xmlreader::Span name;
2975         int nsId;
2976 
2977         xmlreader::XmlReader::Result res = reader.nextItem(
2978             xmlreader::XmlReader::Text::NONE, &name, &nsId);
2979 
2980         if (res == xmlreader::XmlReader::Result::Done)
2981             break;
2982 
2983         if (res == xmlreader::XmlReader::Result::Begin)
2984         {
2985             ++nLevel;
2986             if (name == "col")
2987             {
2988                 bool bTranslated = false;
2989                 sal_uInt32 nId = 0;
2990                 OString sContext;
2991 
2992                 while (reader.nextAttribute(&nsId, &name))
2993                 {
2994                     if (name == "id")
2995                     {
2996                         name = reader.getAttributeValue(false);
2997                         nId = o3tl::toUInt32(std::string_view(name.begin, name.length));
2998                     }
2999                     else if (nId == 0 && name == "translatable" && reader.getAttributeValue(false) == "yes")
3000                     {
3001                         bTranslated = true;
3002                     }
3003                     else if (name == "context")
3004                     {
3005                         name = reader.getAttributeValue(false);
3006                         sContext = OString(name.begin, name.length);
3007                     }
3008                 }
3009 
3010                 (void)reader.nextItem(
3011                     xmlreader::XmlReader::Text::Raw, &name, &nsId);
3012 
3013                 OString sValue(name.begin, name.length);
3014                 OUString sFinalValue;
3015                 if (bTranslated)
3016                 {
3017                     sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale);
3018                 }
3019                 else
3020                     sFinalValue = OUString::fromUtf8(sValue);
3021 
3022 
3023                 if (aRow.size() < nId+1)
3024                     aRow.resize(nId+1);
3025                 aRow[nId] = sFinalValue;
3026             }
3027         }
3028 
3029         if (res == xmlreader::XmlReader::Result::End)
3030         {
3031             --nLevel;
3032         }
3033 
3034         if (!nLevel)
3035             break;
3036     }
3037 
3038     m_pParserState->m_aModels[rID].m_aEntries.push_back(aRow);
3039 }
3040 
handleListStore(xmlreader::XmlReader & reader,const OUString & rID,std::u16string_view rClass)3041 void VclBuilder::handleListStore(xmlreader::XmlReader &reader, const OUString &rID, std::u16string_view rClass)
3042 {
3043     int nLevel = 1;
3044 
3045     while(true)
3046     {
3047         xmlreader::Span name;
3048         int nsId;
3049 
3050         xmlreader::XmlReader::Result res = reader.nextItem(
3051             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3052 
3053         if (res == xmlreader::XmlReader::Result::Done)
3054             break;
3055 
3056         if (res == xmlreader::XmlReader::Result::Begin)
3057         {
3058             if (name == "row")
3059             {
3060                 bool bNotTreeStore = rClass != u"GtkTreeStore";
3061                 if (bNotTreeStore)
3062                     handleRow(reader, rID);
3063                 assert(bNotTreeStore && "gtk, as the time of writing, doesn't support data in GtkTreeStore serialization");
3064             }
3065             else
3066                 ++nLevel;
3067         }
3068 
3069         if (res == xmlreader::XmlReader::Result::End)
3070         {
3071             --nLevel;
3072         }
3073 
3074         if (!nLevel)
3075             break;
3076     }
3077 }
3078 
handleAtkObject(xmlreader::XmlReader & reader) const3079 VclBuilder::stringmap VclBuilder::handleAtkObject(xmlreader::XmlReader &reader) const
3080 {
3081     int nLevel = 1;
3082 
3083     stringmap aProperties;
3084 
3085     while (true)
3086     {
3087         xmlreader::Span name;
3088         int nsId;
3089 
3090         xmlreader::XmlReader::Result res = reader.nextItem(
3091             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3092 
3093         if (res == xmlreader::XmlReader::Result::Done)
3094             break;
3095 
3096         if (res == xmlreader::XmlReader::Result::Begin)
3097         {
3098             ++nLevel;
3099             if (name == "property")
3100                 collectProperty(reader, aProperties);
3101         }
3102 
3103         if (res == xmlreader::XmlReader::Result::End)
3104         {
3105             --nLevel;
3106         }
3107 
3108         if (!nLevel)
3109             break;
3110     }
3111 
3112     return aProperties;
3113 }
3114 
applyAtkProperties(vcl::Window * pWindow,const stringmap & rProperties,bool bToolbarItem)3115 void VclBuilder::applyAtkProperties(vcl::Window *pWindow, const stringmap& rProperties, bool bToolbarItem)
3116 {
3117     assert(pWindow);
3118     for (auto const& [ rKey, rValue ] : rProperties)
3119     {
3120         if (bToolbarItem)
3121         {
3122             // apply accessible name to the toolbar item
3123             if (rKey == u"AtkObject::accessible-name")
3124             {
3125                 ToolBox* pToolBox = dynamic_cast<ToolBox*>(pWindow);
3126                 assert(pToolBox);
3127                 pToolBox->SetAccessibleName(m_pParserState->m_nLastToolbarId, rValue);
3128             }
3129         }
3130         else if (pWindow && rKey.match("AtkObject::"))
3131             pWindow->set_property(rKey.copy(RTL_CONSTASCII_LENGTH("AtkObject::")), rValue);
3132         else
3133             SAL_WARN("vcl.builder", "unhandled atk prop: " << rKey);
3134     }
3135 }
3136 
handleItems(xmlreader::XmlReader & reader) const3137 std::vector<ComboBoxTextItem> VclBuilder::handleItems(xmlreader::XmlReader &reader) const
3138 {
3139     int nLevel = 1;
3140 
3141     std::vector<ComboBoxTextItem> aItems;
3142 
3143     while(true)
3144     {
3145         xmlreader::Span name;
3146         int nsId;
3147 
3148         xmlreader::XmlReader::Result res = reader.nextItem(
3149             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3150 
3151         if (res == xmlreader::XmlReader::Result::Done)
3152             break;
3153 
3154         if (res == xmlreader::XmlReader::Result::Begin)
3155         {
3156             ++nLevel;
3157             if (name == "item")
3158             {
3159                 bool bTranslated = false;
3160                 OString sContext;
3161                 OUString sId;
3162 
3163                 while (reader.nextAttribute(&nsId, &name))
3164                 {
3165                     if (name == "translatable" && reader.getAttributeValue(false) == "yes")
3166                     {
3167                         bTranslated = true;
3168                     }
3169                     else if (name == "context")
3170                     {
3171                         name = reader.getAttributeValue(false);
3172                         sContext = OString(name.begin, name.length);
3173                     }
3174                     else if (name == "id")
3175                     {
3176                         name = reader.getAttributeValue(false);
3177                         sId = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3178                     }
3179                 }
3180 
3181                 (void)reader.nextItem(
3182                     xmlreader::XmlReader::Text::Raw, &name, &nsId);
3183 
3184                 OString sValue(name.begin, name.length);
3185                 OUString sFinalValue;
3186                 if (bTranslated)
3187                 {
3188                     sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale);
3189                 }
3190                 else
3191                     sFinalValue = OUString::fromUtf8(sValue);
3192 
3193                 if (m_pStringReplace)
3194                     sFinalValue = (*m_pStringReplace)(sFinalValue);
3195 
3196                 aItems.emplace_back(sFinalValue, sId);
3197             }
3198         }
3199 
3200         if (res == xmlreader::XmlReader::Result::End)
3201         {
3202             --nLevel;
3203         }
3204 
3205         if (!nLevel)
3206             break;
3207     }
3208 
3209     return aItems;
3210 }
3211 
handleMenu(xmlreader::XmlReader & reader,vcl::Window * pParent,const OUString & rID,bool bMenuBar)3212 void VclBuilder::handleMenu(xmlreader::XmlReader& reader, vcl::Window* pParent, const OUString& rID,
3213                             bool bMenuBar)
3214 {
3215     VclPtr<Menu> pCurrentMenu;
3216     if (bMenuBar)
3217         pCurrentMenu = VclPtr<MenuBar>::Create();
3218     else
3219         pCurrentMenu = VclPtr<PopupMenu>::Create();
3220 
3221     pCurrentMenu->set_id(rID);
3222 
3223     int nLevel = 1;
3224 
3225     stringmap aProperties;
3226 
3227     while(true)
3228     {
3229         xmlreader::Span name;
3230         int nsId;
3231 
3232         xmlreader::XmlReader::Result res = reader.nextItem(
3233             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3234 
3235         if (res == xmlreader::XmlReader::Result::Done)
3236             break;
3237 
3238         if (res == xmlreader::XmlReader::Result::Begin)
3239         {
3240             if (name == "child")
3241             {
3242                 handleMenuChild(pCurrentMenu, reader);
3243             }
3244             else
3245             {
3246                 ++nLevel;
3247                 if (name == "property")
3248                     collectProperty(reader, aProperties);
3249             }
3250         }
3251 
3252         if (res == xmlreader::XmlReader::Result::End)
3253         {
3254             --nLevel;
3255         }
3256 
3257         if (!nLevel)
3258             break;
3259     }
3260 
3261     m_aMenus.emplace_back(rID, pCurrentMenu);
3262 
3263     if (bMenuBar && pParent)
3264     {
3265         if (SystemWindow* pTopLevel = pParent->GetSystemWindow())
3266             pTopLevel->SetMenuBar(dynamic_cast<MenuBar*>(pCurrentMenu.get()));
3267     }
3268 }
3269 
handleMenuChild(Menu * pParent,xmlreader::XmlReader & reader)3270 void VclBuilder::handleMenuChild(Menu *pParent, xmlreader::XmlReader &reader)
3271 {
3272     xmlreader::Span name;
3273     int nsId;
3274 
3275     int nLevel = 1;
3276     while(true)
3277     {
3278         xmlreader::XmlReader::Result res = reader.nextItem(
3279             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3280 
3281         if (res == xmlreader::XmlReader::Result::Begin)
3282         {
3283             if (name == "object" || name == "placeholder")
3284             {
3285                 handleMenuObject(pParent, reader);
3286             }
3287             else
3288                 ++nLevel;
3289         }
3290 
3291         if (res == xmlreader::XmlReader::Result::End)
3292             --nLevel;
3293 
3294         if (!nLevel)
3295             break;
3296 
3297         if (res == xmlreader::XmlReader::Result::Done)
3298             break;
3299     }
3300 }
3301 
handleMenuObject(Menu * pParent,xmlreader::XmlReader & reader)3302 void VclBuilder::handleMenuObject(Menu *pParent, xmlreader::XmlReader &reader)
3303 {
3304     OUString sClass;
3305     OUString sID;
3306     OUString sCustomProperty;
3307     PopupMenu *pSubMenu = nullptr;
3308 
3309     xmlreader::Span name;
3310     int nsId;
3311 
3312     while (reader.nextAttribute(&nsId, &name))
3313     {
3314         if (name == "class")
3315         {
3316             name = reader.getAttributeValue(false);
3317             sClass = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3318         }
3319         else if (name == "id")
3320         {
3321             name = reader.getAttributeValue(false);
3322             sID = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3323             if (m_bLegacy)
3324             {
3325                 sal_Int32 nDelim = sID.indexOf(':');
3326                 if (nDelim != -1)
3327                 {
3328                     sCustomProperty = sID.subView(nDelim+1);
3329                     sID = sID.copy(0, nDelim);
3330                 }
3331             }
3332         }
3333     }
3334 
3335     int nLevel = 1;
3336 
3337     stringmap aProperties;
3338     stringmap aAtkProperties;
3339     accelmap aAccelerators;
3340 
3341     if (!sCustomProperty.isEmpty())
3342         aProperties[u"customproperty"_ustr] = sCustomProperty;
3343 
3344     while(true)
3345     {
3346         xmlreader::XmlReader::Result res = reader.nextItem(
3347             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3348 
3349         if (res == xmlreader::XmlReader::Result::Done)
3350             break;
3351 
3352         if (res == xmlreader::XmlReader::Result::Begin)
3353         {
3354             if (name == "child")
3355             {
3356                 size_t nChildMenuIdx = m_aMenus.size();
3357                 handleChild(nullptr, &aAtkProperties, reader);
3358                 bool bSubMenuInserted = m_aMenus.size() > nChildMenuIdx;
3359                 if (bSubMenuInserted)
3360                     pSubMenu = dynamic_cast<PopupMenu*>(m_aMenus[nChildMenuIdx].m_pMenu.get());
3361             }
3362             else
3363             {
3364                 ++nLevel;
3365                 if (name == "property")
3366                     collectProperty(reader, aProperties);
3367                 else if (name == "accelerator")
3368                     collectAccelerator(reader, aAccelerators);
3369             }
3370         }
3371 
3372         if (res == xmlreader::XmlReader::Result::End)
3373         {
3374             --nLevel;
3375         }
3376 
3377         if (!nLevel)
3378             break;
3379     }
3380 
3381     insertMenuObject(pParent, pSubMenu, sClass, sID, aProperties, aAtkProperties, aAccelerators);
3382 }
3383 
handleSizeGroup(xmlreader::XmlReader & reader)3384 void VclBuilder::handleSizeGroup(xmlreader::XmlReader &reader)
3385 {
3386     m_pParserState->m_aSizeGroups.emplace_back();
3387     SizeGroup &rSizeGroup = m_pParserState->m_aSizeGroups.back();
3388 
3389     int nLevel = 1;
3390 
3391     while(true)
3392     {
3393         xmlreader::Span name;
3394         int nsId;
3395 
3396         xmlreader::XmlReader::Result res = reader.nextItem(
3397             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3398 
3399         if (res == xmlreader::XmlReader::Result::Done)
3400             break;
3401 
3402         if (res == xmlreader::XmlReader::Result::Begin)
3403         {
3404             ++nLevel;
3405             if (name == "widget")
3406             {
3407                 while (reader.nextAttribute(&nsId, &name))
3408                 {
3409                     if (name == "name")
3410                     {
3411                         name = reader.getAttributeValue(false);
3412                         OUString sWidget(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3413                         sal_Int32 nDelim = sWidget.indexOf(':');
3414                         if (nDelim != -1)
3415                             sWidget = sWidget.copy(0, nDelim);
3416                         rSizeGroup.m_aWidgets.push_back(sWidget);
3417                     }
3418                 }
3419             }
3420             else
3421             {
3422                 if (name == "property")
3423                     collectProperty(reader, rSizeGroup.m_aProperties);
3424             }
3425         }
3426 
3427         if (res == xmlreader::XmlReader::Result::End)
3428         {
3429             --nLevel;
3430         }
3431 
3432         if (!nLevel)
3433             break;
3434     }
3435 }
3436 
3437 namespace
3438 {
makeKeyCode(const std::pair<OUString,OUString> & rKey)3439     vcl::KeyCode makeKeyCode(const std::pair<OUString,OUString> &rKey)
3440     {
3441         bool bShift = rKey.second.indexOf("GDK_SHIFT_MASK") != -1;
3442         bool bMod1 = rKey.second.indexOf("GDK_CONTROL_MASK") != -1;
3443         bool bMod2 = rKey.second.indexOf("GDK_ALT_MASK") != -1;
3444         bool bMod3 = rKey.second.indexOf("GDK_MOD2_MASK") != -1;
3445 
3446         if (rKey.first == "Insert")
3447             return vcl::KeyCode(KEY_INSERT, bShift, bMod1, bMod2, bMod3);
3448         else if (rKey.first == "Delete")
3449             return vcl::KeyCode(KEY_DELETE, bShift, bMod1, bMod2, bMod3);
3450         else if (rKey.first == "Return")
3451             return vcl::KeyCode(KEY_RETURN, bShift, bMod1, bMod2, bMod3);
3452         else if (rKey.first == "Up")
3453             return vcl::KeyCode(KEY_UP, bShift, bMod1, bMod2, bMod3);
3454         else if (rKey.first == "Down")
3455             return vcl::KeyCode(KEY_DOWN, bShift, bMod1, bMod2, bMod3);
3456         else if (rKey.first == "Left")
3457             return vcl::KeyCode(KEY_LEFT, bShift, bMod1, bMod2, bMod3);
3458         else if (rKey.first == "Right")
3459             return vcl::KeyCode(KEY_RIGHT, bShift, bMod1, bMod2, bMod3);
3460         else if (rKey.first == "asterisk")
3461             return vcl::KeyCode(KEY_MULTIPLY, bShift, bMod1, bMod2, bMod3);
3462         else if (rKey.first.getLength() > 1 && rKey.first[0] == 'F')
3463         {
3464             sal_uInt32 nIndex = o3tl::toUInt32(rKey.first.subView(1));
3465             assert(nIndex >= 1 && nIndex <= 26);
3466             return vcl::KeyCode(KEY_F1 + nIndex - 1, bShift, bMod1, bMod2, bMod3);
3467         }
3468 
3469         assert (rKey.first.getLength() == 1);
3470         sal_Unicode cChar = rKey.first.toChar();
3471 
3472         if (cChar >= 'a' && cChar <= 'z')
3473             return vcl::KeyCode(KEY_A + (cChar - 'a'), bShift, bMod1, bMod2, bMod3);
3474         else if (cChar >= 'A' && cChar <= 'Z')
3475             return vcl::KeyCode(KEY_A + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
3476         else if (cChar >= '0' && cChar <= '9')
3477             return vcl::KeyCode(KEY_0 + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
3478 
3479         return vcl::KeyCode(cChar, bShift, bMod1, bMod2, bMod3);
3480     }
3481 }
3482 
insertMenuObject(Menu * pParent,PopupMenu * pSubMenu,const OUString & rClass,const OUString & rID,stringmap & rProps,stringmap & rAtkProps,accelmap & rAccels)3483 void VclBuilder::insertMenuObject(Menu *pParent, PopupMenu *pSubMenu, const OUString &rClass, const OUString &rID,
3484     stringmap &rProps, stringmap &rAtkProps, accelmap &rAccels)
3485 {
3486     sal_uInt16 nOldCount = pParent->GetItemCount();
3487     sal_uInt16 nNewId = ++m_pParserState->m_nLastMenuItemId;
3488 
3489     if(rClass == "NotebookBarAddonsMenuMergePoint")
3490     {
3491         NotebookBarAddonsMerger::MergeNotebookBarMenuAddons(pParent, nNewId, rID, *m_pNotebookBarAddonsItem);
3492         m_pParserState->m_nLastMenuItemId = pParent->GetItemCount();
3493     }
3494     else if (rClass == "GtkMenuItem")
3495     {
3496         OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
3497         OUString aCommand(extractActionName(rProps));
3498         pParent->InsertItem(nNewId, sLabel, MenuItemBits::NONE , rID);
3499         pParent->SetItemCommand(nNewId, aCommand);
3500         if (pSubMenu)
3501             pParent->SetPopupMenu(nNewId, pSubMenu);
3502     }
3503     else if (rClass == "GtkCheckMenuItem")
3504     {
3505         OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
3506         OUString aCommand(extractActionName(rProps));
3507         pParent->InsertItem(nNewId, sLabel, MenuItemBits::CHECKABLE, rID);
3508         pParent->SetItemCommand(nNewId, aCommand);
3509     }
3510     else if (rClass == "GtkRadioMenuItem")
3511     {
3512         OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
3513         OUString aCommand(extractActionName(rProps));
3514         pParent->InsertItem(nNewId, sLabel, MenuItemBits::AUTOCHECK | MenuItemBits::RADIOCHECK, rID);
3515         pParent->SetItemCommand(nNewId, aCommand);
3516     }
3517     else if (rClass == "GtkSeparatorMenuItem")
3518     {
3519         pParent->InsertSeparator(rID);
3520     }
3521 
3522     SAL_WARN_IF(nOldCount == pParent->GetItemCount(), "vcl.builder", "probably need to implement " << rClass);
3523 
3524     if (nOldCount != pParent->GetItemCount())
3525     {
3526         pParent->SetHelpId(nNewId, m_sHelpRoot + rID);
3527         if (!extractVisible(rProps))
3528             pParent->HideItem(nNewId);
3529 
3530         for (auto const& [ rKey, rValue ] : rProps)
3531         {
3532             if (rKey == "tooltip-markup")
3533                 pParent->SetTipHelpText(nNewId, rValue);
3534             else if (rKey == "tooltip-text")
3535                 pParent->SetTipHelpText(nNewId, rValue);
3536             else
3537                 SAL_INFO("vcl.builder", "unhandled property: " << rKey);
3538         }
3539 
3540         for (auto const& [ rKey, rValue ] : rAtkProps)
3541         {
3542             if (rKey == "AtkObject::accessible-name")
3543                 pParent->SetAccessibleName(nNewId, rValue);
3544             else if (rKey == "AtkObject::accessible-description")
3545                 pParent->SetAccessibleDescription(nNewId, rValue);
3546             else
3547                 SAL_INFO("vcl.builder", "unhandled atk property: " << rKey);
3548         }
3549 
3550         for (auto const& [ rSignal, rValue ] : rAccels)
3551         {
3552             if (rSignal == "activate")
3553                 pParent->SetAccelKey(nNewId, makeKeyCode(rValue));
3554             else
3555                 SAL_INFO("vcl.builder", "unhandled accelerator for: " << rSignal);
3556         }
3557     }
3558 
3559     rProps.clear();
3560 }
3561 
3562 /// Insert items to a ComboBox or a ListBox.
3563 /// They have no common ancestor that would have 'InsertEntry()', so use a template.
insertItems(vcl::Window * pWindow,VclBuilder::stringmap & rMap,std::vector<std::unique_ptr<OUString>> & rUserData,const std::vector<ComboBoxTextItem> & rItems)3564 template<typename T> static bool insertItems(vcl::Window *pWindow, VclBuilder::stringmap &rMap,
3565                                              std::vector<std::unique_ptr<OUString>>& rUserData,
3566                                              const std::vector<ComboBoxTextItem> &rItems)
3567 {
3568     T *pContainer = dynamic_cast<T*>(pWindow);
3569     if (!pContainer)
3570         return false;
3571 
3572     sal_uInt16 nActiveId = extractActive(rMap);
3573     for (auto const& item : rItems)
3574     {
3575         sal_Int32 nPos = pContainer->InsertEntry(item.m_sItem);
3576         if (!item.m_sId.isEmpty())
3577         {
3578             rUserData.emplace_back(std::make_unique<OUString>(item.m_sId));
3579             pContainer->SetEntryData(nPos, rUserData.back().get());
3580         }
3581     }
3582     if (nActiveId < rItems.size())
3583         pContainer->SelectEntryPos(nActiveId);
3584 
3585     return true;
3586 }
3587 
handleObject(vcl::Window * pParent,stringmap * pAtkProps,xmlreader::XmlReader & reader,bool bToolbarItem)3588 VclPtr<vcl::Window> VclBuilder::handleObject(vcl::Window *pParent, stringmap *pAtkProps, xmlreader::XmlReader &reader, bool bToolbarItem)
3589 {
3590     OUString sClass;
3591     OUString sID;
3592     OUString sCustomProperty;
3593 
3594     xmlreader::Span name;
3595     int nsId;
3596 
3597     while (reader.nextAttribute(&nsId, &name))
3598     {
3599         if (name == "class")
3600         {
3601             name = reader.getAttributeValue(false);
3602             sClass = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3603         }
3604         else if (name == "id")
3605         {
3606             name = reader.getAttributeValue(false);
3607             sID = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3608             if (m_bLegacy)
3609             {
3610                 sal_Int32 nDelim = sID.indexOf(':');
3611                 if (nDelim != -1)
3612                 {
3613                     sCustomProperty = sID.subView(nDelim+1);
3614                     sID = sID.copy(0, nDelim);
3615                 }
3616             }
3617         }
3618     }
3619 
3620     if (sClass == "GtkListStore" || sClass == "GtkTreeStore")
3621     {
3622         handleListStore(reader, sID, sClass);
3623         return nullptr;
3624     }
3625     else if (sClass == "GtkMenu")
3626     {
3627         handleMenu(reader, pParent, sID, false);
3628         return nullptr;
3629     }
3630     else if (sClass == "GtkMenuBar")
3631     {
3632         handleMenu(reader, pParent, sID, true);
3633         return nullptr;
3634     }
3635     else if (sClass == "GtkSizeGroup")
3636     {
3637         handleSizeGroup(reader);
3638         return nullptr;
3639     }
3640     else if (sClass == "AtkObject")
3641     {
3642         assert((pParent || pAtkProps) && "must have one set");
3643         assert(!(pParent && pAtkProps) && "must not have both");
3644         auto aAtkProperties = handleAtkObject(reader);
3645         if (pParent)
3646             applyAtkProperties(pParent, aAtkProperties, bToolbarItem);
3647         if (pAtkProps)
3648             *pAtkProps = aAtkProperties;
3649         return nullptr;
3650     }
3651 
3652     int nLevel = 1;
3653 
3654     stringmap aProperties, aPangoAttributes;
3655     stringmap aAtkAttributes;
3656     std::vector<ComboBoxTextItem> aItems;
3657 
3658     if (!sCustomProperty.isEmpty())
3659         aProperties[u"customproperty"_ustr] = sCustomProperty;
3660 
3661     VclPtr<vcl::Window> pCurrentChild;
3662     while(true)
3663     {
3664         xmlreader::XmlReader::Result res = reader.nextItem(
3665             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3666 
3667         if (res == xmlreader::XmlReader::Result::Done)
3668             break;
3669 
3670         if (res == xmlreader::XmlReader::Result::Begin)
3671         {
3672             if (name == "child")
3673             {
3674                 if (!pCurrentChild)
3675                 {
3676                     pCurrentChild = insertObject(pParent, sClass, sID,
3677                         aProperties, aPangoAttributes, aAtkAttributes);
3678                 }
3679                 const bool bToolItem = pCurrentChild == pParent && pCurrentChild->GetType() == WindowType::TOOLBOX && isToolbarItemClass(sClass);
3680                 handleChild(pCurrentChild, nullptr, reader, bToolItem);
3681             }
3682             else if (name == "items")
3683                 aItems = handleItems(reader);
3684             else if (name == "style")
3685             {
3686                 int nPriority = 0;
3687                 std::vector<vcl::EnumContext::Context> aContext = handleStyle(reader, nPriority);
3688                 if (nPriority != 0)
3689                 {
3690                     vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pCurrentChild.get());
3691                     SAL_WARN_IF(!pPrioritable, "vcl", "priority set for not supported item");
3692                     if (pPrioritable)
3693                         pPrioritable->SetPriority(nPriority);
3694                 }
3695                 if (!aContext.empty())
3696                 {
3697                     vcl::IContext* pContextControl = dynamic_cast<vcl::IContext*>(pCurrentChild.get());
3698                     SAL_WARN_IF(!pContextControl, "vcl", "context set for not supported item");
3699                     if (pContextControl)
3700                         pContextControl->SetContext(std::move(aContext));
3701                 }
3702             }
3703             else
3704             {
3705                 ++nLevel;
3706                 if (name == "property")
3707                     collectProperty(reader, aProperties);
3708                 else if (name == "attribute")
3709                     collectPangoAttribute(reader, aPangoAttributes);
3710                 else if (name == "relation")
3711                     collectAtkRelationAttribute(reader, aAtkAttributes);
3712                 else if (name == "role")
3713                     collectAtkRoleAttribute(reader, aAtkAttributes);
3714                 else if (name == "action-widget")
3715                     handleActionWidget(reader);
3716             }
3717         }
3718 
3719         if (res == xmlreader::XmlReader::Result::End)
3720         {
3721             --nLevel;
3722         }
3723 
3724         if (!nLevel)
3725             break;
3726     }
3727 
3728     if (sClass == "GtkAdjustment")
3729     {
3730         m_pParserState->m_aAdjustments[sID] = aProperties;
3731         return nullptr;
3732     }
3733     else if (sClass == "GtkTextBuffer")
3734     {
3735         m_pParserState->m_aTextBuffers[sID] = aProperties;
3736         return nullptr;
3737     }
3738 
3739     if (!pCurrentChild)
3740     {
3741         pCurrentChild = insertObject(pParent, sClass, sID, aProperties,
3742             aPangoAttributes, aAtkAttributes);
3743     }
3744 
3745     if (!aItems.empty())
3746     {
3747         // try to fill-in the items
3748         if (!insertItems<ComboBox>(pCurrentChild, aProperties, m_aUserData, aItems))
3749             insertItems<ListBox>(pCurrentChild, aProperties, m_aUserData, aItems);
3750     }
3751 
3752     return pCurrentChild;
3753 }
3754 
handlePacking(vcl::Window * pCurrent,vcl::Window * pParent,xmlreader::XmlReader & reader)3755 void VclBuilder::handlePacking(vcl::Window *pCurrent, vcl::Window *pParent, xmlreader::XmlReader &reader)
3756 {
3757     xmlreader::Span name;
3758     int nsId;
3759 
3760     int nLevel = 1;
3761 
3762     while(true)
3763     {
3764         xmlreader::XmlReader::Result res = reader.nextItem(
3765             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3766 
3767         if (res == xmlreader::XmlReader::Result::Done)
3768             break;
3769 
3770         if (res == xmlreader::XmlReader::Result::Begin)
3771         {
3772             ++nLevel;
3773             if (name == "property")
3774                 applyPackingProperty(pCurrent, pParent, reader);
3775         }
3776 
3777         if (res == xmlreader::XmlReader::Result::End)
3778         {
3779             --nLevel;
3780         }
3781 
3782         if (!nLevel)
3783             break;
3784     }
3785 }
3786 
applyPackingProperty(vcl::Window * pCurrent,vcl::Window * pParent,xmlreader::XmlReader & reader)3787 void VclBuilder::applyPackingProperty(vcl::Window *pCurrent,
3788     vcl::Window *pParent,
3789     xmlreader::XmlReader &reader)
3790 {
3791     if (!pCurrent)
3792         return;
3793 
3794     //ToolBoxItems are not true widgets just elements
3795     //of the ToolBox itself
3796     ToolBox *pToolBoxParent = nullptr;
3797     if (pCurrent == pParent)
3798         pToolBoxParent = dynamic_cast<ToolBox*>(pParent);
3799 
3800     xmlreader::Span name;
3801     int nsId;
3802 
3803     if (pCurrent->GetType() == WindowType::SCROLLWINDOW)
3804     {
3805         auto aFind = m_pParserState->m_aRedundantParentWidgets.find(VclPtr<vcl::Window>(pCurrent));
3806         if (aFind != m_pParserState->m_aRedundantParentWidgets.end())
3807         {
3808             pCurrent = aFind->second;
3809             assert(pCurrent);
3810         }
3811     }
3812 
3813     while (reader.nextAttribute(&nsId, &name))
3814     {
3815         if (name == "name")
3816         {
3817             name = reader.getAttributeValue(false);
3818             OString sKey(name.begin, name.length);
3819             sKey = sKey.replace('_', '-');
3820             (void)reader.nextItem(
3821                 xmlreader::XmlReader::Text::Raw, &name, &nsId);
3822             OString sValue(name.begin, name.length);
3823 
3824             if (sKey == "expand" || sKey == "resize")
3825             {
3826                 bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1'));
3827                 if (pToolBoxParent)
3828                     pToolBoxParent->SetItemExpand(m_pParserState->m_nLastToolbarId, bTrue);
3829                 else
3830                     pCurrent->set_expand(bTrue);
3831                 continue;
3832             }
3833 
3834             if (pToolBoxParent)
3835                 continue;
3836 
3837             if (sKey == "fill")
3838             {
3839                 bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1'));
3840                 pCurrent->set_fill(bTrue);
3841             }
3842             else if (sKey == "pack-type")
3843             {
3844                 VclPackType ePackType = (!sValue.isEmpty() && (sValue[0] == 'e' || sValue[0] == 'E')) ? VclPackType::End : VclPackType::Start;
3845                 pCurrent->set_pack_type(ePackType);
3846             }
3847             else if (sKey == "left-attach")
3848             {
3849                 pCurrent->set_grid_left_attach(sValue.toInt32());
3850             }
3851             else if (sKey == "top-attach")
3852             {
3853                 pCurrent->set_grid_top_attach(sValue.toInt32());
3854             }
3855             else if (sKey == "width")
3856             {
3857                 pCurrent->set_grid_width(sValue.toInt32());
3858             }
3859             else if (sKey == "height")
3860             {
3861                 pCurrent->set_grid_height(sValue.toInt32());
3862             }
3863             else if (sKey == "padding")
3864             {
3865                 pCurrent->set_padding(sValue.toInt32());
3866             }
3867             else if (sKey == "position")
3868             {
3869                 set_window_packing_position(pCurrent, sValue.toInt32());
3870             }
3871             else if (sKey == "secondary")
3872             {
3873                 pCurrent->set_secondary(toBool(sValue));
3874             }
3875             else if (sKey == "non-homogeneous")
3876             {
3877                 pCurrent->set_non_homogeneous(toBool(sValue));
3878             }
3879             else if (sKey == "homogeneous")
3880             {
3881                 pCurrent->set_non_homogeneous(!toBool(sValue));
3882             }
3883             else
3884             {
3885                 SAL_WARN_IF(sKey != "shrink", "vcl.builder", "unknown packing: " << sKey);
3886             }
3887         }
3888     }
3889 }
3890 
handleStyle(xmlreader::XmlReader & reader,int & nPriority)3891 std::vector<vcl::EnumContext::Context> VclBuilder::handleStyle(xmlreader::XmlReader &reader, int &nPriority)
3892 {
3893     std::vector<vcl::EnumContext::Context> aContext;
3894 
3895     xmlreader::Span name;
3896     int nsId;
3897 
3898     int nLevel = 1;
3899 
3900     while(true)
3901     {
3902         xmlreader::XmlReader::Result res = reader.nextItem(
3903             xmlreader::XmlReader::Text::NONE, &name, &nsId);
3904 
3905         if (res == xmlreader::XmlReader::Result::Done)
3906             break;
3907 
3908         if (res == xmlreader::XmlReader::Result::Begin)
3909         {
3910             ++nLevel;
3911             if (name == "class")
3912             {
3913                 OUString classStyle = getStyleClass(reader);
3914                 OUString rest;
3915 
3916                 if (classStyle.startsWith("context-", &rest))
3917                 {
3918                     aContext.push_back(vcl::EnumContext::GetContextEnum(rest));
3919                 }
3920                 else if (classStyle.startsWith("priority-", &rest))
3921                 {
3922                     nPriority = rest.toInt32();
3923                 }
3924                 else if (classStyle != "small-button" && classStyle != "destructive-action" && classStyle != "suggested-action")
3925                 {
3926                     SAL_WARN("vcl.builder", "unknown class: " << classStyle);
3927                 }
3928             }
3929         }
3930 
3931         if (res == xmlreader::XmlReader::Result::End)
3932         {
3933             --nLevel;
3934         }
3935 
3936         if (!nLevel)
3937             break;
3938     }
3939 
3940     return aContext;
3941 }
3942 
getStyleClass(xmlreader::XmlReader & reader)3943 OUString VclBuilder::getStyleClass(xmlreader::XmlReader &reader)
3944 {
3945     xmlreader::Span name;
3946     int nsId;
3947     OUString aRet;
3948 
3949     while (reader.nextAttribute(&nsId, &name))
3950     {
3951         if (name == "name")
3952         {
3953             name = reader.getAttributeValue(false);
3954             aRet = OUString (name.begin, name.length, RTL_TEXTENCODING_UTF8);
3955         }
3956     }
3957 
3958     return aRet;
3959 }
3960 
collectProperty(xmlreader::XmlReader & reader,stringmap & rMap) const3961 void VclBuilder::collectProperty(xmlreader::XmlReader &reader, stringmap &rMap) const
3962 {
3963     xmlreader::Span name;
3964     int nsId;
3965 
3966     OUString sProperty;
3967     OString sContext;
3968 
3969     bool bTranslated = false;
3970 
3971     while (reader.nextAttribute(&nsId, &name))
3972     {
3973         if (name == "name")
3974         {
3975             name = reader.getAttributeValue(false);
3976             sProperty = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
3977         }
3978         else if (name == "context")
3979         {
3980             name = reader.getAttributeValue(false);
3981             sContext = OString(name.begin, name.length);
3982         }
3983         else if (name == "translatable" && reader.getAttributeValue(false) == "yes")
3984         {
3985             bTranslated = true;
3986         }
3987     }
3988 
3989     (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
3990     OString sValue(name.begin, name.length);
3991     OUString sFinalValue;
3992     if (bTranslated)
3993     {
3994         sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale);
3995     }
3996     else
3997         sFinalValue = OUString::fromUtf8(sValue);
3998 
3999     if (!sProperty.isEmpty())
4000     {
4001         sProperty = sProperty.replace('_', '-');
4002         if (m_pStringReplace)
4003             sFinalValue = (*m_pStringReplace)(sFinalValue);
4004         rMap[sProperty] = sFinalValue;
4005     }
4006 }
4007 
handleActionWidget(xmlreader::XmlReader & reader)4008 void VclBuilder::handleActionWidget(xmlreader::XmlReader &reader)
4009 {
4010     xmlreader::Span name;
4011     int nsId;
4012 
4013     OString sResponse;
4014 
4015     while (reader.nextAttribute(&nsId, &name))
4016     {
4017         if (name == "response")
4018         {
4019             name = reader.getAttributeValue(false);
4020             sResponse = OString(name.begin, name.length);
4021         }
4022     }
4023 
4024     (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
4025     OUString sID(name.begin, name.length, RTL_TEXTENCODING_UTF8);
4026     sal_Int32 nDelim = sID.indexOf(':');
4027     if (nDelim != -1)
4028         sID = sID.copy(0, nDelim);
4029     set_response(sID, sResponse.toInt32());
4030 }
4031 
collectAccelerator(xmlreader::XmlReader & reader,accelmap & rMap)4032 void BuilderBase::collectAccelerator(xmlreader::XmlReader& reader, accelmap& rMap)
4033 {
4034     xmlreader::Span name;
4035     int nsId;
4036 
4037     OUString sProperty;
4038     OUString sValue;
4039     OUString sModifiers;
4040 
4041     while (reader.nextAttribute(&nsId, &name))
4042     {
4043         if (name == "key")
4044         {
4045             name = reader.getAttributeValue(false);
4046             sValue = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
4047         }
4048         else if (name == "signal")
4049         {
4050             name = reader.getAttributeValue(false);
4051             sProperty = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
4052         }
4053         else if (name == "modifiers")
4054         {
4055             name = reader.getAttributeValue(false);
4056             sModifiers = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
4057         }
4058     }
4059 
4060     if (!sProperty.isEmpty() && !sValue.isEmpty())
4061     {
4062         rMap[sProperty] = std::make_pair(sValue, sModifiers);
4063     }
4064 }
4065 
isToolbarItemClass(std::u16string_view sClass)4066 bool BuilderBase::isToolbarItemClass(std::u16string_view sClass)
4067 {
4068     return sClass == u"GtkToolButton" || sClass == u"GtkMenuToolButton"
4069            || sClass == u"GtkToggleToolButton" || sClass == u"GtkRadioToolButton"
4070            || sClass == u"GtkToolItem";
4071 }
4072 
get_widget_root()4073 vcl::Window *VclBuilder::get_widget_root()
4074 {
4075     return m_aChildren.empty() ? nullptr : m_aChildren[0].m_pWindow.get();
4076 }
4077 
get_by_name(std::u16string_view sID)4078 vcl::Window *VclBuilder::get_by_name(std::u16string_view sID)
4079 {
4080     for (auto const& child : m_aChildren)
4081     {
4082         if (child.m_sID == sID)
4083             return child.m_pWindow;
4084     }
4085 
4086     return nullptr;
4087 }
4088 
get_menu(std::u16string_view sID)4089 PopupMenu *VclBuilder::get_menu(std::u16string_view sID)
4090 {
4091     for (auto const& menu : m_aMenus)
4092     {
4093         if (menu.m_sID == sID)
4094             return dynamic_cast<PopupMenu*>(menu.m_pMenu.get());
4095     }
4096 
4097     return nullptr;
4098 }
4099 
set_response(std::u16string_view sID,short nResponse)4100 void VclBuilder::set_response(std::u16string_view sID, short nResponse)
4101 {
4102     switch (nResponse)
4103     {
4104         case -5:
4105             nResponse = RET_OK;
4106             break;
4107         case -6:
4108             nResponse = RET_CANCEL;
4109             break;
4110         case -7:
4111             nResponse = RET_CLOSE;
4112             break;
4113         case -8:
4114             nResponse = RET_YES;
4115             break;
4116         case -9:
4117             nResponse = RET_NO;
4118             break;
4119         case -11:
4120             nResponse = RET_HELP;
4121             break;
4122         default:
4123             assert(nResponse >= 100 && "keep non-canned responses in range 100+ to avoid collision with vcl RET_*");
4124             break;
4125     }
4126 
4127     for (const auto & child : m_aChildren)
4128     {
4129         if (child.m_sID == sID)
4130         {
4131             PushButton* pPushButton = dynamic_cast<PushButton*>(child.m_pWindow.get());
4132             assert(pPushButton);
4133             Dialog* pDialog = pPushButton->GetParentDialog();
4134             assert(pDialog);
4135             pDialog->add_button(pPushButton, nResponse, false);
4136             return;
4137         }
4138     }
4139 
4140     assert(false);
4141 }
4142 
delete_by_name(const OUString & sID)4143 void VclBuilder::delete_by_name(const OUString& sID)
4144 {
4145     auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(),
4146         [&sID](WinAndId& rItem) { return rItem.m_sID == sID; });
4147     if (aI != m_aChildren.end())
4148     {
4149         aI->m_pWindow.disposeAndClear();
4150         m_aChildren.erase(aI);
4151     }
4152 }
4153 
delete_by_window(vcl::Window * pWindow)4154 void VclBuilder::delete_by_window(vcl::Window *pWindow)
4155 {
4156     drop_ownership(pWindow);
4157     pWindow->disposeOnce();
4158 }
4159 
drop_ownership(const vcl::Window * pWindow)4160 void VclBuilder::drop_ownership(const vcl::Window *pWindow)
4161 {
4162     auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(),
4163         [&pWindow](WinAndId& rItem) { return rItem.m_pWindow == pWindow; });
4164     if (aI != m_aChildren.end())
4165         m_aChildren.erase(aI);
4166 }
4167 
get_by_window(const vcl::Window * pWindow) const4168 OUString VclBuilder::get_by_window(const vcl::Window *pWindow) const
4169 {
4170     for (auto const& child : m_aChildren)
4171     {
4172         if (child.m_pWindow == pWindow)
4173             return child.m_sID;
4174     }
4175 
4176     return {};
4177 }
4178 
get_window_packing_data(const vcl::Window * pWindow) const4179 VclBuilder::PackingData VclBuilder::get_window_packing_data(const vcl::Window *pWindow) const
4180 {
4181     //We've stored the return of new Control, some of these get
4182     //border windows placed around them which are what you get
4183     //from GetChild, so scoot up a level if necessary to get the
4184     //window whose position value we have
4185     const vcl::Window *pPropHolder = pWindow->ImplGetWindow();
4186 
4187     for (auto const& child : m_aChildren)
4188     {
4189         if (child.m_pWindow == pPropHolder)
4190             return child.m_aPackingData;
4191     }
4192 
4193     return PackingData();
4194 }
4195 
set_window_packing_position(const vcl::Window * pWindow,sal_Int32 nPosition)4196 void VclBuilder::set_window_packing_position(const vcl::Window *pWindow, sal_Int32 nPosition)
4197 {
4198     for (auto & child : m_aChildren)
4199     {
4200         if (child.m_pWindow == pWindow)
4201             child.m_aPackingData.m_nPosition = nPosition;
4202     }
4203 }
4204 
get_model_by_name(const OUString & sID) const4205 const VclBuilder::ListStore *VclBuilder::get_model_by_name(const OUString& sID) const
4206 {
4207     const auto aI = m_pParserState->m_aModels.find(sID);
4208     if (aI != m_pParserState->m_aModels.end())
4209         return &(aI->second);
4210     return nullptr;
4211 }
4212 
get_buffer_by_name(const OUString & sID) const4213 const VclBuilder::TextBuffer *VclBuilder::get_buffer_by_name(const OUString& sID) const
4214 {
4215     const auto aI = m_pParserState->m_aTextBuffers.find(sID);
4216     if (aI != m_pParserState->m_aTextBuffers.end())
4217         return &(aI->second);
4218     return nullptr;
4219 }
4220 
get_adjustment_by_name(const OUString & sID) const4221 const VclBuilder::Adjustment *VclBuilder::get_adjustment_by_name(const OUString& sID) const
4222 {
4223     const auto aI = m_pParserState->m_aAdjustments.find(sID);
4224     if (aI != m_pParserState->m_aAdjustments.end())
4225         return &(aI->second);
4226     return nullptr;
4227 }
4228 
mungeModel(ComboBox & rTarget,const ListStore & rStore,sal_uInt16 nActiveId)4229 void VclBuilder::mungeModel(ComboBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
4230 {
4231     for (auto const& entry : rStore.m_aEntries)
4232     {
4233         const ListStore::row &rRow = entry;
4234         sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]);
4235         if (rRow.size() > 1)
4236         {
4237             if (m_bLegacy)
4238             {
4239                 sal_IntPtr nValue = rRow[1].toInt32();
4240                 rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue));
4241             }
4242             else
4243             {
4244                 if (!rRow[1].isEmpty())
4245                 {
4246                     m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
4247                     rTarget.SetEntryData(nEntry, m_aUserData.back().get());
4248                 }
4249             }
4250         }
4251     }
4252     if (nActiveId < rStore.m_aEntries.size())
4253         rTarget.SelectEntryPos(nActiveId);
4254 }
4255 
mungeModel(ListBox & rTarget,const ListStore & rStore,sal_uInt16 nActiveId)4256 void VclBuilder::mungeModel(ListBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
4257 {
4258     for (auto const& entry : rStore.m_aEntries)
4259     {
4260         const ListStore::row &rRow = entry;
4261         sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]);
4262         if (rRow.size() > 1)
4263         {
4264             if (m_bLegacy)
4265             {
4266                 sal_IntPtr nValue = rRow[1].toInt32();
4267                 rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue));
4268             }
4269             else
4270             {
4271                 if (!rRow[1].isEmpty())
4272                 {
4273                     m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
4274                     rTarget.SetEntryData(nEntry, m_aUserData.back().get());
4275                 }
4276             }
4277         }
4278     }
4279     if (nActiveId < rStore.m_aEntries.size())
4280         rTarget.SelectEntryPos(nActiveId);
4281 }
4282 
mungeModel(SvTabListBox & rTarget,const ListStore & rStore,sal_uInt16 nActiveId)4283 void VclBuilder::mungeModel(SvTabListBox& rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
4284 {
4285     for (auto const& entry : rStore.m_aEntries)
4286     {
4287         const ListStore::row &rRow = entry;
4288         auto pEntry = rTarget.InsertEntry(rRow[0]);
4289         if (rRow.size() > 1)
4290         {
4291             if (m_bLegacy)
4292             {
4293                 sal_IntPtr nValue = rRow[1].toInt32();
4294                 pEntry->SetUserData(reinterpret_cast<void*>(nValue));
4295             }
4296             else
4297             {
4298                 if (!rRow[1].isEmpty())
4299                 {
4300                     m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1]));
4301                     pEntry->SetUserData(m_aUserData.back().get());
4302                 }
4303             }
4304         }
4305     }
4306     if (nActiveId < rStore.m_aEntries.size())
4307     {
4308         SvTreeListEntry* pEntry = rTarget.GetEntry(nullptr, nActiveId);
4309         rTarget.Select(pEntry);
4310     }
4311 }
4312 
mungeAdjustment(NumericFormatter & rTarget,const Adjustment & rAdjustment)4313 void VclBuilder::mungeAdjustment(NumericFormatter &rTarget, const Adjustment &rAdjustment)
4314 {
4315     int nMul = rtl_math_pow10Exp(1, rTarget.GetDecimalDigits());
4316 
4317     for (auto const& [ rKey, rValue ] : rAdjustment)
4318     {
4319         if (rKey == "upper")
4320         {
4321             sal_Int64 nUpper = rValue.toDouble() * nMul;
4322             rTarget.SetMax(nUpper);
4323             rTarget.SetLast(nUpper);
4324         }
4325         else if (rKey == "lower")
4326         {
4327             sal_Int64 nLower = rValue.toDouble() * nMul;
4328             rTarget.SetMin(nLower);
4329             rTarget.SetFirst(nLower);
4330         }
4331         else if (rKey == "value")
4332         {
4333             sal_Int64 nValue = rValue.toDouble() * nMul;
4334             rTarget.SetValue(nValue);
4335         }
4336         else if (rKey == "step-increment")
4337         {
4338             sal_Int64 nSpinSize = rValue.toDouble() * nMul;
4339             rTarget.SetSpinSize(nSpinSize);
4340         }
4341         else
4342         {
4343             SAL_INFO("vcl.builder", "unhandled property :" << rKey);
4344         }
4345     }
4346 }
4347 
mungeAdjustment(FormattedField & rTarget,const Adjustment & rAdjustment)4348 void VclBuilder::mungeAdjustment(FormattedField &rTarget, const Adjustment &rAdjustment)
4349 {
4350     double nMaxValue = 0, nMinValue = 0, nValue = 0, nSpinSize = 0;
4351 
4352     for (auto const& [ rKey, rValue ] : rAdjustment)
4353     {
4354         if (rKey == "upper")
4355             nMaxValue = rValue.toDouble();
4356         else if (rKey == "lower")
4357             nMinValue = rValue.toDouble();
4358         else if (rKey == "value")
4359             nValue = rValue.toDouble();
4360         else if (rKey == "step-increment")
4361             nSpinSize = rValue.toDouble();
4362         else
4363             SAL_INFO("vcl.builder", "unhandled property :" << rKey);
4364     }
4365 
4366     Formatter& rFormatter = rTarget.GetFormatter();
4367     rFormatter.SetMinValue(nMinValue);
4368     rFormatter.SetMaxValue(nMaxValue);
4369     rFormatter.SetValue(nValue);
4370     rFormatter.SetSpinSize(nSpinSize);
4371 }
4372 
mungeAdjustment(ScrollBar & rTarget,const Adjustment & rAdjustment)4373 void VclBuilder::mungeAdjustment(ScrollBar &rTarget, const Adjustment &rAdjustment)
4374 {
4375     for (auto const& [ rKey, rValue ] : rAdjustment)
4376     {
4377         if (rKey == "upper")
4378             rTarget.SetRangeMax(rValue.toInt32());
4379         else if (rKey == "lower")
4380             rTarget.SetRangeMin(rValue.toInt32());
4381         else if (rKey == "value")
4382             rTarget.SetThumbPos(rValue.toInt32());
4383         else if (rKey == "step-increment")
4384             rTarget.SetLineSize(rValue.toInt32());
4385         else if (rKey == "page-increment")
4386             rTarget.SetPageSize(rValue.toInt32());
4387         else
4388         {
4389             SAL_INFO("vcl.builder", "unhandled property :" << rKey);
4390         }
4391     }
4392 }
4393 
mungeAdjustment(Slider & rTarget,const Adjustment & rAdjustment)4394 void VclBuilder::mungeAdjustment(Slider& rTarget, const Adjustment& rAdjustment)
4395 {
4396     for (auto const& [ rKey, rValue ] : rAdjustment)
4397     {
4398         if (rKey == "upper")
4399             rTarget.SetRangeMax(rValue.toInt32());
4400         else if (rKey == "lower")
4401             rTarget.SetRangeMin(rValue.toInt32());
4402         else if (rKey == "value")
4403             rTarget.SetThumbPos(rValue.toInt32());
4404         else if (rKey == "step-increment")
4405             rTarget.SetLineSize(rValue.toInt32());
4406         else if (rKey == "page-increment")
4407             rTarget.SetPageSize(rValue.toInt32());
4408         else
4409         {
4410             SAL_INFO("vcl.builder", "unhandled property :" << rKey);
4411         }
4412     }
4413 }
4414 
mungeTextBuffer(VclMultiLineEdit & rTarget,const TextBuffer & rTextBuffer)4415 void VclBuilder::mungeTextBuffer(VclMultiLineEdit &rTarget, const TextBuffer &rTextBuffer)
4416 {
4417     for (auto const& [ rKey, rValue ] : rTextBuffer)
4418     {
4419         if (rKey == "text")
4420             rTarget.SetText(rValue);
4421         else
4422         {
4423             SAL_INFO("vcl.builder", "unhandled property :" << rKey);
4424         }
4425     }
4426 }
4427 
ParserState()4428 VclBuilder::ParserState::ParserState()
4429     : m_nLastToolbarId(0)
4430     , m_nLastMenuItemId(0)
4431 {}
4432 
MenuAndId(OUString aId,Menu * pMenu)4433 VclBuilder::MenuAndId::MenuAndId(OUString aId, Menu *pMenu)
4434     : m_sID(std::move(aId))
4435     , m_pMenu(pMenu)
4436 {}
4437 
4438 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4439