Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / uibase / utlui / gloslst.cxx
blob9878d0aa6716d291bfc631c6afa8a6f06f4877f4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <vcl/weld.hxx>
21 #include <svl/fstathelper.hxx>
22 #include <unotools/pathoptions.hxx>
23 #include <unotools/transliterationwrapper.hxx>
24 #include <osl/diagnose.h>
25 #include <o3tl/string_view.hxx>
26 #include <swtypes.hxx>
27 #include <swmodule.hxx>
28 #include <shellio.hxx>
29 #include <initui.hxx>
30 #include <glosdoc.hxx>
31 #include <gloslst.hxx>
32 #include <swunohelper.hxx>
33 #include <view.hxx>
35 #include <vector>
37 #define STRING_DELIM char(0x0A)
38 #define GLOS_TIMEOUT 30000 // update every 30 seconds
39 #define FIND_MAX_GLOS 20
41 namespace {
43 struct TripleString
45 OUString sGroup;
46 OUString sBlock;
47 OUString sShort;
50 class SwGlossDecideDlg : public weld::GenericDialogController
52 std::unique_ptr<weld::Button> m_xOk;
53 std::unique_ptr<weld::TreeView> m_xListLB;
55 DECL_LINK(DoubleClickHdl, weld::TreeView&, bool);
56 DECL_LINK(SelectHdl, weld::TreeView&, void);
58 public:
59 explicit SwGlossDecideDlg(weld::Window* pParent);
61 weld::TreeView& GetTreeView() {return *m_xListLB;}
66 SwGlossDecideDlg::SwGlossDecideDlg(weld::Window* pParent)
67 : GenericDialogController(pParent, "modules/swriter/ui/selectautotextdialog.ui", "SelectAutoTextDialog")
68 , m_xOk(m_xBuilder->weld_button("ok"))
69 , m_xListLB(m_xBuilder->weld_tree_view("treeview"))
71 m_xListLB->set_size_request(m_xListLB->get_approximate_digit_width() * 32,
72 m_xListLB->get_height_rows(8));
73 m_xListLB->connect_row_activated(LINK(this, SwGlossDecideDlg, DoubleClickHdl));
74 m_xListLB->connect_changed(LINK(this, SwGlossDecideDlg, SelectHdl));
77 IMPL_LINK_NOARG(SwGlossDecideDlg, DoubleClickHdl, weld::TreeView&, bool)
79 m_xDialog->response(RET_OK);
80 return true;
83 IMPL_LINK_NOARG(SwGlossDecideDlg, SelectHdl, weld::TreeView&, void)
85 m_xOk->set_sensitive(m_xListLB->get_selected_index() != -1);
88 SwGlossaryList::SwGlossaryList() :
89 AutoTimer("SwGlossaryList"), m_bFilled(false)
91 SvtPathOptions aPathOpt;
92 m_sPath = aPathOpt.GetAutoTextPath();
93 SetTimeout(GLOS_TIMEOUT);
96 SwGlossaryList::~SwGlossaryList()
98 ClearGroups();
101 // If the GroupName is already known, then only rShortName
102 // will be filled. Otherwise also rGroupName will be set and
103 // on demand asked for the right group.
105 bool SwGlossaryList::GetShortName(std::u16string_view rLongName,
106 OUString& rShortName, OUString& rGroupName )
108 if(!m_bFilled)
109 Update();
111 std::vector<TripleString> aTripleStrings;
113 size_t nCount = m_aGroupArr.size();
114 for(size_t i = 0; i < nCount; i++ )
116 AutoTextGroup* pGroup = m_aGroupArr[i].get();
117 if(!rGroupName.isEmpty() && rGroupName != pGroup->sName)
118 continue;
120 sal_Int32 nPosLong = 0;
121 for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
123 const OUString sLong = pGroup->sLongNames.getToken(0, STRING_DELIM, nPosLong);
124 if(rLongName != sLong)
125 continue;
127 TripleString aTriple;
128 aTriple.sGroup = pGroup->sName;
129 aTriple.sBlock = sLong;
130 aTriple.sShort = pGroup->sShortNames.getToken(j, STRING_DELIM);
131 aTripleStrings.push_back(aTriple);
135 bool bRet = false;
136 nCount = aTripleStrings.size();
137 if(1 == nCount)
139 const TripleString& rTriple(aTripleStrings.front());
140 rShortName = rTriple.sShort;
141 rGroupName = rTriple.sGroup;
142 bRet = true;
144 else if(1 < nCount)
146 SwView *pView = ::GetActiveView();
147 if (!pView)
148 return bRet;
149 SwGlossDecideDlg aDlg(pView->GetFrameWeld());
150 OUString sTitle = aDlg.get_title() + " " + aTripleStrings.front().sBlock;
151 aDlg.set_title(sTitle);
153 weld::TreeView& rLB = aDlg.GetTreeView();
154 for (const auto& rTriple : aTripleStrings)
155 rLB.append_text(rTriple.sGroup.getToken(0, GLOS_DELIM));
157 rLB.select(0);
158 if (aDlg.run() == RET_OK && rLB.get_selected_index() != -1)
160 const TripleString& rTriple(aTripleStrings[rLB.get_selected_index()]);
161 rShortName = rTriple.sShort;
162 rGroupName = rTriple.sGroup;
163 bRet = true;
165 else
166 bRet = false;
168 return bRet;
171 size_t SwGlossaryList::GetGroupCount()
173 if(!m_bFilled)
174 Update();
175 return m_aGroupArr.size();
178 OUString SwGlossaryList::GetGroupName(size_t nPos)
180 OSL_ENSURE(m_aGroupArr.size() > nPos, "group not available");
181 if(nPos < m_aGroupArr.size())
183 AutoTextGroup* pGroup = m_aGroupArr[nPos].get();
184 OUString sRet = pGroup->sName;
185 return sRet;
187 return OUString();
190 OUString SwGlossaryList::GetGroupTitle(size_t nPos)
192 OSL_ENSURE(m_aGroupArr.size() > nPos, "group not available");
193 if(nPos < m_aGroupArr.size())
195 AutoTextGroup* pGroup = m_aGroupArr[nPos].get();
196 return pGroup->sTitle;
198 return OUString();
201 sal_uInt16 SwGlossaryList::GetBlockCount(size_t nGroup)
203 OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available");
204 if(nGroup < m_aGroupArr.size())
206 AutoTextGroup* pGroup = m_aGroupArr[nGroup].get();
207 return pGroup->nCount;
209 return 0;
212 OUString SwGlossaryList::GetBlockLongName(size_t nGroup, sal_uInt16 nBlock)
214 OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available");
215 if(nGroup < m_aGroupArr.size())
217 AutoTextGroup* pGroup = m_aGroupArr[nGroup].get();
218 return pGroup->sLongNames.getToken(nBlock, STRING_DELIM);
220 return OUString();
223 OUString SwGlossaryList::GetBlockShortName(size_t nGroup, sal_uInt16 nBlock)
225 OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available");
226 if(nGroup < m_aGroupArr.size())
228 AutoTextGroup* pGroup = m_aGroupArr[nGroup].get();
229 return pGroup->sShortNames.getToken(nBlock, STRING_DELIM);
231 return OUString();
234 void SwGlossaryList::Update()
236 if(!IsActive())
237 Start();
239 SvtPathOptions aPathOpt;
240 const OUString& sTemp( aPathOpt.GetAutoTextPath() );
241 if(sTemp != m_sPath)
243 m_sPath = sTemp;
244 m_bFilled = false;
245 ClearGroups();
247 SwGlossaries* pGlossaries = ::GetGlossaries();
248 const std::vector<OUString> & rPathArr = pGlossaries->GetPathArray();
249 const OUString sExt( SwGlossaries::GetExtension() );
250 if(!m_bFilled)
252 const size_t nGroupCount = pGlossaries->GetGroupCnt();
253 for(size_t i = 0; i < nGroupCount; ++i)
255 OUString sGrpName = pGlossaries->GetGroupName(i);
256 const size_t nPath = static_cast<size_t>(
257 o3tl::toInt32(o3tl::getToken(sGrpName, 1, GLOS_DELIM)));
258 if( nPath < rPathArr.size() )
260 std::unique_ptr<AutoTextGroup> pGroup(new AutoTextGroup);
261 pGroup->sName = sGrpName;
263 FillGroup(pGroup.get(), pGlossaries);
264 OUString sName = rPathArr[nPath] + "/" +
265 o3tl::getToken(pGroup->sName, 0, GLOS_DELIM) + sExt;
266 FStatHelper::GetModifiedDateTimeOfFile( sName,
267 &pGroup->aDateModified,
268 &pGroup->aDateModified );
270 m_aGroupArr.insert( m_aGroupArr.begin(), std::move(pGroup) );
273 m_bFilled = true;
275 else
277 for( size_t nPath = 0; nPath < rPathArr.size(); nPath++ )
279 std::vector<OUString> aFoundGroupNames;
280 std::vector<OUString> aFiles;
281 std::vector<DateTime> aDateTimeArr;
283 SWUnoHelper::UCB_GetFileListOfFolder( rPathArr[nPath], aFiles,
284 &sExt, &aDateTimeArr );
285 for( size_t nFiles = 0; nFiles < aFiles.size(); ++nFiles )
287 const OUString aTitle = aFiles[ nFiles ];
288 ::DateTime& rDT = aDateTimeArr[ nFiles ];
290 OUString sName( aTitle.copy( 0, aTitle.getLength() - sExt.getLength() ));
292 aFoundGroupNames.push_back(sName);
293 sName += OUStringChar(GLOS_DELIM) + OUString::number( o3tl::narrowing<sal_uInt16>(nPath) );
294 AutoTextGroup* pFound = FindGroup( sName );
295 if( !pFound )
297 pFound = new AutoTextGroup;
298 pFound->sName = sName;
299 FillGroup( pFound, pGlossaries );
300 pFound->aDateModified = rDT;
302 m_aGroupArr.push_back(std::unique_ptr<AutoTextGroup>(pFound));
304 else if( pFound->aDateModified != rDT )
306 FillGroup(pFound, pGlossaries);
307 pFound->aDateModified = rDT;
311 for( size_t i = m_aGroupArr.size(); i>0; )
313 --i;
314 // maybe remove deleted groups
315 AutoTextGroup* pGroup = m_aGroupArr[i].get();
316 const size_t nGroupPath = static_cast<size_t>(
317 o3tl::toInt32(o3tl::getToken(pGroup->sName, 1, GLOS_DELIM)));
318 // Only the groups will be checked which are registered
319 // for the current subpath.
320 if( nGroupPath == nPath )
322 std::u16string_view sCompareGroup = o3tl::getToken(pGroup->sName, 0, GLOS_DELIM);
323 bool bFound = std::any_of(aFoundGroupNames.begin(), aFoundGroupNames.end(),
324 [&sCompareGroup](const OUString& rGroupName) { return sCompareGroup == rGroupName; });
326 if(!bFound)
328 m_aGroupArr.erase(m_aGroupArr.begin() + i);
336 void SwGlossaryList::Invoke()
338 // Only update automatically if a SwView has the focus.
339 if(::GetActiveView())
340 Update();
343 AutoTextGroup* SwGlossaryList::FindGroup(std::u16string_view rGroupName)
345 for(const auto & pRet : m_aGroupArr)
347 if(pRet->sName == rGroupName)
348 return pRet.get();
350 return nullptr;
353 void SwGlossaryList::FillGroup(AutoTextGroup* pGroup, SwGlossaries* pGlossaries)
355 std::unique_ptr<SwTextBlocks> pBlock = pGlossaries->GetGroupDoc(pGroup->sName);
356 pGroup->nCount = pBlock ? pBlock->GetCount() : 0;
357 pGroup->sLongNames.clear();
358 pGroup->sShortNames.clear();
359 if(pBlock)
360 pGroup->sTitle = pBlock->GetName();
362 for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
364 pGroup->sLongNames += pBlock->GetLongName(j)
365 + OUStringChar(STRING_DELIM);
366 pGroup->sShortNames += pBlock->GetShortName(j)
367 + OUStringChar(STRING_DELIM);
371 // Give back all (not exceeding FIND_MAX_GLOS) found modules
372 // with matching beginning.
374 void SwGlossaryList::HasLongName(const std::vector<OUString>& rBeginCandidates,
375 std::vector<std::pair<OUString, sal_uInt16>>& rLongNames)
377 if(!m_bFilled)
378 Update();
379 const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore();
380 // We store results for all candidate words in separate lists, so that later
381 // we can sort them according to the candidate position
382 std::vector<std::vector<OUString>> aResults(rBeginCandidates.size());
384 // We can't break after FIND_MAX_GLOS items found, since first items may have ended up in
385 // lower-priority lists, and those from higher-priority lists are yet to come. So process all.
386 for (const auto& pGroup : m_aGroupArr)
388 sal_Int32 nIdx{ 0 };
389 for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
391 OUString sBlock = pGroup->sLongNames.getToken(0, STRING_DELIM, nIdx);
392 for (size_t i = 0; i < rBeginCandidates.size(); ++i)
394 const OUString& s = rBeginCandidates[i];
395 if (s.getLength() + 1 < sBlock.getLength()
396 && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s))
398 aResults[i].push_back(sBlock);
404 std::vector<std::pair<OUString, sal_uInt16>> aAllResults;
405 // Sort and concatenate all result lists. See QuickHelpData::SortAndFilter
406 for (size_t i = 0; i < rBeginCandidates.size(); ++i)
408 std::sort(aResults[i].begin(), aResults[i].end(),
409 [origWord = rBeginCandidates[i]](const OUString& s1, const OUString& s2) {
410 int nRet = s1.compareToIgnoreAsciiCase(s2);
411 if (nRet == 0)
413 // fdo#61251 sort stuff that starts with the exact rOrigWord before
414 // another ignore-case candidate
415 int n1StartsWithOrig = s1.startsWith(origWord) ? 0 : 1;
416 int n2StartsWithOrig = s2.startsWith(origWord) ? 0 : 1;
417 return n1StartsWithOrig < n2StartsWithOrig;
419 return nRet < 0;
421 // All suggestions must be accompanied with length of the text they would replace
422 std::transform(aResults[i].begin(), aResults[i].end(), std::back_inserter(aAllResults),
423 [nLen = sal_uInt16(rBeginCandidates[i].getLength())](const OUString& s) {
424 return std::make_pair(s, nLen);
428 const auto& it = std::unique(
429 aAllResults.begin(), aAllResults.end(),
430 [](const std::pair<OUString, sal_uInt16>& s1, const std::pair<OUString, sal_uInt16>& s2) {
431 return s1.first.equalsIgnoreAsciiCase(s2.first);
433 if (const auto nCount = std::min<size_t>(std::distance(aAllResults.begin(), it), FIND_MAX_GLOS))
435 rLongNames.insert(rLongNames.end(), aAllResults.begin(), aAllResults.begin() + nCount);
439 void SwGlossaryList::ClearGroups()
441 m_aGroupArr.clear();
442 m_bFilled = false;
445 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */