1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20
21 #include <rtl/strbuf.hxx>
22 #include <sal/log.hxx>
23 #include <svx/fmtools.hxx>
24 #include <svx/fmsrccfg.hxx>
25 #include <tools/debug.hxx>
26 #include <tools/diagnose_ex.h>
27 #include <tools/wldcrd.hxx>
28 #include <vcl/svapp.hxx>
29 #include <unotools/textsearch.hxx>
30 #include <com/sun/star/awt/XTextComponent.hpp>
31 #include <com/sun/star/awt/XListBox.hpp>
32 #include <com/sun/star/awt/XCheckBox.hpp>
33 #include <com/sun/star/container/XIndexAccess.hpp>
34 #include <com/sun/star/util/SearchAlgorithms2.hpp>
35 #include <com/sun/star/util/SearchResult.hpp>
36 #include <com/sun/star/util/SearchFlags.hpp>
37 #include <com/sun/star/lang/Locale.hpp>
38 #include <com/sun/star/i18n/CollatorOptions.hpp>
39
40 #include <com/sun/star/sdb/XColumn.hpp>
41 #include <com/sun/star/sdbc/SQLException.hpp>
42 #include <com/sun/star/sdbc/XConnection.hpp>
43 #include <com/sun/star/sdbc/XDatabaseMetaData.hpp>
44 #include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
45 #include <com/sun/star/util/NumberFormatter.hpp>
46 #include <com/sun/star/util/NumberFormat.hpp>
47 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
48 #include <com/sun/star/util/XNumberFormats.hpp>
49
50 #include <fmprop.hxx>
51 #include <fmservs.hxx>
52 #include <svx/fmsrcimp.hxx>
53 #include <svx/fmsearch.hxx>
54
55 #include <comphelper/types.hxx>
56 #include <unotools/syslocale.hxx>
57 #include <i18nutil/searchopt.hxx>
58
59 #define EQUAL_BOOKMARKS(a, b) a == b
60
61 #define IFACECAST(c) static_cast<const Reference< XInterface >&>(c)
62
63 using namespace ::com::sun::star::uno;
64 using namespace ::com::sun::star::util;
65 using namespace ::com::sun::star::lang;
66 using namespace ::com::sun::star::sdbc;
67 using namespace ::com::sun::star::i18n;
68 using namespace ::com::sun::star::beans;
69 using namespace ::svxform;
70
71
72 // = FmRecordCountListener
73
74 // SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject);
75
76
FmRecordCountListener(const Reference<css::sdbc::XResultSet> & dbcCursor)77 FmRecordCountListener::FmRecordCountListener(const Reference< css::sdbc::XResultSet > & dbcCursor)
78 {
79
80 m_xListening.set(dbcCursor, UNO_QUERY);
81 if (!m_xListening.is())
82 return;
83
84 if (::comphelper::getBOOL(m_xListening->getPropertyValue(FM_PROP_ROWCOUNTFINAL)))
85 {
86 m_xListening = nullptr;
87 // there's nothing to do as the record count is already known
88 return;
89 }
90
91 m_xListening->addPropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
92 }
93
94
SetPropChangeHandler(const Link<sal_Int32,void> & lnk)95 void FmRecordCountListener::SetPropChangeHandler(const Link<sal_Int32,void>& lnk)
96 {
97 m_lnkWhoWantsToKnow = lnk;
98
99 if (m_xListening.is())
100 NotifyCurrentCount();
101 }
102
103
~FmRecordCountListener()104 FmRecordCountListener::~FmRecordCountListener()
105 {
106
107 }
108
109
DisConnect()110 void FmRecordCountListener::DisConnect()
111 {
112 if(m_xListening.is())
113 m_xListening->removePropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this));
114 m_xListening = nullptr;
115 }
116
117
disposing(const css::lang::EventObject &)118 void SAL_CALL FmRecordCountListener::disposing(const css::lang::EventObject& /*Source*/)
119 {
120 DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::disposing should never have been called without a propset !");
121 DisConnect();
122 }
123
124
NotifyCurrentCount()125 void FmRecordCountListener::NotifyCurrentCount()
126 {
127 if (m_lnkWhoWantsToKnow.IsSet())
128 {
129 DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::NotifyCurrentCount : I have no propset ... !?");
130 sal_Int32 theCount = ::comphelper::getINT32(m_xListening->getPropertyValue(FM_PROP_ROWCOUNT));
131 m_lnkWhoWantsToKnow.Call(theCount);
132 }
133 }
134
135
propertyChange(const css::beans::PropertyChangeEvent &)136 void FmRecordCountListener::propertyChange(const css::beans::PropertyChangeEvent& /*evt*/)
137 {
138 NotifyCurrentCount();
139 }
140
141
142 // FmSearchEngine - local classes
143
SimpleTextWrapper(const Reference<css::awt::XTextComponent> & _xText)144 SimpleTextWrapper::SimpleTextWrapper(const Reference< css::awt::XTextComponent > & _xText)
145 :ControlTextWrapper(_xText.get())
146 ,m_xText(_xText)
147 {
148 DBG_ASSERT(m_xText.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !");
149 }
150
151
getCurrentText() const152 OUString SimpleTextWrapper::getCurrentText() const
153 {
154 return m_xText->getText();
155 }
156
157
ListBoxWrapper(const Reference<css::awt::XListBox> & _xBox)158 ListBoxWrapper::ListBoxWrapper(const Reference< css::awt::XListBox > & _xBox)
159 :ControlTextWrapper(_xBox.get())
160 ,m_xBox(_xBox)
161 {
162 DBG_ASSERT(m_xBox.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !");
163 }
164
165
getCurrentText() const166 OUString ListBoxWrapper::getCurrentText() const
167 {
168 return m_xBox->getSelectedItem();
169 }
170
171
CheckBoxWrapper(const Reference<css::awt::XCheckBox> & _xBox)172 CheckBoxWrapper::CheckBoxWrapper(const Reference< css::awt::XCheckBox > & _xBox)
173 :ControlTextWrapper(_xBox.get())
174 ,m_xBox(_xBox)
175 {
176 DBG_ASSERT(m_xBox.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !");
177 }
178
179
getCurrentText() const180 OUString CheckBoxWrapper::getCurrentText() const
181 {
182 switch (static_cast<TriState>(m_xBox->getState()))
183 {
184 case TRISTATE_FALSE: return "0";
185 case TRISTATE_TRUE: return "1";
186 default: break;
187 }
188 return OUString();
189 }
190
191
192 // = FmSearchEngine
193
MoveCursor()194 bool FmSearchEngine::MoveCursor()
195 {
196 bool bSuccess = true;
197 try
198 {
199 if (m_bForward)
200 if (m_xSearchCursor.isLast())
201 m_xSearchCursor.first();
202 else
203 m_xSearchCursor.next();
204 else
205 if (m_xSearchCursor.isFirst())
206 {
207 rtl::Reference<FmRecordCountListener> prclListener = new FmRecordCountListener(m_xSearchCursor);
208 prclListener->SetPropChangeHandler(LINK(this, FmSearchEngine, OnNewRecordCount));
209
210 m_xSearchCursor.last();
211
212 prclListener->DisConnect();
213 }
214 else
215 m_xSearchCursor.previous();
216 }
217 catch(Exception const&)
218 {
219 TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor");
220 bSuccess = false;
221 }
222 catch(...)
223 {
224 OSL_FAIL("FmSearchEngine::MoveCursor : caught an unknown Exception !");
225 bSuccess = false;
226 }
227
228 return bSuccess;
229 }
230
231
MoveField(sal_Int32 & nPos,FieldCollection::iterator & iter,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)232 bool FmSearchEngine::MoveField(sal_Int32& nPos, FieldCollection::iterator& iter, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
233 {
234 bool bSuccess(true);
235 if (m_bForward)
236 {
237 ++iter;
238 ++nPos;
239 if (iter == iterEnd)
240 {
241 bSuccess = MoveCursor();
242 iter = iterBegin;
243 nPos = 0;
244 }
245 } else
246 {
247 if (iter == iterBegin)
248 {
249 bSuccess = MoveCursor();
250 iter = iterEnd;
251 nPos = iter-iterBegin;
252 }
253 --iter;
254 --nPos;
255 }
256 return bSuccess;
257 }
258
259
BuildAndInsertFieldInfo(const Reference<css::container::XIndexAccess> & xAllFields,sal_Int32 nField)260 void FmSearchEngine::BuildAndInsertFieldInfo(const Reference< css::container::XIndexAccess > & xAllFields, sal_Int32 nField)
261 {
262 DBG_ASSERT( xAllFields.is() && ( nField >= 0 ) && ( nField < xAllFields->getCount() ),
263 "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" );
264
265 // the field itself
266 Reference< XInterface > xCurrentField;
267 xAllFields->getByIndex(nField) >>= xCurrentField;
268
269 // From this I now know that it supports the DatabaseRecord service (I hope).
270 // For the FormatKey and the type I need the PropertySet.
271 Reference< css::beans::XPropertySet > xProperties(xCurrentField, UNO_QUERY);
272
273 // build the FieldInfo for that
274 FieldInfo fiCurrent;
275 fiCurrent.xContents.set(xCurrentField, UNO_QUERY);
276
277 // and memorize
278 m_arrUsedFields.insert(m_arrUsedFields.end(), fiCurrent);
279
280 }
281
FormatField(sal_Int32 nWhich)282 OUString FmSearchEngine::FormatField(sal_Int32 nWhich)
283 {
284 DBG_ASSERT(static_cast<sal_uInt32>(nWhich) < m_aControlTexts.size(), "FmSearchEngine::FormatField(sal_Int32) : invalid position !");
285 DBG_ASSERT(m_aControlTexts[nWhich], "FmSearchEngine::FormatField(sal_Int32) : invalid object in array !");
286 DBG_ASSERT(m_aControlTexts[nWhich]->getControl().is(), "FmSearchEngine::FormatField : invalid control !");
287
288 if (m_nCurrentFieldIndex != -1)
289 {
290 DBG_ASSERT((nWhich == 0) || (nWhich == m_nCurrentFieldIndex), "FmSearchEngine::FormatField : parameter nWhich is invalid");
291 // analogous situation as below
292 nWhich = m_nCurrentFieldIndex;
293 }
294
295 DBG_ASSERT((nWhich >= 0) && (static_cast<sal_uInt32>(nWhich) < m_aControlTexts.size()),
296 "FmSearchEngine::FormatField : invalid argument nWhich !");
297 return m_aControlTexts[m_nCurrentFieldIndex == -1 ? nWhich : m_nCurrentFieldIndex]->getCurrentText();
298 }
299
300
SearchSpecial(bool _bSearchForNull,sal_Int32 & nFieldPos,FieldCollection::iterator & iterFieldLoop,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)301 FmSearchEngine::SearchResult FmSearchEngine::SearchSpecial(bool _bSearchForNull, sal_Int32& nFieldPos,
302 FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
303 {
304 // memorize the start position
305 Any aStartMark;
306 try { aStartMark = m_xSearchCursor.getBookmark(); }
307 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
308 FieldCollection::const_iterator iterInitialField = iterFieldLoop;
309
310
311 bool bFound(false);
312 bool bMovedAround(false);
313 do
314 {
315 Application::Reschedule( true );
316
317 // the content to be compared currently
318 iterFieldLoop->xContents->getString(); // needed for wasNull
319 bFound = _bSearchForNull == bool(iterFieldLoop->xContents->wasNull());
320 if (bFound)
321 break;
322
323 // next field (implicitly next record, if necessary)
324 if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
325 { // When moving to the next field, something went wrong...
326 // Continuing is not possible, since the next time exactly the same
327 // will definitely go wrong again, thus abort.
328 // Before, however, so that the search continues at the current position:
329 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
330 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
331 m_iterPreviousLocField = iterFieldLoop;
332 // and leave
333 return SearchResult::Error;
334 }
335
336 Any aCurrentBookmark;
337 try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
338 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
339
340 bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
341
342 if (nFieldPos == 0)
343 // that is, I've moved to a new record
344 PropagateProgress(bMovedAround);
345 // if we moved to the starting position we don't have to propagate an 'overflow' message
346 // FS - 07.12.99 - 68530
347
348 // cancel requested?
349 if (CancelRequested())
350 return SearchResult::Cancelled;
351
352 } while (!bMovedAround);
353
354 return bFound ? SearchResult::Found : SearchResult::NotFound;
355 }
356
357
SearchWildcard(const OUString & strExpression,sal_Int32 & nFieldPos,FieldCollection::iterator & iterFieldLoop,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)358 FmSearchEngine::SearchResult FmSearchEngine::SearchWildcard(const OUString& strExpression, sal_Int32& nFieldPos,
359 FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
360 {
361 // memorize the start position
362 Any aStartMark;
363 try { aStartMark = m_xSearchCursor.getBookmark(); }
364 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
365 FieldCollection::const_iterator iterInitialField = iterFieldLoop;
366
367 WildCard aSearchExpression(strExpression);
368
369
370 bool bFound(false);
371 bool bMovedAround(false);
372 do
373 {
374 Application::Reschedule( true );
375
376 // the content to be compared currently
377 OUString sCurrentCheck;
378 if (m_bFormatter)
379 sCurrentCheck = FormatField(nFieldPos);
380 else
381 sCurrentCheck = iterFieldLoop->xContents->getString();
382
383 if (!GetCaseSensitive())
384 // norm the string
385 sCurrentCheck = m_aCharacterClassficator.lowercase(sCurrentCheck);
386
387 // now the test is easy...
388 bFound = aSearchExpression.Matches(sCurrentCheck);
389
390 if (bFound)
391 break;
392
393 // next field (implicitly next record, if necessary)
394 if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
395 { // When moving to the next field, something went wrong...
396 // Continuing is not possible, since the next time exactly the same
397 // will definitely go wrong again, thus abort.
398 // Before, however, so that the search continues at the current position:
399 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
400 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
401 m_iterPreviousLocField = iterFieldLoop;
402 // and leave
403 return SearchResult::Error;
404 }
405
406 Any aCurrentBookmark;
407 try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
408 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
409
410 bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
411
412 if (nFieldPos == 0)
413 // that is, I've moved to a new record
414 PropagateProgress(bMovedAround);
415 // if we moved to the starting position we don't have to propagate an 'overflow' message
416 // FS - 07.12.99 - 68530
417
418 // cancel requested?
419 if (CancelRequested())
420 return SearchResult::Cancelled;
421
422 } while (!bMovedAround);
423
424 return bFound ? SearchResult::Found : SearchResult::NotFound;
425 }
426
427
SearchRegularApprox(const OUString & strExpression,sal_Int32 & nFieldPos,FieldCollection::iterator & iterFieldLoop,const FieldCollection::iterator & iterBegin,const FieldCollection::iterator & iterEnd)428 FmSearchEngine::SearchResult FmSearchEngine::SearchRegularApprox(const OUString& strExpression, sal_Int32& nFieldPos,
429 FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd)
430 {
431 DBG_ASSERT(m_bLevenshtein || m_bRegular,
432 "FmSearchEngine::SearchRegularApprox : invalid search mode!");
433 DBG_ASSERT(!m_bLevenshtein || !m_bRegular,
434 "FmSearchEngine::SearchRegularApprox : cannot search for regular expressions and similarities at the same time!");
435
436 // memorize start position
437 Any aStartMark;
438 try { aStartMark = m_xSearchCursor.getBookmark(); }
439 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
440 FieldCollection::const_iterator iterInitialField = iterFieldLoop;
441
442 // collect parameters
443 i18nutil::SearchOptions2 aParam;
444 aParam.AlgorithmType2 = m_bRegular ? SearchAlgorithms2::REGEXP : SearchAlgorithms2::APPROXIMATE;
445 aParam.searchFlag = 0;
446 aParam.transliterateFlags = GetTransliterationFlags();
447 if ( !GetTransliteration() )
448 { // if transliteration is not enabled, the only flags which matter are IGNORE_CASE and IGNORE_WIDTH
449 aParam.transliterateFlags &= TransliterationFlags::IGNORE_CASE | TransliterationFlags::IGNORE_WIDTH;
450 }
451 if (m_bLevenshtein)
452 {
453 if (m_bLevRelaxed)
454 aParam.searchFlag |= SearchFlags::LEV_RELAXED;
455 aParam.changedChars = m_nLevOther;
456 aParam.deletedChars = m_nLevShorter;
457 aParam.insertedChars = m_nLevLonger;
458 }
459 aParam.searchString = strExpression;
460 aParam.Locale = SvtSysLocale().GetLanguageTag().getLocale();
461 ::utl::TextSearch aLocalEngine( aParam);
462
463
464 bool bFound = false;
465 bool bMovedAround(false);
466 do
467 {
468 Application::Reschedule( true );
469
470 // the content to be compared currently
471 OUString sCurrentCheck;
472 if (m_bFormatter)
473 sCurrentCheck = FormatField(nFieldPos);
474 else
475 sCurrentCheck = iterFieldLoop->xContents->getString();
476
477 // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it)
478
479 sal_Int32 nStart = 0, nEnd = sCurrentCheck.getLength();
480 bFound = aLocalEngine.SearchForward(sCurrentCheck, &nStart, &nEnd);
481 // it says 'forward' here, but that only refers to the search within
482 // sCurrentCheck, so it has nothing to do with the direction of my
483 // record migration (MoveField takes care of that)
484
485 // check if the position is correct
486 if (bFound)
487 {
488 switch (m_nPosition)
489 {
490 case MATCHING_WHOLETEXT :
491 if (nEnd != sCurrentCheck.getLength())
492 {
493 bFound = false;
494 break;
495 }
496 [[fallthrough]];
497 case MATCHING_BEGINNING :
498 if (nStart != 0)
499 bFound = false;
500 break;
501 case MATCHING_END :
502 if (nEnd != sCurrentCheck.getLength())
503 bFound = false;
504 break;
505 }
506 }
507
508 if (bFound) // still?
509 break;
510
511 // next field (implicitly next record, if necessary)
512 if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd))
513 { // When moving to the next field, something went wrong...
514 // Continuing is not possible, since the next time exactly the same
515 // will definitely go wrong again, thus abort (without error
516 // notification, I expect it to be displayed in the Move).
517 // Before, however, so that the search continues at the current position:
518 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
519 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
520 m_iterPreviousLocField = iterFieldLoop;
521 // and leave
522 return SearchResult::Error;
523 }
524
525 Any aCurrentBookmark;
526 try { aCurrentBookmark = m_xSearchCursor.getBookmark(); }
527 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; }
528 bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField);
529
530 if (nFieldPos == 0)
531 // that is, I've moved to a new record
532 PropagateProgress(bMovedAround);
533 // if we moved to the starting position we don't have to propagate an 'overflow' message
534 // FS - 07.12.99 - 68530
535
536 // cancel requested?
537 if (CancelRequested())
538 return SearchResult::Cancelled;
539
540 } while (!bMovedAround);
541
542 return bFound ? SearchResult::Found : SearchResult::NotFound;
543 }
544
545
FmSearchEngine(const Reference<XComponentContext> & _rxContext,const Reference<XResultSet> & xCursor,const OUString & sVisibleFields,const InterfaceArray & arrFields)546 FmSearchEngine::FmSearchEngine(const Reference< XComponentContext >& _rxContext,
547 const Reference< XResultSet > & xCursor, const OUString& sVisibleFields,
548 const InterfaceArray& arrFields)
549 :m_xSearchCursor(xCursor)
550 ,m_aCharacterClassficator( _rxContext, SvtSysLocale().GetLanguageTag() )
551 ,m_aStringCompare( _rxContext )
552 ,m_nCurrentFieldIndex(-2) // -1 already has a meaning, so I take -2 for 'invalid'
553 ,m_xOriginalIterator(xCursor)
554 ,m_xClonedIterator(m_xOriginalIterator, true)
555 ,m_eSearchForType(SearchFor::String)
556 ,m_srResult(SearchResult::Found)
557 ,m_bSearchingCurrently(false)
558 ,m_bCancelAsynchRequest(false)
559 ,m_bFormatter(true) // this must be consistent with m_xSearchCursor, which is generally == m_xOriginalIterator
560 ,m_bForward(false)
561 ,m_bWildcard(false)
562 ,m_bRegular(false)
563 ,m_bLevenshtein(false)
564 ,m_bTransliteration(false)
565 ,m_bLevRelaxed(false)
566 ,m_nLevOther(0)
567 ,m_nLevShorter(0)
568 ,m_nLevLonger(0)
569 ,m_nPosition(MATCHING_ANYWHERE)
570 ,m_nTransliterationFlags(TransliterationFlags::NONE)
571 {
572
573 fillControlTexts(arrFields);
574 Init(sVisibleFields);
575 }
576
577
SetIgnoreWidthCJK(bool bSet)578 void FmSearchEngine::SetIgnoreWidthCJK(bool bSet)
579 {
580 if (bSet)
581 m_nTransliterationFlags |= TransliterationFlags::IGNORE_WIDTH;
582 else
583 m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_WIDTH;
584 }
585
586
GetIgnoreWidthCJK() const587 bool FmSearchEngine::GetIgnoreWidthCJK() const
588 {
589 return bool(m_nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH);
590 }
591
592
SetCaseSensitive(bool bSet)593 void FmSearchEngine::SetCaseSensitive(bool bSet)
594 {
595 if (bSet)
596 m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_CASE;
597 else
598 m_nTransliterationFlags |= TransliterationFlags::IGNORE_CASE;
599 }
600
601
GetCaseSensitive() const602 bool FmSearchEngine::GetCaseSensitive() const
603 {
604 return !(m_nTransliterationFlags & TransliterationFlags::IGNORE_CASE);
605 }
606
607
clearControlTexts()608 void FmSearchEngine::clearControlTexts()
609 {
610 m_aControlTexts.clear();
611 }
612
613
fillControlTexts(const InterfaceArray & arrFields)614 void FmSearchEngine::fillControlTexts(const InterfaceArray& arrFields)
615 {
616 clearControlTexts();
617 Reference< XInterface > xCurrent;
618 for (const auto & rField : arrFields)
619 {
620 xCurrent = rField;
621 DBG_ASSERT(xCurrent.is(), "FmSearchEngine::fillControlTexts : invalid field interface !");
622 // check which type of control this is
623 Reference< css::awt::XTextComponent > xAsText(xCurrent, UNO_QUERY);
624 if (xAsText.is())
625 {
626 m_aControlTexts.emplace_back(new SimpleTextWrapper(xAsText));
627 continue;
628 }
629
630 Reference< css::awt::XListBox > xAsListBox(xCurrent, UNO_QUERY);
631 if (xAsListBox.is())
632 {
633 m_aControlTexts.emplace_back(new ListBoxWrapper(xAsListBox));
634 continue;
635 }
636
637 Reference< css::awt::XCheckBox > xAsCheckBox(xCurrent, UNO_QUERY);
638 DBG_ASSERT(xAsCheckBox.is(), "FmSearchEngine::fillControlTexts : invalid field interface (no supported type) !");
639 // we don't have any more options ...
640 m_aControlTexts.emplace_back(new CheckBoxWrapper(xAsCheckBox));
641 }
642 }
643
644
Init(const OUString & sVisibleFields)645 void FmSearchEngine::Init(const OUString& sVisibleFields)
646 {
647 // analyze the fields
648 // additionally, create the mapping: because the list of used columns can be shorter than the list
649 // of columns of the cursor, we need a mapping: "used column number n" -> "cursor column m"
650 m_arrFieldMapping.clear();
651
652 // important: The case of the columns does not need to be exact - for instance:
653 // - a user created a form which works on a table, for which the driver returns a column name "COLUMN"
654 // - the driver itself works case-insensitive with column names
655 // - a control in the form is bound to "column" - not the different case
656 // In such a scenario, the form and the field would work okay, but we here need to case for the different case
657 // explicitly
658 // #i8755#
659
660 // so first of all, check if the database handles identifiers case sensitive
661 Reference< XConnection > xConn;
662 Reference< XDatabaseMetaData > xMeta;
663 Reference< XPropertySet > xCursorProps( IFACECAST( m_xSearchCursor ), UNO_QUERY );
664 if ( xCursorProps.is() )
665 {
666 try
667 {
668 xCursorProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ) >>= xConn;
669 }
670 catch( const Exception& ) { /* silent this - will be asserted below */ }
671 }
672 if ( xConn.is() )
673 xMeta = xConn->getMetaData();
674 OSL_ENSURE( xMeta.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" );
675
676 bool bCaseSensitiveIdentifiers = true; // assume case sensitivity
677 if ( xMeta.is() )
678 bCaseSensitiveIdentifiers = xMeta->supportsMixedCaseQuotedIdentifiers();
679
680 // now that we have this information, we need a collator which is able to case (in)sensitivity compare strings
681 m_aStringCompare.loadDefaultCollator( SvtSysLocale().GetLanguageTag().getLocale(),
682 bCaseSensitiveIdentifiers ? 0 : css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
683
684 try
685 {
686 // the cursor can give me a record (as PropertySet), which supports the DatabaseRecord service
687 Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
688 DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::Init : invalid cursor (no columns supplier) !");
689 Reference< css::container::XNameAccess > xAllFieldNames = xSupplyCols->getColumns();
690 Sequence< OUString > seqFieldNames = xAllFieldNames->getElementNames();
691
692 OUString sCurrentField;
693 sal_Int32 nIndex = 0;
694 do
695 {
696 sCurrentField = sVisibleFields.getToken(0, ';' , nIndex);
697
698 // search in the field collection
699 sal_Int32 nFoundIndex = -1;
700 auto pFieldName = std::find_if(seqFieldNames.begin(), seqFieldNames.end(),
701 [this, &sCurrentField](const OUString& rFieldName) {
702 return 0 == m_aStringCompare.compareString( rFieldName, sCurrentField ); });
703 if (pFieldName != seqFieldNames.end())
704 nFoundIndex = static_cast<sal_Int32>(std::distance(seqFieldNames.begin(), pFieldName));
705 DBG_ASSERT(nFoundIndex != -1, "FmSearchEngine::Init : Invalid field name were given !");
706 m_arrFieldMapping.push_back(nFoundIndex);
707 }
708 while ( nIndex >= 0 );
709 }
710 catch (const Exception&)
711 {
712 OSL_FAIL("Exception occurred!");
713 }
714
715 }
716
717
SetFormatterUsing(bool bSet)718 void FmSearchEngine::SetFormatterUsing(bool bSet)
719 {
720 if (m_bFormatter == bSet)
721 return;
722 m_bFormatter = bSet;
723
724 // I did not use a formatter, but TextComponents -> the SearchIterator needs to be adjusted
725 try
726 {
727 if (m_bFormatter)
728 {
729 DBG_ASSERT(m_xSearchCursor == m_xClonedIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
730 m_xSearchCursor = m_xOriginalIterator;
731 m_xSearchCursor.moveToBookmark(m_xClonedIterator.getBookmark());
732 // so that I continue with the new iterator at the actual place where I previously stopped
733 }
734 else
735 {
736 DBG_ASSERT(m_xSearchCursor == m_xOriginalIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !");
737 m_xSearchCursor = m_xClonedIterator;
738 m_xSearchCursor.moveToBookmark(m_xOriginalIterator.getBookmark());
739 }
740 }
741 catch( const Exception& )
742 {
743 DBG_UNHANDLED_EXCEPTION("svx");
744 }
745
746 // I have to re-bind the fields, because the text exchange might take
747 // place over these fields and the underlying cursor has changed
748 RebuildUsedFields(m_nCurrentFieldIndex, true);
749 }
750
751
PropagateProgress(bool _bDontPropagateOverflow)752 void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow)
753 {
754 if (m_aProgressHandler.IsSet())
755 {
756 FmSearchProgress aProgress;
757 try
758 {
759 aProgress.aSearchState = FmSearchProgress::State::Progress;
760 aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
761 if (m_bForward)
762 aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isFirst();
763 else
764 aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isLast();
765 }
766 catch( const Exception& )
767 {
768 DBG_UNHANDLED_EXCEPTION("svx");
769 }
770
771 m_aProgressHandler.Call(&aProgress);
772 }
773 }
774
775
SearchNextImpl()776 void FmSearchEngine::SearchNextImpl()
777 {
778 DBG_ASSERT(!(m_bWildcard && m_bRegular) && !(m_bRegular && m_bLevenshtein) && !(m_bLevenshtein && m_bWildcard),
779 "FmSearchEngine::SearchNextImpl : search parameters are mutually exclusive!");
780
781 DBG_ASSERT(m_xSearchCursor.is(), "FmSearchEngine::SearchNextImpl : have invalid iterator!");
782
783 // the parameters of the search
784 OUString strSearchExpression(m_strSearchExpression); // I need non-const
785 if (!GetCaseSensitive())
786 // norm the string
787 strSearchExpression = m_aCharacterClassficator.lowercase(strSearchExpression);
788
789 if (!m_bRegular && !m_bLevenshtein)
790 { // 'normal' search I run through WildCards in any case, but must before adjust the OUString depending on the mode
791
792 if (!m_bWildcard)
793 { // since in all other cases * and ? in the search string are of course
794 // also allowed, but should not count as WildCards, I need to normalize
795 OUString aTmp(strSearchExpression);
796 const OUString s_sStar("\\*");
797 const OUString s_sQuotation("\\?");
798 aTmp = aTmp.replaceAll("*", s_sStar);
799 aTmp = aTmp.replaceAll("?", s_sQuotation);
800 strSearchExpression = aTmp;
801
802 switch (m_nPosition)
803 {
804 case MATCHING_ANYWHERE :
805 strSearchExpression = "*" + strSearchExpression + "*";
806 break;
807 case MATCHING_BEGINNING :
808 strSearchExpression += "*";
809 break;
810 case MATCHING_END :
811 strSearchExpression = "*" + strSearchExpression;
812 break;
813 case MATCHING_WHOLETEXT :
814 break;
815 default :
816 OSL_FAIL("FmSearchEngine::SearchNextImpl() : the methods listbox may contain only 4 entries ...");
817 }
818 }
819 }
820
821 // for work on field list
822 FieldCollection::iterator iterBegin = m_arrUsedFields.begin();
823 FieldCollection::iterator iterEnd = m_arrUsedFields.end();
824 FieldCollection::iterator iterFieldCheck;
825
826 sal_Int32 nFieldPos;
827
828 if (m_aPreviousLocBookmark.hasValue())
829 {
830 DBG_ASSERT(EQUAL_BOOKMARKS(m_aPreviousLocBookmark, m_xSearchCursor.getBookmark()),
831 "FmSearchEngine::SearchNextImpl : invalid position!");
832 iterFieldCheck = m_iterPreviousLocField;
833 // continue in the field after (or before) the last discovery
834 nFieldPos = iterFieldCheck - iterBegin;
835 MoveField(nFieldPos, iterFieldCheck, iterBegin, iterEnd);
836 }
837 else
838 {
839 if (m_bForward)
840 iterFieldCheck = iterBegin;
841 else
842 {
843 iterFieldCheck = iterEnd;
844 --iterFieldCheck;
845 }
846 nFieldPos = iterFieldCheck - iterBegin;
847 }
848
849 PropagateProgress(true);
850 SearchResult srResult;
851 if (m_eSearchForType != SearchFor::String)
852 srResult = SearchSpecial(m_eSearchForType == SearchFor::Null, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
853 else if (!m_bRegular && !m_bLevenshtein)
854 srResult = SearchWildcard(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
855 else
856 srResult = SearchRegularApprox(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd);
857
858 m_srResult = srResult;
859
860 if (SearchResult::Error == m_srResult)
861 return;
862
863 // found?
864 if (SearchResult::Found == m_srResult)
865 {
866 // memorize the position
867 try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); }
868 catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); }
869 m_iterPreviousLocField = iterFieldCheck;
870 }
871 else
872 // invalidate the "last discovery"
873 InvalidatePreviousLoc();
874 }
875
876
OnSearchTerminated()877 void FmSearchEngine::OnSearchTerminated()
878 {
879 if (!m_aProgressHandler.IsSet())
880 return;
881
882 FmSearchProgress aProgress;
883 try
884 {
885 switch (m_srResult)
886 {
887 case SearchResult::Error :
888 aProgress.aSearchState = FmSearchProgress::State::Error;
889 break;
890 case SearchResult::Found :
891 aProgress.aSearchState = FmSearchProgress::State::Successful;
892 aProgress.aBookmark = m_aPreviousLocBookmark;
893 aProgress.nFieldIndex = m_iterPreviousLocField - m_arrUsedFields.begin();
894 break;
895 case SearchResult::NotFound :
896 aProgress.aSearchState = FmSearchProgress::State::NothingFound;
897 aProgress.aBookmark = m_xSearchCursor.getBookmark();
898 break;
899 case SearchResult::Cancelled :
900 aProgress.aSearchState = FmSearchProgress::State::Canceled;
901 aProgress.aBookmark = m_xSearchCursor.getBookmark();
902 break;
903 }
904 aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1;
905 }
906 catch( const Exception& )
907 {
908 DBG_UNHANDLED_EXCEPTION("svx");
909 }
910
911 // by definition, the link must be thread-safe (I just require that),
912 // so that I do not have to worry about such things here
913 m_aProgressHandler.Call(&aProgress);
914
915 m_bSearchingCurrently = false;
916 }
917
918
IMPL_LINK(FmSearchEngine,OnNewRecordCount,sal_Int32,theCounter,void)919 IMPL_LINK(FmSearchEngine, OnNewRecordCount, sal_Int32, theCounter, void)
920 {
921 if (!m_aProgressHandler.IsSet())
922 return;
923
924 FmSearchProgress aProgress;
925 aProgress.nCurrentRecord = theCounter;
926 aProgress.aSearchState = FmSearchProgress::State::ProgressCounting;
927 m_aProgressHandler.Call(&aProgress);
928 }
929
930
CancelRequested()931 bool FmSearchEngine::CancelRequested()
932 {
933 m_aCancelAsynchAccess.acquire();
934 bool bReturn = m_bCancelAsynchRequest;
935 m_aCancelAsynchAccess.release();
936 return bReturn;
937 }
938
939
CancelSearch()940 void FmSearchEngine::CancelSearch()
941 {
942 m_aCancelAsynchAccess.acquire();
943 m_bCancelAsynchRequest = true;
944 m_aCancelAsynchAccess.release();
945 }
946
947
SwitchToContext(const Reference<css::sdbc::XResultSet> & xCursor,const OUString & sVisibleFields,const InterfaceArray & arrFields,sal_Int32 nFieldIndex)948 void FmSearchEngine::SwitchToContext(const Reference< css::sdbc::XResultSet > & xCursor, const OUString& sVisibleFields, const InterfaceArray& arrFields,
949 sal_Int32 nFieldIndex)
950 {
951 DBG_ASSERT(!m_bSearchingCurrently, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !");
952 if (m_bSearchingCurrently)
953 return;
954
955 m_xSearchCursor = xCursor;
956 m_xOriginalIterator = xCursor;
957 m_xClonedIterator = CursorWrapper(m_xOriginalIterator, true);
958
959 fillControlTexts(arrFields);
960
961 Init(sVisibleFields);
962 RebuildUsedFields(nFieldIndex, true);
963 }
964
965
ImplStartNextSearch()966 void FmSearchEngine::ImplStartNextSearch()
967 {
968 m_bCancelAsynchRequest = false;
969 m_bSearchingCurrently = true;
970
971 SearchNextImpl();
972 OnSearchTerminated();
973 }
974
975
SearchNext(const OUString & strExpression)976 void FmSearchEngine::SearchNext(const OUString& strExpression)
977 {
978 m_strSearchExpression = strExpression;
979 m_eSearchForType = SearchFor::String;
980 ImplStartNextSearch();
981 }
982
983
SearchNextSpecial(bool _bSearchForNull)984 void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull)
985 {
986 m_eSearchForType = _bSearchForNull ? SearchFor::Null : SearchFor::NotNull;
987 ImplStartNextSearch();
988 }
989
990
StartOver(const OUString & strExpression)991 void FmSearchEngine::StartOver(const OUString& strExpression)
992 {
993 try
994 {
995 if (m_bForward)
996 m_xSearchCursor.first();
997 else
998 m_xSearchCursor.last();
999 }
1000 catch( const Exception& )
1001 {
1002 DBG_UNHANDLED_EXCEPTION("svx");
1003 return;
1004 }
1005
1006 InvalidatePreviousLoc();
1007 SearchNext(strExpression);
1008 }
1009
1010
StartOverSpecial(bool _bSearchForNull)1011 void FmSearchEngine::StartOverSpecial(bool _bSearchForNull)
1012 {
1013 try
1014 {
1015 if (m_bForward)
1016 m_xSearchCursor.first();
1017 else
1018 m_xSearchCursor.last();
1019 }
1020 catch( const Exception& )
1021 {
1022 DBG_UNHANDLED_EXCEPTION("svx");
1023 return;
1024 }
1025
1026 InvalidatePreviousLoc();
1027 SearchNextSpecial(_bSearchForNull);
1028 }
1029
1030
InvalidatePreviousLoc()1031 void FmSearchEngine::InvalidatePreviousLoc()
1032 {
1033 m_aPreviousLocBookmark.clear();
1034 m_iterPreviousLocField = m_arrUsedFields.end();
1035 }
1036
1037
RebuildUsedFields(sal_Int32 nFieldIndex,bool bForce)1038 void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex, bool bForce)
1039 {
1040 if (!bForce && (nFieldIndex == m_nCurrentFieldIndex))
1041 return;
1042 // (since I allow no change of the iterator from the outside, the same css::sdbcx::Index
1043 // also always means the same column, so I have nothing to do)
1044
1045 DBG_ASSERT((nFieldIndex == -1) ||
1046 ((nFieldIndex >= 0) &&
1047 (static_cast<size_t>(nFieldIndex) < m_arrFieldMapping.size())),
1048 "FmSearchEngine::RebuildUsedFields : nFieldIndex is invalid!");
1049 // collect all fields I need to search through
1050 m_arrUsedFields.clear();
1051 if (nFieldIndex == -1)
1052 {
1053 Reference< css::container::XIndexAccess > xFields;
1054 for (sal_Int32 i : m_arrFieldMapping)
1055 {
1056 Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
1057 DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1058 xFields.set(xSupplyCols->getColumns(), UNO_QUERY);
1059 BuildAndInsertFieldInfo(xFields, i);
1060 }
1061 }
1062 else
1063 {
1064 Reference< css::container::XIndexAccess > xFields;
1065 Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY);
1066 DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !");
1067 xFields.set (xSupplyCols->getColumns(), UNO_QUERY);
1068 BuildAndInsertFieldInfo(xFields, m_arrFieldMapping[static_cast< size_t >(nFieldIndex)]);
1069 }
1070
1071 m_nCurrentFieldIndex = nFieldIndex;
1072 // and of course I start the next search in a virgin state again
1073 InvalidatePreviousLoc();
1074 }
1075
1076 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1077