1 /* -*- mode: C++; c-file-style: "gnu" -*-
3 Author: Marc Mutz <Marc@Mutz.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "kmsearchpattern.h"
21 #include "kmaddrbook.h"
22 #include "kmmsgdict.h"
23 #include "filterlog.h"
24 using KMail::FilterLog
;
28 #include <kpimutils/email.h>
34 #include <kconfiggroup.h>
36 #include <kabc/stdaddressbook.h>
41 #include <mimelib/string.h>
42 #include <mimelib/boyermor.h>
46 static const char* funcConfigNames
[] =
47 { "contains", "contains-not", "equals", "not-equal", "regexp",
48 "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal",
49 "is-in-addressbook", "is-not-in-addressbook", "is-in-category", "is-not-in-category",
50 "has-attachment", "has-no-attachment"};
51 static const int numFuncConfigNames
= sizeof funcConfigNames
/ sizeof *funcConfigNames
;
58 static struct _statusNames statusNames
[] = {
59 { "Important", MessageStatus::statusImportant() },
60 { "New", MessageStatus::statusNew() },
61 { "Unread", MessageStatus::statusNewAndUnread() },
62 { "Read", MessageStatus::statusRead() },
63 { "Old", MessageStatus::statusOld() },
64 { "Deleted", MessageStatus::statusDeleted() },
65 { "Replied", MessageStatus::statusReplied() },
66 { "Forwarded", MessageStatus::statusForwarded() },
67 { "Queued", MessageStatus::statusQueued() },
68 { "Sent", MessageStatus::statusSent() },
69 { "Watched", MessageStatus::statusWatched() },
70 { "Ignored", MessageStatus::statusIgnored() },
71 { "Action Item", MessageStatus::statusToAct() },
72 { "Spam", MessageStatus::statusSpam() },
73 { "Ham", MessageStatus::statusHam() },
74 { "Has Attachment", MessageStatus::statusHasAttachment() }
77 static const int numStatusNames
= sizeof statusNames
/ sizeof ( struct _statusNames
);
80 //==================================================
82 // class KMSearchRule (was: KMFilterRule)
84 //==================================================
86 KMSearchRule::KMSearchRule( const QByteArray
& field
, Function func
, const QString
& contents
)
93 KMSearchRule::KMSearchRule( const KMSearchRule
& other
)
94 : mField( other
.mField
),
95 mFunction( other
.mFunction
),
96 mContents( other
.mContents
)
100 const KMSearchRule
& KMSearchRule::operator=( const KMSearchRule
& other
) {
101 if ( this == &other
)
104 mField
= other
.mField
;
105 mFunction
= other
.mFunction
;
106 mContents
= other
.mContents
;
111 KMSearchRule
* KMSearchRule::createInstance( const QByteArray
& field
,
113 const QString
& contents
)
115 KMSearchRule
*ret
= 0;
116 if (field
== "<status>")
117 ret
= new KMSearchRuleStatus( field
, func
, contents
);
118 else if ( field
== "<age in days>" || field
== "<size>" )
119 ret
= new KMSearchRuleNumerical( field
, func
, contents
);
121 ret
= new KMSearchRuleString( field
, func
, contents
);
126 KMSearchRule
* KMSearchRule::createInstance( const QByteArray
& field
,
128 const QString
& contents
)
130 return ( createInstance( field
, configValueToFunc( func
), contents
) );
133 KMSearchRule
* KMSearchRule::createInstance( const KMSearchRule
& other
)
135 return ( createInstance( other
.field(), other
.function(), other
.contents() ) );
138 KMSearchRule
* KMSearchRule::createInstanceFromConfig( const KConfigGroup
& config
, int aIdx
)
140 const char cIdx
= char( int('A') + aIdx
);
142 static const QString
& field
= KGlobal::staticQString( "field" );
143 static const QString
& func
= KGlobal::staticQString( "func" );
144 static const QString
& contents
= KGlobal::staticQString( "contents" );
146 const QByteArray
&field2
= config
.readEntry( field
+ cIdx
, QString() ).toLatin1();
147 Function func2
= configValueToFunc( config
.readEntry( func
+ cIdx
, QString() ).toLatin1() );
148 const QString
& contents2
= config
.readEntry( contents
+ cIdx
, QString() );
150 if ( field2
== "<To or Cc>" ) // backwards compat
151 return KMSearchRule::createInstance( "<recipients>", func2
, contents2
);
153 return KMSearchRule::createInstance( field2
, func2
, contents2
);
156 KMSearchRule::Function
KMSearchRule::configValueToFunc( const char * str
) {
160 for ( int i
= 0 ; i
< numFuncConfigNames
; ++i
)
161 if ( qstricmp( funcConfigNames
[i
], str
) == 0 ) return (Function
)i
;
166 QString
KMSearchRule::functionToString( Function function
)
168 if ( function
!= FuncNone
)
169 return funcConfigNames
[int( function
)];
174 void KMSearchRule::writeConfig( KConfigGroup
& config
, int aIdx
) const {
175 const char cIdx
= char('A' + aIdx
);
176 static const QString
& field
= KGlobal::staticQString( "field" );
177 static const QString
& func
= KGlobal::staticQString( "func" );
178 static const QString
& contents
= KGlobal::staticQString( "contents" );
180 config
.writeEntry( field
+ cIdx
, QString(mField
) );
181 config
.writeEntry( func
+ cIdx
, functionToString( mFunction
) );
182 config
.writeEntry( contents
+ cIdx
, mContents
);
185 bool KMSearchRule::matches( const DwString
& aStr
, KMMessage
& msg
,
186 const DwBoyerMoore
*, int ) const
188 if ( !msg
.isComplete() ) {
189 msg
.fromDwString( aStr
);
190 msg
.setComplete( true );
192 return matches( &msg
);
195 const QString
KMSearchRule::asString() const
197 QString result
= "\"" + mField
+ "\" <";
198 result
+= functionToString( mFunction
);
199 result
+= "> \"" + mContents
+ "\"";
204 //==================================================
206 // class KMSearchRuleString
208 //==================================================
210 KMSearchRuleString::KMSearchRuleString( const QByteArray
& field
,
211 Function func
, const QString
& contents
)
212 : KMSearchRule(field
, func
, contents
)
214 if ( field
.isEmpty() || field
[0] == '<' )
216 else // make sure you handle the unrealistic case of the message starting with mField
217 mBmHeaderField
= new DwBoyerMoore(('\n' + field
+ ": ").data());
220 KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString
& other
)
221 : KMSearchRule( other
),
224 if ( other
.mBmHeaderField
)
225 mBmHeaderField
= new DwBoyerMoore( *other
.mBmHeaderField
);
228 const KMSearchRuleString
& KMSearchRuleString::operator=( const KMSearchRuleString
& other
)
230 if ( this == &other
)
233 setField( other
.field() );
234 setFunction( other
.function() );
235 setContents( other
.contents() );
236 delete mBmHeaderField
; mBmHeaderField
= 0;
237 if ( other
.mBmHeaderField
)
238 mBmHeaderField
= new DwBoyerMoore( *other
.mBmHeaderField
);
243 KMSearchRuleString::~KMSearchRuleString()
245 delete mBmHeaderField
;
249 bool KMSearchRuleString::isEmpty() const
251 return field().trimmed().isEmpty() || contents().isEmpty();
254 bool KMSearchRuleString::requiresBody() const
256 if (mBmHeaderField
|| (field() == "<recipients>" ))
261 bool KMSearchRuleString::matches( const DwString
& aStr
, KMMessage
& msg
,
262 const DwBoyerMoore
* aHeaderField
, int aHeaderLen
) const
269 const DwBoyerMoore
* headerField
= aHeaderField
? aHeaderField
: mBmHeaderField
;
271 const int headerLen
= ( aHeaderLen
> -1 ? aHeaderLen
: field().length() ) + 2 ; // +1 for ': '
274 static const DwBoyerMoore
lflf( "\n\n" );
275 static const DwBoyerMoore
lfcrlf( "\n\r\n" );
277 size_t endOfHeader
= lflf
.FindIn( aStr
, 0 );
278 if ( endOfHeader
== DwString::npos
)
279 endOfHeader
= lfcrlf
.FindIn( aStr
, 0 );
280 const DwString headers
= ( endOfHeader
== DwString::npos
) ? aStr
: aStr
.substr( 0, endOfHeader
);
281 // In case the searched header is at the beginning, we have to prepend
282 // a newline - see the comment in KMSearchRuleString constructor
283 DwString
fakedHeaders( "\n" );
284 size_t start
= headerField
->FindIn( fakedHeaders
.append( headers
), 0, false );
285 // if the header field doesn't exist then return false for positive
286 // functions and true for negated functions (e.g. "does not
287 // contain"); note that all negated string functions correspond
289 if ( start
== DwString::npos
)
290 rc
= ( ( function() & 1 ) == 1 );
293 size_t stop
= aStr
.find( '\n', start
);
295 while ( stop
!= DwString::npos
&& ( ( ch
= aStr
.at( stop
+ 1 ) ) == ' ' || ch
== '\t' ) )
296 stop
= aStr
.find( '\n', stop
+ 1 );
297 const int len
= stop
== DwString::npos
? aStr
.length() - start
: stop
- start
;
298 const QByteArray
codedValue( aStr
.data() + start
, len
+ 1 );
299 const QString msgContents
= KMMsgBase::decodeRFC2047String( codedValue
).trimmed(); // FIXME: This needs to be changed for IDN support.
300 rc
= matchesInternal( msgContents
);
302 } else if ( field() == "<recipients>" ) {
303 static const DwBoyerMoore
to("\nTo: ");
304 static const DwBoyerMoore
cc("\nCc: ");
305 static const DwBoyerMoore
bcc("\nBcc: ");
306 // <recipients> "contains" "foo" is true if any of the fields contains
307 // "foo", while <recipients> "does not contain" "foo" is true if none
308 // of the fields contains "foo"
309 if ( ( function() & 1 ) == 0 ) {
310 // positive function, e.g. "contains"
311 rc
= ( matches( aStr
, msg
, &to
, 2 ) ||
312 matches( aStr
, msg
, &cc
, 2 ) ||
313 matches( aStr
, msg
, &bcc
, 3 ) );
316 // negated function, e.g. "does not contain"
317 rc
= ( matches( aStr
, msg
, &to
, 2 ) &&
318 matches( aStr
, msg
, &cc
, 2 ) &&
319 matches( aStr
, msg
, &bcc
, 3 ) );
322 if ( FilterLog::instance()->isLogging() ) {
323 QString msg
= ( rc
? "<font color=#00FF00>1 = </font>"
324 : "<font color=#FF0000>0 = </font>" );
325 msg
+= FilterLog::recode( asString() );
326 // only log headers bcause messages and bodies can be pretty large
327 // FIXME We have to separate the text which is used for filtering to be able to show it in the log
328 // if ( logContents )
329 // msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
330 FilterLog::instance()->add( msg
, FilterLog::ruleResult
);
335 bool KMSearchRuleString::matches( const KMMessage
* msg
) const
343 // Show the value used to compare the rules against in the log.
344 // Overwrite the value for complete messages and all headers!
345 bool logContents
= true;
347 if( field() == "<message>" ) {
348 msgContents
= msg
->asString();
350 } else if ( field() == "<body>" ) {
351 msgContents
= msg
->bodyToUnicode();
353 } else if ( field() == "<any header>" ) {
354 msgContents
= msg
->headerAsString();
356 } else if ( field() == "<recipients>" ) {
357 // (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
358 // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
360 if ( function() == FuncEquals
|| function() == FuncNotEqual
)
361 // do we need to treat this case specially? Ie.: What shall
362 // "equality" mean for recipients.
363 return matchesInternal( msg
->headerField("To") )
364 || matchesInternal( msg
->headerField("Cc") )
365 || matchesInternal( msg
->headerField("Bcc") )
366 // sometimes messages have multiple Cc headers
367 || matchesInternal( msg
->cc() );
369 msgContents
= msg
->headerField("To");
370 if ( !msg
->headerField("Cc").compare( msg
->cc() ) )
371 msgContents
+= ", " + msg
->headerField("Cc");
373 msgContents
+= ", " + msg
->cc();
374 msgContents
+= ", " + msg
->headerField("Bcc");
375 } else if ( field() == "<tag>" ) {
376 if ( msg
->tagList() ) {
377 foreach ( const QString
&label
, * msg
->tagList() ) {
378 const KMMessageTagDescription
* tagDesc
= kmkernel
->msgTagMgr()->find( label
);
380 msgContents
+= tagDesc
->name();
385 // make sure to treat messages with multiple header lines for
386 // the same header correctly
387 msgContents
= msg
->headerFields( field() ).join( " " );
390 if ( function() == FuncIsInAddressbook
||
391 function() == FuncIsNotInAddressbook
) {
392 // I think only the "from"-field makes sense.
393 msgContents
= msg
->headerField( field() );
394 if ( msgContents
.isEmpty() )
395 return ( function() == FuncIsInAddressbook
) ? false : true;
398 // these two functions need the kmmessage therefore they don't call matchesInternal
399 if ( function() == FuncHasAttachment
)
400 return ( msg
->toMsgBase().attachmentState() == KMMsgHasAttachment
);
401 if ( function() == FuncHasNoAttachment
)
402 return ( ((KMMsgAttachmentState
) msg
->toMsgBase().attachmentState()) == KMMsgHasNoAttachment
);
404 bool rc
= matchesInternal( msgContents
);
405 if ( FilterLog::instance()->isLogging() ) {
406 QString msg
= ( rc
? "<font color=#00FF00>1 = </font>"
407 : "<font color=#FF0000>0 = </font>" );
408 msg
+= FilterLog::recode( asString() );
409 // only log headers bcause messages and bodies can be pretty large
411 msg
+= " (<i>" + FilterLog::recode( msgContents
) + "</i>)";
412 FilterLog::instance()->add( msg
, FilterLog::ruleResult
);
417 // helper, does the actual comparing
418 bool KMSearchRuleString::matchesInternal( const QString
& msgContents
) const
420 switch ( function() ) {
421 case KMSearchRule::FuncEquals
:
422 return ( QString::compare( msgContents
.toLower(), contents().toLower() ) == 0 );
424 case KMSearchRule::FuncNotEqual
:
425 return ( QString::compare( msgContents
.toLower(), contents().toLower() ) != 0 );
427 case KMSearchRule::FuncContains
:
428 return ( msgContents
.contains( contents(), Qt::CaseInsensitive
) );
430 case KMSearchRule::FuncContainsNot
:
431 return ( !msgContents
.contains( contents(), Qt::CaseInsensitive
) );
433 case KMSearchRule::FuncRegExp
:
435 QRegExp
regexp( contents(), Qt::CaseInsensitive
);
436 return ( regexp
.indexIn( msgContents
) >= 0 );
439 case KMSearchRule::FuncNotRegExp
:
441 QRegExp
regexp( contents(), Qt::CaseInsensitive
);
442 return ( regexp
.indexIn( msgContents
) < 0 );
446 return ( QString::compare( msgContents
.toLower(), contents().toLower() ) > 0 );
448 case FuncIsLessOrEqual
:
449 return ( QString::compare( msgContents
.toLower(), contents().toLower() ) <= 0 );
452 return ( QString::compare( msgContents
.toLower(), contents().toLower() ) < 0 );
454 case FuncIsGreaterOrEqual
:
455 return ( QString::compare( msgContents
.toLower(), contents().toLower() ) >= 0 );
457 case FuncIsInAddressbook
: {
458 KABC::AddressBook
*stdAb
= KABC::StdAddressBook::self( true );
459 const QStringList addressList
=
460 KPIMUtils::splitAddressList( msgContents
.toLower() );
461 for( QStringList::ConstIterator it
= addressList
.constBegin();
462 ( it
!= addressList
.constEnd() );
464 if ( !stdAb
->findByEmail( KPIMUtils::extractEmailAddress( *it
) ).isEmpty() )
470 case FuncIsNotInAddressbook
: {
471 KABC::AddressBook
*stdAb
= KABC::StdAddressBook::self( true );
472 const QStringList addressList
=
473 KPIMUtils::splitAddressList( msgContents
.toLower() );
474 for( QStringList::ConstIterator it
= addressList
.constBegin();
475 ( it
!= addressList
.constEnd() );
477 if ( stdAb
->findByEmail( KPIMUtils::extractEmailAddress( *it
) ).isEmpty() )
483 case FuncIsInCategory
: {
484 QString category
= contents();
485 const QStringList addressList
= KPIMUtils::splitAddressList( msgContents
.toLower() );
486 KABC::AddressBook
*stdAb
= KABC::StdAddressBook::self( true );
488 for( QStringList::ConstIterator it
= addressList
.constBegin();
489 it
!= addressList
.constEnd(); ++it
) {
490 KABC::Addressee::List addresses
= stdAb
->findByEmail( KPIMUtils::extractEmailAddress( *it
) );
492 for ( KABC::Addressee::List::Iterator itAd
= addresses
.begin(); itAd
!= addresses
.end(); ++itAd
)
493 if ( (*itAd
).hasCategory(category
) )
500 case FuncIsNotInCategory
: {
501 QString category
= contents();
502 const QStringList addressList
= KPIMUtils::splitAddressList( msgContents
.toLower() );
503 KABC::AddressBook
*stdAb
= KABC::StdAddressBook::self( true );
505 for( QStringList::ConstIterator it
= addressList
.constBegin();
506 it
!= addressList
.constEnd(); ++it
) {
507 KABC::Addressee::List addresses
= stdAb
->findByEmail( KPIMUtils::extractEmailAddress( *it
) );
509 for ( KABC::Addressee::List::Iterator itAd
= addresses
.begin(); itAd
!= addresses
.end(); ++itAd
)
510 if ( (*itAd
).hasCategory(category
) )
524 //==================================================
526 // class KMSearchRuleNumerical
528 //==================================================
530 KMSearchRuleNumerical::KMSearchRuleNumerical( const QByteArray
& field
,
531 Function func
, const QString
& contents
)
532 : KMSearchRule(field
, func
, contents
)
536 bool KMSearchRuleNumerical::isEmpty() const
539 contents().toInt( &ok
);
545 bool KMSearchRuleNumerical::matches( const KMMessage
* msg
) const
549 int numericalMsgContents
= 0;
550 int numericalValue
= 0;
552 if ( field() == "<size>" ) {
553 numericalMsgContents
= int( msg
->msgLength() );
554 numericalValue
= contents().toInt();
555 msgContents
.setNum( numericalMsgContents
);
556 } else if ( field() == "<age in days>" ) {
557 QDateTime msgDateTime
;
558 msgDateTime
.setTime_t( msg
->date() );
559 numericalMsgContents
= msgDateTime
.daysTo( QDateTime::currentDateTime() );
560 numericalValue
= contents().toInt();
561 msgContents
.setNum( numericalMsgContents
);
563 bool rc
= matchesInternal( numericalValue
, numericalMsgContents
, msgContents
);
564 if ( FilterLog::instance()->isLogging() ) {
565 QString msg
= ( rc
? "<font color=#00FF00>1 = </font>"
566 : "<font color=#FF0000>0 = </font>" );
567 msg
+= FilterLog::recode( asString() );
568 msg
+= " ( <i>" + QString::number( numericalMsgContents
) + "</i> )";
569 FilterLog::instance()->add( msg
, FilterLog::ruleResult
);
574 bool KMSearchRuleNumerical::matchesInternal( long numericalValue
,
575 long numericalMsgContents
, const QString
& msgContents
) const
577 switch ( function() ) {
578 case KMSearchRule::FuncEquals
:
579 return ( numericalValue
== numericalMsgContents
);
581 case KMSearchRule::FuncNotEqual
:
582 return ( numericalValue
!= numericalMsgContents
);
584 case KMSearchRule::FuncContains
:
585 return ( msgContents
.contains( contents(), Qt::CaseInsensitive
) );
587 case KMSearchRule::FuncContainsNot
:
588 return ( !msgContents
.contains( contents(), Qt::CaseInsensitive
) );
590 case KMSearchRule::FuncRegExp
:
592 QRegExp
regexp( contents(), Qt::CaseInsensitive
);
593 return ( regexp
.indexIn( msgContents
) >= 0 );
596 case KMSearchRule::FuncNotRegExp
:
598 QRegExp
regexp( contents(), Qt::CaseInsensitive
);
599 return ( regexp
.indexIn( msgContents
) < 0 );
603 return ( numericalMsgContents
> numericalValue
);
605 case FuncIsLessOrEqual
:
606 return ( numericalMsgContents
<= numericalValue
);
609 return ( numericalMsgContents
< numericalValue
);
611 case FuncIsGreaterOrEqual
:
612 return ( numericalMsgContents
>= numericalValue
);
614 case FuncIsInAddressbook
: // since email-addresses are not numerical, I settle for false here
617 case FuncIsNotInAddressbook
:
629 //==================================================
631 // class KMSearchRuleStatus
633 //==================================================
634 QString
englishNameForStatus( const MessageStatus
&status
)
636 for ( int i
=0; i
< numStatusNames
; i
++ ) {
637 if ( statusNames
[i
].status
== status
) {
638 return statusNames
[i
].name
;
644 KMSearchRuleStatus::KMSearchRuleStatus( const QByteArray
& field
,
645 Function func
, const QString
& aContents
)
646 : KMSearchRule(field
, func
, aContents
)
648 // the values are always in english, both from the conf file as well as
649 // the patternedit gui
650 mStatus
= statusFromEnglishName( aContents
);
653 KMSearchRuleStatus::KMSearchRuleStatus( MessageStatus status
, Function func
)
654 : KMSearchRule( "<status>", func
, englishNameForStatus( status
) )
659 MessageStatus
KMSearchRuleStatus::statusFromEnglishName( const QString
&aStatusString
)
661 for ( int i
=0; i
< numStatusNames
; i
++ ) {
662 if ( !aStatusString
.compare( statusNames
[i
].name
) ) {
663 return statusNames
[i
].status
;
666 MessageStatus unknown
;
670 bool KMSearchRuleStatus::isEmpty() const
672 return field().trimmed().isEmpty() || contents().isEmpty();
675 bool KMSearchRuleStatus::matches( const KMMessage
* msg
) const
680 switch ( function() ) {
681 case FuncEquals
: // fallthrough. So that "<status> 'is' 'read'" works
683 if (msg
->messageStatus() & mStatus
)
686 case FuncNotEqual
: // fallthrough. So that "<status> 'is not' 'read'" works
687 case FuncContainsNot
:
688 if (! (msg
->messageStatus() & mStatus
) )
691 // FIXME what about the remaining funcs, how can they make sense for
697 if ( FilterLog::instance()->isLogging() ) {
698 QString msg
= ( rc
? "<font color=#00FF00>1 = </font>"
699 : "<font color=#FF0000>0 = </font>" );
700 msg
+= FilterLog::recode( asString() );
701 FilterLog::instance()->add( msg
, FilterLog::ruleResult
);
706 // ----------------------------------------------------------------------------
708 //==================================================
710 // class KMSearchPattern
712 //==================================================
714 KMSearchPattern::KMSearchPattern()
715 : QList
<KMSearchRule
*>()
720 KMSearchPattern::KMSearchPattern( const KConfigGroup
& config
)
721 : QList
<KMSearchRule
*>()
723 readConfig( config
);
726 KMSearchPattern::~KMSearchPattern()
731 bool KMSearchPattern::matches( const KMMessage
* msg
, bool ignoreBody
) const
736 QList
<KMSearchRule
*>::const_iterator it
;
737 switch ( mOperator
) {
738 case OpAnd
: // all rules must match
739 for ( it
= begin() ; it
!= end() ; ++it
)
740 if ( !((*it
)->requiresBody() && ignoreBody
) )
741 if ( !(*it
)->matches( msg
) )
744 case OpOr
: // at least one rule must match
745 for ( it
= begin() ; it
!= end() ; ++it
)
746 if ( !((*it
)->requiresBody() && ignoreBody
) )
747 if ( (*it
)->matches( msg
) )
755 bool KMSearchPattern::matches( const DwString
& aStr
, bool ignoreBody
) const
761 QList
<KMSearchRule
*>::const_iterator it
;
762 switch ( mOperator
) {
763 case OpAnd
: // all rules must match
764 for ( it
= begin() ; it
!= end() ; ++it
)
765 if ( !((*it
)->requiresBody() && ignoreBody
) )
766 if ( !(*it
)->matches( aStr
, msg
) )
769 case OpOr
: // at least one rule must match
770 for ( it
= begin() ; it
!= end() ; ++it
)
771 if ( !((*it
)->requiresBody() && ignoreBody
) )
772 if ( (*it
)->matches( aStr
, msg
) )
780 bool KMSearchPattern::matches( quint32 serNum
, bool ignoreBody
) const
788 KMFolder
*folder
= 0;
789 KMMsgDict::instance()->getLocation( serNum
, &folder
, &idx
);
790 if ( !folder
|| ( idx
== -1 ) || ( idx
>= folder
->count() ) ) {
794 KMFolderOpener
openFolder( folder
, "searptr" );
795 if ( openFolder
.openResult() == 0 ) { // 0 means no error codes
796 KMFolder
*f
= openFolder
.folder();
797 KMMsgBase
*msgBase
= f
->getMsgBase( idx
);
798 if ( msgBase
&& requiresBody() && !ignoreBody
) {
799 bool unGet
= !msgBase
->isMessage();
800 KMMessage
*msg
= f
->getMsg( idx
);
803 res
= matches( msg
, ignoreBody
);
805 folder
->unGetMsg( idx
);
809 res
= matches( f
->getDwString( idx
), ignoreBody
);
815 bool KMSearchPattern::requiresBody() const {
816 QList
<KMSearchRule
*>::const_iterator it
;
817 for ( it
= begin() ; it
!= end() ; ++it
)
818 if ( (*it
)->requiresBody() )
823 void KMSearchPattern::purify() {
824 QList
<KMSearchRule
*>::iterator it
= end();
825 while ( it
!= begin() ) {
827 if ( (*it
)->isEmpty() ) {
829 kDebug() << "Removing" << (*it
)->asString();
837 void KMSearchPattern::readConfig( const KConfigGroup
& config
) {
840 mName
= config
.readEntry("name");
841 if ( !config
.hasKey("rules") ) {
842 kDebug() << "Found legacy config! Converting.";
843 importLegacyConfig( config
);
847 mOperator
= config
.readEntry("operator") == "or" ? OpOr
: OpAnd
;
849 const int nRules
= config
.readEntry( "rules", 0 );
851 for ( int i
= 0 ; i
< nRules
; i
++ ) {
852 KMSearchRule
* r
= KMSearchRule::createInstanceFromConfig( config
, i
);
860 void KMSearchPattern::importLegacyConfig( const KConfigGroup
& config
) {
861 KMSearchRule
* rule
= KMSearchRule::createInstance( config
.readEntry("fieldA").toLatin1(),
862 config
.readEntry("funcA").toLatin1(),
863 config
.readEntry("contentsA") );
864 if ( rule
->isEmpty() ) {
865 // if the first rule is invalid,
866 // we really can't do much heuristics...
872 const QString sOperator
= config
.readEntry("operator");
873 if ( sOperator
== "ignore" ) return;
875 rule
= KMSearchRule::createInstance( config
.readEntry("fieldB").toLatin1(),
876 config
.readEntry("funcB").toLatin1(),
877 config
.readEntry("contentsB") );
878 if ( rule
->isEmpty() ) {
884 if ( sOperator
== "or" ) {
888 // This is the interesting case...
889 if ( sOperator
== "unless" ) { // meaning "and not", ie we need to...
890 // ...invert the function (e.g. "equals" <-> "doesn't equal")
891 // We simply toggle the last bit (xor with 0x1)... This assumes that
892 // KMSearchRule::Function's come in adjacent pairs of pros and cons
893 KMSearchRule::Function func
= last()->function();
894 unsigned int intFunc
= (unsigned int)func
;
895 func
= KMSearchRule::Function( intFunc
^ 0x1 );
897 last()->setFunction( func
);
900 // treat any other case as "and" (our default).
903 void KMSearchPattern::writeConfig( KConfigGroup
& config
) const {
904 config
.writeEntry("name", mName
);
905 config
.writeEntry("operator", (mOperator
== KMSearchPattern::OpOr
) ? "or" : "and" );
908 QList
<KMSearchRule
*>::const_iterator it
;
909 for ( it
= begin() ; it
!= end() && i
< FILTER_MAX_RULES
; ++i
, ++it
)
910 // we could do this ourselves, but we want the rules to be extensible,
911 // so we give the rule it's number and let it do the rest.
912 (*it
)->writeConfig( config
, i
);
914 // save the total number of rules.
915 config
.writeEntry( "rules", i
);
918 void KMSearchPattern::init() {
921 mName
= '<' + i18nc("name used for a virgin filter","unknown") + '>';
924 QString
KMSearchPattern::asString() const {
926 if ( mOperator
== OpOr
)
927 result
= i18n("(match any of the following)");
929 result
= i18n("(match all of the following)");
931 QList
<KMSearchRule
*>::const_iterator it
;
932 for ( it
= begin() ; it
!= end() ; ++it
)
933 result
+= "\n\t" + FilterLog::recode( (*it
)->asString() );
938 const KMSearchPattern
& KMSearchPattern::operator=( const KMSearchPattern
& other
) {
939 if ( this == &other
)
943 setName( other
.name() );
946 QList
<KMSearchRule
*>::const_iterator it
;
947 for ( it
= other
.begin() ; it
!= other
.end() ; ++it
)
948 append( KMSearchRule::createInstance( **it
) ); // deep copy