/* -*- 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 <sfx2/devtools/ObjectInspectorTreeHandler.hxx>
#include <sfx2/sfxresid.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include "DevToolsStrings.hrc"
#include <com/sun/star/beans/theIntrospection.hpp>
#include <com/sun/star/beans/XIntrospection.hpp>
#include <com/sun/star/beans/XIntrospectionAccess.hpp>
#include <com/sun/star/beans/PropertyConcept.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/MethodConcept.hpp>
#include <com/sun/star/reflection/theCoreReflection.hpp>
#include <com/sun/star/reflection/XIdlReflection.hpp>
#include <com/sun/star/reflection/XIdlMethod.hpp>
#include <com/sun/star/reflection/XIdlArray.hpp>
#include <com/sun/star/reflection/XEnumTypeDescription.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/script/Invocation.hpp>
#include <com/sun/star/script/XInvocation2.hpp>
#include <com/sun/star/script/MemberType.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XTypeProvider.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/extract.hxx>
#include <vcl/settings.hxx>
#include <i18nlangtag/languagetag.hxx>
using namespace css;
namespace
{
constexpr OUStringLiteral constTypeDescriptionManagerSingletonName
= u"/singletons/com.sun.star.reflection.theTypeDescriptionManager";
OUString enumValueToEnumName(uno::Any const& aValue,
uno::Reference<uno::XComponentContext> const& xContext)
{
sal_Int32 nIntValue = 0;
if (!cppu::enum2int(nIntValue, aValue))
return OUString();
uno::Reference<container::XHierarchicalNameAccess> xManager;
xManager.set(xContext->getValueByName(constTypeDescriptionManagerSingletonName),
uno::UNO_QUERY);
uno::Reference<reflection::XEnumTypeDescription> xTypeDescription;
xTypeDescription.set(xManager->getByHierarchicalName(aValue.getValueTypeName()),
uno::UNO_QUERY);
const uno::Sequence<sal_Int32> aValues = xTypeDescription->getEnumValues();
sal_Int32 nValuesIndex = std::find(aValues.begin(), aValues.end(), nIntValue) - aValues.begin();
uno::Sequence<OUString> aNames = xTypeDescription->getEnumNames();
return aNames[nValuesIndex];
}
OUString getInterfaceImplementationClass(uno::Reference<uno::XInterface> const& xInterface)
{
auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xInterface, uno::UNO_QUERY);
if (xServiceInfo.is())
return xServiceInfo->getImplementationName();
return OUString();
}
/** converts basic any value to a string */
OUString convertBasicValueToString(const uno::Any& aValue,
const uno::Reference<uno::XComponentContext>& xContext)
{
OUString aRetStr;
// return early if we don't have any value
if (!aValue.hasValue())
return SfxResId(STR_ANY_VALUE_NULL);
uno::TypeClass eType = aValue.getValueTypeClass();
switch (eType)
{
case uno::TypeClass_BOOLEAN:
{
bool bBool = aValue.get<bool>();
aRetStr = bBool ? SfxResId(STR_ANY_VALUE_TRUE) : SfxResId(STR_ANY_VALUE_FALSE);
break;
}
case uno::TypeClass_CHAR:
{
sal_Unicode aChar = aValue.get<sal_Unicode>();
aRetStr = OUString::number(aChar);
break;
}
case uno::TypeClass_STRING:
{
aRetStr = u"\"" + aValue.get<OUString>() + u"\"";
break;
}
case uno::TypeClass_FLOAT:
{
auto aNumber = aValue.get<float>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_DOUBLE:
{
auto aNumber = aValue.get<double>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_BYTE:
{
auto aNumber = aValue.get<sal_Int8>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_SHORT:
{
auto aNumber = aValue.get<sal_Int16>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_LONG:
{
auto aNumber = aValue.get<sal_Int32>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_HYPER:
{
auto aNumber = aValue.get<sal_Int64>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_UNSIGNED_SHORT:
{
auto aNumber = aValue.get<sal_uInt16>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_UNSIGNED_LONG:
{
auto aNumber = aValue.get<sal_uInt32>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_UNSIGNED_HYPER:
{
auto aNumber = aValue.get<sal_uInt64>();
aRetStr = OUString::number(aNumber);
break;
}
case uno::TypeClass_TYPE:
{
auto aType = aValue.get<uno::Type>();
aRetStr = aType.getTypeName();
break;
}
case uno::TypeClass_ENUM:
{
aRetStr = enumValueToEnumName(aValue, xContext);
break;
}
default:
break;
}
return aRetStr;
}
// returns a name of the object, if available
OUString getInterfaceName(uno::Reference<uno::XInterface> const& xInterface,
const uno::Reference<uno::XComponentContext>& xContext)
{
uno::Reference<container::XNamed> xNamed(xInterface, uno::UNO_QUERY);
if (xNamed.is())
return xNamed->getName();
auto xInvocationFactory = css::script::Invocation::create(xContext);
uno::Sequence<uno::Any> aParameters = { uno::Any(xInterface) };
auto xInvocationInterface = xInvocationFactory->createInstanceWithArguments(aParameters);
if (xInvocationInterface.is())
{
uno::Reference<script::XInvocation2> xInvocation(xInvocationInterface, uno::UNO_QUERY);
if (xInvocation.is() && xInvocation->hasProperty(u"Name"_ustr))
{
uno::Any aAny = xInvocation->getValue(u"Name"_ustr);
if (aAny.hasValue() && aAny.getValueTypeClass() == uno::TypeClass_STRING)
return aAny.get<OUString>();
}
}
return OUString();
}
OUString convertAnyToString(const uno::Any& aValue,
const uno::Reference<uno::XComponentContext>& xContext)
{
// return early if we don't have any value
if (!aValue.hasValue())
return SfxResId(STR_ANY_VALUE_NULL);
OUString aRetStr;
uno::TypeClass eType = aValue.getValueTypeClass();
switch (eType)
{
case uno::TypeClass_INTERFACE:
{
uno::Reference<uno::XInterface> xInterface(aValue, uno::UNO_QUERY);
if (!xInterface.is())
aRetStr = SfxResId(STR_ANY_VALUE_NULL);
else
{
OUString aImplementationClass = getInterfaceImplementationClass(xInterface);
if (aImplementationClass.isEmpty())
aImplementationClass = SfxResId(STR_CLASS_UNKNOWN);
aRetStr
= SfxResId(STR_PROPERTY_VALUE_OBJECT).replaceFirst("%1", aImplementationClass);
OUString aString = getInterfaceName(xInterface, xContext);
if (!aString.isEmpty())
aRetStr += " {" + aString + "}";
}
break;
}
case uno::TypeClass_STRUCT:
{
aRetStr = SfxResId(STR_PROPERTY_VALUE_STRUCT);
break;
}
default:
{
aRetStr = convertBasicValueToString(aValue, xContext);
break;
}
}
return aRetStr;
}
OUString convertAnyToShortenedString(const uno::Any& aValue,
const uno::Reference<uno::XComponentContext>& xContext)
{
// return early if we don't have any value
if (!aValue.hasValue())
return SfxResId(STR_ANY_VALUE_NULL);
OUString aRetStr;
uno::TypeClass eType = aValue.getValueTypeClass();
constexpr const sal_Int32 constMaxStringLength = 60;
switch (eType)
{
case uno::TypeClass_INTERFACE:
{
aRetStr = convertAnyToString(aValue, xContext);
if (aRetStr.getLength() > constMaxStringLength + 3)
aRetStr = OUString::Concat(aRetStr.subView(0, constMaxStringLength)) + u"...";
break;
}
case uno::TypeClass_STRING:
{
OUString aString = convertAnyToString(aValue, xContext);
if (aString.getLength() > constMaxStringLength + 4)
aString = OUString::Concat(aString.subView(0, constMaxStringLength)) + u"\"...";
aRetStr = aString.replaceAll("\n", " ");
break;
}
default:
{
aRetStr = convertAnyToString(aValue, xContext);
break;
}
}
return aRetStr;
}
/** converts an any's type to a string (in a short form) */
OUString getAnyType(const uno::Any& aValue)
{
OUString aTypeName = aValue.getValueTypeName();
return aTypeName.replaceAll("com.sun.star", "css");
}
/** converts a Type to a XIdlClass */
uno::Reference<reflection::XIdlClass>
convertTypeToIdlClass(const uno::Type& rType,
const uno::Reference<uno::XComponentContext>& xContext)
{
auto xReflection = reflection::theCoreReflection::get(xContext);
return xReflection->forName(rType.getTypeName());
}
// Object inspector nodes
/** Object inspector node's main interface
*
* The interface for the "attached" object to a tree view nodes that
* are added to the tree views of the object inspector part. The node
* can return the main value of the node (object name) and if present
* also the values for additional columns. It signals if a tree needs
* an expander and fills the children of the tree is any exists.
*
*/
class ObjectInspectorNodeInterface
{
public:
ObjectInspectorNodeInterface() = default;
virtual ~ObjectInspectorNodeInterface() {}
// main value (object name) of the tree view node
virtual OUString getObjectName() = 0;
// should show the expander for the tree view node
virtual bool shouldShowExpander() { return false; }
// fill the children for the current tree view node
virtual void fillChildren(std::unique_ptr<weld::TreeView>& rTree, const weld::TreeIter* pParent)
= 0;
// fill any additional column values for the current tree view node
virtual std::vector<std::pair<sal_Int32, OUString>> getColumnValues()
{
return std::vector<std::pair<sal_Int32, OUString>>();
}
};
// appends the node to the root of the tree view
OUString lclAppendNode(const std::unique_ptr<weld::TreeView>& pTree,
ObjectInspectorNodeInterface* pEntry)
{
OUString sName = pEntry->getObjectName();
OUString sId(weld::toId(pEntry));
std::unique_ptr<weld::TreeIter> pCurrent = pTree->make_iterator();
pTree->insert(nullptr, -1, &sName, &sId, nullptr, nullptr, pEntry->shouldShowExpander(),
pCurrent.get());
pTree->set_text_emphasis(*pCurrent, true, 0);
for (auto const& rPair : pEntry->getColumnValues())
{
pTree->set_text(*pCurrent, rPair.second, rPair.first);
}
return sId;
}
// appends the node to the parent
OUString lclAppendNodeToParent(const std::unique_ptr<weld::TreeView>& pTree,
const weld::TreeIter* pParent, ObjectInspectorNodeInterface* pEntry)
{
OUString sName = pEntry->getObjectName();
OUString sId(weld::toId(pEntry));
std::unique_ptr<weld::TreeIter> pCurrent = pTree->make_iterator();
pTree->insert(pParent, -1, &sName, &sId, nullptr, nullptr, pEntry->shouldShowExpander(),
pCurrent.get());
pTree->set_text_emphasis(*pCurrent, true, 0);
for (auto const& rPair : pEntry->getColumnValues())
{
pTree->set_text(*pCurrent, rPair.second, rPair.first);
}
return sId;
}
/** Node that represent just a simple string with no children or columns */
class SimpleStringNode : public ObjectInspectorNodeInterface
{
protected:
OUString msName;
public:
SimpleStringNode(OUString sName)
: msName(std::move(sName))
{
}
void fillChildren(std::unique_ptr<weld::TreeView>& /*rTree*/,
const weld::TreeIter* /*pParent*/) override
{
}
OUString getObjectName() override { return msName; }
};
/** Node represents a method of an object */
class MethodNode : public ObjectInspectorNodeInterface
{
private:
uno::Reference<reflection::XIdlMethod> mxMethod;
public:
MethodNode(uno::Reference<reflection::XIdlMethod> xMethod)
: mxMethod(std::move(xMethod))
{
}
OUString getObjectName() override { return mxMethod->getName(); }
static OUString simpleTypeName(uno::Reference<reflection::XIdlClass> const& xClass)
{
switch (xClass->getTypeClass())
{
case uno::TypeClass_INTERFACE:
return SfxResId(STR_METHOD_TYPE_OBJECT);
case uno::TypeClass_STRUCT:
return SfxResId(STR_METHOD_TYPE_STRUCT);
case uno::TypeClass_ENUM:
return SfxResId(STR_METHOD_TYPE_ENUM);
case uno::TypeClass_SEQUENCE:
return SfxResId(STR_METHOD_TYPE_SEQUENCE);
default:
break;
}
return xClass->getName();
}
std::vector<std::pair<sal_Int32, OUString>> getColumnValues() override
{
OUString aOutString;
auto xClass = mxMethod->getReturnType();
aOutString = simpleTypeName(xClass);
OUString aInString;
const auto aParameters = mxMethod->getParameterInfos();
bool bFirst = true;
for (auto const& rParameterInfo : aParameters)
{
if (!bFirst)
aInString += ", ";
else
bFirst = false;
switch (rParameterInfo.aMode)
{
case reflection::ParamMode_IN:
aInString += SfxResId(STR_PARMETER_MODE_IN) + " ";
break;
case reflection::ParamMode_OUT:
aInString += SfxResId(STR_PARMETER_MODE_OUT) + " ";
break;
case reflection::ParamMode_INOUT:
aInString += SfxResId(STR_PARMETER_MODE_IN_AND_OUT) + " ";
break;
default:
break;
}
aInString += rParameterInfo.aName + " : " + simpleTypeName(rParameterInfo.aType);
}
OUString aImplementationClass = mxMethod->getDeclaringClass()->getName();
return {
{ 1, aOutString },
{ 2, aInString },
{ 3, aImplementationClass },
};
}
void fillChildren(std::unique_ptr<weld::TreeView>& /*rTree*/,
const weld::TreeIter* /*pParent*/) override
{
}
};
/** Node represents a class (XIdlClass) of an object.
*
* Children are superclasses of the current class. XInterface superclass
* is ignored.
*
*/
class ClassNode : public ObjectInspectorNodeInterface
{
private:
uno::Reference<reflection::XIdlClass> mxClass;
static bool isXInterface(uno::Reference<reflection::XIdlClass> const& xClass)
{
return xClass->getName() == "com.sun.star.uno.XInterface";
}
public:
ClassNode(uno::Reference<reflection::XIdlClass> xClass)
: mxClass(std::move(xClass))
{
}
bool shouldShowExpander() override
{
auto const xSuperClasses = mxClass->getSuperclasses();
return xSuperClasses.getLength() > 2
|| (xSuperClasses.getLength() == 1 && !isXInterface(xSuperClasses[0]));
}
OUString getObjectName() override { return mxClass->getName(); }
// Fill superclasses
void fillChildren(std::unique_ptr<weld::TreeView>& rTree,
const weld::TreeIter* pParent) override
{
auto const xSuperClasses = mxClass->getSuperclasses();
for (auto const& xSuper : xSuperClasses)
{
if (!isXInterface(xSuper))
lclAppendNodeToParent(rTree, pParent, new ClassNode(xSuper));
}
}
};
/** Node represents a basic value, that can be any object, sequence, struct */
class BasicValueNode : public SimpleStringNode
{
protected:
uno::Any maAny;
OUString mrInfo;
uno::Reference<uno::XComponentContext> mxContext;
ObjectInspectorNodeInterface*
createNodeObjectForAny(OUString const& rName, const uno::Any& rAny, OUString const& mrInfo);
public:
BasicValueNode(OUString const& rName, uno::Any aAny, OUString aInfo,
uno::Reference<uno::XComponentContext> xContext)
: SimpleStringNode(rName)
, maAny(std::move(aAny))
, mrInfo(std::move(aInfo))
, mxContext(std::move(xContext))
{
}
const uno::Any& getAny() const { return maAny; }
bool shouldShowExpander() override
{
if (maAny.hasValue())
{
switch (maAny.getValueTypeClass())
{
case uno::TypeClass_INTERFACE:
{
uno::Reference<uno::XInterface> xInterface(maAny, uno::UNO_QUERY);
return xInterface.is();
}
case uno::TypeClass_SEQUENCE:
return true;
default:
break;
}
}
return false;
}
std::vector<std::pair<sal_Int32, OUString>> getColumnValues() override
{
OUString aValue = convertAnyToShortenedString(maAny, mxContext);
OUString aType = getAnyType(maAny);
return { { 1, aValue }, { 2, aType }, { 3, mrInfo } };
}
};
/** Node represents a property */
class GenericPropertiesNode : public BasicValueNode
{
public:
GenericPropertiesNode(OUString const& rName, uno::Any const& rAny, OUString const& rInfo,
uno::Reference<uno::XComponentContext> const& xContext)
: BasicValueNode(rName, rAny, rInfo, xContext)
{
}
void fillChildren(std::unique_ptr<weld::TreeView>& pTree,
const weld::TreeIter* pParent) override;
};
/** Node represents a struct */
class StructNode : public BasicValueNode
{
public:
StructNode(OUString const& rName, uno::Any const& rAny, OUString const& rInfo,
uno::Reference<uno::XComponentContext> const& xContext)
: BasicValueNode(rName, rAny, rInfo, xContext)
{
}
bool shouldShowExpander() override { return true; }
void fillChildren(std::unique_ptr<weld::TreeView>& pTree,
const weld::TreeIter* pParent) override;
};
/** Node represents a sequence */
class SequenceNode : public BasicValueNode
{
uno::Reference<reflection::XIdlArray> mxIdlArray;
public:
SequenceNode(OUString const& rName, uno::Any const& rAny, OUString const& rInfo,
uno::Reference<uno::XComponentContext> const& xContext)
: BasicValueNode(rName, rAny, rInfo, xContext)
{
auto xClass = convertTypeToIdlClass(maAny.getValueType(), mxContext);
mxIdlArray = xClass->getArray();
}
bool shouldShowExpander() override
{
// Show expander only if the sequence has elements
int nLength = mxIdlArray->getLen(maAny);
return nLength > 0;
}
void fillChildren(std::unique_ptr<weld::TreeView>& pTree,
const weld::TreeIter* pParent) override
{
int nLength = mxIdlArray->getLen(maAny);
for (int i = 0; i < nLength; i++)
{
uno::Any aArrayValue = mxIdlArray->get(maAny, i);
auto* pObjectInspectorNode
= createNodeObjectForAny(OUString::number(i), aArrayValue, u""_ustr);
if (pObjectInspectorNode)
lclAppendNodeToParent(pTree, pParent, pObjectInspectorNode);
}
}
std::vector<std::pair<sal_Int32, OUString>> getColumnValues() override
{
int nLength = mxIdlArray->getLen(maAny);
OUString aType
= getAnyType(maAny).replaceAll(u"[]", u"") + u"[" + OUString::number(nLength) + u"]";
OUString aValue
= SfxResId(STR_PROPERTY_VALUE_SEQUENCE).replaceFirst("%1", OUString::number(nLength));
return {
{ 1, aValue },
{ 2, aType },
};
}
};
void GenericPropertiesNode::fillChildren(std::unique_ptr<weld::TreeView>& pTree,
const weld::TreeIter* pParent)
{
if (!maAny.hasValue())
return;
try
{
const auto xNameAccess = uno::Reference<container::XNameAccess>(maAny, uno::UNO_QUERY);
if (xNameAccess.is())
{
const uno::Sequence<OUString> aNames = xNameAccess->getElementNames();
for (OUString const& rName : aNames)
{
uno::Any aAny = xNameAccess->getByName(rName);
auto* pObjectInspectorNode = createNodeObjectForAny(
u"@" + rName, aAny, SfxResId(STR_PROPERTY_TYPE_IS_NAMED_CONTAINER));
lclAppendNodeToParent(pTree, pParent, pObjectInspectorNode);
}
}
}
catch (...)
{
}
try
{
const auto xIndexAccess = uno::Reference<container::XIndexAccess>(maAny, uno::UNO_QUERY);
if (xIndexAccess.is())
{
for (sal_Int32 nIndex = 0; nIndex < xIndexAccess->getCount(); ++nIndex)
{
uno::Any aAny = xIndexAccess->getByIndex(nIndex);
auto* pObjectInspectorNode
= createNodeObjectForAny(u"@" + OUString::number(nIndex), aAny,
SfxResId(STR_PROPERTY_TYPE_IS_INDEX_CONTAINER));
lclAppendNodeToParent(pTree, pParent, pObjectInspectorNode);
}
}
}
catch (...)
{
}
try
{
const auto xEnumAccess
= uno::Reference<container::XEnumerationAccess>(maAny, uno::UNO_QUERY);
if (xEnumAccess.is())
{
uno::Reference<container::XEnumeration> xEnumeration = xEnumAccess->createEnumeration();
if (xEnumeration.is())
{
for (sal_Int32 nIndex = 0; xEnumeration->hasMoreElements(); nIndex++)
{
uno::Any aAny = xEnumeration->nextElement();
auto* pObjectInspectorNode
= createNodeObjectForAny(u"@" + OUString::number(nIndex), aAny,
SfxResId(STR_PROPERTY_TYPE_IS_ENUMERATION));
lclAppendNodeToParent(pTree, pParent, pObjectInspectorNode);
}
}
}
}
catch (...)
{
}
auto xInvocationFactory = css::script::Invocation::create(mxContext);
uno::Sequence<uno::Any> aParameters = { maAny };
auto xInvocationInterface = xInvocationFactory->createInstanceWithArguments(aParameters);
if (!xInvocationInterface.is())
return;
uno::Reference<script::XInvocation2> xInvocation(xInvocationInterface, uno::UNO_QUERY);
if (!xInvocation.is())
return;
auto const xInvocationAccess = xInvocation->getIntrospection();
if (!xInvocationAccess.is())
return;
uno::Sequence<script::InvocationInfo> aInvocationInfoSequence;
try
{
aInvocationInfoSequence = xInvocation->getInfo();
}
catch (...)
{
}
for (auto const& aInvocationInfo : aInvocationInfoSequence)
{
if (aInvocationInfo.eMemberType == script::MemberType_PROPERTY)
{
uno::Any aCurrentAny;
auto const& aPropertyName = aInvocationInfo.aName;
bool bIsAttribute = false;
bool bIsGetSetMethod = false;
bool bMethodGet = false;
bool bMethodSet = false;
bool bMethodIs = false;
try
{
aCurrentAny = xInvocation->getValue(aPropertyName);
bIsAttribute = xInvocationAccess->hasProperty(aPropertyName,
beans::PropertyConcept::ATTRIBUTES);
bIsGetSetMethod = xInvocationAccess->hasProperty(aPropertyName,
beans::PropertyConcept::METHODS);
if (bIsGetSetMethod)
{
bMethodGet = xInvocationAccess->hasMethod(u"get" + aPropertyName,
beans::MethodConcept::PROPERTY);
bMethodSet = xInvocationAccess->hasMethod(u"set" + aPropertyName,
beans::MethodConcept::PROPERTY);
bMethodIs = xInvocationAccess->hasMethod(u"is" + aPropertyName,
beans::MethodConcept::PROPERTY);
}
}
catch (...)
{
}
std::vector<OUString> aInfoCollection;
if (bIsAttribute)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_IS_ATTRIBUTE));
if (bIsGetSetMethod)
{
bool bHasGet = false;
OUString aString;
if (bMethodGet || bMethodIs)
{
aString += SfxResId(STR_PROPERTY_ATTRIBUTE_GET);
bHasGet = true;
}
if (bMethodSet)
{
if (bHasGet)
aString += u"+";
aString += SfxResId(STR_PROPERTY_ATTRIBUTE_SET);
}
aInfoCollection.push_back(aString);
if (bMethodSet && !bHasGet)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_WRITEONLY));
}
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::MAYBEVOID)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_MAYBEVOID));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::READONLY)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_READONLY));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::REMOVABLE)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_REMOVABLE));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::BOUND)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_BOUND));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::CONSTRAINED)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_CONSTRAINED));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::TRANSIENT)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_TRANSIENT));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::MAYBEAMBIGUOUS)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_MAYBEAMBIGUOUS));
if (aInvocationInfo.PropertyAttribute & beans::PropertyAttribute::MAYBEDEFAULT)
aInfoCollection.push_back(SfxResId(STR_PROPERTY_ATTRIBUTE_MAYBEDEFAULT));
bool bSet = false;
OUString aInfoString;
for (auto const& rString : aInfoCollection)
{
if (bSet)
aInfoString += ", ";
else
bSet = true;
aInfoString += rString;
}
auto* pObjectInspectorNode
= createNodeObjectForAny(aPropertyName, aCurrentAny, aInfoString);
if (pObjectInspectorNode)
lclAppendNodeToParent(pTree, pParent, pObjectInspectorNode);
}
}
}
void StructNode::fillChildren(std::unique_ptr<weld::TreeView>& pTree, const weld::TreeIter* pParent)
{
auto xReflection = reflection::theCoreReflection::get(mxContext);
uno::Reference<reflection::XIdlClass> xClass = xReflection->forName(maAny.getValueTypeName());
const auto xFields = xClass->getFields();
for (auto const& xField : xFields)
{
OUString aFieldName = xField->getName();
uno::Any aFieldValue = xField->get(maAny);
auto* pObjectInspectorNode = createNodeObjectForAny(aFieldName, aFieldValue, u""_ustr);
if (pObjectInspectorNode)
{
lclAppendNodeToParent(pTree, pParent, pObjectInspectorNode);
}
}
}
ObjectInspectorNodeInterface* BasicValueNode::createNodeObjectForAny(OUString const& rName,
const uno::Any& rAny,
OUString const& rInfo)
{
switch (rAny.getValueTypeClass())
{
case uno::TypeClass_INTERFACE:
return new GenericPropertiesNode(rName, rAny, rInfo, mxContext);
case uno::TypeClass_SEQUENCE:
return new SequenceNode(rName, rAny, rInfo, mxContext);
case uno::TypeClass_STRUCT:
return new StructNode(rName, rAny, rInfo, mxContext);
default:
break;
}
return new BasicValueNode(rName, rAny, rInfo, mxContext);
}
} // end anonymous namespace
// Object inspector tree view helper functions
namespace
{
ObjectInspectorNodeInterface* getSelectedNode(weld::TreeView const& rTreeView)
{
OUString sID = rTreeView.get_selected_id();
if (sID.isEmpty())
return nullptr;
if (auto* pNode = weld::fromId<ObjectInspectorNodeInterface*>(sID))
return pNode;
return nullptr;
}
uno::Reference<uno::XInterface> getSelectedXInterface(weld::TreeView const& rTreeView)
{
uno::Reference<uno::XInterface> xInterface;
if (auto* pNode = getSelectedNode(rTreeView))
{
if (auto* pBasicValueNode = dynamic_cast<BasicValueNode*>(pNode))
{
uno::Any aAny = pBasicValueNode->getAny();
xInterface.set(aAny, uno::UNO_QUERY);
}
}
return xInterface;
}
} // end anonymous namespace
ObjectInspectorTreeHandler::ObjectInspectorTreeHandler(
std::unique_ptr<ObjectInspectorWidgets>& pObjectInspectorWidgets)
: mpObjectInspectorWidgets(pObjectInspectorWidgets)
, mxContext(comphelper::getProcessComponentContext())
, mxSorter(mxContext, Application::GetSettings().GetLanguageTag().getLocale())
{
mpObjectInspectorWidgets->mpInterfacesTreeView->connect_expanding(
LINK(this, ObjectInspectorTreeHandler, ExpandingHandlerInterfaces));
mpObjectInspectorWidgets->mpServicesTreeView->connect_expanding(
LINK(this, ObjectInspectorTreeHandler, ExpandingHandlerServices));
mpObjectInspectorWidgets->mpPropertiesTreeView->connect_expanding(
LINK(this, ObjectInspectorTreeHandler, ExpandingHandlerProperties));
mpObjectInspectorWidgets->mpMethodsTreeView->connect_expanding(
LINK(this, ObjectInspectorTreeHandler, ExpandingHandlerMethods));
mpObjectInspectorWidgets->mpPropertiesTreeView->connect_popup_menu(
LINK(this, ObjectInspectorTreeHandler, PopupMenuHandler));
mpObjectInspectorWidgets->mpInterfacesTreeView->connect_changed(
LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
mpObjectInspectorWidgets->mpServicesTreeView->connect_changed(
LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
mpObjectInspectorWidgets->mpPropertiesTreeView->connect_changed(
LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
mpObjectInspectorWidgets->mpMethodsTreeView->connect_changed(
LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
mpObjectInspectorWidgets->mpInterfacesTreeView->make_sorted();
mpObjectInspectorWidgets->mpServicesTreeView->make_sorted();
mpObjectInspectorWidgets->mpPropertiesTreeView->make_sorted();
mpObjectInspectorWidgets->mpMethodsTreeView->make_sorted();
setSortFunction(mpObjectInspectorWidgets->mpInterfacesTreeView);
setSortFunction(mpObjectInspectorWidgets->mpServicesTreeView);
setSortFunction(mpObjectInspectorWidgets->mpPropertiesTreeView);
setSortFunction(mpObjectInspectorWidgets->mpMethodsTreeView);
mpObjectInspectorWidgets->mpInterfacesTreeView->connect_column_clicked(
LINK(this, ObjectInspectorTreeHandler, HeaderBarClick));
mpObjectInspectorWidgets->mpServicesTreeView->connect_column_clicked(
LINK(this, ObjectInspectorTreeHandler, HeaderBarClick));
mpObjectInspectorWidgets->mpPropertiesTreeView->connect_column_clicked(
LINK(this, ObjectInspectorTreeHandler, HeaderBarClick));
mpObjectInspectorWidgets->mpMethodsTreeView->connect_column_clicked(
LINK(this, ObjectInspectorTreeHandler, HeaderBarClick));
mpObjectInspectorWidgets->mpToolbar->connect_clicked(
LINK(this, ObjectInspectorTreeHandler, ToolbarButtonClicked));
mpObjectInspectorWidgets->mpToolbar->set_item_sensitive(u"inspect"_ustr, false);
mpObjectInspectorWidgets->mpToolbar->set_item_sensitive(u"back"_ustr, false);
mpObjectInspectorWidgets->mpNotebook->connect_leave_page(
LINK(this, ObjectInspectorTreeHandler, NotebookLeavePage));
mpObjectInspectorWidgets->mpNotebook->connect_enter_page(
LINK(this, ObjectInspectorTreeHandler, NotebookEnterPage));
auto nPropertiesDigitWidth
= mpObjectInspectorWidgets->mpPropertiesTreeView->get_approximate_digit_width();
std::vector<int> aPropertiesWidths(4, nPropertiesDigitWidth * 30);
mpObjectInspectorWidgets->mpPropertiesTreeView->set_column_fixed_widths(aPropertiesWidths);
auto nMethodsDigitWidth
= mpObjectInspectorWidgets->mpMethodsTreeView->get_approximate_digit_width();
std::vector<int> aMethodsWidths{ static_cast<int>(nMethodsDigitWidth * 30),
static_cast<int>(nMethodsDigitWidth * 15),
static_cast<int>(nMethodsDigitWidth * 30),
static_cast<int>(nMethodsDigitWidth * 50) };
mpObjectInspectorWidgets->mpMethodsTreeView->set_column_fixed_widths(aMethodsWidths);
mpObjectInspectorWidgets->mpPaned->set_position(160);
}
void ObjectInspectorTreeHandler::setSortFunction(std::unique_ptr<weld::TreeView>& pTreeView)
{
pTreeView->set_sort_func(
[this, &pTreeView](const weld::TreeIter& rLeft, const weld::TreeIter& rRight) {
return compare(pTreeView, rLeft, rRight);
});
}
sal_Int32 ObjectInspectorTreeHandler::compare(std::unique_ptr<weld::TreeView>& pTreeView,
const weld::TreeIter& rLeft,
const weld::TreeIter& rRight)
{
int nSortColumn = pTreeView->get_sort_column();
OUString sLeft = pTreeView->get_text(rLeft, nSortColumn);
OUString sRight = pTreeView->get_text(rRight, nSortColumn);
sal_Int32 nCompare = mxSorter.compare(sLeft, sRight);
return nCompare;
}
void ObjectInspectorTreeHandler::handleExpanding(std::unique_ptr<weld::TreeView>& pTreeView,
weld::TreeIter const& rParent)
{
OUString sID = pTreeView->get_id(rParent);
if (sID.isEmpty())
return;
clearObjectInspectorChildren(pTreeView, rParent);
auto* pNode = weld::fromId<ObjectInspectorNodeInterface*>(sID);
pNode->fillChildren(pTreeView, &rParent);
}
IMPL_LINK(ObjectInspectorTreeHandler, ExpandingHandlerInterfaces, weld::TreeIter const&, rParent,
bool)
{
handleExpanding(mpObjectInspectorWidgets->mpInterfacesTreeView, rParent);
return true;
}
IMPL_LINK(ObjectInspectorTreeHandler, ExpandingHandlerServices, weld::TreeIter const&, rParent,
bool)
{
handleExpanding(mpObjectInspectorWidgets->mpServicesTreeView, rParent);
return true;
}
IMPL_LINK(ObjectInspectorTreeHandler, ExpandingHandlerProperties, weld::TreeIter const&, rParent,
bool)
{
handleExpanding(mpObjectInspectorWidgets->mpPropertiesTreeView, rParent);
return true;
}
IMPL_LINK(ObjectInspectorTreeHandler, ExpandingHandlerMethods, weld::TreeIter const&, rParent, bool)
{
handleExpanding(mpObjectInspectorWidgets->mpMethodsTreeView, rParent);
return true;
}
IMPL_LINK(ObjectInspectorTreeHandler, SelectionChanged, weld::TreeView&, rTreeView, void)
{
bool bHaveNodeWithObject = false;
mpObjectInspectorWidgets->mpTextView->set_text(u""_ustr);
if (mpObjectInspectorWidgets->mpPropertiesTreeView.get() == &rTreeView)
{
auto* pNode = getSelectedNode(rTreeView);
if (auto* pBasicValueNode = dynamic_cast<BasicValueNode*>(pNode))
{
uno::Any aAny = pBasicValueNode->getAny();
uno::Reference<uno::XInterface> xInterface(aAny, uno::UNO_QUERY);
bHaveNodeWithObject = xInterface.is();
mpObjectInspectorWidgets->mpTextView->set_text(convertAnyToString(aAny, mxContext));
}
}
mpObjectInspectorWidgets->mpToolbar->set_item_sensitive(u"inspect"_ustr, bHaveNodeWithObject);
}
static void updateOrder(const std::unique_ptr<weld::TreeView>& pTreeView, sal_Int32 nColumn)
{
pTreeView->set_sort_column(nColumn);
bool bSortAtoZ = pTreeView->get_sort_order();
pTreeView->set_sort_order(!bSortAtoZ);
pTreeView->set_sort_indicator(!bSortAtoZ ? TRISTATE_TRUE : TRISTATE_FALSE, nColumn);
}
IMPL_LINK(ObjectInspectorTreeHandler, HeaderBarClick, int, nColumn, void)
{
auto rPageId = mpObjectInspectorWidgets->mpNotebook->get_current_page_ident();
if (rPageId == "object_inspector_interfaces_tab")
updateOrder(mpObjectInspectorWidgets->mpInterfacesTreeView, nColumn);
else if (rPageId == "object_inspector_services_tab")
updateOrder(mpObjectInspectorWidgets->mpServicesTreeView, nColumn);
else if (rPageId == "object_inspector_properties_tab")
updateOrder(mpObjectInspectorWidgets->mpPropertiesTreeView, nColumn);
else if (rPageId == "object_inspector_methods_tab")
updateOrder(mpObjectInspectorWidgets->mpMethodsTreeView, nColumn);
}
IMPL_LINK(ObjectInspectorTreeHandler, PopupMenuHandler, const CommandEvent&, rCommandEvent, bool)
{
if (rCommandEvent.GetCommand() != CommandEventId::ContextMenu)
return false;
auto xInterface = getSelectedXInterface(*mpObjectInspectorWidgets->mpPropertiesTreeView);
if (xInterface.is())
{
std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(
mpObjectInspectorWidgets->mpPropertiesTreeView.get(), u"sfx/ui/devtoolsmenu.ui"_ustr));
std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu(u"inspect_menu"_ustr));
OUString sCommand(
xMenu->popup_at_rect(mpObjectInspectorWidgets->mpPropertiesTreeView.get(),
tools::Rectangle(rCommandEvent.GetMousePosPixel(), Size(1, 1))));
if (sCommand == "inspect")
{
addToStack(uno::Any(xInterface));
inspectObject(xInterface);
}
}
return true;
}
IMPL_LINK(ObjectInspectorTreeHandler, ToolbarButtonClicked, const OUString&, rSelectionId, void)
{
if (rSelectionId == "inspect")
{
auto xInterface = getSelectedXInterface(*mpObjectInspectorWidgets->mpPropertiesTreeView);
if (xInterface.is())
{
addToStack(uno::Any(xInterface));
inspectObject(xInterface);
}
}
else if (rSelectionId == "back")
{
uno::Any aAny = popFromStack();
if (aAny.hasValue())
{
uno::Reference<uno::XInterface> xInterface(aAny, uno::UNO_QUERY);
inspectObject(xInterface);
}
}
else if (rSelectionId == "refresh")
{
auto rPageId = mpObjectInspectorWidgets->mpNotebook->get_current_page_ident();
NotebookEnterPage(rPageId);
}
}
IMPL_LINK(ObjectInspectorTreeHandler, NotebookEnterPage, const OUString&, rPageId, void)
{
uno::Any aAny = maInspectionStack.back();
if (!aAny.hasValue())
return;
uno::Reference<uno::XInterface> xInterface(aAny, uno::UNO_QUERY);
if (rPageId == "object_inspector_interfaces_tab")
{
mpObjectInspectorWidgets->mpInterfacesTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpInterfacesTreeView);
appendInterfaces(xInterface);
mpObjectInspectorWidgets->mpInterfacesTreeView->thaw();
}
else if (rPageId == "object_inspector_services_tab")
{
mpObjectInspectorWidgets->mpServicesTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpServicesTreeView);
appendServices(xInterface);
mpObjectInspectorWidgets->mpServicesTreeView->thaw();
}
else if (rPageId == "object_inspector_properties_tab")
{
mpObjectInspectorWidgets->mpPropertiesTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpPropertiesTreeView);
appendProperties(xInterface);
mpObjectInspectorWidgets->mpPropertiesTreeView->thaw();
}
else if (rPageId == "object_inspector_methods_tab")
{
mpObjectInspectorWidgets->mpMethodsTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpMethodsTreeView);
appendMethods(xInterface);
mpObjectInspectorWidgets->mpMethodsTreeView->thaw();
}
}
IMPL_LINK(ObjectInspectorTreeHandler, NotebookLeavePage, const OUString&, rPageId, bool)
{
if (rPageId == "object_inspector_interfaces_tab")
{
mpObjectInspectorWidgets->mpInterfacesTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpInterfacesTreeView);
mpObjectInspectorWidgets->mpInterfacesTreeView->thaw();
}
else if (rPageId == "object_inspector_services_tab")
{
mpObjectInspectorWidgets->mpServicesTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpServicesTreeView);
mpObjectInspectorWidgets->mpServicesTreeView->thaw();
}
else if (rPageId == "object_inspector_properties_tab")
{
mpObjectInspectorWidgets->mpPropertiesTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpPropertiesTreeView);
mpObjectInspectorWidgets->mpPropertiesTreeView->thaw();
}
else if (rPageId == "object_inspector_methods_tab")
{
mpObjectInspectorWidgets->mpMethodsTreeView->freeze();
clearAll(mpObjectInspectorWidgets->mpMethodsTreeView);
mpObjectInspectorWidgets->mpMethodsTreeView->thaw();
}
return true;
}
void ObjectInspectorTreeHandler::clearObjectInspectorChildren(
std::unique_ptr<weld::TreeView>& pTreeView, weld::TreeIter const& rParent)
{
bool bChild = false;
do
{
bChild = pTreeView->iter_has_child(rParent);
if (bChild)
{
std::unique_ptr<weld::TreeIter> pChild = pTreeView->make_iterator(&rParent);
bChild = pTreeView->iter_children(*pChild);
if (bChild)
{
clearObjectInspectorChildren(pTreeView, *pChild);
OUString sID = pTreeView->get_id(*pChild);
auto* pEntry = weld::fromId<ObjectInspectorNodeInterface*>(sID);
delete pEntry;
pTreeView->remove(*pChild);
}
}
} while (bChild);
}
/** Deletes all the node objects in a tree view */
void ObjectInspectorTreeHandler::clearAll(std::unique_ptr<weld::TreeView>& pTreeView)
{
// destroy all ObjectInspectorNodes from the tree
pTreeView->all_foreach([&pTreeView](weld::TreeIter& rEntry) {
OUString sID = pTreeView->get_id(rEntry);
auto* pEntry = weld::fromId<ObjectInspectorNodeInterface*>(sID);
delete pEntry;
return false;
});
pTreeView->clear();
}
/** Append interfaces to the "interfaces" tree view */
void ObjectInspectorTreeHandler::appendInterfaces(uno::Reference<uno::XInterface> const& xInterface)
{
if (!xInterface.is())
return;
uno::Reference<lang::XTypeProvider> xTypeProvider(xInterface, uno::UNO_QUERY);
if (xTypeProvider.is())
{
const auto xSequenceTypes = xTypeProvider->getTypes();
for (auto const& xType : xSequenceTypes)
{
auto xClass = convertTypeToIdlClass(xType, mxContext);
lclAppendNode(mpObjectInspectorWidgets->mpInterfacesTreeView, new ClassNode(xClass));
}
}
}
/** Append services to the "services" tree view */
void ObjectInspectorTreeHandler::appendServices(uno::Reference<uno::XInterface> const& xInterface)
{
if (!xInterface.is())
return;
auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xInterface, uno::UNO_QUERY);
const uno::Sequence<OUString> aServiceNames(xServiceInfo->getSupportedServiceNames());
for (auto const& aServiceName : aServiceNames)
{
lclAppendNode(mpObjectInspectorWidgets->mpServicesTreeView,
new SimpleStringNode(aServiceName));
}
}
/** Append properties to the "properties" tree view */
void ObjectInspectorTreeHandler::appendProperties(uno::Reference<uno::XInterface> const& xInterface)
{
if (!xInterface.is())
return;
GenericPropertiesNode aNode(u""_ustr, uno::Any(xInterface), u""_ustr, mxContext);
aNode.fillChildren(mpObjectInspectorWidgets->mpPropertiesTreeView, nullptr);
}
/** Append methods to the "methods" tree view */
void ObjectInspectorTreeHandler::appendMethods(uno::Reference<uno::XInterface> const& xInterface)
{
if (!xInterface.is())
return;
uno::Reference<beans::XIntrospection> xIntrospection = beans::theIntrospection::get(mxContext);
auto xIntrospectionAccess = xIntrospection->inspect(uno::Any(xInterface));
const auto xMethods = xIntrospectionAccess->getMethods(beans::MethodConcept::ALL);
for (auto const& xMethod : xMethods)
{
lclAppendNode(mpObjectInspectorWidgets->mpMethodsTreeView, new MethodNode(xMethod));
}
}
// Update the back button state depending if there are objects in the stack
void ObjectInspectorTreeHandler::updateBackButtonState()
{
mpObjectInspectorWidgets->mpToolbar->set_item_sensitive(u"back"_ustr,
maInspectionStack.size() > 1);
}
// Clears all the objects from the stack
void ObjectInspectorTreeHandler::clearStack()
{
maInspectionStack.clear();
updateBackButtonState();
}
// Adds an object to the stack
void ObjectInspectorTreeHandler::addToStack(css::uno::Any const& rAny)
{
maInspectionStack.push_back(rAny);
updateBackButtonState();
}
// Removes an object from the back of the stack and return it
css::uno::Any ObjectInspectorTreeHandler::popFromStack()
{
maInspectionStack.pop_back();
uno::Any aAny = maInspectionStack.back();
updateBackButtonState();
return aAny;
}
// Inspect the input object in the object inspector
void ObjectInspectorTreeHandler::inspectObject(uno::Reference<uno::XInterface> const& xInterface)
{
if (!xInterface.is())
return;
// Set implementation name
OUString aImplementationName = getInterfaceImplementationClass(xInterface);
mpObjectInspectorWidgets->mpClassNameLabel->set_label(aImplementationName);
sal_Int32 nStrLen = aImplementationName.getLength();
sal_Int32 nDigitWidth
= mpObjectInspectorWidgets->mpClassNameLabel->get_approximate_digit_width();
//get_about_digit_width() returns an approximate value. To always see the full class name (nStrLen+2)
mpObjectInspectorWidgets->mpClassNameLabel->set_size_request((nStrLen + 2) * nDigitWidth, -1);
// Fire entering the current opened page manually
auto rPageId = mpObjectInspectorWidgets->mpNotebook->get_current_page_ident();
NotebookEnterPage(rPageId);
}
// Inspect the input object in the object inspector.
// Make the input object the root of the stack (clear all other
// objects from the stack).
void ObjectInspectorTreeHandler::introspect(uno::Reference<uno::XInterface> const& xInterface)
{
clearStack();
addToStack(uno::Any(xInterface));
inspectObject(xInterface);
}
void ObjectInspectorTreeHandler::dispose()
{
// We need to clear all the nodes
clearAll(mpObjectInspectorWidgets->mpInterfacesTreeView);
clearAll(mpObjectInspectorWidgets->mpServicesTreeView);
clearAll(mpObjectInspectorWidgets->mpPropertiesTreeView);
clearAll(mpObjectInspectorWidgets->mpMethodsTreeView);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNodeToParent' is required to be utilized.
↑ V530 The return value of function 'lclAppendNode' is required to be utilized.
↑ V530 The return value of function 'lclAppendNode' is required to be utilized.
↑ V530 The return value of function 'lclAppendNode' is required to be utilized.