summaryrefslogtreecommitdiffstats
path: root/cui/source/customize/cfgutil.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /cui/source/customize/cfgutil.cxx
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--cui/source/customize/cfgutil.cxx1195
1 files changed, 1195 insertions, 0 deletions
diff --git a/cui/source/customize/cfgutil.cxx b/cui/source/customize/cfgutil.cxx
new file mode 100644
index 000000000..d8ec5d79c
--- /dev/null
+++ b/cui/source/customize/cfgutil.cxx
@@ -0,0 +1,1195 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cfgutil.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <com/sun/star/document/XScriptInvocationContext.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/theUICommandDescription.hpp>
+#include <com/sun/star/frame/XDispatchInformationProvider.hpp>
+#include <com/sun/star/script/browse/XBrowseNode.hpp>
+#include <com/sun/star/script/browse/BrowseNodeTypes.hpp>
+#include <com/sun/star/script/browse/theBrowseNodeFactory.hpp>
+#include <com/sun/star/script/browse/BrowseNodeFactoryViewTypes.hpp>
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/ui/theUICategoryDescription.hpp>
+
+#include <basic/basmgr.hxx>
+#include <tools/urlobj.hxx>
+#include <strings.hrc>
+#include <bitmaps.hlst>
+#include <sfx2/minfitem.hxx>
+#include <comphelper/SetFlagContextHelper.hxx>
+#include <comphelper/documentinfo.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <svtools/imagemgr.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <dialmgr.hxx>
+#include <tools/diagnose_ex.h>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/help.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::script;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::document;
+
+SfxStylesInfo_Impl::SfxStylesInfo_Impl()
+{}
+
+void SfxStylesInfo_Impl::init(const OUString& rModuleName, const css::uno::Reference< css::frame::XModel >& xModel)
+{
+ m_aModuleName = rModuleName;
+ m_xDoc = xModel;
+}
+
+static const char CMDURL_STYLEPROT_ONLY[] = ".uno:StyleApply?";
+static const char CMDURL_SPART_ONLY [] = "Style:string=";
+static const char CMDURL_FPART_ONLY [] = "FamilyName:string=";
+
+static const char STYLEPROP_UINAME[] = "DisplayName";
+
+OUString SfxStylesInfo_Impl::generateCommand(const OUString& sFamily, const OUString& sStyle)
+{
+ return ".uno:StyleApply?Style:string="
+ + sStyle
+ + "&FamilyName:string="
+ + sFamily;
+}
+
+bool SfxStylesInfo_Impl::parseStyleCommand(SfxStyleInfo_Impl& aStyle)
+{
+ static const sal_Int32 LEN_STYLEPROT = strlen(CMDURL_STYLEPROT_ONLY);
+ static const sal_Int32 LEN_SPART = strlen(CMDURL_SPART_ONLY);
+ static const sal_Int32 LEN_FPART = strlen(CMDURL_FPART_ONLY);
+
+ if (!aStyle.sCommand.startsWith(CMDURL_STYLEPROT_ONLY))
+ return false;
+
+ aStyle.sFamily.clear();
+ aStyle.sStyle.clear();
+
+ sal_Int32 nCmdLen = aStyle.sCommand.getLength();
+ OUString sCmdArgs = aStyle.sCommand.copy(LEN_STYLEPROT, nCmdLen-LEN_STYLEPROT);
+ sal_Int32 i = sCmdArgs.indexOf('&');
+ if (i<0)
+ return false;
+
+ OUString sArg = sCmdArgs.copy(0, i);
+ if (sArg.startsWith(CMDURL_SPART_ONLY))
+ aStyle.sStyle = sArg.copy(LEN_SPART);
+ else if (sArg.startsWith(CMDURL_FPART_ONLY))
+ aStyle.sFamily = sArg.copy(LEN_FPART);
+
+ sArg = sCmdArgs.copy(i+1, sCmdArgs.getLength()-i-1);
+ if (sArg.startsWith(CMDURL_SPART_ONLY))
+ aStyle.sStyle = sArg.copy(LEN_SPART);
+ else if (sArg.startsWith(CMDURL_FPART_ONLY))
+ aStyle.sFamily = sArg.copy(LEN_FPART);
+
+ return !(aStyle.sFamily.isEmpty() || aStyle.sStyle.isEmpty());
+}
+
+void SfxStylesInfo_Impl::getLabel4Style(SfxStyleInfo_Impl& aStyle)
+{
+ try
+ {
+ css::uno::Reference< css::style::XStyleFamiliesSupplier > xModel(m_xDoc, css::uno::UNO_QUERY);
+
+ css::uno::Reference< css::container::XNameAccess > xFamilies;
+ if (xModel.is())
+ xFamilies = xModel->getStyleFamilies();
+
+ css::uno::Reference< css::container::XNameAccess > xStyleSet;
+ if (xFamilies.is())
+ xFamilies->getByName(aStyle.sFamily) >>= xStyleSet;
+
+ css::uno::Reference< css::beans::XPropertySet > xStyle;
+ if (xStyleSet.is())
+ xStyleSet->getByName(aStyle.sStyle) >>= xStyle;
+
+ aStyle.sLabel.clear();
+ if (xStyle.is())
+ xStyle->getPropertyValue(STYLEPROP_UINAME) >>= aStyle.sLabel;
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { aStyle.sLabel.clear(); }
+
+ if (aStyle.sLabel.isEmpty())
+ {
+ aStyle.sLabel = aStyle.sCommand;
+ }
+}
+
+std::vector< SfxStyleInfo_Impl > SfxStylesInfo_Impl::getStyleFamilies() const
+{
+ // It's an optional interface!
+ css::uno::Reference< css::style::XStyleFamiliesSupplier > xModel(m_xDoc, css::uno::UNO_QUERY);
+ if (!xModel.is())
+ return std::vector< SfxStyleInfo_Impl >();
+
+ css::uno::Reference< css::container::XNameAccess > xCont = xModel->getStyleFamilies();
+ const css::uno::Sequence< OUString > lFamilyNames = xCont->getElementNames();
+ std::vector< SfxStyleInfo_Impl > lFamilies;
+ for (const auto& aFamily : lFamilyNames)
+ {
+ if ((aFamily == "CellStyles" && m_aModuleName != "com.sun.star.sheet.SpreadsheetDocument") ||
+ aFamily == "cell" || aFamily == "table" || aFamily == "Default")
+ continue;
+
+ SfxStyleInfo_Impl aFamilyInfo;
+ aFamilyInfo.sFamily = aFamily;
+
+ try
+ {
+ css::uno::Reference< css::beans::XPropertySet > xFamilyInfo;
+ xCont->getByName(aFamilyInfo.sFamily) >>= xFamilyInfo;
+ if (!xFamilyInfo.is())
+ {
+ // TODO_AS currently there is no support for an UIName property .. use internal family name instead
+ aFamilyInfo.sLabel = aFamilyInfo.sFamily;
+ }
+ else
+ xFamilyInfo->getPropertyValue(STYLEPROP_UINAME) >>= aFamilyInfo.sLabel;
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { return std::vector< SfxStyleInfo_Impl >(); }
+
+ lFamilies.push_back(aFamilyInfo);
+ }
+
+ return lFamilies;
+}
+
+std::vector< SfxStyleInfo_Impl > SfxStylesInfo_Impl::getStyles(const OUString& sFamily)
+{
+ css::uno::Sequence< OUString > lStyleNames;
+ css::uno::Reference< css::style::XStyleFamiliesSupplier > xModel(m_xDoc, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::container::XNameAccess > xFamilies = xModel->getStyleFamilies();
+ css::uno::Reference< css::container::XNameAccess > xStyleSet;
+ try
+ {
+ xFamilies->getByName(sFamily) >>= xStyleSet;
+ lStyleNames = xStyleSet->getElementNames();
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { return std::vector< SfxStyleInfo_Impl >(); }
+
+ std::vector< SfxStyleInfo_Impl > lStyles;
+ sal_Int32 c = lStyleNames.getLength();
+ sal_Int32 i = 0;
+ for (i=0; i<c; ++i)
+ {
+ SfxStyleInfo_Impl aStyleInfo;
+ aStyleInfo.sFamily = sFamily;
+ aStyleInfo.sStyle = lStyleNames[i];
+ aStyleInfo.sCommand = SfxStylesInfo_Impl::generateCommand(aStyleInfo.sFamily, aStyleInfo.sStyle);
+
+ try
+ {
+ css::uno::Reference< css::beans::XPropertySet > xStyle;
+ xStyleSet->getByName(aStyleInfo.sStyle) >>= xStyle;
+ if (!xStyle.is())
+ continue;
+ xStyle->getPropertyValue("DisplayName") >>= aStyleInfo.sLabel;
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ { continue; }
+
+ lStyles.push_back(aStyleInfo);
+ }
+ return lStyles;
+}
+
+OUString CuiConfigFunctionListBox::GetHelpText( bool bConsiderParent )
+{
+ SfxGroupInfo_Impl *pData = reinterpret_cast<SfxGroupInfo_Impl*>(get_selected_id().toInt64());
+ if (pData)
+ {
+ if ( pData->nKind == SfxCfgKind::FUNCTION_SLOT )
+ {
+ if (bConsiderParent)
+ return Application::GetHelp()->GetHelpText(pData->sCommand, m_xTreeView.get());
+ else
+ return Application::GetHelp()->GetHelpText(pData->sCommand, static_cast<weld::Widget*>(nullptr));
+ }
+ else if ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT )
+ {
+ return pData->sHelpText;
+ }
+ }
+ return OUString();
+}
+
+OUString CuiConfigFunctionListBox::GetCurCommand() const
+{
+ SfxGroupInfo_Impl *pData = reinterpret_cast<SfxGroupInfo_Impl*>(get_selected_id().toInt64());
+ if (!pData)
+ return OUString();
+ return pData->sCommand;
+}
+
+OUString CuiConfigFunctionListBox::GetCurLabel() const
+{
+ SfxGroupInfo_Impl *pData = reinterpret_cast<SfxGroupInfo_Impl*>(get_selected_id().toInt64());
+ if (!pData)
+ return OUString();
+ if (!pData->sLabel.isEmpty())
+ return pData->sLabel;
+ return pData->sCommand;
+}
+
+CuiConfigFunctionListBox::CuiConfigFunctionListBox(std::unique_ptr<weld::TreeView> xTreeView)
+ : m_xTreeView(std::move(xTreeView))
+ , m_xScratchIter(m_xTreeView->make_iterator())
+{
+ m_xTreeView->make_sorted();
+ m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 35, m_xTreeView->get_height_rows(9));
+ m_xTreeView->connect_query_tooltip(LINK(this, CuiConfigFunctionListBox, QueryTooltip));
+}
+
+CuiConfigFunctionListBox::~CuiConfigFunctionListBox()
+{
+ ClearAll();
+}
+
+IMPL_LINK(CuiConfigFunctionListBox, QueryTooltip, const weld::TreeIter&, rIter, OUString)
+{
+ SfxGroupInfo_Impl *pData = reinterpret_cast<SfxGroupInfo_Impl*>(m_xTreeView->get_id(rIter).toInt64());
+ if (!pData)
+ return OUString();
+ OUString aLabel = CuiResId(RID_SVXSTR_COMMANDLABEL) + ": ";
+ OUString aName = CuiResId(RID_SVXSTR_COMMANDNAME) + ": ";
+ OUString aTip = CuiResId(RID_SVXSTR_COMMANDTIP) + ": ";
+ return aLabel + pData->sLabel + "\n" + aName + pData->sCommand+ "\n" + aTip + pData->sTooltip;
+}
+
+void CuiConfigFunctionListBox::ClearAll()
+/* Description
+ Deletes all entries in the FunctionListBox, all UserData and all
+ possibly existing MacroInfo.
+*/
+{
+ sal_uInt16 nCount = aArr.size();
+ for ( sal_uInt16 i=0; i<nCount; ++i )
+ {
+ SfxGroupInfo_Impl *pData = aArr[i].get();
+
+ if ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT )
+ {
+ OUString* pScriptURI = static_cast<OUString*>(pData->pObject);
+ delete pScriptURI;
+ }
+
+ if ( pData->nKind == SfxCfgKind::GROUP_SCRIPTCONTAINER )
+ {
+ XInterface* xi = static_cast<XInterface *>(pData->pObject);
+ if (xi != nullptr)
+ {
+ xi->release();
+ }
+ }
+ }
+
+ aArr.clear();
+ m_xTreeView->clear();
+}
+
+OUString CuiConfigFunctionListBox::GetSelectedScriptURI() const
+{
+ SfxGroupInfo_Impl *pData = reinterpret_cast<SfxGroupInfo_Impl*>(get_selected_id().toInt64());
+ if (pData && pData->nKind == SfxCfgKind::FUNCTION_SCRIPT)
+ return *static_cast<OUString*>(pData->pObject);
+ return OUString();
+}
+
+struct SvxConfigGroupBoxResource_Impl
+{
+ OUString m_sMyMacros;
+ OUString m_sProdMacros;
+ OUString m_sMacros;
+ OUString m_sDlgMacros;
+ OUString m_aStrGroupStyles;
+
+ SvxConfigGroupBoxResource_Impl();
+};
+
+SvxConfigGroupBoxResource_Impl::SvxConfigGroupBoxResource_Impl() :
+ m_sMyMacros(CuiResId(RID_SVXSTR_MYMACROS)),
+ m_sProdMacros(CuiResId(RID_SVXSTR_PRODMACROS)),
+ m_sMacros(CuiResId(RID_SVXSTR_BASICMACROS)),
+ m_sDlgMacros(CuiResId(RID_SVXSTR_PRODMACROS)),
+ m_aStrGroupStyles(CuiResId(RID_SVXSTR_GROUP_STYLES))
+{
+}
+
+void CuiConfigGroupListBox::SetStylesInfo(SfxStylesInfo_Impl* pStyles)
+{
+ m_pStylesInfo = pStyles;
+}
+
+namespace
+{
+
+ /** examines a component whether it supports XEmbeddedScripts, or provides access to such a
+ component by implementing XScriptInvocationContext.
+ @return
+ the model which supports the embedded scripts, or <NULL/> if it cannot find such a
+ model
+ */
+ Reference< XModel > lcl_getDocumentWithScripts_throw( const Reference< XInterface >& _rxComponent )
+ {
+ Reference< XEmbeddedScripts > xScripts( _rxComponent, UNO_QUERY );
+ if ( !xScripts.is() )
+ {
+ Reference< XScriptInvocationContext > xContext( _rxComponent, UNO_QUERY );
+ if ( xContext.is() )
+ xScripts = xContext->getScriptContainer();
+ }
+
+ return Reference< XModel >( xScripts, UNO_QUERY );
+ }
+
+
+ Reference< XModel > lcl_getScriptableDocument_nothrow( const Reference< XFrame >& _rxFrame )
+ {
+ Reference< XModel > xDocument;
+
+ // examine our associated frame
+ try
+ {
+ OSL_ENSURE( _rxFrame.is(), "lcl_getScriptableDocument_nothrow: you need to pass a frame to this dialog/tab page!" );
+ if ( _rxFrame.is() )
+ {
+ // first try the model in the frame
+ Reference< XController > xController( _rxFrame->getController(), UNO_SET_THROW );
+ xDocument = lcl_getDocumentWithScripts_throw( xController->getModel() );
+
+ if ( !xDocument.is() )
+ {
+ // if there is no suitable document in the frame, try the controller
+ xDocument = lcl_getDocumentWithScripts_throw( _rxFrame->getController() );
+ }
+ }
+ }
+ catch( const Exception& )
+ {
+ }
+
+ return xDocument;
+ }
+}
+
+CuiConfigGroupListBox::CuiConfigGroupListBox(std::unique_ptr<weld::TreeView> xTreeView)
+ : xImp(new SvxConfigGroupBoxResource_Impl())
+ , m_pFunctionListBox(nullptr)
+ , m_pStylesInfo(nullptr)
+ , m_xTreeView(std::move(xTreeView))
+{
+ m_xTreeView->connect_expanding(LINK(this, CuiConfigGroupListBox, ExpandingHdl));
+ m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 35, m_xTreeView->get_height_rows(9));
+}
+
+CuiConfigGroupListBox::~CuiConfigGroupListBox()
+{
+ ClearAll();
+}
+
+void CuiConfigGroupListBox::ClearAll()
+{
+ sal_uInt16 nCount = aArr.size();
+ for ( sal_uInt16 i=0; i<nCount; ++i )
+ {
+ SfxGroupInfo_Impl *pData = aArr[i].get();
+ if (pData->nKind == SfxCfgKind::GROUP_SCRIPTCONTAINER)
+ {
+ XInterface* xi = static_cast<XInterface *>(pData->pObject);
+ if (xi != nullptr)
+ {
+ xi->release();
+ }
+ }
+ }
+
+ aArr.clear();
+ m_xTreeView->clear();
+}
+
+void CuiConfigGroupListBox::InitModule()
+{
+ try
+ {
+ css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider(m_xFrame, css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence< sal_Int16 > lGroups = xProvider->getSupportedCommandGroups();
+ sal_Int32 c1 = lGroups.getLength();
+ sal_Int32 i1 = 0;
+
+ if ( c1 )
+ {
+ // Add All Commands category
+ aArr.push_back(std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_ALLFUNCTIONS, 0));
+ m_xTreeView->append(OUString::number(reinterpret_cast<sal_Int64>(aArr.back().get())),
+ CuiResId(RID_SVXSTR_ALLFUNCTIONS));
+ }
+
+ for (i1=0; i1<c1; ++i1)
+ {
+ sal_Int16& rGroupID = lGroups[i1];
+ OUString sGroupID = OUString::number(rGroupID);
+ OUString sGroupName ;
+
+ try
+ {
+ m_xModuleCategoryInfo->getByName(sGroupID) >>= sGroupName;
+ if (sGroupName.isEmpty())
+ continue;
+ }
+ catch(const css::container::NoSuchElementException&)
+ { continue; }
+
+ aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_FUNCTION, rGroupID ) );
+ m_xTreeView->append(OUString::number(reinterpret_cast<sal_Int64>(aArr.back().get())),
+ sGroupName);
+ }
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ {}
+}
+
+void CuiConfigGroupListBox::FillScriptList(const css::uno::Reference< css::script::browse::XBrowseNode >& xRootNode,
+ const weld::TreeIter* pParentEntry, bool bCheapChildrenOnDemand)
+{
+ try {
+ if ( xRootNode->hasChildNodes() )
+ {
+ // tdf#120362: Don't ask to enable disabled Java when filling script list
+ css::uno::ContextLayer layer(comphelper::NoEnableJavaInteractionContext());
+
+ const Sequence< Reference< browse::XBrowseNode > > children =
+ xRootNode->getChildNodes();
+ bool bIsRootNode = false;
+
+ OUString user("user");
+ OUString share("share");
+ if ( xRootNode->getName() == "Root" )
+ {
+ bIsRootNode = true;
+ }
+
+ //To mimic current starbasic behaviour we
+ //need to make sure that only the current document
+ //is displayed in the config tree. Tests below
+ //set the bDisplay flag to FALSE if the current
+ //node is a first level child of the Root and is NOT
+ //either the current document, user or share
+ OUString currentDocTitle;
+ Reference< XModel > xDocument( lcl_getScriptableDocument_nothrow( m_xFrame ) );
+ if ( xDocument.is() )
+ {
+ currentDocTitle = ::comphelper::DocumentInfo::getDocumentTitle( xDocument );
+ }
+
+ for ( Reference< browse::XBrowseNode > const & theChild : children )
+ {
+ bool bDisplay = true;
+ OUString uiName = theChild->getName();
+ if ( bIsRootNode )
+ {
+ if ( ! (uiName == user || uiName == share ||
+ uiName == currentDocTitle ) )
+ {
+ bDisplay=false;
+ }
+ else
+ {
+ if ( uiName == user )
+ {
+ uiName = xImp->m_sMyMacros;
+ }
+ else if ( uiName == share )
+ {
+ uiName = xImp->m_sProdMacros;
+ }
+ }
+ }
+ if (theChild->getType() != browse::BrowseNodeTypes::SCRIPT && bDisplay )
+ {
+// We call acquire on the XBrowseNode so that it does not
+// get autodestructed and become invalid when accessed later.
+ theChild->acquire();
+
+ bool bChildOnDemand = false;
+ if ( !bCheapChildrenOnDemand && theChild->hasChildNodes() )
+ {
+ const Sequence< Reference< browse::XBrowseNode > > grandchildren =
+ theChild->getChildNodes();
+
+ for ( const auto& rxNode : grandchildren )
+ {
+ if ( rxNode->getType() == browse::BrowseNodeTypes::CONTAINER )
+ {
+ bChildOnDemand = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* i30923 - Would be nice if there was a better
+ * way to determine if a basic lib had children
+ * without having to ask for them (which forces
+ * the library to be loaded */
+ bChildOnDemand = true;
+ }
+
+ OUString aImage = GetImage(theChild, m_xContext, bIsRootNode);
+
+ aArr.push_back( std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_SCRIPTCONTAINER,
+ 0, static_cast<void *>( theChild.get())));
+
+ OUString sId(OUString::number(reinterpret_cast<sal_Int64>(aArr.back().get())));
+ m_xTreeView->insert(pParentEntry, -1, &uiName, &sId, nullptr, nullptr, &aImage, bChildOnDemand, nullptr);
+ }
+ }
+ }
+ }
+ catch (RuntimeException&) {
+ // do nothing, the entry will not be displayed in the UI
+ }
+}
+
+void CuiConfigGroupListBox::FillFunctionsList(const css::uno::Sequence<DispatchInformation>& xCommands)
+{
+ m_pFunctionListBox->freeze();
+ for (const auto & rInfo : xCommands)
+ {
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rInfo.Command, m_sModuleLongName);
+
+ OUString sUIName = MapCommand2UIName(rInfo.Command);
+ aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::FUNCTION_SLOT, 0 ) );
+ SfxGroupInfo_Impl* pGrpInfo = aArr.back().get();
+ pGrpInfo->sCommand = rInfo.Command;
+ pGrpInfo->sLabel = sUIName;
+ pGrpInfo->sTooltip = vcl::CommandInfoProvider::GetTooltipForCommand(rInfo.Command, aProperties, m_xFrame);
+ m_pFunctionListBox->append(OUString::number(reinterpret_cast<sal_Int64>(pGrpInfo)), sUIName);
+ }
+ m_pFunctionListBox->thaw();
+}
+
+void CuiConfigGroupListBox::Init(const css::uno::Reference< css::uno::XComponentContext >& xContext,
+ const css::uno::Reference< css::frame::XFrame >& xFrame,
+ const OUString& sModuleLongName,
+ bool bEventMode)
+{
+ m_xTreeView->freeze();
+ ClearAll(); // Remove all old entries from treelist box
+
+ m_xContext = xContext;
+ m_xFrame = xFrame;
+ if( bEventMode )
+ {
+ m_sModuleLongName = sModuleLongName;
+ m_xGlobalCategoryInfo = css::ui::theUICategoryDescription::get( m_xContext );
+ m_xModuleCategoryInfo.set(m_xGlobalCategoryInfo->getByName(m_sModuleLongName), css::uno::UNO_QUERY_THROW);
+ m_xUICmdDescription = css::frame::theUICommandDescription::get( m_xContext );
+
+ InitModule();
+ }
+
+ SAL_INFO("cui.customize", "** ** About to initialise SF Scripts");
+ // Add Scripting Framework entries
+ Reference< browse::XBrowseNode > rootNode;
+ try
+ {
+ Reference< browse::XBrowseNodeFactory > xFac = browse::theBrowseNodeFactory::get( m_xContext );
+ rootNode.set( xFac->createView( browse::BrowseNodeFactoryViewTypes::MACROSELECTOR ) );
+ }
+ catch( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("cui.customize", "Caught some exception whilst retrieving browse nodes from factory");
+ // TODO exception handling
+ }
+
+
+ if ( rootNode.is() )
+ {
+ if ( bEventMode )
+ {
+ //We call acquire on the XBrowseNode so that it does not
+ //get autodestructed and become invalid when accessed later.
+ rootNode->acquire();
+
+ aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_SCRIPTCONTAINER, 0,
+ static_cast<void *>(rootNode.get())));
+ OUString aTitle(xImp->m_sDlgMacros);
+ OUString sId(OUString::number(reinterpret_cast<sal_Int64>(aArr.back().get())));
+ m_xTreeView->insert(nullptr, -1, &aTitle, &sId, nullptr, nullptr, nullptr, true, nullptr);
+ }
+ else
+ {
+ //We are only showing scripts not slot APIs so skip
+ //Root node and show location nodes
+ FillScriptList(rootNode, nullptr, false);
+ }
+ }
+
+ // add styles
+ if ( bEventMode )
+ {
+ aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_STYLES, 0, nullptr ) ); // TODO last parameter should contain user data
+ OUString sStyle(xImp->m_aStrGroupStyles);
+ OUString sId(OUString::number(reinterpret_cast<sal_Int64>(aArr.back().get())));
+ m_xTreeView->insert(nullptr, -1, &sStyle, &sId, nullptr, nullptr, nullptr, true, nullptr);
+ }
+
+ m_xTreeView->thaw();
+ m_xTreeView->scroll_to_row(0);
+ m_xTreeView->select(0);
+}
+
+OUString CuiConfigGroupListBox::GetImage(
+ const Reference< browse::XBrowseNode >& node,
+ Reference< XComponentContext > const & xCtx,
+ bool bIsRootNode)
+{
+ OUString aImage;
+ if ( bIsRootNode )
+ {
+ if (node->getName() == "user" || node->getName() == "share" )
+ {
+ aImage = RID_CUIBMP_HARDDISK;
+ }
+ else
+ {
+ OUString factoryURL;
+ OUString nodeName = node->getName();
+ Reference<XInterface> xDocumentModel = getDocumentModel(xCtx, nodeName );
+ if ( xDocumentModel.is() )
+ {
+ Reference< frame::XModuleManager2 > xModuleManager( frame::ModuleManager::create(xCtx) );
+ // get the long name of the document:
+ OUString appModule( xModuleManager->identify(
+ xDocumentModel ) );
+ Sequence<beans::PropertyValue> moduleDescr;
+ Any aAny = xModuleManager->getByName(appModule);
+ if( !( aAny >>= moduleDescr ) )
+ {
+ throw RuntimeException("SFTreeListBox::Init: failed to get PropertyValue");
+ }
+ beans::PropertyValue const * pmoduleDescr =
+ moduleDescr.getConstArray();
+ for ( sal_Int32 pos = moduleDescr.getLength(); pos--; )
+ {
+ if ( pmoduleDescr[ pos ].Name == "ooSetupFactoryEmptyDocumentURL" )
+ {
+ pmoduleDescr[ pos ].Value >>= factoryURL;
+ SAL_INFO("cui.customize", "factory url for doc images is " << factoryURL);
+ break;
+ }
+ }
+ }
+ if( !factoryURL.isEmpty() )
+ {
+ aImage = SvFileInformationManager::GetFileImageId(INetURLObject(factoryURL));
+ }
+ else
+ {
+ aImage = RID_CUIBMP_DOC;
+ }
+ }
+ }
+ else
+ {
+ if( node->getType() == browse::BrowseNodeTypes::SCRIPT )
+ aImage = RID_CUIBMP_MACRO;
+ else
+ aImage = RID_CUIBMP_LIB;
+ }
+ return aImage;
+}
+
+Reference< XInterface >
+CuiConfigGroupListBox::getDocumentModel( Reference< XComponentContext > const & xCtx, OUString const & docName )
+{
+ Reference< XInterface > xModel;
+ Reference< frame::XDesktop2 > desktop = frame::Desktop::create( xCtx );
+
+ Reference< container::XEnumerationAccess > componentsAccess =
+ desktop->getComponents();
+ Reference< container::XEnumeration > components =
+ componentsAccess->createEnumeration();
+ while (components->hasMoreElements())
+ {
+ Reference< frame::XModel > model(
+ components->nextElement(), UNO_QUERY );
+ if ( model.is() )
+ {
+ OUString sTdocUrl =
+ ::comphelper::DocumentInfo::getDocumentTitle( model );
+ if( sTdocUrl == docName )
+ {
+ xModel = model;
+ break;
+ }
+ }
+ }
+ return xModel;
+}
+
+OUString CuiConfigGroupListBox::MapCommand2UIName(const OUString& sCommand)
+{
+ OUString sUIName;
+ try
+ {
+ css::uno::Reference< css::container::XNameAccess > xModuleConf;
+ m_xUICmdDescription->getByName(m_sModuleLongName) >>= xModuleConf;
+ if (xModuleConf.is())
+ {
+ ::comphelper::SequenceAsHashMap lProps(xModuleConf->getByName(sCommand));
+ sUIName = lProps.getUnpackedValueOrDefault("Name", OUString());
+ }
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(css::uno::Exception&)
+ { sUIName.clear(); }
+
+ // fallback for missing UINames !?
+ if (sUIName.isEmpty())
+ {
+ sUIName = sCommand;
+ }
+
+ return sUIName;
+}
+
+void CuiConfigGroupListBox::GroupSelected()
+/* Description
+ A function group or a basic module has been selected.
+ All functions/macros are displayed in the functionlistbox.
+*/
+{
+ std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator());
+ if (!m_xTreeView->get_selected(xIter.get()))
+ return;
+
+ SfxGroupInfo_Impl *pInfo = reinterpret_cast<SfxGroupInfo_Impl*>(m_xTreeView->get_id(*xIter).toInt64());
+ m_pFunctionListBox->freeze();
+ m_pFunctionListBox->ClearAll();
+
+ switch ( pInfo->nKind )
+ {
+ case SfxCfgKind::GROUP_ALLFUNCTIONS:
+ {
+ css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider( m_xFrame, UNO_QUERY );
+ bool bValidIter = m_xTreeView->get_iter_first(*xIter);
+ while (bValidIter)
+ {
+ SfxGroupInfo_Impl *pCurrentInfo = reinterpret_cast<SfxGroupInfo_Impl*>(m_xTreeView->get_id(*xIter).toInt64());
+ if (pCurrentInfo->nKind == SfxCfgKind::GROUP_FUNCTION)
+ {
+ css::uno::Sequence< css::frame::DispatchInformation > lCommands;
+ try
+ {
+ lCommands = xProvider->getConfigurableDispatchInformation( pCurrentInfo->nUniqueID );
+ FillFunctionsList( lCommands );
+ }
+ catch ( container::NoSuchElementException& )
+ {
+ }
+ }
+ bValidIter = m_xTreeView->iter_next(*xIter);
+ }
+ break;
+ }
+
+ case SfxCfgKind::GROUP_FUNCTION :
+ {
+ sal_uInt16 nGroup = pInfo->nUniqueID;
+ css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider (m_xFrame, css::uno::UNO_QUERY_THROW);
+ css::uno::Sequence< css::frame::DispatchInformation > lCommands = xProvider->getConfigurableDispatchInformation(nGroup);
+ FillFunctionsList( lCommands );
+ break;
+ }
+
+ case SfxCfgKind::GROUP_SCRIPTCONTAINER:
+ {
+ if (!m_xTreeView->iter_has_child(*xIter))
+ {
+ Reference< browse::XBrowseNode > rootNode(
+ static_cast< browse::XBrowseNode* >( pInfo->pObject ) ) ;
+
+ try {
+ if ( rootNode->hasChildNodes() )
+ {
+ const Sequence< Reference< browse::XBrowseNode > > children =
+ rootNode->getChildNodes();
+
+ for ( const Reference< browse::XBrowseNode >& childNode : children )
+ {
+ if (childNode->getType() == browse::BrowseNodeTypes::SCRIPT)
+ {
+ OUString uri, description;
+
+ Reference < beans::XPropertySet >xPropSet( childNode, UNO_QUERY );
+ if (!xPropSet.is())
+ {
+ continue;
+ }
+
+ Any value =
+ xPropSet->getPropertyValue("URI");
+ value >>= uri;
+
+ try
+ {
+ value = xPropSet->getPropertyValue("Description");
+ value >>= description;
+ }
+ catch (Exception &) {
+ // do nothing, the description will be empty
+ }
+
+ OUString* pScriptURI = new OUString( uri );
+
+ OUString aImage = GetImage(childNode, Reference< XComponentContext >(), false);
+ m_pFunctionListBox->aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::FUNCTION_SCRIPT, 0, pScriptURI ));
+ m_pFunctionListBox->aArr.back()->sCommand = uri;
+ m_pFunctionListBox->aArr.back()->sLabel = childNode->getName();
+ m_pFunctionListBox->aArr.back()->sHelpText = description;
+
+ OUString sId(OUString::number(reinterpret_cast<sal_Int64>(m_pFunctionListBox->aArr.back().get())));
+ m_pFunctionListBox->append(sId, childNode->getName(), aImage);
+ }
+ }
+ }
+ }
+ catch (RuntimeException&) {
+ // do nothing, the entry will not be displayed in the UI
+ }
+ }
+ break;
+ }
+
+ case SfxCfgKind::GROUP_STYLES :
+ {
+ SfxStyleInfo_Impl* pFamily = static_cast<SfxStyleInfo_Impl*>(pInfo->pObject);
+ if (pFamily)
+ {
+ const std::vector< SfxStyleInfo_Impl > lStyles = m_pStylesInfo->getStyles(pFamily->sFamily);
+ for (auto const& lStyle : lStyles)
+ {
+ SfxStyleInfo_Impl* pStyle = new SfxStyleInfo_Impl(lStyle);
+ m_pFunctionListBox->aArr.push_back(std::make_unique<SfxGroupInfo_Impl>(SfxCfgKind::GROUP_STYLES, 0, pStyle));
+ m_pFunctionListBox->aArr.back()->sCommand = pStyle->sCommand;
+ m_pFunctionListBox->aArr.back()->sLabel = pStyle->sLabel;
+ OUString sId(OUString::number(reinterpret_cast<sal_Int64>(m_pFunctionListBox->aArr.back().get())));
+ m_pFunctionListBox->append(sId, pStyle->sLabel);
+ }
+ }
+ break;
+ }
+
+ default:
+ // Do nothing, the list box will stay empty
+ SAL_INFO( "cui.customize", "Ignoring unexpected SfxCfgKind: " << static_cast<int>(pInfo->nKind) );
+ break;
+ }
+
+ m_pFunctionListBox->thaw();
+
+ if (m_pFunctionListBox->n_children())
+ m_pFunctionListBox->select(0);
+}
+
+/* Description
+ A basic or a library is opened.
+*/
+IMPL_LINK(CuiConfigGroupListBox, ExpandingHdl, const weld::TreeIter&, rIter, bool)
+{
+ SfxGroupInfo_Impl *pInfo = reinterpret_cast<SfxGroupInfo_Impl*>(m_xTreeView->get_id(rIter).toInt64());
+ switch ( pInfo->nKind )
+ {
+ case SfxCfgKind::GROUP_SCRIPTCONTAINER:
+ {
+ if (!m_xTreeView->iter_has_child(rIter))
+ {
+ Reference< browse::XBrowseNode > rootNode(
+ static_cast< browse::XBrowseNode* >( pInfo->pObject ) ) ;
+ FillScriptList(rootNode, &rIter, true /* i30923 */ );
+ }
+ break;
+ }
+
+ case SfxCfgKind::GROUP_STYLES:
+ {
+ if (!m_xTreeView->iter_has_child(rIter))
+ {
+ const std::vector<SfxStyleInfo_Impl> lStyleFamilies = m_pStylesInfo->getStyleFamilies();
+ for (auto const& lStyleFamily : lStyleFamilies)
+ {
+ SfxStyleInfo_Impl* pFamily = new SfxStyleInfo_Impl(lStyleFamily);
+ aArr.push_back( std::make_unique<SfxGroupInfo_Impl>( SfxCfgKind::GROUP_STYLES, 0, pFamily ));
+ OUString sId(OUString::number(reinterpret_cast<sal_Int64>(aArr.back().get())));
+ m_xTreeView->insert(&rIter, -1, &pFamily->sLabel, &sId, nullptr, nullptr, nullptr, false, nullptr);
+ }
+ }
+ break;
+ }
+
+ default:
+ OSL_FAIL( "Wrong group type!" );
+ break;
+ }
+ return true;
+}
+
+void CuiConfigGroupListBox::SelectMacro( const SfxMacroInfoItem *pItem )
+{
+ SelectMacro( pItem->GetBasicManager()->GetName(),
+ pItem->GetQualifiedName() );
+}
+
+void CuiConfigGroupListBox::SelectMacro( const OUString& rBasic,
+ const OUString& rMacro )
+{
+ const OUString aBasicName(rBasic + " " + xImp->m_sMacros);
+ sal_Int32 nIdx {rMacro.lastIndexOf('.')};
+ const OUString aMethod( rMacro.copy(nIdx+1) );
+ OUString aLib;
+ OUString aModule;
+ if ( nIdx>0 )
+ {
+ // string contains at least 2 tokens
+ nIdx = rMacro.lastIndexOf('.', nIdx);
+ if (nIdx>=0)
+ {
+ // string contains at least 3 tokens
+ aLib = rMacro.getToken( 0, '.' );
+ aModule = rMacro.getToken( 0, '.', ++nIdx );
+ }
+ }
+
+ std::unique_ptr<weld::TreeIter> xIter = m_xTreeView->make_iterator();
+ if (!m_xTreeView->get_iter_first(*xIter))
+ return;
+
+ do
+ {
+ OUString aEntryBas = m_xTreeView->get_text(*xIter);
+ if (aEntryBas == aBasicName)
+ {
+ m_xTreeView->expand_row(*xIter);
+ std::unique_ptr<weld::TreeIter> xLibIter = m_xTreeView->make_iterator(xIter.get());
+ if (m_xTreeView->get_iter_first(*xLibIter))
+ {
+ do
+ {
+ OUString aEntryLib = m_xTreeView->get_text(*xLibIter);
+ if (aEntryLib == aLib)
+ {
+ m_xTreeView->expand_row(*xLibIter);
+ std::unique_ptr<weld::TreeIter> xModIter = m_xTreeView->make_iterator(xLibIter.get());
+ if (m_xTreeView->get_iter_first(*xModIter))
+ {
+ do
+ {
+ OUString aEntryMod = m_xTreeView->get_text(*xModIter);
+ if ( aEntryMod == aModule )
+ {
+ m_xTreeView->expand_row(*xModIter);
+ m_xTreeView->scroll_to_row(*xModIter);
+ m_xTreeView->select(*xModIter);
+ for (int i = 0, nCount = m_pFunctionListBox->n_children(); i < nCount; ++i)
+ {
+ OUString aEntryMethod = m_pFunctionListBox->get_text(i);
+ if (aEntryMethod == aMethod)
+ {
+ m_pFunctionListBox->select(i);
+ m_pFunctionListBox->scroll_to_row(i);
+ return;
+ }
+ }
+ }
+ } while (m_xTreeView->iter_next_sibling(*xModIter));
+ }
+ }
+ } while (m_xTreeView->iter_next_sibling(*xLibIter));
+ }
+ }
+ } while (m_xTreeView->iter_next_sibling(*xIter));
+}
+
+/*
+ * Implementation of SvxScriptSelectorDialog
+ *
+ * This dialog is used for selecting Slot API commands
+ * and Scripting Framework Scripts.
+ */
+
+SvxScriptSelectorDialog::SvxScriptSelectorDialog(
+ weld::Window* pParent, const css::uno::Reference< css::frame::XFrame >& xFrame)
+ : GenericDialogController(pParent, "cui/ui/macroselectordialog.ui", "MacroSelectorDialog")
+ , m_xDialogDescription(m_xBuilder->weld_label("helpmacro"))
+ , m_xCategories(new CuiConfigGroupListBox(m_xBuilder->weld_tree_view("categories")))
+ , m_xCommands(new CuiConfigFunctionListBox(m_xBuilder->weld_tree_view("commands")))
+ , m_xLibraryFT(m_xBuilder->weld_label("libraryft"))
+ , m_xCategoryFT(m_xBuilder->weld_label("categoryft"))
+ , m_xMacronameFT(m_xBuilder->weld_label("macronameft"))
+ , m_xCommandsFT(m_xBuilder->weld_label("commandsft"))
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xCancelButton(m_xBuilder->weld_button("cancel"))
+ , m_xDescriptionText(m_xBuilder->weld_text_view("description"))
+{
+ m_xCancelButton->show();
+ m_xDialogDescription->show();
+ m_xOKButton->show();
+
+ m_xLibraryFT->set_visible(true);
+ m_xCategoryFT->set_visible(false);
+ m_xMacronameFT->set_visible(true);
+ m_xCommandsFT->set_visible(false);
+
+ const OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame));
+ m_xCategories->SetFunctionListBox(m_xCommands.get());
+ m_xCategories->Init(comphelper::getProcessComponentContext(), xFrame, aModuleName, /*bShowSlots*/false);
+
+ m_xCategories->connect_changed(
+ LINK( this, SvxScriptSelectorDialog, SelectHdl ) );
+ m_xCommands->connect_changed( LINK( this, SvxScriptSelectorDialog, SelectHdl ) );
+ m_xCommands->connect_row_activated( LINK( this, SvxScriptSelectorDialog, FunctionDoubleClickHdl ) );
+
+ m_xOKButton->connect_clicked( LINK( this, SvxScriptSelectorDialog, ClickHdl ) );
+ m_xCancelButton->connect_clicked( LINK( this, SvxScriptSelectorDialog, ClickHdl ) );
+
+ m_sDefaultDesc = m_xDescriptionText->get_text();
+
+ // Support style commands
+ uno::Reference<frame::XController> xController;
+ uno::Reference<frame::XModel> xModel;
+ if (xFrame.is())
+ xController = xFrame->getController();
+ if (xController.is())
+ xModel = xController->getModel();
+
+ m_aStylesInfo.init(aModuleName, xModel);
+ m_xCategories->SetStylesInfo(&m_aStylesInfo);
+
+ UpdateUI();
+}
+
+SvxScriptSelectorDialog::~SvxScriptSelectorDialog()
+{
+}
+
+IMPL_LINK(SvxScriptSelectorDialog, SelectHdl, weld::TreeView&, rCtrl, void)
+{
+ if (&rCtrl == &m_xCategories->get_widget())
+ {
+ m_xCategories->GroupSelected();
+ }
+ UpdateUI();
+}
+
+IMPL_LINK_NOARG(SvxScriptSelectorDialog, FunctionDoubleClickHdl, weld::TreeView&, bool)
+{
+ if (m_xOKButton->get_sensitive())
+ ClickHdl(*m_xOKButton);
+ return true;
+}
+
+// Check if command is selected and enable the OK button accordingly
+// Grab the help text for this id if available and update the description field
+void
+SvxScriptSelectorDialog::UpdateUI()
+{
+ OUString url = GetScriptURL();
+ if ( !url.isEmpty() )
+ {
+ OUString sMessage = m_xCommands->GetHelpText();
+ m_xDescriptionText->set_text(sMessage.isEmpty() ? m_sDefaultDesc : sMessage);
+
+ m_xOKButton->set_sensitive(true);
+ }
+ else
+ {
+ m_xDescriptionText->set_text(m_sDefaultDesc);
+ m_xOKButton->set_sensitive(false);
+ }
+}
+
+IMPL_LINK(SvxScriptSelectorDialog, ClickHdl, weld::Button&, rButton, void)
+{
+ if (&rButton == m_xCancelButton.get())
+ {
+ m_xDialog->response(RET_CANCEL);
+ }
+ else if (&rButton == m_xOKButton.get())
+ {
+ m_xDialog->response(RET_OK);
+ }
+}
+
+void
+SvxScriptSelectorDialog::SetRunLabel()
+{
+ m_xOKButton->set_label(CuiResId(RID_SVXSTR_SELECTOR_RUN));
+}
+
+OUString
+SvxScriptSelectorDialog::GetScriptURL() const
+{
+ OUString result;
+
+ std::unique_ptr<weld::TreeIter> xIter = m_xCommands->make_iterator();
+ if (m_xCommands->get_selected(xIter.get()))
+ {
+ SfxGroupInfo_Impl *pData = reinterpret_cast<SfxGroupInfo_Impl*>(m_xCommands->get_id(*xIter).toInt64());
+ if ( ( pData->nKind == SfxCfgKind::FUNCTION_SLOT )
+ || ( pData->nKind == SfxCfgKind::FUNCTION_SCRIPT )
+ || ( pData->nKind == SfxCfgKind::GROUP_STYLES )
+ )
+ {
+ result = pData->sCommand;
+ }
+ }
+
+ return result;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */