summaryrefslogtreecommitdiffstats
path: root/sw/source/uibase/utlui/gloslst.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/uibase/utlui/gloslst.cxx')
-rw-r--r--sw/source/uibase/utlui/gloslst.cxx445
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: */