// -*- mode: C++; c-file-style: "gnu" -*- // kmsearchpattern.cpp // Author: Marc Mutz // This code is under GPL! #include #include "kmaddrbook.h" #include "kmsearchpattern.h" #include "kmmsgdict.h" #include "filterlog.h" #include "kmkernel.h" #include "kmmsgdict.h" #include "kmfolder.h" using KMail::FilterLog; #include #include #include #include #include #include #include #include #include #include #include #include static const char* funcConfigNames[] = { "contains", "contains-not", "equals", "not-equal", "regexp", "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal", "is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category", "has-attachment", "has-no-attachment"}; static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames; struct _statusNames { const char* name; KMMsgStatus status; }; static struct _statusNames statusNames[] = { { "Important", KMMsgStatusFlag }, { "New", KMMsgStatusNew }, { "Unread", KMMsgStatusUnread | KMMsgStatusNew }, { "Read", KMMsgStatusRead }, { "Old", KMMsgStatusOld }, { "Deleted", KMMsgStatusDeleted }, { "Replied", KMMsgStatusReplied }, { "Forwarded", KMMsgStatusForwarded }, { "Queued", KMMsgStatusQueued }, { "Sent", KMMsgStatusSent }, { "Watched", KMMsgStatusWatched }, { "Ignored", KMMsgStatusIgnored }, { "To Do", KMMsgStatusTodo }, { "Spam", KMMsgStatusSpam }, { "Ham", KMMsgStatusHam }, { "Has Attachment", KMMsgStatusHasAttach }, { "Invitation", KMMsgStatusHasInvitation } }; static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames ); //================================================== // // class KMSearchRule (was: KMFilterRule) // //================================================== KMSearchRule::KMSearchRule( const TQCString & field, Function func, const TQString & contents ) : mField( field ), mFunction( func ), mContents( contents ) { } KMSearchRule::KMSearchRule( const KMSearchRule & other ) : mField( other.mField ), mFunction( other.mFunction ), mContents( other.mContents ) { } const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) { if ( this == &other ) return *this; mField = other.mField; mFunction = other.mFunction; mContents = other.mContents; return *this; } KMSearchRule * KMSearchRule::createInstance( const TQCString & field, Function func, const TQString & contents ) { KMSearchRule *ret = 0; if (field == "") ret = new KMSearchRuleStatus( field, func, contents ); else if ( field == "" || field == "" ) ret = new KMSearchRuleNumerical( field, func, contents ); else ret = new KMSearchRuleString( field, func, contents ); return ret; } KMSearchRule * KMSearchRule::createInstance( const TQCString & field, const char *func, const TQString & contents ) { return ( createInstance( field, configValueToFunc( func ), contents ) ); } KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other ) { return ( createInstance( other.field(), other.function(), other.contents() ) ); } KMSearchRule * KMSearchRule::createInstanceFromConfig( const TDEConfig * config, int aIdx ) { const char cIdx = char( int('A') + aIdx ); static const TQString & field = TDEGlobal::staticQString( "field" ); static const TQString & func = TDEGlobal::staticQString( "func" ); static const TQString & contents = TDEGlobal::staticQString( "contents" ); const TQCString &field2 = config->readEntry( field + cIdx ).latin1(); Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() ); const TQString & contents2 = config->readEntry( contents + cIdx ); if ( field2 == "" ) // backwards compat return KMSearchRule::createInstance( "", func2, contents2 ); else return KMSearchRule::createInstance( field2, func2, contents2 ); } KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) { if ( !str ) return FuncNone; for ( int i = 0 ; i < numFuncConfigNames ; ++i ) if ( tqstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i; return FuncNone; } TQString KMSearchRule::functionToString( Function function ) { if ( function != FuncNone ) return funcConfigNames[int( function )]; else return "invalid"; } void KMSearchRule::writeConfig( TDEConfig * config, int aIdx ) const { const char cIdx = char('A' + aIdx); static const TQString & field = TDEGlobal::staticQString( "field" ); static const TQString & func = TDEGlobal::staticQString( "func" ); static const TQString & contents = TDEGlobal::staticQString( "contents" ); config->writeEntry( field + cIdx, TQString(mField) ); config->writeEntry( func + cIdx, functionToString( mFunction ) ); config->writeEntry( contents + cIdx, mContents ); } bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg, const DwBoyerMoore *, int ) const { if ( !msg.isComplete() ) { msg.fromDwString( aStr ); msg.setComplete( true ); } return matches( &msg ); } const TQString KMSearchRule::asString() const { TQString result = "\"" + mField + "\" <"; result += functionToString( mFunction ); result += "> \"" + mContents + "\""; return result; } //================================================== // // class KMSearchRuleString // //================================================== KMSearchRuleString::KMSearchRuleString( const TQCString & field, Function func, const TQString & contents ) : KMSearchRule(field, func, contents) { if ( field.isEmpty() || field[0] == '<' ) mBmHeaderField = 0; else // make sure you handle the unrealistic case of the message starting with mField mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data()); } KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other ) : KMSearchRule( other ), mBmHeaderField( 0 ) { if ( other.mBmHeaderField ) mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField ); } const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other ) { if ( this == &other ) return *this; setField( other.field() ); mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField ); setFunction( other.function() ); setContents( other.contents() ); delete mBmHeaderField; mBmHeaderField = 0; if ( other.mBmHeaderField ) mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField ); return *this; } KMSearchRuleString::~KMSearchRuleString() { delete mBmHeaderField; mBmHeaderField = 0; } bool KMSearchRuleString::isEmpty() const { return field().stripWhiteSpace().isEmpty() || contents().isEmpty(); } bool KMSearchRuleString::requiresBody() const { if (mBmHeaderField || (field() == "" )) return false; return true; } bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg, const DwBoyerMoore * aHeaderField, int aHeaderLen ) const { if ( isEmpty() ) return false; bool rc = false; const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ; const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': ' if ( headerField ) { static const DwBoyerMoore lflf( "\n\n" ); static const DwBoyerMoore lfcrlf( "\n\r\n" ); size_t endOfHeader = lflf.FindIn( aStr, 0 ); if ( endOfHeader == DwString::npos ) endOfHeader = lfcrlf.FindIn( aStr, 0 ); const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader ); // In case the searched header is at the beginning, we have to prepend // a newline - see the comment in KMSearchRuleString constructor DwString fakedHeaders( "\n" ); size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false ); // if the header field doesn't exist then return false for positive // functions and true for negated functions (e.g. "does not // contain"); note that all negated string functions correspond // to an odd value if ( start == DwString::npos ) rc = ( ( function() & 1 ) == 1 ); else { start += headerLen; size_t stop = aStr.find( '\n', start ); char ch = '\0'; while ( stop != DwString::npos && ( ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) ) stop = aStr.find( '\n', stop + 1 ); const int len = stop == DwString::npos ? aStr.length() - start : stop - start ; const TQCString codedValue( aStr.data() + start, len + 1 ); const TQString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support. rc = matchesInternal( msgContents ); } } else if ( field() == "" ) { static const DwBoyerMoore to("\nTo: "); static const DwBoyerMoore cc("\nCc: "); static const DwBoyerMoore bcc("\nBcc: "); // "contains" "foo" is true if any of the fields contains // "foo", while "does not contain" "foo" is true if none // of the fields contains "foo" if ( ( function() & 1 ) == 0 ) { // positive function, e.g. "contains" rc = ( matches( aStr, msg, &to, 2 ) || matches( aStr, msg, &cc, 2 ) || matches( aStr, msg, &bcc, 3 ) ); } else { // negated function, e.g. "does not contain" rc = ( matches( aStr, msg, &to, 2 ) && matches( aStr, msg, &cc, 2 ) && matches( aStr, msg, &bcc, 3 ) ); } } if ( FilterLog::instance()->isLogging() ) { TQString msg = ( rc ? "1 = " : "0 = " ); msg += FilterLog::recode( asString() ); // only log headers bcause messages and bodies can be pretty large // FIXME We have to separate the text which is used for filtering to be able to show it in the log // if ( logContents ) // msg += " (" + FilterLog::recode( msgContents ) + ")"; FilterLog::instance()->add( msg, FilterLog::ruleResult ); } return rc; } bool KMSearchRuleString::matches( const KMMessage * msg ) const { assert( msg ); if ( isEmpty() ) return false; TQString msgContents; // Show the value used to compare the rules against in the log. // Overwrite the value for complete messages and all headers! bool logContents = true; if( field() == "" ) { // When searching in the complete message, we can't simply use msg->asString() here, // as that wouldn't decode the body. Therefore we use the decoded body and all decoded // header fields and add all to the one big search string. msgContents += msg->bodyToUnicode(); const DwHeaders& headers = msg->headers(); const DwField * dwField = headers.FirstField(); while( dwField != 0 ) { const char * const fieldName = dwField->FieldNameStr().c_str(); const TQString fieldValue = msg->headerFields( fieldName ).join( " " ); msgContents += " " + fieldValue; dwField = dwField->Next(); } logContents = false; } else if ( field() == "" ) { msgContents = msg->bodyToUnicode(); logContents = false; } else if ( field() == "" ) { msgContents = msg->headerAsString(); logContents = false; } else if ( field() == "" ) { // (mmutz 2001-11-05) hack to fix " !contains foo" to // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail // handbook if ( function() == FuncEquals || function() == FuncNotEqual ) // do we need to treat this case specially? Ie.: What shall // "equality" mean for recipients. return matchesInternal( msg->headerField("To") ) || matchesInternal( msg->headerField("Cc") ) || matchesInternal( msg->headerField("Bcc") ) // sometimes messages have multiple Cc headers || matchesInternal( msg->cc() ); msgContents = msg->headerField("To"); if ( !msg->headerField("Cc").compare( msg->cc() ) ) msgContents += ", " + msg->headerField("Cc"); else msgContents += ", " + msg->cc(); msgContents += ", " + msg->headerField("Bcc"); } else { // make sure to treat messages with multiple header lines for // the same header correctly msgContents = msg->headerFields( field() ).join( " " ); } if ( function() == FuncIsInAddressbook || function() == FuncIsNotInAddressbook ) { // I think only the "from"-field makes sense. msgContents = msg->headerField( field() ); if ( msgContents.isEmpty() ) return ( function() == FuncIsInAddressbook ) ? false : true; } // these two functions need the kmmessage therefore they don't call matchesInternal if ( function() == FuncHasAttachment ) return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment ); if ( function() == FuncHasNoAttachment ) return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment ); bool rc = matchesInternal( msgContents ); if ( FilterLog::instance()->isLogging() ) { TQString msg = ( rc ? "1 = " : "0 = " ); msg += FilterLog::recode( asString() ); // only log headers bcause messages and bodies can be pretty large if ( logContents ) msg += " (" + FilterLog::recode( msgContents ) + ")"; FilterLog::instance()->add( msg, FilterLog::ruleResult ); } return rc; } // helper, does the actual comparing bool KMSearchRuleString::matchesInternal( const TQString & msgContents ) const { switch ( function() ) { case KMSearchRule::FuncEquals: return ( TQString::compare( msgContents.lower(), contents().lower() ) == 0 ); case KMSearchRule::FuncNotEqual: return ( TQString::compare( msgContents.lower(), contents().lower() ) != 0 ); case KMSearchRule::FuncContains: return ( msgContents.find( contents(), 0, false ) >= 0 ); case KMSearchRule::FuncContainsNot: return ( msgContents.find( contents(), 0, false ) < 0 ); case KMSearchRule::FuncRegExp: { TQRegExp regexp( contents(), false ); return ( regexp.search( msgContents ) >= 0 ); } case KMSearchRule::FuncNotRegExp: { TQRegExp regexp( contents(), false ); return ( regexp.search( msgContents ) < 0 ); } case FuncIsGreater: return ( TQString::compare( msgContents.lower(), contents().lower() ) > 0 ); case FuncIsLessOrEqual: return ( TQString::compare( msgContents.lower(), contents().lower() ) <= 0 ); case FuncIsLess: return ( TQString::compare( msgContents.lower(), contents().lower() ) < 0 ); case FuncIsGreaterOrEqual: return ( TQString::compare( msgContents.lower(), contents().lower() ) >= 0 ); case FuncIsInAddressbook: { TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true ); TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() ); for( TQStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() ) return true; } return false; } case FuncIsNotInAddressbook: { TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true ); TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() ); for( TQStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() ) return true; } return false; } case FuncIsInCategory: { TQString category = contents(); TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() ); TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true ); for( TQStringList::ConstIterator it = addressList.begin(); it != addressList.end(); ++it ) { TDEABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) ); for ( TDEABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd ) if ( (*itAd).hasCategory(category) ) return true; } return false; } case FuncIsNotInCategory: { TQString category = contents(); TQStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() ); TDEABC::AddressBook *stdAb = TDEABC::StdAddressBook::self( true ); for( TQStringList::ConstIterator it = addressList.begin(); it != addressList.end(); ++it ) { TDEABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) ); for ( TDEABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd ) if ( (*itAd).hasCategory(category) ) return false; } return true; } default: ; } return false; } //================================================== // // class KMSearchRuleNumerical // //================================================== KMSearchRuleNumerical::KMSearchRuleNumerical( const TQCString & field, Function func, const TQString & contents ) : KMSearchRule(field, func, contents) { } bool KMSearchRuleNumerical::isEmpty() const { bool ok = false; contents().toInt( &ok ); return !ok; } bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const { TQString msgContents; int numericalMsgContents = 0; int numericalValue = 0; if ( field() == "" ) { numericalMsgContents = int( msg->msgLength() ); numericalValue = contents().toInt(); msgContents.setNum( numericalMsgContents ); } else if ( field() == "" ) { TQDateTime msgDateTime; msgDateTime.setTime_t( msg->date() ); numericalMsgContents = msgDateTime.daysTo( TQDateTime::currentDateTime() ); numericalValue = contents().toInt(); msgContents.setNum( numericalMsgContents ); } bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents ); if ( FilterLog::instance()->isLogging() ) { TQString msg = ( rc ? "1 = " : "0 = " ); msg += FilterLog::recode( asString() ); msg += " ( " + TQString::number( numericalMsgContents ) + " )"; FilterLog::instance()->add( msg, FilterLog::ruleResult ); } return rc; } bool KMSearchRuleNumerical::matchesInternal( long numericalValue, long numericalMsgContents, const TQString & msgContents ) const { switch ( function() ) { case KMSearchRule::FuncEquals: return ( numericalValue == numericalMsgContents ); case KMSearchRule::FuncNotEqual: return ( numericalValue != numericalMsgContents ); case KMSearchRule::FuncContains: return ( msgContents.find( contents(), 0, false ) >= 0 ); case KMSearchRule::FuncContainsNot: return ( msgContents.find( contents(), 0, false ) < 0 ); case KMSearchRule::FuncRegExp: { TQRegExp regexp( contents(), false ); return ( regexp.search( msgContents ) >= 0 ); } case KMSearchRule::FuncNotRegExp: { TQRegExp regexp( contents(), false ); return ( regexp.search( msgContents ) < 0 ); } case FuncIsGreater: return ( numericalMsgContents > numericalValue ); case FuncIsLessOrEqual: return ( numericalMsgContents <= numericalValue ); case FuncIsLess: return ( numericalMsgContents < numericalValue ); case FuncIsGreaterOrEqual: return ( numericalMsgContents >= numericalValue ); case FuncIsInAddressbook: // since email-addresses are not numerical, I settle for false here return false; case FuncIsNotInAddressbook: return false; default: ; } return false; } //================================================== // // class KMSearchRuleStatus // //================================================== TQString englishNameForStatus( const KMMsgStatus& status ) { for ( int i=0; i< numStatusNames; i++ ) { if ( statusNames[i].status == status ) { return statusNames[i].name; } } return TQString(); } KMSearchRuleStatus::KMSearchRuleStatus( const TQCString & field, Function func, const TQString & aContents ) : KMSearchRule(field, func, aContents) { // the values are always in english, both from the conf file as well as // the patternedit gui mStatus = statusFromEnglishName( aContents ); } KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func ) : KMSearchRule( "", func, englishNameForStatus( status ) ) { mStatus = status; } KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const TQString & aStatusString ) { for ( int i=0; i< numStatusNames; i++ ) { if ( !aStatusString.compare( statusNames[i].name ) ) { return statusNames[i].status; } } return KMMsgStatusUnknown; } bool KMSearchRuleStatus::isEmpty() const { return field().stripWhiteSpace().isEmpty() || contents().isEmpty(); } bool KMSearchRuleStatus::matches( const DwString &, KMMessage &, const DwBoyerMoore *, int ) const { assert( 0 ); return false; // don't warn } bool KMSearchRuleStatus::matches( const KMMessage * msg ) const { KMMsgStatus msgStatus = msg->status(); bool rc = false; switch ( function() ) { case FuncEquals: // fallthrough. So that " 'is' 'read'" works case FuncContains: if (msgStatus & mStatus) rc = true; break; case FuncNotEqual: // fallthrough. So that " 'is not' 'read'" works case FuncContainsNot: if (! (msgStatus & mStatus) ) rc = true; break; // FIXME what about the remaining funcs, how can they make sense for // stati? default: break; } if ( FilterLog::instance()->isLogging() ) { TQString msg = ( rc ? "1 = " : "0 = " ); msg += FilterLog::recode( asString() ); FilterLog::instance()->add( msg, FilterLog::ruleResult ); } return rc; } // ---------------------------------------------------------------------------- //================================================== // // class KMSearchPattern // //================================================== KMSearchPattern::KMSearchPattern( const TDEConfig * config ) : TQPtrList() { setAutoDelete( true ); if ( config ) readConfig( config ); else init(); } KMSearchPattern::~KMSearchPattern() { } bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const { if ( isEmpty() ) return true; TQPtrListIterator it( *this ); switch ( mOperator ) { case OpAnd: // all rules must match for ( it.toFirst() ; it.current() ; ++it ) if ( !((*it)->requiresBody() && ignoreBody) ) if ( !(*it)->matches( msg ) ) return false; return true; case OpOr: // at least one rule must match for ( it.toFirst() ; it.current() ; ++it ) if ( !((*it)->requiresBody() && ignoreBody) ) if ( (*it)->matches( msg ) ) return true; // fall through default: return false; } } bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const { if ( isEmpty() ) return true; KMMessage msg; TQPtrListIterator it( *this ); switch ( mOperator ) { case OpAnd: // all rules must match for ( it.toFirst() ; it.current() ; ++it ) if ( !((*it)->requiresBody() && ignoreBody) ) if ( !(*it)->matches( aStr, msg ) ) return false; return true; case OpOr: // at least one rule must match for ( it.toFirst() ; it.current() ; ++it ) if ( !((*it)->requiresBody() && ignoreBody) ) if ( (*it)->matches( aStr, msg ) ) return true; // fall through default: return false; } } bool KMSearchPattern::matches( TQ_UINT32 serNum, bool ignoreBody ) const { if ( isEmpty() ) return true; bool res; int idx = -1; KMFolder *folder = 0; KMMsgDict::instance()->getLocation(serNum, &folder, &idx); if (!folder || (idx == -1) || (idx >= folder->count())) { return false; } KMFolderOpener openFolder(folder, "searchptr"); KMMsgBase *msgBase = folder->getMsgBase(idx); if (requiresBody() && !ignoreBody) { bool unGet = !msgBase->isMessage(); KMMessage *msg = folder->getMsg(idx); res = false; if ( msg ) { res = matches( msg, ignoreBody ); if (unGet) folder->unGetMsg(idx); } } else { res = matches( folder->getDwString(idx), ignoreBody ); } return res; } bool KMSearchPattern::requiresBody() const { TQPtrListIterator it( *this ); for ( it.toFirst() ; it.current() ; ++it ) if ( (*it)->requiresBody() ) return true; return false; } void KMSearchPattern::purify() { TQPtrListIterator it( *this ); it.toLast(); while ( it.current() ) if ( (*it)->isEmpty() ) { #ifndef NDEBUG kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl; #endif remove( *it ); } else { --it; } } void KMSearchPattern::readConfig( const TDEConfig * config ) { init(); mName = config->readEntry("name"); if ( !config->hasKey("rules") ) { kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl; importLegacyConfig( config ); return; } mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd; const int nRules = config->readNumEntry( "rules", 0 ); for ( int i = 0 ; i < nRules ; i++ ) { KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i ); if ( r->isEmpty() ) delete r; else append( r ); } } void KMSearchPattern::importLegacyConfig( const TDEConfig * config ) { KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(), config->readEntry("funcA").latin1(), config->readEntry("contentsA") ); if ( rule->isEmpty() ) { // if the first rule is invalid, // we really can't do much heuristics... delete rule; return; } append( rule ); const TQString sOperator = config->readEntry("operator"); if ( sOperator == "ignore" ) return; rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(), config->readEntry("funcB").latin1(), config->readEntry("contentsB") ); if ( rule->isEmpty() ) { delete rule; return; } append( rule ); if ( sOperator == "or" ) { mOperator = OpOr; return; } // This is the interesting case... if ( sOperator == "unless" ) { // meaning "and not", ie we need to... // ...invert the function (e.g. "equals" <-> "doesn't equal") // We simply toggle the last bit (xor with 0x1)... This assumes that // KMSearchRule::Function's come in adjacent pairs of pros and cons KMSearchRule::Function func = last()->function(); unsigned int intFunc = (unsigned int)func; func = KMSearchRule::Function( intFunc ^ 0x1 ); last()->setFunction( func ); } // treat any other case as "and" (our default). } void KMSearchPattern::writeConfig( TDEConfig * config ) const { config->writeEntry("name", mName); config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" ); int i = 0; for ( TQPtrListIterator it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it ) // we could do this ourselves, but we want the rules to be extensible, // so we give the rule it's number and let it do the rest. (*it)->writeConfig( config, i ); // save the total number of rules. config->writeEntry( "rules", i ); } void KMSearchPattern::init() { clear(); mOperator = OpAnd; mName = '<' + i18n("name used for a virgin filter","unknown") + '>'; } TQString KMSearchPattern::asString() const { TQString result; if ( mOperator == OpOr ) result = i18n("(match any of the following)"); else result = i18n("(match all of the following)"); for ( TQPtrListIterator it( *this ) ; it.current() ; ++it ) result += "\n\t" + FilterLog::recode( (*it)->asString() ); return result; } const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) { if ( this == &other ) return *this; setOp( other.op() ); setName( other.name() ); clear(); // ### for ( TQPtrListIterator it( other ) ; it.current() ; ++it ) { KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy append( rule ); } return *this; }