summaryrefslogtreecommitdiffstats
path: root/svx/source/form/fmsrcimp.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /svx/source/form/fmsrcimp.cxx
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/form/fmsrcimp.cxx')
-rw-r--r--svx/source/form/fmsrcimp.cxx1063
1 files changed, 1063 insertions, 0 deletions
diff --git a/svx/source/form/fmsrcimp.cxx b/svx/source/form/fmsrcimp.cxx
new file mode 100644
index 000000000..82a6313a9
--- /dev/null
+++ b/svx/source/form/fmsrcimp.cxx
@@ -0,0 +1,1063 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <svx/fmtools.hxx>
+#include <svx/fmsrccfg.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/wldcrd.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/textsearch.hxx>
+#include <com/sun/star/awt/XTextComponent.hpp>
+#include <com/sun/star/awt/XListBox.hpp>
+#include <com/sun/star/awt/XCheckBox.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/util/SearchAlgorithms2.hpp>
+#include <com/sun/star/util/SearchFlags.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/i18n/CollatorOptions.hpp>
+
+#include <com/sun/star/sdb/XColumn.hpp>
+#include <com/sun/star/sdbc/XConnection.hpp>
+#include <com/sun/star/sdbc/XDatabaseMetaData.hpp>
+#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
+
+#include <fmprop.hxx>
+#include <svx/fmsrcimp.hxx>
+
+#include <comphelper/types.hxx>
+#include <unotools/syslocale.hxx>
+#include <i18nutil/searchopt.hxx>
+
+#define EQUAL_BOOKMARKS(a, b) a == b
+
+#define IFACECAST(c) static_cast<const Reference< XInterface >&>(c)
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::util;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::sdbc;
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star::beans;
+using namespace ::svxform;
+
+
+// = FmRecordCountListener
+
+// SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject);
+
+
+FmRecordCountListener::FmRecordCountListener(const Reference< css::sdbc::XResultSet > & dbcCursor)
+{
+
+ m_xListening.set(dbcCursor, UNO_QUERY);
+ if (!m_xListening.is())
+ return;
+
+ if (::comphelper::getBOOL(m_xListening->getPropertyValue(FM_PROP_ROWCOUNTFINAL)))
+ {
+ m_xListening = nullptr;
+ // there's nothing to do as the record count is already known
+ return;
+ }
+
+ m_xListening->addPropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
+}
+
+
+void FmRecordCountListener::SetPropChangeHandler(const Link<sal_Int32,void>& lnk)
+{
+ m_lnkWhoWantsToKnow = lnk;
+
+ if (m_xListening.is())
+ NotifyCurrentCount();
+}
+
+
+FmRecordCountListener::~FmRecordCountListener()
+{
+
+}
+
+
+void FmRecordCountListener::DisConnect()
+{
+ if(m_xListening.is())
+ m_xListening->removePropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
+ m_xListening = nullptr;
+}
+
+
+void SAL_CALL FmRecordCountListener::disposing(const css::lang::EventObject& /*Source*/)
+{
+ DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::disposing should never have been called without a propset !");
+ DisConnect();
+}
+
+
+void FmRecordCountListener::NotifyCurrentCount()
+{
+ if (m_lnkWhoWantsToKnow.IsSet())
+ {
+ DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::NotifyCurrentCount : I have no propset ... !?");
+ sal_Int32 theCount = ::comphelper::getINT32(m_xListening->getPropertyValue(FM_PROP_ROWCOUNT));
+ m_lnkWhoWantsToKnow.Call(theCount);
+ }
+}
+
+
+void FmRecordCountListener::propertyChange(const css::beans::PropertyChangeEvent& /*evt*/)
+{
+ NotifyCurrentCount();
+}
+
+
+// FmSearchEngine - local classes
+
+SimpleTextWrapper::SimpleTextWrapper(const Reference< css::awt::XTextComponent > & _xText)
+ :ControlTextWrapper(_xText.get())
+ ,m_xText(_xText)
+{
+ DBG_ASSERT(m_xText.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !");
+}
+
+
+OUString SimpleTextWrapper::getCurrentText() const
+{
+ return m_xText->getText();
+}
+
+
+ListBoxWrapper::ListBoxWrapper(const Reference< css::awt::XListBox > & _xBox)
+ :ControlTextWrapper(_xBox.get())
+ ,m_xBox(_xBox)
+{
+ DBG_ASSERT(m_xBox.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !");
+}
+
+
+OUString ListBoxWrapper::getCurrentText() const
+{
+ return m_xBox->getSelectedItem();
+}
+
+
+CheckBoxWrapper::CheckBoxWrapper(const Reference< css::awt::XCheckBox > & _xBox)
+ :ControlTextWrapper(_xBox.get())
+ ,m_xBox(_xBox)
+{
+ DBG_ASSERT(m_xBox.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !");
+}
+
+
+OUString CheckBoxWrapper::getCurrentText() const
+{
+ switch (static_cast<TriState>(m_xBox->getState()))
+ {
+ case TRISTATE_FALSE: return "0";
+ case TRISTATE_TRUE: return "1";
+ default: break;
+ }
+ return OUString();
+}
+
+
+// = FmSearchEngine
+
+bool FmSearchEngine::MoveCursor()
+{
+ bool bSuccess = true;
+ try
+ {
+ if (m_bForward)
+ if (m_xSearchCursor.isLast())
+ m_xSearchCursor.first();
+ else
+ m_xSearchCursor.next();
+ else
+ if (m_xSearchCursor.isFirst())
+ {
+ rtl::Reference<FmRecordCountListener> prclListener = new FmRecordCountListener(m_xSearchCursor);
+ prclListener->SetPropChangeHandler(LINK(this, FmSearchEngine, OnNewRecordCount));
+
+ m_xSearchCursor.last();
+
+ prclListener->DisConnect();
+ }
+ else
+ m_xSearchCursor.previous();
+ }
+ catch(Exception const&)
+ {
+ TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor");
+ bSuccess = false;
+ }
+ catch(...)
+ {
+ OSL_FAIL("FmSearchEngine::MoveCursor : caught an unknown Exception !");
+ bSuccess = false;
+ }
+
+ return bSuccess;
+}
+
+
+bool FmSearchEngine::MoveField(sal_Int32& nPos, FieldCollection::iterator& iter, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
+{
+ bool bSuccess(true);
+ if (m_bForward)
+ {
+ ++iter;
+ ++nPos;
+ if (iter == iterEnd)
+ {
+ bSuccess = MoveCursor();
+ iter = iterBegin;
+ nPos = 0;
+ }
+ } else
+ {
+ if (iter == iterBegin)
+ {
+ bSuccess = MoveCursor();
+ iter = iterEnd;
+ nPos = iter-iterBegin;
+ }
+ --iter;
+ --nPos;
+ }
+ return bSuccess;
+}
+
+
+void FmSearchEngine::BuildAndInsertFieldInfo(const Reference< css::container::XIndexAccess > & xAllFields, sal_Int32 nField)
+{
+ DBG_ASSERT( xAllFields.is() && ( nField >= 0 ) && ( nField < xAllFields->getCount() ),
+ "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" );
+
+ // the field itself
+ Reference< XInterface > xCurrentField;
+ xAllFields->getByIndex(nField) >>= xCurrentField;
+
+ // From this I now know that it supports the DatabaseRecord service (I hope).
+ // For the FormatKey and the type I need the PropertySet.
+ Reference< css::beans::XPropertySet > xProperties(xCurrentField, UNO_QUERY_THROW);
+
+ // build the FieldInfo for that
+ FieldInfo fiCurrent;
+ fiCurrent.xContents.set(xCurrentField, UNO_QUERY);
+
+ // and memorize
+ m_arrUsedFields.insert(m_arrUsedFields.end(), fiCurrent);
+
+}
+
+OUString FmSearchEngine::FormatField(sal_Int32 nWhich)
+{
+ DBG_ASSERT(o3tl::make_unsigned(nWhich) < m_aControlTexts.size(), "FmSearchEngine::FormatField(sal_Int32) : invalid position !");
+ DBG_ASSERT(m_aControlTexts[nWhich], "FmSearchEngine::FormatField(sal_Int32) : invalid object in array !");
+ DBG_ASSERT(m_aControlTexts[nWhich]->getControl().is(), "FmSearchEngine::FormatField : invalid control !");
+
+ if (m_nCurrentFieldIndex != -1)
+ {
+ DBG_ASSERT((nWhich == 0) || (nWhich == m_nCurrentFieldIndex), "FmSearchEngine::FormatField : parameter nWhich is invalid");
+ // analogous situation as below
+ nWhich = m_nCurrentFieldIndex;
+ }
+
+ DBG_ASSERT((nWhich >= 0) && (o3tl::make_unsigned(nWhich) < m_aControlTexts.size()),
+ "FmSearchEngine::FormatField : invalid argument nWhich !");
+ return m_aControlTexts[m_nCurrentFieldIndex == -1 ? nWhich : m_nCurrentFieldIndex]->getCurrentText();
+}
+
+
+FmSearchEngine::SearchResult FmSearchEngine::SearchSpecial(bool _bSearchForNull, sal_Int32& nFieldPos,
+ FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
+{
+ // memorize the start position
+ Any aStartMark;
+ try { aStartMark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
+ FieldCollection::const_iterator iterInitialField = iterFieldLoop;
+
+
+ bool bFound(false);
+ bool bMovedAround(false);
+ do
+ {
+ Application::Reschedule( true );
+
+ // the content to be compared currently
+ iterFieldLoop->xContents->getString(); // needed for wasNull
+ bFound = _bSearchForNull == bool(iterFieldLoop->xContents->wasNull());
+ if (bFound)
+ break;
+
+ // next field (implicitly next record, if necessary)
+ if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
+ { // When moving to the next field, something went wrong...
+ // Continuing is not possible, since the next time exactly the same
+ // will definitely go wrong again, thus abort.
+ // Before, however, so that the search continues at the current position:
+ try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
+ m_iterPreviousLocField = iterFieldLoop;
+ // and leave
+ return SearchResult::Error;
+ }
+
+ Any aCurrentBookmark;
+ try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
+
+ bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
+
+ if (nFieldPos == 0)
+ // that is, I've moved to a new record
+ PropagateProgress(bMovedAround);
+ // if we moved to the starting position we don't have to propagate an 'overflow' message
+ // FS - 07.12.99 - 68530
+
+ // cancel requested?
+ if (CancelRequested())
+ return SearchResult::Cancelled;
+
+ } while (!bMovedAround);
+
+ return bFound ? SearchResult::Found : SearchResult::NotFound;
+}
+
+
+FmSearchEngine::SearchResult FmSearchEngine::SearchWildcard(const OUString& strExpression, sal_Int32& nFieldPos,
+ FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
+{
+ // memorize the start position
+ Any aStartMark;
+ try { aStartMark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
+ FieldCollection::const_iterator iterInitialField = iterFieldLoop;
+
+ WildCard aSearchExpression(strExpression);
+
+
+ bool bFound(false);
+ bool bMovedAround(false);
+ do
+ {
+ Application::Reschedule( true );
+
+ // the content to be compared currently
+ OUString sCurrentCheck;
+ if (m_bFormatter)
+ sCurrentCheck = FormatField(nFieldPos);
+ else
+ sCurrentCheck = iterFieldLoop->xContents->getString();
+
+ if (!GetCaseSensitive())
+ // norm the string
+ sCurrentCheck = m_aCharacterClassficator.lowercase(sCurrentCheck);
+
+ // now the test is easy...
+ bFound = aSearchExpression.Matches(sCurrentCheck);
+
+ if (bFound)
+ break;
+
+ // next field (implicitly next record, if necessary)
+ if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
+ { // When moving to the next field, something went wrong...
+ // Continuing is not possible, since the next time exactly the same
+ // will definitely go wrong again, thus abort.
+ // Before, however, so that the search continues at the current position:
+ try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
+ m_iterPreviousLocField = iterFieldLoop;
+ // and leave
+ return SearchResult::Error;
+ }
+
+ Any aCurrentBookmark;
+ try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
+
+ bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
+
+ if (nFieldPos == 0)
+ // that is, I've moved to a new record
+ PropagateProgress(bMovedAround);
+ // if we moved to the starting position we don't have to propagate an 'overflow' message
+ // FS - 07.12.99 - 68530
+
+ // cancel requested?
+ if (CancelRequested())
+ return SearchResult::Cancelled;
+
+ } while (!bMovedAround);
+
+ return bFound ? SearchResult::Found : SearchResult::NotFound;
+}
+
+
+FmSearchEngine::SearchResult FmSearchEngine::SearchRegularApprox(const OUString& strExpression, sal_Int32& nFieldPos,
+ FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
+{
+ DBG_ASSERT(m_bLevenshtein || m_bRegular,
+ "FmSearchEngine::SearchRegularApprox : invalid search mode!");
+ DBG_ASSERT(!m_bLevenshtein || !m_bRegular,
+ "FmSearchEngine::SearchRegularApprox : cannot search for regular expressions and similarities at the same time!");
+
+ // memorize start position
+ Any aStartMark;
+ try { aStartMark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
+ FieldCollection::const_iterator iterInitialField = iterFieldLoop;
+
+ // collect parameters
+ i18nutil::SearchOptions2 aParam;
+ aParam.AlgorithmType2 = m_bRegular ? SearchAlgorithms2::REGEXP : SearchAlgorithms2::APPROXIMATE;
+ aParam.searchFlag = 0;
+ aParam.transliterateFlags = GetTransliterationFlags();
+ if ( !GetTransliteration() )
+ { // if transliteration is not enabled, the only flags which matter are IGNORE_CASE and IGNORE_WIDTH
+ aParam.transliterateFlags &= TransliterationFlags::IGNORE_CASE | TransliterationFlags::IGNORE_WIDTH;
+ }
+ if (m_bLevenshtein)
+ {
+ if (m_bLevRelaxed)
+ aParam.searchFlag |= SearchFlags::LEV_RELAXED;
+ aParam.changedChars = m_nLevOther;
+ aParam.deletedChars = m_nLevShorter;
+ aParam.insertedChars = m_nLevLonger;
+ }
+ aParam.searchString = strExpression;
+ aParam.Locale = SvtSysLocale().GetLanguageTag().getLocale();
+ ::utl::TextSearch aLocalEngine( aParam);
+
+
+ bool bFound = false;
+ bool bMovedAround(false);
+ do
+ {
+ Application::Reschedule( true );
+
+ // the content to be compared currently
+ OUString sCurrentCheck;
+ if (m_bFormatter)
+ sCurrentCheck = FormatField(nFieldPos);
+ else
+ sCurrentCheck = iterFieldLoop->xContents->getString();
+
+ // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it)
+
+ sal_Int32 nStart = 0, nEnd = sCurrentCheck.getLength();
+ bFound = aLocalEngine.SearchForward(sCurrentCheck, &nStart, &nEnd);
+ // it says 'forward' here, but that only refers to the search within
+ // sCurrentCheck, so it has nothing to do with the direction of my
+ // record migration (MoveField takes care of that)
+
+ // check if the position is correct
+ if (bFound)
+ {
+ switch (m_nPosition)
+ {
+ case MATCHING_WHOLETEXT :
+ if (nEnd != sCurrentCheck.getLength())
+ {
+ bFound = false;
+ break;
+ }
+ [[fallthrough]];
+ case MATCHING_BEGINNING :
+ if (nStart != 0)
+ bFound = false;
+ break;
+ case MATCHING_END :
+ if (nEnd != sCurrentCheck.getLength())
+ bFound = false;
+ break;
+ }
+ }
+
+ if (bFound) // still?
+ break;
+
+ // next field (implicitly next record, if necessary)
+ if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
+ { // When moving to the next field, something went wrong...
+ // Continuing is not possible, since the next time exactly the same
+ // will definitely go wrong again, thus abort (without error
+ // notification, I expect it to be displayed in the Move).
+ // Before, however, so that the search continues at the current position:
+ try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
+ m_iterPreviousLocField = iterFieldLoop;
+ // and leave
+ return SearchResult::Error;
+ }
+
+ Any aCurrentBookmark;
+ try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
+ bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
+
+ if (nFieldPos == 0)
+ // that is, I've moved to a new record
+ PropagateProgress(bMovedAround);
+ // if we moved to the starting position we don't have to propagate an 'overflow' message
+ // FS - 07.12.99 - 68530
+
+ // cancel requested?
+ if (CancelRequested())
+ return SearchResult::Cancelled;
+
+ } while (!bMovedAround);
+
+ return bFound ? SearchResult::Found : SearchResult::NotFound;
+}
+
+
+FmSearchEngine::FmSearchEngine(const Reference< XComponentContext >& _rxContext,
+ const Reference< XResultSet > & xCursor, const OUString& sVisibleFields,
+ const InterfaceArray& arrFields)
+ :m_xSearchCursor(xCursor)
+ ,m_aCharacterClassficator( _rxContext, SvtSysLocale().GetLanguageTag() )
+ ,m_aStringCompare( _rxContext )
+ ,m_nCurrentFieldIndex(-2) // -1 already has a meaning, so I take -2 for 'invalid'
+ ,m_xOriginalIterator(xCursor)
+ ,m_xClonedIterator(m_xOriginalIterator, true)
+ ,m_eSearchForType(SearchFor::String)
+ ,m_srResult(SearchResult::Found)
+ ,m_bSearchingCurrently(false)
+ ,m_bCancelAsynchRequest(false)
+ ,m_bFormatter(true) // this must be consistent with m_xSearchCursor, which is generally == m_xOriginalIterator
+ ,m_bForward(false)
+ ,m_bWildcard(false)
+ ,m_bRegular(false)
+ ,m_bLevenshtein(false)
+ ,m_bTransliteration(false)
+ ,m_bLevRelaxed(false)
+ ,m_nLevOther(0)
+ ,m_nLevShorter(0)
+ ,m_nLevLonger(0)
+ ,m_nPosition(MATCHING_ANYWHERE)
+ ,m_nTransliterationFlags(TransliterationFlags::NONE)
+{
+
+ fillControlTexts(arrFields);
+ Init(sVisibleFields);
+}
+
+
+void FmSearchEngine::SetIgnoreWidthCJK(bool bSet)
+{
+ if (bSet)
+ m_nTransliterationFlags |= TransliterationFlags::IGNORE_WIDTH;
+ else
+ m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_WIDTH;
+}
+
+
+bool FmSearchEngine::GetIgnoreWidthCJK() const
+{
+ return bool(m_nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH);
+}
+
+
+void FmSearchEngine::SetCaseSensitive(bool bSet)
+{
+ if (bSet)
+ m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_CASE;
+ else
+ m_nTransliterationFlags |= TransliterationFlags::IGNORE_CASE;
+}
+
+
+bool FmSearchEngine::GetCaseSensitive() const
+{
+ return !(m_nTransliterationFlags & TransliterationFlags::IGNORE_CASE);
+}
+
+
+void FmSearchEngine::fillControlTexts(const InterfaceArray& arrFields)
+{
+ m_aControlTexts.clear();
+ Reference< XInterface > xCurrent;
+ for (const auto & rField : arrFields)
+ {
+ xCurrent = rField;
+ DBG_ASSERT(xCurrent.is(), "FmSearchEngine::fillControlTexts : invalid field interface !");
+ // check which type of control this is
+ Reference< css::awt::XTextComponent > xAsText(xCurrent, UNO_QUERY);
+ if (xAsText.is())
+ {
+ m_aControlTexts.emplace_back(new SimpleTextWrapper(xAsText));
+ continue;
+ }
+
+ Reference< css::awt::XListBox > xAsListBox(xCurrent, UNO_QUERY);
+ if (xAsListBox.is())
+ {
+ m_aControlTexts.emplace_back(new ListBoxWrapper(xAsListBox));
+ continue;
+ }
+
+ Reference< css::awt::XCheckBox > xAsCheckBox(xCurrent, UNO_QUERY);
+ DBG_ASSERT(xAsCheckBox.is(), "FmSearchEngine::fillControlTexts : invalid field interface (no supported type) !");
+ // we don't have any more options ...
+ m_aControlTexts.emplace_back(new CheckBoxWrapper(xAsCheckBox));
+ }
+}
+
+
+void FmSearchEngine::Init(const OUString& sVisibleFields)
+{
+ // analyze the fields
+ // additionally, create the mapping: because the list of used columns can be shorter than the list
+ // of columns of the cursor, we need a mapping: "used column number n" -> "cursor column m"
+ m_arrFieldMapping.clear();
+
+ // important: The case of the columns does not need to be exact - for instance:
+ // - a user created a form which works on a table, for which the driver returns a column name "COLUMN"
+ // - the driver itself works case-insensitive with column names
+ // - a control in the form is bound to "column" - not the different case
+ // In such a scenario, the form and the field would work okay, but we here need to case for the different case
+ // explicitly
+ // #i8755#
+
+ // so first of all, check if the database handles identifiers case sensitive
+ Reference< XConnection > xConn;
+ Reference< XDatabaseMetaData > xMeta;
+ Reference< XPropertySet > xCursorProps( IFACECAST( m_xSearchCursor ), UNO_QUERY );
+ if ( xCursorProps.is() )
+ {
+ try
+ {
+ xCursorProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ) >>= xConn;
+ }
+ catch( const Exception& ) { /* silent this - will be asserted below */ }
+ }
+ if ( xConn.is() )
+ xMeta = xConn->getMetaData();
+ OSL_ENSURE( xMeta.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" );
+
+ bool bCaseSensitiveIdentifiers = true; // assume case sensitivity
+ if ( xMeta.is() )
+ bCaseSensitiveIdentifiers = xMeta->supportsMixedCaseQuotedIdentifiers();
+
+ // now that we have this information, we need a collator which is able to case (in)sensitivity compare strings
+ m_aStringCompare.loadDefaultCollator( SvtSysLocale().GetLanguageTag().getLocale(),
+ bCaseSensitiveIdentifiers ? 0 : css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
+
+ try
+ {
+ // the cursor can give me a record (as PropertySet), which supports the DatabaseRecord service
+ Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
+ DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::Init : invalid cursor (no columns supplier) !");
+ Reference< css::container::XNameAccess > xAllFieldNames = xSupplyCols->getColumns();
+ Sequence< OUString > seqFieldNames = xAllFieldNames->getElementNames();
+
+ OUString sCurrentField;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ sCurrentField = sVisibleFields.getToken(0, ';' , nIndex);
+
+ // search in the field collection
+ sal_Int32 nFoundIndex = -1;
+ auto pFieldName = std::find_if(seqFieldNames.begin(), seqFieldNames.end(),
+ [this, &sCurrentField](const OUString& rFieldName) {
+ return 0 == m_aStringCompare.compareString( rFieldName, sCurrentField ); });
+ if (pFieldName != seqFieldNames.end())
+ nFoundIndex = static_cast<sal_Int32>(std::distance(seqFieldNames.begin(), pFieldName));
+ DBG_ASSERT(nFoundIndex != -1, "FmSearchEngine::Init : Invalid field name were given !");
+ m_arrFieldMapping.push_back(nFoundIndex);
+ }
+ while ( nIndex >= 0 );
+ }
+ catch (const Exception&)
+ {
+ OSL_FAIL("Exception occurred!");
+ }
+
+}
+
+
+void FmSearchEngine::SetFormatterUsing(bool bSet)
+{
+ if (m_bFormatter == bSet)
+ return;
+ m_bFormatter = bSet;
+
+ // I did not use a formatter, but TextComponents -> the SearchIterator needs to be adjusted
+ try
+ {
+ if (m_bFormatter)
+ {
+ DBG_ASSERT(m_xSearchCursor == m_xClonedIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
+ m_xSearchCursor = m_xOriginalIterator;
+ m_xSearchCursor.moveToBookmark(m_xClonedIterator.getBookmark());
+ // so that I continue with the new iterator at the actual place where I previously stopped
+ }
+ else
+ {
+ DBG_ASSERT(m_xSearchCursor == m_xOriginalIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
+ m_xSearchCursor = m_xClonedIterator;
+ m_xSearchCursor.moveToBookmark(m_xOriginalIterator.getBookmark());
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+
+ // I have to re-bind the fields, because the text exchange might take
+ // place over these fields and the underlying cursor has changed
+ RebuildUsedFields(m_nCurrentFieldIndex, true);
+}
+
+
+void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow)
+{
+ if (m_aProgressHandler.IsSet())
+ {
+ FmSearchProgress aProgress;
+ try
+ {
+ aProgress.aSearchState = FmSearchProgress::State::Progress;
+ aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
+ if (m_bForward)
+ aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isFirst();
+ else
+ aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isLast();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+
+ m_aProgressHandler.Call(&aProgress);
+ }
+}
+
+
+void FmSearchEngine::SearchNextImpl()
+{
+ DBG_ASSERT(!(m_bWildcard && m_bRegular) && !(m_bRegular && m_bLevenshtein) && !(m_bLevenshtein && m_bWildcard),
+ "FmSearchEngine::SearchNextImpl : search parameters are mutually exclusive!");
+
+ DBG_ASSERT(m_xSearchCursor.is(), "FmSearchEngine::SearchNextImpl : have invalid iterator!");
+
+ // the parameters of the search
+ OUString strSearchExpression(m_strSearchExpression); // I need non-const
+ if (!GetCaseSensitive())
+ // norm the string
+ strSearchExpression = m_aCharacterClassficator.lowercase(strSearchExpression);
+
+ if (!m_bRegular && !m_bLevenshtein)
+ { // 'normal' search I run through WildCards in any case, but must before adjust the OUString depending on the mode
+
+ if (!m_bWildcard)
+ { // since in all other cases * and ? in the search string are of course
+ // also allowed, but should not count as WildCards, I need to normalize
+ OUString aTmp(strSearchExpression);
+ const OUString s_sStar("\\*");
+ const OUString s_sQuotation("\\?");
+ aTmp = aTmp.replaceAll("*", s_sStar);
+ aTmp = aTmp.replaceAll("?", s_sQuotation);
+ strSearchExpression = aTmp;
+
+ switch (m_nPosition)
+ {
+ case MATCHING_ANYWHERE :
+ strSearchExpression = "*" + strSearchExpression + "*";
+ break;
+ case MATCHING_BEGINNING :
+ strSearchExpression += "*";
+ break;
+ case MATCHING_END :
+ strSearchExpression = "*" + strSearchExpression;
+ break;
+ case MATCHING_WHOLETEXT :
+ break;
+ default :
+ OSL_FAIL("FmSearchEngine::SearchNextImpl() : the methods listbox may contain only 4 entries ...");
+ }
+ }
+ }
+
+ // for work on field list
+ FieldCollection::iterator iterBegin = m_arrUsedFields.begin();
+ FieldCollection::iterator iterEnd = m_arrUsedFields.end();
+ FieldCollection::iterator iterFieldCheck;
+
+ sal_Int32 nFieldPos;
+
+ if (m_aPreviousLocBookmark.hasValue())
+ {
+ DBG_ASSERT(EQUAL_BOOKMARKS(m_aPreviousLocBookmark, m_xSearchCursor.getBookmark()),
+ "FmSearchEngine::SearchNextImpl : invalid position!");
+ iterFieldCheck = m_iterPreviousLocField;
+ // continue in the field after (or before) the last discovery
+ nFieldPos = iterFieldCheck - iterBegin;
+ MoveField(nFieldPos, iterFieldCheck, iterBegin, iterEnd);
+ }
+ else
+ {
+ if (m_bForward)
+ iterFieldCheck = iterBegin;
+ else
+ {
+ iterFieldCheck = iterEnd;
+ --iterFieldCheck;
+ }
+ nFieldPos = iterFieldCheck - iterBegin;
+ }
+
+ PropagateProgress(true);
+ SearchResult srResult;
+ if (m_eSearchForType != SearchFor::String)
+ srResult = SearchSpecial(m_eSearchForType == SearchFor::Null, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
+ else if (!m_bRegular && !m_bLevenshtein)
+ srResult = SearchWildcard(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
+ else
+ srResult = SearchRegularApprox(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
+
+ m_srResult = srResult;
+
+ if (SearchResult::Error == m_srResult)
+ return;
+
+ // found?
+ if (SearchResult::Found == m_srResult)
+ {
+ // memorize the position
+ try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
+ catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
+ m_iterPreviousLocField = iterFieldCheck;
+ }
+ else
+ // invalidate the "last discovery"
+ InvalidatePreviousLoc();
+}
+
+
+void FmSearchEngine::OnSearchTerminated()
+{
+ if (!m_aProgressHandler.IsSet())
+ return;
+
+ FmSearchProgress aProgress;
+ try
+ {
+ switch (m_srResult)
+ {
+ case SearchResult::Error :
+ aProgress.aSearchState = FmSearchProgress::State::Error;
+ break;
+ case SearchResult::Found :
+ aProgress.aSearchState = FmSearchProgress::State::Successful;
+ aProgress.aBookmark = m_aPreviousLocBookmark;
+ aProgress.nFieldIndex = m_iterPreviousLocField - m_arrUsedFields.begin();
+ break;
+ case SearchResult::NotFound :
+ aProgress.aSearchState = FmSearchProgress::State::NothingFound;
+ aProgress.aBookmark = m_xSearchCursor.getBookmark();
+ break;
+ case SearchResult::Cancelled :
+ aProgress.aSearchState = FmSearchProgress::State::Canceled;
+ aProgress.aBookmark = m_xSearchCursor.getBookmark();
+ break;
+ }
+ aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+
+ // by definition, the link must be thread-safe (I just require that),
+ // so that I do not have to worry about such things here
+ m_aProgressHandler.Call(&aProgress);
+
+ m_bSearchingCurrently = false;
+}
+
+
+IMPL_LINK(FmSearchEngine, OnNewRecordCount, sal_Int32, theCounter, void)
+{
+ if (!m_aProgressHandler.IsSet())
+ return;
+
+ FmSearchProgress aProgress;
+ aProgress.nCurrentRecord = theCounter;
+ aProgress.aSearchState = FmSearchProgress::State::ProgressCounting;
+ m_aProgressHandler.Call(&aProgress);
+}
+
+
+bool FmSearchEngine::CancelRequested()
+{
+ m_aCancelAsynchAccess.acquire();
+ bool bReturn = m_bCancelAsynchRequest;
+ m_aCancelAsynchAccess.release();
+ return bReturn;
+}
+
+
+void FmSearchEngine::CancelSearch()
+{
+ m_aCancelAsynchAccess.acquire();
+ m_bCancelAsynchRequest = true;
+ m_aCancelAsynchAccess.release();
+}
+
+
+void FmSearchEngine::SwitchToContext(const Reference< css::sdbc::XResultSet > & xCursor, const OUString& sVisibleFields, const InterfaceArray& arrFields,
+ sal_Int32 nFieldIndex)
+{
+ DBG_ASSERT(!m_bSearchingCurrently, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !");
+ if (m_bSearchingCurrently)
+ return;
+
+ m_xSearchCursor = xCursor;
+ m_xOriginalIterator = xCursor;
+ m_xClonedIterator = CursorWrapper(m_xOriginalIterator, true);
+
+ fillControlTexts(arrFields);
+
+ Init(sVisibleFields);
+ RebuildUsedFields(nFieldIndex, true);
+}
+
+
+void FmSearchEngine::ImplStartNextSearch()
+{
+ m_bCancelAsynchRequest = false;
+ m_bSearchingCurrently = true;
+
+ SearchNextImpl();
+ OnSearchTerminated();
+}
+
+
+void FmSearchEngine::SearchNext(const OUString& strExpression)
+{
+ m_strSearchExpression = strExpression;
+ m_eSearchForType = SearchFor::String;
+ ImplStartNextSearch();
+}
+
+
+void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull)
+{
+ m_eSearchForType = _bSearchForNull ? SearchFor::Null : SearchFor::NotNull;
+ ImplStartNextSearch();
+}
+
+
+void FmSearchEngine::StartOver(const OUString& strExpression)
+{
+ try
+ {
+ if (m_bForward)
+ m_xSearchCursor.first();
+ else
+ m_xSearchCursor.last();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ return;
+ }
+
+ InvalidatePreviousLoc();
+ SearchNext(strExpression);
+}
+
+
+void FmSearchEngine::StartOverSpecial(bool _bSearchForNull)
+{
+ try
+ {
+ if (m_bForward)
+ m_xSearchCursor.first();
+ else
+ m_xSearchCursor.last();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ return;
+ }
+
+ InvalidatePreviousLoc();
+ SearchNextSpecial(_bSearchForNull);
+}
+
+
+void FmSearchEngine::InvalidatePreviousLoc()
+{
+ m_aPreviousLocBookmark.clear();
+ m_iterPreviousLocField = m_arrUsedFields.end();
+}
+
+
+void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex, bool bForce)
+{
+ if (!bForce && (nFieldIndex == m_nCurrentFieldIndex))
+ return;
+ // (since I allow no change of the iterator from the outside, the same css::sdbcx::Index
+ // also always means the same column, so I have nothing to do)
+
+ DBG_ASSERT((nFieldIndex == -1) ||
+ ((nFieldIndex >= 0) &&
+ (o3tl::make_unsigned(nFieldIndex) < m_arrFieldMapping.size())),
+ "FmSearchEngine::RebuildUsedFields : nFieldIndex is invalid!");
+ // collect all fields I need to search through
+ m_arrUsedFields.clear();
+ if (nFieldIndex == -1)
+ {
+ Reference< css::container::XIndexAccess > xFields;
+ for (sal_Int32 i : m_arrFieldMapping)
+ {
+ Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
+ DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
+ xFields.set(xSupplyCols->getColumns(), UNO_QUERY);
+ BuildAndInsertFieldInfo(xFields, i);
+ }
+ }
+ else
+ {
+ Reference< css::container::XIndexAccess > xFields;
+ Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
+ DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
+ xFields.set (xSupplyCols->getColumns(), UNO_QUERY);
+ BuildAndInsertFieldInfo(xFields, m_arrFieldMapping[static_cast< size_t >(nFieldIndex)]);
+ }
+
+ m_nCurrentFieldIndex = nFieldIndex;
+ // and of course I start the next search in a virgin state again
+ InvalidatePreviousLoc();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */