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 .
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>
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>
51 #include <fmservs.hxx>
52 #include <svx/fmsrcimp.hxx>
53 #include <svx/fmsearch.hxx>
55 #include <comphelper/types.hxx>
56 #include <unotools/syslocale.hxx>
57 #include <i18nutil/searchopt.hxx>
59 #define EQUAL_BOOKMARKS(a, b) a == b
61 #define IFACECAST(c) static_cast<const Reference< XInterface >&>(c)
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
;
72 // = FmRecordCountListener
74 // SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject);
77 FmRecordCountListener::FmRecordCountListener(const Reference
< css::sdbc::XResultSet
> & dbcCursor
)
80 m_xListening
.set(dbcCursor
, UNO_QUERY
);
81 if (!m_xListening
.is())
84 if (::comphelper::getBOOL(m_xListening
->getPropertyValue(FM_PROP_ROWCOUNTFINAL
)))
86 m_xListening
= nullptr;
87 // there's nothing to do as the record count is already known
91 m_xListening
->addPropertyChangeListener(FM_PROP_ROWCOUNT
, static_cast<css::beans::XPropertyChangeListener
*>(this));
95 void FmRecordCountListener::SetPropChangeHandler(const Link
<sal_Int32
,void>& lnk
)
97 m_lnkWhoWantsToKnow
= lnk
;
99 if (m_xListening
.is())
100 NotifyCurrentCount();
104 FmRecordCountListener::~FmRecordCountListener()
110 void FmRecordCountListener::DisConnect()
112 if(m_xListening
.is())
113 m_xListening
->removePropertyChangeListener(FM_PROP_ROWCOUNT
, static_cast<css::beans::XPropertyChangeListener
*>(this));
114 m_xListening
= nullptr;
118 void SAL_CALL
FmRecordCountListener::disposing(const css::lang::EventObject
& /*Source*/)
120 DBG_ASSERT(m_xListening
.is(), "FmRecordCountListener::disposing should never have been called without a propset !");
125 void FmRecordCountListener::NotifyCurrentCount()
127 if (m_lnkWhoWantsToKnow
.IsSet())
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
);
136 void FmRecordCountListener::propertyChange(const css::beans::PropertyChangeEvent
& /*evt*/)
138 NotifyCurrentCount();
142 // FmSearchEngine - local classes
144 SimpleTextWrapper::SimpleTextWrapper(const Reference
< css::awt::XTextComponent
> & _xText
)
145 :ControlTextWrapper(_xText
.get())
148 DBG_ASSERT(m_xText
.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !");
152 OUString
SimpleTextWrapper::getCurrentText() const
154 return m_xText
->getText();
158 ListBoxWrapper::ListBoxWrapper(const Reference
< css::awt::XListBox
> & _xBox
)
159 :ControlTextWrapper(_xBox
.get())
162 DBG_ASSERT(m_xBox
.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !");
166 OUString
ListBoxWrapper::getCurrentText() const
168 return m_xBox
->getSelectedItem();
172 CheckBoxWrapper::CheckBoxWrapper(const Reference
< css::awt::XCheckBox
> & _xBox
)
173 :ControlTextWrapper(_xBox
.get())
176 DBG_ASSERT(m_xBox
.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !");
180 OUString
CheckBoxWrapper::getCurrentText() const
182 switch (static_cast<TriState
>(m_xBox
->getState()))
184 case TRISTATE_FALSE
: return "0";
185 case TRISTATE_TRUE
: return "1";
194 bool FmSearchEngine::MoveCursor()
196 bool bSuccess
= true;
200 if (m_xSearchCursor
.isLast())
201 m_xSearchCursor
.first();
203 m_xSearchCursor
.next();
205 if (m_xSearchCursor
.isFirst())
207 rtl::Reference
<FmRecordCountListener
> prclListener
= new FmRecordCountListener(m_xSearchCursor
);
208 prclListener
->SetPropChangeHandler(LINK(this, FmSearchEngine
, OnNewRecordCount
));
210 m_xSearchCursor
.last();
212 prclListener
->DisConnect();
215 m_xSearchCursor
.previous();
217 catch(Exception
const&)
219 TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor");
224 OSL_FAIL("FmSearchEngine::MoveCursor : caught an unknown Exception !");
232 bool FmSearchEngine::MoveField(sal_Int32
& nPos
, FieldCollection::iterator
& iter
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
241 bSuccess
= MoveCursor();
247 if (iter
== iterBegin
)
249 bSuccess
= MoveCursor();
251 nPos
= iter
-iterBegin
;
260 void FmSearchEngine::BuildAndInsertFieldInfo(const Reference
< css::container::XIndexAccess
> & xAllFields
, sal_Int32 nField
)
262 DBG_ASSERT( xAllFields
.is() && ( nField
>= 0 ) && ( nField
< xAllFields
->getCount() ),
263 "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" );
266 Reference
< XInterface
> xCurrentField
;
267 xAllFields
->getByIndex(nField
) >>= xCurrentField
;
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
);
273 // build the FieldInfo for that
275 fiCurrent
.xContents
.set(xCurrentField
, UNO_QUERY
);
278 m_arrUsedFields
.insert(m_arrUsedFields
.end(), fiCurrent
);
282 OUString
FmSearchEngine::FormatField(sal_Int32 nWhich
)
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 !");
288 if (m_nCurrentFieldIndex
!= -1)
290 DBG_ASSERT((nWhich
== 0) || (nWhich
== m_nCurrentFieldIndex
), "FmSearchEngine::FormatField : parameter nWhich is invalid");
291 // analogous situation as below
292 nWhich
= m_nCurrentFieldIndex
;
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();
301 FmSearchEngine::SearchResult
FmSearchEngine::SearchSpecial(bool _bSearchForNull
, sal_Int32
& nFieldPos
,
302 FieldCollection::iterator
& iterFieldLoop
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
304 // memorize the start position
306 try { aStartMark
= m_xSearchCursor
.getBookmark(); }
307 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error
; }
308 FieldCollection::const_iterator iterInitialField
= iterFieldLoop
;
312 bool bMovedAround(false);
315 Application::Reschedule( true );
317 // the content to be compared currently
318 iterFieldLoop
->xContents
->getString(); // needed for wasNull
319 bFound
= _bSearchForNull
== bool(iterFieldLoop
->xContents
->wasNull());
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
;
333 return SearchResult::Error
;
336 Any aCurrentBookmark
;
337 try { aCurrentBookmark
= m_xSearchCursor
.getBookmark(); }
338 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error
; }
340 bMovedAround
= EQUAL_BOOKMARKS(aStartMark
, aCurrentBookmark
) && (iterFieldLoop
== iterInitialField
);
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
349 if (CancelRequested())
350 return SearchResult::Cancelled
;
352 } while (!bMovedAround
);
354 return bFound
? SearchResult::Found
: SearchResult::NotFound
;
358 FmSearchEngine::SearchResult
FmSearchEngine::SearchWildcard(const OUString
& strExpression
, sal_Int32
& nFieldPos
,
359 FieldCollection::iterator
& iterFieldLoop
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
361 // memorize the start position
363 try { aStartMark
= m_xSearchCursor
.getBookmark(); }
364 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error
; }
365 FieldCollection::const_iterator iterInitialField
= iterFieldLoop
;
367 WildCard
aSearchExpression(strExpression
);
371 bool bMovedAround(false);
374 Application::Reschedule( true );
376 // the content to be compared currently
377 OUString sCurrentCheck
;
379 sCurrentCheck
= FormatField(nFieldPos
);
381 sCurrentCheck
= iterFieldLoop
->xContents
->getString();
383 if (!GetCaseSensitive())
385 sCurrentCheck
= m_aCharacterClassficator
.lowercase(sCurrentCheck
);
387 // now the test is easy...
388 bFound
= aSearchExpression
.Matches(sCurrentCheck
);
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
;
403 return SearchResult::Error
;
406 Any aCurrentBookmark
;
407 try { aCurrentBookmark
= m_xSearchCursor
.getBookmark(); }
408 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error
; }
410 bMovedAround
= EQUAL_BOOKMARKS(aStartMark
, aCurrentBookmark
) && (iterFieldLoop
== iterInitialField
);
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
419 if (CancelRequested())
420 return SearchResult::Cancelled
;
422 } while (!bMovedAround
);
424 return bFound
? SearchResult::Found
: SearchResult::NotFound
;
428 FmSearchEngine::SearchResult
FmSearchEngine::SearchRegularApprox(const OUString
& strExpression
, sal_Int32
& nFieldPos
,
429 FieldCollection::iterator
& iterFieldLoop
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
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!");
436 // memorize start position
438 try { aStartMark
= m_xSearchCursor
.getBookmark(); }
439 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error
; }
440 FieldCollection::const_iterator iterInitialField
= iterFieldLoop
;
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
;
454 aParam
.searchFlag
|= SearchFlags::LEV_RELAXED
;
455 aParam
.changedChars
= m_nLevOther
;
456 aParam
.deletedChars
= m_nLevShorter
;
457 aParam
.insertedChars
= m_nLevLonger
;
459 aParam
.searchString
= strExpression
;
460 aParam
.Locale
= SvtSysLocale().GetLanguageTag().getLocale();
461 ::utl::TextSearch
aLocalEngine( aParam
);
465 bool bMovedAround(false);
468 Application::Reschedule( true );
470 // the content to be compared currently
471 OUString sCurrentCheck
;
473 sCurrentCheck
= FormatField(nFieldPos
);
475 sCurrentCheck
= iterFieldLoop
->xContents
->getString();
477 // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it)
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)
485 // check if the position is correct
490 case MATCHING_WHOLETEXT
:
491 if (nEnd
!= sCurrentCheck
.getLength())
497 case MATCHING_BEGINNING
:
502 if (nEnd
!= sCurrentCheck
.getLength())
508 if (bFound
) // still?
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
;
522 return SearchResult::Error
;
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
);
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
537 if (CancelRequested())
538 return SearchResult::Cancelled
;
540 } while (!bMovedAround
);
542 return bFound
? SearchResult::Found
: SearchResult::NotFound
;
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
563 ,m_bLevenshtein(false)
564 ,m_bTransliteration(false)
565 ,m_bLevRelaxed(false)
569 ,m_nPosition(MATCHING_ANYWHERE
)
570 ,m_nTransliterationFlags(TransliterationFlags::NONE
)
573 fillControlTexts(arrFields
);
574 Init(sVisibleFields
);
578 void FmSearchEngine::SetIgnoreWidthCJK(bool bSet
)
581 m_nTransliterationFlags
|= TransliterationFlags::IGNORE_WIDTH
;
583 m_nTransliterationFlags
&= ~TransliterationFlags::IGNORE_WIDTH
;
587 bool FmSearchEngine::GetIgnoreWidthCJK() const
589 return bool(m_nTransliterationFlags
& TransliterationFlags::IGNORE_WIDTH
);
593 void FmSearchEngine::SetCaseSensitive(bool bSet
)
596 m_nTransliterationFlags
&= ~TransliterationFlags::IGNORE_CASE
;
598 m_nTransliterationFlags
|= TransliterationFlags::IGNORE_CASE
;
602 bool FmSearchEngine::GetCaseSensitive() const
604 return !(m_nTransliterationFlags
& TransliterationFlags::IGNORE_CASE
);
608 void FmSearchEngine::clearControlTexts()
610 m_aControlTexts
.clear();
614 void FmSearchEngine::fillControlTexts(const InterfaceArray
& arrFields
)
617 Reference
< XInterface
> xCurrent
;
618 for (const auto & rField
: arrFields
)
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
);
626 m_aControlTexts
.emplace_back(new SimpleTextWrapper(xAsText
));
630 Reference
< css::awt::XListBox
> xAsListBox(xCurrent
, UNO_QUERY
);
633 m_aControlTexts
.emplace_back(new ListBoxWrapper(xAsListBox
));
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
));
645 void FmSearchEngine::Init(const OUString
& sVisibleFields
)
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();
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
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() )
668 xCursorProps
->getPropertyValue( FM_PROP_ACTIVE_CONNECTION
) >>= xConn
;
670 catch( const Exception
& ) { /* silent this - will be asserted below */ }
673 xMeta
= xConn
->getMetaData();
674 OSL_ENSURE( xMeta
.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" );
676 bool bCaseSensitiveIdentifiers
= true; // assume case sensitivity
678 bCaseSensitiveIdentifiers
= xMeta
->supportsMixedCaseQuotedIdentifiers();
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
);
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();
692 OUString sCurrentField
;
693 sal_Int32 nIndex
= 0;
696 sCurrentField
= sVisibleFields
.getToken(0, ';' , nIndex
);
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
);
708 while ( nIndex
>= 0 );
710 catch (const Exception
&)
712 OSL_FAIL("Exception occurred!");
718 void FmSearchEngine::SetFormatterUsing(bool bSet
)
720 if (m_bFormatter
== bSet
)
724 // I did not use a formatter, but TextComponents -> the SearchIterator needs to be adjusted
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
736 DBG_ASSERT(m_xSearchCursor
== m_xOriginalIterator
, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
737 m_xSearchCursor
= m_xClonedIterator
;
738 m_xSearchCursor
.moveToBookmark(m_xOriginalIterator
.getBookmark());
741 catch( const Exception
& )
743 DBG_UNHANDLED_EXCEPTION("svx");
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);
752 void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow
)
754 if (m_aProgressHandler
.IsSet())
756 FmSearchProgress aProgress
;
759 aProgress
.aSearchState
= FmSearchProgress::State::Progress
;
760 aProgress
.nCurrentRecord
= m_xSearchCursor
.getRow() - 1;
762 aProgress
.bOverflow
= !_bDontPropagateOverflow
&& m_xSearchCursor
.isFirst();
764 aProgress
.bOverflow
= !_bDontPropagateOverflow
&& m_xSearchCursor
.isLast();
766 catch( const Exception
& )
768 DBG_UNHANDLED_EXCEPTION("svx");
771 m_aProgressHandler
.Call(&aProgress
);
776 void FmSearchEngine::SearchNextImpl()
778 DBG_ASSERT(!(m_bWildcard
&& m_bRegular
) && !(m_bRegular
&& m_bLevenshtein
) && !(m_bLevenshtein
&& m_bWildcard
),
779 "FmSearchEngine::SearchNextImpl : search parameters are mutually exclusive!");
781 DBG_ASSERT(m_xSearchCursor
.is(), "FmSearchEngine::SearchNextImpl : have invalid iterator!");
783 // the parameters of the search
784 OUString
strSearchExpression(m_strSearchExpression
); // I need non-const
785 if (!GetCaseSensitive())
787 strSearchExpression
= m_aCharacterClassficator
.lowercase(strSearchExpression
);
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
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
;
804 case MATCHING_ANYWHERE
:
805 strSearchExpression
= "*" + strSearchExpression
+ "*";
807 case MATCHING_BEGINNING
:
808 strSearchExpression
+= "*";
811 strSearchExpression
= "*" + strSearchExpression
;
813 case MATCHING_WHOLETEXT
:
816 OSL_FAIL("FmSearchEngine::SearchNextImpl() : the methods listbox may contain only 4 entries ...");
821 // for work on field list
822 FieldCollection::iterator iterBegin
= m_arrUsedFields
.begin();
823 FieldCollection::iterator iterEnd
= m_arrUsedFields
.end();
824 FieldCollection::iterator iterFieldCheck
;
828 if (m_aPreviousLocBookmark
.hasValue())
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
);
840 iterFieldCheck
= iterBegin
;
843 iterFieldCheck
= iterEnd
;
846 nFieldPos
= iterFieldCheck
- iterBegin
;
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
);
856 srResult
= SearchRegularApprox(strSearchExpression
, nFieldPos
, iterFieldCheck
, iterBegin
, iterEnd
);
858 m_srResult
= srResult
;
860 if (SearchResult::Error
== m_srResult
)
864 if (SearchResult::Found
== m_srResult
)
866 // memorize the position
867 try { m_aPreviousLocBookmark
= m_xSearchCursor
.getBookmark(); }
868 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
869 m_iterPreviousLocField
= iterFieldCheck
;
872 // invalidate the "last discovery"
873 InvalidatePreviousLoc();
877 void FmSearchEngine::OnSearchTerminated()
879 if (!m_aProgressHandler
.IsSet())
882 FmSearchProgress aProgress
;
887 case SearchResult::Error
:
888 aProgress
.aSearchState
= FmSearchProgress::State::Error
;
890 case SearchResult::Found
:
891 aProgress
.aSearchState
= FmSearchProgress::State::Successful
;
892 aProgress
.aBookmark
= m_aPreviousLocBookmark
;
893 aProgress
.nFieldIndex
= m_iterPreviousLocField
- m_arrUsedFields
.begin();
895 case SearchResult::NotFound
:
896 aProgress
.aSearchState
= FmSearchProgress::State::NothingFound
;
897 aProgress
.aBookmark
= m_xSearchCursor
.getBookmark();
899 case SearchResult::Cancelled
:
900 aProgress
.aSearchState
= FmSearchProgress::State::Canceled
;
901 aProgress
.aBookmark
= m_xSearchCursor
.getBookmark();
904 aProgress
.nCurrentRecord
= m_xSearchCursor
.getRow() - 1;
906 catch( const Exception
& )
908 DBG_UNHANDLED_EXCEPTION("svx");
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
);
915 m_bSearchingCurrently
= false;
919 IMPL_LINK(FmSearchEngine
, OnNewRecordCount
, sal_Int32
, theCounter
, void)
921 if (!m_aProgressHandler
.IsSet())
924 FmSearchProgress aProgress
;
925 aProgress
.nCurrentRecord
= theCounter
;
926 aProgress
.aSearchState
= FmSearchProgress::State::ProgressCounting
;
927 m_aProgressHandler
.Call(&aProgress
);
931 bool FmSearchEngine::CancelRequested()
933 m_aCancelAsynchAccess
.acquire();
934 bool bReturn
= m_bCancelAsynchRequest
;
935 m_aCancelAsynchAccess
.release();
940 void FmSearchEngine::CancelSearch()
942 m_aCancelAsynchAccess
.acquire();
943 m_bCancelAsynchRequest
= true;
944 m_aCancelAsynchAccess
.release();
948 void FmSearchEngine::SwitchToContext(const Reference
< css::sdbc::XResultSet
> & xCursor
, const OUString
& sVisibleFields
, const InterfaceArray
& arrFields
,
949 sal_Int32 nFieldIndex
)
951 DBG_ASSERT(!m_bSearchingCurrently
, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !");
952 if (m_bSearchingCurrently
)
955 m_xSearchCursor
= xCursor
;
956 m_xOriginalIterator
= xCursor
;
957 m_xClonedIterator
= CursorWrapper(m_xOriginalIterator
, true);
959 fillControlTexts(arrFields
);
961 Init(sVisibleFields
);
962 RebuildUsedFields(nFieldIndex
, true);
966 void FmSearchEngine::ImplStartNextSearch()
968 m_bCancelAsynchRequest
= false;
969 m_bSearchingCurrently
= true;
972 OnSearchTerminated();
976 void FmSearchEngine::SearchNext(const OUString
& strExpression
)
978 m_strSearchExpression
= strExpression
;
979 m_eSearchForType
= SearchFor::String
;
980 ImplStartNextSearch();
984 void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull
)
986 m_eSearchForType
= _bSearchForNull
? SearchFor::Null
: SearchFor::NotNull
;
987 ImplStartNextSearch();
991 void FmSearchEngine::StartOver(const OUString
& strExpression
)
996 m_xSearchCursor
.first();
998 m_xSearchCursor
.last();
1000 catch( const Exception
& )
1002 DBG_UNHANDLED_EXCEPTION("svx");
1006 InvalidatePreviousLoc();
1007 SearchNext(strExpression
);
1011 void FmSearchEngine::StartOverSpecial(bool _bSearchForNull
)
1016 m_xSearchCursor
.first();
1018 m_xSearchCursor
.last();
1020 catch( const Exception
& )
1022 DBG_UNHANDLED_EXCEPTION("svx");
1026 InvalidatePreviousLoc();
1027 SearchNextSpecial(_bSearchForNull
);
1031 void FmSearchEngine::InvalidatePreviousLoc()
1033 m_aPreviousLocBookmark
.clear();
1034 m_iterPreviousLocField
= m_arrUsedFields
.end();
1038 void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex
, bool bForce
)
1040 if (!bForce
&& (nFieldIndex
== m_nCurrentFieldIndex
))
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)
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)
1053 Reference
< css::container::XIndexAccess
> xFields
;
1054 for (sal_Int32 i
: m_arrFieldMapping
)
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
);
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
)]);
1071 m_nCurrentFieldIndex
= nFieldIndex
;
1072 // and of course I start the next search in a virgin state again
1073 InvalidatePreviousLoc();
1076 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */