krop's commit fixes my problem in a better way, reverting
[kdepim.git] / kmail / kmsearchpattern.cpp
blob5377a2ab2a499b03edf81c1785715c8812ec9812
1 /* -*- mode: C++; c-file-style: "gnu" -*-
2 kmsearchpattern.cpp
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;
25 #include "kmkernel.h"
26 #include "kmfolder.h"
28 #include <kpimutils/email.h>
30 #include <kglobal.h>
31 #include <klocale.h>
32 #include <kdebug.h>
33 #include <kconfig.h>
34 #include <kconfiggroup.h>
36 #include <kabc/stdaddressbook.h>
38 #include <QRegExp>
39 #include <QByteArray>
41 #include <mimelib/string.h>
42 #include <mimelib/boyermor.h>
44 #include <assert.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;
53 struct _statusNames {
54 const char* name;
55 MessageStatus status;
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 )
87 : mField( field ),
88 mFunction( func ),
89 mContents( 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 )
102 return *this;
104 mField = other.mField;
105 mFunction = other.mFunction;
106 mContents = other.mContents;
108 return *this;
111 KMSearchRule * KMSearchRule::createInstance( const QByteArray & field,
112 Function func,
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 );
120 else
121 ret = new KMSearchRuleString( field, func, contents );
123 return ret;
126 KMSearchRule * KMSearchRule::createInstance( const QByteArray & field,
127 const char *func,
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 );
152 else
153 return KMSearchRule::createInstance( field2, func2, contents2 );
156 KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
157 if ( !str )
158 return FuncNone;
160 for ( int i = 0 ; i < numFuncConfigNames ; ++i )
161 if ( qstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
163 return FuncNone;
166 QString KMSearchRule::functionToString( Function function )
168 if ( function != FuncNone )
169 return funcConfigNames[int( function )];
170 else
171 return "invalid";
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 + "\"";
201 return result;
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] == '<' )
215 mBmHeaderField = 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 ),
222 mBmHeaderField( 0 )
224 if ( other.mBmHeaderField )
225 mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
228 const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
230 if ( this == &other )
231 return *this;
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 );
240 return *this;
243 KMSearchRuleString::~KMSearchRuleString()
245 delete mBmHeaderField;
246 mBmHeaderField = 0;
249 bool KMSearchRuleString::isEmpty() const
251 return field().trimmed().isEmpty() || contents().isEmpty();
254 bool KMSearchRuleString::requiresBody() const
256 if (mBmHeaderField || (field() == "<recipients>" ))
257 return false;
258 return true;
261 bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
262 const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
264 if ( isEmpty() )
265 return false;
267 bool rc = false;
269 const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
271 const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': '
273 if ( headerField ) {
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
288 // to an odd value
289 if ( start == DwString::npos )
290 rc = ( ( function() & 1 ) == 1 );
291 else {
292 start += headerLen;
293 size_t stop = aStr.find( '\n', start );
294 char ch = '\0';
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 ) );
315 else {
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 );
332 return rc;
335 bool KMSearchRuleString::matches( const KMMessage * msg ) const
337 assert( msg );
339 if ( isEmpty() )
340 return false;
342 QString msgContents;
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();
349 logContents = false;
350 } else if ( field() == "<body>" ) {
351 msgContents = msg->bodyToUnicode();
352 logContents = false;
353 } else if ( field() == "<any header>" ) {
354 msgContents = msg->headerAsString();
355 logContents = false;
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
359 // handbook
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");
372 else
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 );
379 if ( tagDesc )
380 msgContents += tagDesc->name();
382 logContents = false;
384 } else {
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
410 if ( logContents )
411 msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
412 FilterLog::instance()->add( msg, FilterLog::ruleResult );
414 return rc;
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 );
445 case FuncIsGreater:
446 return ( QString::compare( msgContents.toLower(), contents().toLower() ) > 0 );
448 case FuncIsLessOrEqual:
449 return ( QString::compare( msgContents.toLower(), contents().toLower() ) <= 0 );
451 case FuncIsLess:
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() );
463 ++it ) {
464 if ( !stdAb->findByEmail( KPIMUtils::extractEmailAddress( *it ) ).isEmpty() )
465 return true;
467 return false;
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() );
476 ++it ) {
477 if ( stdAb->findByEmail( KPIMUtils::extractEmailAddress( *it ) ).isEmpty() )
478 return true;
480 return false;
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) )
494 return true;
497 return false;
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) )
511 return false;
514 return true;
516 default:
520 return false;
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
538 bool ok = false;
539 contents().toInt( &ok );
541 return !ok;
545 bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
548 QString msgContents;
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 );
571 return rc;
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 );
602 case FuncIsGreater:
603 return ( numericalMsgContents > numericalValue );
605 case FuncIsLessOrEqual:
606 return ( numericalMsgContents <= numericalValue );
608 case FuncIsLess:
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
615 return false;
617 case FuncIsNotInAddressbook:
618 return false;
620 default:
624 return false;
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;
641 return QString();
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 ) )
656 mStatus = 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;
667 return unknown;
670 bool KMSearchRuleStatus::isEmpty() const
672 return field().trimmed().isEmpty() || contents().isEmpty();
675 bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
678 bool rc = false;
680 switch ( function() ) {
681 case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works
682 case FuncContains:
683 if (msg->messageStatus() & mStatus)
684 rc = true;
685 break;
686 case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works
687 case FuncContainsNot:
688 if (! (msg->messageStatus() & mStatus) )
689 rc = true;
690 break;
691 // FIXME what about the remaining funcs, how can they make sense for
692 // stati?
693 default:
694 break;
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 );
703 return rc;
706 // ----------------------------------------------------------------------------
708 //==================================================
710 // class KMSearchPattern
712 //==================================================
714 KMSearchPattern::KMSearchPattern()
715 : QList<KMSearchRule*>()
717 init();
720 KMSearchPattern::KMSearchPattern( const KConfigGroup & config )
721 : QList<KMSearchRule*>()
723 readConfig( config );
726 KMSearchPattern::~KMSearchPattern()
728 qDeleteAll( *this );
731 bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const
733 if ( isEmpty() )
734 return true;
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 ) )
742 return false;
743 return true;
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 ) )
748 return true;
749 // fall through
750 default:
751 return false;
755 bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const
757 if ( isEmpty() )
758 return true;
760 KMMessage msg;
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 ) )
767 return false;
768 return true;
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 ) )
773 return true;
774 // fall through
775 default:
776 return false;
780 bool KMSearchPattern::matches( quint32 serNum, bool ignoreBody ) const
782 if ( isEmpty() ) {
783 return true;
786 bool res = false;
787 int idx = -1;
788 KMFolder *folder = 0;
789 KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
790 if ( !folder || ( idx == -1 ) || ( idx >= folder->count() ) ) {
791 return res;
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 );
801 res = false;
802 if ( msg ) {
803 res = matches( msg, ignoreBody );
804 if ( unGet ) {
805 folder->unGetMsg( idx );
808 } else {
809 res = matches( f->getDwString( idx ), ignoreBody );
812 return res;
815 bool KMSearchPattern::requiresBody() const {
816 QList<KMSearchRule*>::const_iterator it;
817 for ( it = begin() ; it != end() ; ++it )
818 if ( (*it)->requiresBody() )
819 return true;
820 return false;
823 void KMSearchPattern::purify() {
824 QList<KMSearchRule*>::iterator it = end();
825 while ( it != begin() ) {
826 --it;
827 if ( (*it)->isEmpty() ) {
828 #ifndef NDEBUG
829 kDebug() << "Removing" << (*it)->asString();
830 #endif
831 erase( it );
832 it = end();
837 void KMSearchPattern::readConfig( const KConfigGroup & config ) {
838 init();
840 mName = config.readEntry("name");
841 if ( !config.hasKey("rules") ) {
842 kDebug() << "Found legacy config! Converting.";
843 importLegacyConfig( config );
844 return;
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 );
853 if ( r->isEmpty() )
854 delete r;
855 else
856 append( r );
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...
867 delete rule;
868 return;
870 append( rule );
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() ) {
879 delete rule;
880 return;
882 append( rule );
884 if ( sOperator == "or" ) {
885 mOperator = OpOr;
886 return;
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" );
907 int i = 0;
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() {
919 clear();
920 mOperator = OpAnd;
921 mName = '<' + i18nc("name used for a virgin filter","unknown") + '>';
924 QString KMSearchPattern::asString() const {
925 QString result;
926 if ( mOperator == OpOr )
927 result = i18n("(match any of the following)");
928 else
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() );
935 return result;
938 const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) {
939 if ( this == &other )
940 return *this;
942 setOp( other.op() );
943 setName( other.name() );
945 clear(); // ###
946 QList<KMSearchRule*>::const_iterator it;
947 for ( it = other.begin() ; it != other.end() ; ++it )
948 append( KMSearchRule::createInstance( **it ) ); // deep copy
950 return *this;