bump product version to 7.2.5.1
[LibreOffice.git] / svx / source / form / fmsrcimp.cxx
blob60613663eda55baa3b25302cd535b3e8055fbd05
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <o3tl/safeint.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/beans/XPropertySet.hpp>
34 #include <com/sun/star/container/XIndexAccess.hpp>
35 #include <com/sun/star/util/SearchAlgorithms2.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/XConnection.hpp>
42 #include <com/sun/star/sdbc/XDatabaseMetaData.hpp>
43 #include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
45 #include <fmprop.hxx>
46 #include <svx/fmsrcimp.hxx>
48 #include <comphelper/types.hxx>
49 #include <unotools/syslocale.hxx>
50 #include <i18nutil/searchopt.hxx>
52 #define EQUAL_BOOKMARKS(a, b) a == b
54 #define IFACECAST(c) static_cast<const Reference< XInterface >&>(c)
56 using namespace ::com::sun::star::uno;
57 using namespace ::com::sun::star::util;
58 using namespace ::com::sun::star::lang;
59 using namespace ::com::sun::star::sdbc;
60 using namespace ::com::sun::star::i18n;
61 using namespace ::com::sun::star::beans;
62 using namespace ::svxform;
65 // = FmRecordCountListener
67 // SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject);
70 FmRecordCountListener::FmRecordCountListener(const Reference< css::sdbc::XResultSet > & dbcCursor)
73 m_xListening.set(dbcCursor, UNO_QUERY);
74 if (!m_xListening.is())
75 return;
77 if (::comphelper::getBOOL(m_xListening->getPropertyValue(FM_PROP_ROWCOUNTFINAL)))
79 m_xListening = nullptr;
80 // there's nothing to do as the record count is already known
81 return;
84 m_xListening->addPropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
88 void FmRecordCountListener::SetPropChangeHandler(const Link<sal_Int32,void>& lnk)
90 m_lnkWhoWantsToKnow = lnk;
92 if (m_xListening.is())
93 NotifyCurrentCount();
97 FmRecordCountListener::~FmRecordCountListener()
103 void FmRecordCountListener::DisConnect()
105 if(m_xListening.is())
106 m_xListening->removePropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
107 m_xListening = nullptr;
111 void SAL_CALL FmRecordCountListener::disposing(const css::lang::EventObject& /*Source*/)
113 DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::disposing should never have been called without a propset !");
114 DisConnect();
118 void FmRecordCountListener::NotifyCurrentCount()
120 if (m_lnkWhoWantsToKnow.IsSet())
122 DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::NotifyCurrentCount : I have no propset ... !?");
123 sal_Int32 theCount = ::comphelper::getINT32(m_xListening->getPropertyValue(FM_PROP_ROWCOUNT));
124 m_lnkWhoWantsToKnow.Call(theCount);
129 void FmRecordCountListener::propertyChange(const css::beans::PropertyChangeEvent& /*evt*/)
131 NotifyCurrentCount();
135 // FmSearchEngine - local classes
137 SimpleTextWrapper::SimpleTextWrapper(const Reference< css::awt::XTextComponent > & _xText)
138 :ControlTextWrapper(_xText)
139 ,m_xText(_xText)
141 DBG_ASSERT(m_xText.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !");
145 OUString SimpleTextWrapper::getCurrentText() const
147 return m_xText->getText();
151 ListBoxWrapper::ListBoxWrapper(const Reference< css::awt::XListBox > & _xBox)
152 :ControlTextWrapper(_xBox)
153 ,m_xBox(_xBox)
155 DBG_ASSERT(m_xBox.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !");
159 OUString ListBoxWrapper::getCurrentText() const
161 return m_xBox->getSelectedItem();
165 CheckBoxWrapper::CheckBoxWrapper(const Reference< css::awt::XCheckBox > & _xBox)
166 :ControlTextWrapper(_xBox)
167 ,m_xBox(_xBox)
169 DBG_ASSERT(m_xBox.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !");
173 OUString CheckBoxWrapper::getCurrentText() const
175 switch (static_cast<TriState>(m_xBox->getState()))
177 case TRISTATE_FALSE: return "0";
178 case TRISTATE_TRUE: return "1";
179 default: break;
181 return OUString();
185 // = FmSearchEngine
187 bool FmSearchEngine::MoveCursor()
189 bool bSuccess = true;
192 if (m_bForward)
193 if (m_xSearchCursor.isLast())
194 m_xSearchCursor.first();
195 else
196 m_xSearchCursor.next();
197 else
198 if (m_xSearchCursor.isFirst())
200 rtl::Reference<FmRecordCountListener> prclListener = new FmRecordCountListener(m_xSearchCursor);
201 prclListener->SetPropChangeHandler(LINK(this, FmSearchEngine, OnNewRecordCount));
203 m_xSearchCursor.last();
205 prclListener->DisConnect();
207 else
208 m_xSearchCursor.previous();
210 catch(Exception const&)
212 TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor");
213 bSuccess = false;
215 catch(...)
217 TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor : caught an unknown Exception !");
218 bSuccess = false;
221 return bSuccess;
225 bool FmSearchEngine::MoveField(sal_Int32& nPos, FieldCollection::iterator& iter, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
227 bool bSuccess(true);
228 if (m_bForward)
230 ++iter;
231 ++nPos;
232 if (iter == iterEnd)
234 bSuccess = MoveCursor();
235 iter = iterBegin;
236 nPos = 0;
238 } else
240 if (iter == iterBegin)
242 bSuccess = MoveCursor();
243 iter = iterEnd;
244 nPos = iter-iterBegin;
246 --iter;
247 --nPos;
249 return bSuccess;
253 void FmSearchEngine::BuildAndInsertFieldInfo(const Reference< css::container::XIndexAccess > & xAllFields, sal_Int32 nField)
255 DBG_ASSERT( xAllFields.is() && ( nField >= 0 ) && ( nField < xAllFields->getCount() ),
256 "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" );
258 // the field itself
259 Reference< XInterface > xCurrentField;
260 xAllFields->getByIndex(nField) >>= xCurrentField;
262 // From this I now know that it supports the DatabaseRecord service (I hope).
263 // For the FormatKey and the type I need the PropertySet.
264 Reference< css::beans::XPropertySet > xProperties(xCurrentField, UNO_QUERY_THROW);
266 // build the FieldInfo for that
267 FieldInfo fiCurrent;
268 fiCurrent.xContents.set(xCurrentField, UNO_QUERY);
270 // and memorize
271 m_arrUsedFields.insert(m_arrUsedFields.end(), fiCurrent);
275 OUString FmSearchEngine::FormatField(sal_Int32 nWhich)
277 DBG_ASSERT(o3tl::make_unsigned(nWhich) < m_aControlTexts.size(), "FmSearchEngine::FormatField(sal_Int32) : invalid position !");
278 DBG_ASSERT(m_aControlTexts[nWhich], "FmSearchEngine::FormatField(sal_Int32) : invalid object in array !");
279 DBG_ASSERT(m_aControlTexts[nWhich]->getControl().is(), "FmSearchEngine::FormatField : invalid control !");
281 if (m_nCurrentFieldIndex != -1)
283 DBG_ASSERT((nWhich == 0) || (nWhich == m_nCurrentFieldIndex), "FmSearchEngine::FormatField : parameter nWhich is invalid");
284 // analogous situation as below
285 nWhich = m_nCurrentFieldIndex;
288 DBG_ASSERT((nWhich >= 0) && (o3tl::make_unsigned(nWhich) < m_aControlTexts.size()),
289 "FmSearchEngine::FormatField : invalid argument nWhich !");
290 return m_aControlTexts[m_nCurrentFieldIndex == -1 ? nWhich : m_nCurrentFieldIndex]->getCurrentText();
294 FmSearchEngine::SearchResult FmSearchEngine::SearchSpecial(bool _bSearchForNull, sal_Int32& nFieldPos,
295 FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
297 // memorize the start position
298 Any aStartMark;
299 try { aStartMark = m_xSearchCursor.getBookmark(); }
300 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
301 FieldCollection::const_iterator iterInitialField = iterFieldLoop;
304 bool bFound(false);
305 bool bMovedAround(false);
308 Application::Reschedule( true );
310 // the content to be compared currently
311 iterFieldLoop->xContents->getString(); // needed for wasNull
312 bFound = _bSearchForNull == bool(iterFieldLoop->xContents->wasNull());
313 if (bFound)
314 break;
316 // next field (implicitly next record, if necessary)
317 if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
318 { // When moving to the next field, something went wrong...
319 // Continuing is not possible, since the next time exactly the same
320 // will definitely go wrong again, thus abort.
321 // Before, however, so that the search continues at the current position:
322 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
323 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
324 m_iterPreviousLocField = iterFieldLoop;
325 // and leave
326 return SearchResult::Error;
329 Any aCurrentBookmark;
330 try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
331 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
333 bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
335 if (nFieldPos == 0)
336 // that is, I've moved to a new record
337 PropagateProgress(bMovedAround);
338 // if we moved to the starting position we don't have to propagate an 'overflow' message
339 // FS - 07.12.99 - 68530
341 // cancel requested?
342 if (CancelRequested())
343 return SearchResult::Cancelled;
345 } while (!bMovedAround);
347 return bFound ? SearchResult::Found : SearchResult::NotFound;
351 FmSearchEngine::SearchResult FmSearchEngine::SearchWildcard(std::u16string_view strExpression, sal_Int32& nFieldPos,
352 FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
354 // memorize the start position
355 Any aStartMark;
356 try { aStartMark = m_xSearchCursor.getBookmark(); }
357 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
358 FieldCollection::const_iterator iterInitialField = iterFieldLoop;
360 WildCard aSearchExpression(strExpression);
363 bool bFound(false);
364 bool bMovedAround(false);
367 Application::Reschedule( true );
369 // the content to be compared currently
370 OUString sCurrentCheck;
371 if (m_bFormatter)
372 sCurrentCheck = FormatField(nFieldPos);
373 else
374 sCurrentCheck = iterFieldLoop->xContents->getString();
376 if (!GetCaseSensitive())
377 // norm the string
378 sCurrentCheck = m_aCharacterClassficator.lowercase(sCurrentCheck);
380 // now the test is easy...
381 bFound = aSearchExpression.Matches(sCurrentCheck);
383 if (bFound)
384 break;
386 // next field (implicitly next record, if necessary)
387 if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
388 { // When moving to the next field, something went wrong...
389 // Continuing is not possible, since the next time exactly the same
390 // will definitely go wrong again, thus abort.
391 // Before, however, so that the search continues at the current position:
392 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
393 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
394 m_iterPreviousLocField = iterFieldLoop;
395 // and leave
396 return SearchResult::Error;
399 Any aCurrentBookmark;
400 try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
401 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
403 bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
405 if (nFieldPos == 0)
406 // that is, I've moved to a new record
407 PropagateProgress(bMovedAround);
408 // if we moved to the starting position we don't have to propagate an 'overflow' message
409 // FS - 07.12.99 - 68530
411 // cancel requested?
412 if (CancelRequested())
413 return SearchResult::Cancelled;
415 } while (!bMovedAround);
417 return bFound ? SearchResult::Found : SearchResult::NotFound;
421 FmSearchEngine::SearchResult FmSearchEngine::SearchRegularApprox(const OUString& strExpression, sal_Int32& nFieldPos,
422 FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
424 DBG_ASSERT(m_bLevenshtein || m_bRegular,
425 "FmSearchEngine::SearchRegularApprox : invalid search mode!");
426 DBG_ASSERT(!m_bLevenshtein || !m_bRegular,
427 "FmSearchEngine::SearchRegularApprox : cannot search for regular expressions and similarities at the same time!");
429 // memorize start position
430 Any aStartMark;
431 try { aStartMark = m_xSearchCursor.getBookmark(); }
432 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
433 FieldCollection::const_iterator iterInitialField = iterFieldLoop;
435 // collect parameters
436 i18nutil::SearchOptions2 aParam;
437 aParam.AlgorithmType2 = m_bRegular ? SearchAlgorithms2::REGEXP : SearchAlgorithms2::APPROXIMATE;
438 aParam.searchFlag = 0;
439 aParam.transliterateFlags = GetTransliterationFlags();
440 if ( !GetTransliteration() )
441 { // if transliteration is not enabled, the only flags which matter are IGNORE_CASE and IGNORE_WIDTH
442 aParam.transliterateFlags &= TransliterationFlags::IGNORE_CASE | TransliterationFlags::IGNORE_WIDTH;
444 if (m_bLevenshtein)
446 if (m_bLevRelaxed)
447 aParam.searchFlag |= SearchFlags::LEV_RELAXED;
448 aParam.changedChars = m_nLevOther;
449 aParam.deletedChars = m_nLevShorter;
450 aParam.insertedChars = m_nLevLonger;
452 aParam.searchString = strExpression;
453 aParam.Locale = SvtSysLocale().GetLanguageTag().getLocale();
454 ::utl::TextSearch aLocalEngine( aParam);
457 bool bFound = false;
458 bool bMovedAround(false);
461 Application::Reschedule( true );
463 // the content to be compared currently
464 OUString sCurrentCheck;
465 if (m_bFormatter)
466 sCurrentCheck = FormatField(nFieldPos);
467 else
468 sCurrentCheck = iterFieldLoop->xContents->getString();
470 // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it)
472 sal_Int32 nStart = 0, nEnd = sCurrentCheck.getLength();
473 bFound = aLocalEngine.SearchForward(sCurrentCheck, &nStart, &nEnd);
474 // it says 'forward' here, but that only refers to the search within
475 // sCurrentCheck, so it has nothing to do with the direction of my
476 // record migration (MoveField takes care of that)
478 // check if the position is correct
479 if (bFound)
481 switch (m_nPosition)
483 case MATCHING_WHOLETEXT :
484 if (nEnd != sCurrentCheck.getLength())
486 bFound = false;
487 break;
489 [[fallthrough]];
490 case MATCHING_BEGINNING :
491 if (nStart != 0)
492 bFound = false;
493 break;
494 case MATCHING_END :
495 if (nEnd != sCurrentCheck.getLength())
496 bFound = false;
497 break;
501 if (bFound) // still?
502 break;
504 // next field (implicitly next record, if necessary)
505 if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
506 { // When moving to the next field, something went wrong...
507 // Continuing is not possible, since the next time exactly the same
508 // will definitely go wrong again, thus abort (without error
509 // notification, I expect it to be displayed in the Move).
510 // Before, however, so that the search continues at the current position:
511 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
512 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
513 m_iterPreviousLocField = iterFieldLoop;
514 // and leave
515 return SearchResult::Error;
518 Any aCurrentBookmark;
519 try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
520 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
521 bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
523 if (nFieldPos == 0)
524 // that is, I've moved to a new record
525 PropagateProgress(bMovedAround);
526 // if we moved to the starting position we don't have to propagate an 'overflow' message
527 // FS - 07.12.99 - 68530
529 // cancel requested?
530 if (CancelRequested())
531 return SearchResult::Cancelled;
533 } while (!bMovedAround);
535 return bFound ? SearchResult::Found : SearchResult::NotFound;
539 FmSearchEngine::FmSearchEngine(const Reference< XComponentContext >& _rxContext,
540 const Reference< XResultSet > & xCursor, const OUString& sVisibleFields,
541 const InterfaceArray& arrFields)
542 :m_xSearchCursor(xCursor)
543 ,m_aCharacterClassficator( _rxContext, SvtSysLocale().GetLanguageTag() )
544 ,m_aStringCompare( _rxContext )
545 ,m_nCurrentFieldIndex(-2) // -1 already has a meaning, so I take -2 for 'invalid'
546 ,m_xOriginalIterator(xCursor)
547 ,m_xClonedIterator(m_xOriginalIterator, true)
548 ,m_eSearchForType(SearchFor::String)
549 ,m_srResult(SearchResult::Found)
550 ,m_bSearchingCurrently(false)
551 ,m_bCancelAsynchRequest(false)
552 ,m_bFormatter(true) // this must be consistent with m_xSearchCursor, which is generally == m_xOriginalIterator
553 ,m_bForward(false)
554 ,m_bWildcard(false)
555 ,m_bRegular(false)
556 ,m_bLevenshtein(false)
557 ,m_bTransliteration(false)
558 ,m_bLevRelaxed(false)
559 ,m_nLevOther(0)
560 ,m_nLevShorter(0)
561 ,m_nLevLonger(0)
562 ,m_nPosition(MATCHING_ANYWHERE)
563 ,m_nTransliterationFlags(TransliterationFlags::NONE)
566 fillControlTexts(arrFields);
567 Init(sVisibleFields);
571 void FmSearchEngine::SetIgnoreWidthCJK(bool bSet)
573 if (bSet)
574 m_nTransliterationFlags |= TransliterationFlags::IGNORE_WIDTH;
575 else
576 m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_WIDTH;
580 bool FmSearchEngine::GetIgnoreWidthCJK() const
582 return bool(m_nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH);
586 void FmSearchEngine::SetCaseSensitive(bool bSet)
588 if (bSet)
589 m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_CASE;
590 else
591 m_nTransliterationFlags |= TransliterationFlags::IGNORE_CASE;
595 bool FmSearchEngine::GetCaseSensitive() const
597 return !(m_nTransliterationFlags & TransliterationFlags::IGNORE_CASE);
601 void FmSearchEngine::fillControlTexts(const InterfaceArray& arrFields)
603 m_aControlTexts.clear();
604 Reference< XInterface > xCurrent;
605 for (const auto & rField : arrFields)
607 xCurrent = rField;
608 DBG_ASSERT(xCurrent.is(), "FmSearchEngine::fillControlTexts : invalid field interface !");
609 // check which type of control this is
610 Reference< css::awt::XTextComponent > xAsText(xCurrent, UNO_QUERY);
611 if (xAsText.is())
613 m_aControlTexts.emplace_back(new SimpleTextWrapper(xAsText));
614 continue;
617 Reference< css::awt::XListBox > xAsListBox(xCurrent, UNO_QUERY);
618 if (xAsListBox.is())
620 m_aControlTexts.emplace_back(new ListBoxWrapper(xAsListBox));
621 continue;
624 Reference< css::awt::XCheckBox > xAsCheckBox(xCurrent, UNO_QUERY);
625 DBG_ASSERT(xAsCheckBox.is(), "FmSearchEngine::fillControlTexts : invalid field interface (no supported type) !");
626 // we don't have any more options ...
627 m_aControlTexts.emplace_back(new CheckBoxWrapper(xAsCheckBox));
632 void FmSearchEngine::Init(const OUString& sVisibleFields)
634 // analyze the fields
635 // additionally, create the mapping: because the list of used columns can be shorter than the list
636 // of columns of the cursor, we need a mapping: "used column number n" -> "cursor column m"
637 m_arrFieldMapping.clear();
639 // important: The case of the columns does not need to be exact - for instance:
640 // - a user created a form which works on a table, for which the driver returns a column name "COLUMN"
641 // - the driver itself works case-insensitive with column names
642 // - a control in the form is bound to "column" - not the different case
643 // In such a scenario, the form and the field would work okay, but we here need to case for the different case
644 // explicitly
645 // #i8755#
647 // so first of all, check if the database handles identifiers case sensitive
648 Reference< XConnection > xConn;
649 Reference< XDatabaseMetaData > xMeta;
650 Reference< XPropertySet > xCursorProps( IFACECAST( m_xSearchCursor ), UNO_QUERY );
651 if ( xCursorProps.is() )
655 xCursorProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ) >>= xConn;
657 catch( const Exception& ) { /* silent this - will be asserted below */ }
659 if ( xConn.is() )
660 xMeta = xConn->getMetaData();
661 OSL_ENSURE( xMeta.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" );
663 bool bCaseSensitiveIdentifiers = true; // assume case sensitivity
664 if ( xMeta.is() )
665 bCaseSensitiveIdentifiers = xMeta->supportsMixedCaseQuotedIdentifiers();
667 // now that we have this information, we need a collator which is able to case (in)sensitivity compare strings
668 m_aStringCompare.loadDefaultCollator( SvtSysLocale().GetLanguageTag().getLocale(),
669 bCaseSensitiveIdentifiers ? 0 : css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
673 // the cursor can give me a record (as PropertySet), which supports the DatabaseRecord service
674 Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
675 DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::Init : invalid cursor (no columns supplier) !");
676 Reference< css::container::XNameAccess > xAllFieldNames = xSupplyCols->getColumns();
677 Sequence< OUString > seqFieldNames = xAllFieldNames->getElementNames();
679 OUString sCurrentField;
680 sal_Int32 nIndex = 0;
683 sCurrentField = sVisibleFields.getToken(0, ';' , nIndex);
685 // search in the field collection
686 sal_Int32 nFoundIndex = -1;
687 auto pFieldName = std::find_if(seqFieldNames.begin(), seqFieldNames.end(),
688 [this, &sCurrentField](const OUString& rFieldName) {
689 return 0 == m_aStringCompare.compareString( rFieldName, sCurrentField ); });
690 if (pFieldName != seqFieldNames.end())
691 nFoundIndex = static_cast<sal_Int32>(std::distance(seqFieldNames.begin(), pFieldName));
692 DBG_ASSERT(nFoundIndex != -1, "FmSearchEngine::Init : Invalid field name were given !");
693 m_arrFieldMapping.push_back(nFoundIndex);
695 while ( nIndex >= 0 );
697 catch (const Exception&)
699 TOOLS_WARN_EXCEPTION("svx.form", "");
705 void FmSearchEngine::SetFormatterUsing(bool bSet)
707 if (m_bFormatter == bSet)
708 return;
709 m_bFormatter = bSet;
711 // I did not use a formatter, but TextComponents -> the SearchIterator needs to be adjusted
714 if (m_bFormatter)
716 DBG_ASSERT(m_xSearchCursor == m_xClonedIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
717 m_xSearchCursor = m_xOriginalIterator;
718 m_xSearchCursor.moveToBookmark(m_xClonedIterator.getBookmark());
719 // so that I continue with the new iterator at the actual place where I previously stopped
721 else
723 DBG_ASSERT(m_xSearchCursor == m_xOriginalIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
724 m_xSearchCursor = m_xClonedIterator;
725 m_xSearchCursor.moveToBookmark(m_xOriginalIterator.getBookmark());
728 catch( const Exception& )
730 DBG_UNHANDLED_EXCEPTION("svx");
733 // I have to re-bind the fields, because the text exchange might take
734 // place over these fields and the underlying cursor has changed
735 RebuildUsedFields(m_nCurrentFieldIndex, true);
739 void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow)
741 if (!m_aProgressHandler.IsSet())
742 return;
744 FmSearchProgress aProgress;
747 aProgress.aSearchState = FmSearchProgress::State::Progress;
748 aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
749 if (m_bForward)
750 aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isFirst();
751 else
752 aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isLast();
754 catch( const Exception& )
756 DBG_UNHANDLED_EXCEPTION("svx");
759 m_aProgressHandler.Call(&aProgress);
763 void FmSearchEngine::SearchNextImpl()
765 DBG_ASSERT(!(m_bWildcard && m_bRegular) && !(m_bRegular && m_bLevenshtein) && !(m_bLevenshtein && m_bWildcard),
766 "FmSearchEngine::SearchNextImpl : search parameters are mutually exclusive!");
768 DBG_ASSERT(m_xSearchCursor.is(), "FmSearchEngine::SearchNextImpl : have invalid iterator!");
770 // the parameters of the search
771 OUString strSearchExpression(m_strSearchExpression); // I need non-const
772 if (!GetCaseSensitive())
773 // norm the string
774 strSearchExpression = m_aCharacterClassficator.lowercase(strSearchExpression);
776 if (!m_bRegular && !m_bLevenshtein)
777 { // 'normal' search I run through WildCards in any case, but must before adjust the OUString depending on the mode
779 if (!m_bWildcard)
780 { // since in all other cases * and ? in the search string are of course
781 // also allowed, but should not count as WildCards, I need to normalize
782 OUString aTmp(strSearchExpression);
783 aTmp = aTmp.replaceAll("*", "\\*");
784 aTmp = aTmp.replaceAll("?", "\\?");
785 strSearchExpression = aTmp;
787 switch (m_nPosition)
789 case MATCHING_ANYWHERE :
790 strSearchExpression = "*" + strSearchExpression + "*";
791 break;
792 case MATCHING_BEGINNING :
793 strSearchExpression += "*";
794 break;
795 case MATCHING_END :
796 strSearchExpression = "*" + strSearchExpression;
797 break;
798 case MATCHING_WHOLETEXT :
799 break;
800 default :
801 OSL_FAIL("FmSearchEngine::SearchNextImpl() : the methods listbox may contain only 4 entries ...");
806 // for work on field list
807 FieldCollection::iterator iterBegin = m_arrUsedFields.begin();
808 FieldCollection::iterator iterEnd = m_arrUsedFields.end();
809 FieldCollection::iterator iterFieldCheck;
811 sal_Int32 nFieldPos;
813 if (m_aPreviousLocBookmark.hasValue())
815 DBG_ASSERT(EQUAL_BOOKMARKS(m_aPreviousLocBookmark, m_xSearchCursor.getBookmark()),
816 "FmSearchEngine::SearchNextImpl : invalid position!");
817 iterFieldCheck = m_iterPreviousLocField;
818 // continue in the field after (or before) the last discovery
819 nFieldPos = iterFieldCheck - iterBegin;
820 MoveField(nFieldPos, iterFieldCheck, iterBegin, iterEnd);
822 else
824 if (m_bForward)
825 iterFieldCheck = iterBegin;
826 else
828 iterFieldCheck = iterEnd;
829 --iterFieldCheck;
831 nFieldPos = iterFieldCheck - iterBegin;
834 PropagateProgress(true);
835 SearchResult srResult;
836 if (m_eSearchForType != SearchFor::String)
837 srResult = SearchSpecial(m_eSearchForType == SearchFor::Null, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
838 else if (!m_bRegular && !m_bLevenshtein)
839 srResult = SearchWildcard(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
840 else
841 srResult = SearchRegularApprox(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
843 m_srResult = srResult;
845 if (SearchResult::Error == m_srResult)
846 return;
848 // found?
849 if (SearchResult::Found == m_srResult)
851 // memorize the position
852 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
853 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
854 m_iterPreviousLocField = iterFieldCheck;
856 else
857 // invalidate the "last discovery"
858 InvalidatePreviousLoc();
862 void FmSearchEngine::OnSearchTerminated()
864 if (!m_aProgressHandler.IsSet())
865 return;
867 FmSearchProgress aProgress;
870 switch (m_srResult)
872 case SearchResult::Error :
873 aProgress.aSearchState = FmSearchProgress::State::Error;
874 break;
875 case SearchResult::Found :
876 aProgress.aSearchState = FmSearchProgress::State::Successful;
877 aProgress.aBookmark = m_aPreviousLocBookmark;
878 aProgress.nFieldIndex = m_iterPreviousLocField - m_arrUsedFields.begin();
879 break;
880 case SearchResult::NotFound :
881 aProgress.aSearchState = FmSearchProgress::State::NothingFound;
882 aProgress.aBookmark = m_xSearchCursor.getBookmark();
883 break;
884 case SearchResult::Cancelled :
885 aProgress.aSearchState = FmSearchProgress::State::Canceled;
886 aProgress.aBookmark = m_xSearchCursor.getBookmark();
887 break;
889 aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
891 catch( const Exception& )
893 DBG_UNHANDLED_EXCEPTION("svx");
896 // by definition, the link must be thread-safe (I just require that),
897 // so that I do not have to worry about such things here
898 m_aProgressHandler.Call(&aProgress);
900 m_bSearchingCurrently = false;
904 IMPL_LINK(FmSearchEngine, OnNewRecordCount, sal_Int32, theCounter, void)
906 if (!m_aProgressHandler.IsSet())
907 return;
909 FmSearchProgress aProgress;
910 aProgress.nCurrentRecord = theCounter;
911 aProgress.aSearchState = FmSearchProgress::State::ProgressCounting;
912 m_aProgressHandler.Call(&aProgress);
916 bool FmSearchEngine::CancelRequested()
918 m_aCancelAsynchAccess.acquire();
919 bool bReturn = m_bCancelAsynchRequest;
920 m_aCancelAsynchAccess.release();
921 return bReturn;
925 void FmSearchEngine::CancelSearch()
927 m_aCancelAsynchAccess.acquire();
928 m_bCancelAsynchRequest = true;
929 m_aCancelAsynchAccess.release();
933 void FmSearchEngine::SwitchToContext(const Reference< css::sdbc::XResultSet > & xCursor, const OUString& sVisibleFields, const InterfaceArray& arrFields,
934 sal_Int32 nFieldIndex)
936 DBG_ASSERT(!m_bSearchingCurrently, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !");
937 if (m_bSearchingCurrently)
938 return;
940 m_xSearchCursor = xCursor;
941 m_xOriginalIterator = xCursor;
942 m_xClonedIterator = CursorWrapper(m_xOriginalIterator, true);
944 fillControlTexts(arrFields);
946 Init(sVisibleFields);
947 RebuildUsedFields(nFieldIndex, true);
951 void FmSearchEngine::ImplStartNextSearch()
953 m_bCancelAsynchRequest = false;
954 m_bSearchingCurrently = true;
956 SearchNextImpl();
957 OnSearchTerminated();
961 void FmSearchEngine::SearchNext(const OUString& strExpression)
963 m_strSearchExpression = strExpression;
964 m_eSearchForType = SearchFor::String;
965 ImplStartNextSearch();
969 void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull)
971 m_eSearchForType = _bSearchForNull ? SearchFor::Null : SearchFor::NotNull;
972 ImplStartNextSearch();
976 void FmSearchEngine::StartOver(const OUString& strExpression)
980 if (m_bForward)
981 m_xSearchCursor.first();
982 else
983 m_xSearchCursor.last();
985 catch( const Exception& )
987 DBG_UNHANDLED_EXCEPTION("svx");
988 return;
991 InvalidatePreviousLoc();
992 SearchNext(strExpression);
996 void FmSearchEngine::StartOverSpecial(bool _bSearchForNull)
1000 if (m_bForward)
1001 m_xSearchCursor.first();
1002 else
1003 m_xSearchCursor.last();
1005 catch( const Exception& )
1007 DBG_UNHANDLED_EXCEPTION("svx");
1008 return;
1011 InvalidatePreviousLoc();
1012 SearchNextSpecial(_bSearchForNull);
1016 void FmSearchEngine::InvalidatePreviousLoc()
1018 m_aPreviousLocBookmark.clear();
1019 m_iterPreviousLocField = m_arrUsedFields.end();
1023 void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex, bool bForce)
1025 if (!bForce && (nFieldIndex == m_nCurrentFieldIndex))
1026 return;
1027 // (since I allow no change of the iterator from the outside, the same css::sdbcx::Index
1028 // also always means the same column, so I have nothing to do)
1030 DBG_ASSERT((nFieldIndex == -1) ||
1031 ((nFieldIndex >= 0) &&
1032 (o3tl::make_unsigned(nFieldIndex) < m_arrFieldMapping.size())),
1033 "FmSearchEngine::RebuildUsedFields : nFieldIndex is invalid!");
1034 // collect all fields I need to search through
1035 m_arrUsedFields.clear();
1036 if (nFieldIndex == -1)
1038 Reference< css::container::XIndexAccess > xFields;
1039 for (sal_Int32 i : m_arrFieldMapping)
1041 Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
1042 DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1043 xFields.set(xSupplyCols->getColumns(), UNO_QUERY);
1044 BuildAndInsertFieldInfo(xFields, i);
1047 else
1049 Reference< css::container::XIndexAccess > xFields;
1050 Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
1051 DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1052 xFields.set (xSupplyCols->getColumns(), UNO_QUERY);
1053 BuildAndInsertFieldInfo(xFields, m_arrFieldMapping[static_cast< size_t >(nFieldIndex)]);
1056 m_nCurrentFieldIndex = nFieldIndex;
1057 // and of course I start the next search in a virgin state again
1058 InvalidatePreviousLoc();
1061 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */