/*****************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2004 Lubos Lunak <l.lunak@kde.org>

You can Freely distribute this program under the GNU General Public
License. See the file "COPYING" for the exact licensing terms.
******************************************************************/

#include "rules.h"

#include <fixx11h.h>
#include <tdeconfig.h>
#include <tqregexp.h>
#include <tdetempfile.h>
#include <ksimpleconfig.h>
#include <tqfile.h>

#ifndef KCMRULES
#include "client.h"
#include "workspace.h"
#endif

namespace KWinInternal
{

Rules::Rules()
    : temporary_state( 0 )
    , wmclassmatch( UnimportantMatch )
    , wmclasscomplete( UnimportantMatch )
    , windowrolematch( UnimportantMatch )
    , titlematch( UnimportantMatch )
    , extrarolematch( UnimportantMatch )
    , clientmachinematch( UnimportantMatch )
    , types( NET::AllTypesMask )
    , placementrule( UnusedForceRule )
    , positionrule( UnusedSetRule )
    , sizerule( UnusedSetRule )
    , minsizerule( UnusedForceRule )
    , maxsizerule( UnusedForceRule )
    , opacityactiverule( UnusedForceRule )
    , opacityinactiverule( UnusedForceRule )
    , ignorepositionrule( UnusedForceRule )
    , desktoprule( UnusedSetRule )
    , typerule( UnusedForceRule )
    , maximizevertrule( UnusedSetRule )
    , maximizehorizrule( UnusedSetRule )
    , minimizerule( UnusedSetRule )
    , shaderule( UnusedSetRule )
    , skiptaskbarrule( UnusedSetRule )
    , skippagerrule( UnusedSetRule )
    , aboverule( UnusedSetRule )
    , belowrule( UnusedSetRule )
    , fullscreenrule( UnusedSetRule )
    , noborderrule( UnusedSetRule )
    , fsplevelrule( UnusedForceRule )
    , acceptfocusrule( UnusedForceRule )
    , moveresizemoderule( UnusedForceRule )
    , closeablerule( UnusedForceRule )
    , strictgeometryrule( UnusedForceRule )
    , shortcutrule( UnusedSetRule )
    , disableglobalshortcutsrule( UnusedForceRule )
    {
    }

Rules::Rules( const TQString& str, bool temporary )
    : temporary_state( temporary ? 2 : 0 )
    {
    KTempFile file;
    TQFile* f = file.file();
    if( f != NULL )
        {
        TQCString s = str.utf8();
        f->writeBlock( s.data(), s.length());
        }
    file.close();
    KSimpleConfig cfg( file.name());
    readFromCfg( cfg );
    if( description.isEmpty())
        description = "temporary";
    file.unlink();
    }

#define READ_MATCH_STRING( var, func ) \
    var = cfg.readEntry( #var ) func; \
    var##match = (StringMatch) TQMAX( FirstStringMatch, TQMIN( LastStringMatch, cfg.readNumEntry( #var "match" )));
    
#define READ_SET_RULE( var, type, func ) \
    var = func ( cfg.read##type##Entry( #var )); \
    var##rule = readSetRule( cfg, #var "rule" );
    
#define READ_SET_RULE_DEF( var, type, func, def ) \
    var = func ( cfg.read##type##Entry( #var, def )); \
    var##rule = readSetRule( cfg, #var "rule" );
    
#define READ_SET_RULE_2( var, type, func, funcarg ) \
    var = func ( cfg.read##type##Entry( #var ), funcarg ); \
    var##rule = readSetRule( cfg, #var "rule" );

#define READ_FORCE_RULE( var, type, func ) \
    var = func ( cfg.read##type##Entry( #var )); \
    var##rule = readForceRule( cfg, #var "rule" );

#define READ_FORCE_RULE_2( var, type, func, funcarg ) \
    var = func ( cfg.read##type##Entry( #var ), funcarg ); \
    var##rule = readForceRule( cfg, #var "rule" );


Rules::Rules( TDEConfig& cfg )
    : temporary_state( 0 )
    {
    readFromCfg( cfg );
    }

static int limit0to4( int i ) { return TQMAX( 0, TQMIN( 4, i )); }

void Rules::readFromCfg( TDEConfig& cfg )
    {
    description = cfg.readEntry( "Description" );
    if( description.isEmpty()) // capitalized first, lowercase for backwards compatibility
        description = cfg.readEntry( "description" );
    READ_MATCH_STRING( wmclass, .lower().latin1() );
    wmclasscomplete = cfg.readBoolEntry( "wmclasscomplete" );
    READ_MATCH_STRING( windowrole, .lower().latin1() );
    READ_MATCH_STRING( title, );
    READ_MATCH_STRING( extrarole, .lower().latin1() );
    READ_MATCH_STRING( clientmachine, .lower().latin1() );
    types = cfg.readUnsignedLongNumEntry( "types", NET::AllTypesMask );
    READ_FORCE_RULE_2( placement,, Placement::policyFromString, false );
    READ_SET_RULE_DEF( position, Point,, &invalidPoint );
    READ_SET_RULE( size, Size, );
    if( size.isEmpty() && sizerule != ( SetRule )Remember)
        sizerule = UnusedSetRule;
    READ_FORCE_RULE( minsize, Size, );
    if( !minsize.isValid())
        minsize = TQSize( 1, 1 );
    READ_FORCE_RULE( maxsize, Size, );
    if( maxsize.isEmpty())
        maxsize = TQSize( 32767, 32767 );
    READ_FORCE_RULE( opacityactive, Num, );
    if( opacityactive < 0 || opacityactive > 100 )
        opacityactive = 100;
    READ_FORCE_RULE( opacityinactive, Num, );
    if( opacityinactive < 0 || opacityinactive > 100 )
        opacityinactive = 100;
    READ_FORCE_RULE( ignoreposition, Bool, );
    READ_SET_RULE( desktop, Num, );
    type = readType( cfg, "type" );
    typerule = type != NET::Unknown ? readForceRule( cfg, "typerule" ) : UnusedForceRule;
    READ_SET_RULE( maximizevert, Bool, );
    READ_SET_RULE( maximizehoriz, Bool, );
    READ_SET_RULE( minimize, Bool, );
    READ_SET_RULE( shade, Bool, );
    READ_SET_RULE( skiptaskbar, Bool, );
    READ_SET_RULE( skippager, Bool, );
    READ_SET_RULE( above, Bool, );
    READ_SET_RULE( below, Bool, );
    READ_SET_RULE( fullscreen, Bool, );
    READ_SET_RULE( noborder, Bool, );
    READ_FORCE_RULE( fsplevel, Num, limit0to4 ); // fsp is 0-4
    READ_FORCE_RULE( acceptfocus, Bool, );
    READ_FORCE_RULE( moveresizemode, , Options::stringToMoveResizeMode );
    READ_FORCE_RULE( closeable, Bool, );
    READ_FORCE_RULE( strictgeometry, Bool, );
    READ_SET_RULE( shortcut, , );
    READ_FORCE_RULE( disableglobalshortcuts, Bool, );
    }

#undef READ_MATCH_STRING
#undef READ_SET_RULE
#undef READ_SET_RULE_2
#undef READ_FORCE_RULE
#undef READ_FORCE_RULE_2

#define WRITE_MATCH_STRING( var, cast, force ) \
    if( !var.isEmpty() || force ) \
        { \
        cfg.writeEntry( #var, cast var ); \
        cfg.writeEntry( #var "match", var##match ); \
        } \
    else \
        { \
        cfg.deleteEntry( #var ); \
        cfg.deleteEntry( #var "match" ); \
        }

#define WRITE_SET_RULE( var, func ) \
    if( var##rule != UnusedSetRule ) \
        { \
        cfg.writeEntry( #var, func ( var )); \
        cfg.writeEntry( #var "rule", var##rule ); \
        } \
    else \
        { \
        cfg.deleteEntry( #var ); \
        cfg.deleteEntry( #var "rule" ); \
        }

#define WRITE_FORCE_RULE( var, func ) \
    if( var##rule != UnusedForceRule ) \
        { \
        cfg.writeEntry( #var, func ( var )); \
        cfg.writeEntry( #var "rule", var##rule ); \
        } \
    else \
        { \
        cfg.deleteEntry( #var ); \
        cfg.deleteEntry( #var "rule" ); \
        }

#define WRITE_WITH_DEFAULT( var, default ) \
    if( var != default ) \
        cfg.writeEntry( #var, var ); \
    else \
        cfg.deleteEntry( #var );


void Rules::write( TDEConfig& cfg ) const
    {
    cfg.writeEntry( "Description", description );
    // always write wmclass
    WRITE_MATCH_STRING( wmclass, (const char*), true );
    cfg.writeEntry( "wmclasscomplete", wmclasscomplete );
    WRITE_MATCH_STRING( windowrole, (const char*), false );
    WRITE_MATCH_STRING( title,, false );
    WRITE_MATCH_STRING( extrarole, (const char*), false );
    WRITE_MATCH_STRING( clientmachine, (const char*), false );
    WRITE_WITH_DEFAULT( types, NET::AllTypesMask );
    WRITE_FORCE_RULE( placement, Placement::policyToString );
    WRITE_SET_RULE( position, );
    WRITE_SET_RULE( size, );
    WRITE_FORCE_RULE( minsize, );
    WRITE_FORCE_RULE( maxsize, );
    WRITE_FORCE_RULE( opacityactive, );
    WRITE_FORCE_RULE( opacityinactive, );
    WRITE_FORCE_RULE( ignoreposition, );
    WRITE_SET_RULE( desktop, );
    WRITE_FORCE_RULE( type, );
    WRITE_SET_RULE( maximizevert, );
    WRITE_SET_RULE( maximizehoriz, );
    WRITE_SET_RULE( minimize, );
    WRITE_SET_RULE( shade, );
    WRITE_SET_RULE( skiptaskbar, );
    WRITE_SET_RULE( skippager, );
    WRITE_SET_RULE( above, );
    WRITE_SET_RULE( below, );
    WRITE_SET_RULE( fullscreen, );
    WRITE_SET_RULE( noborder, );
    WRITE_FORCE_RULE( fsplevel, );
    WRITE_FORCE_RULE( acceptfocus, );
    WRITE_FORCE_RULE( moveresizemode, Options::moveResizeModeToString );
    WRITE_FORCE_RULE( closeable, );
    WRITE_FORCE_RULE( strictgeometry, );
    WRITE_SET_RULE( shortcut, );
    WRITE_FORCE_RULE( disableglobalshortcuts, );
    }
    
#undef WRITE_MATCH_STRING
#undef WRITE_SET_RULE
#undef WRITE_FORCE_RULE
#undef WRITE_WITH_DEFAULT

// returns true if it doesn't affect anything
bool Rules::isEmpty() const
    {
    return( placementrule == UnusedForceRule
        && positionrule == UnusedSetRule
        && sizerule == UnusedSetRule
        && minsizerule == UnusedForceRule
        && maxsizerule == UnusedForceRule
        && opacityactiverule == UnusedForceRule
        && opacityinactiverule == UnusedForceRule
        && ignorepositionrule == UnusedForceRule
        && desktoprule == UnusedSetRule
        && typerule == UnusedForceRule
        && maximizevertrule == UnusedSetRule
        && maximizehorizrule == UnusedSetRule
        && minimizerule == UnusedSetRule
        && shaderule == UnusedSetRule
        && skiptaskbarrule == UnusedSetRule
        && skippagerrule == UnusedSetRule
        && aboverule == UnusedSetRule
        && belowrule == UnusedSetRule
        && fullscreenrule == UnusedSetRule
        && noborderrule == UnusedSetRule
        && fsplevelrule == UnusedForceRule
        && acceptfocusrule == UnusedForceRule
        && moveresizemoderule == UnusedForceRule
        && closeablerule == UnusedForceRule
        && strictgeometryrule == UnusedForceRule
        && shortcutrule == UnusedSetRule
        && disableglobalshortcutsrule == UnusedForceRule );
    }

Rules::SetRule Rules::readSetRule( TDEConfig& cfg, const TQString& key )
    {
    int v = cfg.readNumEntry( key );
    if( v >= DontAffect && v <= ForceTemporarily )
        return static_cast< SetRule >( v );
    return UnusedSetRule;
    }

Rules::ForceRule Rules::readForceRule( TDEConfig& cfg, const TQString& key )
    {
    int v = cfg.readNumEntry( key );
    if( v == DontAffect || v == Force || v == ForceTemporarily )
        return static_cast< ForceRule >( v );
    return UnusedForceRule;
    }

NET::WindowType Rules::readType( TDEConfig& cfg, const TQString& key )
    {
    int v = cfg.readNumEntry( key );
    if( v >= NET::Normal && v <= NET::Splash )
        return static_cast< NET::WindowType >( v );
    return NET::Unknown;
    }

bool Rules::matchType( NET::WindowType match_type ) const
    {
    if( types != NET::AllTypesMask )
        {
        if( match_type == NET::Unknown )
            match_type = NET::Normal; // NET::Unknown->NET::Normal is only here for matching
        if( !NET::typeMatchesMask( match_type, types ))
            return false;
        }
    return true;
    }
    
bool Rules::matchWMClass( const TQCString& match_class, const TQCString& match_name ) const
    {
    if( wmclassmatch != UnimportantMatch )
        { // TODO optimize?
        TQCString cwmclass = wmclasscomplete
            ? match_name + ' ' + match_class : match_class;
        if( wmclassmatch == RegExpMatch && TQRegExp( wmclass ).search( cwmclass ) == -1 )
            return false;
        if( wmclassmatch == ExactMatch && wmclass != cwmclass )
            return false;
        if( wmclassmatch == SubstringMatch && !cwmclass.contains( wmclass ))
            return false;
        }
    return true;
    }
    
bool Rules::matchRole( const TQCString& match_role ) const
    {
    if( windowrolematch != UnimportantMatch )
        {
        if( windowrolematch == RegExpMatch && TQRegExp( windowrole ).search( match_role ) == -1 )
            return false;
        if( windowrolematch == ExactMatch && windowrole != match_role )
            return false;
        if( windowrolematch == SubstringMatch && !match_role.contains( windowrole ))
            return false;
        }
    return true;
    }
    
bool Rules::matchTitle( const TQString& match_title ) const
    {
    if( titlematch != UnimportantMatch )
        {
        if( titlematch == RegExpMatch && TQRegExp( title ).search( match_title ) == -1 )
            return false;
        if( titlematch == ExactMatch && title != match_title )
            return false;
        if( titlematch == SubstringMatch && !match_title.contains( title ))
            return false;
        }
    return true;
    }

bool Rules::matchClientMachine( const TQCString& match_machine ) const
    {
    if( clientmachinematch != UnimportantMatch )
        {
        // if it's localhost, check also "localhost" before checking hostname
        if( match_machine != "localhost" && isLocalMachine( match_machine )
            && matchClientMachine( "localhost" ))
            return true;
        if( clientmachinematch == RegExpMatch
            && TQRegExp( clientmachine ).search( match_machine ) == -1 )
            return false;
        if( clientmachinematch == ExactMatch
            && clientmachine != match_machine )
            return false;
        if( clientmachinematch == SubstringMatch
            && !match_machine.contains( clientmachine ))
            return false;
        }
    return true;
    }

#ifndef KCMRULES
bool Rules::match( const Client* c ) const
    {
    if( !matchType( c->windowType( true )))
        return false;
    if( !matchWMClass( c->resourceClass(), c->resourceName()))
        return false;
    if( !matchRole( c->windowRole()))
        return false;
    if( !matchTitle( c->caption( false )))
        return false;
    // TODO extrarole
    if( !matchClientMachine( c->wmClientMachine( false )))
        return false;
    return true;
    }

bool Rules::update( Client* c )
    {
    // TODO check this setting is for this client ?
    bool updated = false;
    if( positionrule == ( SetRule )Remember)
        {
        if( !c->isFullScreen())
            {
            TQPoint new_pos = position;
            // don't use the position in the direction which is maximized
            if(( c->maximizeMode() & MaximizeHorizontal ) == 0 )
                new_pos.setX( c->pos().x());
            if(( c->maximizeMode() & MaximizeVertical ) == 0 )
                new_pos.setY( c->pos().y());
            updated = updated || position != new_pos;
            position = new_pos;
            }
        }
    if( sizerule == ( SetRule )Remember)
        {
        if( !c->isFullScreen())
            {
            TQSize new_size = size;
            // don't use the position in the direction which is maximized
            if(( c->maximizeMode() & MaximizeHorizontal ) == 0 )
                new_size.setWidth( c->size().width());
            if(( c->maximizeMode() & MaximizeVertical ) == 0 )
                new_size.setHeight( c->size().height());
            updated = updated || size != new_size;
            size = new_size;
            }
        }
    if( desktoprule == ( SetRule )Remember)
        {
        updated = updated || desktop != c->desktop();
        desktop = c->desktop();
        }
    if( maximizevertrule == ( SetRule )Remember)
        {
        updated = updated || maximizevert != bool( c->maximizeMode() & MaximizeVertical );
        maximizevert = c->maximizeMode() & MaximizeVertical;
        }
    if( maximizehorizrule == ( SetRule )Remember)
        {
        updated = updated || maximizehoriz != bool( c->maximizeMode() & MaximizeHorizontal );
        maximizehoriz = c->maximizeMode() & MaximizeHorizontal;
        }
    if( minimizerule == ( SetRule )Remember)
        {
        updated = updated || minimize != c->isMinimized();
        minimize = c->isMinimized();
        }
    if( shaderule == ( SetRule )Remember)
        {
        updated = updated || ( shade != ( c->shadeMode() != ShadeNone ));
        shade = c->shadeMode() != ShadeNone;
        }
    if( skiptaskbarrule == ( SetRule )Remember)
        {
        updated = updated || skiptaskbar != c->skipTaskbar();
        skiptaskbar = c->skipTaskbar();
        }
    if( skippagerrule == ( SetRule )Remember)
        {
        updated = updated || skippager != c->skipPager();
        skippager = c->skipPager();
        }
    if( aboverule == ( SetRule )Remember)
        {
        updated = updated || above != c->keepAbove();
        above = c->keepAbove();
        }
    if( belowrule == ( SetRule )Remember)
        {
        updated = updated || below != c->keepBelow();
        below = c->keepBelow();
        }
    if( fullscreenrule == ( SetRule )Remember)
        {
        updated = updated || fullscreen != c->isFullScreen();
        fullscreen = c->isFullScreen();
        }
    if( noborderrule == ( SetRule )Remember)
        {
        updated = updated || noborder != c->isUserNoBorder();
        noborder = c->isUserNoBorder();
        }
    if (opacityactiverule == ( ForceRule )Force)
        {
        updated = updated || (uint) (opacityactive/100.0*0xffffffff) != c->ruleOpacityActive();
        opacityactive = (uint)(((double)c->ruleOpacityActive())/0xffffffff*100);
        }
    if (opacityinactiverule == ( ForceRule )Force)
        {
        updated = updated || (uint) (opacityinactive/100.0*0xffffffff) != c->ruleOpacityInactive();
        opacityinactive = (uint)(((double)c->ruleOpacityInactive())/0xffffffff*100);
        }
    return updated;
    }

#define APPLY_RULE( var, name, type ) \
bool Rules::apply##name( type& arg, bool init ) const \
    { \
    if( checkSetRule( var##rule, init )) \
        arg = this->var; \
    return checkSetStop( var##rule ); \
    }

#define APPLY_FORCE_RULE( var, name, type ) \
bool Rules::apply##name( type& arg ) const \
    { \
    if( checkForceRule( var##rule )) \
        arg = this->var; \
    return checkForceStop( var##rule ); \
    }

APPLY_FORCE_RULE( placement, Placement, Placement::Policy )

bool Rules::applyGeometry( TQRect& rect, bool init ) const
    {
    TQPoint p = rect.topLeft();
    TQSize s = rect.size();
    bool ret = false; // no short-circuiting
    if( applyPosition( p, init ))
        {
        rect.moveTopLeft( p );
        ret = true;
        }
    if( applySize( s, init ))
        {
        rect.setSize( s );
        ret = true;
        }
    return ret;
    }

bool Rules::applyPosition( TQPoint& pos, bool init ) const
    {
    if( this->position != invalidPoint && checkSetRule( positionrule, init ))
        pos = this->position;
    return checkSetStop( positionrule );
    }

bool Rules::applySize( TQSize& s, bool init ) const
    {
    if( this->size.isValid() && checkSetRule( sizerule, init ))
        s = this->size;
    return checkSetStop( sizerule );
    }

APPLY_FORCE_RULE( minsize, MinSize, TQSize )
APPLY_FORCE_RULE( maxsize, MaxSize, TQSize )
APPLY_FORCE_RULE( opacityactive, OpacityActive, int )
APPLY_FORCE_RULE( opacityinactive, OpacityInactive, int )
APPLY_FORCE_RULE( ignoreposition, IgnorePosition, bool )

// the cfg. entry needs to stay named the say for backwards compatibility
bool Rules::applyIgnoreGeometry( bool& ignore ) const
    {
    return applyIgnorePosition( ignore );
    }

APPLY_RULE( desktop, Desktop, int )
APPLY_FORCE_RULE( type, Type, NET::WindowType )

bool Rules::applyMaximizeHoriz( MaximizeMode& mode, bool init ) const
    {
    if( checkSetRule( maximizehorizrule, init ))
        mode = static_cast< MaximizeMode >(( maximizehoriz ? MaximizeHorizontal : 0 ) | ( mode & MaximizeVertical ));
    return checkSetStop( maximizehorizrule );
    }

bool Rules::applyMaximizeVert( MaximizeMode& mode, bool init ) const
    {
    if( checkSetRule( maximizevertrule, init ))
        mode = static_cast< MaximizeMode >(( maximizevert ? MaximizeVertical : 0 ) | ( mode & MaximizeHorizontal ));
    return checkSetStop( maximizevertrule );
    }

APPLY_RULE( minimize, Minimize, bool )

bool Rules::applyShade( ShadeMode& sh, bool init ) const
    {
    if( checkSetRule( shaderule, init ))
        {
        if( !this->shade )
            sh = ShadeNone;
        if( this->shade && sh == ShadeNone )
            sh = ShadeNormal;
        }
    return checkSetStop( shaderule );
    }

APPLY_RULE( skiptaskbar, SkipTaskbar, bool )
APPLY_RULE( skippager, SkipPager, bool )
APPLY_RULE( above, KeepAbove, bool )
APPLY_RULE( below, KeepBelow, bool )
APPLY_RULE( fullscreen, FullScreen, bool )
APPLY_RULE( noborder, NoBorder, bool )
APPLY_FORCE_RULE( fsplevel, FSP, int )
APPLY_FORCE_RULE( acceptfocus, AcceptFocus, bool )
APPLY_FORCE_RULE( moveresizemode, MoveResizeMode, Options::MoveResizeMode )
APPLY_FORCE_RULE( closeable, Closeable, bool )
APPLY_FORCE_RULE( strictgeometry, StrictGeometry, bool )
APPLY_RULE( shortcut, Shortcut, TQString )
APPLY_FORCE_RULE( disableglobalshortcuts, DisableGlobalShortcuts, bool )


#undef APPLY_RULE
#undef APPLY_FORCE_RULE

bool Rules::isTemporary() const
    {
    return temporary_state > 0;
    }

bool Rules::discardTemporary( bool force )
    {
    if( temporary_state == 0 ) // not temporary
        return false;
    if( force || --temporary_state == 0 ) // too old
        {
        delete this;
        return true;
        }
    return false;
    }
    
#define DISCARD_USED_SET_RULE( var ) \
    do { \
    if( var##rule == ( SetRule ) ApplyNow || ( withdrawn && var##rule == ( SetRule ) ForceTemporarily )) \
        var##rule = UnusedSetRule; \
    } while( false )
#define DISCARD_USED_FORCE_RULE( var ) \
    do { \
    if( withdrawn && var##rule == ( ForceRule ) ForceTemporarily ) \
        var##rule = UnusedForceRule; \
    } while( false )

void Rules::discardUsed( bool withdrawn )
    {
    DISCARD_USED_FORCE_RULE( placement );
    DISCARD_USED_SET_RULE( position );
    DISCARD_USED_SET_RULE( size );
    DISCARD_USED_FORCE_RULE( minsize );
    DISCARD_USED_FORCE_RULE( maxsize );
    DISCARD_USED_FORCE_RULE( opacityactive );
    DISCARD_USED_FORCE_RULE( opacityinactive );
    DISCARD_USED_FORCE_RULE( ignoreposition );
    DISCARD_USED_SET_RULE( desktop );
    DISCARD_USED_FORCE_RULE( type );
    DISCARD_USED_SET_RULE( maximizevert );
    DISCARD_USED_SET_RULE( maximizehoriz );
    DISCARD_USED_SET_RULE( minimize );
    DISCARD_USED_SET_RULE( shade );
    DISCARD_USED_SET_RULE( skiptaskbar );
    DISCARD_USED_SET_RULE( skippager );
    DISCARD_USED_SET_RULE( above );
    DISCARD_USED_SET_RULE( below );
    DISCARD_USED_SET_RULE( fullscreen );
    DISCARD_USED_SET_RULE( noborder );
    DISCARD_USED_FORCE_RULE( fsplevel );
    DISCARD_USED_FORCE_RULE( acceptfocus );
    DISCARD_USED_FORCE_RULE( moveresizemode );
    DISCARD_USED_FORCE_RULE( closeable );
    DISCARD_USED_FORCE_RULE( strictgeometry );
    DISCARD_USED_SET_RULE( shortcut );
    DISCARD_USED_FORCE_RULE( disableglobalshortcuts );
    }
#undef DISCARD_USED_SET_RULE
#undef DISCARD_USED_FORCE_RULE

#endif

#ifndef NDEBUG
kdbgstream& operator<<( kdbgstream& stream, const Rules* r )
    {
    return stream << "[" << r->description << ":" << r->wmclass << "]" ;
    }
#endif

#ifndef KCMRULES
void WindowRules::discardTemporary()
    {
    TQValueVector< Rules* >::Iterator it2 = rules.begin();
    for( TQValueVector< Rules* >::Iterator it = rules.begin();
         it != rules.end();
         )
        {
        if( (*it)->discardTemporary( true ))
            ++it;
        else
            {
            *it2++ = *it++;
            }
        }
    rules.erase( it2, rules.end());
    }

void WindowRules::update( Client* c )
    {
    bool updated = false;
    for( TQValueVector< Rules* >::ConstIterator it = rules.begin();
         it != rules.end();
         ++it )
        if( (*it)->update( c )) // no short-circuiting here
            updated = true;
    if( updated )
        Workspace::self()->rulesUpdated();
    }

#define CHECK_RULE( rule, type ) \
type WindowRules::check##rule( type arg, bool init ) const \
    { \
    if( rules.count() == 0 ) \
        return arg; \
    type ret = arg; \
    for( TQValueVector< Rules* >::ConstIterator it = rules.begin(); \
         it != rules.end(); \
         ++it ) \
        { \
        if( (*it)->apply##rule( ret, init )) \
            break; \
        } \
    return ret; \
    }

#define CHECK_FORCE_RULE( rule, type ) \
type WindowRules::check##rule( type arg ) const \
    { \
    if( rules.count() == 0 ) \
        return arg; \
    type ret = arg; \
    for( TQValueVector< Rules* >::ConstIterator it = rules.begin(); \
         it != rules.end(); \
         ++it ) \
        { \
        if( (*it)->apply##rule( ret )) \
            break; \
        } \
    return ret; \
    }

CHECK_FORCE_RULE( Placement, Placement::Policy )

TQRect WindowRules::checkGeometry( TQRect rect, bool init ) const
    {
    return TQRect( checkPosition( rect.topLeft(), init ), checkSize( rect.size(), init ));
    }

CHECK_RULE( Position, TQPoint )
CHECK_RULE( Size, TQSize )
CHECK_FORCE_RULE( MinSize, TQSize )
CHECK_FORCE_RULE( MaxSize, TQSize )
CHECK_FORCE_RULE( OpacityActive, int )
CHECK_FORCE_RULE( OpacityInactive, int )
CHECK_FORCE_RULE( IgnorePosition, bool )

bool WindowRules::checkIgnoreGeometry( bool ignore ) const
    {
    return checkIgnorePosition( ignore );
    }

CHECK_RULE( Desktop, int )
CHECK_FORCE_RULE( Type, NET::WindowType )
CHECK_RULE( MaximizeVert, KDecorationDefines::MaximizeMode )
CHECK_RULE( MaximizeHoriz, KDecorationDefines::MaximizeMode )

KDecorationDefines::MaximizeMode WindowRules::checkMaximize( MaximizeMode mode, bool init ) const
    {
    bool vert = checkMaximizeVert( mode, init ) & MaximizeVertical;
    bool horiz = checkMaximizeHoriz( mode, init ) & MaximizeHorizontal;
    return static_cast< MaximizeMode >(( vert ? MaximizeVertical : 0 ) | ( horiz ? MaximizeHorizontal : 0 ));
    }

CHECK_RULE( Minimize, bool )
CHECK_RULE( Shade, ShadeMode )
CHECK_RULE( SkipTaskbar, bool )
CHECK_RULE( SkipPager, bool )
CHECK_RULE( KeepAbove, bool )
CHECK_RULE( KeepBelow, bool )
CHECK_RULE( FullScreen, bool )
CHECK_RULE( NoBorder, bool )
CHECK_FORCE_RULE( FSP, int )
CHECK_FORCE_RULE( AcceptFocus, bool )
CHECK_FORCE_RULE( MoveResizeMode, Options::MoveResizeMode )
CHECK_FORCE_RULE( Closeable, bool )
CHECK_FORCE_RULE( StrictGeometry, bool )
CHECK_RULE( Shortcut, TQString )
CHECK_FORCE_RULE( DisableGlobalShortcuts, bool )

#undef CHECK_RULE
#undef CHECK_FORCE_RULE

// Client

void Client::setupWindowRules( bool ignore_temporary )
    {
    client_rules = workspace()->findWindowRules( this, ignore_temporary );
    // check only after getting the rules, because there may be a rule forcing window type
    if( isTopMenu()) // TODO cannot have restrictions
        client_rules = WindowRules();
    }

// Applies Force, ForceTemporarily and ApplyNow rules
// Used e.g. after the rules have been modified using the kcm.    
void Client::applyWindowRules()
    {
    checkAndSetInitialRuledOpacity();        
    // apply force rules
    // Placement - does need explicit update, just like some others below
    // Geometry : setGeometry() doesn't check rules
    TQRect orig_geom = TQRect( pos(), sizeForClientSize( clientSize())); // handle shading
    TQRect geom = client_rules.checkGeometry( orig_geom );
    if( geom != orig_geom )
        setGeometry( geom );
    // MinSize, MaxSize handled by Geometry
    // IgnorePosition
    setDesktop( desktop());
    // Type
    maximize( maximizeMode());
    // Minimize : functions don't check, and there are two functions
    if( client_rules.checkMinimize( isMinimized()))
        minimize();
    else
        unminimize();
    setShade( shadeMode());
    setSkipTaskbar( skipTaskbar(), true );
    setSkipPager( skipPager());
    setKeepAbove( keepAbove());
    setKeepBelow( keepBelow());
    setFullScreen( isFullScreen(), true );
    setUserNoBorder( isUserNoBorder());
    // FSP
    // AcceptFocus :
    if( workspace()->mostRecentlyActivatedClient() == this
        && !client_rules.checkAcceptFocus( true ))
        workspace()->activateNextClient( this );
    // MoveResizeMode
    // Closeable
    TQSize s = adjustedSize();
    if( s != size())
        resizeWithChecks( s );
    // StrictGeometry
    setShortcut( rules()->checkShortcut( shortcut().toString()));
    // see also Client::setActive()
    if( isActive())
        workspace()->disableGlobalShortcutsForClient( rules()->checkDisableGlobalShortcuts( false ));
    }

void Client::updateWindowRules()
    {
    if( !isManaged()) // not fully setup yet
        return;
    if( workspace()->rulesUpdatesDisabled())
        return;
    client_rules.update( this );
    }

void Client::finishWindowRules()
    {
    updateWindowRules();
    client_rules = WindowRules();
    }
    
void Client::checkAndSetInitialRuledOpacity()
//apply twin-rules for window-translucency upon hitting apply or starting to manage client
    {
    int tmp;
    
    //active translucency
    tmp = -1;
    tmp = rules()->checkOpacityActive(tmp);
    if( tmp != -1 ) //rule did apply and returns valid value
        {
        rule_opacity_active = (uint)((tmp/100.0)*0xffffffff);
        }
    else
        rule_opacity_active = 0;

    //inactive translucency
    tmp = -1;
    tmp = rules()->checkOpacityInactive(tmp);
    if( tmp != -1 ) //rule did apply and returns valid value
        {
        rule_opacity_inactive = (uint)((tmp/100.0)*0xffffffff);
        }
    else
        rule_opacity_inactive = 0;

    return;
        
    if( isDock() )
     //workaround for docks, as they don't have active/inactive settings and don't aut, therefore we take only the active one...
        {
        uint tmp = rule_opacity_active ? rule_opacity_active : options->dockOpacity;
        setOpacity(tmp < 0xFFFFFFFF && (rule_opacity_active || options->translucentDocks), tmp);
        }
    else
        updateOpacity();
    }

// Workspace

WindowRules Workspace::findWindowRules( const Client* c, bool ignore_temporary )
    {
    TQValueVector< Rules* > ret;
    for( TQValueList< Rules* >::Iterator it = rules.begin();
         it != rules.end();
         )
        {
        if( ignore_temporary && (*it)->isTemporary())
            {
            ++it;
            continue;
            }
        if( (*it)->match( c ))
            {
            Rules* rule = *it;
            kdDebug( 1212 ) << "Rule found:" << rule << ":" << c << endl;
            if( rule->isTemporary())
                it = rules.remove( it );
            else
                ++it;
            ret.append( rule );
            continue;
            }
        ++it;
        }
    return WindowRules( ret );
    }

void Workspace::editWindowRules( Client* c, bool whole_app )
    {
    writeWindowRules();
    TQStringList args;
    args << "--wid" << TQString::number( c->window());
    if( whole_app )
        args << "--whole-app";
    TDEApplication::tdeinitExec( "twin_rules_dialog", args );
    }

void Workspace::loadWindowRules()
    {
    while( !rules.isEmpty())
        {
        delete rules.front();
        rules.pop_front();
        }
    TDEConfig cfg( "twinrulesrc", true );
    cfg.setGroup( "General" );
    int count = cfg.readNumEntry( "count" );
    for( int i = 1;
         i <= count;
         ++i )
        {
        cfg.setGroup( TQString::number( i ));
        Rules* rule = new Rules( cfg );
        rules.append( rule );
        }
    }

void Workspace::writeWindowRules()
    {
    rulesUpdatedTimer.stop();
    TDEConfig cfg( "twinrulesrc" );
    TQStringList groups = cfg.groupList();
    for( TQStringList::ConstIterator it = groups.begin();
         it != groups.end();
         ++it )
        cfg.deleteGroup( *it );
    cfg.setGroup( "General" );
    cfg.writeEntry( "count", rules.count());
    int i = 1;
    for( TQValueList< Rules* >::ConstIterator it = rules.begin();
         it != rules.end();
         ++it )
        {
        if( (*it)->isTemporary())
            continue;
        cfg.setGroup( TQString::number( i ));
        (*it)->write( cfg );
        ++i;
        }
    }

void Workspace::gotTemporaryRulesMessage( const TQString& message )
    {
    bool was_temporary = false;
    for( TQValueList< Rules* >::ConstIterator it = rules.begin();
         it != rules.end();
         ++it )
        if( (*it)->isTemporary())
            was_temporary = true;
    Rules* rule = new Rules( message, true );
    rules.prepend( rule ); // highest priority first
    if( !was_temporary )
        TQTimer::singleShot( 60000, this, TQT_SLOT( cleanupTemporaryRules()));
    }

void Workspace::cleanupTemporaryRules()
    {
    bool has_temporary = false;
    for( TQValueList< Rules* >::Iterator it = rules.begin();
         it != rules.end();
         )
        {
        if( (*it)->discardTemporary( false ))
            it = rules.remove( it );
        else
            {
            if( (*it)->isTemporary())
                has_temporary = true;
            ++it;
            }
        }
    if( has_temporary )
        TQTimer::singleShot( 60000, this, TQT_SLOT( cleanupTemporaryRules()));
    }

void Workspace::discardUsedWindowRules( Client* c, bool withdrawn )
    {
    bool updated = false;
    for( TQValueList< Rules* >::Iterator it = rules.begin();
         it != rules.end();
         )
        {
        if( c->rules()->contains( *it ))
            {
            updated = true;
            (*it)->discardUsed( withdrawn );
            if( (*it)->isEmpty())
                {
                c->removeRule( *it );
                Rules* r = *it;
                it = rules.remove( it );
                delete r;
                continue;
                }
            }
        ++it;
        }
    if( updated )
        rulesUpdated();
    }

void Workspace::rulesUpdated()
    {
    rulesUpdatedTimer.start( 1000, true );
    }

void Workspace::disableRulesUpdates( bool disable )
    {
    rules_updates_disabled = disable;
    if( !disable )
        for( ClientList::ConstIterator it = clients.begin();
             it != clients.end();
             ++it )
            (*it)->updateWindowRules();
    }

#endif

} // namespace
