/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include // include search util #include #include #include #include #include #include #include #include #include #include #include #include #include #include //for SaveInData CommandCategoryListBox::CommandCategoryListBox(std::unique_ptr xControl) : pStylesInfo(nullptr) , m_xControl(std::move(xControl)) { //Initialize search util m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE; m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE; m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE | css::util::SearchFlags::REG_NOT_ENDOFLINE); } CommandCategoryListBox::~CommandCategoryListBox() { ClearAll(); } void CommandCategoryListBox::ClearAll() { // Clear objects from m_aGroupInfo vector to avoid memory leak for (const auto& It : m_aGroupInfo) { if (It->nKind == SfxCfgKind::GROUP_STYLES && It->pObject) { SfxStyleInfo_Impl* pStyle = static_cast(It->pObject); delete pStyle; } else if (It->nKind == SfxCfgKind::FUNCTION_SCRIPT && It->pObject) { OUString* pScriptURI = static_cast(It->pObject); delete pScriptURI; } else if (It->nKind == SfxCfgKind::GROUP_SCRIPTCONTAINER && It->pObject) { css::uno::XInterface* xi = static_cast(It->pObject); if (xi != nullptr) { xi->release(); } } } m_aGroupInfo.clear(); m_xControl->clear(); } void CommandCategoryListBox::Init(const css::uno::Reference& xContext, const css::uno::Reference& xFrame, const OUString& sModuleLongName) { // User will not see incomplete UI m_xControl->freeze(); ClearAll(); m_xContext = xContext; m_xFrame = xFrame; 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); // Support style commands css::uno::Reference xController; css::uno::Reference xModel; if (xFrame.is()) xController = xFrame->getController(); if (xController.is()) xModel = xController->getModel(); m_aStylesInfo.init(sModuleLongName, xModel); SetStylesInfo(&m_aStylesInfo); try { css::uno::Reference xProvider( m_xFrame, css::uno::UNO_QUERY_THROW); css::uno::Sequence lGroups = xProvider->getSupportedCommandGroups(); sal_Int32 nGroupsLength = lGroups.getLength(); if (nGroupsLength > 0) { // Add the category of "All commands" m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::GROUP_ALLFUNCTIONS, 0)); m_xControl->append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), CuiResId(RID_SVXSTR_ALLFUNCTIONS)); } // Separate the "All commands"category from the actual categories m_xControl->append_separator(""); typedef std::pair str_id; std::vector aCategories; // Add the actual categories for (sal_Int32 i = 0; i < nGroupsLength; ++i) { sal_Int16 nGroupID = lGroups[i]; OUString sGroupID = OUString::number(nGroupID); OUString sGroupName; try { m_xModuleCategoryInfo->getByName(sGroupID) >>= sGroupName; if (sGroupName.isEmpty()) continue; } catch (const css::container::NoSuchElementException&) { continue; } aCategories.emplace_back(std::make_pair(sGroupName, nGroupID)); } auto const sort = comphelper::string::NaturalStringSorter( comphelper::getProcessComponentContext(), Application::GetSettings().GetUILanguageTag().getLocale()); std::sort(aCategories.begin(), aCategories.end(), [&sort](const str_id& a, const str_id& b) { return sort.compare(a.first, b.first) < 0; }); // Add the actual categories for (const auto& a : aCategories) { const OUString& rGroupName = a.first; sal_Int16 nGroupID = a.second; m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::GROUP_FUNCTION, nGroupID)); m_xControl->append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), rGroupName); } // Separate regular commands from styles and macros m_xControl->append_separator(""); // Add macros category m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, nullptr)); m_xControl->append(OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), CuiResId(RID_SVXSTR_MACROS)); // Add styles category //TODO: last param should contain user data? m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::GROUP_STYLES, 0, nullptr)); m_xControl->append(OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), CuiResId(RID_SVXSTR_GROUP_STYLES)); } catch (const css::uno::RuntimeException&) { throw; } catch (const css::uno::Exception&) { } // Reveal the updated UI to user m_xControl->thaw(); m_xControl->set_active(0); } void CommandCategoryListBox::FillFunctionsList( const css::uno::Sequence& xCommands, CuiConfigFunctionListBox* pFunctionListBox, const OUString& filterTerm, SaveInData* pCurrentSaveInData) { // Setup search filter parameters m_searchOptions.searchString = filterTerm; utl::TextSearch textSearch(m_searchOptions); for (const auto& rInfo : xCommands) { auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rInfo.Command, m_sModuleLongName); OUString sUIName = getCommandName(rInfo.Command); OUString sLabel = vcl::CommandInfoProvider::GetLabelForCommand(aProperties); OUString sTooltipLabel = vcl::CommandInfoProvider::GetTooltipForCommand(rInfo.Command, aProperties, m_xFrame); OUString sPopupLabel = (vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties)) .replaceFirst("~", ""); // Apply the search filter if (!filterTerm.isEmpty() && !textSearch.searchForward(sUIName) && !textSearch.searchForward(sLabel) && !textSearch.searchForward(sTooltipLabel) && !textSearch.searchForward(sPopupLabel)) { continue; } css::uno::Reference xImage; if (pCurrentSaveInData) xImage = pCurrentSaveInData->GetImage(rInfo.Command); m_aGroupInfo.push_back(std::make_unique(SfxCfgKind::FUNCTION_SLOT, 0)); SfxGroupInfo_Impl* pGrpInfo = m_aGroupInfo.back().get(); pGrpInfo->sCommand = rInfo.Command; pGrpInfo->sLabel = sUIName; pGrpInfo->sTooltip = sTooltipLabel; pFunctionListBox->append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), sUIName, xImage); } } OUString CommandCategoryListBox::getCommandName(const OUString& sCommand) { OUString sUIName; try { css::uno::Reference 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 CommandCategoryListBox::categorySelected(CuiConfigFunctionListBox* pFunctionListBox, const OUString& filterTerm, SaveInData* pCurrentSaveInData) { SfxGroupInfo_Impl* pInfo = reinterpret_cast(m_xControl->get_active_id().toInt64()); std::vector> aNodesToExpand; pFunctionListBox->freeze(); pFunctionListBox->ClearAll(); switch (pInfo->nKind) { case SfxCfgKind::GROUP_ALLFUNCTIONS: { css::uno::Reference xProvider( m_xFrame, css::uno::UNO_QUERY); sal_Int32 nEntryCount = m_xControl->get_count(); for (sal_Int32 nCurPos = 0; nCurPos < nEntryCount; ++nCurPos) { SfxGroupInfo_Impl* pCurrentInfo = reinterpret_cast(m_xControl->get_id(nCurPos).toInt64()); if (!pCurrentInfo) //separator continue; if (pCurrentInfo->nKind == SfxCfgKind::GROUP_FUNCTION) { css::uno::Sequence lCommands; try { lCommands = xProvider->getConfigurableDispatchInformation( pCurrentInfo->nUniqueID); FillFunctionsList(lCommands, pFunctionListBox, filterTerm, pCurrentSaveInData); } catch (css::container::NoSuchElementException&) { } } } break; } case SfxCfgKind::GROUP_FUNCTION: { sal_uInt16 nGroup = pInfo->nUniqueID; css::uno::Reference xProvider( m_xFrame, css::uno::UNO_QUERY_THROW); css::uno::Sequence lCommands = xProvider->getConfigurableDispatchInformation(nGroup); FillFunctionsList(lCommands, pFunctionListBox, filterTerm, pCurrentSaveInData); break; } case SfxCfgKind::GROUP_SCRIPTCONTAINER: //Macros { SAL_INFO("cui.customize", "** ** About to initialise SF Scripts"); // Add Scripting Framework entries css::uno::Reference rootNode; try { css::uno::Reference xFac = css::script::browse::theBrowseNodeFactory::get(m_xContext); rootNode.set(xFac->createView( css::script::browse::BrowseNodeFactoryViewTypes::MACROSELECTOR)); } catch (css::uno::Exception const&) { TOOLS_WARN_EXCEPTION( "cui.customize", "Caught some exception whilst retrieving browse nodes from factory"); // TODO exception handling } if (rootNode.is() && rootNode->hasChildNodes()) { //We call acquire on the XBrowseNode so that it does not //get autodestructed and become invalid when accessed later. rootNode->acquire(); m_aGroupInfo.push_back(std::make_unique( SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, static_cast(rootNode.get()))); // Add main macro groups const css::uno::Sequence> aChildNodes = rootNode->getChildNodes(); for (auto const& childGroup : aChildNodes) { childGroup->acquire(); if (childGroup->hasChildNodes()) { OUString sUIName; if (childGroup->getName() == "user") { sUIName = CuiResId(RID_SVXSTR_MYMACROS); } else if (childGroup->getName() == "share") { sUIName = CuiResId(RID_SVXSTR_PRODMACROS); } else { sUIName = childGroup->getName(); } if (sUIName.isEmpty()) { continue; } m_aGroupInfo.push_back(std::make_unique( SfxCfgKind::GROUP_SCRIPTCONTAINER, 0)); std::unique_ptr xMacroGroup(pFunctionListBox->tree_append( OUString::number( reinterpret_cast(m_aGroupInfo.back().get())), sUIName)); { // tdf#128010: Do not nag user asking to enable JRE: if it's disabled, // simply don't show relevant entries (user chose to not use JRE) css::uno::ContextLayer layer( comphelper::NoEnableJavaInteractionContext()); //Add the children and the grand children addChildren(xMacroGroup.get(), childGroup, pFunctionListBox, filterTerm, pCurrentSaveInData, aNodesToExpand); } // Remove the main group if empty if (!pFunctionListBox->iter_has_child(*xMacroGroup)) { pFunctionListBox->remove(*xMacroGroup); } else if (!filterTerm.isEmpty()) { aNodesToExpand.emplace_back(std::move(xMacroGroup)); } } } } break; } case SfxCfgKind::GROUP_STYLES: { const std::vector lStyleFamilies = pStylesInfo->getStyleFamilies(); for (const auto& pIt : lStyleFamilies) { if (pIt.sLabel.isEmpty()) { continue; } m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::GROUP_STYLES, 0)); // pIt.sLabel is Name of the style family std::unique_ptr xFuncEntry(pFunctionListBox->tree_append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), pIt.sLabel)); const std::vector lStyles = pStylesInfo->getStyles(pIt.sFamily); // Setup search filter parameters m_searchOptions.searchString = filterTerm; utl::TextSearch textSearch(m_searchOptions); // Insert children (styles) for (const auto& pStyleIt : lStyles) { OUString sUIName = pStyleIt.sLabel; sal_Int32 aStartPos = 0; sal_Int32 aEndPos = sUIName.getLength(); // Apply the search filter if (!filterTerm.isEmpty() && !textSearch.SearchForward(sUIName, &aStartPos, &aEndPos)) { continue; } SfxStyleInfo_Impl* pStyle = new SfxStyleInfo_Impl(pStyleIt); m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::GROUP_STYLES, 0, pStyle)); m_aGroupInfo.back()->sCommand = pStyle->sCommand; m_aGroupInfo.back()->sLabel = pStyle->sLabel; pFunctionListBox->append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), sUIName, xFuncEntry.get()); } // Remove the style group from the list if no children if (!pFunctionListBox->iter_has_child(*xFuncEntry)) { pFunctionListBox->remove(*xFuncEntry); } else if (!filterTerm.isEmpty()) { aNodesToExpand.emplace_back(std::move(xFuncEntry)); } } break; } default: // Do nothing, the list box will stay empty SAL_INFO("cui.customize", "Ignoring unexpected SfxCfgKind: " << static_cast(pInfo->nKind)); break; } pFunctionListBox->thaw(); if (pFunctionListBox->n_children()) pFunctionListBox->select(0); //post freeze for (const auto& it : aNodesToExpand) pFunctionListBox->expand_row(*it); } void CommandCategoryListBox::SetStylesInfo(SfxStylesInfo_Impl* pStyles) { pStylesInfo = pStyles; } void CommandCategoryListBox::addChildren( const weld::TreeIter* parentEntry, const css::uno::Reference& parentNode, CuiConfigFunctionListBox* pFunctionListBox, const OUString& filterTerm, SaveInData* pCurrentSaveInData, std::vector>& rNodesToExpand) { // Setup search filter parameters m_searchOptions.searchString = filterTerm; utl::TextSearch textSearch(m_searchOptions); const css::uno::Sequence> aChildNodes = parentNode->getChildNodes(); for (auto const& child : aChildNodes) { // Acquire to prevent auto-destruction child->acquire(); if (child->hasChildNodes()) { OUString sUIName = child->getName(); m_aGroupInfo.push_back(std::make_unique( SfxCfgKind::GROUP_SCRIPTCONTAINER, 0, static_cast(child.get()))); std::unique_ptr xNewEntry(pFunctionListBox->tree_append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), sUIName, parentEntry)); addChildren(xNewEntry.get(), child, pFunctionListBox, filterTerm, pCurrentSaveInData, rNodesToExpand); // Remove the group if empty if (!pFunctionListBox->iter_has_child(*xNewEntry)) pFunctionListBox->remove(*xNewEntry); else rNodesToExpand.emplace_back(std::move(xNewEntry)); } else if (child->getType() == css::script::browse::BrowseNodeTypes::SCRIPT) { // Prepare for filtering OUString sUIName = child->getName(); sal_Int32 aStartPos = 0; sal_Int32 aEndPos = sUIName.getLength(); // Apply the search filter if (!filterTerm.isEmpty() && !textSearch.SearchForward(sUIName, &aStartPos, &aEndPos)) { continue; } OUString uri, description; css::uno::Reference xPropSet(child, css::uno::UNO_QUERY); if (!xPropSet.is()) { continue; } css::uno::Any value = xPropSet->getPropertyValue("URI"); value >>= uri; try { value = xPropSet->getPropertyValue("Description"); value >>= description; } catch (css::uno::Exception&) { // do nothing, the description will be empty } if (description.isEmpty()) { description = CuiResId(RID_SVXSTR_NOMACRODESC); } OUString* pScriptURI = new OUString(uri); css::uno::Reference xImage; if (pCurrentSaveInData) xImage = pCurrentSaveInData->GetImage(uri); m_aGroupInfo.push_back( std::make_unique(SfxCfgKind::FUNCTION_SCRIPT, 0, pScriptURI)); m_aGroupInfo.back()->sCommand = uri; m_aGroupInfo.back()->sLabel = sUIName; m_aGroupInfo.back()->sHelpText = description; pFunctionListBox->append( OUString::number(reinterpret_cast(m_aGroupInfo.back().get())), sUIName, xImage, parentEntry); } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */