1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <rtl/strbuf.hxx>
22 #include "svx/fmresids.hrc"
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/msgbox.hxx>
29 #include <svx/dialmgr.hxx>
30 #include <vcl/svapp.hxx>
31 #include <unotools/textsearch.hxx>
32 #include <com/sun/star/util/SearchOptions.hpp>
33 #include <com/sun/star/util/SearchAlgorithms.hpp>
34 #include <com/sun/star/util/SearchResult.hpp>
35 #include <com/sun/star/util/SearchFlags.hpp>
36 #include <com/sun/star/lang/Locale.hpp>
37 #include <com/sun/star/i18n/TransliterationModules.hpp>
38 #include <com/sun/star/i18n/CollatorOptions.hpp>
40 #include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
41 #include <com/sun/star/util/NumberFormatter.hpp>
42 #include <com/sun/star/util/NumberFormat.hpp>
43 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
44 #include <com/sun/star/util/XNumberFormats.hpp>
45 #include <comphelper/processfactory.hxx>
48 #include "fmservs.hxx"
49 #include "svx/fmsrcimp.hxx"
50 #include <svx/fmsearch.hxx>
52 #include <comphelper/numbers.hxx>
53 #include <unotools/syslocale.hxx>
55 #define EQUAL_BOOKMARKS(a, b) a == b
57 #define IFACECAST(c) ((const Reference< XInterface >&)c)
59 using namespace ::com::sun::star::uno
;
60 using namespace ::com::sun::star::util
;
61 using namespace ::com::sun::star::lang
;
62 using namespace ::com::sun::star::sdbc
;
63 using namespace ::com::sun::star::i18n
;
64 using namespace ::com::sun::star::beans
;
65 using namespace ::svxform
;
71 void FmSearchThread::run()
73 osl_setThreadName("FmSearchThread");
75 m_pEngine
->SearchNextImpl();
79 void FmSearchThread::onTerminated()
81 if (m_aTerminationHdl
.IsSet())
82 m_aTerminationHdl
.Call(this);
87 // = FmRecordCountListener
89 // SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject);
92 FmRecordCountListener::FmRecordCountListener(const Reference
< ::com::sun::star::sdbc::XResultSet
> & dbcCursor
)
95 m_xListening
= Reference
< ::com::sun::star::beans::XPropertySet
> (dbcCursor
, UNO_QUERY
);
96 if (!m_xListening
.is())
99 if (::comphelper::getBOOL(m_xListening
->getPropertyValue(FM_PROP_ROWCOUNTFINAL
)))
102 // there's nothing to do as the record count is already known
106 m_xListening
->addPropertyChangeListener(FM_PROP_ROWCOUNT
, (::com::sun::star::beans::XPropertyChangeListener
*)this);
110 Link
<> FmRecordCountListener::SetPropChangeHandler(const Link
<>& lnk
)
112 Link
<> lnkReturn
= m_lnkWhoWantsToKnow
;
113 m_lnkWhoWantsToKnow
= lnk
;
115 if (m_xListening
.is())
116 NotifyCurrentCount();
122 FmRecordCountListener::~FmRecordCountListener()
128 void FmRecordCountListener::DisConnect()
130 if(m_xListening
.is())
131 m_xListening
->removePropertyChangeListener(FM_PROP_ROWCOUNT
, (::com::sun::star::beans::XPropertyChangeListener
*)this);
136 void SAL_CALL
FmRecordCountListener::disposing(const ::com::sun::star::lang::EventObject
& /*Source*/) throw( RuntimeException
, std::exception
)
138 DBG_ASSERT(m_xListening
.is(), "FmRecordCountListener::disposing should never have been called without a propset !");
143 void FmRecordCountListener::NotifyCurrentCount()
145 if (m_lnkWhoWantsToKnow
.IsSet())
147 DBG_ASSERT(m_xListening
.is(), "FmRecordCountListener::NotifyCurrentCount : I have no propset ... !?");
148 void* pTheCount
= reinterpret_cast<void*>(::comphelper::getINT32(m_xListening
->getPropertyValue(FM_PROP_ROWCOUNT
)));
149 m_lnkWhoWantsToKnow
.Call(pTheCount
);
154 void FmRecordCountListener::propertyChange(const ::com::sun::star::beans::PropertyChangeEvent
& /*evt*/) throw(::com::sun::star::uno::RuntimeException
, std::exception
)
156 NotifyCurrentCount();
160 // FmSearchEngine - local classes
162 SimpleTextWrapper::SimpleTextWrapper(const Reference
< ::com::sun::star::awt::XTextComponent
> & _xText
)
163 :ControlTextWrapper(_xText
.get())
166 DBG_ASSERT(m_xText
.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !");
170 OUString
SimpleTextWrapper::getCurrentText() const
172 return m_xText
->getText();
176 ListBoxWrapper::ListBoxWrapper(const Reference
< ::com::sun::star::awt::XListBox
> & _xBox
)
177 :ControlTextWrapper(_xBox
.get())
180 DBG_ASSERT(m_xBox
.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !");
184 OUString
ListBoxWrapper::getCurrentText() const
186 return m_xBox
->getSelectedItem();
190 CheckBoxWrapper::CheckBoxWrapper(const Reference
< ::com::sun::star::awt::XCheckBox
> & _xBox
)
191 :ControlTextWrapper(_xBox
.get())
194 DBG_ASSERT(m_xBox
.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !");
198 OUString
CheckBoxWrapper::getCurrentText() const
200 switch ((TriState
)m_xBox
->getState())
202 case TRISTATE_FALSE
: return OUString("0");
203 case TRISTATE_TRUE
: return OUString("1");
212 bool FmSearchEngine::MoveCursor()
214 bool bSuccess
= true;
218 if (m_xSearchCursor
.isLast())
219 m_xSearchCursor
.first();
221 m_xSearchCursor
.next();
223 if (m_xSearchCursor
.isFirst())
225 FmRecordCountListener
* prclListener
= new FmRecordCountListener(m_xSearchCursor
);
226 prclListener
->acquire();
227 prclListener
->SetPropChangeHandler(LINK(this, FmSearchEngine
, OnNewRecordCount
));
229 m_xSearchCursor
.last();
231 prclListener
->DisConnect();
232 prclListener
->release();
235 m_xSearchCursor
.previous();
237 catch(::com::sun::star::sdbc::SQLException
const& e
)
239 #if OSL_DEBUG_LEVEL > 0
240 OStringBuffer
sDebugMessage("FmSearchEngine::MoveCursor : catched a DatabaseException (");
241 sDebugMessage
.append(OUStringToOString(e
.SQLState
, RTL_TEXTENCODING_ASCII_US
));
242 sDebugMessage
.append(") !");
243 OSL_FAIL(sDebugMessage
.getStr());
249 catch(Exception
const& e
)
251 #if OSL_DEBUG_LEVEL > 0
252 OStringBuffer
sDebugMessage("FmSearchEngine::MoveCursor : catched an Exception (");
253 sDebugMessage
.append(OUStringToOString(e
.Message
, RTL_TEXTENCODING_ASCII_US
));
254 sDebugMessage
.append(") !");
255 OSL_FAIL(sDebugMessage
.getStr());
263 OSL_FAIL("FmSearchEngine::MoveCursor : catched an unknown Exception !");
271 bool FmSearchEngine::MoveField(sal_Int32
& nPos
, FieldCollection::iterator
& iter
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
280 bSuccess
= MoveCursor();
286 if (iter
== iterBegin
)
288 bSuccess
= MoveCursor();
290 nPos
= iter
-iterBegin
;
299 void FmSearchEngine::BuildAndInsertFieldInfo(const Reference
< ::com::sun::star::container::XIndexAccess
> & xAllFields
, sal_Int32 nField
)
301 DBG_ASSERT( xAllFields
.is() && ( nField
>= 0 ) && ( nField
< xAllFields
->getCount() ),
302 "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" );
305 Reference
< XInterface
> xCurrentField
;
306 xAllFields
->getByIndex(nField
) >>= xCurrentField
;
308 // von dem weiss ich jetzt, dass es den DatabaseRecord-Service unterstuetzt (hoffe ich)
309 // fuer den FormatKey und den Typ brauche ich das PropertySet
310 Reference
< ::com::sun::star::beans::XPropertySet
> xProperties(xCurrentField
, UNO_QUERY
);
312 // die FieldInfo dazu aufbauen
314 fiCurrent
.xContents
= Reference
< ::com::sun::star::sdb::XColumn
> (xCurrentField
, UNO_QUERY
);
315 fiCurrent
.nFormatKey
= ::comphelper::getINT32(xProperties
->getPropertyValue(FM_PROP_FORMATKEY
));
316 fiCurrent
.bDoubleHandling
= false;
317 if (m_xFormatSupplier
.is())
319 Reference
< ::com::sun::star::util::XNumberFormats
> xNumberFormats(m_xFormatSupplier
->getNumberFormats());
321 sal_Int16 nFormatType
= ::comphelper::getNumberFormatType(xNumberFormats
, fiCurrent
.nFormatKey
) & ~((sal_Int16
)::com::sun::star::util::NumberFormat::DEFINED
);
322 fiCurrent
.bDoubleHandling
= (nFormatType
!= ::com::sun::star::util::NumberFormat::TEXT
);
326 m_arrUsedFields
.insert(m_arrUsedFields
.end(), fiCurrent
);
330 OUString
FmSearchEngine::FormatField(const FieldInfo
& rField
)
332 DBG_ASSERT(!m_bUsingTextComponents
, "FmSearchEngine::FormatField : im UsingTextComponents-Mode bitte FormatField(sal_Int32) benutzen !");
334 if (!m_xFormatter
.is())
336 // sonst werden Datumsflder zum Beispiel zu irgendeinem Default-Wert formatiert
341 if (rField
.bDoubleHandling
)
343 double fValue
= rField
.xContents
->getDouble();
344 if (!rField
.xContents
->wasNull())
345 sReturn
= m_xFormatter
->convertNumberToString(rField
.nFormatKey
, fValue
);
349 OUString sValue
= rField
.xContents
->getString();
350 if (!rField
.xContents
->wasNull())
351 sReturn
= m_xFormatter
->formatString(rField
.nFormatKey
, sValue
);
363 OUString
FmSearchEngine::FormatField(sal_Int32 nWhich
)
365 if (m_bUsingTextComponents
)
367 DBG_ASSERT((sal_uInt32
)nWhich
< m_aControlTexts
.size(), "FmSearchEngine::FormatField(sal_Int32) : invalid position !");
368 DBG_ASSERT(m_aControlTexts
[nWhich
] != NULL
, "FmSearchEngine::FormatField(sal_Int32) : invalid object in array !");
369 DBG_ASSERT(m_aControlTexts
[nWhich
]->getControl().is(), "FmSearchEngine::FormatField : invalid control !");
371 if (m_nCurrentFieldIndex
!= -1)
373 DBG_ASSERT((nWhich
== 0) || (nWhich
== m_nCurrentFieldIndex
), "FmSearchEngine::FormatField : Parameter nWhich ist ungueltig");
374 // analoge Situation wie unten
375 nWhich
= m_nCurrentFieldIndex
;
378 DBG_ASSERT((nWhich
>= 0) && ((sal_uInt32
)nWhich
< m_aControlTexts
.size()),
379 "FmSearchEngine::FormatField : invalid argument nWhich !");
380 return m_aControlTexts
[m_nCurrentFieldIndex
== -1 ? nWhich
: m_nCurrentFieldIndex
]->getCurrentText();
384 if (m_nCurrentFieldIndex
!= -1)
386 DBG_ASSERT((nWhich
== 0) || (nWhich
== m_nCurrentFieldIndex
), "FmSearchEngine::FormatField : Parameter nWhich ist ungueltig");
387 // ich bin im single-field-modus, da ist auch die richtige Feld-Nummer erlaubt, obwohl dann der richtige ::com::sun::star::sdbcx::Index
388 // fuer meinen Array-Zugriff natuerlich 0 ist
392 DBG_ASSERT((nWhich
>=0) && (nWhich
< (m_arrUsedFields
.end() - m_arrUsedFields
.begin())),
393 "FmSearchEngine::FormatField : Parameter nWhich ist ungueltig");
394 return FormatField(m_arrUsedFields
[nWhich
]);
399 FmSearchEngine::SEARCH_RESULT
FmSearchEngine::SearchSpecial(bool _bSearchForNull
, sal_Int32
& nFieldPos
,
400 FieldCollection::iterator
& iterFieldLoop
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
402 // die Startposition merken
404 try { aStartMark
= m_xSearchCursor
.getBookmark(); }
405 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); return SR_ERROR
; }
406 FieldCollection::iterator iterInitialField
= iterFieldLoop
;
410 bool bMovedAround(false);
413 if (m_eMode
== SM_ALLOWSCHEDULE
)
415 Application::Reschedule();
416 Application::Reschedule();
417 // do 2 reschedules because of #70226# : some things done within this loop's body may cause an user event
418 // to be posted (deep within vcl), and these user events will be handled before any keyinput or paintings
419 // or anything like that. So within each loop we create one user event and handle one user event (and no
420 // paintings and these), so the office seems to be frozen while searching.
421 // FS - 70226 - 02.12.99
424 // der aktuell zu vergleichende Inhalt
425 iterFieldLoop
->xContents
->getString(); // needed for wasNull
426 bFound
= _bSearchForNull
== bool(iterFieldLoop
->xContents
->wasNull());
430 // naechstes Feld (implizit naechster Datensatz, wenn noetig)
431 if (!MoveField(nFieldPos
, iterFieldLoop
, iterBegin
, iterEnd
))
432 { // beim Bewegen auf das naechste Feld ging was schief ... weitermachen ist nicht drin, da das naechste Mal genau
433 // das selbe bestimmt wieder schief geht, also Abbruch
434 // vorher aber noch, damit das Weitersuchen an der aktuellen Position weitermacht :
435 try { m_aPreviousLocBookmark
= m_xSearchCursor
.getBookmark(); }
436 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); }
437 m_iterPreviousLocField
= iterFieldLoop
;
442 Any aCurrentBookmark
;
443 try { aCurrentBookmark
= m_xSearchCursor
.getBookmark(); }
444 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); return SR_ERROR
; }
446 bMovedAround
= EQUAL_BOOKMARKS(aStartMark
, aCurrentBookmark
) && (iterFieldLoop
== iterInitialField
);
449 // das heisst, ich habe mich auf einen neuen Datensatz bewegt
450 PropagateProgress(bMovedAround
);
451 // if we moved to the starting position we don't have to propagate an 'overflow' message
452 // FS - 07.12.99 - 68530
454 // abbrechen gefordert ?
455 if (CancelRequested())
458 } while (!bMovedAround
);
460 return bFound
? SR_FOUND
: SR_NOTFOUND
;
464 FmSearchEngine::SEARCH_RESULT
FmSearchEngine::SearchWildcard(const OUString
& strExpression
, sal_Int32
& nFieldPos
,
465 FieldCollection::iterator
& iterFieldLoop
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
467 // die Startposition merken
469 try { aStartMark
= m_xSearchCursor
.getBookmark(); }
470 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); return SR_ERROR
; }
471 FieldCollection::iterator iterInitialField
= iterFieldLoop
;
473 WildCard
aSearchExpression(strExpression
);
477 bool bMovedAround(false);
480 if (m_eMode
== SM_ALLOWSCHEDULE
)
482 Application::Reschedule();
483 Application::Reschedule();
484 // do 2 reschedules because of #70226# : some things done within this loop's body may cause an user event
485 // to be posted (deep within vcl), and these user events will be handled before any keyinput or paintings
486 // or anything like that. So within each loop we create one user event and hanel one user event (and no
487 // paintings and these), so the office seems to be frozen while searching.
488 // FS - 70226 - 02.12.99
491 // der aktuell zu vergleichende Inhalt
492 OUString sCurrentCheck
;
494 sCurrentCheck
= FormatField(nFieldPos
);
496 sCurrentCheck
= iterFieldLoop
->xContents
->getString();
498 if (!GetCaseSensitive())
500 sCurrentCheck
= m_aCharacterClassficator
.lowercase(sCurrentCheck
);
502 // jetzt ist der Test einfach ...
503 bFound
= aSearchExpression
.Matches(sCurrentCheck
);
508 // naechstes Feld (implizit naechster Datensatz, wenn noetig)
509 if (!MoveField(nFieldPos
, iterFieldLoop
, iterBegin
, iterEnd
))
510 { // beim Bewegen auf das naechste Feld ging was schief ... weitermachen ist nicht drin, da das naechste Mal genau
511 // das selbe bestimmt wieder schief geht, also Abbruch
512 // vorher aber noch, damit das Weitersuchen an der aktuellen Position weitermacht :
513 try { m_aPreviousLocBookmark
= m_xSearchCursor
.getBookmark(); }
514 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); }
515 m_iterPreviousLocField
= iterFieldLoop
;
520 Any aCurrentBookmark
;
521 try { aCurrentBookmark
= m_xSearchCursor
.getBookmark(); }
522 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); return SR_ERROR
; }
524 bMovedAround
= EQUAL_BOOKMARKS(aStartMark
, aCurrentBookmark
) && (iterFieldLoop
== iterInitialField
);
527 // das heisst, ich habe mich auf einen neuen Datensatz bewegt
528 PropagateProgress(bMovedAround
);
529 // if we moved to the starting position we don't have to propagate an 'overflow' message
530 // FS - 07.12.99 - 68530
532 // abbrechen gefordert ?
533 if (CancelRequested())
536 } while (!bMovedAround
);
538 return bFound
? SR_FOUND
: SR_NOTFOUND
;
542 FmSearchEngine::SEARCH_RESULT
FmSearchEngine::SearchRegularApprox(const OUString
& strExpression
, sal_Int32
& nFieldPos
,
543 FieldCollection::iterator
& iterFieldLoop
, const FieldCollection::iterator
& iterBegin
, const FieldCollection::iterator
& iterEnd
)
545 DBG_ASSERT(m_bLevenshtein
|| m_bRegular
,
546 "FmSearchEngine::SearchRegularApprox : ungueltiger Suchmodus !");
547 DBG_ASSERT(!m_bLevenshtein
|| !m_bRegular
,
548 "FmSearchEngine::SearchRegularApprox : kann nicht nach regulaeren Ausdruecken und nach Aehnlichkeiten gleichzeitig suchen !");
550 // Startposition merken
552 try { aStartMark
= m_xSearchCursor
.getBookmark(); }
553 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); return SR_ERROR
; }
554 FieldCollection::iterator iterInitialField
= iterFieldLoop
;
557 SearchOptions aParam
;
558 aParam
.algorithmType
= m_bRegular
? SearchAlgorithms_REGEXP
: SearchAlgorithms_APPROXIMATE
;
559 aParam
.searchFlag
= 0;
560 aParam
.transliterateFlags
= GetTransliterationFlags();
561 if ( !GetTransliteration() )
562 { // if transliteration is not enabled, the only flags which matter are IGNORE_CASE and IGNORE_WIDTH
563 aParam
.transliterateFlags
&= TransliterationModules_IGNORE_CASE
| TransliterationModules_IGNORE_WIDTH
;
568 aParam
.searchFlag
|= SearchFlags::LEV_RELAXED
;
569 aParam
.changedChars
= m_nLevOther
;
570 aParam
.deletedChars
= m_nLevShorter
;
571 aParam
.insertedChars
= m_nLevLonger
;
573 aParam
.searchString
= strExpression
;
574 aParam
.Locale
= SvtSysLocale().GetLanguageTag().getLocale();
575 ::utl::TextSearch
aLocalEngine(aParam
);
579 bool bMovedAround(false);
582 if (m_eMode
== SM_ALLOWSCHEDULE
)
584 Application::Reschedule();
585 Application::Reschedule();
586 // do 2 reschedules because of #70226# : some things done within this loop's body may cause an user event
587 // to be posted (deep within vcl), and these user events will be handled before any keyinput or paintings
588 // or anything like that. So within each loop we create one user event and handle one user event (and no
589 // paintings and these), so the office seems to be frozen while searching.
590 // FS - 70226 - 02.12.99
593 // der aktuell zu vergleichende Inhalt
594 OUString sCurrentCheck
;
596 sCurrentCheck
= FormatField(nFieldPos
);
598 sCurrentCheck
= iterFieldLoop
->xContents
->getString();
600 // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it)
602 sal_Int32 nStart
= 0, nEnd
= sCurrentCheck
.getLength();
603 bFound
= aLocalEngine
.SearchForward(sCurrentCheck
, &nStart
, &nEnd
);
604 // das heisst hier 'forward' aber das bezieht sich nur auf die Suche innerhalb von sCurrentCheck, hat also mit
605 // der Richtung meines Datensatz-Durchwanderns nix zu tun (darum kuemmert sich MoveField)
607 // checken, ob die Position stimmt
612 case MATCHING_WHOLETEXT
:
613 if (nEnd
!= sCurrentCheck
.getLength())
618 // laeuft in den naechsten Case rein !
619 case MATCHING_BEGINNING
:
624 if (nEnd
!= sCurrentCheck
.getLength())
630 if (bFound
) // immer noch ?
633 // naechstes Feld (implizit naechster Datensatz, wenn noetig)
634 if (!MoveField(nFieldPos
, iterFieldLoop
, iterBegin
, iterEnd
))
635 { // beim Bewegen auf das naechste Feld ging was schief ... weitermachen ist nicht drin, da das naechste Mal genau
636 // das selbe bestimmt wieder schief geht, also Abbruch (ohne Fehlermeldung, von der erwarte ich, dass sie im Move
638 // vorher aber noch, damit das Weitersuchen an der aktuellen Position weitermacht :
639 try { m_aPreviousLocBookmark
= m_xSearchCursor
.getBookmark(); }
640 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); }
641 m_iterPreviousLocField
= iterFieldLoop
;
646 Any aCurrentBookmark
;
647 try { aCurrentBookmark
= m_xSearchCursor
.getBookmark(); }
648 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); return SR_ERROR
; }
649 bMovedAround
= EQUAL_BOOKMARKS(aStartMark
, aCurrentBookmark
) && (iterFieldLoop
== iterInitialField
);
652 // das heisst, ich habe mich auf einen neuen Datensatz bewegt
653 PropagateProgress(bMovedAround
);
654 // if we moved to the starting position we don't have to propagate an 'overflow' message
655 // FS - 07.12.99 - 68530
657 // abbrechen gefordert ?
658 if (CancelRequested())
661 } while (!bMovedAround
);
663 return bFound
? SR_FOUND
: SR_NOTFOUND
;
668 FmSearchEngine::FmSearchEngine(const Reference
< XComponentContext
>& _rxContext
,
669 const Reference
< XResultSet
> & xCursor
, const OUString
& sVisibleFields
,
670 const Reference
< XNumberFormatsSupplier
> & xFormatSupplier
, FMSEARCH_MODE eMode
)
672 :m_xSearchCursor(xCursor
)
673 ,m_xFormatSupplier(xFormatSupplier
)
674 ,m_aCharacterClassficator( _rxContext
, SvtSysLocale().GetLanguageTag() )
675 ,m_aStringCompare( _rxContext
)
676 ,m_nCurrentFieldIndex(-2) // -1 hat schon eine Bedeutung, also nehme ich -2 fuer 'ungueltig'
677 ,m_bUsingTextComponents(false)
678 ,m_eSearchForType(SEARCHFOR_STRING
)
679 ,m_srResult(SR_FOUND
)
680 ,m_bSearchingCurrently(false)
681 ,m_bCancelAsynchRequest(false)
687 ,m_bLevenshtein(false)
688 ,m_bTransliteration(false)
689 ,m_bLevRelaxed(false)
693 ,m_nPosition(MATCHING_ANYWHERE
)
694 ,m_nTransliterationFlags(0)
697 m_xFormatter
= Reference
< ::com::sun::star::util::XNumberFormatter
>(
698 ::com::sun::star::util::NumberFormatter::create( ::comphelper::getProcessComponentContext() ),
700 m_xFormatter
->attachNumberFormatsSupplier(m_xFormatSupplier
);
702 Init(sVisibleFields
);
706 FmSearchEngine::FmSearchEngine(const Reference
< XComponentContext
>& _rxContext
,
707 const Reference
< XResultSet
> & xCursor
, const OUString
& sVisibleFields
,
708 const InterfaceArray
& arrFields
, FMSEARCH_MODE eMode
)
709 :m_xSearchCursor(xCursor
)
710 ,m_aCharacterClassficator( _rxContext
, SvtSysLocale().GetLanguageTag() )
711 ,m_aStringCompare( _rxContext
)
712 ,m_nCurrentFieldIndex(-2) // -1 hat schon eine Bedeutung, also nehme ich -2 fuer 'ungueltig'
713 ,m_bUsingTextComponents(true)
714 ,m_xOriginalIterator(xCursor
)
715 ,m_xClonedIterator(m_xOriginalIterator
, true)
716 ,m_eSearchForType(SEARCHFOR_STRING
)
717 ,m_srResult(SR_FOUND
)
718 ,m_bSearchingCurrently(false)
719 ,m_bCancelAsynchRequest(false)
721 ,m_bFormatter(true) // das muss konsistent sein mit m_xSearchCursor, der i.A. == m_xOriginalIterator ist
725 ,m_bLevenshtein(false)
726 ,m_bTransliteration(false)
727 ,m_bLevRelaxed(false)
731 ,m_nPosition(MATCHING_ANYWHERE
)
732 ,m_nTransliterationFlags(0)
735 fillControlTexts(arrFields
);
736 Init(sVisibleFields
);
740 FmSearchEngine::~FmSearchEngine()
747 void FmSearchEngine::SetIgnoreWidthCJK(bool bSet
)
750 m_nTransliterationFlags
|= TransliterationModules_IGNORE_WIDTH
;
752 m_nTransliterationFlags
&= ~TransliterationModules_IGNORE_WIDTH
;
756 bool FmSearchEngine::GetIgnoreWidthCJK() const
758 return 0 != (m_nTransliterationFlags
& TransliterationModules_IGNORE_WIDTH
);
762 void FmSearchEngine::SetCaseSensitive(bool bSet
)
765 m_nTransliterationFlags
&= ~TransliterationModules_IGNORE_CASE
;
767 m_nTransliterationFlags
|= TransliterationModules_IGNORE_CASE
;
771 bool FmSearchEngine::GetCaseSensitive() const
773 return 0 == (m_nTransliterationFlags
& TransliterationModules_IGNORE_CASE
);
777 void FmSearchEngine::clearControlTexts()
779 for ( ControlTextSuppliers::iterator aIter
= m_aControlTexts
.begin();
780 aIter
< m_aControlTexts
.end();
786 m_aControlTexts
.clear();
790 void FmSearchEngine::fillControlTexts(const InterfaceArray
& arrFields
)
793 Reference
< XInterface
> xCurrent
;
794 for (sal_uInt32 i
=0; i
<arrFields
.size(); ++i
)
796 xCurrent
= arrFields
.at(i
);
797 DBG_ASSERT(xCurrent
.is(), "FmSearchEngine::fillControlTexts : invalid field interface !");
798 // check which type of control this is
799 Reference
< ::com::sun::star::awt::XTextComponent
> xAsText(xCurrent
, UNO_QUERY
);
802 m_aControlTexts
.insert(m_aControlTexts
.end(), new SimpleTextWrapper(xAsText
));
806 Reference
< ::com::sun::star::awt::XListBox
> xAsListBox(xCurrent
, UNO_QUERY
);
809 m_aControlTexts
.insert(m_aControlTexts
.end(), new ListBoxWrapper(xAsListBox
));
813 Reference
< ::com::sun::star::awt::XCheckBox
> xAsCheckBox(xCurrent
, UNO_QUERY
);
814 DBG_ASSERT(xAsCheckBox
.is(), "FmSearchEngine::fillControlTexts : invalid field interface (no supported type) !");
815 // we don't have any more options ...
816 m_aControlTexts
.insert(m_aControlTexts
.end(), new CheckBoxWrapper(xAsCheckBox
));
821 void FmSearchEngine::Init(const OUString
& sVisibleFields
)
823 // analyze the fields
824 // additionally, create the mapping: because the list of used columns can be shorter than the list
825 // of columns of the cursor, we need a mapping: "used column numer n" -> "cursor column m"
826 m_arrFieldMapping
.clear();
828 // important: The case of the columns does not need to be exact - for instance:
829 // - a user created a form which works on a table, for which the driver returns a column name "COLUMN"
830 // - the driver itself works case-insensitve with column names
831 // - a control in the form is bound to "column" - not the different case
832 // In such a scenario, the form and the field would work okay, but we here need to case for the different case
836 // so first of all, check if the database handles identifiers case sensitive
837 Reference
< XConnection
> xConn
;
838 Reference
< XDatabaseMetaData
> xMeta
;
839 Reference
< XPropertySet
> xCursorProps( IFACECAST( m_xSearchCursor
), UNO_QUERY
);
840 if ( xCursorProps
.is() )
844 xCursorProps
->getPropertyValue( FM_PROP_ACTIVE_CONNECTION
) >>= xConn
;
846 catch( const Exception
& ) { /* silent this - will be asserted below */ }
849 xMeta
= xConn
->getMetaData();
850 OSL_ENSURE( xMeta
.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" );
852 bool bCaseSensitiveIdentifiers
= true; // assume case sensivity
854 bCaseSensitiveIdentifiers
= xMeta
->supportsMixedCaseQuotedIdentifiers();
856 // now that we have this information, we need a collator which is able to case (in)sentively compare strings
857 m_aStringCompare
.loadDefaultCollator( SvtSysLocale().GetLanguageTag().getLocale(),
858 bCaseSensitiveIdentifiers
? 0 : ::com::sun::star::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE
);
862 // der Cursor kann mir einen Record (als PropertySet) liefern, dieser unterstuetzt den DatabaseRecord-Service
863 Reference
< ::com::sun::star::sdbcx::XColumnsSupplier
> xSupplyCols(IFACECAST(m_xSearchCursor
), UNO_QUERY
);
864 DBG_ASSERT(xSupplyCols
.is(), "FmSearchEngine::Init : invalid cursor (no columns supplier) !");
865 Reference
< ::com::sun::star::container::XNameAccess
> xAllFieldNames
= xSupplyCols
->getColumns();
866 Sequence
< OUString
> seqFieldNames
= xAllFieldNames
->getElementNames();
867 OUString
* pFieldNames
= seqFieldNames
.getArray();
870 OUString sCurrentField
;
871 OUString
sVis(sVisibleFields
.getStr());
872 sal_Int32 nIndex
= 0;
875 sCurrentField
= sVis
.getToken(0, ';' , nIndex
);
877 // in der Feld-Sammlung suchen
878 sal_Int32 nFoundIndex
= -1;
879 for (sal_Int32 j
=0; j
<seqFieldNames
.getLength(); ++j
, ++pFieldNames
)
881 if ( 0 == m_aStringCompare
.compareString( *pFieldNames
, sCurrentField
) )
887 // set the field selection back to the first
888 pFieldNames
= seqFieldNames
.getArray();
889 DBG_ASSERT(nFoundIndex
!= -1, "FmSearchEngine::Init : Invalid field name were given !");
890 m_arrFieldMapping
.push_back(nFoundIndex
);
892 while ( nIndex
>= 0 );
894 catch (const Exception
&)
896 OSL_FAIL("Exception occurred!");
902 void FmSearchEngine::SetFormatterUsing(bool bSet
)
904 if (m_bFormatter
== bSet
)
908 if (m_bUsingTextComponents
)
910 // ich benutzte keinen Formatter, sondern TextComponents -> der SearchIterator muss angepasst werden
915 DBG_ASSERT(m_xSearchCursor
== m_xClonedIterator
, "FmSearchEngine::SetFormatterUsing : inkonsistenter Zustand !");
916 m_xSearchCursor
= m_xOriginalIterator
;
917 m_xSearchCursor
.moveToBookmark(m_xClonedIterator
.getBookmark());
918 // damit ich mit dem neuen Iterator wirklich dort weitermache, wo ich vorher aufgehoert habe
922 DBG_ASSERT(m_xSearchCursor
== m_xOriginalIterator
, "FmSearchEngine::SetFormatterUsing : inkonsistenter Zustand !");
923 m_xSearchCursor
= m_xClonedIterator
;
924 m_xSearchCursor
.moveToBookmark(m_xOriginalIterator
.getBookmark());
927 catch( const Exception
& )
929 DBG_UNHANDLED_EXCEPTION();
932 // ich muss die Fields neu binden, da der Textaustausch eventuell ueber diese Fields erfolgt und sich der unterliegende Cursor
934 RebuildUsedFields(m_nCurrentFieldIndex
, true);
937 InvalidatePreviousLoc();
941 void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow
)
943 if (m_aProgressHandler
.IsSet())
945 FmSearchProgress aProgress
;
948 aProgress
.aSearchState
= FmSearchProgress::STATE_PROGRESS
;
949 aProgress
.nCurrentRecord
= m_xSearchCursor
.getRow() - 1;
951 aProgress
.bOverflow
= !_bDontPropagateOverflow
&& m_xSearchCursor
.isFirst();
953 aProgress
.bOverflow
= !_bDontPropagateOverflow
&& m_xSearchCursor
.isLast();
955 catch( const Exception
& )
957 DBG_UNHANDLED_EXCEPTION();
960 m_aProgressHandler
.Call(&aProgress
);
965 void FmSearchEngine::SearchNextImpl()
967 DBG_ASSERT(!(m_bWildcard
&& m_bRegular
) && !(m_bRegular
&& m_bLevenshtein
) && !(m_bLevenshtein
&& m_bWildcard
),
968 "FmSearchEngine::SearchNextImpl : Suchparameter schliessen sich gegenseitig aus !");
970 DBG_ASSERT(m_xSearchCursor
.is(), "FmSearchEngine::SearchNextImpl : habe ungueltigen Iterator !");
972 // die Parameter der Suche
973 OUString
strSearchExpression(m_strSearchExpression
); // brauche ich non-const
974 if (!GetCaseSensitive())
976 strSearchExpression
= m_aCharacterClassficator
.lowercase(strSearchExpression
);
978 if (!m_bRegular
&& !m_bLevenshtein
)
979 { // 'normale' Suche fuehre ich auf jeden Fall ueber WildCards durch, muss aber vorher je nach Modus den OUString anpassen
982 { // da natuerlich in allen anderen Faellen auch * und ? im Suchstring erlaubt sind, aber nicht als WildCards zaehlen
983 // sollen, muss ich normieren
984 OUString
aTmp(strSearchExpression
);
985 const OUString
s_sStar("\\*");
986 const OUString
s_sQuotation("\\?");
987 aTmp
= aTmp
.replaceAll("*", s_sStar
);
988 aTmp
= aTmp
.replaceAll("?", s_sQuotation
);
989 strSearchExpression
= aTmp
;
993 case MATCHING_ANYWHERE
:
994 strSearchExpression
= "*" + strSearchExpression
+ "*";
996 case MATCHING_BEGINNING
:
997 strSearchExpression
= strSearchExpression
+ "*";
1000 strSearchExpression
= "*" + strSearchExpression
;
1002 case MATCHING_WHOLETEXT
:
1005 OSL_FAIL("FmSearchEngine::SearchNextImpl() : die Methoden-Listbox duerfte nur 4 Eintraege enthalten ...");
1010 // fuer Arbeit auf Feldliste
1011 FieldCollection::iterator iterBegin
= m_arrUsedFields
.begin();
1012 FieldCollection::iterator iterEnd
= m_arrUsedFields
.end();
1013 FieldCollection::iterator iterFieldCheck
;
1015 sal_Int32 nFieldPos
;
1017 if (HasPreviousLoc())
1019 DBG_ASSERT(EQUAL_BOOKMARKS(m_aPreviousLocBookmark
, m_xSearchCursor
.getBookmark()),
1020 "FmSearchEngine::SearchNextImpl : ungueltige Position !");
1021 iterFieldCheck
= m_iterPreviousLocField
;
1022 // im Feld nach (oder vor) der letzten Fundstelle weitermachen
1023 nFieldPos
= iterFieldCheck
- iterBegin
;
1024 MoveField(nFieldPos
, iterFieldCheck
, iterBegin
, iterEnd
);
1029 iterFieldCheck
= iterBegin
;
1032 iterFieldCheck
= iterEnd
;
1035 nFieldPos
= iterFieldCheck
- iterBegin
;
1038 PropagateProgress(true);
1039 SEARCH_RESULT srResult
;
1040 if (m_eSearchForType
!= SEARCHFOR_STRING
)
1041 srResult
= SearchSpecial(m_eSearchForType
== SEARCHFOR_NULL
, nFieldPos
, iterFieldCheck
, iterBegin
, iterEnd
);
1042 else if (!m_bRegular
&& !m_bLevenshtein
)
1043 srResult
= SearchWildcard(strSearchExpression
, nFieldPos
, iterFieldCheck
, iterBegin
, iterEnd
);
1045 srResult
= SearchRegularApprox(strSearchExpression
, nFieldPos
, iterFieldCheck
, iterBegin
, iterEnd
);
1047 m_srResult
= srResult
;
1049 if (SR_ERROR
== m_srResult
)
1053 if (SR_FOUND
== m_srResult
)
1056 try { m_aPreviousLocBookmark
= m_xSearchCursor
.getBookmark(); }
1057 catch ( const Exception
& ) { DBG_UNHANDLED_EXCEPTION(); }
1058 m_iterPreviousLocField
= iterFieldCheck
;
1061 // die "letzte Fundstelle" invalidieren
1062 InvalidatePreviousLoc();
1066 IMPL_LINK(FmSearchEngine
, OnSearchTerminated
, FmSearchThread
*, /*pThread*/)
1068 if (!m_aProgressHandler
.IsSet())
1071 FmSearchProgress aProgress
;
1077 aProgress
.aSearchState
= FmSearchProgress::STATE_ERROR
;
1080 aProgress
.aSearchState
= FmSearchProgress::STATE_SUCCESSFULL
;
1081 aProgress
.aBookmark
= m_aPreviousLocBookmark
;
1082 aProgress
.nFieldIndex
= m_iterPreviousLocField
- m_arrUsedFields
.begin();
1085 aProgress
.aSearchState
= FmSearchProgress::STATE_NOTHINGFOUND
;
1086 aProgress
.aBookmark
= m_xSearchCursor
.getBookmark();
1089 aProgress
.aSearchState
= FmSearchProgress::STATE_CANCELED
;
1090 aProgress
.aBookmark
= m_xSearchCursor
.getBookmark();
1093 aProgress
.nCurrentRecord
= m_xSearchCursor
.getRow() - 1;
1095 catch( const Exception
& )
1097 DBG_UNHANDLED_EXCEPTION();
1100 // per definitionem muss der Link Thread-sicher sein (das verlange ich einfach), so dass ich mich um so etwas hier nicht kuemmern muss
1101 m_aProgressHandler
.Call(&aProgress
);
1103 m_bSearchingCurrently
= false;
1108 IMPL_LINK(FmSearchEngine
, OnNewRecordCount
, void*, pCounterAsVoid
)
1110 if (!m_aProgressHandler
.IsSet())
1113 FmSearchProgress aProgress
;
1114 aProgress
.nCurrentRecord
= reinterpret_cast<sal_uIntPtr
>(pCounterAsVoid
);
1115 aProgress
.aSearchState
= FmSearchProgress::STATE_PROGRESS_COUNTING
;
1116 m_aProgressHandler
.Call(&aProgress
);
1122 bool FmSearchEngine::CancelRequested()
1124 m_aCancelAsynchAccess
.acquire();
1125 bool bReturn
= m_bCancelAsynchRequest
;
1126 m_aCancelAsynchAccess
.release();
1131 void FmSearchEngine::CancelSearch()
1133 m_aCancelAsynchAccess
.acquire();
1134 m_bCancelAsynchRequest
= true;
1135 m_aCancelAsynchAccess
.release();
1139 bool FmSearchEngine::SwitchToContext(const Reference
< ::com::sun::star::sdbc::XResultSet
> & xCursor
, const OUString
& sVisibleFields
, const InterfaceArray
& arrFields
,
1140 sal_Int32 nFieldIndex
)
1142 DBG_ASSERT(!m_bSearchingCurrently
, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !");
1143 if (m_bSearchingCurrently
)
1146 m_xSearchCursor
= xCursor
;
1147 m_xOriginalIterator
= xCursor
;
1148 m_xClonedIterator
= CursorWrapper(m_xOriginalIterator
, true);
1149 m_bUsingTextComponents
= true;
1151 fillControlTexts(arrFields
);
1153 Init(sVisibleFields
);
1154 RebuildUsedFields(nFieldIndex
, true);
1160 void FmSearchEngine::ImplStartNextSearch()
1162 m_bCancelAsynchRequest
= false;
1163 m_bSearchingCurrently
= true;
1165 if (m_eMode
== SM_USETHREAD
)
1167 FmSearchThread
* pSearcher
= new FmSearchThread(this);
1168 // der loescht sich nach Beendigung selber ...
1169 pSearcher
->setTerminationHandler(LINK(this, FmSearchEngine
, OnSearchTerminated
));
1171 pSearcher
->createSuspended();
1172 pSearcher
->setPriority(osl_Thread_PriorityLowest
);
1173 pSearcher
->resume();
1178 LINK(this, FmSearchEngine
, OnSearchTerminated
).Call(NULL
);
1183 void FmSearchEngine::SearchNext(const OUString
& strExpression
)
1185 m_strSearchExpression
= strExpression
;
1186 m_eSearchForType
= SEARCHFOR_STRING
;
1187 ImplStartNextSearch();
1191 void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull
)
1193 m_eSearchForType
= _bSearchForNull
? SEARCHFOR_NULL
: SEARCHFOR_NOTNULL
;
1194 ImplStartNextSearch();
1198 void FmSearchEngine::StartOver(const OUString
& strExpression
)
1203 m_xSearchCursor
.first();
1205 m_xSearchCursor
.last();
1207 catch( const Exception
& )
1209 DBG_UNHANDLED_EXCEPTION();
1213 InvalidatePreviousLoc();
1214 SearchNext(strExpression
);
1218 void FmSearchEngine::StartOverSpecial(bool _bSearchForNull
)
1223 m_xSearchCursor
.first();
1225 m_xSearchCursor
.last();
1227 catch( const Exception
& )
1229 DBG_UNHANDLED_EXCEPTION();
1233 InvalidatePreviousLoc();
1234 SearchNextSpecial(_bSearchForNull
);
1238 void FmSearchEngine::InvalidatePreviousLoc()
1240 m_aPreviousLocBookmark
.setValue(0,cppu::UnoType
<void>::get());
1241 m_iterPreviousLocField
= m_arrUsedFields
.end();
1245 void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex
, bool bForce
)
1247 if (!bForce
&& (nFieldIndex
== m_nCurrentFieldIndex
))
1249 // (da ich keinen Wechsel des Iterators von aussen zulasse, heisst selber ::com::sun::star::sdbcx::Index auch immer selbe Spalte, also habe ich nix zu tun)
1251 DBG_ASSERT((nFieldIndex
== -1) ||
1252 ((nFieldIndex
>= 0) &&
1253 (static_cast<size_t>(nFieldIndex
) < m_arrFieldMapping
.size())),
1254 "FmSearchEngine::RebuildUsedFields : nFieldIndex is invalid!");
1255 // alle Felder, die ich durchsuchen muss, einsammeln
1256 m_arrUsedFields
.clear();
1257 if (nFieldIndex
== -1)
1259 Reference
< ::com::sun::star::container::XIndexAccess
> xFields
;
1260 for (size_t i
=0; i
<m_arrFieldMapping
.size(); ++i
)
1262 Reference
< ::com::sun::star::sdbcx::XColumnsSupplier
> xSupplyCols(IFACECAST(m_xSearchCursor
), UNO_QUERY
);
1263 DBG_ASSERT(xSupplyCols
.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1264 xFields
= Reference
< ::com::sun::star::container::XIndexAccess
> (xSupplyCols
->getColumns(), UNO_QUERY
);
1265 BuildAndInsertFieldInfo(xFields
, m_arrFieldMapping
[i
]);
1270 Reference
< ::com::sun::star::container::XIndexAccess
> xFields
;
1271 Reference
< ::com::sun::star::sdbcx::XColumnsSupplier
> xSupplyCols(IFACECAST(m_xSearchCursor
), UNO_QUERY
);
1272 DBG_ASSERT(xSupplyCols
.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1273 xFields
= Reference
< ::com::sun::star::container::XIndexAccess
> (xSupplyCols
->getColumns(), UNO_QUERY
);
1274 BuildAndInsertFieldInfo(xFields
, m_arrFieldMapping
[static_cast< size_t >(nFieldIndex
)]);
1277 m_nCurrentFieldIndex
= nFieldIndex
;
1278 // und natuerlich beginne ich die naechste Suche wieder jungfraeulich
1279 InvalidatePreviousLoc();
1282 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */