/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 
#include <memory>
#include <unordered_map>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
 
#include <i18nutil/unicode.hxx>
#include <osl/module.hxx>
#include <osl/file.hxx>
#include <sal/log.hxx>
#include <unotools/resmgr.hxx>
#include <vcl/builder.hxx>
#include <vcl/button.hxx>
#include <vcl/dialog.hxx>
#include <vcl/edit.hxx>
#include <vcl/field.hxx>
#include <vcl/fixed.hxx>
#include <vcl/fixedhyper.hxx>
#include <vcl/IPrioritable.hxx>
#include <vcl/layout.hxx>
#include <vcl/lstbox.hxx>
#include <vcl/menubtn.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/prgsbar.hxx>
#include <vcl/scrbar.hxx>
#include <vcl/svapp.hxx>
#include <vcl/tabctrl.hxx>
#include <vcl/tabpage.hxx>
#include <vcl/throbber.hxx>
#include <vcl/toolbox.hxx>
#include <vcl/vclmedit.hxx>
#include <vcl/settings.hxx>
#include <vcl/slider.hxx>
#include <vcl/listctrl.hxx>
#include <vcl/weld.hxx>
#include <vcl/commandinfoprovider.hxx>
#include <svdata.hxx>
#include <bitmaps.hlst>
#include <messagedialog.hxx>
#include <window.h>
#include <xmlreader/xmlreader.hxx>
#include <desktop/crashreport.hxx>
#include <salinst.hxx>
#include <strings.hrc>
#include <tools/svlibrary.h>
#include <tools/diagnose_ex.h>
 
#ifdef DISABLE_DYNLOADING
#include <dlfcn.h>
#endif
 
bool toBool(const OString &rValue)
{
    return (!rValue.isEmpty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1'));
}
 
namespace
{
    OUString mapStockToImageResource(const OUString& sType)
    {
        if (sType == "gtk-index")
            return OUString(SV_RESID_BITMAP_INDEX);
        else if (sType == "gtk-refresh")
            return OUString(SV_RESID_BITMAP_REFRESH);
        return OUString();
    }
 
    SymbolType mapStockToSymbol(const OUString& sType)
    {
        SymbolType eRet = SymbolType::DONTKNOW;
        if (sType == "gtk-media-next")
            eRet = SymbolType::NEXT;
        else if (sType == "gtk-media-previous")
            eRet = SymbolType::PREV;
        else if (sType == "gtk-media-play")
            eRet = SymbolType::PLAY;
        else if (sType == "gtk-media-stop")
            eRet = SymbolType::STOP;
        else if (sType == "gtk-goto-first")
            eRet = SymbolType::FIRST;
        else if (sType == "gtk-goto-last")
            eRet = SymbolType::LAST;
        else if (sType == "gtk-go-back")
            eRet = SymbolType::ARROW_LEFT;
        else if (sType == "gtk-go-forward")
            eRet = SymbolType::ARROW_RIGHT;
        else if (sType == "gtk-go-up")
            eRet = SymbolType::ARROW_UP;
        else if (sType == "gtk-go-down")
            eRet = SymbolType::ARROW_DOWN;
        else if (sType == "gtk-missing-image")
            eRet = SymbolType::IMAGE;
        else if (sType == "gtk-help")
            eRet = SymbolType::HELP;
        else if (sType == "gtk-close")
            eRet = SymbolType::CLOSE;
        else if (!mapStockToImageResource(sType).isEmpty())
            eRet = SymbolType::IMAGE;
        return eRet;
    }
 
    void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame);
}
 
#if defined SAL_LOG_WARN
namespace
{
    bool isButtonType(WindowType nType)
    {
        return nType == WindowType::PUSHBUTTON ||
               nType == WindowType::OKBUTTON ||
               nType == WindowType::CANCELBUTTON ||
               nType == WindowType::HELPBUTTON ||
               nType == WindowType::IMAGEBUTTON ||
               nType == WindowType::MENUBUTTON ||
               nType == WindowType::MOREBUTTON ||
               nType == WindowType::SPINBUTTON;
    }
}
#endif
 
weld::Builder* Application::CreateBuilder(weld::Widget* pParent, const OUString &rUIFile)
{
    return ImplGetSVData()->mpDefInst->CreateBuilder(pParent, VclBuilderContainer::getUIRootDir(), rUIFile);
}
 
weld::Builder* Application::CreateInterimBuilder(vcl::Window* pParent, const OUString &rUIFile)
{
    return ImplGetSVData()->mpDefInst->CreateInterimBuilder(pParent, VclBuilderContainer::getUIRootDir(), rUIFile);
}
 
weld::MessageDialog* Application::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
                                                      VclButtonsType eButtonType, const OUString& rPrimaryMessage)
{
    return ImplGetSVData()->mpDefInst->CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage);
}
 
weld::Window* Application::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
{
    return ImplGetSVData()->mpDefInst->GetFrameWeld(rWindow);
}
 
namespace
{
    const OUString MetricToString(FieldUnit rUnit)
    {
        FieldUnitStringList* pList = ImplGetFieldUnits();
        if (pList)
        {
            // return unit's default string (ie, the first one )
            for (auto it = pList->begin(); it != pList->end(); ++it)
            {
                if (it->second == rUnit)
                    return it->first;
            }
        }
 
        return OUString();
    }
}
 
namespace weld
{
    IMPL_LINK_NOARG(MetricSpinButton, spin_button_value_changed, SpinButton&, void)
    {
        signal_value_changed();
    }
 
    IMPL_LINK(MetricSpinButton, spin_button_output, SpinButton&, rSpinButton, void)
    {
        rSpinButton.set_text(format_number(rSpinButton.get_value()));
    }
 
    void MetricSpinButton::update_width_chars()
    {
        int min, max;
        m_xSpinButton->get_range(min, max);
        auto width = std::max(m_xSpinButton->get_pixel_size(format_number(min)).Width(),
                              m_xSpinButton->get_pixel_size(format_number(max)).Width());
        int chars = ceil(width / m_xSpinButton->get_approximate_digit_width());
        m_xSpinButton->set_width_chars(chars);
    }
 
    unsigned int SpinButton::Power10(unsigned int n)
    {
        unsigned int nValue = 1;
        for (unsigned int i = 0; i < n; ++i)
            nValue *= 10;
        return nValue;
    }
 
    int SpinButton::denormalize(int nValue) const
    {
        const int nFactor = Power10(get_digits());
 
        if ((nValue < (SAL_MIN_INT32 + nFactor)) || (nValue > (SAL_MAX_INT32 - nFactor)))
        {
            return nValue / nFactor;
        }
 
        const int nHalf = nFactor / 2;
 
        if (nValue < 0)
            return (nValue - nHalf) / nFactor;
        return (nValue + nHalf) / nFactor;
    }
 
    OUString MetricSpinButton::format_number(int nValue) const
    {
        OUString aStr;
 
        unsigned int nDecimalDigits = m_xSpinButton->get_digits();
        //pawn percent off to icu to decide whether percent is separated from its number for this locale
        if (m_eSrcUnit == FUNIT_PERCENT)
        {
            double fValue = nValue;
            fValue /= SpinButton::Power10(nDecimalDigits);
            aStr = unicode::formatPercent(fValue, Application::GetSettings().GetUILanguageTag());
        }
        else
        {
            const SvtSysLocale aSysLocale;
            const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
            aStr = rLocaleData.getNum(nValue, nDecimalDigits, true, true);
            if (m_eSrcUnit != FUNIT_NONE && m_eSrcUnit != FUNIT_DEGREE)
                aStr += " ";
            assert(m_eSrcUnit != FUNIT_PERCENT);
            aStr += MetricToString(m_eSrcUnit);
        }
 
        return aStr;
    }
 
    int MetricSpinButton::ConvertValue(int nValue, FieldUnit eInUnit, FieldUnit eOutUnit) const
    {
        return MetricField::ConvertValue(nValue, 0, m_xSpinButton->get_digits(), eInUnit, eOutUnit);
    }
 
    IMPL_LINK(MetricSpinButton, spin_button_input, int*, result, bool)
    {
        const SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
        double fResult(0.0);
        bool bRet = MetricFormatter::TextToValue(get_text(), fResult, 0, m_xSpinButton->get_digits(), rLocaleData, m_eSrcUnit);
        if (bRet)
            *result = fResult;
        return bRet;
    }
 
    IMPL_LINK_NOARG(TimeSpinButton, spin_button_cursor_position, Entry&, void)
    {
        int nStartPos, nEndPos;
        m_xSpinButton->get_selection_bounds(nStartPos, nEndPos);
 
        const SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
        const int nTimeArea = TimeFormatter::GetTimeArea(m_eFormat, m_xSpinButton->get_text(), nEndPos,
                                                         rLocaleData);
 
        int nIncrements = 1;
 
        if (nTimeArea == 1)
            nIncrements = 1000 * 60 * 60;
        else if (nTimeArea == 2)
            nIncrements = 1000 * 60;
        else if (nTimeArea == 3)
            nIncrements = 1000;
 
        m_xSpinButton->set_increments(nIncrements, nIncrements * 10);
    }
 
    IMPL_LINK_NOARG(TimeSpinButton, spin_button_value_changed, SpinButton&, void)
    {
        signal_value_changed();
    }
 
    IMPL_LINK(TimeSpinButton, spin_button_output, SpinButton&, rSpinButton, void)
    {
        int nStartPos, nEndPos;
        rSpinButton.get_selection_bounds(nStartPos, nEndPos);
        rSpinButton.set_text(format_number(rSpinButton.get_value()));
        rSpinButton.set_position(nEndPos);
    }
 
    IMPL_LINK(TimeSpinButton, spin_button_input, int*, result, bool)
    {
        int nStartPos, nEndPos;
        m_xSpinButton->get_selection_bounds(nStartPos, nEndPos);
 
        const SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
        tools::Time aResult(0);
        bool bRet = TimeFormatter::TextToTime(m_xSpinButton->get_text(), aResult, m_eFormat, true, rLocaleData);
        if (bRet)
            *result = ConvertValue(aResult);
        return bRet;
    }
 
    void TimeSpinButton::update_width_chars()
    {
        int min, max;
        m_xSpinButton->get_range(min, max);
        auto width = std::max(m_xSpinButton->get_pixel_size(format_number(min)).Width(),
                              m_xSpinButton->get_pixel_size(format_number(max)).Width());
        int chars = ceil(width / m_xSpinButton->get_approximate_digit_width());
        m_xSpinButton->set_width_chars(chars);
    }
 
    tools::Time TimeSpinButton::ConvertValue(int nValue) const
    {
        tools::Time aTime(0);
        aTime.MakeTimeFromMS(nValue);
        return aTime;
    }
 
    int TimeSpinButton::ConvertValue(const tools::Time& rTime) const
    {
        return rTime.GetMSFromTime();
    }
 
    OUString TimeSpinButton::format_number(int nValue) const
    {
        const SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
        return TimeFormatter::FormatTime(ConvertValue(nValue), m_eFormat, TimeFormat::Hour24, true, rLocaleData);
    }
 
    EntryTreeView::EntryTreeView(std::unique_ptr<Entry> xEntry, std::unique_ptr<TreeView> xTreeView)
        : m_xEntry(std::move(xEntry))
        , m_xTreeView(std::move(xTreeView))
    {
        m_xTreeView->connect_changed(LINK(this, EntryTreeView, ClickHdl));
        m_xEntry->connect_changed(LINK(this, EntryTreeView, ModifyHdl));
    }
 
    IMPL_LINK(EntryTreeView, ClickHdl, weld::TreeView&, rView, void)
    {
        m_xEntry->set_text(rView.get_selected_text());
    }
 
    void EntryTreeView::EntryModifyHdl(weld::Entry& rBox)
    {
        OUString sText(rBox.get_text());
        int nExists = m_xTreeView->find_text(sText);
        if (nExists != -1)
        {
            m_xTreeView->select(nExists);
            return;
        }
 
        m_xTreeView->select(-1);
        if (sText.isEmpty())
            return;
 
        int nCount = m_xTreeView->n_children();
        for (int i = 0; i < nCount; ++i)
        {
            if (m_xTreeView->get_text(i).startsWith(sText))
            {
                m_xTreeView->select(i);
                break;
            }
        }
    }
 
    IMPL_LINK(EntryTreeView, ModifyHdl, weld::Entry&, rBox, void)
    {
        EntryModifyHdl(rBox);
        m_aChangeHdl.Call(rBox);
    }
 
    void EntryTreeView::set_size_request_by_digits_rows(int nDigits, int nRows)
    {
        m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * nDigits,
                                      m_xTreeView->get_height_rows(nRows));
    }
}
 
VclBuilder::VclBuilder(vcl::Window *pParent, const OUString& sUIDir, const OUString& sUIFile, const OString& sID,
                       const css::uno::Reference<css::frame::XFrame>& rFrame, bool bLegacy)
    : m_sID(sID)
    , m_sHelpRoot(OUStringToOString(sUIFile, RTL_TEXTENCODING_UTF8))
    , m_pStringReplace(Translate::GetReadStringHook())
    , m_pParent(pParent)
    , m_bToplevelParentFound(false)
    , m_bLegacy(bLegacy)
    , m_pParserState(new ParserState)
    , m_xFrame(rFrame)
{
    m_bToplevelHasDeferredInit = pParent &&
        ((pParent->IsSystemWindow() && static_cast<SystemWindow*>(pParent)->isDeferredInit()) ||
         (pParent->IsDockingWindow() && static_cast<DockingWindow*>(pParent)->isDeferredInit()));
    m_bToplevelHasDeferredProperties = m_bToplevelHasDeferredInit;
 
    sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.');
    if (nIdx != -1)
        m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
    m_sHelpRoot = m_sHelpRoot + OString('/');
 
    OUString sUri = sUIDir + sUIFile;
 
    try
    {
        xmlreader::XmlReader reader(sUri);
 
        handleChild(pParent, reader);
    }
    catch (const css::uno::Exception &rExcept)
    {
        DBG_UNHANDLED_EXCEPTION("vcl.layout", "Unable to read .ui file");
        CrashReporter::AddKeyValue("VclBuilderException", "Unable to read .ui file: " + rExcept.Message);
        throw;
    }
 
    //Set Mnemonic widgets when everything has been imported
    for (auto const& mnemonicWidget : m_pParserState->m_aMnemonicWidgetMaps)
    {
        FixedText *pOne = get<FixedText>(mnemonicWidget.m_sID);
        vcl::Window *pOther = get<vcl::Window>(mnemonicWidget.m_sValue.toUtf8());
        SAL_WARN_IF(!pOne || !pOther, "vcl", "missing either source " << mnemonicWidget.m_sID
            << " or target " << mnemonicWidget.m_sValue << " member of Mnemonic Widget Mapping");
        if (pOne && pOther)
            pOne->set_mnemonic_widget(pOther);
    }
 
    //Set a11y relations and role when everything has been imported
    for (auto const& elemAtk : m_pParserState->m_aAtkInfo)
    {
        vcl::Window *pSource = elemAtk.first;
        const stringmap &rMap = elemAtk.second;
 
        for (auto const& elemMap : rMap)
        {
            const OString &rType = elemMap.first;
            const OUString &rParam = elemMap.second;
            if (rType == "role")
            {
                sal_Int16 role = BuilderUtils::getRoleFromName(rParam.toUtf8());
                if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN)
                    pSource->SetAccessibleRole(role);
            }
            else
            {
                vcl::Window *pTarget = get<vcl::Window>(rParam.toUtf8());
                SAL_WARN_IF(!pTarget, "vcl", "missing parameter of a11y relation: " << rParam);
                if (!pTarget)
                    continue;
                if (rType == "labelled-by")
                    pSource->SetAccessibleRelationLabeledBy(pTarget);
                else if (rType == "label-for")
                    pSource->SetAccessibleRelationLabelFor(pTarget);
                else if (rType == "member-of")
                    pSource->SetAccessibleRelationMemberOf(pTarget);
                else
                {
                    SAL_WARN("vcl.layout", "unhandled a11y relation :" << rType);
                }
            }
        }
    }
 
    //Set radiobutton groups when everything has been imported
    for (auto const& elem : m_pParserState->m_aGroupMaps)
    {
        RadioButton *pOne = get<RadioButton>(elem.m_sID);
        RadioButton *pOther = get<RadioButton>(elem.m_sValue);
        SAL_WARN_IF(!pOne || !pOther, "vcl", "missing member of radiobutton group");
        if (pOne && pOther)
        {
            if (m_bLegacy)
                pOne->group(*pOther);
            else
                pOther->group(*pOne);
        }
    }
 
    //Set ComboBox models when everything has been imported
    for (auto const& elem : m_pParserState->m_aModelMaps)
    {
        ListBox *pTarget = get<ListBox>(elem.m_sID);
        // pStore may be empty
        const ListStore *pStore = get_model_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget, "vcl", "missing elements of combobox");
        if (pTarget && pStore)
            mungeModel(*pTarget, *pStore, elem.m_nActiveId);
    }
 
    //Set TextView buffers when everything has been imported
    for (auto const& elem : m_pParserState->m_aTextBufferMaps)
    {
        VclMultiLineEdit *pTarget = get<VclMultiLineEdit>(elem.m_sID);
        const TextBuffer *pBuffer = get_buffer_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pBuffer, "vcl", "missing elements of textview/textbuffer");
        if (pTarget && pBuffer)
            mungeTextBuffer(*pTarget, *pBuffer);
    }
 
    //Set SpinButton adjustments when everything has been imported
    for (auto const& elem : m_pParserState->m_aNumericFormatterAdjustmentMaps)
    {
        NumericFormatter *pTarget = dynamic_cast<NumericFormatter*>(get<vcl::Window>(elem.m_sID));
        const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget, "vcl", "missing NumericFormatter element of spinbutton/adjustment");
        SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment");
        if (pTarget && pAdjustment)
            mungeAdjustment(*pTarget, *pAdjustment);
    }
 
    for (auto const& elem : m_pParserState->m_aTimeFormatterAdjustmentMaps)
    {
        TimeField *pTarget = dynamic_cast<TimeField*>(get<vcl::Window>(elem.m_sID));
        const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of spinbutton/adjustment");
        if (pTarget && pAdjustment)
            mungeAdjustment(*pTarget, *pAdjustment);
    }
 
    for (auto const& elem : m_pParserState->m_aDateFormatterAdjustmentMaps)
    {
        DateField *pTarget = dynamic_cast<DateField*>(get<vcl::Window>(elem.m_sID));
        const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of spinbutton/adjustment");
        if (pTarget && pAdjustment)
            mungeAdjustment(*pTarget, *pAdjustment);
    }
 
    //Set ScrollBar adjustments when everything has been imported
    for (auto const& elem : m_pParserState->m_aScrollAdjustmentMaps)
    {
        ScrollBar *pTarget = get<ScrollBar>(elem.m_sID);
        const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scrollbar/adjustment");
        if (pTarget && pAdjustment)
            mungeAdjustment(*pTarget, *pAdjustment);
    }
 
    //Set Scale(Slider) adjustments
    for (auto const& elem : m_pParserState->m_aSliderAdjustmentMaps)
    {
        Slider* pTarget = dynamic_cast<Slider*>(get<vcl::Window>(elem.m_sID));
        const Adjustment* pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scale(slider)/adjustment");
        if (pTarget && pAdjustment)
        {
            mungeAdjustment(*pTarget, *pAdjustment);
        }
    }
 
    //Set size-groups when all widgets have been imported
    for (auto const& sizeGroup : m_pParserState->m_aSizeGroups)
    {
        std::shared_ptr<VclSizeGroup> xGroup(std::make_shared<VclSizeGroup>());
 
        for (auto const& elem : sizeGroup.m_aProperties)
        {
            const OString &rKey = elem.first;
            const OUString &rValue = elem.second;
            xGroup->set_property(rKey, rValue);
        }
 
        for (auto const& elem : sizeGroup.m_aWidgets)
        {
            vcl::Window* pWindow = get<vcl::Window>(elem.getStr());
            pWindow->add_to_size_group(xGroup);
        }
    }
 
    //Set button images when everything has been imported
    std::set<OUString> aImagesToBeRemoved;
    for (auto const& elem : m_pParserState->m_aButtonImageWidgetMaps)
    {
        PushButton *pTargetButton = nullptr;
        RadioButton *pTargetRadio = nullptr;
        Button *pTarget = nullptr;
 
        if (!elem.m_bRadio)
        {
            pTargetButton = get<PushButton>(elem.m_sID);
            pTarget = pTargetButton;
        }
        else
        {
            pTargetRadio = get<RadioButton>(elem.m_sID);
            pTarget = pTargetRadio;
        }
 
        FixedImage *pImage = get<FixedImage>(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pImage,
            "vcl", "missing elements of button/image/stock");
        if (!pTarget || !pImage)
            continue;
        aImagesToBeRemoved.insert(elem.m_sValue);
 
        VclBuilder::StockMap::iterator aFind = m_pParserState->m_aStockMap.find(elem.m_sValue.toUtf8());
        if (aFind == m_pParserState->m_aStockMap.end())
        {
            if (!elem.m_bRadio)
                pTargetButton->SetModeImage(pImage->GetImage());
            else
                pTargetRadio->SetModeRadioImage(pImage->GetImage());
        }
        else
        {
            const stockinfo &rImageInfo = aFind->second;
            SymbolType eType = mapStockToSymbol(rImageInfo.m_sStock);
            SAL_WARN_IF(eType == SymbolType::DONTKNOW, "vcl", "missing stock image element for button");
            if (eType == SymbolType::DONTKNOW)
                continue;
            if (!elem.m_bRadio)
            {
                pTargetButton->SetSymbol(eType);
                //fdo#76457 keep symbol images small e.g. tools->customize->menu
                //but images the right size. Really the PushButton::CalcMinimumSize
                //and PushButton::ImplDrawPushButton are the better place to handle
                //this, but its such a train-wreck
                if (eType != SymbolType::IMAGE)
                    pTargetButton->SetStyle(pTargetButton->GetStyle() | WB_SMALLSTYLE);
            }
            else
                SAL_WARN_IF(eType != SymbolType::IMAGE, "vcl.layout", "inimplemented symbol type for radiobuttons");
            if (eType == SymbolType::IMAGE)
            {
                BitmapEx aBitmap(mapStockToImageResource(rImageInfo.m_sStock));
                Image const aImage(aBitmap);
                if (!elem.m_bRadio)
                    pTargetButton->SetModeImage(aImage);
                else
                    pTargetRadio->SetModeRadioImage(aImage);
            }
            switch (rImageInfo.m_nSize)
            {
                case 1:
                    pTarget->SetSmallSymbol();
                    break;
                case 4:
                    break;
                default:
                    SAL_WARN("vcl.layout", "unsupported image size " << rImageInfo.m_nSize);
                    break;
            }
        }
    }
 
    //There may be duplicate use of an Image, so we used a set to collect and
    //now we can remove them from the tree after their final munge
    for (auto const& elem : aImagesToBeRemoved)
    {
        delete_by_name(elem.toUtf8());
    }
 
    //Set button menus when everything has been imported
    for (auto const& elem : m_pParserState->m_aButtonMenuMaps)
    {
        MenuButton *pTarget = get<MenuButton>(elem.m_sID);
        PopupMenu *pMenu = get_menu(elem.m_sValue.toUtf8());
        SAL_WARN_IF(!pTarget || !pMenu,
            "vcl", "missing elements of button/menu");
        if (!pTarget || !pMenu)
            continue;
        pTarget->SetPopupMenu(pMenu);
    }
 
    //Remove ScrollWindow parent widgets whose children in vcl implement scrolling
    //internally.
    for (auto const& elem : m_pParserState->m_aRedundantParentWidgets)
    {
        delete_by_window(elem.first);
    }
 
    //fdo#67378 merge the label into the disclosure button
    for (auto const& elem : m_pParserState->m_aExpanderWidgets)
    {
        vcl::Window *pChild = elem->get_child();
        vcl::Window* pLabel = elem->GetWindow(GetWindowType::LastChild);
        if (pLabel && pLabel != pChild && pLabel->GetType() == WindowType::FIXEDTEXT)
        {
            FixedText *pLabelWidget = static_cast<FixedText*>(pLabel);
            elem->set_label(pLabelWidget->GetText());
            delete_by_window(pLabel);
        }
    }
 
    // create message dialog message area now
    for (auto const& elem : m_pParserState->m_aMessageDialogs)
        elem->create_message_area();
 
    //drop maps, etc. that we don't need again
    m_pParserState.reset();
 
    SAL_WARN_IF(!m_sID.isEmpty() && (!m_bToplevelParentFound && !get_by_name(m_sID)), "vcl.layout",
        "Requested top level widget \"" << m_sID << "\" not found in " << sUIFile);
 
#if defined SAL_LOG_WARN
    if (m_bToplevelParentFound && m_pParent->IsDialog())
    {
        int nButtons = 0;
        bool bHasDefButton = false;
        for (auto const& child : m_aChildren)
        {
            if (isButtonType(child.m_pWindow->GetType()))
            {
                ++nButtons;
                if (child.m_pWindow->GetStyle() & WB_DEFBUTTON)
                {
                    bHasDefButton = true;
                    break;
                }
            }
        }
        SAL_WARN_IF(nButtons && !bHasDefButton, "vcl.layout", "No default button defined in " << sUIFile);
    }
#endif
}
 
VclBuilder::~VclBuilder()
{
    disposeBuilder();
}
 
void VclBuilder::disposeBuilder()
{
    for (std::vector<WinAndId>::reverse_iterator aI = m_aChildren.rbegin(),
         aEnd = m_aChildren.rend(); aI != aEnd; ++aI)
    {
        aI->m_pWindow.disposeAndClear();
    }
    m_aChildren.clear();
 
    for (std::vector<MenuAndId>::reverse_iterator aI = m_aMenus.rbegin(),
         aEnd = m_aMenus.rend(); aI != aEnd; ++aI)
    {
        aI->m_pMenu.disposeAndClear();
    }
    m_aMenus.clear();
    m_pParent.clear();
}
 
namespace
{
    bool extractDrawValue(VclBuilder::stringmap& rMap)
    {
        bool bDrawValue = true;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("draw_value"));
        if (aFind != rMap.end())
        {
            bDrawValue = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bDrawValue;
    }
 
    OUString extractPopupMenu(VclBuilder::stringmap& rMap)
    {
        OUString sRet;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("popup"));
        if (aFind != rMap.end())
        {
            sRet = aFind->second;
            rMap.erase(aFind);
        }
        return sRet;
    }
 
    OUString extractValuePos(VclBuilder::stringmap& rMap)
    {
        OUString sRet("top");
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("value_pos"));
        if (aFind != rMap.end())
        {
            sRet = aFind->second;
            rMap.erase(aFind);
        }
        return sRet;
    }
 
    OUString extractTypeHint(VclBuilder::stringmap &rMap)
    {
        OUString sRet("normal");
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("type-hint"));
        if (aFind != rMap.end())
        {
            sRet = aFind->second;
            rMap.erase(aFind);
        }
        return sRet;
    }
 
    bool extractResizable(VclBuilder::stringmap &rMap)
    {
        bool bResizable = true;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("resizable"));
        if (aFind != rMap.end())
        {
            bResizable = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bResizable;
    }
 
    bool extractDecorated(VclBuilder::stringmap &rMap)
    {
        bool bDecorated = true;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("decorated"));
        if (aFind != rMap.end())
        {
            bDecorated = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bDecorated;
    }
 
    bool extractCloseable(VclBuilder::stringmap &rMap)
    {
        bool bCloseable = true;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("deletable"));
        if (aFind != rMap.end())
        {
            bCloseable = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bCloseable;
    }
 
    bool extractEntry(VclBuilder::stringmap &rMap)
    {
        bool bHasEntry = false;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("has-entry"));
        if (aFind != rMap.end())
        {
            bHasEntry = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bHasEntry;
    }
 
    bool extractOrientation(VclBuilder::stringmap &rMap)
    {
        bool bVertical = false;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("orientation"));
        if (aFind != rMap.end())
        {
            bVertical = aFind->second.equalsIgnoreAsciiCase("vertical");
            rMap.erase(aFind);
        }
        return bVertical;
    }
 
    bool extractInconsistent(VclBuilder::stringmap &rMap)
    {
        bool bInconsistent = false;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("inconsistent"));
        if (aFind != rMap.end())
        {
            bInconsistent = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bInconsistent;
    }
 
    OUString extractIconName(VclBuilder::stringmap &rMap)
    {
        OUString sIconName;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("icon-name"));
        if (aFind != rMap.end())
        {
            sIconName = aFind->second;
            rMap.erase(aFind);
        }
        return sIconName;
    }
 
    OUString getStockText(const OUString &rType)
    {
        if (rType == "gtk-ok")
            return VclResId(SV_BUTTONTEXT_OK);
        else if (rType == "gtk-cancel")
            return VclResId(SV_BUTTONTEXT_CANCEL);
        else if (rType == "gtk-help")
            return VclResId(SV_BUTTONTEXT_HELP);
        else if (rType == "gtk-close")
            return VclResId(SV_BUTTONTEXT_CLOSE);
        else if (rType == "gtk-revert-to-saved")
            return VclResId(SV_BUTTONTEXT_RESET);
        else if (rType == "gtk-add")
            return VclResId(SV_BUTTONTEXT_ADD);
        else if (rType == "gtk-delete")
            return VclResId(SV_BUTTONTEXT_DELETE);
        else if (rType == "gtk-remove")
            return VclResId(SV_BUTTONTEXT_REMOVE);
        else if (rType == "gtk-new")
            return VclResId(SV_BUTTONTEXT_NEW);
        else if (rType == "gtk-edit")
            return VclResId(SV_BUTTONTEXT_EDIT);
        else if (rType == "gtk-apply")
            return VclResId(SV_BUTTONTEXT_APPLY);
        else if (rType == "gtk-save")
            return VclResId(SV_BUTTONTEXT_SAVE);
        else if (rType == "gtk-open")
            return VclResId(SV_BUTTONTEXT_OPEN);
        else if (rType == "gtk-undo")
            return VclResId(SV_BUTTONTEXT_UNDO);
        else if (rType == "gtk-paste")
            return VclResId(SV_BUTTONTEXT_PASTE);
        else if (rType == "gtk-media-next")
            return VclResId(SV_BUTTONTEXT_NEXT);
        else if (rType == "gtk-media-previous")
            return VclResId(SV_BUTTONTEXT_PREV);
        else if (rType == "gtk-go-up")
            return VclResId(SV_BUTTONTEXT_GO_UP);
        else if (rType == "gtk-go-down")
            return VclResId(SV_BUTTONTEXT_GO_DOWN);
        else if (rType == "gtk-clear")
            return VclResId(SV_BUTTONTEXT_CLEAR);
        else if (rType == "gtk-media-play")
            return VclResId(SV_BUTTONTEXT_PLAY);
        else if (rType == "gtk-find")
            return VclResId(SV_BUTTONTEXT_FIND);
        else if (rType == "gtk-stop")
            return VclResId(SV_BUTTONTEXT_STOP);
        else if (rType == "gtk-connect")
            return VclResId(SV_BUTTONTEXT_CONNECT);
        else if (rType == "gtk-yes")
            return VclResId(SV_BUTTONTEXT_YES);
        else if (rType == "gtk-no")
            return VclResId(SV_BUTTONTEXT_NO);
        SAL_WARN("vcl.layout", "unknown stock type: " << rType);
        return OUString();
    }
 
    bool extractStock(VclBuilder::stringmap &rMap)
    {
        bool bIsStock = false;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("use-stock"));
        if (aFind != rMap.end())
        {
            bIsStock = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bIsStock;
    }
 
    WinBits extractRelief(VclBuilder::stringmap &rMap)
    {
        WinBits nBits = WB_3DLOOK;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("relief"));
        if (aFind != rMap.end())
        {
            if (aFind->second == "half")
                nBits = WB_FLATBUTTON | WB_BEVELBUTTON;
            else if (aFind->second == "none")
                nBits = WB_FLATBUTTON;
            rMap.erase(aFind);
        }
        return nBits;
    }
 
    OUString extractLabel(VclBuilder::stringmap &rMap)
    {
        OUString sType;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("label"));
        if (aFind != rMap.end())
        {
            sType = aFind->second;
            rMap.erase(aFind);
        }
        return sType;
    }
 
    OUString extractActionName(VclBuilder::stringmap &rMap)
    {
        OUString sActionName;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("action-name"));
        if (aFind != rMap.end())
        {
            sActionName = aFind->second;
            rMap.erase(aFind);
        }
        return sActionName;
    }
 
    bool extractVisible(VclBuilder::stringmap &rMap)
    {
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("visible"));
        if (aFind != rMap.end())
        {
            return toBool(aFind->second);
        }
        return false;
    }
 
    Size extractSizeRequest(VclBuilder::stringmap &rMap)
    {
        OUString sWidthRequest("0");
        OUString sHeightRequest("0");
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("width-request"));
        if (aFind != rMap.end())
        {
            sWidthRequest = aFind->second;
            rMap.erase(aFind);
        }
        aFind = rMap.find(OString("height-request"));
        if (aFind != rMap.end())
        {
            sHeightRequest = aFind->second;
            rMap.erase(aFind);
        }
        return Size(sWidthRequest.toInt32(), sHeightRequest.toInt32());
    }
 
    OUString extractTooltipText(VclBuilder::stringmap &rMap)
    {
        OUString sTooltipText;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("tooltip-text"));
        if (aFind == rMap.end())
            aFind = rMap.find(OString("tooltip-markup"));
        if (aFind != rMap.end())
        {
            sTooltipText = aFind->second;
            rMap.erase(aFind);
        }
        return sTooltipText;
    }
 
    void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame)
    {
        if (!rFrame.is())
            return;
 
        OUString aCommand(extractActionName(rMap));
        if (aCommand.isEmpty())
            return;
 
        OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
        OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aCommand, aModuleName));
        if (!aLabel.isEmpty())
            pButton->SetText(aLabel);
 
        OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(aCommand, rFrame));
        if (!aTooltip.isEmpty())
            pButton->SetQuickHelpText(aTooltip);
 
        Image aImage(vcl::CommandInfoProvider::GetImageForCommand(aCommand, rFrame));
        pButton->SetModeImage(aImage);
 
        pButton->SetCommandHandler(aCommand);
    }
 
    VclPtr<Button> extractStockAndBuildPushButton(vcl::Window *pParent, VclBuilder::stringmap &rMap, bool bToggle, bool bLegacy)
    {
        WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER;
        if (bToggle)
            nBits |= WB_TOGGLE;
 
        nBits |= extractRelief(rMap);
 
        VclPtr<Button> xWindow;
 
        if (extractStock(rMap))
        {
            OUString sType = extractLabel(rMap);
            if (bLegacy)
            {
                if (sType == "gtk-ok")
                    xWindow = VclPtr<OKButton>::Create(pParent, nBits);
                else if (sType == "gtk-cancel")
                    xWindow = VclPtr<CancelButton>::Create(pParent, nBits);
                else if (sType == "gtk-close")
                    xWindow = VclPtr<CloseButton>::Create(pParent, nBits);
                else if (sType == "gtk-help")
                    xWindow = VclPtr<HelpButton>::Create(pParent, nBits);
            }
            if (!xWindow)
            {
                xWindow = VclPtr<PushButton>::Create(pParent, nBits);
                xWindow->SetText(getStockText(sType));
            }
        }
 
        if (!xWindow)
            xWindow = VclPtr<PushButton>::Create(pParent, nBits);
        return xWindow;
    }
 
    VclPtr<Button> extractStockAndBuildMenuButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
    {
        WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
 
        nBits |= extractRelief(rMap);
 
        VclPtr<Button> xWindow = VclPtr<MenuButton>::Create(pParent, nBits);
 
        if (extractStock(rMap))
        {
            xWindow->SetText(getStockText(extractLabel(rMap)));
        }
 
        return xWindow;
    }
 
    VclPtr<Button> extractStockAndBuildMenuToggleButton(vcl::Window *pParent, VclBuilder::stringmap &rMap)
    {
        WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
 
        nBits |= extractRelief(rMap);
 
        VclPtr<Button> xWindow = VclPtr<MenuToggleButton>::Create(pParent, nBits);
 
        if (extractStock(rMap))
        {
            xWindow->SetText(getStockText(extractLabel(rMap)));
        }
 
        return xWindow;
    }
 
    OUString extractUnit(const OUString& sPattern)
    {
        OUString sUnit(sPattern);
        for (sal_Int32 i = 0; i < sPattern.getLength(); ++i)
        {
            if (sPattern[i] != '.' && sPattern[i] != ',' && sPattern[i] != '0')
            {
                sUnit = sPattern.copy(i);
                break;
            }
        }
        return sUnit;
    }
 
    int extractDecimalDigits(const OUString& sPattern)
    {
        int nDigits = 0;
        bool bAfterPoint = false;
        for (sal_Int32 i = 0; i < sPattern.getLength(); ++i)
        {
            if (sPattern[i] == '.' || sPattern[i] == ',')
                bAfterPoint = true;
            else if (sPattern[i] == '0')
            {
                if (bAfterPoint)
                    ++nDigits;
            }
            else
                break;
        }
        return nDigits;
    }
 
    FieldUnit detectMetricUnit(const OUString& sUnit)
    {
        FieldUnit eUnit = FUNIT_NONE;
 
        if (sUnit == "mm")
            eUnit = FUNIT_MM;
        else if (sUnit == "cm")
            eUnit = FUNIT_CM;
        else if (sUnit == "m")
            eUnit = FUNIT_M;
        else if (sUnit == "km")
            eUnit = FUNIT_KM;
        else if ((sUnit == "twips") || (sUnit == "twip"))
            eUnit = FUNIT_TWIP;
        else if (sUnit == "pt")
            eUnit = FUNIT_POINT;
        else if (sUnit == "pc")
            eUnit = FUNIT_PICA;
        else if (sUnit == "\"" || (sUnit == "in") || (sUnit == "inch"))
            eUnit = FUNIT_INCH;
        else if ((sUnit == "'") || (sUnit == "ft") || (sUnit == "foot") || (sUnit == "feet"))
            eUnit = FUNIT_FOOT;
        else if (sUnit == "mile" || (sUnit == "miles"))
            eUnit = FUNIT_MILE;
        else if (sUnit == "ch")
            eUnit = FUNIT_CHAR;
        else if (sUnit == "line")
            eUnit = FUNIT_LINE;
        else if (sUnit == "%")
            eUnit = FUNIT_PERCENT;
        else if ((sUnit == "pixels") || (sUnit == "pixel") || (sUnit == "px"))
            eUnit = FUNIT_PIXEL;
        else if ((sUnit == "degrees") || (sUnit == "degree"))
            eUnit = FUNIT_DEGREE;
        else if ((sUnit == "sec") || (sUnit == "seconds") || (sUnit == "second"))
            eUnit = FUNIT_SECOND;
        else if ((sUnit == "ms") || (sUnit == "milliseconds") || (sUnit == "millisecond"))
            eUnit = FUNIT_MILLISECOND;
        else if (sUnit != "0")
            eUnit = FUNIT_CUSTOM;
 
        return eUnit;
    }
 
    WinBits extractDeferredBits(VclBuilder::stringmap &rMap)
    {
        WinBits nBits = WB_3DLOOK|WB_HIDE;
        if (extractResizable(rMap))
            nBits |= WB_SIZEABLE;
        if (extractCloseable(rMap))
            nBits |= WB_CLOSEABLE;
        OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
        if (!sBorder.isEmpty())
            nBits |= WB_BORDER;
        if (!extractDecorated(rMap))
            nBits |= WB_OWNERDRAWDECORATION;
        OUString sType(extractTypeHint(rMap));
        if (sType == "utility")
            nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_MOVEABLE;
        else if (sType == "popup-menu")
            nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_POPUP;
        else if (sType == "dock")
            nBits |= WB_DOCKABLE | WB_MOVEABLE;
        else
            nBits |= WB_MOVEABLE;
        return nBits;
    }
}
 
void VclBuilder::extractGroup(const OString &id, stringmap &rMap)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("group"));
    if (aFind != rMap.end())
    {
        OUString sID = aFind->second;
        sal_Int32 nDelim = sID.indexOf(':');
        if (nDelim != -1)
            sID = sID.copy(0, nDelim);
        m_pParserState->m_aGroupMaps.emplace_back(id, sID.toUtf8());
        rMap.erase(aFind);
    }
}
 
void VclBuilder::connectNumericFormatterAdjustment(const OString &id, const OUString &rAdjustment)
{
    if (!rAdjustment.isEmpty())
        m_pParserState->m_aNumericFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
}
 
void VclBuilder::connectTimeFormatterAdjustment(const OString &id, const OUString &rAdjustment)
{
    if (!rAdjustment.isEmpty())
        m_pParserState->m_aTimeFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
}
 
void VclBuilder::connectDateFormatterAdjustment(const OString &id, const OUString &rAdjustment)
{
    if (!rAdjustment.isEmpty())
        m_pParserState->m_aDateFormatterAdjustmentMaps.emplace_back(id, rAdjustment);
}
 
bool VclBuilder::extractAdjustmentToMap(const OString& id, VclBuilder::stringmap& rMap, std::vector<WidgetAdjustmentMap>& rAdjustmentMap)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment"));
    if (aFind != rMap.end())
    {
        rAdjustmentMap.emplace_back(id, aFind->second);
        rMap.erase(aFind);
        return true;
    }
    return false;
}
 
namespace
{
    sal_Int32 extractActive(VclBuilder::stringmap &rMap)
    {
        sal_Int32 nActiveId = 0;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("active"));
        if (aFind != rMap.end())
        {
            nActiveId = aFind->second.toInt32();
            rMap.erase(aFind);
        }
        return nActiveId;
    }
 
    bool extractSelectable(VclBuilder::stringmap &rMap)
    {
        bool bSelectable = false;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("selectable"));
        if (aFind != rMap.end())
        {
            bSelectable = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bSelectable;
    }
 
    OUString extractAdjustment(VclBuilder::stringmap &rMap)
    {
        OUString sAdjustment;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment"));
        if (aFind != rMap.end())
        {
            sAdjustment= aFind->second;
            rMap.erase(aFind);
            return sAdjustment;
        }
        return sAdjustment;
    }
}
 
void VclBuilder::extractModel(const OString &id, stringmap &rMap)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("model"));
    if (aFind != rMap.end())
    {
        m_pParserState->m_aModelMaps.emplace_back(id, aFind->second,
            extractActive(rMap));
        rMap.erase(aFind);
    }
}
 
void VclBuilder::extractBuffer(const OString &id, stringmap &rMap)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("buffer"));
    if (aFind != rMap.end())
    {
        m_pParserState->m_aTextBufferMaps.emplace_back(id, aFind->second);
        rMap.erase(aFind);
    }
}
 
void VclBuilder::extractStock(const OString &id, stringmap &rMap)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("stock"));
    if (aFind != rMap.end())
    {
        stockinfo aInfo;
        aInfo.m_sStock = aFind->second;
        rMap.erase(aFind);
        aFind = rMap.find(OString("icon-size"));
        if (aFind != rMap.end())
        {
            aInfo.m_nSize = aFind->second.toInt32();
            rMap.erase(aFind);
        }
        m_pParserState->m_aStockMap[id] = aInfo;
    }
}
 
void VclBuilder::extractButtonImage(const OString &id, stringmap &rMap, bool bRadio)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("image"));
    if (aFind != rMap.end())
    {
        m_pParserState->m_aButtonImageWidgetMaps.emplace_back(id, aFind->second, bRadio);
        rMap.erase(aFind);
    }
}
 
void VclBuilder::extractMnemonicWidget(const OString &rLabelID, stringmap &rMap)
{
    VclBuilder::stringmap::iterator aFind = rMap.find(OString("mnemonic-widget"));
    if (aFind != rMap.end())
    {
        OUString sID = aFind->second;
        sal_Int32 nDelim = sID.indexOf(':');
        if (nDelim != -1)
            sID = sID.copy(0, nDelim);
        m_pParserState->m_aMnemonicWidgetMaps.emplace_back(rLabelID, sID);
        rMap.erase(aFind);
    }
}
 
vcl::Window* VclBuilder::prepareWidgetOwnScrolling(vcl::Window *pParent, WinBits &rWinStyle)
{
    //For Widgets that manage their own scrolling, if one appears as a child of
    //a scrolling window shoehorn that scrolling settings to this widget and
    //return the real parent to use
    if (pParent && pParent->GetType() == WindowType::SCROLLWINDOW)
    {
        WinBits nScrollBits = pParent->GetStyle();
        nScrollBits &= (WB_AUTOHSCROLL|WB_HSCROLL|WB_AUTOVSCROLL|WB_VSCROLL);
        rWinStyle |= nScrollBits | WB_BORDER;
        pParent = pParent->GetParent();
    }
 
    return pParent;
}
 
void VclBuilder::cleanupWidgetOwnScrolling(vcl::Window *pScrollParent, vcl::Window *pWindow, stringmap &rMap)
{
    //remove the redundant scrolling parent
    sal_Int32 nWidthReq = pScrollParent->get_width_request();
    rMap[OString("width-request")] = OUString::number(nWidthReq);
    sal_Int32 nHeightReq = pScrollParent->get_height_request();
    rMap[OString("height-request")] = OUString::number(nHeightReq);
 
    m_pParserState->m_aRedundantParentWidgets[pScrollParent] = pWindow;
}
 
#ifndef DISABLE_DYNLOADING
 
extern "C" { static void thisModule() {} }
 
// Don't unload the module on destruction
class NoAutoUnloadModule : public osl::Module
{
public:
    ~NoAutoUnloadModule() { release(); }
};
 
typedef std::map<OUString, std::shared_ptr<NoAutoUnloadModule>> ModuleMap;
static ModuleMap g_aModuleMap;
 
#if ENABLE_MERGELIBS
static std::shared_ptr<NoAutoUnloadModule> g_pMergedLib = std::make_shared<NoAutoUnloadModule>();
#endif
 
#ifndef SAL_DLLPREFIX
#  define SAL_DLLPREFIX ""
#endif
 
#endif
 
void VclBuilder::preload()
{
#ifndef DISABLE_DYNLOADING
 
#if ENABLE_MERGELIBS
    g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
#else
// find -name '*ui*' | xargs grep 'class=".*lo-' |
//     sed 's/.*class="//' | sed 's/-.*$//' | sort | uniq
    static const char *aWidgetLibs[] = {
        "sfxlo",  "svtlo",     "svxcorelo", "foruilo",
        "vcllo",  "svxlo",     "cuilo",     "swlo",
        "swuilo", "sclo",      "sdlo",      "chartcontrollerlo",
        "smlo",   "scuilo",    "basctllo",  "sduilo",
        "scnlo",  "xsltdlglo", "pcrlo" // "dbulo"
    };
    for (auto & lib : aWidgetLibs)
    {
        OUStringBuffer sModuleBuf;
        sModuleBuf.append(SAL_DLLPREFIX);
        sModuleBuf.append(OUString::createFromAscii(lib));
        sModuleBuf.append(SAL_DLLEXTENSION);
        NoAutoUnloadModule* pModule = new NoAutoUnloadModule;
        OUString sModule = sModuleBuf.makeStringAndClear();
        if (pModule->loadRelative(&thisModule, sModule))
            g_aModuleMap.insert(std::make_pair(sModule, std::unique_ptr<NoAutoUnloadModule>(pModule)));
        else
            delete pModule;
    }
#endif // ENABLE_MERGELIBS
#endif // DISABLE_DYNLOADING
}
 
VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window *pParent, const OString &name, const OString &id,
    stringmap &rMap)
{
    bool bIsPlaceHolder = name.isEmpty();
    bool bVertical = false;
 
    if (pParent && pParent->GetType() == WindowType::TABCONTROL)
    {
        //We have to add a page
 
        //make default pageid == position
        TabControl *pTabControl = static_cast<TabControl*>(pParent);
        sal_uInt16 nNewPageCount = pTabControl->GetPageCount()+1;
        sal_uInt16 nNewPageId = nNewPageCount;
        pTabControl->InsertPage(nNewPageId, OUString());
        pTabControl->SetCurPageId(nNewPageId);
        SAL_WARN_IF(bIsPlaceHolder, "vcl.layout", "we should have no placeholders for tabpages");
        if (!bIsPlaceHolder)
        {
            VclPtrInstance<TabPage> pPage(pTabControl);
            pPage->Show();
 
            //Make up a name for it
            OString sTabPageId = get_by_window(pParent) +
                OString("-page") +
                OString::number(nNewPageCount);
            m_aChildren.emplace_back(sTabPageId, pPage, false);
            pPage->SetHelpId(m_sHelpRoot + sTabPageId);
 
            pParent = pPage;
 
            pTabControl->SetTabPage(nNewPageId, pPage);
        }
    }
 
    if (bIsPlaceHolder || name == "GtkTreeSelection")
        return nullptr;
 
    extractButtonImage(id, rMap, name == "GtkRadioButton");
 
    VclPtr<vcl::Window> xWindow;
    if (name == "GtkDialog")
    {
        WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE;
        if (extractResizable(rMap))
            nBits |= WB_SIZEABLE;
        xWindow = VclPtr<Dialog>::Create(pParent, nBits, !pParent ? Dialog::InitFlag::NoParent : Dialog::InitFlag::Default);
    }
    else if (name == "GtkMessageDialog")
    {
        WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE;
        if (extractResizable(rMap))
            nBits |= WB_SIZEABLE;
        VclPtr<MessageDialog> xDialog(VclPtr<MessageDialog>::Create(pParent, nBits));
        m_pParserState->m_aMessageDialogs.push_back(xDialog);
        xWindow = xDialog;
#if defined WNT
        xWindow->set_border_width(3);
#else
        xWindow->set_border_width(12);
#endif
    }
    else if (name == "GtkBox")
    {
        bVertical = extractOrientation(rMap);
        if (bVertical)
            xWindow = VclPtr<VclVBox>::Create(pParent);
        else
            xWindow = VclPtr<VclHBox>::Create(pParent);
    }
    else if (name == "GtkPaned")
    {
        bVertical = extractOrientation(rMap);
        assert(bVertical && "hori not implemented, shouldn't be hard though");
        xWindow = VclPtr<VclVPaned>::Create(pParent);
    }
    else if (name == "GtkHBox")
        xWindow = VclPtr<VclHBox>::Create(pParent);
    else if (name == "GtkVBox")
        xWindow = VclPtr<VclVBox>::Create(pParent);
    else if (name == "GtkButtonBox")
    {
        bVertical = extractOrientation(rMap);
        if (bVertical)
            xWindow = VclPtr<VclVButtonBox>::Create(pParent);
        else
            xWindow = VclPtr<VclHButtonBox>::Create(pParent);
    }
    else if (name == "GtkHButtonBox")
        xWindow = VclPtr<VclHButtonBox>::Create(pParent);
    else if (name == "GtkVButtonBox")
        xWindow = VclPtr<VclVButtonBox>::Create(pParent);
    else if (name == "GtkGrid")
        xWindow = VclPtr<VclGrid>::Create(pParent);
    else if (name == "GtkFrame")
        xWindow = VclPtr<VclFrame>::Create(pParent);
    else if (name == "GtkExpander")
    {
        VclPtrInstance<VclExpander> pExpander(pParent);
        m_pParserState->m_aExpanderWidgets.push_back(pExpander);
        xWindow = pExpander;
    }
    else if (name == "GtkAlignment")
        xWindow = VclPtr<VclAlignment>::Create(pParent);
    else if (name == "GtkButton" || (!m_bLegacy && name == "GtkToggleButton"))
    {
        VclPtr<Button> xButton;
        OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
        if (sMenu.isEmpty())
            xButton = extractStockAndBuildPushButton(pParent, rMap, name == "GtkToggleButton", m_bLegacy);
        else
        {
            assert(m_bLegacy && "use GtkMenuButton");
            xButton = extractStockAndBuildMenuButton(pParent, rMap);
            m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
        }
        xButton->SetImageAlign(ImageAlign::Left); //default to left
        setupFromActionName(xButton, rMap, m_xFrame);
        xWindow = xButton;
    }
    else if (name == "GtkMenuButton")
    {
        VclPtr<Button> xButton;
        xButton = extractStockAndBuildMenuButton(pParent, rMap);
        OUString sMenu = extractPopupMenu(rMap);
        if (!sMenu.isEmpty())
            m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
        xButton->SetImageAlign(ImageAlign::Left); //default to left
        xButton->SetAccessibleRole(css::accessibility::AccessibleRole::BUTTON_MENU);
        setupFromActionName(xButton, rMap, m_xFrame);
        xWindow = xButton;
    }
    else if (name == "GtkToggleButton" && m_bLegacy)
    {
        VclPtr<Button> xButton;
        OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
        assert(sMenu.getLength() && "not implemented yet");
        xButton = extractStockAndBuildMenuToggleButton(pParent, rMap);
        m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
        xButton->SetImageAlign(ImageAlign::Left); //default to left
        setupFromActionName(xButton, rMap, m_xFrame);
        xWindow = xButton;
    }
    else if (name == "GtkRadioButton")
    {
        extractGroup(id, rMap);
        WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
        OUString sWrap = BuilderUtils::extractCustomProperty(rMap);
        if (!sWrap.isEmpty())
            nBits |= WB_WORDBREAK;
        VclPtr<RadioButton> xButton = VclPtr<RadioButton>::Create(pParent, nBits);
        xButton->SetImageAlign(ImageAlign::Left); //default to left
        xWindow = xButton;
 
        if (::extractStock(rMap))
        {
            xWindow->SetText(getStockText(extractLabel(rMap)));
        }
    }
    else if (name == "GtkCheckButton")
    {
        WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK;
        OUString sWrap = BuilderUtils::extractCustomProperty(rMap);
        if (!sWrap.isEmpty())
            nBits |= WB_WORDBREAK;
        //maybe always import as TriStateBox and enable/disable tristate
        bool bIsTriState = extractInconsistent(rMap);
        VclPtr<CheckBox> xCheckBox;
        if (bIsTriState && m_bLegacy)
            xCheckBox = VclPtr<TriStateBox>::Create(pParent, nBits);
        else
            xCheckBox = VclPtr<CheckBox>::Create(pParent, nBits);
        if (bIsTriState)
        {
            xCheckBox->EnableTriState(true);
            xCheckBox->SetState(TRISTATE_INDET);
        }
        xCheckBox->SetImageAlign(ImageAlign::Left); //default to left
 
        xWindow = xCheckBox;
 
        if (::extractStock(rMap))
        {
            xWindow->SetText(getStockText(extractLabel(rMap)));
        }
    }
    else if (name == "GtkSpinButton")
    {
        OUString sAdjustment = extractAdjustment(rMap);
        OUString sPattern = BuilderUtils::extractCustomProperty(rMap);
        OUString sUnit = extractUnit(sPattern);
 
        WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_BORDER|WB_3DLOOK;
        if (!id.endsWith("-nospin"))
            nBits |= WB_SPIN | WB_REPEAT;
 
        if (sPattern.isEmpty())
        {
            connectNumericFormatterAdjustment(id, sAdjustment);
            SAL_INFO("vcl.layout", "making numeric field for " << name << " " << sUnit);
            xWindow = VclPtr<NumericField>::Create(pParent, nBits);
        }
        else
        {
            if (sPattern == "hh:mm")
            {
                connectTimeFormatterAdjustment(id, sAdjustment);
                SAL_INFO("vcl.layout", "making time field for " << name << " " << sUnit);
                xWindow = VclPtr<TimeField>::Create(pParent, nBits);
            }
            else if (sPattern == "yy:mm:dd")
            {
                connectDateFormatterAdjustment(id, sAdjustment);
                SAL_INFO("vcl.layout", "making date field for " << name << " " << sUnit);
                xWindow = VclPtr<DateField>::Create(pParent, nBits);
            }
            else
            {
                connectNumericFormatterAdjustment(id, sAdjustment);
                FieldUnit eUnit = detectMetricUnit(sUnit);
                SAL_INFO("vcl.layout", "making metric field for " << name << " " << sUnit);
                VclPtrInstance<MetricField> xField(pParent, nBits);
                xField->SetUnit(eUnit);
                if (eUnit == FUNIT_CUSTOM)
                    xField->SetCustomUnitText(sUnit);
                xWindow = xField;
            }
        }
    }
    else if (name == "GtkLinkButton")
        xWindow = VclPtr<FixedHyperlink>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_NOLABEL);
    else if ((name == "GtkComboBox") || (name == "GtkComboBoxText") || (name == "VclComboBoxText"))
    {
        OUString sPattern = BuilderUtils::extractCustomProperty(rMap);
        extractModel(id, rMap);
 
        WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
 
        bool bDropdown = BuilderUtils::extractDropdown(rMap);
 
        if (bDropdown)
            nBits |= WB_DROPDOWN;
 
        if (!sPattern.isEmpty())
        {
            OUString sAdjustment = extractAdjustment(rMap);
            connectNumericFormatterAdjustment(id, sAdjustment);
            OUString sUnit = extractUnit(sPattern);
            FieldUnit eUnit = detectMetricUnit(sUnit);
            SAL_WARN("vcl.layout", "making metric box for type: " << name
                << " unit: " << sUnit
                << " name: " << id
                << " use a VclComboBoxNumeric instead");
            VclPtrInstance<MetricBox> xBox(pParent, nBits);
            xBox->EnableAutoSize(true);
            xBox->SetUnit(eUnit);
            xBox->SetDecimalDigits(extractDecimalDigits(sPattern));
            if (eUnit == FUNIT_CUSTOM)
                xBox->SetCustomUnitText(sUnit);
            xWindow = xBox;
        }
        else if (extractEntry(rMap))
        {
            VclPtrInstance<ComboBox> xComboBox(pParent, nBits);
            xComboBox->EnableAutoSize(true);
            xWindow = xComboBox;
        }
        else
        {
            VclPtrInstance<ListBox> xListBox(pParent, nBits|WB_SIMPLEMODE);
            xListBox->EnableAutoSize(true);
            xWindow = xListBox;
        }
    }
    else if (name == "VclComboBoxNumeric")
    {
        OUString sPattern = BuilderUtils::extractCustomProperty(rMap);
        OUString sAdjustment = extractAdjustment(rMap);
        extractModel(id, rMap);
 
        WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK;
 
        bool bDropdown = BuilderUtils::extractDropdown(rMap);
 
        if (bDropdown)
            nBits |= WB_DROPDOWN;
 
        if (!sPattern.isEmpty())
        {
            connectNumericFormatterAdjustment(id, sAdjustment);
            OUString sUnit = extractUnit(sPattern);
            FieldUnit eUnit = detectMetricUnit(sUnit);
            SAL_INFO("vcl.layout", "making metric box for " << name << " " << sUnit);
            VclPtrInstance<MetricBox> xBox(pParent, nBits);
            xBox->EnableAutoSize(true);
            xBox->SetUnit(eUnit);
            xBox->SetDecimalDigits(extractDecimalDigits(sPattern));
            if (eUnit == FUNIT_CUSTOM)
                xBox->SetCustomUnitText(sUnit);
            xWindow = xBox;
        }
        else
        {
            SAL_INFO("vcl.layout", "making numeric box for " << name);
            connectNumericFormatterAdjustment(id, sAdjustment);
            VclPtrInstance<NumericBox> xBox(pParent, nBits);
            if (bDropdown)
                xBox->EnableAutoSize(true);
            xWindow = xBox;
        }
    }
    else if (name == "GtkTreeView")
    {
        //To-Do
        //a) move svtools SvTreeViewBox into vcl
        //b) make that the default target for GtkTreeView
        //c) remove the non-drop down mode of ListBox and convert
        //   everything over to SvTreeViewBox
        //d) remove the users of makeSvTreeViewBox
        extractModel(id, rMap);
        WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK|WB_SIMPLEMODE;
        if (m_bLegacy)
        {
            OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
            if (!sBorder.isEmpty())
                nWinStyle |= WB_BORDER;
        }
        //ListBox manages its own scrolling,
        vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
        if (pRealParent != pParent)
            nWinStyle |= WB_BORDER;
        xWindow = VclPtr<ListBox>::Create(pRealParent, nWinStyle);
        if (pRealParent != pParent)
            cleanupWidgetOwnScrolling(pParent, xWindow, rMap);
    }
    else if (name == "GtkLabel")
    {
        WinBits nWinStyle = WB_CENTER|WB_VCENTER|WB_3DLOOK;
        OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
        if (!sBorder.isEmpty())
            nWinStyle |= WB_BORDER;
        extractMnemonicWidget(id, rMap);
        if (extractSelectable(rMap))
            xWindow = VclPtr<SelectableFixedText>::Create(pParent, nWinStyle);
        else
            xWindow = VclPtr<FixedText>::Create(pParent, nWinStyle);
    }
    else if (name == "GtkImage")
    {
        extractStock(id, rMap);
        xWindow = VclPtr<FixedImage>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_SCALE);
        //such parentless GtkImages are temps used to set icons on buttons
        //default them to hidden to stop e.g. insert->index entry flicking temp
        //full screen windows
        if (!pParent)
        {
            rMap["visible"] = "false";
        }
    }
    else if (name == "GtkSeparator")
    {
        bVertical = extractOrientation(rMap);
        xWindow = VclPtr<FixedLine>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
    }
    else if (name == "GtkScrollbar")
    {
        extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
        bVertical = extractOrientation(rMap);
        xWindow = VclPtr<ScrollBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
    }
    else if (name == "GtkProgressBar")
    {
        extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps);
        bVertical = extractOrientation(rMap);
        xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ);
    }
    else if (name == "GtkScrolledWindow")
    {
        xWindow = VclPtr<VclScrolledWindow>::Create(pParent);
    }
    else if (name == "GtkViewport")
    {
        xWindow = VclPtr<VclViewport>::Create(pParent);
    }
    else if (name == "GtkEventBox")
    {
        xWindow = VclPtr<VclEventBox>::Create(pParent);
    }
    else if (name == "GtkEntry")
    {
        xWindow = VclPtr<Edit>::Create(pParent, WB_LEFT|WB_VCENTER|WB_BORDER|WB_3DLOOK);
        BuilderUtils::ensureDefaultWidthChars(rMap);
    }
    else if (name == "GtkNotebook")
    {
        xWindow = VclPtr<TabControl>::Create(pParent, WB_STDTABCONTROL|WB_3DLOOK);
    }
    else if (name == "GtkDrawingArea")
    {
        OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
        xWindow = VclPtr<VclDrawingArea>::Create(pParent, sBorder.isEmpty() ? WB_TABSTOP : WB_BORDER | WB_TABSTOP);
    }
    else if (name == "GtkTextView")
    {
        extractBuffer(id, rMap);
 
        WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT;
        OUString sBorder = BuilderUtils::extractCustomProperty(rMap);
        if (!sBorder.isEmpty())
            nWinStyle |= WB_BORDER;
        //VclMultiLineEdit manages its own scrolling,
        vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle);
        xWindow = VclPtr<VclMultiLineEdit>::Create(pRealParent, nWinStyle);
        if (pRealParent != pParent)
            cleanupWidgetOwnScrolling(pParent, xWindow, rMap);
    }
    else if (name == "GtkSpinner")
    {
        xWindow = VclPtr<Throbber>::Create(pParent, WB_3DLOOK);
    }
    else if (name == "GtkScale")
    {
        extractAdjustmentToMap(id, rMap, m_pParserState->m_aSliderAdjustmentMaps);
        bool bDrawValue = extractDrawValue(rMap);
        if (bDrawValue)
        {
            OUString sValuePos = extractValuePos(rMap);
            (void)sValuePos;
        }
        bVertical = extractOrientation(rMap);
 
        WinBits nWinStyle = bVertical ? WB_VERT : WB_HORZ;
 
        xWindow = VclPtr<Slider>::Create(pParent, nWinStyle);
    }
    else if (name == "GtkToolbar")
    {
        xWindow = VclPtr<ToolBox>::Create(pParent, WB_3DLOOK | WB_TABSTOP);
    }
    else if (name == "GtkToolButton" || name == "GtkMenuToolButton" ||
             name == "GtkToggleToolButton" || name == "GtkRadioToolButton")
    {
        ToolBox *pToolBox = dynamic_cast<ToolBox*>(pParent);
        if (pToolBox)
        {
            OUString aCommand(extractActionName(rMap));
 
            sal_uInt16 nItemId = 0;
            ToolBoxItemBits nBits = ToolBoxItemBits::NONE;
            if (name == "GtkMenuToolButton")
                nBits |= ToolBoxItemBits::DROPDOWN;
            else if (name == "GtkToggleToolButton")
                nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::CHECKABLE;
            else if (name == "GtkRadioToolButton")
                nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::RADIOCHECK;
 
            if (!aCommand.isEmpty() && m_xFrame.is())
            {
                pToolBox->InsertItem(aCommand, m_xFrame, nBits, extractSizeRequest(rMap));
                nItemId = pToolBox->GetItemId(aCommand);
            }
            else
            {
                nItemId = pToolBox->GetItemCount() + 1;
                    //TODO: ImplToolItems::size_type -> sal_uInt16!
                pToolBox->InsertItem(nItemId, extractLabel(rMap), nBits);
                pToolBox->SetItemCommand(nItemId, aCommand);
                pToolBox->SetHelpId(nItemId, m_sHelpRoot + id);
            }
 
            OUString sTooltip(extractTooltipText(rMap));
            if (!sTooltip.isEmpty())
                pToolBox->SetQuickHelpText(nItemId, sTooltip);
 
            OUString sIconName(extractIconName(rMap));
            if (!sIconName.isEmpty())
                pToolBox->SetItemImage(nItemId, FixedImage::loadThemeImage(sIconName));
 
            if (!extractVisible(rMap))
                pToolBox->HideItem(nItemId);
 
            m_pParserState->m_nLastToolbarId = nItemId;
 
            return nullptr; // no widget to be created
        }
    }
    else if (name == "GtkSeparatorToolItem")
    {
        ToolBox *pToolBox = dynamic_cast<ToolBox*>(pParent);
        if (pToolBox)
        {
            pToolBox->InsertSeparator();
            return nullptr; // no widget to be created
        }
    }
    else if (name == "GtkWindow")
    {
        WinBits nBits = extractDeferredBits(rMap);
        if (nBits & WB_DOCKABLE)
            xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_MOVEABLE);
        else
            xWindow = VclPtr<FloatingWindow>::Create(pParent, nBits|WB_MOVEABLE);
    }
    else if (name == "GtkPopover")
    {
        WinBits nBits = extractDeferredBits(rMap);
        xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_DOCKABLE|WB_MOVEABLE);
    }
    else if (name == "GtkListBox")
    {
        WinBits nBits = extractDeferredBits(rMap);
        xWindow = VclPtr<ListControl>::Create(pParent, nBits);
    }
    else
    {
        sal_Int32 nDelim = name.indexOf('-');
        if (nDelim != -1)
        {
            OUString sFunction(OStringToOUString(OString("make") + name.copy(nDelim+1), RTL_TEXTENCODING_UTF8));
 
            customMakeWidget pFunction = nullptr;
#ifndef DISABLE_DYNLOADING
            OUStringBuffer sModuleBuf;
            sModuleBuf.append(SAL_DLLPREFIX);
            sModuleBuf.append(OStringToOUString(name.copy(0, nDelim), RTL_TEXTENCODING_UTF8));
            sModuleBuf.append(SAL_DLLEXTENSION);
 
            OUString sModule = sModuleBuf.makeStringAndClear();
            ModuleMap::iterator aI = g_aModuleMap.find(sModule);
            if (aI == g_aModuleMap.end())
            {
                std::shared_ptr<NoAutoUnloadModule> pModule;
#if ENABLE_MERGELIBS
                if (!g_pMergedLib->is())
                    g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged"));
                if ((pFunction = reinterpret_cast<customMakeWidget>(g_pMergedLib->getFunctionSymbol(sFunction))))
                    pModule = g_pMergedLib;
#endif
                if (!pFunction)
                {
                    pModule.reset(new NoAutoUnloadModule);
                    bool ok = pModule->loadRelative(&thisModule, sModule);
                    assert(ok && "bad module name in .ui");
                    (void) ok;
                    pFunction = reinterpret_cast<customMakeWidget>(pModule->getFunctionSymbol(sFunction));
                }
                g_aModuleMap.insert(std::make_pair(sModule, pModule));
            }
            else
                pFunction = reinterpret_cast<customMakeWidget>(aI->second->getFunctionSymbol(sFunction));
#else
            pFunction = reinterpret_cast<customMakeWidget>(osl_getFunctionSymbol((oslModule) RTLD_DEFAULT, sFunction.pData));
#endif
            if (pFunction)
            {
                VclPtr<vcl::Window> xParent(pParent);
                pFunction(xWindow, xParent, rMap);
                if (xWindow->GetType() == WindowType::PUSHBUTTON)
                    setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
                else if (xWindow->GetType() == WindowType::MENUBUTTON)
                {
                    OUString sMenu = BuilderUtils::extractCustomProperty(rMap);
                    if (!sMenu.isEmpty())
                        m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu);
                    setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame);
                }
            }
        }
    }
    SAL_INFO_IF(!xWindow, "vcl.layout", "probably need to implement " << name << " or add a make" << name << " function");
    if (xWindow)
    {
        xWindow->SetHelpId(m_sHelpRoot + id);
        SAL_INFO("vcl.layout", "for " << name <<
            ", created " << xWindow.get() << " child of " <<
            pParent << "(" << xWindow->ImplGetWindowImpl()->mpParent.get() << "/" <<
            xWindow->ImplGetWindowImpl()->mpRealParent.get() << "/" <<
            xWindow->ImplGetWindowImpl()->mpBorderWindow.get() << ") with helpid " <<
            xWindow->GetHelpId());
        m_aChildren.emplace_back(id, xWindow, bVertical);
    }
    return xWindow;
}
 
namespace
{
    //return true for window types which exist in vcl but are not themselves
    //represented in the .ui format, i.e. only their children exist.
    bool isConsideredGtkPseudo(vcl::Window const *pWindow)
    {
        return pWindow->GetType() == WindowType::TABPAGE;
    }
}
 
//Any properties from .ui load we couldn't set because of potential virtual methods
//during ctor are applied here
void VclBuilder::setDeferredProperties()
{
    if (!m_bToplevelHasDeferredProperties)
        return;
    stringmap aDeferredProperties;
    aDeferredProperties.swap(m_aDeferredProperties);
    m_bToplevelHasDeferredProperties = false;
    BuilderUtils::set_properties(m_pParent, aDeferredProperties);
}
 
namespace BuilderUtils
{
    void set_properties(vcl::Window *pWindow, const VclBuilder::stringmap &rProps)
    {
        for (auto const& prop : rProps)
        {
            const OString &rKey = prop.first;
            const OUString &rValue = prop.second;
            pWindow->set_property(rKey, rValue);
        }
    }
 
    OUString convertMnemonicMarkup(const OUString &rIn)
    {
        OUStringBuffer aRet(rIn);
        for (sal_Int32 nI = 0; nI < aRet.getLength(); ++nI)
        {
            if (aRet[nI] == '_' && nI+1 < aRet.getLength())
            {
                if (aRet[nI+1] != '_')
                    aRet[nI] = MNEMONIC_CHAR;
                else
                    aRet.remove(nI, 1);
                ++nI;
            }
        }
        return aRet.makeStringAndClear();
    }
 
    OUString extractCustomProperty(VclBuilder::stringmap &rMap)
    {
        OUString sCustomProperty;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("customproperty"));
        if (aFind != rMap.end())
        {
            sCustomProperty = aFind->second;
            rMap.erase(aFind);
        }
        return sCustomProperty;
    }
 
    FieldUnit detectUnit(OUString const& rString)
    {
        OUString const unit(extractUnit(rString));
        return detectMetricUnit(unit);
    }
 
    void ensureDefaultWidthChars(VclBuilder::stringmap &rMap)
    {
        OString sWidthChars("width-chars");
        VclBuilder::stringmap::iterator aFind = rMap.find(sWidthChars);
        if (aFind == rMap.end())
            rMap[sWidthChars] = "25";
    }
 
    bool extractDropdown(VclBuilder::stringmap &rMap)
    {
        bool bDropdown = true;
        VclBuilder::stringmap::iterator aFind = rMap.find(OString("dropdown"));
        if (aFind != rMap.end())
        {
            bDropdown = toBool(aFind->second);
            rMap.erase(aFind);
        }
        return bDropdown;
    }
 
    void reorderWithinParent(vcl::Window &rWindow, sal_uInt16 nNewPosition)
    {
        WindowImpl *pWindowImpl = rWindow.ImplGetWindowImpl();
        if (pWindowImpl->mpParent != pWindowImpl->mpRealParent)
        {
            assert(pWindowImpl->mpBorderWindow == pWindowImpl->mpParent);
            assert(pWindowImpl->mpBorderWindow->ImplGetWindowImpl()->mpParent == pWindowImpl->mpRealParent);
            reorderWithinParent(*pWindowImpl->mpBorderWindow, nNewPosition);
            return;
        }
        rWindow.reorderWithinParent(nNewPosition);
    }
 
    void reorderWithinParent(std::vector<vcl::Window*>& rChilds, bool bIsButtonBox)
    {
        for (size_t i = 0; i < rChilds.size(); ++i)
        {
            reorderWithinParent(*rChilds[i], i);
 
            if (!bIsButtonBox)
                continue;
 
            //The first member of the group for legacy code needs WB_GROUP set and the
            //others not
            WinBits nBits = rChilds[i]->GetStyle();
            nBits &= ~WB_GROUP;
            if (i == 0)
                nBits |= WB_GROUP;
            rChilds[i]->SetStyle(nBits);
        }
    }
 
    sal_Int16 getRoleFromName(const OString& roleName)
    {
        using namespace com::sun::star::accessibility;
 
        static const std::unordered_map<OString, sal_Int16, OStringHash> aAtkRoleToAccessibleRole = {
            /* This is in atkobject.h's AtkRole order */
            { "invalid",               AccessibleRole::UNKNOWN },
            { "accelerator label",     AccessibleRole::UNKNOWN },
            { "alert",                 AccessibleRole::ALERT },
            { "animation",             AccessibleRole::UNKNOWN },
            { "arrow",                 AccessibleRole::UNKNOWN },
            { "calendar",              AccessibleRole::UNKNOWN },
            { "canvas",                AccessibleRole::CANVAS },
            { "check box",             AccessibleRole::CHECK_BOX },
            { "check menu item",       AccessibleRole::CHECK_MENU_ITEM },
            { "color chooser",         AccessibleRole::COLOR_CHOOSER },
            { "column header",         AccessibleRole::COLUMN_HEADER },
            { "combo box",             AccessibleRole::COMBO_BOX },
            { "date editor",           AccessibleRole::DATE_EDITOR },
            { "desktop icon",          AccessibleRole::DESKTOP_ICON },
            { "desktop frame",         AccessibleRole::DESKTOP_PANE }, // ?
            { "dial",                  AccessibleRole::UNKNOWN },
            { "dialog",                AccessibleRole::DIALOG },
            { "directory pane",        AccessibleRole::DIRECTORY_PANE },
            { "drawing area",          AccessibleRole::UNKNOWN },
            { "file chooser",          AccessibleRole::FILE_CHOOSER },
            { "filler",                AccessibleRole::FILLER },
            { "font chooser",          AccessibleRole::FONT_CHOOSER },
            { "frame",                 AccessibleRole::FRAME },
            { "glass pane",            AccessibleRole::GLASS_PANE },
            { "html container",        AccessibleRole::UNKNOWN },
            { "icon",                  AccessibleRole::ICON },
            { "image",                 AccessibleRole::GRAPHIC },
            { "internal frame",        AccessibleRole::INTERNAL_FRAME },
            { "label",                 AccessibleRole::LABEL },
            { "layered pane",          AccessibleRole::LAYERED_PANE },
            { "list",                  AccessibleRole::LIST },
            { "list item",             AccessibleRole::LIST_ITEM },
            { "menu",                  AccessibleRole::MENU },
            { "menu bar",              AccessibleRole::MENU_BAR },
            { "menu item",             AccessibleRole::MENU_ITEM },
            { "option pane",           AccessibleRole::OPTION_PANE },
            { "page tab",              AccessibleRole::PAGE_TAB },
            { "page tab list",         AccessibleRole::PAGE_TAB_LIST },
            { "panel",                 AccessibleRole::PANEL }, // or SHAPE or TEXT_FRAME ?
            { "password text",         AccessibleRole::PASSWORD_TEXT },
            { "popup menu",            AccessibleRole::POPUP_MENU },
            { "progress bar",          AccessibleRole::PROGRESS_BAR },
            { "push button",           AccessibleRole::PUSH_BUTTON }, // or BUTTON_DROPDOWN or BUTTON_MENU
            { "radio button",          AccessibleRole::RADIO_BUTTON },
            { "radio menu item",       AccessibleRole::RADIO_MENU_ITEM },
            { "root pane",             AccessibleRole::ROOT_PANE },
            { "row header",            AccessibleRole::ROW_HEADER },
            { "scroll bar",            AccessibleRole::SCROLL_BAR },
            { "scroll pane",           AccessibleRole::SCROLL_PANE },
            { "separator",             AccessibleRole::SEPARATOR },
            { "slider",                AccessibleRole::SLIDER },
            { "split pane",            AccessibleRole::SPLIT_PANE },
            { "spin button",           AccessibleRole::SPIN_BOX }, // ?
            { "statusbar",             AccessibleRole::STATUS_BAR },
            { "table",                 AccessibleRole::TABLE },
            { "table cell",            AccessibleRole::TABLE_CELL },
            { "table column header",   AccessibleRole::COLUMN_HEADER }, // approximate
            { "table row header",      AccessibleRole::ROW_HEADER }, // approximate
            { "tear off menu item",    AccessibleRole::UNKNOWN },
            { "terminal",              AccessibleRole::UNKNOWN },
            { "text",                  AccessibleRole::TEXT },
            { "toggle button",         AccessibleRole::TOGGLE_BUTTON },
            { "tool bar",              AccessibleRole::TOOL_BAR },
            { "tool tip",              AccessibleRole::TOOL_TIP },
            { "tree",                  AccessibleRole::TREE },
            { "tree table",            AccessibleRole::TREE_TABLE },
            { "unknown",               AccessibleRole::UNKNOWN },
            { "viewport",              AccessibleRole::VIEW_PORT },
            { "window",                AccessibleRole::WINDOW },
            { "header",                AccessibleRole::HEADER },
            { "footer",                AccessibleRole::FOOTER },
            { "paragraph",             AccessibleRole::PARAGRAPH },
            { "ruler",                 AccessibleRole::RULER },
            { "application",           AccessibleRole::UNKNOWN },
            { "autocomplete",          AccessibleRole::UNKNOWN },
            { "edit bar",              AccessibleRole::EDIT_BAR },
            { "embedded",              AccessibleRole::EMBEDDED_OBJECT },
            { "entry",                 AccessibleRole::UNKNOWN },
            { "chart",                 AccessibleRole::CHART },
            { "caption",               AccessibleRole::CAPTION },
            { "document frame",        AccessibleRole::DOCUMENT },
            { "heading",               AccessibleRole::HEADING },
            { "page",                  AccessibleRole::PAGE },
            { "section",               AccessibleRole::SECTION },
            { "redundant object",      AccessibleRole::UNKNOWN },
            { "form",                  AccessibleRole::FORM },
            { "link",                  AccessibleRole::HYPER_LINK },
            { "input method window",   AccessibleRole::UNKNOWN },
            { "table row",             AccessibleRole::UNKNOWN },
            { "tree item",             AccessibleRole::TREE_ITEM },
            { "document spreadsheet",  AccessibleRole::DOCUMENT_SPREADSHEET },
            { "document presentation", AccessibleRole::DOCUMENT_PRESENTATION },
            { "document text",         AccessibleRole::DOCUMENT_TEXT },
            { "document web",          AccessibleRole::DOCUMENT }, // approximate
            { "document email",        AccessibleRole::DOCUMENT }, // approximate
            { "comment",               AccessibleRole::COMMENT }, // or NOTE or END_NOTE or FOOTNOTE or SCROLL_PANE
            { "list box",              AccessibleRole::UNKNOWN },
            { "grouping",              AccessibleRole::GROUP_BOX },
            { "image map",             AccessibleRole::IMAGE_MAP },
            { "notification",          AccessibleRole::UNKNOWN },
            { "info bar",              AccessibleRole::UNKNOWN },
            { "level bar",             AccessibleRole::UNKNOWN },
            { "title bar",             AccessibleRole::UNKNOWN },
            { "block quote",           AccessibleRole::UNKNOWN },
            { "audio",                 AccessibleRole::UNKNOWN },
            { "video",                 AccessibleRole::UNKNOWN },
            { "definition",            AccessibleRole::UNKNOWN },
            { "article",               AccessibleRole::UNKNOWN },
            { "landmark",              AccessibleRole::UNKNOWN },
            { "log",                   AccessibleRole::UNKNOWN },
            { "marquee",               AccessibleRole::UNKNOWN },
            { "math",                  AccessibleRole::UNKNOWN },
            { "rating",                AccessibleRole::UNKNOWN },
            { "timer",                 AccessibleRole::UNKNOWN },
            { "description list",      AccessibleRole::UNKNOWN },
            { "description term",      AccessibleRole::UNKNOWN },
            { "description value",     AccessibleRole::UNKNOWN },
            { "static",                AccessibleRole::STATIC },
            { "math fraction",         AccessibleRole::UNKNOWN },
            { "math root",             AccessibleRole::UNKNOWN },
            { "subscript",             AccessibleRole::UNKNOWN },
            { "superscript",           AccessibleRole::UNKNOWN },
            { "footnote",              AccessibleRole::FOOTNOTE },
        };
 
        auto it = aAtkRoleToAccessibleRole.find(roleName);
        if (it == aAtkRoleToAccessibleRole.end())
            return AccessibleRole::UNKNOWN;
        return it->second;
    }
}
 
VclPtr<vcl::Window> VclBuilder::insertObject(vcl::Window *pParent, const OString &rClass,
    const OString &rID, stringmap &rProps, stringmap &rPango, stringmap &rAtk)
{
    VclPtr<vcl::Window> pCurrentChild;
 
    if (m_pParent && !isConsideredGtkPseudo(m_pParent) && !m_sID.isEmpty() && rID == m_sID)
    {
        pCurrentChild = m_pParent;
 
        //toplevels default to resizable and apparently you can't change them
        //afterwards, so we need to wait until now before we can truly
        //initialize the dialog.
        if (pParent && pParent->IsSystemWindow())
        {
            SystemWindow *pSysWin = static_cast<SystemWindow*>(pCurrentChild.get());
            pSysWin->doDeferredInit(extractDeferredBits(rProps));
            m_bToplevelHasDeferredInit = false;
        }
        else if (pParent && pParent->IsDockingWindow())
        {
            DockingWindow *pDockWin = static_cast<DockingWindow*>(pCurrentChild.get());
            pDockWin->doDeferredInit(extractDeferredBits(rProps));
            m_bToplevelHasDeferredInit = false;
        }
 
        if (pCurrentChild->GetHelpId().isEmpty())
        {
            pCurrentChild->SetHelpId(m_sHelpRoot + m_sID);
            SAL_INFO("vcl.layout", "for toplevel dialog " << this << " " <<
                rID << ", set helpid " << pCurrentChild->GetHelpId());
        }
        m_bToplevelParentFound = true;
    }
    else
    {
        //if we're being inserting under a toplevel dialog whose init is
        //deferred due to waiting to encounter it in this .ui, and it hasn't
        //been seen yet, then make unattached widgets parent-less toplevels
        if (pParent == m_pParent.get() && m_bToplevelHasDeferredInit)
            pParent = nullptr;
        pCurrentChild = makeObject(pParent, rClass, rID, rProps);
    }
 
    if (pCurrentChild)
    {
        pCurrentChild->set_id(OStringToOUString(rID, RTL_TEXTENCODING_UTF8));
        if (pCurrentChild == m_pParent.get() && m_bToplevelHasDeferredProperties)
            m_aDeferredProperties = rProps;
        else
            BuilderUtils::set_properties(pCurrentChild, rProps);
 
        for (auto const& elem : rPango)
        {
            const OString &rKey = elem.first;
            const OUString &rValue = elem.second;
            pCurrentChild->set_font_attribute(rKey, rValue);
        }
 
        m_pParserState->m_aAtkInfo[VclPtr<vcl::Window>(pCurrentChild)] = rAtk;
    }
 
    rProps.clear();
    rPango.clear();
    rAtk.clear();
 
    if (!pCurrentChild)
        pCurrentChild = m_aChildren.empty() ? pParent : m_aChildren.back().m_pWindow.get();
    return pCurrentChild;
}
 
void VclBuilder::handleTabChild(vcl::Window *pParent, xmlreader::XmlReader &reader)
{
    OString sID;
 
    int nLevel = 1;
    stringmap aProperties;
    std::vector<vcl::EnumContext::Context> context;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("object"))
            {
                while (reader.nextAttribute(&nsId, &name))
                {
                    if (name.equals("id"))
                    {
                        name = reader.getAttributeValue(false);
                        sID = OString(name.begin, name.length);
                        sal_Int32 nDelim = sID.indexOf(':');
                        if (nDelim != -1)
                        {
                            OString sPattern = sID.copy(nDelim+1);
                            aProperties[OString("customproperty")] = OUString::fromUtf8(sPattern);
                            sID = sID.copy(0, nDelim);
                        }
                    }
                }
            }
            else if (name.equals("style"))
            {
                int nPriority = 0;
                context = handleStyle(reader, nPriority);
                --nLevel;
            }
            else if (name.equals("property"))
                collectProperty(reader, aProperties);
        }
 
        if (res == xmlreader::XmlReader::Result::End)
            --nLevel;
 
        if (!nLevel)
            break;
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
    }
 
    if (!pParent)
        return;
 
    TabControl *pTabControl = static_cast<TabControl*>(pParent);
    VclBuilder::stringmap::iterator aFind = aProperties.find(OString("label"));
    if (aFind != aProperties.end())
    {
        sal_uInt16 nPageId = pTabControl->GetCurPageId();
        pTabControl->SetPageText(nPageId, aFind->second);
        pTabControl->SetPageName(nPageId, sID);
        if (context.size() != 0)
        {
            TabPage* pPage = pTabControl->GetTabPage(nPageId);
            pPage->SetContext(context);
        }
    }
    else
        pTabControl->RemovePage(pTabControl->GetCurPageId());
}
 
//so that tabbing between controls goes in a visually sensible sequence
//we sort these into a best-tab-order sequence
bool VclBuilder::sortIntoBestTabTraversalOrder::operator()(const vcl::Window *pA, const vcl::Window *pB) const
{
    //sort child order within parent list by grid position
    sal_Int32 nTopA = pA->get_grid_top_attach();
    sal_Int32 nTopB = pB->get_grid_top_attach();
    if (nTopA < nTopB)
        return true;
    if (nTopA > nTopB)
        return false;
    sal_Int32 nLeftA = pA->get_grid_left_attach();
    sal_Int32 nLeftB = pB->get_grid_left_attach();
    if (nLeftA < nLeftB)
        return true;
    if (nLeftA > nLeftB)
        return false;
    //sort into two groups of pack start and pack end
    VclPackType ePackA = pA->get_pack_type();
    VclPackType ePackB = pB->get_pack_type();
    if (ePackA < ePackB)
        return true;
    if (ePackA > ePackB)
        return false;
    bool bVerticalContainer = m_pBuilder->get_window_packing_data(pA->GetParent()).m_bVerticalOrient;
    bool bPackA = pA->get_secondary();
    bool bPackB = pB->get_secondary();
    if (!bVerticalContainer)
    {
        //for horizontal boxes group secondaries before primaries
        if (bPackA > bPackB)
            return true;
        if (bPackA < bPackB)
            return false;
    }
    else
    {
        //for vertical boxes group secondaries after primaries
        if (bPackA < bPackB)
            return true;
        if (bPackA > bPackB)
            return false;
    }
    //honour relative box positions with pack group, (numerical order is reversed
    //for VclPackType::End, they are packed from the end back, but here we need
    //them in visual layout order so that tabbing works as expected)
    sal_Int32 nPackA = m_pBuilder->get_window_packing_data(pA).m_nPosition;
    sal_Int32 nPackB = m_pBuilder->get_window_packing_data(pB).m_nPosition;
    if (nPackA < nPackB)
        return ePackA == VclPackType::Start;
    if (nPackA > nPackB)
        return ePackA != VclPackType::Start;
    //sort labels of Frames before body
    if (pA->GetParent() == pB->GetParent())
    {
        const VclFrame *pFrameParent = dynamic_cast<const VclFrame*>(pA->GetParent());
        if (pFrameParent)
        {
            const vcl::Window *pLabel = pFrameParent->get_label_widget();
            int nFramePosA = (pA == pLabel) ? 0 : 1;
            int nFramePosB = (pB == pLabel) ? 0 : 1;
            return nFramePosA < nFramePosB;
        }
    }
    return false;
}
 
void VclBuilder::handleChild(vcl::Window *pParent, xmlreader::XmlReader &reader)
{
    vcl::Window *pCurrentChild = nullptr;
 
    xmlreader::Span name;
    int nsId;
    OString sType, sInternalChild;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("type"))
        {
            name = reader.getAttributeValue(false);
            sType = OString(name.begin, name.length);
        }
        else if (name.equals("internal-child"))
        {
            name = reader.getAttributeValue(false);
            sInternalChild = OString(name.begin, name.length);
        }
    }
 
    if (sType == "tab")
    {
        handleTabChild(pParent, reader);
        return;
    }
 
    int nLevel = 1;
    while(true)
    {
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            if (name.equals("object") || name.equals("placeholder"))
            {
                pCurrentChild = handleObject(pParent, reader).get();
 
                bool bObjectInserted = pCurrentChild && pParent != pCurrentChild;
 
                if (bObjectInserted)
                {
                    //Internal-children default in glade to not having their visible bits set
                    //even though they are visible (generally anyway)
                    if (!sInternalChild.isEmpty())
                        pCurrentChild->Show();
 
                    //Select the first page if it's a notebook
                    if (pCurrentChild->GetType() == WindowType::TABCONTROL)
                    {
                        TabControl *pTabControl = static_cast<TabControl*>(pCurrentChild);
                        pTabControl->SetCurPageId(pTabControl->GetPageId(0));
 
                        //To-Do add reorder capability to the TabControl
                    }
                    else
                    {
                        // We want to sort labels before contents of frames
                        // for keyboard traversal, especially if there
                        // are multiple widgets using the same mnemonic
                        if (sType == "label")
                        {
                            if (VclFrame *pFrameParent = dynamic_cast<VclFrame*>(pParent))
                                pFrameParent->designate_label(pCurrentChild);
                        }
                        if (sInternalChild.startsWith("vbox") || sInternalChild.startsWith("messagedialog-vbox"))
                        {
                            if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pParent))
                                pBoxParent->set_content_area(static_cast<VclBox*>(pCurrentChild)); // FIXME-VCLPTR
                        }
                        else if (sInternalChild.startsWith("action_area") || sInternalChild.startsWith("messagedialog-action_area"))
                        {
                            vcl::Window *pContentArea = pCurrentChild->GetParent();
                            if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pContentArea ? pContentArea->GetParent() : nullptr))
                            {
                                pBoxParent->set_action_area(static_cast<VclButtonBox*>(pCurrentChild)); // FIXME-VCLPTR
                            }
                        }
 
                        //To-Do make reorder a virtual in Window, move this foo
                        //there and see above
                        std::vector<vcl::Window*> aChilds;
                        for (vcl::Window* pChild = pCurrentChild->GetWindow(GetWindowType::FirstChild); pChild;
                            pChild = pChild->GetWindow(GetWindowType::Next))
                        {
                            aChilds.push_back(pChild);
                        }
 
                        bool bIsButtonBox = dynamic_cast<VclButtonBox*>(pCurrentChild) != nullptr;
 
                        //sort child order within parent so that tabbing
                        //between controls goes in a visually sensible sequence
                        std::stable_sort(aChilds.begin(), aChilds.end(), sortIntoBestTabTraversalOrder(this));
                        BuilderUtils::reorderWithinParent(aChilds, bIsButtonBox);
                    }
                }
            }
            else if (name.equals("packing"))
            {
                handlePacking(pCurrentChild, pParent, reader);
            }
            else if (name.equals("interface"))
            {
                while (reader.nextAttribute(&nsId, &name))
                {
                    if (name.equals("domain"))
                    {
                        name = reader.getAttributeValue(false);
                        sType = OString(name.begin, name.length);
                        m_pParserState->m_aResLocale = Translate::Create(sType.getStr());
                    }
                }
                ++nLevel;
            }
            else
                ++nLevel;
        }
 
        if (res == xmlreader::XmlReader::Result::End)
            --nLevel;
 
        if (!nLevel)
            break;
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
    }
}
 
void VclBuilder::collectPangoAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
{
    xmlreader::Span span;
    int nsId;
 
    OString sProperty;
    OString sValue;
 
    while (reader.nextAttribute(&nsId, &span))
    {
        if (span.equals("name"))
        {
            span = reader.getAttributeValue(false);
            sProperty = OString(span.begin, span.length);
        }
        else if (span.equals("value"))
        {
            span = reader.getAttributeValue(false);
            sValue = OString(span.begin, span.length);
        }
    }
 
    if (!sProperty.isEmpty())
        rMap[sProperty] = OUString::fromUtf8(sValue);
}
 
void VclBuilder::collectAtkRelationAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
{
    xmlreader::Span span;
    int nsId;
 
    OString sProperty;
    OString sValue;
 
    while (reader.nextAttribute(&nsId, &span))
    {
        if (span.equals("type"))
        {
            span = reader.getAttributeValue(false);
            sProperty = OString(span.begin, span.length);
        }
        else if (span.equals("target"))
        {
            span = reader.getAttributeValue(false);
            sValue = OString(span.begin, span.length);
            sal_Int32 nDelim = sValue.indexOf(':');
            if (nDelim != -1)
                sValue = sValue.copy(0, nDelim);
        }
    }
 
    if (!sProperty.isEmpty())
        rMap[sProperty] = OUString::fromUtf8(sValue);
}
 
void VclBuilder::collectAtkRoleAttribute(xmlreader::XmlReader &reader, stringmap &rMap)
{
    xmlreader::Span span;
    int nsId;
 
    OString sProperty;
 
    while (reader.nextAttribute(&nsId, &span))
    {
        if (span.equals("type"))
        {
            span = reader.getAttributeValue(false);
            sProperty = OString(span.begin, span.length);
        }
    }
 
    if (!sProperty.isEmpty())
        rMap["role"] = OUString::fromUtf8(sProperty);
}
 
void VclBuilder::handleRow(xmlreader::XmlReader &reader, const OString &rID)
{
    int nLevel = 1;
 
    ListStore::row aRow;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("col"))
            {
                bool bTranslated = false;
                sal_uInt32 nId = 0;
                OString sContext;
 
                while (reader.nextAttribute(&nsId, &name))
                {
                    if (name.equals("id"))
                    {
                        name = reader.getAttributeValue(false);
                        nId = OString(name.begin, name.length).toInt32();
                    }
                    else if (nId == 0 && name.equals("translatable") && reader.getAttributeValue(false).equals("yes"))
                    {
                        bTranslated = true;
                    }
                    else if (name.equals("context"))
                    {
                        name = reader.getAttributeValue(false);
                        sContext = OString(name.begin, name.length);
                    }
                }
 
                reader.nextItem(
                    xmlreader::XmlReader::Text::Raw, &name, &nsId);
 
                OString sValue = OString(name.begin, name.length);
                OUString sFinalValue;
                if (bTranslated)
                {
                    if (!sContext.isEmpty())
                        sValue = sContext + "\004" + sValue;
                    sFinalValue = Translate::get(sValue.getStr(), m_pParserState->m_aResLocale);
                }
                else
                    sFinalValue = OUString::fromUtf8(sValue);
 
 
                if (aRow.size() < nId+1)
                    aRow.resize(nId+1);
                aRow[nId] = sFinalValue;
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    m_pParserState->m_aModels[rID].m_aEntries.push_back(aRow);
}
 
void VclBuilder::handleListStore(xmlreader::XmlReader &reader, const OString &rID)
{
    int nLevel = 1;
    sal_Int32 nRowIndex = 0;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            if (name.equals("row"))
            {
                handleRow(reader, rID);
                nRowIndex++;
            }
            else
                ++nLevel;
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
}
 
void VclBuilder::handleAtkObject(xmlreader::XmlReader &reader, vcl::Window *pWindow)
{
    assert(pWindow);
 
    int nLevel = 1;
 
    stringmap aProperties;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("property"))
                collectProperty(reader, aProperties);
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    for (auto const& prop : aProperties)
    {
        const OString &rKey = prop.first;
        const OUString &rValue = prop.second;
 
        if (pWindow && rKey.match("AtkObject::"))
            pWindow->set_property(rKey.copy(RTL_CONSTASCII_LENGTH("AtkObject::")), rValue);
        else
            SAL_WARN("vcl.layout", "unhandled atk prop: " << rKey);
    }
}
 
std::vector<ComboBoxTextItem> VclBuilder::handleItems(xmlreader::XmlReader &reader) const
{
    int nLevel = 1;
 
    std::vector<ComboBoxTextItem> aItems;
    sal_Int32 nItemIndex = 0;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("item"))
            {
                bool bTranslated = false;
                OString sContext, sId;
 
                while (reader.nextAttribute(&nsId, &name))
                {
                    if (name.equals("translatable") && reader.getAttributeValue(false).equals("yes"))
                    {
                        bTranslated = true;
                    }
                    else if (name.equals("context"))
                    {
                        name = reader.getAttributeValue(false);
                        sContext = OString(name.begin, name.length);
                    }
                    else if (name.equals("id"))
                    {
                        name = reader.getAttributeValue(false);
                        sId = OString(name.begin, name.length);
                    }
                }
 
                reader.nextItem(
                    xmlreader::XmlReader::Text::Raw, &name, &nsId);
 
                OString sValue = OString(name.begin, name.length);
                OUString sFinalValue;
                if (bTranslated)
                {
                    if (!sContext.isEmpty())
                        sValue = sContext + "\004" + sValue;
                    sFinalValue = Translate::get(sValue.getStr(), m_pParserState->m_aResLocale);
                }
                else
                    sFinalValue = OUString::fromUtf8(sValue);
 
                if (m_pStringReplace)
                    sFinalValue = (*m_pStringReplace)(sFinalValue);
 
                aItems.emplace_back(sFinalValue, sId);
                ++nItemIndex;
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    return aItems;
}
 
void VclBuilder::handleMenu(xmlreader::XmlReader &reader, const OString &rID)
{
    VclPtr<PopupMenu> pCurrentMenu = VclPtr<PopupMenu>::Create();
 
    int nLevel = 1;
 
    stringmap aProperties;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            if (name.equals("child"))
            {
                handleMenuChild(pCurrentMenu, reader);
            }
            else
            {
                ++nLevel;
                if (name.equals("property"))
                    collectProperty(reader, aProperties);
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    m_aMenus.emplace_back(rID, pCurrentMenu);
}
 
void VclBuilder::handleMenuChild(PopupMenu *pParent, xmlreader::XmlReader &reader)
{
    xmlreader::Span name;
    int nsId;
 
    int nLevel = 1;
    while(true)
    {
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            if (name.equals("object") || name.equals("placeholder"))
            {
                handleMenuObject(pParent, reader);
            }
            else
                ++nLevel;
        }
 
        if (res == xmlreader::XmlReader::Result::End)
            --nLevel;
 
        if (!nLevel)
            break;
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
    }
}
 
void VclBuilder::handleMenuObject(PopupMenu *pParent, xmlreader::XmlReader &reader)
{
    OString sClass;
    OString sID;
    OUString sCustomProperty;
    PopupMenu *pSubMenu = nullptr;
 
    xmlreader::Span name;
    int nsId;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("class"))
        {
            name = reader.getAttributeValue(false);
            sClass = OString(name.begin, name.length);
        }
        else if (name.equals("id"))
        {
            name = reader.getAttributeValue(false);
            sID = OString(name.begin, name.length);
            sal_Int32 nDelim = sID.indexOf(':');
            if (nDelim != -1)
            {
                sCustomProperty = OUString::fromUtf8(sID.copy(nDelim+1));
                sID = sID.copy(0, nDelim);
            }
        }
    }
 
    int nLevel = 1;
 
    stringmap aProperties;
    accelmap aAccelerators;
 
    if (!sCustomProperty.isEmpty())
        aProperties[OString("customproperty")] = sCustomProperty;
 
    while(true)
    {
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            if (name.equals("child"))
            {
                size_t nChildMenuIdx = m_aMenus.size();
                handleChild(nullptr, reader);
                assert(m_aMenus.size() > nChildMenuIdx && "menu not inserted");
                pSubMenu = m_aMenus[nChildMenuIdx].m_pMenu;
            }
            else
            {
                ++nLevel;
                if (name.equals("property"))
                    collectProperty(reader, aProperties);
                else if (name.equals("accelerator"))
                    collectAccelerator(reader, aAccelerators);
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    insertMenuObject(pParent, pSubMenu, sClass, sID, aProperties, aAccelerators);
}
 
void VclBuilder::handleSizeGroup(xmlreader::XmlReader &reader)
{
    m_pParserState->m_aSizeGroups.emplace_back();
    SizeGroup &rSizeGroup = m_pParserState->m_aSizeGroups.back();
 
    int nLevel = 1;
 
    while(true)
    {
        xmlreader::Span name;
        int nsId;
 
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("widget"))
            {
                while (reader.nextAttribute(&nsId, &name))
                {
                    if (name.equals("name"))
                    {
                        name = reader.getAttributeValue(false);
                        OString sWidget = OString(name.begin, name.length);
                        sal_Int32 nDelim = sWidget.indexOf(':');
                        if (nDelim != -1)
                            sWidget = sWidget.copy(0, nDelim);
                        rSizeGroup.m_aWidgets.push_back(sWidget);
                    }
                }
            }
            else
            {
                if (name.equals("property"))
                    collectProperty(reader, rSizeGroup.m_aProperties);
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
}
 
namespace
{
    vcl::KeyCode makeKeyCode(const std::pair<OString,OString> &rKey)
    {
        bool bShift = rKey.second.indexOf("GDK_SHIFT_MASK") != -1;
        bool bMod1 = rKey.second.indexOf("GDK_CONTROL_MASK") != -1;
        bool bMod2 = rKey.second.indexOf("GDK_MOD1_MASK") != -1;
        bool bMod3 = rKey.second.indexOf("GDK_MOD2_MASK") != -1;
 
        if (rKey.first == "Insert")
            return vcl::KeyCode(KEY_INSERT, bShift, bMod1, bMod2, bMod3);
        else if (rKey.first == "Delete")
            return vcl::KeyCode(KEY_DELETE, bShift, bMod1, bMod2, bMod3);
 
        assert (rKey.first.getLength() == 1);
        sal_Char cChar = rKey.first.toChar();
 
        if (cChar >= 'a' && cChar <= 'z')
            return vcl::KeyCode(KEY_A + (cChar - 'a'), bShift, bMod1, bMod2, bMod3);
        else if (cChar >= 'A' && cChar <= 'Z')
            return vcl::KeyCode(KEY_A + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
        else if (cChar >= '0' && cChar <= '9')
            return vcl::KeyCode(KEY_0 + (cChar - 'A'), bShift, bMod1, bMod2, bMod3);
 
        return vcl::KeyCode(cChar, bShift, bMod1, bMod2, bMod3);
    }
}
 
void VclBuilder::insertMenuObject(PopupMenu *pParent, PopupMenu *pSubMenu, const OString &rClass, const OString &rID,
    stringmap &rProps, accelmap &rAccels)
{
    sal_uInt16 nOldCount = pParent->GetItemCount();
    sal_uInt16 nNewId = ++m_pParserState->m_nLastMenuItemId;
 
    if (rClass == "GtkMenuItem")
    {
        OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
        OUString aCommand(extractActionName(rProps));
        pParent->InsertItem(nNewId, sLabel, MenuItemBits::NONE , rID);
        pParent->SetItemCommand(nNewId, aCommand);
        if (pSubMenu)
            pParent->SetPopupMenu(nNewId, pSubMenu);
    }
    else if (rClass == "GtkCheckMenuItem")
    {
        OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
        OUString aCommand(extractActionName(rProps));
        pParent->InsertItem(nNewId, sLabel, MenuItemBits::CHECKABLE, rID);
        pParent->SetItemCommand(nNewId, aCommand);
    }
    else if (rClass == "GtkRadioMenuItem")
    {
        OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps)));
        OUString aCommand(extractActionName(rProps));
        pParent->InsertItem(nNewId, sLabel, MenuItemBits::AUTOCHECK | MenuItemBits::RADIOCHECK, rID);
        pParent->SetItemCommand(nNewId, aCommand);
    }
    else if (rClass == "GtkSeparatorMenuItem")
    {
        pParent->InsertSeparator(rID);
    }
 
    SAL_WARN_IF(nOldCount == pParent->GetItemCount(), "vcl.layout", "probably need to implement " << rClass);
 
    if (nOldCount != pParent->GetItemCount())
    {
        pParent->SetHelpId(nNewId, m_sHelpRoot + rID);
 
        for (auto const& prop : rProps)
        {
            const OString &rKey = prop.first;
            const OUString &rValue = prop.second;
 
            if (rKey == "tooltip-markup")
                pParent->SetTipHelpText(nNewId, rValue);
            else if (rKey == "tooltip-text")
                pParent->SetTipHelpText(nNewId, rValue);
            else if (rKey == "visible")
                pParent->ShowItem(nNewId, toBool(rValue));
            else if (rKey == "has-default" && toBool(rValue))
                pParent->SetSelectedEntry(nNewId);
            else
                SAL_INFO("vcl.layout", "unhandled property: " << rKey);
        }
 
        for (auto const& accel : rAccels)
        {
            const OString &rSignal = accel.first;
            const auto &rValue = accel.second;
 
            if (rSignal == "activate")
                pParent->SetAccelKey(nNewId, makeKeyCode(rValue));
            else
                SAL_INFO("vcl.layout", "unhandled accelerator for: " << rSignal);
        }
    }
 
    rProps.clear();
}
 
/// Insert items to a ComboBox or a ListBox.
/// They have no common ancestor that would have 'InsertEntry()', so use a template.
template<typename T> bool insertItems(vcl::Window *pWindow, VclBuilder::stringmap &rMap, const std::vector<ComboBoxTextItem> &rItems)
{
    T *pContainer = dynamic_cast<T*>(pWindow);
    if (!pContainer)
        return false;
 
    sal_uInt16 nActiveId = extractActive(rMap);
    for (auto const& item : rItems)
    {
        sal_Int32 nPos = pContainer->InsertEntry(item.m_sItem);
        if (!item.m_sId.isEmpty())
            pContainer->SetEntryData(nPos, new OUString(OUString::fromUtf8(item.m_sId)));
    }
    if (nActiveId < rItems.size())
        pContainer->SelectEntryPos(nActiveId);
 
    return true;
}
 
VclPtr<vcl::Window> VclBuilder::handleObject(vcl::Window *pParent, xmlreader::XmlReader &reader)
{
    OString sClass;
    OString sID;
    OUString sCustomProperty;
 
    xmlreader::Span name;
    int nsId;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("class"))
        {
            name = reader.getAttributeValue(false);
            sClass = OString(name.begin, name.length);
        }
        else if (name.equals("id"))
        {
            name = reader.getAttributeValue(false);
            sID = OString(name.begin, name.length);
            sal_Int32 nDelim = sID.indexOf(':');
            if (nDelim != -1)
            {
                sCustomProperty = OUString::fromUtf8(sID.copy(nDelim+1));
                sID = sID.copy(0, nDelim);
            }
        }
    }
 
    if (sClass == "GtkListStore")
    {
        handleListStore(reader, sID);
        return nullptr;
    }
    else if (sClass == "GtkMenu")
    {
        handleMenu(reader, sID);
        return nullptr;
    }
    else if (sClass == "GtkSizeGroup")
    {
        handleSizeGroup(reader);
        return nullptr;
    }
    else if (sClass == "AtkObject")
    {
        handleAtkObject(reader, pParent);
        return nullptr;
    }
 
    int nLevel = 1;
 
    stringmap aProperties, aPangoAttributes;
    stringmap aAtkAttributes;
    std::vector<ComboBoxTextItem> aItems;
 
    if (!sCustomProperty.isEmpty())
        aProperties[OString("customproperty")] = sCustomProperty;
 
    VclPtr<vcl::Window> pCurrentChild;
    while(true)
    {
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            if (name.equals("child"))
            {
                if (!pCurrentChild)
                {
                    pCurrentChild = insertObject(pParent, sClass, sID,
                        aProperties, aPangoAttributes, aAtkAttributes);
                }
                handleChild(pCurrentChild, reader);
            }
            else if (name.equals("items"))
                aItems = handleItems(reader);
            else if (name.equals("style"))
            {
                int nPriority = 0;
                std::vector<vcl::EnumContext::Context> aContext = handleStyle(reader, nPriority);
                if (nPriority != 0)
                {
                    vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pCurrentChild.get());
                    SAL_WARN_IF(!pPrioritable, "vcl", "priority set for not supported item");
                    if (pPrioritable)
                        pPrioritable->SetPriority(nPriority);
                }
                if (aContext.size() != 0)
                {
                    vcl::IContext* pContextControl = dynamic_cast<vcl::IContext*>(pCurrentChild.get());
                    SAL_WARN_IF(!pContextControl, "vcl", "context set for not supported item");
                    if (pContextControl)
                        pContextControl->SetContext(aContext);
                }
            }
            else
            {
                ++nLevel;
                if (name.equals("property"))
                    collectProperty(reader, aProperties);
                else if (name.equals("attribute"))
                    collectPangoAttribute(reader, aPangoAttributes);
                else if (name.equals("relation"))
                    collectAtkRelationAttribute(reader, aAtkAttributes);
                else if (name.equals("role"))
                    collectAtkRoleAttribute(reader, aAtkAttributes);
                else if (name.equals("action-widget"))
                    handleActionWidget(reader);
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    if (sClass == "GtkAdjustment")
    {
        m_pParserState->m_aAdjustments[sID] = aProperties;
        return nullptr;
    }
    else if (sClass == "GtkTextBuffer")
    {
        m_pParserState->m_aTextBuffers[sID] = aProperties;
        return nullptr;
    }
 
    if (!pCurrentChild)
    {
        pCurrentChild = insertObject(pParent, sClass, sID, aProperties,
            aPangoAttributes, aAtkAttributes);
    }
 
    if (!aItems.empty())
    {
        // try to fill-in the items
        if (!insertItems<ComboBox>(pCurrentChild, aProperties, aItems))
            insertItems<ListBox>(pCurrentChild, aProperties, aItems);
    }
 
    return pCurrentChild;
}
 
void VclBuilder::handlePacking(vcl::Window *pCurrent, vcl::Window *pParent, xmlreader::XmlReader &reader)
{
    xmlreader::Span name;
    int nsId;
 
    int nLevel = 1;
 
    while(true)
    {
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("property"))
                applyPackingProperty(pCurrent, pParent, reader);
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
}
 
void VclBuilder::applyPackingProperty(vcl::Window *pCurrent,
    vcl::Window *pParent,
    xmlreader::XmlReader &reader)
{
    if (!pCurrent)
        return;
 
    //ToolBoxItems are not true widgets just elements
    //of the ToolBox itself
    ToolBox *pToolBoxParent = nullptr;
    if (pCurrent == pParent)
        pToolBoxParent = dynamic_cast<ToolBox*>(pParent);
 
    xmlreader::Span name;
    int nsId;
 
    if (pCurrent->GetType() == WindowType::SCROLLWINDOW)
    {
        auto aFind = m_pParserState->m_aRedundantParentWidgets.find(VclPtr<vcl::Window>(pCurrent));
        if (aFind != m_pParserState->m_aRedundantParentWidgets.end())
        {
            pCurrent = aFind->second;
            assert(pCurrent);
        }
    }
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("name"))
        {
            name = reader.getAttributeValue(false);
            OString sKey(name.begin, name.length);
            sKey = sKey.replace('_', '-');
            reader.nextItem(
                xmlreader::XmlReader::Text::Raw, &name, &nsId);
            OString sValue(name.begin, name.length);
 
            if (sKey == "expand" || sKey == "resize")
            {
                bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1'));
                if (pToolBoxParent)
                    pToolBoxParent->SetItemExpand(m_pParserState->m_nLastToolbarId, bTrue);
                else
                    pCurrent->set_expand(bTrue);
                continue;
            }
 
            if (pToolBoxParent)
                continue;
 
            if (sKey == "fill")
            {
                bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1'));
                pCurrent->set_fill(bTrue);
            }
            else if (sKey == "pack-type")
            {
                VclPackType ePackType = (!sValue.isEmpty() && (sValue[0] == 'e' || sValue[0] == 'E')) ? VclPackType::End : VclPackType::Start;
                pCurrent->set_pack_type(ePackType);
            }
            else if (sKey == "left-attach")
            {
                pCurrent->set_grid_left_attach(sValue.toInt32());
            }
            else if (sKey == "top-attach")
            {
                pCurrent->set_grid_top_attach(sValue.toInt32());
            }
            else if (sKey == "width")
            {
                pCurrent->set_grid_width(sValue.toInt32());
            }
            else if (sKey == "height")
            {
                pCurrent->set_grid_height(sValue.toInt32());
            }
            else if (sKey == "padding")
            {
                pCurrent->set_padding(sValue.toInt32());
            }
            else if (sKey == "position")
            {
                set_window_packing_position(pCurrent, sValue.toInt32());
            }
            else if (sKey == "secondary")
            {
                pCurrent->set_secondary(toBool(sValue));
            }
            else if (sKey == "non-homogeneous")
            {
                pCurrent->set_non_homogeneous(toBool(sValue));
            }
            else if (sKey == "homogeneous")
            {
                pCurrent->set_non_homogeneous(!toBool(sValue));
            }
            else
            {
                SAL_WARN("vcl.layout", "unknown packing: " << sKey);
            }
        }
    }
}
 
std::vector<vcl::EnumContext::Context> VclBuilder::handleStyle(xmlreader::XmlReader &reader, int &nPriority)
{
    std::vector<vcl::EnumContext::Context> aContext;
 
    xmlreader::Span name;
    int nsId;
 
    int nLevel = 1;
 
    while(true)
    {
        xmlreader::XmlReader::Result res = reader.nextItem(
            xmlreader::XmlReader::Text::NONE, &name, &nsId);
 
        if (res == xmlreader::XmlReader::Result::Done)
            break;
 
        if (res == xmlreader::XmlReader::Result::Begin)
        {
            ++nLevel;
            if (name.equals("class"))
            {
                OString classStyle = getStyleClass(reader);
 
                if (classStyle.startsWith("context-"))
                {
                    OString sContext = classStyle.copy(classStyle.indexOf('-') + 1);
                    OUString sContext2 = OUString(sContext.getStr(), sContext.getLength(), RTL_TEXTENCODING_UTF8);
                    aContext.push_back(vcl::EnumContext::GetContextEnum(sContext2));
                }
                else if (classStyle.startsWith("priority-"))
                {
                    OString aPriority = classStyle.copy(classStyle.indexOf('-') + 1);
                    OUString aPriority2 = OUString(aPriority.getStr(), aPriority.getLength(), RTL_TEXTENCODING_UTF8);
                    nPriority = aPriority2.toInt32();
                }
                else
                {
                    SAL_WARN("vcl.layout", "unknown class: " << classStyle);
                }
            }
        }
 
        if (res == xmlreader::XmlReader::Result::End)
        {
            --nLevel;
        }
 
        if (!nLevel)
            break;
    }
 
    return aContext;
}
 
OString VclBuilder::getStyleClass(xmlreader::XmlReader &reader)
{
    xmlreader::Span name;
    int nsId;
    OString aRet;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("name"))
        {
            name = reader.getAttributeValue(false);
            aRet = OString (name.begin, name.length);
        }
    }
 
    return aRet;
}
 
void VclBuilder::collectProperty(xmlreader::XmlReader &reader, stringmap &rMap) const
{
    xmlreader::Span name;
    int nsId;
 
    OString sProperty, sContext;
 
    bool bTranslated = false;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("name"))
        {
            name = reader.getAttributeValue(false);
            sProperty = OString(name.begin, name.length);
        }
        else if (name.equals("context"))
        {
            name = reader.getAttributeValue(false);
            sContext = OString(name.begin, name.length);
        }
        else if (name.equals("translatable") && reader.getAttributeValue(false).equals("yes"))
        {
            bTranslated = true;
        }
    }
 
    reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
    OString sValue = OString(name.begin, name.length);
    OUString sFinalValue;
    if (bTranslated)
    {
        if (!sContext.isEmpty())
            sValue = sContext + "\004" + sValue;
        sFinalValue = Translate::get(sValue.getStr(), m_pParserState->m_aResLocale);
    }
    else
        sFinalValue = OUString::fromUtf8(sValue);
 
    if (!sProperty.isEmpty())
    {
        sProperty = sProperty.replace('_', '-');
        if (m_pStringReplace)
            sFinalValue = (*m_pStringReplace)(sFinalValue);
        rMap[sProperty] = sFinalValue;
    }
}
 
void VclBuilder::handleActionWidget(xmlreader::XmlReader &reader)
{
    xmlreader::Span name;
    int nsId;
 
    OString sResponse;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("response"))
        {
            name = reader.getAttributeValue(false);
            sResponse = OString(name.begin, name.length);
        }
    }
 
    reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId);
    OString sID = OString(name.begin, name.length);
    sal_Int32 nDelim = sID.indexOf(':');
    if (nDelim != -1)
        sID = sID.copy(0, nDelim);
    set_response(sID, sResponse.toInt32());
}
 
void VclBuilder::collectAccelerator(xmlreader::XmlReader &reader, accelmap &rMap)
{
    xmlreader::Span name;
    int nsId;
 
    OString sProperty;
    OString sValue;
    OString sModifiers;
 
    while (reader.nextAttribute(&nsId, &name))
    {
        if (name.equals("key"))
        {
            name = reader.getAttributeValue(false);
            sValue = OString(name.begin, name.length);
        }
        else if (name.equals("signal"))
        {
            name = reader.getAttributeValue(false);
            sProperty = OString(name.begin, name.length);
        }
        else if (name.equals("modifiers"))
        {
            name = reader.getAttributeValue(false);
            sModifiers = OString(name.begin, name.length);
        }
    }
 
    if (!sProperty.isEmpty() && !sValue.isEmpty())
    {
        rMap[sProperty] = std::make_pair(sValue, sModifiers);
    }
}
 
vcl::Window *VclBuilder::get_widget_root()
{
    return m_aChildren.empty() ? nullptr : m_aChildren[0].m_pWindow.get();
}
 
vcl::Window *VclBuilder::get_by_name(const OString& sID)
{
    for (auto const& child : m_aChildren)
    {
        if (child.m_sID == sID)
            return child.m_pWindow;
    }
 
    return nullptr;
}
 
PopupMenu *VclBuilder::get_menu(const OString& sID)
{
    for (auto const& menu : m_aMenus)
    {
        if (menu.m_sID == sID)
            return menu.m_pMenu;
    }
 
    return nullptr;
}
 
void VclBuilder::set_response(const OString& sID, short nResponse)
{
    switch (nResponse)
    {
        case -5:
            nResponse = RET_OK;
            break;
        case -6:
            nResponse = RET_CANCEL;
            break;
        case -7:
            nResponse = RET_CLOSE;
            break;
        case -8:
            nResponse = RET_YES;
            break;
        case -9:
            nResponse = RET_NO;
            break;
        case -11:
            nResponse = RET_HELP;
            break;
    };
 
    assert(nResponse >= 0);
 
    for (auto & child : m_aChildren)
    {
        if (child.m_sID == sID)
        {
            PushButton* pPushButton = dynamic_cast<PushButton*>(child.m_pWindow.get());
            assert(pPushButton);
            Dialog* pDialog = pPushButton->GetParentDialog();
            assert(pDialog);
            pDialog->add_button(pPushButton, nResponse, false);
            return;
        }
    }
 
    assert(false);
}
 
void VclBuilder::delete_by_name(const OString& sID)
{
    for (std::vector<WinAndId>::iterator aI = m_aChildren.begin(),
         aEnd = m_aChildren.end(); aI != aEnd; ++aI)
    {
        if (aI->m_sID == sID)
        {
            aI->m_pWindow.disposeAndClear();
            m_aChildren.erase(aI);
            break;
        }
    }
}
 
void VclBuilder::delete_by_window(vcl::Window *pWindow)
{
    drop_ownership(pWindow);
    pWindow->disposeOnce();
}
 
void VclBuilder::drop_ownership(const vcl::Window *pWindow)
{
    for (std::vector<WinAndId>::iterator aI = m_aChildren.begin(),
         aEnd = m_aChildren.end(); aI != aEnd; ++aI)
    {
        if (aI->m_pWindow == pWindow)
        {
            m_aChildren.erase(aI);
            break;
        }
    }
}
 
OString VclBuilder::get_by_window(const vcl::Window *pWindow) const
{
    for (auto const& child : m_aChildren)
    {
        if (child.m_pWindow == pWindow)
            return child.m_sID;
    }
 
    return OString();
}
 
VclBuilder::PackingData VclBuilder::get_window_packing_data(const vcl::Window *pWindow) const
{
    //We've stored the return of new Control, some of these get
    //border windows placed around them which are what you get
    //from GetChild, so scoot up a level if necessary to get the
    //window whose position value we have
    const vcl::Window *pPropHolder = pWindow->ImplGetWindowImpl()->mpClientWindow ?
        pWindow->ImplGetWindowImpl()->mpClientWindow : pWindow;
 
    for (auto const& child : m_aChildren)
    {
        if (child.m_pWindow == pPropHolder)
            return child.m_aPackingData;
    }
 
    return PackingData();
}
 
void VclBuilder::set_window_packing_position(const vcl::Window *pWindow, sal_Int32 nPosition)
{
    for (auto & child : m_aChildren)
    {
        if (child.m_pWindow == pWindow)
            child.m_aPackingData.m_nPosition = nPosition;
    }
}
 
const VclBuilder::ListStore *VclBuilder::get_model_by_name(const OString& sID) const
{
    std::map<OString, ListStore>::const_iterator aI = m_pParserState->m_aModels.find(sID);
    if (aI != m_pParserState->m_aModels.end())
        return &(aI->second);
    return nullptr;
}
 
const VclBuilder::TextBuffer *VclBuilder::get_buffer_by_name(const OString& sID) const
{
    std::map<OString, TextBuffer>::const_iterator aI = m_pParserState->m_aTextBuffers.find(sID);
    if (aI != m_pParserState->m_aTextBuffers.end())
        return &(aI->second);
    return nullptr;
}
 
const VclBuilder::Adjustment *VclBuilder::get_adjustment_by_name(const OString& sID) const
{
    std::map<OString, Adjustment>::const_iterator aI = m_pParserState->m_aAdjustments.find(sID);
    if (aI != m_pParserState->m_aAdjustments.end())
        return &(aI->second);
    return nullptr;
}
 
void VclBuilder::mungeModel(ListBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId)
{
    for (auto const& entry : rStore.m_aEntries)
    {
        const ListStore::row &rRow = entry;
        sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]);
        if (rRow.size() > 1)
        {
            if (m_bLegacy)
            {
                sal_IntPtr nValue = rRow[1].toInt32();
                rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue));
            }
            else
            {
                if (!rRow[1].isEmpty())
                    rTarget.SetEntryData(nEntry, new OUString(rRow[1]));
            }
        }
    }
    if (nActiveId < rStore.m_aEntries.size())
        rTarget.SelectEntryPos(nActiveId);
}
 
void VclBuilder::mungeAdjustment(NumericFormatter &rTarget, const Adjustment &rAdjustment)
{
    int nMul = rtl_math_pow10Exp(1, rTarget.GetDecimalDigits());
 
    for (auto const& elem : rAdjustment)
    {
        const OString &rKey = elem.first;
        const OUString &rValue = elem.second;
 
        if (rKey == "upper")
        {
            sal_Int64 nUpper = rValue.toDouble() * nMul;
            rTarget.SetMax(nUpper);
            rTarget.SetLast(nUpper);
        }
        else if (rKey == "lower")
        {
            sal_Int64 nLower = rValue.toDouble() * nMul;
            rTarget.SetMin(nLower);
            rTarget.SetFirst(nLower);
        }
        else if (rKey == "value")
        {
            sal_Int64 nValue = rValue.toDouble() * nMul;
            rTarget.SetValue(nValue);
        }
        else if (rKey == "step-increment")
        {
            sal_Int64 nSpinSize = rValue.toDouble() * nMul;
            rTarget.SetSpinSize(nSpinSize);
        }
        else
        {
            SAL_INFO("vcl.layout", "unhandled property :" << rKey);
        }
    }
}
 
void VclBuilder::mungeAdjustment(TimeField &rTarget, const Adjustment &rAdjustment)
{
    for (auto const& elem : rAdjustment)
    {
        const OString &rKey = elem.first;
        const OUString &rValue = elem.second;
 
        if (rKey == "upper")
        {
            tools::Time aUpper(rValue.toInt32());
            rTarget.SetMax(aUpper);
            rTarget.SetLast(aUpper);
        }
        else if (rKey == "lower")
        {
            tools::Time aLower(rValue.toInt32());
            rTarget.SetMin(aLower);
            rTarget.SetFirst(aLower);
        }
        else if (rKey == "value")
        {
            tools::Time aValue(rValue.toInt32());
            rTarget.SetTime(aValue);
        }
        else
        {
            SAL_INFO("vcl.layout", "unhandled property :" << rKey);
        }
    }
}
 
void VclBuilder::mungeAdjustment(DateField &rTarget, const Adjustment &rAdjustment)
{
    for (auto const& elem : rAdjustment)
    {
        const OString &rKey = elem.first;
        const OUString &rValue = elem.second;
 
        if (rKey == "upper")
        {
            Date aUpper(rValue.toInt32());
            rTarget.SetMax(aUpper);
            rTarget.SetLast(aUpper);
        }
        else if (rKey == "lower")
        {
            Date aLower(rValue.toInt32());
            rTarget.SetMin(aLower);
            rTarget.SetFirst(aLower);
        }
        else if (rKey == "value")
        {
            Date aValue(rValue.toInt32());
            rTarget.SetDate(aValue);
        }
        else
        {
            SAL_INFO("vcl.layout", "unhandled property :" << rKey);
        }
    }
}
 
void VclBuilder::mungeAdjustment(ScrollBar &rTarget, const Adjustment &rAdjustment)
{
    for (auto const& elem : rAdjustment)
    {
        const OString &rKey = elem.first;
        const OUString &rValue = elem.second;
 
        if (rKey == "upper")
            rTarget.SetRangeMax(rValue.toInt32());
        else if (rKey == "lower")
            rTarget.SetRangeMin(rValue.toInt32());
        else if (rKey == "value")
            rTarget.SetThumbPos(rValue.toInt32());
        else if (rKey == "step-increment")
            rTarget.SetLineSize(rValue.toInt32());
        else if (rKey == "page-increment")
            rTarget.SetPageSize(rValue.toInt32());
        else
        {
            SAL_INFO("vcl.layout", "unhandled property :" << rKey);
        }
    }
}
 
void VclBuilder::mungeAdjustment(Slider& rTarget, const Adjustment& rAdjustment)
{
    for (auto const& elem : rAdjustment)
    {
        const OString &rKey = elem.first;
        const OUString &rValue = elem.second;
 
        if (rKey == "upper")
            rTarget.SetRangeMax(rValue.toInt32());
        else if (rKey == "lower")
            rTarget.SetRangeMin(rValue.toInt32());
        else if (rKey == "value")
            rTarget.SetThumbPos(rValue.toInt32());
        else if (rKey == "step-increment")
            rTarget.SetLineSize(rValue.toInt32());
        else if (rKey == "page-increment")
            rTarget.SetPageSize(rValue.toInt32());
        else
        {
            SAL_INFO("vcl.layout", "unhandled property :" << rKey);
        }
    }
}
 
void VclBuilder::mungeTextBuffer(VclMultiLineEdit &rTarget, const TextBuffer &rTextBuffer)
{
    for (auto const& elem : rTextBuffer)
    {
        const OString &rKey = elem.first;
        const OUString &rValue = elem.second;
 
        if (rKey == "text")
            rTarget.SetText(rValue);
        else
        {
            SAL_INFO("vcl.layout", "unhandled property :" << rKey);
        }
    }
}
 
VclBuilder::ParserState::ParserState()
    : m_nLastToolbarId(0)
    , m_nLastMenuItemId(0)
{}
 
VclBuilder::MenuAndId::MenuAndId(const OString &rId, PopupMenu *pMenu)
            : m_sID(rId)
            , m_pMenu(pMenu)
{};
 
VclBuilder::MenuAndId::~MenuAndId() {}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V623 Consider inspecting the '?:' operator. A temporary object of the 'VclPtr' type is being created and subsequently destroyed. Check third operand.

V601 The 'false' value becomes a class object. Inspect the third argument.

V547 Expression '!pFunction' is always true.