a18672dd3e
In WidgetBuilder::handleChild, pass the child type not only to WidgetBuilder::tweakInsertedChild, but also to WidgetBuilder::handleObject (add a new param for that) and from there down into the virtual WidgetBuilder::insertObject, so it can be evaluated by subclasses when creating new widgets. While it is not used yet, an upcoming commit will make use of it in QtBuilder, in order to distinguish between the two "GtkExpander" children: the label and the actual content child. As described in the "GtkExpander as GtkBuildable" doc [1]: > The GtkExpander implementation of the GtkBuildable interface supports > placing a child in the label position by specifying “label” as the > “type” attribute of a <child> element. A normal content child can be > specified without specifying a <child> type attribute. [1] https://docs.gtk.org/gtk3/class.Expander.html Change-Id: I3e308a6642d72b55d0ccc597dac716b236c22d61 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177191 Tested-by: Jenkins Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
562 lines
18 KiB
C++
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: */
|