/* -*- 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 #include #include #include #include #include "vbacontentcontrol.hxx" #include "vbacontentcontrols.hxx" #include "wordvbahelper.hxx" using namespace ::ooo::vba; using namespace ::com::sun::star; // Helper function to access the content controls // @param rIndex // [in] negative indexes indicate the need to search by name, otherwise get by index, // using SAL_MAX_INT32 to indicate the need to just get the total count. // [out] rIndex indicates the found index, or the total number of content controls static std::shared_ptr lcl_getContentControl(std::u16string_view sName, std::u16string_view sTag, std::u16string_view sTitle, sal_Int32& rIndex, const uno::Reference& xTextDocument, uno::Sequence* pElementNames = nullptr) { SwDoc* pDoc = word::getDocShell(xTextDocument)->GetDoc(); if (!pDoc) return nullptr; assert(sTag.empty() || sTitle.empty()); // only one grouping at a time is allowed std::shared_ptr pControl; std::vector vElementNames; SwContentControlManager& rManager = pDoc->GetContentControlManager(); const size_t nLen = rManager.GetCount(); if (!pElementNames && rIndex > 0 && sName.empty() && sTag.empty() && sTitle.empty()) { size_t i = static_cast(rIndex); // This is the normal get-by-index/getCount mode - no need for fancy filtering. if (i < nLen) pControl = rManager.Get(i)->GetContentControl().GetContentControl(); else rIndex = nLen; } else { // loop through everything collecting names, filtering by Tag/Title sal_Int32 nCounter = 0; for (size_t i = 0; i < nLen; ++i) { pControl = rManager.Get(i)->GetContentControl().GetContentControl(); if (!sTag.empty() && sTag != pControl->GetTag()) { pControl = nullptr; continue; } if (!sTitle.empty() && sTitle != pControl->GetAlias()) { pControl = nullptr; continue; } // When treated as a name, consider the integer ID to be unsigned const OUString sID = OUString::number(static_cast(pControl->GetId())); if (!sName.empty() && sName != sID) { pControl = nullptr; continue; } if (pElementNames) vElementNames.push_back(sID); if (rIndex == nCounter || !sName.empty()) break; pControl = nullptr; ++nCounter; } rIndex = nCounter; } if (pElementNames) *pElementNames = comphelper::containerToSequence(vElementNames); return pControl; } namespace { class ContentControlsEnumWrapper : public EnumerationHelper_BASE { uno::Reference mxIndexAccess; sal_Int32 mnIndex; public: explicit ContentControlsEnumWrapper(uno::Reference xIndexAccess) : mxIndexAccess(std::move(xIndexAccess)) , mnIndex(0) { } sal_Bool SAL_CALL hasMoreElements() override { return (mnIndex < mxIndexAccess->getCount()); } uno::Any SAL_CALL nextElement() override { if (mnIndex < mxIndexAccess->getCount()) { return mxIndexAccess->getByIndex(mnIndex++); } throw container::NoSuchElementException(); } }; class ContentControlCollectionHelper : public ::cppu::WeakImplHelper { private: uno::Reference mxParent; uno::Reference mxContext; uno::Reference mxTextDocument; const OUString m_sTag; const OUString m_sTitle; std::shared_ptr m_pCache; public: /// @throws css::uno::RuntimeException ContentControlCollectionHelper(uno::Reference xParent, uno::Reference xContext, uno::Reference xTextDocument, const OUString& rTag, const OUString& rTitle) : mxParent(std::move(xParent)) , mxContext(std::move(xContext)) , mxTextDocument(std::move(xTextDocument)) , m_sTag(rTag) , m_sTitle(rTitle) { } // XIndexAccess sal_Int32 SAL_CALL getCount() override { sal_Int32 nCount = SAL_MAX_INT32; lcl_getContentControl(u"", m_sTag, m_sTitle, nCount, mxTextDocument); return nCount == SAL_MAX_INT32 || nCount < 0 ? 0 : nCount; } uno::Any SAL_CALL getByIndex(sal_Int32 Index) override { m_pCache = lcl_getContentControl(u"", m_sTag, m_sTitle, Index, mxTextDocument); if (!m_pCache) throw lang::IndexOutOfBoundsException(); return uno::Any(uno::Reference( new SwVbaContentControl(mxParent, mxContext, mxTextDocument, m_pCache))); } // XNameAccess uno::Sequence SAL_CALL getElementNames() override { sal_Int32 nCount = SAL_MAX_INT32; uno::Sequence aSeq; lcl_getContentControl(u"", m_sTag, m_sTitle, nCount, mxTextDocument, &aSeq); return aSeq; } uno::Any SAL_CALL getByName(const OUString& aName) override { if (!hasByName(aName)) throw container::NoSuchElementException(); return uno::Any(uno::Reference( new SwVbaContentControl(mxParent, mxContext, mxTextDocument, m_pCache))); } sal_Bool SAL_CALL hasByName(const OUString& aName) override { sal_Int32 nCount = -1; m_pCache = lcl_getContentControl(aName, m_sTag, m_sTitle, nCount, mxTextDocument); return m_pCache != nullptr; } // XElementAccess uno::Type SAL_CALL getElementType() override { return cppu::UnoType::get(); } sal_Bool SAL_CALL hasElements() override { return getCount() != 0; } // XEnumerationAccess uno::Reference SAL_CALL createEnumeration() override { return new ContentControlsEnumWrapper(this); } }; } /** * Content Controls can be accessed and filtered in many different ways. * Surprisingly however, there is no clear, descriptive "by name" access. * Instead, each content control (probably) has a unique _signed-integer_ identifier, * which can be passed to Item() as a float or _unsigned-integer_ string * (to differentiate it from getByIndex). * * Index access can be filtered by Tag, Title, Range, and XML link. * TODO: add filtering for Range, SelectLinkedControls, SelectUnlinkedControls */ SwVbaContentControls::SwVbaContentControls(const uno::Reference& xParent, const uno::Reference& xContext, const uno::Reference& xTextDocument, const OUString& rTag, const OUString& rTitle) : SwVbaContentControls_BASE( xParent, xContext, uno::Reference( new ContentControlCollectionHelper(xParent, xContext, xTextDocument, rTag, rTitle))) { } // uno::Reference SwVbaContentControls::Add(const uno::Any& Range, // sal_Int32 Type) // { // sw::mark::IFieldmark* pFieldmark = nullptr; // switch (Type) // { // case ooo::vba::word::WdFieldType::wdFieldFormCheckBox: // break; // case ooo::vba::word::WdFieldType::wdFieldFormDropDown: // break; // case ooo::vba::word::WdFieldType::wdFieldFormTextInput: // default:; // } // // return uno::Reference( // new SwVbaContentControl(mxParent, mxContext, m_xTextDocument, pFieldmark)); // } // XEnumerationAccess uno::Type SwVbaContentControls::getElementType() { return cppu::UnoType::get(); } uno::Reference SwVbaContentControls::createEnumeration() { return new ContentControlsEnumWrapper(m_xIndexAccess); } uno::Any SwVbaContentControls::createCollectionObject(const uno::Any& aSource) { return aSource; } OUString SwVbaContentControls::getServiceImplName() { return "SwVbaContentControls"; } uno::Sequence SwVbaContentControls::getServiceNames() { static uno::Sequence const sNames{ "ooo.vba.word.ContentControls" }; return sNames; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */