1
0
Fork 0
libreoffice/include/vcl/widgetbuilder.hxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

562 lines
18 KiB
C++

/* -*- 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/.
*/
#pragma once
#include <com/sun/star/uno/Exception.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <vcl/builderbase.hxx>
#include <xmlreader/span.hxx>
#include <xmlreader/xmlreader.hxx>
/* Template class for a Builder to create a hierarchy of widgets from a .ui file
* for dialogs, sidebar, etc.
*
* This class parses the .ui file and calls overridable methods
* so subclasses can create the widgets of a specific toolkit.
*
* The VclBuilder subclass is the implementation using LibreOffice's own VCL toolkit
* and the QtBuilder subclass uses native Qt widgets.
*/
template <typename Widget, typename WidgetPtr, typename MenuClass, typename MenuPtr>
class WidgetBuilder : public BuilderBase
{
protected:
struct MenuAndId
{
OUString m_sID;
MenuPtr m_pMenu;
MenuAndId(OUString sId, MenuClass* pMenu)
: m_sID(std::move(sId))
, m_pMenu(pMenu)
{
}
};
WidgetBuilder(std::u16string_view sUIDir, const OUString& rUIFile, bool bLegacy)
: BuilderBase(sUIDir, rUIFile, bLegacy)
{
}
virtual ~WidgetBuilder() = default;
std::vector<MenuAndId> m_aMenus;
public:
//sID may not exist
MenuClass* get_menu(std::u16string_view sID)
{
for (auto const& menu : m_aMenus)
{
if (menu.m_sID == sID)
return menu.m_pMenu.get();
}
return nullptr;
}
protected:
void processUIFile(Widget* pParent)
{
try
{
xmlreader::XmlReader reader(getUIFileUrl());
handleChild(pParent, nullptr, reader);
}
catch (const css::uno::Exception& rExcept)
{
TOOLS_WARN_EXCEPTION("vcl.builder", "Unable to read .ui file " << getUIFileUrl());
reportException(rExcept);
assert(false && "missing ui file or missing gb_CppunitTest_use_uiconfigs dependency");
throw;
}
// Set Mnemonic widgets when everything has been imported
for (const MnemonicWidgetMap& rMnemonic : getMnemonicWidgetMaps())
{
setMnemonicWidget(rMnemonic.m_sID, rMnemonic.m_sValue);
}
}
// either pParent or pAtkProps must be set, pParent for a child of a widget, pAtkProps for
// collecting the atk info for a GtkMenuItem or tab child
void handleChild(Widget* pParent, stringmap* pAtkProps, xmlreader::XmlReader& reader,
bool bToolbarItem = false)
{
xmlreader::Span name;
int nsId;
OString sType, sInternalChild;
while (reader.nextAttribute(&nsId, &name))
{
if (name == "type")
{
name = reader.getAttributeValue(false);
sType = OString(name.begin, name.length);
}
else if (name == "internal-child")
{
name = reader.getAttributeValue(false);
sInternalChild = OString(name.begin, name.length);
}
}
if (sType == "tab")
{
handleTabChild(pParent, reader);
return;
}
WidgetPtr pCurrentChild = nullptr;
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 == "object" || name == "placeholder")
{
pCurrentChild = handleObject(pParent, pAtkProps, reader, sType, sInternalChild,
bToolbarItem);
bool bObjectInserted = pCurrentChild && pParent != pCurrentChild;
if (bObjectInserted)
tweakInsertedChild(pParent, pCurrentChild, sType, sInternalChild);
}
else if (name == "packing")
{
const stringmap aPackingProperties = collectPackingProperties(reader);
applyPackingProperties(pCurrentChild, pParent, aPackingProperties);
}
else if (name == "interface")
{
while (reader.nextAttribute(&nsId, &name))
{
if (name == "domain")
handleInterfaceDomain(reader);
}
++nLevel;
}
else
++nLevel;
}
if (res == xmlreader::XmlReader::Result::End)
--nLevel;
if (!nLevel)
break;
if (res == xmlreader::XmlReader::Result::Done)
break;
}
}
WidgetPtr handleObject(Widget* pParent, stringmap* pAtkProps, xmlreader::XmlReader& reader,
std::string_view sType, std::string_view sInternalChild,
bool bToolbarItem)
{
OUString sClass;
OUString sID;
OUString sCustomProperty;
extractClassAndIdAndCustomProperty(reader, sClass, sID, sCustomProperty);
if (sClass == "GtkListStore" || sClass == "GtkTreeStore")
{
handleListStore(reader, sID, sClass);
return nullptr;
}
else if (sClass == "GtkMenu")
{
handleMenu(reader, sID);
return nullptr;
}
else if (sClass == "GtkSizeGroup")
{
handleSizeGroup(reader);
return nullptr;
}
else if (sClass == "AtkObject")
{
assert((pParent || pAtkProps) && "must have one set");
assert(!(pParent && pAtkProps) && "must not have both");
auto aAtkProperties = handleAtkObject(reader);
if (pParent)
applyAtkProperties(pParent, aAtkProperties, bToolbarItem);
if (pAtkProps)
*pAtkProps = std::move(aAtkProperties);
return nullptr;
}
int nLevel = 1;
stringmap aProperties, aPangoAttributes;
stringmap aAtkAttributes;
std::vector<ComboBoxTextItem> aItems;
if (!sCustomProperty.isEmpty())
aProperties[u"customproperty"_ustr] = sCustomProperty;
// Internal-children default in glade to not having their visible bits set
// even though they are visible (generally anyway)
if (!sInternalChild.empty())
aProperties[u"visible"_ustr] = "True";
WidgetPtr pCurrentChild = nullptr;
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 == "child")
{
if (!pCurrentChild)
{
pCurrentChild = insertObject(pParent, sClass, sType, sID, aProperties,
aPangoAttributes, aAtkAttributes);
}
handleChild(pCurrentChild, nullptr, reader, isToolbarItemClass(sClass));
}
else if (name == "items")
aItems = handleItems(reader);
else if (name == "style")
{
int nPriority = 0;
std::vector<vcl::EnumContext::Context> aContext
= handleStyle(reader, nPriority);
if (nPriority != 0)
setPriority(pCurrentChild, nPriority);
if (!aContext.empty())
setContext(pCurrentChild, std::move(aContext));
}
else
{
++nLevel;
if (name == "property")
collectProperty(reader, aProperties);
else if (name == "attribute")
collectPangoAttribute(reader, aPangoAttributes);
else if (name == "relation")
collectAtkRelationAttribute(reader, aAtkAttributes);
else if (name == "role")
collectAtkRoleAttribute(reader, aAtkAttributes);
else if (name == "action-widget")
handleActionWidget(reader);
}
}
if (res == xmlreader::XmlReader::Result::End)
{
--nLevel;
}
if (!nLevel)
break;
}
if (sClass == "GtkAdjustment")
{
addAdjustment(sID, aProperties);
return nullptr;
}
else if (sClass == "GtkTextBuffer")
{
addTextBuffer(sID, aProperties);
return nullptr;
}
if (!pCurrentChild)
{
pCurrentChild = insertObject(pParent, sClass, sType, sID, aProperties, aPangoAttributes,
aAtkAttributes);
}
if (!aItems.empty())
insertComboBoxOrListBoxItems(pCurrentChild, aProperties, aItems);
return pCurrentChild;
}
void handleTabChild(Widget* pParent, xmlreader::XmlReader& reader)
{
std::vector<OUString> sIDs;
int nLevel = 1;
stringmap aProperties;
stringmap aAtkProperties;
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 == "object")
{
while (reader.nextAttribute(&nsId, &name))
{
if (name == "id")
{
name = reader.getAttributeValue(false);
OUString sID(name.begin, name.length, RTL_TEXTENCODING_UTF8);
sal_Int32 nDelim = sID.indexOf(':');
if (nDelim != -1)
{
aProperties[u"customproperty"_ustr] = sID.copy(nDelim + 1);
sID = sID.copy(0, nDelim);
}
sIDs.push_back(sID);
}
}
}
else if (name == "style")
{
int nPriority = 0;
context = handleStyle(reader, nPriority);
--nLevel;
}
else if (name == "property")
collectProperty(reader, aProperties);
else if (name == "child" && isHorizontalTabControl(pParent))
{
// just to collect the atk properties (if any) for the label
handleChild(nullptr, &aAtkProperties, reader);
--nLevel;
}
}
if (res == xmlreader::XmlReader::Result::End)
--nLevel;
if (!nLevel)
break;
if (res == xmlreader::XmlReader::Result::Done)
break;
}
if (!pParent)
return;
applyTabChildProperties(pParent, sIDs, context, aProperties, aAtkProperties);
}
void handleMenu(xmlreader::XmlReader& reader, const OUString& rID)
{
MenuPtr pCurrentMenu = createMenu(rID);
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 == "child")
{
handleMenuChild(pCurrentMenu, reader);
}
else
{
++nLevel;
if (name == "property")
collectProperty(reader, aProperties);
}
}
if (res == xmlreader::XmlReader::Result::End)
{
--nLevel;
}
if (!nLevel)
break;
}
m_aMenus.emplace_back(rID, pCurrentMenu);
}
void handleMenuChild(MenuClass* 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 == "object" || name == "placeholder")
{
handleMenuObject(pParent, reader);
}
else
++nLevel;
}
if (res == xmlreader::XmlReader::Result::End)
--nLevel;
if (!nLevel)
break;
if (res == xmlreader::XmlReader::Result::Done)
break;
}
}
void handleMenuObject(MenuClass* pParent, xmlreader::XmlReader& reader)
{
OUString sClass;
OUString sID;
OUString sCustomProperty;
MenuClass* pSubMenu = nullptr;
xmlreader::Span name;
int nsId;
while (reader.nextAttribute(&nsId, &name))
{
if (name == "class")
{
name = reader.getAttributeValue(false);
sClass = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
}
else if (name == "id")
{
name = reader.getAttributeValue(false);
sID = OUString(name.begin, name.length, RTL_TEXTENCODING_UTF8);
if (isLegacy())
{
sal_Int32 nDelim = sID.indexOf(':');
if (nDelim != -1)
{
sCustomProperty = sID.subView(nDelim + 1);
sID = sID.copy(0, nDelim);
}
}
}
}
int nLevel = 1;
stringmap aProperties;
stringmap aAtkProperties;
accelmap aAccelerators;
if (!sCustomProperty.isEmpty())
aProperties[u"customproperty"_ustr] = 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 == "child")
{
size_t nChildMenuIdx = m_aMenus.size();
handleChild(nullptr, &aAtkProperties, reader);
bool bSubMenuInserted = m_aMenus.size() > nChildMenuIdx;
if (bSubMenuInserted)
pSubMenu = m_aMenus[nChildMenuIdx].m_pMenu;
}
else
{
++nLevel;
if (name == "property")
collectProperty(reader, aProperties);
else if (name == "accelerator")
collectAccelerator(reader, aAccelerators);
}
}
if (res == xmlreader::XmlReader::Result::End)
{
--nLevel;
}
if (!nLevel)
break;
}
insertMenuObject(pParent, pSubMenu, sClass, sID, aProperties, aAtkProperties,
aAccelerators);
}
virtual void applyAtkProperties(Widget* pWidget, const stringmap& rProperties,
bool bToolbarItem)
= 0;
virtual void applyPackingProperties(Widget* pCurrentChild, Widget* pParent,
const stringmap& rPackingProperties)
= 0;
virtual void applyTabChildProperties(Widget* pParent, const std::vector<OUString>& rIDs,
std::vector<vcl::EnumContext::Context>& rContext,
stringmap& rProperties, stringmap& rAtkProperties)
= 0;
virtual void insertComboBoxOrListBoxItems(Widget* pWidget, stringmap& rMap,
const std::vector<ComboBoxTextItem>& rItems)
= 0;
virtual WidgetPtr insertObject(Widget* pParent, const OUString& rClass, std::string_view sType,
const OUString& rID, stringmap& rProps,
stringmap& rPangoAttributes, stringmap& rAtkProps)
= 0;
virtual void tweakInsertedChild(Widget* pParent, Widget* pCurrentChild, std::string_view sType,
std::string_view sInternalChild)
= 0;
virtual void setMnemonicWidget(const OUString& rLabelId, const OUString& rMnemonicWidgetId) = 0;
virtual void setPriority(Widget* pWidget, int nPriority) = 0;
virtual void setContext(Widget* pWidget, std::vector<vcl::EnumContext::Context>&& aContext) = 0;
// Whether the given widget is a horizontal, i.e. non-vertical tab control
virtual bool isHorizontalTabControl(Widget* pWidget) = 0;
virtual MenuPtr createMenu(const OUString& rID) = 0;
virtual void insertMenuObject(MenuClass* pParent, MenuClass* pSubMenu, const OUString& rClass,
const OUString& rID, stringmap& rProps, stringmap& rAtkProps,
accelmap& rAccels)
= 0;
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */