diff options
Diffstat (limited to 'svx/source/form/fmsrcimp.cxx')
-rw-r--r-- | svx/source/form/fmsrcimp.cxx | 1058 |
1 files changed, 1058 insertions, 0 deletions
diff --git a/svx/source/form/fmsrcimp.cxx b/svx/source/form/fmsrcimp.cxx new file mode 100644 index 0000000000..6bdd7a6c8f --- /dev/null +++ b/svx/source/form/fmsrcimp.cxx @@ -0,0 +1,1058 @@ +/* -*- 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 <o3tl/string_view.hxx> +#include <svx/fmtools.hxx> +#include <svx/fmsrccfg.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#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) + ,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) + ,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) + ,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(...) + { + TOOLS_WARN_EXCEPTION( "svx", "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(std::u16string_view 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, std::u16string_view 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_bCancelAsynchRequest(false) + ,m_bSearchingCurrently(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(std::u16string_view 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(); + const Sequence< OUString > seqFieldNames = xAllFieldNames->getElementNames(); + + OUString sCurrentField; + sal_Int32 nIndex = 0; + do + { + sCurrentField = o3tl::getToken(sVisibleFields, 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&) + { + TOOLS_WARN_EXCEPTION("svx.form", ""); + } + +} + + +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()) + return; + + 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); + aTmp = aTmp.replaceAll("*", "\\*"); + aTmp = aTmp.replaceAll("?", "\\?"); + 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() +{ + bool bReturn = m_bCancelAsynchRequest; + return bReturn; +} + + +void FmSearchEngine::CancelSearch() +{ + m_bCancelAsynchRequest = true; +} + + +void FmSearchEngine::SwitchToContext(const Reference< css::sdbc::XResultSet > & xCursor, std::u16string_view 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: */ |