1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <tools/urlobj.hxx>
21 #include <hintids.hxx>
22 #include <acmplwrd.hxx>
24 #include <pagedesc.hxx>
25 #include <poolfmt.hxx>
26 #include <svl/listener.hxx>
27 #include <IDocumentStylePoolAccess.hxx>
28 #include <editeng/svxacorr.hxx>
29 #include <osl/diagnose.h>
31 #include <editeng/acorrcfg.hxx>
32 #include <sfx2/docfile.hxx>
38 class SwAutoCompleteClient
: public SvtListener
40 SwAutoCompleteWord
* m_pAutoCompleteWord
;
42 #if OSL_DEBUG_LEVEL > 0
43 static sal_uLong s_nSwAutoCompleteClientCount
;
46 SwAutoCompleteClient(SwAutoCompleteWord
& rToTell
, SwDoc
& rSwDoc
);
47 SwAutoCompleteClient(const SwAutoCompleteClient
& rClient
);
48 virtual ~SwAutoCompleteClient() override
;
50 SwAutoCompleteClient
& operator=(const SwAutoCompleteClient
& rClient
);
52 const SwDoc
& GetDoc() const {return *m_pDoc
;}
53 #if OSL_DEBUG_LEVEL > 0
54 static sal_uLong
GetElementCount() {return s_nSwAutoCompleteClientCount
;}
57 virtual void Notify(const SfxHint
&) override
;
62 class SwAutoCompleteWord_Impl
64 std::vector
<SwAutoCompleteClient
>
66 SwAutoCompleteWord
& m_rAutoCompleteWord
;
68 explicit SwAutoCompleteWord_Impl(SwAutoCompleteWord
& rParent
) :
69 m_rAutoCompleteWord(rParent
){}
70 void AddDocument(SwDoc
& rDoc
);
71 void RemoveDocument(const SwDoc
& rDoc
);
74 class SwAutoCompleteString
75 : public editeng::IAutoCompleteString
77 #if OSL_DEBUG_LEVEL > 0
78 static sal_uLong s_nSwAutoCompleteStringCount
;
80 std::vector
<const SwDoc
*> m_aSourceDocs
;
82 SwAutoCompleteString(const OUString
& rStr
, sal_Int32 nLen
);
84 virtual ~SwAutoCompleteString() override
;
85 void AddDocument(const SwDoc
& rDoc
);
86 //returns true if last document reference has been removed
87 bool RemoveDocument(const SwDoc
& rDoc
);
88 #if OSL_DEBUG_LEVEL > 0
89 static sal_uLong
GetElementCount() {return s_nSwAutoCompleteStringCount
;}
92 #if OSL_DEBUG_LEVEL > 0
93 sal_uLong
SwAutoCompleteClient::s_nSwAutoCompleteClientCount
= 0;
94 sal_uLong
SwAutoCompleteString::s_nSwAutoCompleteStringCount
= 0;
97 SwAutoCompleteClient::SwAutoCompleteClient(SwAutoCompleteWord
& rToTell
, SwDoc
& rSwDoc
) :
98 m_pAutoCompleteWord(&rToTell
),
101 StartListening(m_pDoc
->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD
)->GetNotifier());
102 #if OSL_DEBUG_LEVEL > 0
103 ++s_nSwAutoCompleteClientCount
;
107 SwAutoCompleteClient::SwAutoCompleteClient(const SwAutoCompleteClient
& rClient
) :
109 m_pAutoCompleteWord(rClient
.m_pAutoCompleteWord
),
110 m_pDoc(rClient
.m_pDoc
)
112 StartListening(m_pDoc
->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD
)->GetNotifier());
113 #if OSL_DEBUG_LEVEL > 0
114 ++s_nSwAutoCompleteClientCount
;
118 SwAutoCompleteClient::~SwAutoCompleteClient()
120 #if OSL_DEBUG_LEVEL > 0
121 --s_nSwAutoCompleteClientCount
;
127 SwAutoCompleteClient
& SwAutoCompleteClient::operator=(const SwAutoCompleteClient
& rClient
)
129 m_pAutoCompleteWord
= rClient
.m_pAutoCompleteWord
;
130 m_pDoc
= rClient
.m_pDoc
;
131 CopyAllBroadcasters(rClient
);
135 void SwAutoCompleteClient::DocumentDying()
138 m_pAutoCompleteWord
->DocumentDying(*m_pDoc
);
141 void SwAutoCompleteClient::Notify(const SfxHint
& rHint
)
143 switch(rHint
.GetId())
145 case SfxHintId::Dying
:
148 case SfxHintId::SwLegacyModify
:
150 auto pLegacy
= static_cast<const sw::LegacyModifyHint
*>(&rHint
);
151 switch(pLegacy
->GetWhich())
153 case RES_REMOVE_UNO_OBJECT
:
154 case RES_OBJECTDYING
:
166 void SwAutoCompleteWord_Impl::AddDocument(SwDoc
& rDoc
)
168 if (std::any_of(m_aClientVector
.begin(), m_aClientVector
.end(),
169 [&rDoc
](SwAutoCompleteClient
& rClient
) { return &rClient
.GetDoc() == &rDoc
; }))
171 m_aClientVector
.emplace_back(m_rAutoCompleteWord
, rDoc
);
174 void SwAutoCompleteWord_Impl::RemoveDocument(const SwDoc
& rDoc
)
176 auto aIt
= std::find_if(m_aClientVector
.begin(), m_aClientVector
.end(),
177 [&rDoc
](SwAutoCompleteClient
& rClient
) { return &rClient
.GetDoc() == &rDoc
; });
178 if (aIt
!= m_aClientVector
.end())
179 m_aClientVector
.erase(aIt
);
182 SwAutoCompleteString::SwAutoCompleteString(
183 const OUString
& rStr
, sal_Int32
const nLen
)
184 : editeng::IAutoCompleteString(rStr
.copy(0, nLen
))
186 #if OSL_DEBUG_LEVEL > 0
187 ++s_nSwAutoCompleteStringCount
;
191 SwAutoCompleteString::~SwAutoCompleteString()
193 #if OSL_DEBUG_LEVEL > 0
194 --s_nSwAutoCompleteStringCount
;
200 void SwAutoCompleteString::AddDocument(const SwDoc
& rDoc
)
202 auto aIt
= std::find(m_aSourceDocs
.begin(), m_aSourceDocs
.end(), &rDoc
);
203 if (aIt
!= m_aSourceDocs
.end())
205 m_aSourceDocs
.push_back(&rDoc
);
208 bool SwAutoCompleteString::RemoveDocument(const SwDoc
& rDoc
)
210 auto aIt
= std::find(m_aSourceDocs
.begin(), m_aSourceDocs
.end(), &rDoc
);
211 if (aIt
!= m_aSourceDocs
.end())
213 m_aSourceDocs
.erase(aIt
);
214 return m_aSourceDocs
.empty();
219 SwAutoCompleteWord::SwAutoCompleteWord(
220 editeng::SortedAutoCompleteStrings::size_type nWords
, sal_uInt16 nMWrdLen
):
221 m_pImpl(new SwAutoCompleteWord_Impl(*this)),
222 m_nMaxCount( nWords
),
223 m_nMinWordLen( nMWrdLen
),
224 m_bLockWordList( false )
228 SwAutoCompleteWord::~SwAutoCompleteWord()
230 m_WordList
.DeleteAndDestroyAll(); // so the assertion below works
231 #if OSL_DEBUG_LEVEL > 0
232 sal_uLong nStrings
= SwAutoCompleteString::GetElementCount();
233 sal_uLong nClients
= SwAutoCompleteClient::GetElementCount();
234 OSL_ENSURE(!nStrings
&& !nClients
, "AutoComplete: clients or string count mismatch");
238 bool SwAutoCompleteWord::InsertWord( const OUString
& rWord
, SwDoc
& rDoc
)
240 SwDocShell
* pDocShell
= rDoc
.GetDocShell();
241 SfxMedium
* pMedium
= pDocShell
? pDocShell
->GetMedium() : nullptr;
242 // strings from help module should not be added
245 const INetURLObject
& rURL
= pMedium
->GetURLObject();
246 if ( rURL
.GetProtocol() == INetProtocol::VndSunStarHelp
)
250 OUString aNewWord
= rWord
.replaceAll(OUStringChar(CH_TXTATR_INWORD
), "")
251 .replaceAll(OUStringChar(CH_TXTATR_BREAKWORD
), "");
253 m_pImpl
->AddDocument(rDoc
);
255 sal_Int32 nWrdLen
= aNewWord
.getLength();
256 while( nWrdLen
&& '.' == aNewWord
[ nWrdLen
-1 ])
259 if( !m_bLockWordList
&& nWrdLen
>= m_nMinWordLen
)
261 SwAutoCompleteString
* pNew
= new SwAutoCompleteString( aNewWord
, nWrdLen
);
262 pNew
->AddDocument(rDoc
);
263 std::pair
<editeng::SortedAutoCompleteStrings::const_iterator
, bool>
264 aInsPair
= m_WordList
.insert(pNew
);
266 m_LookupTree
.insert( aNewWord
.subView(0, nWrdLen
) );
271 if (m_aLRUList
.size() >= m_nMaxCount
)
273 // the last one needs to be removed
274 // so that there is space for the first one
275 SwAutoCompleteString
* pDel
= m_aLRUList
.back();
276 m_aLRUList
.pop_back();
277 m_WordList
.erase(pDel
);
280 m_aLRUList
.push_front(pNew
);
286 pNew
= static_cast<SwAutoCompleteString
*>(*aInsPair
.first
);
288 // add the document to the already inserted string
289 pNew
->AddDocument(rDoc
);
291 // move pNew to the front of the LRU list
292 SwAutoCompleteStringPtrDeque::iterator it
= std::find( m_aLRUList
.begin(), m_aLRUList
.end(), pNew
);
293 OSL_ENSURE( m_aLRUList
.end() != it
, "String not found" );
294 if ( m_aLRUList
.begin() != it
&& m_aLRUList
.end() != it
)
296 m_aLRUList
.erase( it
);
297 m_aLRUList
.push_front( pNew
);
304 void SwAutoCompleteWord::SetMaxCount(
305 editeng::SortedAutoCompleteStrings::size_type nNewMax
)
307 if( nNewMax
< m_nMaxCount
&& m_aLRUList
.size() > nNewMax
)
309 // remove the trailing ones
310 SwAutoCompleteStringPtrDeque::size_type nLRUIndex
= nNewMax
-1;
311 while (nNewMax
< m_WordList
.size() && nLRUIndex
< m_aLRUList
.size())
313 auto it
= m_WordList
.find(m_aLRUList
[nLRUIndex
++]);
314 OSL_ENSURE( m_WordList
.end() != it
, "String not found" );
315 editeng::IAutoCompleteString
*const pDel
= *it
;
316 m_WordList
.erase(it
);
319 m_aLRUList
.erase( m_aLRUList
.begin() + nNewMax
- 1, m_aLRUList
.end() );
321 m_nMaxCount
= nNewMax
;
324 void SwAutoCompleteWord::SetMinWordLen( sal_uInt16 n
)
326 // Do you really want to remove all words that are less than the minWrdLen?
327 if( n
< m_nMinWordLen
)
329 for (size_t nPos
= 0; nPos
< m_WordList
.size(); ++nPos
)
330 if (m_WordList
[ nPos
]->GetAutoCompleteString().getLength() < n
)
332 SwAutoCompleteString
*const pDel
=
333 dynamic_cast<SwAutoCompleteString
*>(m_WordList
[nPos
]);
334 m_WordList
.erase_at(nPos
);
336 SwAutoCompleteStringPtrDeque::iterator it
= std::find( m_aLRUList
.begin(), m_aLRUList
.end(), pDel
);
337 OSL_ENSURE( m_aLRUList
.end() != it
, "String not found" );
338 m_aLRUList
.erase( it
);
347 /** Return all words matching a given prefix
349 * @param aMatch the prefix to search for
350 * @param rWords the words found matching
352 bool SwAutoCompleteWord::GetWordsMatching(std::u16string_view aMatch
, std::vector
<OUString
>& rWords
) const
354 assert(rWords
.empty());
355 m_LookupTree
.findSuggestions(aMatch
, rWords
);
356 return !rWords
.empty();
359 void SwAutoCompleteWord::CheckChangedList(
360 const editeng::SortedAutoCompleteStrings
& rNewLst
)
362 size_t nMyLen
= m_WordList
.size(), nNewLen
= rNewLst
.size();
363 size_t nMyPos
= 0, nNewPos
= 0;
365 for( ; nMyPos
< nMyLen
&& nNewPos
< nNewLen
; ++nMyPos
, ++nNewPos
)
367 const editeng::IAutoCompleteString
* pStr
= rNewLst
[ nNewPos
];
368 while (m_WordList
[nMyPos
] != pStr
)
370 SwAutoCompleteString
*const pDel
=
371 dynamic_cast<SwAutoCompleteString
*>(m_WordList
[nMyPos
]);
372 m_WordList
.erase_at(nMyPos
);
373 SwAutoCompleteStringPtrDeque::iterator it
= std::find( m_aLRUList
.begin(), m_aLRUList
.end(), pDel
);
374 OSL_ENSURE( m_aLRUList
.end() != it
, "String not found" );
375 m_aLRUList
.erase( it
);
377 if( nMyPos
>= --nMyLen
)
381 // remove the elements at the end of the array
382 if( nMyPos
>= nMyLen
)
385 // clear LRU array first then delete the string object
386 for( ; nNewPos
< nMyLen
; ++nNewPos
)
388 SwAutoCompleteString
*const pDel
=
389 dynamic_cast<SwAutoCompleteString
*>(m_WordList
[nNewPos
]);
390 SwAutoCompleteStringPtrDeque::iterator it
= std::find( m_aLRUList
.begin(), m_aLRUList
.end(), pDel
);
391 OSL_ENSURE( m_aLRUList
.end() != it
, "String not found" );
392 m_aLRUList
.erase( it
);
396 m_WordList
.erase(m_WordList
.begin() + nMyPos
,
397 m_WordList
.begin() + nMyLen
);
400 void SwAutoCompleteWord::DocumentDying(const SwDoc
& rDoc
)
402 m_pImpl
->RemoveDocument(rDoc
);
404 SvxAutoCorrect
* pACorr
= SvxAutoCorrCfg::Get().GetAutoCorrect();
405 const bool bDelete
= !pACorr
->GetSwFlags().bAutoCmpltKeepList
;
406 for (size_t nPos
= m_WordList
.size(); nPos
; nPos
--)
408 SwAutoCompleteString
*const pCurrent
= dynamic_cast<SwAutoCompleteString
*>(m_WordList
[nPos
- 1]);
409 if(pCurrent
&& pCurrent
->RemoveDocument(rDoc
) && bDelete
)
411 m_WordList
.erase_at(nPos
- 1);
412 SwAutoCompleteStringPtrDeque::iterator it
= std::find( m_aLRUList
.begin(), m_aLRUList
.end(), pCurrent
);
413 OSL_ENSURE( m_aLRUList
.end() != it
, "word not found in LRU list" );
414 m_aLRUList
.erase( it
);
420 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */