So far, the WidgetBuilder::insertObject implementations in
the VclBuilder and QtBuilder subclasses were clearing the properties
passed to them. (QtBuilder currently only uses one of the 3 maps, so
only clears one.) Stop doing it there.
There are 2 places where WidgetBuilder::insertObject is called
inside the WidgetBuilder implementation. Leave the behavior of
the first one effectively unchanged, by clearing the maps after the call
to WidgetBuilder::insertObject (that now no longer clears the
maps itself).
For the second call, don't explicitly clear the maps, so that
the active index can still be extracted and passed to the
WidgetBuilder::insertComboBoxOrListBoxItems call that follows
in case there are entries.
Otherwise, behavior is unchanged there as well, because the
local variables then go out of scope as the end of the
method is reached.
Still being able to extract the "active" property for comboboxes
ensures that the correct entry gets preselected if one
has explicitly been specified in the .ui file.
This can e.g. be seen with the gen vcl plugin
and the qt6 vcl plugin with SAL_VCL_QT_USE_WELDED_WIDGETS=1
for the widget test dialog that can be triggered like this:
* in "Tools" -> "Customize", set a keyboard shortcut for
function/UNO command ".uno:WidgetTestDialog"
* close the dialog and activate the shortcut
Without this commit in place, the first entry ("Test 1")
was preselected in the combobox for both cases.
With this commit in place, the third entry ("Test 3")
is now preselected as expected because that entry
is specified as the active entry:
<object class="GtkComboBoxText" id="combo_box_enable">
<property name="visible">True</property>
<property name="can-focus">False</property>
-> <property name="active">2</property>
<property name="has-entry">True</property>
<items>
<item id="0">Test 1</item>
<item id="0">Test 2</item>
<item id="0">Test 3</item>
<item id="0">Test 4</item>
<item id="0">Test 5</item>
</items>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can-focus">False</property>
</object>
</child>
</object>
This also matches the behavior seen with the gtk3 vcl
plugin (with or without this commit in place, because
WidgetBuilder with its custom .ui file parsing isn't used
for it.)
Change-Id: Ibe36984e5216aca272d4979e9b6109ffa36286d2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/203201
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
Tested-by: Jenkins
575 lines
19 KiB
C++
575 lines
19 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 <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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Set radiobutton groups when everything has been imported
|
|
for (const RadioButtonGroupMap& rGroup : getRadioButtonGroupMaps())
|
|
setRadioButtonGroup(rGroup.m_sID, rGroup.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);
|
|
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);
|
|
aProperties.clear();
|
|
aPangoAttributes.clear();
|
|
aAtkAttributes.clear();
|
|
}
|
|
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, std::move(aProperties));
|
|
return nullptr;
|
|
}
|
|
else if (sClass == "GtkTextBuffer")
|
|
{
|
|
addTextBuffer(sID, std::move(aProperties));
|
|
return nullptr;
|
|
}
|
|
|
|
if (!pCurrentChild)
|
|
{
|
|
pCurrentChild = insertObject(pParent, sClass, sType, sID, aProperties, aPangoAttributes,
|
|
aAtkAttributes);
|
|
}
|
|
|
|
if (!aItems.empty())
|
|
{
|
|
const sal_Int32 nActiveIndex = BuilderBase::extractActive(aProperties);
|
|
insertComboBoxOrListBoxItems(pCurrentChild, aItems, nActiveIndex);
|
|
}
|
|
|
|
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,
|
|
const std::vector<ComboBoxTextItem>& rItems,
|
|
sal_Int32 nActiveIndex)
|
|
= 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 setRadioButtonGroup(const OUString& rRadioButtonId, const OUString& rRadioGroupId)
|
|
= 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: */
|