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 
21 #include <rtl/strbuf.hxx>
22 #include <sal/log.hxx>
23 #include <svx/fmtools.hxx>
24 #include <svx/fmsrccfg.hxx>
25 #include <tools/debug.hxx>
26 #include <tools/diagnose_ex.h>
27 #include <tools/wldcrd.hxx>
28 #include <vcl/svapp.hxx>
29 #include <unotools/textsearch.hxx>
30 #include <com/sun/star/awt/XTextComponent.hpp>
31 #include <com/sun/star/awt/XListBox.hpp>
32 #include <com/sun/star/awt/XCheckBox.hpp>
33 #include <com/sun/star/container/XIndexAccess.hpp>
34 #include <com/sun/star/util/SearchAlgorithms2.hpp>
35 #include <com/sun/star/util/SearchResult.hpp>
36 #include <com/sun/star/util/SearchFlags.hpp>
37 #include <com/sun/star/lang/Locale.hpp>
38 #include <com/sun/star/i18n/CollatorOptions.hpp>
39 
40 #include <com/sun/star/sdb/XColumn.hpp>
41 #include <com/sun/star/sdbc/SQLException.hpp>
42 #include <com/sun/star/sdbc/XConnection.hpp>
43 #include <com/sun/star/sdbc/XDatabaseMetaData.hpp>
44 #include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
45 #include <com/sun/star/util/NumberFormatter.hpp>
46 #include <com/sun/star/util/NumberFormat.hpp>
47 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
48 #include <com/sun/star/util/XNumberFormats.hpp>
49 
50 #include <fmprop.hxx>
51 #include <fmservs.hxx>
52 #include <svx/fmsrcimp.hxx>
53 #include <svx/fmsearch.hxx>
54 
55 #include <comphelper/types.hxx>
56 #include <unotools/syslocale.hxx>
57 #include <i18nutil/searchopt.hxx>
58 
59 #define EQUAL_BOOKMARKS(a, b) a == b
60 
61 #define IFACECAST(c)          static_cast<const Reference< XInterface >&>(c)
62 
63 using namespace ::com::sun::star::uno;
64 using namespace ::com::sun::star::util;
65 using namespace ::com::sun::star::lang;
66 using namespace ::com::sun::star::sdbc;
67 using namespace ::com::sun::star::i18n;
68 using namespace ::com::sun::star::beans;
69 using namespace ::svxform;
70 
71 
72 // = FmRecordCountListener
73 
74 //  SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject);
75 
76 
FmRecordCountListener(const Reference<css::sdbc::XResultSet> & dbcCursor)77 FmRecordCountListener::FmRecordCountListener(const Reference< css::sdbc::XResultSet > & dbcCursor)
78 {
79 
80     m_xListening.set(dbcCursor, UNO_QUERY);
81     if (!m_xListening.is())
82         return;
83 
84     if (::comphelper::getBOOL(m_xListening->getPropertyValue(FM_PROP_ROWCOUNTFINAL)))
85     {
86         m_xListening = nullptr;
87         // there's nothing to do as the record count is already known
88         return;
89     }
90 
91     m_xListening->addPropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
92 }
93 
94 
SetPropChangeHandler(const Link<sal_Int32,void> & lnk)95 void FmRecordCountListener::SetPropChangeHandler(const Link<sal_Int32,void>& lnk)
96 {
97     m_lnkWhoWantsToKnow = lnk;
98 
99     if (m_xListening.is())
100         NotifyCurrentCount();
101 }
102 
103 
~FmRecordCountListener()104 FmRecordCountListener::~FmRecordCountListener()
105 {
106 
107 }
108 
109 
DisConnect()110 void FmRecordCountListener::DisConnect()
111 {
112     if(m_xListening.is())
113         m_xListening->removePropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
114     m_xListening = nullptr;
115 }
116 
117 
disposing(const css::lang::EventObject &)118 void SAL_CALL FmRecordCountListener::disposing(const css::lang::EventObject& /*Source*/)
119 {
120     DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::disposing should never have been called without a propset !");
121     DisConnect();
122 }
123 
124 
NotifyCurrentCount()125 void FmRecordCountListener::NotifyCurrentCount()
126 {
127     if (m_lnkWhoWantsToKnow.IsSet())
128     {
129         DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::NotifyCurrentCount : I have no propset ... !?");
130         sal_Int32 theCount = ::comphelper::getINT32(m_xListening->getPropertyValue(FM_PROP_ROWCOUNT));
131         m_lnkWhoWantsToKnow.Call(theCount);
132     }
133 }
134 
135 
propertyChange(const css::beans::PropertyChangeEvent &)136 void FmRecordCountListener::propertyChange(const  css::beans::PropertyChangeEvent& /*evt*/)
137 {
138     NotifyCurrentCount();
139 }
140 
141 
142 // FmSearchEngine - local classes
143 
SimpleTextWrapper(const Reference<css::awt::XTextComponent> & _xText)144 SimpleTextWrapper::SimpleTextWrapper(const Reference< css::awt::XTextComponent > & _xText)
145     :ControlTextWrapper(_xText.get())
146     ,m_xText(_xText)
147 {
148     DBG_ASSERT(m_xText.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !");
149 }
150 
151 
getCurrentText() const152 OUString SimpleTextWrapper::getCurrentText() const
153 {
154     return m_xText->getText();
155 }
156 
157 
ListBoxWrapper(const Reference<css::awt::XListBox> & _xBox)158 ListBoxWrapper::ListBoxWrapper(const Reference< css::awt::XListBox > & _xBox)
159     :ControlTextWrapper(_xBox.get())
160     ,m_xBox(_xBox)
161 {
162     DBG_ASSERT(m_xBox.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !");
163 }
164 
165 
getCurrentText() const166 OUString ListBoxWrapper::getCurrentText() const
167 {
168     return m_xBox->getSelectedItem();
169 }
170 
171 
CheckBoxWrapper(const Reference<css::awt::XCheckBox> & _xBox)172 CheckBoxWrapper::CheckBoxWrapper(const Reference< css::awt::XCheckBox > & _xBox)
173     :ControlTextWrapper(_xBox.get())
174     ,m_xBox(_xBox)
175 {
176     DBG_ASSERT(m_xBox.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !");
177 }
178 
179 
getCurrentText() const180 OUString CheckBoxWrapper::getCurrentText() const
181 {
182     switch (static_cast<TriState>(m_xBox->getState()))
183     {
184         case TRISTATE_FALSE: return "0";
185         case TRISTATE_TRUE: return "1";
186         default: break;
187     }
188     return OUString();
189 }
190 
191 
192 // = FmSearchEngine
193 
MoveCursor()194 bool FmSearchEngine::MoveCursor()
195 {
196     bool bSuccess = true;
197     try
198     {
199         if (m_bForward)
200             if (m_xSearchCursor.isLast())
201                 m_xSearchCursor.first();
202             else
203                 m_xSearchCursor.next();
204         else
205             if (m_xSearchCursor.isFirst())
206             {
207                 rtl::Reference<FmRecordCountListener> prclListener = new FmRecordCountListener(m_xSearchCursor);
208                 prclListener->SetPropChangeHandler(LINK(this, FmSearchEngine, OnNewRecordCount));
209 
210                 m_xSearchCursor.last();
211 
212                 prclListener->DisConnect();
213             }
214             else
215                 m_xSearchCursor.previous();
216     }
217     catch(Exception const&)
218     {
219         TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor");
220         bSuccess = false;
221     }
222     catch(...)
223     {
224         OSL_FAIL("FmSearchEngine::MoveCursor : caught an unknown Exception !");
225         bSuccess = false;
226     }
227 
228     return bSuccess;
229 }
230 
231 
MoveField(sal_Int32 & nPos,FieldCollection::iterator & iter,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)232 bool FmSearchEngine::MoveField(sal_Int32& nPos, FieldCollection::iterator& iter, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
233 {
234     bool bSuccess(true);
235     if (m_bForward)
236     {
237         ++iter;
238         ++nPos;
239         if (iter == iterEnd)
240         {
241             bSuccess = MoveCursor();
242             iter = iterBegin;
243             nPos = 0;
244         }
245     } else
246     {
247         if (iter == iterBegin)
248         {
249             bSuccess = MoveCursor();
250             iter = iterEnd;
251             nPos = iter-iterBegin;
252         }
253         --iter;
254         --nPos;
255     }
256     return bSuccess;
257 }
258 
259 
BuildAndInsertFieldInfo(const Reference<css::container::XIndexAccess> & xAllFields,sal_Int32 nField)260 void FmSearchEngine::BuildAndInsertFieldInfo(const Reference< css::container::XIndexAccess > & xAllFields, sal_Int32 nField)
261 {
262     DBG_ASSERT( xAllFields.is() && ( nField >= 0 ) && ( nField < xAllFields->getCount() ),
263         "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" );
264 
265     // the field itself
266     Reference< XInterface > xCurrentField;
267     xAllFields->getByIndex(nField) >>= xCurrentField;
268 
269     // From this I now know that it supports the DatabaseRecord service (I hope).
270     // For the FormatKey and the type I need the PropertySet.
271     Reference< css::beans::XPropertySet >  xProperties(xCurrentField, UNO_QUERY);
272 
273     // build the FieldInfo for that
274     FieldInfo fiCurrent;
275     fiCurrent.xContents.set(xCurrentField, UNO_QUERY);
276 
277     // and memorize
278     m_arrUsedFields.insert(m_arrUsedFields.end(), fiCurrent);
279 
280 }
281 
FormatField(sal_Int32 nWhich)282 OUString FmSearchEngine::FormatField(sal_Int32 nWhich)
283 {
284     DBG_ASSERT(static_cast<sal_uInt32>(nWhich) < m_aControlTexts.size(), "FmSearchEngine::FormatField(sal_Int32) : invalid position !");
285     DBG_ASSERT(m_aControlTexts[nWhich], "FmSearchEngine::FormatField(sal_Int32) : invalid object in array !");
286     DBG_ASSERT(m_aControlTexts[nWhich]->getControl().is(), "FmSearchEngine::FormatField : invalid control !");
287 
288     if (m_nCurrentFieldIndex != -1)
289     {
290         DBG_ASSERT((nWhich == 0) || (nWhich == m_nCurrentFieldIndex), "FmSearchEngine::FormatField : parameter nWhich is invalid");
291         // analogous situation as below
292         nWhich = m_nCurrentFieldIndex;
293     }
294 
295     DBG_ASSERT((nWhich >= 0) && (static_cast<sal_uInt32>(nWhich) < m_aControlTexts.size()),
296         "FmSearchEngine::FormatField : invalid argument nWhich !");
297     return m_aControlTexts[m_nCurrentFieldIndex == -1 ? nWhich : m_nCurrentFieldIndex]->getCurrentText();
298 }
299 
300 
SearchSpecial(bool _bSearchForNull,sal_Int32 & nFieldPos,FieldCollection::iterator & iterFieldLoop,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)301 FmSearchEngine::SearchResult FmSearchEngine::SearchSpecial(bool _bSearchForNull, sal_Int32& nFieldPos,
302     FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
303 {
304     // memorize the start position
305     Any aStartMark;
306     try { aStartMark = m_xSearchCursor.getBookmark(); }
307     catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
308     FieldCollection::const_iterator iterInitialField = iterFieldLoop;
309 
310 
311     bool bFound(false);
312     bool bMovedAround(false);
313     do
314     {
315         Application::Reschedule( true );
316 
317         // the content to be compared currently
318         iterFieldLoop->xContents->getString();  // needed for wasNull
319         bFound = _bSearchForNull == bool(iterFieldLoop->xContents->wasNull());
320         if (bFound)
321             break;
322 
323         // next field (implicitly next record, if necessary)
324         if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
325         {   // When moving to the next field, something went wrong...
326             // Continuing is not possible, since the next time exactly the same
327             // will definitely go wrong again, thus abort.
328             // Before, however, so that the search continues at the current position:
329             try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
330             catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
331             m_iterPreviousLocField = iterFieldLoop;
332             // and leave
333             return SearchResult::Error;
334         }
335 
336         Any aCurrentBookmark;
337         try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
338         catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
339 
340         bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
341 
342         if (nFieldPos == 0)
343             // that is, I've moved to a new record
344             PropagateProgress(bMovedAround);
345                 // if we moved to the starting position we don't have to propagate an 'overflow' message
346                 // FS - 07.12.99 - 68530
347 
348         // cancel requested?
349         if (CancelRequested())
350             return SearchResult::Cancelled;
351 
352     } while (!bMovedAround);
353 
354     return bFound ? SearchResult::Found : SearchResult::NotFound;
355 }
356 
357 
SearchWildcard(const OUString & strExpression,sal_Int32 & nFieldPos,FieldCollection::iterator & iterFieldLoop,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)358 FmSearchEngine::SearchResult FmSearchEngine::SearchWildcard(const OUString& strExpression, sal_Int32& nFieldPos,
359     FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
360 {
361     // memorize the start position
362     Any aStartMark;
363     try { aStartMark = m_xSearchCursor.getBookmark(); }
364     catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
365     FieldCollection::const_iterator iterInitialField = iterFieldLoop;
366 
367     WildCard aSearchExpression(strExpression);
368 
369 
370     bool bFound(false);
371     bool bMovedAround(false);
372     do
373     {
374         Application::Reschedule( true );
375 
376         // the content to be compared currently
377         OUString sCurrentCheck;
378         if (m_bFormatter)
379             sCurrentCheck = FormatField(nFieldPos);
380         else
381             sCurrentCheck = iterFieldLoop->xContents->getString();
382 
383         if (!GetCaseSensitive())
384             // norm the string
385             sCurrentCheck = m_aCharacterClassficator.lowercase(sCurrentCheck);
386 
387         // now the test is easy...
388         bFound = aSearchExpression.Matches(sCurrentCheck);
389 
390         if (bFound)
391             break;
392 
393         // next field (implicitly next record, if necessary)
394         if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
395         {   // When moving to the next field, something went wrong...
396             // Continuing is not possible, since the next time exactly the same
397             // will definitely go wrong again, thus abort.
398             // Before, however, so that the search continues at the current position:
399             try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
400             catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
401             m_iterPreviousLocField = iterFieldLoop;
402             // and leave
403             return SearchResult::Error;
404         }
405 
406         Any aCurrentBookmark;
407         try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
408         catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
409 
410         bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
411 
412         if (nFieldPos == 0)
413             // that is, I've moved to a new record
414             PropagateProgress(bMovedAround);
415                 // if we moved to the starting position we don't have to propagate an 'overflow' message
416                 // FS - 07.12.99 - 68530
417 
418         //  cancel requested?
419         if (CancelRequested())
420             return SearchResult::Cancelled;
421 
422     } while (!bMovedAround);
423 
424     return bFound ? SearchResult::Found : SearchResult::NotFound;
425 }
426 
427 
SearchRegularApprox(const OUString & strExpression,sal_Int32 & nFieldPos,FieldCollection::iterator & iterFieldLoop,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)428 FmSearchEngine::SearchResult FmSearchEngine::SearchRegularApprox(const OUString& strExpression, sal_Int32& nFieldPos,
429     FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
430 {
431     DBG_ASSERT(m_bLevenshtein || m_bRegular,
432         "FmSearchEngine::SearchRegularApprox : invalid search mode!");
433     DBG_ASSERT(!m_bLevenshtein || !m_bRegular,
434         "FmSearchEngine::SearchRegularApprox : cannot search for regular expressions and similarities at the same time!");
435 
436     // memorize start position
437     Any aStartMark;
438     try { aStartMark = m_xSearchCursor.getBookmark(); }
439     catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
440     FieldCollection::const_iterator iterInitialField = iterFieldLoop;
441 
442     // collect parameters
443     i18nutil::SearchOptions2 aParam;
444     aParam.AlgorithmType2 = m_bRegular ? SearchAlgorithms2::REGEXP : SearchAlgorithms2::APPROXIMATE;
445     aParam.searchFlag = 0;
446     aParam.transliterateFlags = GetTransliterationFlags();
447     if ( !GetTransliteration() )
448     {   // if transliteration is not enabled, the only flags which matter are IGNORE_CASE and IGNORE_WIDTH
449         aParam.transliterateFlags &= TransliterationFlags::IGNORE_CASE | TransliterationFlags::IGNORE_WIDTH;
450     }
451     if (m_bLevenshtein)
452     {
453         if (m_bLevRelaxed)
454             aParam.searchFlag |= SearchFlags::LEV_RELAXED;
455         aParam.changedChars = m_nLevOther;
456         aParam.deletedChars = m_nLevShorter;
457         aParam.insertedChars = m_nLevLonger;
458     }
459     aParam.searchString = strExpression;
460     aParam.Locale = SvtSysLocale().GetLanguageTag().getLocale();
461     ::utl::TextSearch aLocalEngine( aParam);
462 
463 
464     bool bFound = false;
465     bool bMovedAround(false);
466     do
467     {
468         Application::Reschedule( true );
469 
470         // the content to be compared currently
471         OUString sCurrentCheck;
472         if (m_bFormatter)
473             sCurrentCheck = FormatField(nFieldPos);
474         else
475             sCurrentCheck = iterFieldLoop->xContents->getString();
476 
477         // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it)
478 
479         sal_Int32 nStart = 0, nEnd = sCurrentCheck.getLength();
480         bFound = aLocalEngine.SearchForward(sCurrentCheck, &nStart, &nEnd);
481             // it says 'forward' here, but that only refers to the search within
482             // sCurrentCheck, so it has nothing to do with the direction of my
483             // record migration (MoveField takes care of that)
484 
485         // check if the position is correct
486         if (bFound)
487         {
488             switch (m_nPosition)
489             {
490                 case MATCHING_WHOLETEXT :
491                     if (nEnd != sCurrentCheck.getLength())
492                     {
493                         bFound = false;
494                         break;
495                     }
496                     [[fallthrough]];
497                 case MATCHING_BEGINNING :
498                     if (nStart != 0)
499                         bFound = false;
500                     break;
501                 case MATCHING_END :
502                     if (nEnd != sCurrentCheck.getLength())
503                         bFound = false;
504                     break;
505             }
506         }
507 
508         if (bFound) // still?
509             break;
510 
511         // next field (implicitly next record, if necessary)
512         if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
513         {   // When moving to the next field, something went wrong...
514             // Continuing is not possible, since the next time exactly the same
515             // will definitely go wrong again, thus abort (without error
516             // notification, I expect it to be displayed in the Move).
517             // Before, however, so that the search continues at the current position:
518             try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
519             catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
520             m_iterPreviousLocField = iterFieldLoop;
521             // and leave
522             return SearchResult::Error;
523         }
524 
525         Any aCurrentBookmark;
526         try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
527         catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
528         bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
529 
530         if (nFieldPos == 0)
531             // that is, I've moved to a new record
532             PropagateProgress(bMovedAround);
533                 // if we moved to the starting position we don't have to propagate an 'overflow' message
534                 // FS - 07.12.99 - 68530
535 
536         // cancel requested?
537         if (CancelRequested())
538             return SearchResult::Cancelled;
539 
540     } while (!bMovedAround);
541 
542     return bFound ? SearchResult::Found : SearchResult::NotFound;
543 }
544 
545 
FmSearchEngine(const Reference<XComponentContext> & _rxContext,const Reference<XResultSet> & xCursor,const OUString & sVisibleFields,const InterfaceArray & arrFields)546 FmSearchEngine::FmSearchEngine(const Reference< XComponentContext >& _rxContext,
547         const Reference< XResultSet > & xCursor, const OUString& sVisibleFields,
548         const InterfaceArray& arrFields)
549     :m_xSearchCursor(xCursor)
550     ,m_aCharacterClassficator( _rxContext, SvtSysLocale().GetLanguageTag() )
551     ,m_aStringCompare( _rxContext )
552     ,m_nCurrentFieldIndex(-2)   // -1 already has a meaning, so I take -2 for 'invalid'
553     ,m_xOriginalIterator(xCursor)
554     ,m_xClonedIterator(m_xOriginalIterator, true)
555     ,m_eSearchForType(SearchFor::String)
556     ,m_srResult(SearchResult::Found)
557     ,m_bSearchingCurrently(false)
558     ,m_bCancelAsynchRequest(false)
559     ,m_bFormatter(true)     // this must be consistent with m_xSearchCursor, which is generally == m_xOriginalIterator
560     ,m_bForward(false)
561     ,m_bWildcard(false)
562     ,m_bRegular(false)
563     ,m_bLevenshtein(false)
564     ,m_bTransliteration(false)
565     ,m_bLevRelaxed(false)
566     ,m_nLevOther(0)
567     ,m_nLevShorter(0)
568     ,m_nLevLonger(0)
569     ,m_nPosition(MATCHING_ANYWHERE)
570     ,m_nTransliterationFlags(TransliterationFlags::NONE)
571 {
572 
573     fillControlTexts(arrFields);
574     Init(sVisibleFields);
575 }
576 
577 
SetIgnoreWidthCJK(bool bSet)578 void FmSearchEngine::SetIgnoreWidthCJK(bool bSet)
579 {
580     if (bSet)
581         m_nTransliterationFlags |= TransliterationFlags::IGNORE_WIDTH;
582     else
583         m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_WIDTH;
584 }
585 
586 
GetIgnoreWidthCJK() const587 bool FmSearchEngine::GetIgnoreWidthCJK() const
588 {
589     return bool(m_nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH);
590 }
591 
592 
SetCaseSensitive(bool bSet)593 void FmSearchEngine::SetCaseSensitive(bool bSet)
594 {
595     if (bSet)
596         m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_CASE;
597     else
598         m_nTransliterationFlags |= TransliterationFlags::IGNORE_CASE;
599 }
600 
601 
GetCaseSensitive() const602 bool FmSearchEngine::GetCaseSensitive() const
603 {
604     return !(m_nTransliterationFlags & TransliterationFlags::IGNORE_CASE);
605 }
606 
607 
clearControlTexts()608 void FmSearchEngine::clearControlTexts()
609 {
610     m_aControlTexts.clear();
611 }
612 
613 
fillControlTexts(const InterfaceArray & arrFields)614 void FmSearchEngine::fillControlTexts(const InterfaceArray& arrFields)
615 {
616     clearControlTexts();
617     Reference< XInterface >  xCurrent;
618     for (const auto & rField : arrFields)
619     {
620         xCurrent = rField;
621         DBG_ASSERT(xCurrent.is(), "FmSearchEngine::fillControlTexts : invalid field interface !");
622         // check which type of control this is
623         Reference< css::awt::XTextComponent >  xAsText(xCurrent, UNO_QUERY);
624         if (xAsText.is())
625         {
626             m_aControlTexts.emplace_back(new SimpleTextWrapper(xAsText));
627             continue;
628         }
629 
630         Reference< css::awt::XListBox >  xAsListBox(xCurrent, UNO_QUERY);
631         if (xAsListBox.is())
632         {
633             m_aControlTexts.emplace_back(new ListBoxWrapper(xAsListBox));
634             continue;
635         }
636 
637         Reference< css::awt::XCheckBox >  xAsCheckBox(xCurrent, UNO_QUERY);
638         DBG_ASSERT(xAsCheckBox.is(), "FmSearchEngine::fillControlTexts : invalid field interface (no supported type) !");
639             // we don't have any more options ...
640         m_aControlTexts.emplace_back(new CheckBoxWrapper(xAsCheckBox));
641     }
642 }
643 
644 
Init(const OUString & sVisibleFields)645 void FmSearchEngine::Init(const OUString& sVisibleFields)
646 {
647     // analyze the fields
648     // additionally, create the mapping: because the list of used columns can be shorter than the list
649     // of columns of the cursor, we need a mapping: "used column number n" -> "cursor column m"
650     m_arrFieldMapping.clear();
651 
652     // important: The case of the columns does not need to be exact - for instance:
653     // - a user created a form which works on a table, for which the driver returns a column name "COLUMN"
654     // - the driver itself works case-insensitive with column names
655     // - a control in the form is bound to "column" - not the different case
656     // In such a scenario, the form and the field would work okay, but we here need to case for the different case
657     // explicitly
658     // #i8755#
659 
660     // so first of all, check if the database handles identifiers case sensitive
661     Reference< XConnection > xConn;
662     Reference< XDatabaseMetaData > xMeta;
663     Reference< XPropertySet > xCursorProps( IFACECAST( m_xSearchCursor ), UNO_QUERY );
664     if ( xCursorProps.is() )
665     {
666         try
667         {
668             xCursorProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ) >>= xConn;
669         }
670         catch( const Exception& ) { /* silent this - will be asserted below */ }
671     }
672     if ( xConn.is() )
673         xMeta = xConn->getMetaData();
674     OSL_ENSURE( xMeta.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" );
675 
676     bool bCaseSensitiveIdentifiers = true;  // assume case sensitivity
677     if ( xMeta.is() )
678         bCaseSensitiveIdentifiers = xMeta->supportsMixedCaseQuotedIdentifiers();
679 
680     // now that we have this information, we need a collator which is able to case (in)sensitivity compare strings
681     m_aStringCompare.loadDefaultCollator( SvtSysLocale().GetLanguageTag().getLocale(),
682         bCaseSensitiveIdentifiers ? 0 : css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
683 
684     try
685     {
686         // the cursor can give me a record (as PropertySet), which supports the DatabaseRecord service
687         Reference< css::sdbcx::XColumnsSupplier >  xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
688         DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::Init : invalid cursor (no columns supplier) !");
689         Reference< css::container::XNameAccess >       xAllFieldNames = xSupplyCols->getColumns();
690         Sequence< OUString > seqFieldNames = xAllFieldNames->getElementNames();
691 
692         OUString sCurrentField;
693         sal_Int32 nIndex = 0;
694         do
695         {
696             sCurrentField = sVisibleFields.getToken(0, ';' , nIndex);
697 
698             // search in the field collection
699             sal_Int32 nFoundIndex = -1;
700             auto pFieldName = std::find_if(seqFieldNames.begin(), seqFieldNames.end(),
701                 [this, &sCurrentField](const OUString& rFieldName) {
702                     return 0 == m_aStringCompare.compareString( rFieldName, sCurrentField ); });
703             if (pFieldName != seqFieldNames.end())
704                 nFoundIndex = static_cast<sal_Int32>(std::distance(seqFieldNames.begin(), pFieldName));
705             DBG_ASSERT(nFoundIndex != -1, "FmSearchEngine::Init : Invalid field name were given !");
706             m_arrFieldMapping.push_back(nFoundIndex);
707         }
708         while ( nIndex >= 0 );
709     }
710     catch (const Exception&)
711     {
712         OSL_FAIL("Exception occurred!");
713     }
714 
715 }
716 
717 
SetFormatterUsing(bool bSet)718 void FmSearchEngine::SetFormatterUsing(bool bSet)
719 {
720     if (m_bFormatter == bSet)
721         return;
722     m_bFormatter = bSet;
723 
724     // I did not use a formatter, but TextComponents -> the SearchIterator needs to be adjusted
725     try
726     {
727         if (m_bFormatter)
728         {
729             DBG_ASSERT(m_xSearchCursor == m_xClonedIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
730             m_xSearchCursor = m_xOriginalIterator;
731             m_xSearchCursor.moveToBookmark(m_xClonedIterator.getBookmark());
732                 // so that I continue with the new iterator at the actual place where I previously stopped
733         }
734         else
735         {
736             DBG_ASSERT(m_xSearchCursor == m_xOriginalIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
737             m_xSearchCursor = m_xClonedIterator;
738             m_xSearchCursor.moveToBookmark(m_xOriginalIterator.getBookmark());
739         }
740     }
741     catch( const Exception& )
742     {
743         DBG_UNHANDLED_EXCEPTION("svx");
744     }
745 
746     // I have to re-bind the fields, because the text exchange might take
747     // place over these fields and the underlying cursor has changed
748     RebuildUsedFields(m_nCurrentFieldIndex, true);
749 }
750 
751 
PropagateProgress(bool _bDontPropagateOverflow)752 void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow)
753 {
754     if (m_aProgressHandler.IsSet())
755     {
756         FmSearchProgress aProgress;
757         try
758         {
759             aProgress.aSearchState = FmSearchProgress::State::Progress;
760             aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
761             if (m_bForward)
762                 aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isFirst();
763             else
764                 aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isLast();
765         }
766         catch( const Exception& )
767         {
768             DBG_UNHANDLED_EXCEPTION("svx");
769         }
770 
771         m_aProgressHandler.Call(&aProgress);
772     }
773 }
774 
775 
SearchNextImpl()776 void FmSearchEngine::SearchNextImpl()
777 {
778     DBG_ASSERT(!(m_bWildcard && m_bRegular) && !(m_bRegular && m_bLevenshtein) && !(m_bLevenshtein && m_bWildcard),
779         "FmSearchEngine::SearchNextImpl : search parameters are mutually exclusive!");
780 
781     DBG_ASSERT(m_xSearchCursor.is(), "FmSearchEngine::SearchNextImpl : have invalid iterator!");
782 
783     // the parameters of the search
784     OUString strSearchExpression(m_strSearchExpression); // I need non-const
785     if (!GetCaseSensitive())
786         // norm the string
787         strSearchExpression = m_aCharacterClassficator.lowercase(strSearchExpression);
788 
789     if (!m_bRegular && !m_bLevenshtein)
790     {   // 'normal' search I run through WildCards in any case, but must before adjust the OUString depending on the mode
791 
792         if (!m_bWildcard)
793         {   // since in all other cases * and ? in the search string are of course
794             // also allowed, but should not count as WildCards, I need to normalize
795             OUString aTmp(strSearchExpression);
796             const OUString s_sStar("\\*");
797             const OUString s_sQuotation("\\?");
798             aTmp = aTmp.replaceAll("*", s_sStar);
799             aTmp = aTmp.replaceAll("?", s_sQuotation);
800             strSearchExpression = aTmp;
801 
802             switch (m_nPosition)
803             {
804                 case MATCHING_ANYWHERE :
805                     strSearchExpression = "*" + strSearchExpression + "*";
806                     break;
807                 case MATCHING_BEGINNING :
808                     strSearchExpression += "*";
809                     break;
810                 case MATCHING_END :
811                     strSearchExpression = "*" + strSearchExpression;
812                     break;
813                 case MATCHING_WHOLETEXT :
814                     break;
815                 default :
816                     OSL_FAIL("FmSearchEngine::SearchNextImpl() : the methods listbox may contain only 4 entries ...");
817             }
818         }
819     }
820 
821     // for work on field list
822     FieldCollection::iterator iterBegin = m_arrUsedFields.begin();
823     FieldCollection::iterator iterEnd = m_arrUsedFields.end();
824     FieldCollection::iterator iterFieldCheck;
825 
826     sal_Int32 nFieldPos;
827 
828     if (m_aPreviousLocBookmark.hasValue())
829     {
830         DBG_ASSERT(EQUAL_BOOKMARKS(m_aPreviousLocBookmark, m_xSearchCursor.getBookmark()),
831             "FmSearchEngine::SearchNextImpl : invalid position!");
832         iterFieldCheck = m_iterPreviousLocField;
833         // continue in the field after (or before) the last discovery
834         nFieldPos = iterFieldCheck - iterBegin;
835         MoveField(nFieldPos, iterFieldCheck, iterBegin, iterEnd);
836     }
837     else
838     {
839         if (m_bForward)
840             iterFieldCheck = iterBegin;
841         else
842         {
843             iterFieldCheck = iterEnd;
844             --iterFieldCheck;
845         }
846         nFieldPos = iterFieldCheck - iterBegin;
847     }
848 
849     PropagateProgress(true);
850     SearchResult srResult;
851     if (m_eSearchForType != SearchFor::String)
852         srResult = SearchSpecial(m_eSearchForType == SearchFor::Null, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
853     else if (!m_bRegular && !m_bLevenshtein)
854         srResult = SearchWildcard(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
855     else
856         srResult = SearchRegularApprox(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
857 
858     m_srResult = srResult;
859 
860     if (SearchResult::Error == m_srResult)
861         return;
862 
863     // found?
864     if (SearchResult::Found == m_srResult)
865     {
866         // memorize the position
867         try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
868         catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
869         m_iterPreviousLocField = iterFieldCheck;
870     }
871     else
872         // invalidate the "last discovery"
873         InvalidatePreviousLoc();
874 }
875 
876 
OnSearchTerminated()877 void FmSearchEngine::OnSearchTerminated()
878 {
879     if (!m_aProgressHandler.IsSet())
880         return;
881 
882     FmSearchProgress aProgress;
883     try
884     {
885         switch (m_srResult)
886         {
887             case SearchResult::Error :
888                 aProgress.aSearchState = FmSearchProgress::State::Error;
889                 break;
890             case SearchResult::Found :
891                 aProgress.aSearchState = FmSearchProgress::State::Successful;
892                 aProgress.aBookmark = m_aPreviousLocBookmark;
893                 aProgress.nFieldIndex = m_iterPreviousLocField - m_arrUsedFields.begin();
894                 break;
895             case SearchResult::NotFound :
896                 aProgress.aSearchState = FmSearchProgress::State::NothingFound;
897                 aProgress.aBookmark = m_xSearchCursor.getBookmark();
898                 break;
899             case SearchResult::Cancelled :
900                 aProgress.aSearchState = FmSearchProgress::State::Canceled;
901                 aProgress.aBookmark = m_xSearchCursor.getBookmark();
902                 break;
903         }
904         aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
905     }
906     catch( const Exception& )
907     {
908         DBG_UNHANDLED_EXCEPTION("svx");
909     }
910 
911     // by definition, the link must be thread-safe (I just require that),
912     // so that I do not have to worry about such things here
913     m_aProgressHandler.Call(&aProgress);
914 
915     m_bSearchingCurrently = false;
916 }
917 
918 
IMPL_LINK(FmSearchEngine,OnNewRecordCount,sal_Int32,theCounter,void)919 IMPL_LINK(FmSearchEngine, OnNewRecordCount, sal_Int32, theCounter, void)
920 {
921     if (!m_aProgressHandler.IsSet())
922         return;
923 
924     FmSearchProgress aProgress;
925     aProgress.nCurrentRecord = theCounter;
926     aProgress.aSearchState = FmSearchProgress::State::ProgressCounting;
927     m_aProgressHandler.Call(&aProgress);
928 }
929 
930 
CancelRequested()931 bool FmSearchEngine::CancelRequested()
932 {
933     m_aCancelAsynchAccess.acquire();
934     bool bReturn = m_bCancelAsynchRequest;
935     m_aCancelAsynchAccess.release();
936     return bReturn;
937 }
938 
939 
CancelSearch()940 void FmSearchEngine::CancelSearch()
941 {
942     m_aCancelAsynchAccess.acquire();
943     m_bCancelAsynchRequest = true;
944     m_aCancelAsynchAccess.release();
945 }
946 
947 
SwitchToContext(const Reference<css::sdbc::XResultSet> & xCursor,const OUString & sVisibleFields,const InterfaceArray & arrFields,sal_Int32 nFieldIndex)948 void FmSearchEngine::SwitchToContext(const Reference< css::sdbc::XResultSet > & xCursor, const OUString& sVisibleFields, const InterfaceArray& arrFields,
949     sal_Int32 nFieldIndex)
950 {
951     DBG_ASSERT(!m_bSearchingCurrently, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !");
952     if (m_bSearchingCurrently)
953         return;
954 
955     m_xSearchCursor = xCursor;
956     m_xOriginalIterator = xCursor;
957     m_xClonedIterator = CursorWrapper(m_xOriginalIterator, true);
958 
959     fillControlTexts(arrFields);
960 
961     Init(sVisibleFields);
962     RebuildUsedFields(nFieldIndex, true);
963 }
964 
965 
ImplStartNextSearch()966 void FmSearchEngine::ImplStartNextSearch()
967 {
968     m_bCancelAsynchRequest = false;
969     m_bSearchingCurrently = true;
970 
971     SearchNextImpl();
972     OnSearchTerminated();
973 }
974 
975 
SearchNext(const OUString & strExpression)976 void FmSearchEngine::SearchNext(const OUString& strExpression)
977 {
978     m_strSearchExpression = strExpression;
979     m_eSearchForType = SearchFor::String;
980     ImplStartNextSearch();
981 }
982 
983 
SearchNextSpecial(bool _bSearchForNull)984 void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull)
985 {
986     m_eSearchForType = _bSearchForNull ? SearchFor::Null : SearchFor::NotNull;
987     ImplStartNextSearch();
988 }
989 
990 
StartOver(const OUString & strExpression)991 void FmSearchEngine::StartOver(const OUString& strExpression)
992 {
993     try
994     {
995         if (m_bForward)
996             m_xSearchCursor.first();
997         else
998             m_xSearchCursor.last();
999     }
1000     catch( const Exception& )
1001     {
1002         DBG_UNHANDLED_EXCEPTION("svx");
1003         return;
1004     }
1005 
1006     InvalidatePreviousLoc();
1007     SearchNext(strExpression);
1008 }
1009 
1010 
StartOverSpecial(bool _bSearchForNull)1011 void FmSearchEngine::StartOverSpecial(bool _bSearchForNull)
1012 {
1013     try
1014     {
1015         if (m_bForward)
1016             m_xSearchCursor.first();
1017         else
1018             m_xSearchCursor.last();
1019     }
1020     catch( const Exception& )
1021     {
1022         DBG_UNHANDLED_EXCEPTION("svx");
1023         return;
1024     }
1025 
1026     InvalidatePreviousLoc();
1027     SearchNextSpecial(_bSearchForNull);
1028 }
1029 
1030 
InvalidatePreviousLoc()1031 void FmSearchEngine::InvalidatePreviousLoc()
1032 {
1033     m_aPreviousLocBookmark.clear();
1034     m_iterPreviousLocField = m_arrUsedFields.end();
1035 }
1036 
1037 
RebuildUsedFields(sal_Int32 nFieldIndex,bool bForce)1038 void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex, bool bForce)
1039 {
1040     if (!bForce && (nFieldIndex == m_nCurrentFieldIndex))
1041         return;
1042     // (since I allow no change of the iterator from the outside, the same css::sdbcx::Index
1043     // also always means the same column, so I have nothing to do)
1044 
1045     DBG_ASSERT((nFieldIndex == -1) ||
1046                ((nFieldIndex >= 0) &&
1047                 (static_cast<size_t>(nFieldIndex) < m_arrFieldMapping.size())),
1048             "FmSearchEngine::RebuildUsedFields : nFieldIndex is invalid!");
1049     // collect all fields I need to search through
1050     m_arrUsedFields.clear();
1051     if (nFieldIndex == -1)
1052     {
1053         Reference< css::container::XIndexAccess >  xFields;
1054         for (sal_Int32 i : m_arrFieldMapping)
1055         {
1056             Reference< css::sdbcx::XColumnsSupplier >  xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
1057             DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1058             xFields.set(xSupplyCols->getColumns(), UNO_QUERY);
1059             BuildAndInsertFieldInfo(xFields, i);
1060         }
1061     }
1062     else
1063     {
1064         Reference< css::container::XIndexAccess >  xFields;
1065         Reference< css::sdbcx::XColumnsSupplier >  xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
1066         DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1067         xFields.set (xSupplyCols->getColumns(), UNO_QUERY);
1068         BuildAndInsertFieldInfo(xFields, m_arrFieldMapping[static_cast< size_t >(nFieldIndex)]);
1069     }
1070 
1071     m_nCurrentFieldIndex = nFieldIndex;
1072     // and of course I start the next search in a virgin state again
1073     InvalidatePreviousLoc();
1074 }
1075 
1076 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1077