diff options
Diffstat (limited to 'sw/source/uibase/utlui/gloslst.cxx')
-rw-r--r-- | sw/source/uibase/utlui/gloslst.cxx | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/sw/source/uibase/utlui/gloslst.cxx b/sw/source/uibase/utlui/gloslst.cxx new file mode 100644 index 0000000000..9878d0aa67 --- /dev/null +++ b/sw/source/uibase/utlui/gloslst.cxx @@ -0,0 +1,445 @@ +/* -*- 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 <vcl/weld.hxx> +#include <svl/fstathelper.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> +#include <swtypes.hxx> +#include <swmodule.hxx> +#include <shellio.hxx> +#include <initui.hxx> +#include <glosdoc.hxx> +#include <gloslst.hxx> +#include <swunohelper.hxx> +#include <view.hxx> + +#include <vector> + +#define STRING_DELIM char(0x0A) +#define GLOS_TIMEOUT 30000 // update every 30 seconds +#define FIND_MAX_GLOS 20 + +namespace { + +struct TripleString +{ + OUString sGroup; + OUString sBlock; + OUString sShort; +}; + +class SwGlossDecideDlg : public weld::GenericDialogController +{ + std::unique_ptr<weld::Button> m_xOk; + std::unique_ptr<weld::TreeView> m_xListLB; + + DECL_LINK(DoubleClickHdl, weld::TreeView&, bool); + DECL_LINK(SelectHdl, weld::TreeView&, void); + +public: + explicit SwGlossDecideDlg(weld::Window* pParent); + + weld::TreeView& GetTreeView() {return *m_xListLB;} +}; + +} + +SwGlossDecideDlg::SwGlossDecideDlg(weld::Window* pParent) + : GenericDialogController(pParent, "modules/swriter/ui/selectautotextdialog.ui", "SelectAutoTextDialog") + , m_xOk(m_xBuilder->weld_button("ok")) + , m_xListLB(m_xBuilder->weld_tree_view("treeview")) +{ + m_xListLB->set_size_request(m_xListLB->get_approximate_digit_width() * 32, + m_xListLB->get_height_rows(8)); + m_xListLB->connect_row_activated(LINK(this, SwGlossDecideDlg, DoubleClickHdl)); + m_xListLB->connect_changed(LINK(this, SwGlossDecideDlg, SelectHdl)); +} + +IMPL_LINK_NOARG(SwGlossDecideDlg, DoubleClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +IMPL_LINK_NOARG(SwGlossDecideDlg, SelectHdl, weld::TreeView&, void) +{ + m_xOk->set_sensitive(m_xListLB->get_selected_index() != -1); +} + +SwGlossaryList::SwGlossaryList() : + AutoTimer("SwGlossaryList"), m_bFilled(false) +{ + SvtPathOptions aPathOpt; + m_sPath = aPathOpt.GetAutoTextPath(); + SetTimeout(GLOS_TIMEOUT); +} + +SwGlossaryList::~SwGlossaryList() +{ + ClearGroups(); +} + +// If the GroupName is already known, then only rShortName +// will be filled. Otherwise also rGroupName will be set and +// on demand asked for the right group. + +bool SwGlossaryList::GetShortName(std::u16string_view rLongName, + OUString& rShortName, OUString& rGroupName ) +{ + if(!m_bFilled) + Update(); + + std::vector<TripleString> aTripleStrings; + + size_t nCount = m_aGroupArr.size(); + for(size_t i = 0; i < nCount; i++ ) + { + AutoTextGroup* pGroup = m_aGroupArr[i].get(); + if(!rGroupName.isEmpty() && rGroupName != pGroup->sName) + continue; + + sal_Int32 nPosLong = 0; + for(sal_uInt16 j = 0; j < pGroup->nCount; j++) + { + const OUString sLong = pGroup->sLongNames.getToken(0, STRING_DELIM, nPosLong); + if(rLongName != sLong) + continue; + + TripleString aTriple; + aTriple.sGroup = pGroup->sName; + aTriple.sBlock = sLong; + aTriple.sShort = pGroup->sShortNames.getToken(j, STRING_DELIM); + aTripleStrings.push_back(aTriple); + } + } + + bool bRet = false; + nCount = aTripleStrings.size(); + if(1 == nCount) + { + const TripleString& rTriple(aTripleStrings.front()); + rShortName = rTriple.sShort; + rGroupName = rTriple.sGroup; + bRet = true; + } + else if(1 < nCount) + { + SwView *pView = ::GetActiveView(); + if (!pView) + return bRet; + SwGlossDecideDlg aDlg(pView->GetFrameWeld()); + OUString sTitle = aDlg.get_title() + " " + aTripleStrings.front().sBlock; + aDlg.set_title(sTitle); + + weld::TreeView& rLB = aDlg.GetTreeView(); + for (const auto& rTriple : aTripleStrings) + rLB.append_text(rTriple.sGroup.getToken(0, GLOS_DELIM)); + + rLB.select(0); + if (aDlg.run() == RET_OK && rLB.get_selected_index() != -1) + { + const TripleString& rTriple(aTripleStrings[rLB.get_selected_index()]); + rShortName = rTriple.sShort; + rGroupName = rTriple.sGroup; + bRet = true; + } + else + bRet = false; + } + return bRet; +} + +size_t SwGlossaryList::GetGroupCount() +{ + if(!m_bFilled) + Update(); + return m_aGroupArr.size(); +} + +OUString SwGlossaryList::GetGroupName(size_t nPos) +{ + OSL_ENSURE(m_aGroupArr.size() > nPos, "group not available"); + if(nPos < m_aGroupArr.size()) + { + AutoTextGroup* pGroup = m_aGroupArr[nPos].get(); + OUString sRet = pGroup->sName; + return sRet; + } + return OUString(); +} + +OUString SwGlossaryList::GetGroupTitle(size_t nPos) +{ + OSL_ENSURE(m_aGroupArr.size() > nPos, "group not available"); + if(nPos < m_aGroupArr.size()) + { + AutoTextGroup* pGroup = m_aGroupArr[nPos].get(); + return pGroup->sTitle; + } + return OUString(); +} + +sal_uInt16 SwGlossaryList::GetBlockCount(size_t nGroup) +{ + OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available"); + if(nGroup < m_aGroupArr.size()) + { + AutoTextGroup* pGroup = m_aGroupArr[nGroup].get(); + return pGroup->nCount; + } + return 0; +} + +OUString SwGlossaryList::GetBlockLongName(size_t nGroup, sal_uInt16 nBlock) +{ + OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available"); + if(nGroup < m_aGroupArr.size()) + { + AutoTextGroup* pGroup = m_aGroupArr[nGroup].get(); + return pGroup->sLongNames.getToken(nBlock, STRING_DELIM); + } + return OUString(); +} + +OUString SwGlossaryList::GetBlockShortName(size_t nGroup, sal_uInt16 nBlock) +{ + OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available"); + if(nGroup < m_aGroupArr.size()) + { + AutoTextGroup* pGroup = m_aGroupArr[nGroup].get(); + return pGroup->sShortNames.getToken(nBlock, STRING_DELIM); + } + return OUString(); +} + +void SwGlossaryList::Update() +{ + if(!IsActive()) + Start(); + + SvtPathOptions aPathOpt; + const OUString& sTemp( aPathOpt.GetAutoTextPath() ); + if(sTemp != m_sPath) + { + m_sPath = sTemp; + m_bFilled = false; + ClearGroups(); + } + SwGlossaries* pGlossaries = ::GetGlossaries(); + const std::vector<OUString> & rPathArr = pGlossaries->GetPathArray(); + const OUString sExt( SwGlossaries::GetExtension() ); + if(!m_bFilled) + { + const size_t nGroupCount = pGlossaries->GetGroupCnt(); + for(size_t i = 0; i < nGroupCount; ++i) + { + OUString sGrpName = pGlossaries->GetGroupName(i); + const size_t nPath = static_cast<size_t>( + o3tl::toInt32(o3tl::getToken(sGrpName, 1, GLOS_DELIM))); + if( nPath < rPathArr.size() ) + { + std::unique_ptr<AutoTextGroup> pGroup(new AutoTextGroup); + pGroup->sName = sGrpName; + + FillGroup(pGroup.get(), pGlossaries); + OUString sName = rPathArr[nPath] + "/" + + o3tl::getToken(pGroup->sName, 0, GLOS_DELIM) + sExt; + FStatHelper::GetModifiedDateTimeOfFile( sName, + &pGroup->aDateModified, + &pGroup->aDateModified ); + + m_aGroupArr.insert( m_aGroupArr.begin(), std::move(pGroup) ); + } + } + m_bFilled = true; + } + else + { + for( size_t nPath = 0; nPath < rPathArr.size(); nPath++ ) + { + std::vector<OUString> aFoundGroupNames; + std::vector<OUString> aFiles; + std::vector<DateTime> aDateTimeArr; + + SWUnoHelper::UCB_GetFileListOfFolder( rPathArr[nPath], aFiles, + &sExt, &aDateTimeArr ); + for( size_t nFiles = 0; nFiles < aFiles.size(); ++nFiles ) + { + const OUString aTitle = aFiles[ nFiles ]; + ::DateTime& rDT = aDateTimeArr[ nFiles ]; + + OUString sName( aTitle.copy( 0, aTitle.getLength() - sExt.getLength() )); + + aFoundGroupNames.push_back(sName); + sName += OUStringChar(GLOS_DELIM) + OUString::number( o3tl::narrowing<sal_uInt16>(nPath) ); + AutoTextGroup* pFound = FindGroup( sName ); + if( !pFound ) + { + pFound = new AutoTextGroup; + pFound->sName = sName; + FillGroup( pFound, pGlossaries ); + pFound->aDateModified = rDT; + + m_aGroupArr.push_back(std::unique_ptr<AutoTextGroup>(pFound)); + } + else if( pFound->aDateModified != rDT ) + { + FillGroup(pFound, pGlossaries); + pFound->aDateModified = rDT; + } + } + + for( size_t i = m_aGroupArr.size(); i>0; ) + { + --i; + // maybe remove deleted groups + AutoTextGroup* pGroup = m_aGroupArr[i].get(); + const size_t nGroupPath = static_cast<size_t>( + o3tl::toInt32(o3tl::getToken(pGroup->sName, 1, GLOS_DELIM))); + // Only the groups will be checked which are registered + // for the current subpath. + if( nGroupPath == nPath ) + { + std::u16string_view sCompareGroup = o3tl::getToken(pGroup->sName, 0, GLOS_DELIM); + bool bFound = std::any_of(aFoundGroupNames.begin(), aFoundGroupNames.end(), + [&sCompareGroup](const OUString& rGroupName) { return sCompareGroup == rGroupName; }); + + if(!bFound) + { + m_aGroupArr.erase(m_aGroupArr.begin() + i); + } + } + } + } + } +} + +void SwGlossaryList::Invoke() +{ + // Only update automatically if a SwView has the focus. + if(::GetActiveView()) + Update(); +} + +AutoTextGroup* SwGlossaryList::FindGroup(std::u16string_view rGroupName) +{ + for(const auto & pRet : m_aGroupArr) + { + if(pRet->sName == rGroupName) + return pRet.get(); + } + return nullptr; +} + +void SwGlossaryList::FillGroup(AutoTextGroup* pGroup, SwGlossaries* pGlossaries) +{ + std::unique_ptr<SwTextBlocks> pBlock = pGlossaries->GetGroupDoc(pGroup->sName); + pGroup->nCount = pBlock ? pBlock->GetCount() : 0; + pGroup->sLongNames.clear(); + pGroup->sShortNames.clear(); + if(pBlock) + pGroup->sTitle = pBlock->GetName(); + + for(sal_uInt16 j = 0; j < pGroup->nCount; j++) + { + pGroup->sLongNames += pBlock->GetLongName(j) + + OUStringChar(STRING_DELIM); + pGroup->sShortNames += pBlock->GetShortName(j) + + OUStringChar(STRING_DELIM); + } +} + +// Give back all (not exceeding FIND_MAX_GLOS) found modules +// with matching beginning. + +void SwGlossaryList::HasLongName(const std::vector<OUString>& rBeginCandidates, + std::vector<std::pair<OUString, sal_uInt16>>& rLongNames) +{ + if(!m_bFilled) + Update(); + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + // We store results for all candidate words in separate lists, so that later + // we can sort them according to the candidate position + std::vector<std::vector<OUString>> aResults(rBeginCandidates.size()); + + // We can't break after FIND_MAX_GLOS items found, since first items may have ended up in + // lower-priority lists, and those from higher-priority lists are yet to come. So process all. + for (const auto& pGroup : m_aGroupArr) + { + sal_Int32 nIdx{ 0 }; + for(sal_uInt16 j = 0; j < pGroup->nCount; j++) + { + OUString sBlock = pGroup->sLongNames.getToken(0, STRING_DELIM, nIdx); + for (size_t i = 0; i < rBeginCandidates.size(); ++i) + { + const OUString& s = rBeginCandidates[i]; + if (s.getLength() + 1 < sBlock.getLength() + && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s)) + { + aResults[i].push_back(sBlock); + } + } + } + } + + std::vector<std::pair<OUString, sal_uInt16>> aAllResults; + // Sort and concatenate all result lists. See QuickHelpData::SortAndFilter + for (size_t i = 0; i < rBeginCandidates.size(); ++i) + { + std::sort(aResults[i].begin(), aResults[i].end(), + [origWord = rBeginCandidates[i]](const OUString& s1, const OUString& s2) { + int nRet = s1.compareToIgnoreAsciiCase(s2); + if (nRet == 0) + { + // fdo#61251 sort stuff that starts with the exact rOrigWord before + // another ignore-case candidate + int n1StartsWithOrig = s1.startsWith(origWord) ? 0 : 1; + int n2StartsWithOrig = s2.startsWith(origWord) ? 0 : 1; + return n1StartsWithOrig < n2StartsWithOrig; + } + return nRet < 0; + }); + // All suggestions must be accompanied with length of the text they would replace + std::transform(aResults[i].begin(), aResults[i].end(), std::back_inserter(aAllResults), + [nLen = sal_uInt16(rBeginCandidates[i].getLength())](const OUString& s) { + return std::make_pair(s, nLen); + }); + } + + const auto& it = std::unique( + aAllResults.begin(), aAllResults.end(), + [](const std::pair<OUString, sal_uInt16>& s1, const std::pair<OUString, sal_uInt16>& s2) { + return s1.first.equalsIgnoreAsciiCase(s2.first); + }); + if (const auto nCount = std::min<size_t>(std::distance(aAllResults.begin(), it), FIND_MAX_GLOS)) + { + rLongNames.insert(rLongNames.end(), aAllResults.begin(), aAllResults.begin() + nCount); + } +} + +void SwGlossaryList::ClearGroups() +{ + m_aGroupArr.clear(); + m_bFilled = false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |