Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include <comphelper/string.hxx>
21 : #include <tools/urlobj.hxx>
22 : #include <hintids.hxx>
23 : #include <hints.hxx>
24 : #include <unotools/transliterationwrapper.hxx>
25 : #include <acmplwrd.hxx>
26 : #include <doc.hxx>
27 : #include <ndindex.hxx>
28 : #include <docary.hxx>
29 : #include <ndtxt.hxx>
30 : #include <pam.hxx>
31 : #include <pagedesc.hxx>
32 : #include <poolfmt.hxx>
33 : #include <calbck.hxx>
34 : #include <editeng/svxacorr.hxx>
35 :
36 : #include <editeng/acorrcfg.hxx>
37 : #include <sfx2/docfile.hxx>
38 : #include <docsh.hxx>
39 :
40 : #include <vector>
41 :
42 : class SwAutoCompleteClient : public SwClient
43 : {
44 : SwAutoCompleteWord* pAutoCompleteWord;
45 : SwDoc* pDoc;
46 : #if OSL_DEBUG_LEVEL > 0
47 : static sal_uLong nSwAutoCompleteClientCount;
48 : #endif
49 : public:
50 : SwAutoCompleteClient(SwAutoCompleteWord& rToTell, SwDoc& rSwDoc);
51 : SwAutoCompleteClient(const SwAutoCompleteClient& rClient);
52 : ~SwAutoCompleteClient();
53 :
54 : SwAutoCompleteClient& operator=(const SwAutoCompleteClient& rClient);
55 :
56 3 : const SwDoc& GetDoc(){return *pDoc;}
57 : #if OSL_DEBUG_LEVEL > 0
58 : static sal_uLong GetElementCount() {return nSwAutoCompleteClientCount;}
59 : #endif
60 : protected:
61 : virtual void Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew);
62 : };
63 :
64 : typedef std::vector<SwAutoCompleteClient> SwAutoCompleteClientVector;
65 :
66 24 : class SwAutoCompleteWord_Impl
67 : {
68 : SwAutoCompleteClientVector aClientVector;
69 : SwAutoCompleteWord& rAutoCompleteWord;
70 : public:
71 24 : SwAutoCompleteWord_Impl(SwAutoCompleteWord& rParent) :
72 24 : rAutoCompleteWord(rParent){}
73 : void AddDocument(SwDoc& rDoc);
74 : void RemoveDocument(const SwDoc& rDoc);
75 : };
76 :
77 : typedef const SwDoc* SwDocPtr;
78 : typedef std::vector<SwDocPtr> SwDocPtrVector;
79 : class SwAutoCompleteString
80 : : public editeng::IAutoCompleteString
81 : {
82 : #if OSL_DEBUG_LEVEL > 0
83 : static sal_uLong nSwAutoCompleteStringCount;
84 : #endif
85 : SwDocPtrVector aSourceDocs;
86 : public:
87 : SwAutoCompleteString(const String& rStr, xub_StrLen nPos, xub_StrLen nLen);
88 :
89 : ~SwAutoCompleteString();
90 : void AddDocument(const SwDoc& rDoc);
91 : //returns true if last document reference has been removed
92 : bool RemoveDocument(const SwDoc& rDoc);
93 : #if OSL_DEBUG_LEVEL > 0
94 : static sal_uLong GetElementCount() {return nSwAutoCompleteStringCount;}
95 : #endif
96 : };
97 : #if OSL_DEBUG_LEVEL > 0
98 : sal_uLong SwAutoCompleteClient::nSwAutoCompleteClientCount = 0;
99 : sal_uLong SwAutoCompleteString::nSwAutoCompleteStringCount = 0;
100 : #endif
101 :
102 1 : SwAutoCompleteClient::SwAutoCompleteClient(SwAutoCompleteWord& rToTell, SwDoc& rSwDoc) :
103 : pAutoCompleteWord(&rToTell),
104 1 : pDoc(&rSwDoc)
105 : {
106 1 : pDoc->GetPageDescFromPool(RES_POOLPAGE_STANDARD)->Add(this);
107 : #if OSL_DEBUG_LEVEL > 0
108 : ++nSwAutoCompleteClientCount;
109 : #endif
110 1 : }
111 :
112 1 : SwAutoCompleteClient::SwAutoCompleteClient(const SwAutoCompleteClient& rClient) :
113 : SwClient(),
114 : pAutoCompleteWord(rClient.pAutoCompleteWord),
115 1 : pDoc(rClient.pDoc)
116 : {
117 1 : pDoc->GetPageDescFromPool(RES_POOLPAGE_STANDARD)->Add(this);
118 : #if OSL_DEBUG_LEVEL > 0
119 : ++nSwAutoCompleteClientCount;
120 : #endif
121 1 : }
122 :
123 2 : SwAutoCompleteClient::~SwAutoCompleteClient()
124 : {
125 : #if OSL_DEBUG_LEVEL > 0
126 : --nSwAutoCompleteClientCount;
127 : #endif
128 2 : }
129 :
130 0 : SwAutoCompleteClient& SwAutoCompleteClient::operator=(const SwAutoCompleteClient& rClient)
131 : {
132 0 : pAutoCompleteWord = rClient.pAutoCompleteWord;
133 0 : pDoc = rClient.pDoc;
134 0 : if(rClient.GetRegisteredIn())
135 0 : ((SwModify*)rClient.GetRegisteredIn())->Add(this);
136 0 : else if(GetRegisteredIn())
137 0 : GetRegisteredInNonConst()->Remove(this);
138 0 : return *this;
139 : }
140 :
141 1 : void SwAutoCompleteClient::Modify( const SfxPoolItem* pOld, const SfxPoolItem *)
142 : {
143 1 : switch( pOld ? pOld->Which() : 0 )
144 : {
145 : case RES_REMOVE_UNO_OBJECT:
146 : case RES_OBJECTDYING:
147 1 : if( (void*)GetRegisteredIn() == ((SwPtrMsgPoolItem *)pOld)->pObject )
148 1 : ((SwModify*)GetRegisteredIn())->Remove(this);
149 1 : pAutoCompleteWord->DocumentDying(*pDoc);
150 1 : break;
151 :
152 : }
153 1 : }
154 :
155 3 : void SwAutoCompleteWord_Impl::AddDocument(SwDoc& rDoc)
156 : {
157 3 : SwAutoCompleteClientVector::iterator aIt;
158 3 : for(aIt = aClientVector.begin(); aIt != aClientVector.end(); ++aIt)
159 : {
160 2 : if(&aIt->GetDoc() == &rDoc)
161 3 : return;
162 : }
163 1 : aClientVector.push_back(SwAutoCompleteClient(rAutoCompleteWord, rDoc));
164 : }
165 :
166 1 : void SwAutoCompleteWord_Impl::RemoveDocument(const SwDoc& rDoc)
167 : {
168 1 : SwAutoCompleteClientVector::iterator aIt;
169 1 : for(aIt = aClientVector.begin(); aIt != aClientVector.end(); ++aIt)
170 : {
171 1 : if(&aIt->GetDoc() == &rDoc)
172 : {
173 1 : aClientVector.erase(aIt);
174 1 : return;
175 : }
176 : }
177 : }
178 :
179 3 : SwAutoCompleteString::SwAutoCompleteString(
180 : const String& rStr, xub_StrLen const nPos, xub_StrLen const nLen)
181 3 : : editeng::IAutoCompleteString(String(rStr, nPos, nLen))
182 : {
183 : #if OSL_DEBUG_LEVEL > 0
184 : ++nSwAutoCompleteStringCount;
185 : #endif
186 3 : }
187 :
188 6 : SwAutoCompleteString::~SwAutoCompleteString()
189 : {
190 : #if OSL_DEBUG_LEVEL > 0
191 : --nSwAutoCompleteStringCount;
192 : #endif
193 6 : }
194 :
195 4 : void SwAutoCompleteString::AddDocument(const SwDoc& rDoc)
196 : {
197 4 : for(SwDocPtrVector::iterator aIt = aSourceDocs.begin(); aIt != aSourceDocs.end(); ++aIt)
198 : {
199 1 : if( *aIt == &rDoc )
200 4 : return;
201 : }
202 3 : aSourceDocs.push_back(&rDoc);
203 : }
204 :
205 2 : bool SwAutoCompleteString::RemoveDocument(const SwDoc& rDoc)
206 : {
207 2 : for(SwDocPtrVector::iterator aIt = aSourceDocs.begin(); aIt != aSourceDocs.end(); ++aIt)
208 : {
209 2 : if( *aIt == &rDoc )
210 : {
211 2 : aSourceDocs.erase(aIt);
212 2 : return aSourceDocs.empty();
213 : }
214 : }
215 0 : return false;
216 : }
217 :
218 24 : SwAutoCompleteWord::SwAutoCompleteWord( sal_uInt16 nWords, sal_uInt16 nMWrdLen ) :
219 24 : pImpl(new SwAutoCompleteWord_Impl(*this)),
220 : nMaxCount( nWords ),
221 : nMinWrdLen( nMWrdLen ),
222 48 : bLockWordLst( sal_False )
223 : {
224 24 : m_LookupTree = new LatinLookupTree(OUString("default"));
225 24 : }
226 :
227 48 : SwAutoCompleteWord::~SwAutoCompleteWord()
228 : {
229 24 : m_WordList.DeleteAndDestroyAll(); // so the assertion below works
230 24 : delete m_LookupTree;
231 24 : delete pImpl;
232 : #if OSL_DEBUG_LEVEL > 0
233 : sal_uLong nStrings = SwAutoCompleteString::GetElementCount();
234 : sal_uLong nClients = SwAutoCompleteClient::GetElementCount();
235 : OSL_ENSURE(!nStrings && !nClients, "AutoComplete: clients or string count mismatch");
236 : #endif
237 24 : }
238 :
239 3 : bool SwAutoCompleteWord::InsertWord( const String& rWord, SwDoc& rDoc )
240 : {
241 3 : SwDocShell* pDocShell = rDoc.GetDocShell();
242 3 : SfxMedium* pMedium = pDocShell ? pDocShell->GetMedium() : 0;
243 : // strings from help module should not be added
244 3 : if( pMedium )
245 : {
246 3 : const INetURLObject& rURL = pMedium->GetURLObject();
247 3 : if ( rURL.GetProtocol() == INET_PROT_VND_SUN_STAR_HELP )
248 0 : return sal_False;
249 : }
250 :
251 3 : String aNewWord(rWord);
252 3 : aNewWord = comphelper::string::remove(aNewWord, CH_TXTATR_INWORD);
253 3 : aNewWord = comphelper::string::remove(aNewWord, CH_TXTATR_BREAKWORD);
254 :
255 3 : pImpl->AddDocument(rDoc);
256 3 : bool bRet = false;
257 3 : xub_StrLen nWrdLen = aNewWord.Len();
258 6 : while( nWrdLen && '.' == aNewWord.GetChar( nWrdLen-1 ))
259 0 : --nWrdLen;
260 :
261 3 : if( !bLockWordLst && nWrdLen >= nMinWrdLen )
262 : {
263 3 : SwAutoCompleteString* pNew = new SwAutoCompleteString( aNewWord, 0, nWrdLen );
264 3 : pNew->AddDocument(rDoc);
265 : std::pair<editeng::SortedAutoCompleteStrings::const_iterator, bool>
266 3 : aInsPair = m_WordList.insert(pNew);
267 :
268 3 : m_LookupTree->insert( OUString(aNewWord) );
269 :
270 3 : if (aInsPair.second)
271 : {
272 2 : bRet = true;
273 2 : if (aLRULst.size() >= nMaxCount)
274 : {
275 : // the last one needs to be removed
276 : // so that there is space for the first one
277 0 : SwAutoCompleteString* pDel = aLRULst.back();
278 0 : aLRULst.pop_back();
279 0 : m_WordList.erase(pDel);
280 0 : delete pDel;
281 : }
282 2 : aLRULst.push_front(pNew);
283 : }
284 : else
285 : {
286 1 : delete pNew;
287 : // then move "up"
288 1 : pNew = (SwAutoCompleteString*)(*aInsPair.first);
289 :
290 : // add the document to the already inserted string
291 1 : pNew->AddDocument(rDoc);
292 :
293 : // move pNew to the front of the LRU list
294 1 : SwAutoCompleteStringPtrDeque::iterator it = std::find( aLRULst.begin(), aLRULst.end(), pNew );
295 : OSL_ENSURE( aLRULst.end() != it, "String not found" );
296 1 : if ( aLRULst.begin() != it && aLRULst.end() != it )
297 : {
298 0 : aLRULst.erase( it );
299 0 : aLRULst.push_front( pNew );
300 : }
301 : }
302 : }
303 3 : return bRet;
304 : }
305 :
306 0 : void SwAutoCompleteWord::SetMaxCount( sal_uInt16 nNewMax )
307 : {
308 0 : if( nNewMax < nMaxCount && aLRULst.size() > nNewMax )
309 : {
310 : // remove the trailing ones
311 0 : sal_uInt16 nLRUIndex = nNewMax-1;
312 0 : while (nNewMax < m_WordList.size() && nLRUIndex < aLRULst.size())
313 : {
314 : editeng::SortedAutoCompleteStrings::const_iterator it =
315 0 : m_WordList.find(aLRULst[ nLRUIndex++ ]);
316 : OSL_ENSURE( m_WordList.end() != it, "String not found" );
317 0 : editeng::IAutoCompleteString *const pDel = *it;
318 0 : m_WordList.erase(it - m_WordList.begin());
319 0 : delete pDel;
320 : }
321 0 : aLRULst.erase( aLRULst.begin() + nNewMax - 1, aLRULst.end() );
322 : }
323 0 : nMaxCount = nNewMax;
324 0 : }
325 :
326 0 : void SwAutoCompleteWord::SetMinWordLen( sal_uInt16 n )
327 : {
328 : // Do you really want to remove all words that are less than the minWrdLen?
329 0 : if( n < nMinWrdLen )
330 : {
331 0 : for (size_t nPos = 0; nPos < m_WordList.size(); ++nPos)
332 0 : if (m_WordList[ nPos ]->GetAutoCompleteString().Len() < n)
333 : {
334 : SwAutoCompleteString *const pDel =
335 0 : dynamic_cast<SwAutoCompleteString*>(m_WordList[nPos]);
336 0 : m_WordList.erase(nPos);
337 :
338 0 : SwAutoCompleteStringPtrDeque::iterator it = std::find( aLRULst.begin(), aLRULst.end(), pDel );
339 : OSL_ENSURE( aLRULst.end() != it, "String not found" );
340 0 : aLRULst.erase( it );
341 0 : --nPos;
342 0 : delete pDel;
343 : }
344 : }
345 :
346 0 : nMinWrdLen = n;
347 0 : }
348 :
349 : // Resets the current position within the tree to its root node.
350 0 : void SwAutoCompleteWord::returnToRoot()
351 : {
352 0 : m_LookupTree->returnToRoot();
353 0 : }
354 :
355 : // Advances to a given node within the AutoComplete tree.
356 0 : void SwAutoCompleteWord::gotoNode(OUString sNode)
357 : {
358 0 : m_LookupTree->gotoNode( sNode );
359 0 : }
360 :
361 : // Advances from the current position towards the node keyed with cKey.
362 0 : void SwAutoCompleteWord::advance(const sal_Unicode cKey)
363 : {
364 0 : m_LookupTree->advance( cKey );
365 0 : }
366 :
367 : // Goes back one char within the tree, except if the current node is already the root node.
368 0 : void SwAutoCompleteWord::goBack()
369 : {
370 0 : m_LookupTree->goBack();
371 0 : }
372 :
373 : // Returns all words matching a given prefix aMatch. If bIgnoreCurrentPos is set, the current
374 : // position within the tree is ignored and replaced by aMatch.
375 0 : bool SwAutoCompleteWord::GetWordsMatching(String aMatch, std::vector<String>& aWords, bool bIgnoreCurrentPos) const
376 : {
377 0 : OUString aStringRoot = OUString( aMatch );
378 :
379 : // The lookup tree already contains the information about the root keyword in most cases. Only if we don't trust that
380 : // information (e.g. if we need some autocompletion for a place other than the main writing area), the location within
381 : // the tree needs to be refreshed.
382 0 : if (bIgnoreCurrentPos)
383 : {
384 0 : m_LookupTree->gotoNode( aStringRoot );
385 : }
386 :
387 0 : OUString aAutocompleteWord = m_LookupTree->suggestAutoCompletion();
388 0 : if (aAutocompleteWord.isEmpty())
389 : {
390 0 : return false;
391 : }
392 :
393 0 : OUString aCompleteWord = aStringRoot + aAutocompleteWord;
394 0 : aWords.push_back( String(aCompleteWord) );
395 0 : return true;
396 : }
397 :
398 0 : void SwAutoCompleteWord::CheckChangedList(
399 : const editeng::SortedAutoCompleteStrings& rNewLst)
400 : {
401 0 : size_t nMyLen = m_WordList.size(), nNewLen = rNewLst.size();
402 0 : size_t nMyPos = 0, nNewPos = 0;
403 :
404 0 : for( ; nMyPos < nMyLen && nNewPos < nNewLen; ++nMyPos, ++nNewPos )
405 : {
406 0 : const editeng::IAutoCompleteString * pStr = rNewLst[ nNewPos ];
407 0 : while (m_WordList[nMyPos] != pStr)
408 : {
409 : SwAutoCompleteString *const pDel =
410 0 : dynamic_cast<SwAutoCompleteString*>(m_WordList[nMyPos]);
411 0 : m_WordList.erase(nMyPos);
412 0 : SwAutoCompleteStringPtrDeque::iterator it = std::find( aLRULst.begin(), aLRULst.end(), pDel );
413 : OSL_ENSURE( aLRULst.end() != it, "String not found" );
414 0 : aLRULst.erase( it );
415 0 : delete pDel;
416 0 : if( nMyPos >= --nMyLen )
417 : break;
418 : }
419 : }
420 : // remove the elements at the end of the array
421 0 : if( nMyPos < nMyLen )
422 : {
423 : // clear LRU array first then delete the string object
424 0 : for( ; nNewPos < nMyLen; ++nNewPos )
425 : {
426 : SwAutoCompleteString *const pDel =
427 0 : dynamic_cast<SwAutoCompleteString*>(m_WordList[nNewPos]);
428 0 : SwAutoCompleteStringPtrDeque::iterator it = std::find( aLRULst.begin(), aLRULst.end(), pDel );
429 : OSL_ENSURE( aLRULst.end() != it, "String not found" );
430 0 : aLRULst.erase( it );
431 0 : delete pDel;
432 : }
433 : // remove from array
434 0 : m_WordList.erase(m_WordList.begin() + nMyPos,
435 0 : m_WordList.begin() + nMyLen);
436 : }
437 0 : }
438 :
439 1 : void SwAutoCompleteWord::DocumentDying(const SwDoc& rDoc)
440 : {
441 1 : pImpl->RemoveDocument(rDoc);
442 :
443 1 : SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
444 1 : const sal_Bool bDelete = !pACorr->GetSwFlags().bAutoCmpltKeepList;
445 3 : for (size_t nPos = m_WordList.size(); nPos; nPos--)
446 : {
447 : SwAutoCompleteString *const pCurrent =
448 2 : dynamic_cast<SwAutoCompleteString*>(m_WordList[nPos - 1]);
449 2 : if(pCurrent->RemoveDocument(rDoc) && bDelete)
450 : {
451 0 : m_WordList.erase(nPos - 1);
452 0 : SwAutoCompleteStringPtrDeque::iterator it = std::find( aLRULst.begin(), aLRULst.end(), pCurrent );
453 : OSL_ENSURE( aLRULst.end() != it, "word not found in LRU list" );
454 0 : aLRULst.erase( it );
455 0 : delete pCurrent;
456 : }
457 : }
458 1 : }
459 :
460 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|