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